Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 424d7d9caf | |||
| 6aa643837a | |||
| 32cbb33e10 | |||
| 51ae6f5978 | |||
| b64dde152a | |||
| d1b6755997 | |||
| da6e2d92e0 | |||
| 37e9c2372f | |||
| a620a358d3 | |||
| fd46fbd961 | |||
| 732f28e653 | |||
| 7671998541 | |||
| cab9674803 | |||
| 1b53410a86 | |||
| 1ba6983573 | |||
| d5a36fa5e3 | |||
| b6e47fb820 | |||
| 06e21154c4 | |||
| adc0c40ab7 | |||
| a44bb92ec4 | |||
| bd98244869 | |||
| 2acfa48b98 | |||
| a0283b6048 | |||
| 0e771a68b8 | |||
| 838f9d4969 | |||
| f98c804094 | |||
| 0382c246b8 | |||
| 0b38fb5a2a | |||
| ff686b4361 | |||
| 7857451ec9 |
@@ -1,11 +1,11 @@
|
||||
name: "Specklepy test and build"
|
||||
on:
|
||||
# pull_request:
|
||||
# branches:
|
||||
# - 'v3-dev'
|
||||
pull_request:
|
||||
branches:
|
||||
- "v3-dev"
|
||||
push:
|
||||
branches:
|
||||
- "gergo/uvSetup"
|
||||
- "v3-dev"
|
||||
jobs:
|
||||
ci:
|
||||
name: continuous-integration
|
||||
|
||||
@@ -1,36 +1,33 @@
|
||||
# Publish a release to PyPI.
|
||||
# name: 'Publish to PyPI'
|
||||
name: "Publish to PyPI"
|
||||
|
||||
# on:
|
||||
# push:
|
||||
# branches:
|
||||
# - 'gergo/uvSetup'
|
||||
on:
|
||||
workflow_run:
|
||||
workflows: ["Specklepy test and build"]
|
||||
branches: [v3-dev]
|
||||
types:
|
||||
- completed
|
||||
|
||||
# jobs:
|
||||
# pypi-publish:
|
||||
# name: Upload to PyPI
|
||||
# runs-on: ubuntu-latest
|
||||
# environment:
|
||||
# name: release
|
||||
# permissions:
|
||||
# # For PyPI's trusted publishing.
|
||||
# id-token: write
|
||||
# steps:
|
||||
# - name: 'Install uv'
|
||||
# uses: astral-sh/setup-uv@v5
|
||||
# - uses: actions/checkout@v4
|
||||
# with:
|
||||
# # This is necessary so that we have the tags.
|
||||
# fetch-depth: 0
|
||||
# - uses: mtkennerly/dunamai-action@v1
|
||||
# with:
|
||||
# env-var: MY_VERSION
|
||||
# args: --style semver
|
||||
# - run: echo $MY_VERSION
|
||||
# - name: 'Build artifacts'
|
||||
# run: uv build
|
||||
# - name: Publish to PyPi
|
||||
# run: uv publish --publish-url https://test.pypi.org/simple/
|
||||
jobs:
|
||||
pypi-publish:
|
||||
name: Upload to PyPI
|
||||
runs-on: ubuntu-latest
|
||||
environment:
|
||||
name: testpypi
|
||||
permissions:
|
||||
# For PyPI's trusted publishing.
|
||||
id-token: write
|
||||
steps:
|
||||
- name: "Install uv"
|
||||
uses: astral-sh/setup-uv@v5
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
# This is necessary so that we have the tags.
|
||||
fetch-depth: 0
|
||||
- name: "Build artifacts"
|
||||
run: uv build
|
||||
- name: Publish to PyPi
|
||||
run: uv publish --index test
|
||||
|
||||
# - name: Test package install
|
||||
# run: uv run --with specklepy --no-project -- python -c "import specklepy"
|
||||
- name: Test package install
|
||||
run: uv run --index test --with specklepy --no-project -- python -c "import specklepy"
|
||||
|
||||
+2
-5
@@ -6,7 +6,7 @@ services:
|
||||
# Speckle Server dependencies
|
||||
#######
|
||||
postgres:
|
||||
image: "postgres:16-alpine"
|
||||
image: "postgres:16.4-alpine3.20@sha256:d898b0b78a2627cb4ee63464a14efc9d296884f1b28c841b0ab7d7c42f1fffdf"
|
||||
restart: always
|
||||
environment:
|
||||
POSTGRES_DB: speckle
|
||||
@@ -49,10 +49,6 @@ services:
|
||||
retries: 30
|
||||
start_period: 10s
|
||||
|
||||
####
|
||||
# Speckle Server
|
||||
#######
|
||||
|
||||
speckle-server:
|
||||
image: speckle/speckle-server:latest
|
||||
restart: always
|
||||
@@ -79,6 +75,7 @@ services:
|
||||
# TODO: Change this to the URL of the speckle server, as accessed from the network
|
||||
CANONICAL_URL: "http://127.0.0.1:8080"
|
||||
SPECKLE_AUTOMATE_URL: "http://127.0.0.1:3030"
|
||||
FRONTEND_ORIGIN: "http://127.0.0.1:8081"
|
||||
|
||||
# TODO: Change thvolumes:
|
||||
REDIS_URL: "redis://redis"
|
||||
|
||||
+52
-35
@@ -1,5 +1,6 @@
|
||||
[project]
|
||||
dynamic = ["version"]
|
||||
# version = "3.0.0a1"
|
||||
name = "specklepy"
|
||||
description = "The Python SDK for Speckle 2.0"
|
||||
readme = "README.md"
|
||||
@@ -7,30 +8,32 @@ authors = [{ name = "Speckle Systems", email = "devops@speckle.systems" }]
|
||||
license = { text = "Apache-2.0" }
|
||||
requires-python = ">=3.10.0, <4.0"
|
||||
dependencies = [
|
||||
"appdirs>=1.4.4",
|
||||
"attrs>=24.3.0",
|
||||
"deprecated>=1.2.15",
|
||||
"gql[requests,websockets]>=3.5.0",
|
||||
"httpx>=0.28.1",
|
||||
"pydantic>=2.10.5",
|
||||
"pydantic-settings>=2.7.1",
|
||||
"stringcase>=1.2.0",
|
||||
"ujson>=5.10.0",
|
||||
"appdirs>=1.4.4",
|
||||
"attrs>=24.3.0",
|
||||
"deprecated>=1.2.15",
|
||||
"gql[requests,websockets]>=3.5.0",
|
||||
"httpx>=0.28.1",
|
||||
"pydantic>=2.10.5",
|
||||
"pydantic-settings>=2.7.1",
|
||||
"stringcase>=1.2.0",
|
||||
"ujson>=5.10.0",
|
||||
]
|
||||
|
||||
[dependency-groups]
|
||||
dev = [
|
||||
"commitizen>=4.1.0",
|
||||
"devtools>=0.12.2",
|
||||
"pre-commit>=4.0.1",
|
||||
"pytest>=8.3.4",
|
||||
"pytest-asyncio>=0.25.2",
|
||||
"pytest-cov>=6.0.0",
|
||||
"pytest-ordering>=0.6",
|
||||
"ruff>=0.9.2",
|
||||
"types-deprecated>=1.2.15.20241117",
|
||||
"types-requests>=2.32.0.20241016",
|
||||
"types-ujson>=5.10.0.20240515",
|
||||
"commitizen>=4.1.0",
|
||||
"devtools>=0.12.2",
|
||||
"hatch>=1.14.0",
|
||||
"hatch-vcs>=0.4.0",
|
||||
"pre-commit>=4.0.1",
|
||||
"pytest>=8.3.4",
|
||||
"pytest-asyncio>=0.25.2",
|
||||
"pytest-cov>=6.0.0",
|
||||
"pytest-ordering>=0.6",
|
||||
"ruff>=0.9.2",
|
||||
"types-deprecated>=1.2.15.20241117",
|
||||
"types-requests>=2.32.0.20241016",
|
||||
"types-ujson>=5.10.0.20240515",
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
@@ -39,10 +42,14 @@ documentation = "https://speckle.guide/dev/py-examples.html"
|
||||
homepage = "https://speckle.systems/"
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=64", "setuptools-scm>=8"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
requires = ["hatchling", "hatch-vcs"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.setuptools_scm]
|
||||
[tool.hatch.version]
|
||||
source = "vcs"
|
||||
|
||||
[tool.hatch.version.raw-options]
|
||||
local_scheme = "no-local-version"
|
||||
|
||||
[tool.commitizen]
|
||||
name = "cz_conventional_commits"
|
||||
@@ -54,17 +61,27 @@ exclude = [".venv", "**/*.yml"]
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = [
|
||||
# pycodestyle
|
||||
"E",
|
||||
# Pyflakes
|
||||
"F",
|
||||
# pyupgrade
|
||||
"UP",
|
||||
# flake8-bugbear
|
||||
"B",
|
||||
# flake8-simplify
|
||||
"SIM",
|
||||
# isort
|
||||
"I",
|
||||
# pycodestyle
|
||||
"E",
|
||||
# Pyflakes
|
||||
"F",
|
||||
# pyupgrade
|
||||
"UP",
|
||||
# flake8-bugbear
|
||||
"B",
|
||||
# flake8-simplify
|
||||
"SIM",
|
||||
# isort
|
||||
"I",
|
||||
]
|
||||
ignore = ["UP006", "UP007", "UP035"]
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "pypi"
|
||||
url = "https://pypi.org/simple/"
|
||||
publish-url = "https://pypi.org/legacy/"
|
||||
|
||||
[[tool.uv.index]]
|
||||
name = "test"
|
||||
url = "https://test.pypi.org/simple/"
|
||||
publish-url = "https://test.pypi.org/legacy/"
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
# ignoring "line too long" check from linter
|
||||
# ruff: noqa: E501
|
||||
"""This module provides an abstraction layer above the Speckle Automate runtime."""
|
||||
|
||||
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
|
||||
@@ -18,7 +20,9 @@ from speckle_automate.schema import (
|
||||
)
|
||||
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
|
||||
@@ -99,22 +103,23 @@ class AutomationContext:
|
||||
# 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",
|
||||
@@ -122,45 +127,48 @@ class AutomationContext:
|
||||
)
|
||||
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,
|
||||
@@ -168,19 +176,17 @@ class AutomationContext:
|
||||
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]:
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import contextlib
|
||||
|
||||
from deprecated import deprecated
|
||||
|
||||
from specklepy.api.credentials import Account
|
||||
from specklepy.api.resources import (
|
||||
ActiveUserResource,
|
||||
@@ -12,12 +10,6 @@ from specklepy.api.resources import (
|
||||
ServerResource,
|
||||
SubscriptionResource,
|
||||
VersionResource,
|
||||
branch,
|
||||
commit,
|
||||
object,
|
||||
stream,
|
||||
subscriptions,
|
||||
user,
|
||||
)
|
||||
from specklepy.core.api.client import SpeckleClient as CoreSpeckleClient
|
||||
from specklepy.logging import metrics
|
||||
@@ -36,6 +28,7 @@ class SpeckleClient(CoreSpeckleClient):
|
||||
|
||||
```py
|
||||
from specklepy.api.client import SpeckleClient
|
||||
from specklepy.core.api.inputs.project_inputs import ProjectCreateInput
|
||||
from specklepy.api.credentials import get_default_account
|
||||
|
||||
# initialise the client
|
||||
@@ -47,11 +40,12 @@ class SpeckleClient(CoreSpeckleClient):
|
||||
account = get_default_account()
|
||||
client.authenticate_with_account(account)
|
||||
|
||||
# create a new stream. this returns the stream id
|
||||
new_stream_id = client.stream.create(name="a shiny new stream")
|
||||
# create a new project
|
||||
input = ProjectCreateInput(name="a shiny new project")
|
||||
project = self.project.create(input)
|
||||
|
||||
# use that stream id to get the stream from the server
|
||||
new_stream = client.stream.get(id=new_stream_id)
|
||||
# or, use a project id to get an existing project from the server
|
||||
new_stream = client.project.get("abcdefghij")
|
||||
```
|
||||
"""
|
||||
|
||||
@@ -123,53 +117,6 @@ class SpeckleClient(CoreSpeckleClient):
|
||||
client=self.wsclient,
|
||||
# todo: why doesn't this take a server version
|
||||
)
|
||||
# Deprecated Resources
|
||||
self.user = user.Resource(
|
||||
account=self.account,
|
||||
basepath=self.url,
|
||||
client=self.httpclient,
|
||||
server_version=server_version,
|
||||
)
|
||||
self.stream = stream.Resource(
|
||||
account=self.account,
|
||||
basepath=self.url,
|
||||
client=self.httpclient,
|
||||
server_version=server_version,
|
||||
)
|
||||
self.commit = commit.Resource(
|
||||
account=self.account, basepath=self.url, client=self.httpclient
|
||||
)
|
||||
self.branch = branch.Resource(
|
||||
account=self.account, basepath=self.url, client=self.httpclient
|
||||
)
|
||||
self.object = object.Resource(
|
||||
account=self.account, basepath=self.url, client=self.httpclient
|
||||
)
|
||||
self.subscribe = subscriptions.Resource(
|
||||
account=self.account,
|
||||
basepath=self.ws_url,
|
||||
client=self.wsclient,
|
||||
)
|
||||
|
||||
@deprecated(
|
||||
version="2.6.0",
|
||||
reason=(
|
||||
"Renamed: please use `authenticate_with_account` or"
|
||||
" `authenticate_with_token` instead."
|
||||
),
|
||||
)
|
||||
def authenticate(self, token: str) -> None:
|
||||
"""Authenticate the client using a personal access token
|
||||
The token is saved in the client object and a synchronous GraphQL
|
||||
entrypoint is created
|
||||
|
||||
Arguments:
|
||||
token {str} -- an api token
|
||||
"""
|
||||
metrics.track(
|
||||
metrics.SDK, self.account, {"name": "Client Authenticate_deprecated"}
|
||||
)
|
||||
return super().authenticate(token)
|
||||
|
||||
def authenticate_with_token(self, token: str) -> None:
|
||||
"""
|
||||
|
||||
@@ -1,35 +1,15 @@
|
||||
# following imports seem to be unnecessary, but they need to stay
|
||||
# to not break the scripts using these functions as non-core
|
||||
from specklepy.core.api.models import (
|
||||
Activity,
|
||||
ActivityCollection,
|
||||
Branch,
|
||||
Branches,
|
||||
Collaborator,
|
||||
Commit,
|
||||
Commits,
|
||||
LimitedUser,
|
||||
Object,
|
||||
PendingStreamCollaborator,
|
||||
ServerInfo,
|
||||
Stream,
|
||||
Streams,
|
||||
User,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"Activity",
|
||||
"ActivityCollection",
|
||||
"Branch",
|
||||
"Branches",
|
||||
"Collaborator",
|
||||
"Commit",
|
||||
"Commits",
|
||||
"LimitedUser",
|
||||
"Object",
|
||||
"PendingStreamCollaborator",
|
||||
"ServerInfo",
|
||||
"Stream",
|
||||
"Streams",
|
||||
"User",
|
||||
]
|
||||
|
||||
@@ -8,17 +8,6 @@ from specklepy.api.resources.current.project_resource import ProjectResource
|
||||
from specklepy.api.resources.current.server_resource import ServerResource
|
||||
from specklepy.api.resources.current.subscription_resource import SubscriptionResource
|
||||
from specklepy.api.resources.current.version_resource import VersionResource
|
||||
from specklepy.api.resources.deprecated import (
|
||||
active_user,
|
||||
branch,
|
||||
commit,
|
||||
object,
|
||||
other_user,
|
||||
server,
|
||||
stream,
|
||||
subscriptions,
|
||||
user,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"ActiveUserResource",
|
||||
@@ -29,13 +18,4 @@ __all__ = [
|
||||
"ServerResource",
|
||||
"SubscriptionResource",
|
||||
"VersionResource",
|
||||
"active_user",
|
||||
"branch",
|
||||
"commit",
|
||||
"object",
|
||||
"other_user",
|
||||
"server",
|
||||
"stream",
|
||||
"subscriptions",
|
||||
"user",
|
||||
]
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
from datetime import datetime
|
||||
from typing import List, Optional, overload
|
||||
|
||||
from deprecated import deprecated
|
||||
from typing import List, Optional
|
||||
|
||||
from specklepy.core.api.inputs.user_inputs import UserProjectsFilter, UserUpdateInput
|
||||
from specklepy.core.api.models import (
|
||||
@@ -10,10 +7,6 @@ from specklepy.core.api.models import (
|
||||
ResourceCollection,
|
||||
User,
|
||||
)
|
||||
from specklepy.core.api.models.deprecated import (
|
||||
FE1_DEPRECATION_REASON,
|
||||
FE1_DEPRECATION_VERSION,
|
||||
)
|
||||
from specklepy.core.api.resources import ActiveUserResource as CoreResource
|
||||
from specklepy.logging import metrics
|
||||
|
||||
@@ -35,40 +28,13 @@ class ActiveUserResource(CoreResource):
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Active User Get"})
|
||||
return super().get()
|
||||
|
||||
@deprecated("Use UserUpdateInput overload", version=FE1_DEPRECATION_VERSION)
|
||||
@overload
|
||||
def update(
|
||||
self,
|
||||
name: Optional[str] = None,
|
||||
company: Optional[str] = None,
|
||||
bio: Optional[str] = None,
|
||||
avatar: Optional[str] = None,
|
||||
) -> User: ...
|
||||
|
||||
@overload
|
||||
def update(self, *, input: UserUpdateInput) -> User: ...
|
||||
|
||||
def update(
|
||||
self,
|
||||
name: Optional[str] = None,
|
||||
company: Optional[str] = None,
|
||||
bio: Optional[str] = None,
|
||||
avatar: Optional[str] = None,
|
||||
*,
|
||||
input: Optional[UserUpdateInput] = None,
|
||||
input: UserUpdateInput,
|
||||
) -> User:
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Active User Update"})
|
||||
if isinstance(input, UserUpdateInput):
|
||||
return super()._update(input=input)
|
||||
else:
|
||||
return super()._update(
|
||||
input=UserUpdateInput(
|
||||
name=name,
|
||||
company=company,
|
||||
bio=bio,
|
||||
avatar=avatar,
|
||||
)
|
||||
)
|
||||
|
||||
return super().update(input=input)
|
||||
|
||||
def get_projects(
|
||||
self,
|
||||
@@ -85,61 +51,3 @@ class ActiveUserResource(CoreResource):
|
||||
metrics.SDK, self.account, {"name": "Active User Get Project Invites"}
|
||||
)
|
||||
return super().get_project_invites()
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def activity(
|
||||
self,
|
||||
limit: int = 20,
|
||||
action_type: Optional[str] = None,
|
||||
before: Optional[datetime] = None,
|
||||
after: Optional[datetime] = None,
|
||||
cursor: Optional[datetime] = None,
|
||||
):
|
||||
"""
|
||||
Fetches collection the current authenticated user's activity
|
||||
as filtered by given parameters
|
||||
|
||||
Note: all timestamps arguments should be `datetime` of any tz as they will be
|
||||
converted to UTC ISO format strings
|
||||
|
||||
Args:
|
||||
limit (int): The maximum number of activity items to return.
|
||||
action_type (Optional[str]): Filter results to a single action type.
|
||||
before (Optional[datetime]): Latest cutoff for activity to include.
|
||||
after (Optional[datetime]): Oldest cutoff for an activity to include.
|
||||
cursor (Optional[datetime]): Timestamp cursor for pagination.
|
||||
|
||||
Returns:
|
||||
Activity collection, filtered according to the provided parameters.
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "User Active Activity"})
|
||||
return super().activity(limit, action_type, before, after, cursor)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def get_all_pending_invites(self) -> List[PendingStreamCollaborator]:
|
||||
"""Fetches all of the current user's pending stream invitations.
|
||||
|
||||
Returns:
|
||||
List[PendingStreamCollaborator]: A list of pending stream invitations.
|
||||
"""
|
||||
metrics.track(
|
||||
metrics.SDK, self.account, {"name": "User Active Invites All Get"}
|
||||
)
|
||||
return super().get_all_pending_invites()
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def get_pending_invite(
|
||||
self, stream_id: str, token: Optional[str] = None
|
||||
) -> Optional[PendingStreamCollaborator]:
|
||||
"""Fetches a specific pending invite for the current user on a given stream.
|
||||
|
||||
Args:
|
||||
stream_id (str): The ID of the stream to look for invites on.
|
||||
token (Optional[str]): The token of the invite to look for (optional).
|
||||
|
||||
Returns:
|
||||
Optional[PendingStreamCollaborator]: The invite for the given stream,
|
||||
or None if not found.
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "User Active Invite Get"})
|
||||
return super().get_pending_invite(stream_id, token)
|
||||
|
||||
@@ -1,20 +1,11 @@
|
||||
from datetime import datetime
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from deprecated import deprecated
|
||||
from typing import Optional
|
||||
|
||||
from specklepy.core.api.models import (
|
||||
ActivityCollection,
|
||||
LimitedUser,
|
||||
UserSearchResultCollection,
|
||||
)
|
||||
from specklepy.core.api.models.deprecated import (
|
||||
FE1_DEPRECATION_REASON,
|
||||
FE1_DEPRECATION_VERSION,
|
||||
)
|
||||
from specklepy.core.api.resources import OtherUserResource as CoreResource
|
||||
from specklepy.logging import metrics
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
|
||||
|
||||
class OtherUserResource(CoreResource):
|
||||
@@ -52,57 +43,3 @@ class OtherUserResource(CoreResource):
|
||||
return super().user_search(
|
||||
query, limit=limit, cursor=cursor, archived=archived, emailOnly=emailOnly
|
||||
)
|
||||
|
||||
@deprecated(reason="Use user_search instead", version=FE1_DEPRECATION_VERSION)
|
||||
def search(
|
||||
self, search_query: str, limit: int = 25
|
||||
) -> Union[List[LimitedUser], SpeckleException]:
|
||||
"""
|
||||
Searches for users by name or email.
|
||||
The search requires a minimum query length of 3 characters.
|
||||
|
||||
Args:
|
||||
search_query (str): The search string.
|
||||
limit (int): Maximum number of search results to return.
|
||||
|
||||
Returns:
|
||||
Union[List[LimitedUser], SpeckleException]:
|
||||
A list of users matching the search
|
||||
query or an exception if the query is too short.
|
||||
"""
|
||||
if len(search_query) < 3:
|
||||
return SpeckleException(
|
||||
message="User search query must be at least 3 characters."
|
||||
)
|
||||
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Other User Search"})
|
||||
return super().search(search_query, limit)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def activity(
|
||||
self,
|
||||
user_id: str,
|
||||
limit: int = 20,
|
||||
action_type: Optional[str] = None,
|
||||
before: Optional[datetime] = None,
|
||||
after: Optional[datetime] = None,
|
||||
cursor: Optional[datetime] = None,
|
||||
) -> ActivityCollection:
|
||||
"""
|
||||
Retrieves a collection of activities for a specified user,
|
||||
with optional filters for activity type, time frame, and pagination.
|
||||
|
||||
Args:
|
||||
user_id (str): The ID of the user whose activities are being requested.
|
||||
limit (int): The maximum number of activity items to return.
|
||||
action_type (Optional[str]): A specific type of activity to filter.
|
||||
before (Optional[datetime]): Latest timestamp to include activities before.
|
||||
after (Optional[datetime]): Earliest timestamp to include activities after.
|
||||
cursor (Optional[datetime]): Timestamp for pagination cursor.
|
||||
|
||||
Returns:
|
||||
ActivityCollection: A collection of user activities filtered
|
||||
according to specified criteria.
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Other User Activity"})
|
||||
return super().activity(user_id, limit, action_type, before, after, cursor)
|
||||
|
||||
@@ -42,7 +42,7 @@ class VersionResource(CoreResource):
|
||||
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)
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
from deprecated import deprecated
|
||||
|
||||
from specklepy.api.resources import ActiveUserResource
|
||||
from specklepy.core.api.models.deprecated import FE1_DEPRECATION_VERSION
|
||||
|
||||
|
||||
@deprecated(reason="Renamed to ActiveUserResource", version=FE1_DEPRECATION_VERSION)
|
||||
class Resource(ActiveUserResource):
|
||||
"""Renamed to ActiveUserResource"""
|
||||
@@ -1,108 +0,0 @@
|
||||
from typing import Optional, Union
|
||||
|
||||
from deprecated import deprecated
|
||||
|
||||
from specklepy.api.models import Branch
|
||||
from specklepy.core.api.models.deprecated import (
|
||||
FE1_DEPRECATION_REASON,
|
||||
FE1_DEPRECATION_VERSION,
|
||||
)
|
||||
from specklepy.core.api.resources.deprecated.branch import Resource as CoreResource
|
||||
from specklepy.logging import metrics
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
|
||||
|
||||
class Resource(CoreResource):
|
||||
"""API Access class for branches"""
|
||||
|
||||
def __init__(self, account, basepath, client) -> None:
|
||||
super().__init__(
|
||||
account=account,
|
||||
basepath=basepath,
|
||||
client=client,
|
||||
)
|
||||
self.schema = Branch
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def create(
|
||||
self, stream_id: str, name: str, description: str = "No description provided"
|
||||
) -> str:
|
||||
"""Create a new branch on this stream
|
||||
|
||||
Arguments:
|
||||
name {str} -- the name of the new branch
|
||||
description {str} -- a short description of the branch
|
||||
|
||||
Returns:
|
||||
id {str} -- the newly created branch's id
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Branch Create"})
|
||||
return super().create(stream_id, name, description)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def get(
|
||||
self, stream_id: str, name: str, commits_limit: int = 10
|
||||
) -> Union[Branch, None, SpeckleException]:
|
||||
"""Get a branch by name from a stream
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to get the branch from
|
||||
name {str} -- the name of the branch to get
|
||||
commits_limit {int} -- maximum number of commits to get
|
||||
|
||||
Returns:
|
||||
Branch -- the fetched branch with its latest commits
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Branch Get"})
|
||||
return super().get(stream_id, name, commits_limit)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def list(self, stream_id: str, branches_limit: int = 10, commits_limit: int = 10):
|
||||
"""Get a list of branches from a given stream
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to get the branches from
|
||||
branches_limit {int} -- maximum number of branches to get
|
||||
commits_limit {int} -- maximum number of commits to get
|
||||
|
||||
Returns:
|
||||
List[Branch] -- the branches on the stream
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Branch List"})
|
||||
return super().list(stream_id, branches_limit, commits_limit)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def update(
|
||||
self,
|
||||
stream_id: str,
|
||||
branch_id: str,
|
||||
name: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
):
|
||||
"""Update a branch
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream containing the branch to update
|
||||
branch_id {str} -- the id of the branch to update
|
||||
name {str} -- optional: the updated branch name
|
||||
description {str} -- optional: the updated branch description
|
||||
|
||||
Returns:
|
||||
bool -- True if update is successful
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Branch Update"})
|
||||
return super().update(stream_id, branch_id, name, description)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def delete(self, stream_id: str, branch_id: str):
|
||||
"""Delete a branch
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream containing the branch to delete
|
||||
branch_id {str} -- the branch to delete
|
||||
|
||||
Returns:
|
||||
bool -- True if deletion is successful
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Branch Delete"})
|
||||
return super().delete(stream_id, branch_id)
|
||||
@@ -1,134 +0,0 @@
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from deprecated import deprecated
|
||||
|
||||
from specklepy.api.models import Commit
|
||||
from specklepy.core.api.models.deprecated import (
|
||||
FE1_DEPRECATION_REASON,
|
||||
FE1_DEPRECATION_VERSION,
|
||||
)
|
||||
from specklepy.core.api.resources.deprecated.commit import Resource as CoreResource
|
||||
from specklepy.logging import metrics
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
|
||||
|
||||
class Resource(CoreResource):
|
||||
"""API Access class for commits"""
|
||||
|
||||
def __init__(self, account, basepath, client) -> None:
|
||||
super().__init__(
|
||||
account=account,
|
||||
basepath=basepath,
|
||||
client=client,
|
||||
)
|
||||
self.schema = Commit
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def get(self, stream_id: str, commit_id: str) -> Commit:
|
||||
"""
|
||||
Gets a commit given a stream and the commit id
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the stream where we can find the commit
|
||||
commit_id {str} -- the id of the commit you want to get
|
||||
|
||||
Returns:
|
||||
Commit -- the retrieved commit object
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Commit Get"})
|
||||
return super().get(stream_id, commit_id)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def list(self, stream_id: str, limit: int = 10) -> List[Commit]:
|
||||
"""
|
||||
Get a list of commits on a given stream
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the stream where the commits are
|
||||
limit {int} -- the maximum number of commits to fetch (default = 10)
|
||||
|
||||
Returns:
|
||||
List[Commit] -- a list of the most recent commit objects
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Commit List"})
|
||||
return super().list(stream_id, limit)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def create(
|
||||
self,
|
||||
stream_id: str,
|
||||
object_id: str,
|
||||
branch_name: str = "main",
|
||||
message: str = "",
|
||||
source_application: str = "python",
|
||||
parents: Optional[List[str]] = None,
|
||||
) -> Union[str, SpeckleException]:
|
||||
"""
|
||||
Creates a commit on a branch
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the stream you want to commit to
|
||||
object_id {str} -- the hash of your commit object
|
||||
branch_name {str}
|
||||
-- the name of the branch to commit to (defaults to "main")
|
||||
message {str}
|
||||
-- optional: a message to give more information about the commit
|
||||
source_application{str}
|
||||
-- optional: the application from which the commit was created
|
||||
(defaults to "python")
|
||||
parents {List[str]} -- optional: the id of the parent commits
|
||||
|
||||
Returns:
|
||||
str -- the id of the created commit
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Commit Create"})
|
||||
return super().create(
|
||||
stream_id, object_id, branch_name, message, source_application, parents
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def update(self, stream_id: str, commit_id: str, message: str) -> bool:
|
||||
"""
|
||||
Update a commit
|
||||
|
||||
Arguments:
|
||||
stream_id {str}
|
||||
-- the id of the stream that contains the commit you'd like to update
|
||||
commit_id {str} -- the id of the commit you'd like to update
|
||||
message {str} -- the updated commit message
|
||||
|
||||
Returns:
|
||||
bool -- True if the operation succeeded
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Commit Update"})
|
||||
return super().update(stream_id, commit_id, message)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def delete(self, stream_id: str, commit_id: str) -> bool:
|
||||
"""
|
||||
Delete a commit
|
||||
|
||||
Arguments:
|
||||
stream_id {str}
|
||||
-- the id of the stream that contains the commit you'd like to delete
|
||||
commit_id {str} -- the id of the commit you'd like to delete
|
||||
|
||||
Returns:
|
||||
bool -- True if the operation succeeded
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Commit Delete"})
|
||||
return super().delete(stream_id, commit_id)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def received(
|
||||
self,
|
||||
stream_id: str,
|
||||
commit_id: str,
|
||||
source_application: str = "python",
|
||||
message: Optional[str] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Mark a commit object a received by the source application.
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Commit Received"})
|
||||
return super().received(stream_id, commit_id, source_application, message)
|
||||
@@ -1,63 +0,0 @@
|
||||
from typing import Dict, List
|
||||
|
||||
from deprecated import deprecated
|
||||
|
||||
from specklepy.core.api.models.deprecated import (
|
||||
FE1_DEPRECATION_REASON,
|
||||
FE1_DEPRECATION_VERSION,
|
||||
)
|
||||
from specklepy.core.api.resources.deprecated.object import Resource as CoreResource
|
||||
from specklepy.logging import metrics
|
||||
from specklepy.objects.base import Base
|
||||
|
||||
|
||||
class Resource(CoreResource):
|
||||
"""API Access class for objects"""
|
||||
|
||||
def __init__(self, account, basepath, client) -> None:
|
||||
super().__init__(
|
||||
account=account,
|
||||
basepath=basepath,
|
||||
client=client,
|
||||
)
|
||||
self.schema = Base
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def get(self, stream_id: str, object_id: str) -> Base:
|
||||
"""
|
||||
Get a stream object
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream for the object
|
||||
object_id {str} -- the hash of the object you want to get
|
||||
|
||||
Returns:
|
||||
Base -- the returned Base object
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Object Get"})
|
||||
return super().get(stream_id, object_id)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def create(self, stream_id: str, objects: List[Dict]) -> str:
|
||||
"""
|
||||
Not advised - generally, you want to use `operations.send()`.
|
||||
|
||||
Create a new object on a stream.
|
||||
To send a base object, you can prepare it by running it through the
|
||||
`BaseObjectSerializer.traverse_base()` function to get a valid (serialisable)
|
||||
object to send.
|
||||
|
||||
NOTE: this does not create a commit - you can create one with
|
||||
`SpeckleClient.commit.create`.
|
||||
Dynamic fields will be located in the 'data' dict of the received `Base` object
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream you want to send the object to
|
||||
objects {List[Dict]}
|
||||
-- a list of base dictionary objects (NOTE: must be json serialisable)
|
||||
|
||||
Returns:
|
||||
str -- the id of the object
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Object Create"})
|
||||
return super().create(stream_id, objects)
|
||||
@@ -1,11 +0,0 @@
|
||||
from deprecated import deprecated
|
||||
|
||||
from specklepy.api.resources import OtherUserResource
|
||||
from specklepy.core.api.models.deprecated import FE1_DEPRECATION_VERSION
|
||||
|
||||
|
||||
@deprecated(reason="Renamed to OtherUserResource", version=FE1_DEPRECATION_VERSION)
|
||||
class Resource(OtherUserResource):
|
||||
"""
|
||||
Renamed to OtherUserResource
|
||||
"""
|
||||
@@ -1,9 +0,0 @@
|
||||
from deprecated import deprecated
|
||||
|
||||
from specklepy.api.resources import ServerResource
|
||||
from specklepy.core.api.models.deprecated import FE1_DEPRECATION_VERSION
|
||||
|
||||
|
||||
@deprecated(reason="Renamed to ServerResource", version=FE1_DEPRECATION_VERSION)
|
||||
class Resource(ServerResource):
|
||||
"""Renamed to ServerResource"""
|
||||
@@ -1,322 +0,0 @@
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
from deprecated import deprecated
|
||||
|
||||
from specklepy.api.models import PendingStreamCollaborator, Stream
|
||||
from specklepy.core.api.models.deprecated import (
|
||||
FE1_DEPRECATION_REASON,
|
||||
FE1_DEPRECATION_VERSION,
|
||||
)
|
||||
from specklepy.core.api.resources.deprecated.stream import Resource as CoreResource
|
||||
from specklepy.logging import metrics
|
||||
|
||||
|
||||
class Resource(CoreResource):
|
||||
"""API Access class for streams"""
|
||||
|
||||
def __init__(self, account, basepath, client, server_version) -> None:
|
||||
super().__init__(
|
||||
account=account,
|
||||
basepath=basepath,
|
||||
client=client,
|
||||
server_version=server_version,
|
||||
)
|
||||
|
||||
self.schema = Stream
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def get(self, id: str, branch_limit: int = 10, commit_limit: int = 10) -> Stream:
|
||||
"""Get the specified stream from the server
|
||||
|
||||
Arguments:
|
||||
id {str} -- the stream id
|
||||
branch_limit {int} -- the maximum number of branches to return
|
||||
commit_limit {int} -- the maximum number of commits to return
|
||||
|
||||
Returns:
|
||||
Stream -- the retrieved stream
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Stream Get"})
|
||||
return super().get(id, branch_limit, commit_limit)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def list(self, stream_limit: int = 10) -> List[Stream]:
|
||||
"""Get a list of the user's streams
|
||||
|
||||
Arguments:
|
||||
stream_limit {int} -- The maximum number of streams to return
|
||||
|
||||
Returns:
|
||||
List[Stream] -- A list of Stream objects
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Stream List"})
|
||||
return super().list(stream_limit)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def create(
|
||||
self,
|
||||
name: str = "Anonymous Python Stream",
|
||||
description: str = "No description provided",
|
||||
is_public: bool = True,
|
||||
) -> str:
|
||||
"""Create a new stream
|
||||
|
||||
Arguments:
|
||||
name {str} -- the name of the string
|
||||
description {str} -- a short description of the stream
|
||||
is_public {bool}
|
||||
-- whether or not the stream can be viewed by anyone with the id
|
||||
|
||||
Returns:
|
||||
id {str} -- the id of the newly created stream
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Stream Create"})
|
||||
return super().create(name, description, is_public)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def update(
|
||||
self,
|
||||
id: str,
|
||||
name: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
is_public: Optional[bool] = None,
|
||||
) -> bool:
|
||||
"""Update an existing stream
|
||||
|
||||
Arguments:
|
||||
id {str} -- the id of the stream to be updated
|
||||
name {str} -- the name of the string
|
||||
description {str} -- a short description of the stream
|
||||
is_public {bool}
|
||||
-- whether or not the stream can be viewed by anyone with the id
|
||||
|
||||
Returns:
|
||||
bool -- whether the stream update was successful
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Stream Update"})
|
||||
return super().update(id, name, description, is_public)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def delete(self, id: str) -> bool:
|
||||
"""Delete a stream given its id
|
||||
|
||||
Arguments:
|
||||
id {str} -- the id of the stream to delete
|
||||
|
||||
Returns:
|
||||
bool -- whether the deletion was successful
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Stream Delete"})
|
||||
return super().delete(id)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def search(
|
||||
self,
|
||||
search_query: str,
|
||||
limit: int = 25,
|
||||
branch_limit: int = 10,
|
||||
commit_limit: int = 10,
|
||||
):
|
||||
"""Search for streams by name, description, or id
|
||||
|
||||
Arguments:
|
||||
search_query {str} -- a string to search for
|
||||
limit {int} -- the maximum number of results to return
|
||||
branch_limit {int} -- the maximum number of branches to return
|
||||
commit_limit {int} -- the maximum number of commits to return
|
||||
|
||||
Returns:
|
||||
List[Stream] -- a list of Streams that match the search query
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Stream Search"})
|
||||
return super().search(search_query, limit, branch_limit, commit_limit)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def favorite(self, stream_id: str, favorited: bool = True):
|
||||
"""Favorite or unfavorite the given stream.
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to favorite / unfavorite
|
||||
favorited {bool}
|
||||
-- whether to favorite (True) or unfavorite (False) the stream
|
||||
|
||||
Returns:
|
||||
Stream -- the stream with its `id`, `name`, and `favoritedDate`
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Stream Favorite"})
|
||||
return super().favorite(stream_id, favorited)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def get_all_pending_invites(
|
||||
self, stream_id: str
|
||||
) -> List[PendingStreamCollaborator]:
|
||||
"""Get all of the pending invites on a stream.
|
||||
You must be a `stream:owner` to query this.
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the stream id from which to get the pending invites
|
||||
|
||||
Returns:
|
||||
List[PendingStreamCollaborator]
|
||||
-- a list of pending invites for the specified stream
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Stream Invite Get"})
|
||||
return super().get_all_pending_invites(stream_id)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def invite(
|
||||
self,
|
||||
stream_id: str,
|
||||
email: Optional[str] = None,
|
||||
user_id: Optional[str] = None,
|
||||
role: str = "stream:contributor", # should default be reviewer?
|
||||
message: Optional[str] = None,
|
||||
):
|
||||
"""Invite someone to a stream using either their email or user id
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to invite the user to
|
||||
email {str} -- the email of the user to invite (use this OR `user_id`)
|
||||
user_id {str} -- the id of the user to invite (use this OR `email`)
|
||||
role {str}
|
||||
-- the role to assign to the user (defaults to `stream:contributor`)
|
||||
message {str}
|
||||
-- a message to send along with this invite to the specified user
|
||||
|
||||
Returns:
|
||||
bool -- True if the operation was successful
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Stream Invite Create"})
|
||||
return super().invite(stream_id, email, user_id, role, message)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def invite_batch(
|
||||
self,
|
||||
stream_id: str,
|
||||
emails: Optional[List[str]] = None,
|
||||
user_ids: Optional[List[None]] = None,
|
||||
message: Optional[str] = None,
|
||||
) -> bool:
|
||||
"""Invite a batch of users to a specified stream.
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to invite the user to
|
||||
emails {List[str]}
|
||||
-- the email of the user to invite (use this and/or `user_ids`)
|
||||
user_id {List[str]}
|
||||
-- the id of the user to invite (use this and/or `emails`)
|
||||
message {str}
|
||||
-- a message to send along with this invite to the specified user
|
||||
|
||||
Returns:
|
||||
bool -- True if the operation was successful
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Stream Invite Batch Create"})
|
||||
return super().invite_batch(stream_id, emails, user_ids, message)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def invite_cancel(self, stream_id: str, invite_id: str) -> bool:
|
||||
"""Cancel an existing stream invite
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream invite
|
||||
invite_id {str} -- the id of the invite to use
|
||||
|
||||
Returns:
|
||||
bool -- true if the operation was successful
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Stream Invite Cancel"})
|
||||
return super().invite_cancel(stream_id, invite_id)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def invite_use(self, stream_id: str, token: str, accept: bool = True) -> bool:
|
||||
"""Accept or decline a stream invite
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
|
||||
Arguments:
|
||||
stream_id {str}
|
||||
-- the id of the stream for which the user has a pending invite
|
||||
token {str} -- the token of the invite to use
|
||||
accept {bool} -- whether or not to accept the invite (defaults to True)
|
||||
|
||||
Returns:
|
||||
bool -- true if the operation was successful
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Invite Use"})
|
||||
return super().invite_use(stream_id, token, accept)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def update_permission(self, stream_id: str, user_id: str, role: str):
|
||||
"""Updates permissions for a user on a given stream
|
||||
|
||||
Valid for Speckle Server >=2.6.4
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to grant permissions to
|
||||
user_id {str} -- the id of the user to grant permissions for
|
||||
role {str} -- the role to grant the user
|
||||
|
||||
Returns:
|
||||
bool -- True if the operation was successful
|
||||
"""
|
||||
metrics.track(
|
||||
metrics.SDK,
|
||||
self.account,
|
||||
{"name": "Stream Permission Update", "role": role},
|
||||
)
|
||||
return super().update_permission(stream_id, user_id, role)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def revoke_permission(self, stream_id: str, user_id: str):
|
||||
"""Revoke permissions from a user on a given stream
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to revoke permissions from
|
||||
user_id {str} -- the id of the user to revoke permissions from
|
||||
|
||||
Returns:
|
||||
bool -- True if the operation was successful
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Stream Permission Revoke"})
|
||||
return super().revoke_permission(stream_id, user_id)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def activity(
|
||||
self,
|
||||
stream_id: str,
|
||||
action_type: Optional[str] = None,
|
||||
limit: int = 20,
|
||||
before: Optional[datetime] = None,
|
||||
after: Optional[datetime] = None,
|
||||
cursor: Optional[datetime] = None,
|
||||
):
|
||||
"""
|
||||
Get the activity from a given stream in an Activity collection.
|
||||
Step into the activity `items` for the list of activity.
|
||||
|
||||
Note: all timestamps arguments should be `datetime` of any tz
|
||||
as they will be converted to UTC ISO format strings
|
||||
|
||||
stream_id {str} -- the id of the stream to get activity from
|
||||
action_type {str}
|
||||
-- filter results to a single action type
|
||||
(eg: `commit_create` or `commit_receive`)
|
||||
limit {int} -- max number of Activity items to return
|
||||
before {datetime}
|
||||
-- latest cutoff for activity (ie: return all activity _before_ this time)
|
||||
after {datetime}
|
||||
-- oldest cutoff for activity (ie: return all activity _after_ this time)
|
||||
cursor {datetime} -- timestamp cursor for pagination
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Stream Activity"})
|
||||
return super().activity(stream_id, action_type, limit, before, after, cursor)
|
||||
@@ -1,107 +0,0 @@
|
||||
from typing import Callable, Dict, List, Optional, Union
|
||||
|
||||
from deprecated import deprecated
|
||||
from graphql import DocumentNode
|
||||
|
||||
from specklepy.core.api.models.deprecated import (
|
||||
FE1_DEPRECATION_REASON,
|
||||
FE1_DEPRECATION_VERSION,
|
||||
)
|
||||
from specklepy.core.api.resources.current.subscription_resource import check_wsclient
|
||||
from specklepy.core.api.resources.deprecated.subscriptions import (
|
||||
Resource as CoreResource,
|
||||
)
|
||||
from specklepy.logging import metrics
|
||||
|
||||
|
||||
class Resource(CoreResource):
|
||||
"""API Access class for subscriptions"""
|
||||
|
||||
def __init__(self, account, basepath, client) -> None:
|
||||
super().__init__(
|
||||
account=account,
|
||||
basepath=basepath,
|
||||
client=client,
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
@check_wsclient
|
||||
async def stream_added(self, callback: Optional[Callable] = None):
|
||||
"""Subscribes to new stream added event for your profile.
|
||||
Use this to display an up-to-date list of streams.
|
||||
|
||||
Arguments:
|
||||
callback {Callable[Stream]} -- a function that takes the updated stream
|
||||
as an argument and executes each time a stream is added
|
||||
|
||||
Returns:
|
||||
Stream -- the update stream
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "Subscription Stream Added"})
|
||||
return super().stream_added(callback)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
@check_wsclient
|
||||
async def stream_updated(self, id: str, callback: Optional[Callable] = None):
|
||||
"""
|
||||
Subscribes to stream updated event.
|
||||
Use this in clients/components that pertain only to this stream.
|
||||
|
||||
Arguments:
|
||||
id {str} -- the stream id of the stream to subscribe to
|
||||
callback {Callable[Stream]}
|
||||
-- a function that takes the updated stream
|
||||
as an argument and executes each time the stream is updated
|
||||
|
||||
Returns:
|
||||
Stream -- the update stream
|
||||
"""
|
||||
metrics.track(
|
||||
metrics.SDK, self.account, {"name": "Subscription Stream Updated"}
|
||||
)
|
||||
return super().stream_updated(id, callback)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
@check_wsclient
|
||||
async def stream_removed(self, callback: Optional[Callable] = None):
|
||||
"""Subscribes to stream removed event for your profile.
|
||||
Use this to display an up-to-date list of streams for your profile.
|
||||
NOTE: If someone revokes your permissions on a stream,
|
||||
this subscription will be triggered with an extra value of revokedBy
|
||||
in the payload.
|
||||
|
||||
Arguments:
|
||||
callback {Callable[Dict]}
|
||||
-- a function that takes the returned dict as an argument
|
||||
and executes each time a stream is removed
|
||||
|
||||
Returns:
|
||||
dict -- dict containing 'id' of stream removed and optionally 'revokedBy'
|
||||
"""
|
||||
metrics.track(
|
||||
metrics.SDK, self.account, {"name": "Subscription Stream Removed"}
|
||||
)
|
||||
return super().stream_removed(callback)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
@check_wsclient
|
||||
async def subscribe(
|
||||
self,
|
||||
query: DocumentNode,
|
||||
params: Optional[Dict] = None,
|
||||
callback: Optional[Callable] = None,
|
||||
return_type: Optional[Union[str, List]] = None,
|
||||
schema=None,
|
||||
parse_response: bool = True,
|
||||
):
|
||||
# if self.client.transport.websocket is None:
|
||||
# TODO: add multiple subs to the same ws connection
|
||||
async with self.client as session:
|
||||
async for res in session.subscribe(query, variable_values=params):
|
||||
res = self._step_into_response(response=res, return_type=return_type)
|
||||
if parse_response:
|
||||
res = self._parse_response(response=res, schema=schema)
|
||||
if callback is not None:
|
||||
callback(res)
|
||||
else:
|
||||
return res
|
||||
@@ -1,153 +0,0 @@
|
||||
from datetime import datetime
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from deprecated import deprecated
|
||||
|
||||
from specklepy.api.models import PendingStreamCollaborator, User
|
||||
from specklepy.core.api.resources.deprecated.user import Resource as CoreResource
|
||||
from specklepy.logging import metrics
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
|
||||
DEPRECATION_VERSION = "2.9.0"
|
||||
DEPRECATION_TEXT = (
|
||||
"The user resource is deprecated, please use the active_user or other_user"
|
||||
" resources"
|
||||
)
|
||||
|
||||
|
||||
class Resource(CoreResource):
|
||||
"""API Access class for users"""
|
||||
|
||||
def __init__(self, account, basepath, client, server_version) -> None:
|
||||
super().__init__(
|
||||
account=account,
|
||||
basepath=basepath,
|
||||
client=client,
|
||||
server_version=server_version,
|
||||
)
|
||||
self.schema = User
|
||||
|
||||
@deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT)
|
||||
def get(self, id: Optional[str] = None) -> User:
|
||||
"""
|
||||
Gets the profile of a user.
|
||||
If no id argument is provided, will return the current authenticated
|
||||
user's profile (as extracted from the authorization header).
|
||||
|
||||
Arguments:
|
||||
id {str} -- the user id
|
||||
|
||||
Returns:
|
||||
User -- the retrieved user
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "User Get_deprecated"})
|
||||
return super().get(id)
|
||||
|
||||
@deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT)
|
||||
def search(
|
||||
self, search_query: str, limit: int = 25
|
||||
) -> Union[List[User], SpeckleException]:
|
||||
"""
|
||||
Searches for user by name or email.
|
||||
The search query must be at least 3 characters long
|
||||
|
||||
Arguments:
|
||||
search_query {str} -- a string to search for
|
||||
limit {int} -- the maximum number of results to return
|
||||
Returns:
|
||||
List[User] -- a list of User objects that match the search query
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "User Search_deprecated"})
|
||||
return super().search(search_query, limit)
|
||||
|
||||
@deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT)
|
||||
def update(
|
||||
self,
|
||||
name: Optional[str] = None,
|
||||
company: Optional[str] = None,
|
||||
bio: Optional[str] = None,
|
||||
avatar: Optional[str] = None,
|
||||
):
|
||||
"""Updates your user profile. All arguments are optional.
|
||||
|
||||
Arguments:
|
||||
name {str} -- your name
|
||||
company {str} -- the company you may or may not work for
|
||||
bio {str} -- tell us about yourself
|
||||
avatar {str} -- a nice photo of yourself
|
||||
|
||||
Returns:
|
||||
bool -- True if your profile was updated successfully
|
||||
"""
|
||||
# metrics.track(metrics.USER, self.account, {"name": "update"})
|
||||
metrics.track(metrics.SDK, self.account, {"name": "User Update_deprecated"})
|
||||
return super().update(name, company, bio, avatar)
|
||||
|
||||
@deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT)
|
||||
def activity(
|
||||
self,
|
||||
user_id: Optional[str] = None,
|
||||
limit: int = 20,
|
||||
action_type: Optional[str] = None,
|
||||
before: Optional[datetime] = None,
|
||||
after: Optional[datetime] = None,
|
||||
cursor: Optional[datetime] = None,
|
||||
):
|
||||
"""
|
||||
Get the activity from a given stream in an Activity collection.
|
||||
Step into the activity `items` for the list of activity.
|
||||
If no id argument is provided, will return the current authenticated
|
||||
user's activity (as extracted from the authorization header).
|
||||
|
||||
Note: all timestamps arguments should be `datetime` of any tz as
|
||||
they will be converted to UTC ISO format strings
|
||||
|
||||
user_id {str} -- the id of the user to get the activity from
|
||||
action_type {str} -- filter results to a single action type
|
||||
(eg: `commit_create` or `commit_receive`)
|
||||
limit {int} -- max number of Activity items to return
|
||||
before {datetime}
|
||||
-- latest cutoff for activity (ie: return all activity _before_ this time)
|
||||
after {datetime}
|
||||
-- oldest cutoff for activity (ie: return all activity _after_ this time)
|
||||
cursor {datetime} -- timestamp cursor for pagination
|
||||
"""
|
||||
metrics.track(metrics.SDK, self.account, {"name": "User Activity_deprecated"})
|
||||
return super().activity(user_id, limit, action_type, before, after, cursor)
|
||||
|
||||
@deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT)
|
||||
def get_all_pending_invites(self) -> List[PendingStreamCollaborator]:
|
||||
"""Get all of the active user's pending stream invites
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
|
||||
Returns:
|
||||
List[PendingStreamCollaborator]
|
||||
-- a list of pending invites for the current user
|
||||
"""
|
||||
# metrics.track(metrics.INVITE, self.account, {"name": "get"})
|
||||
metrics.track(
|
||||
metrics.SDK, self.account, {"name": "User GetAllInvites_deprecated"}
|
||||
)
|
||||
return super().get_all_pending_invites()
|
||||
|
||||
@deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT)
|
||||
def get_pending_invite(
|
||||
self, stream_id: str, token: Optional[str] = None
|
||||
) -> Optional[PendingStreamCollaborator]:
|
||||
"""Get a particular pending invite for the active user on a given stream.
|
||||
If no invite_id is provided, any valid invite will be returned.
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to look for invites on
|
||||
token {str} -- the token of the invite to look for (optional)
|
||||
|
||||
Returns:
|
||||
PendingStreamCollaborator
|
||||
-- the invite for the given stream (or None if it isn't found)
|
||||
"""
|
||||
# metrics.track(metrics.INVITE, self.account, {"name": "get"})
|
||||
metrics.track(metrics.SDK, self.account, {"name": "User GetInvite_deprecated"})
|
||||
return super().get_pending_invite(stream_id, token)
|
||||
@@ -10,7 +10,7 @@ class StreamWrapper(CoreStreamWrapper):
|
||||
The `StreamWrapper` gives you some handy helpers to deal with urls and
|
||||
get authenticated clients and transports.
|
||||
|
||||
Construct a `StreamWrapper` with a stream, branch, commit, or object URL.
|
||||
Construct a `StreamWrapper` with a URL of a model, version, or object.
|
||||
The corresponding ids will be stored
|
||||
in the wrapper. If you have local accounts on the machine,
|
||||
you can use the `get_account` and `get_client` methods
|
||||
@@ -21,8 +21,8 @@ class StreamWrapper(CoreStreamWrapper):
|
||||
```py
|
||||
from specklepy.api.wrapper import StreamWrapper
|
||||
|
||||
# provide any stream, branch, commit, object, or globals url
|
||||
wrapper = StreamWrapper("https://app.speckle.systems/streams/3073b96e86/commits/604bea8cc6")
|
||||
# provide a url for a model, version, or object
|
||||
wrapper = StreamWrapper("https://app.speckle.systems/projects/3073b96e86/models/0fe47c9dca@604bea8cc6")
|
||||
|
||||
# get an authenticated SpeckleClient if you have a local account for the server
|
||||
client = wrapper.get_client()
|
||||
|
||||
@@ -3,14 +3,12 @@ import re
|
||||
from typing import Dict
|
||||
from warnings import warn
|
||||
|
||||
from deprecated import deprecated
|
||||
from gql import Client
|
||||
from gql.transport.exceptions import TransportServerError
|
||||
from gql.transport.requests import RequestsHTTPTransport
|
||||
from gql.transport.websockets import WebsocketsTransport
|
||||
|
||||
from specklepy.core.api import resources
|
||||
from specklepy.core.api.credentials import Account, get_account_from_token
|
||||
from specklepy.core.api.credentials import Account
|
||||
from specklepy.core.api.resources import (
|
||||
ActiveUserResource,
|
||||
ModelResource,
|
||||
@@ -20,12 +18,6 @@ from specklepy.core.api.resources import (
|
||||
ServerResource,
|
||||
SubscriptionResource,
|
||||
VersionResource,
|
||||
branch,
|
||||
commit,
|
||||
object,
|
||||
stream,
|
||||
subscriptions,
|
||||
user,
|
||||
)
|
||||
from specklepy.logging import metrics
|
||||
from specklepy.logging.exceptions import SpeckleException, SpeckleWarning
|
||||
@@ -44,6 +36,7 @@ class SpeckleClient:
|
||||
|
||||
```py
|
||||
from specklepy.api.client import SpeckleClient
|
||||
from specklepy.core.api.inputs.project_inputs import ProjectCreateInput
|
||||
from specklepy.api.credentials import get_default_account
|
||||
|
||||
# initialise the client
|
||||
@@ -55,11 +48,12 @@ class SpeckleClient:
|
||||
account = get_default_account()
|
||||
client.authenticate_with_account(account)
|
||||
|
||||
# create a new stream. this returns the stream id
|
||||
new_stream_id = client.stream.create(name="a shiny new stream")
|
||||
# create a new project
|
||||
input = ProjectCreateInput(name="a shiny new project")
|
||||
project = self.project.create(input)
|
||||
|
||||
# use that stream id to get the stream from the server
|
||||
new_stream = client.stream.get(id=new_stream_id)
|
||||
# or, use a project id to get an existing project from the server
|
||||
new_stream = client.project.get("abcdefghij")
|
||||
```
|
||||
"""
|
||||
|
||||
@@ -123,23 +117,6 @@ class SpeckleClient:
|
||||
f" {self.account.token is not None} )"
|
||||
)
|
||||
|
||||
@deprecated(
|
||||
version="2.6.0",
|
||||
reason=(
|
||||
"Renamed: please use `authenticate_with_account` or"
|
||||
" `authenticate_with_token` instead."
|
||||
),
|
||||
)
|
||||
def authenticate(self, token: str) -> None:
|
||||
"""Authenticate the client using a personal access token
|
||||
The token is saved in the client object and a synchronous GraphQL
|
||||
entrypoint is created
|
||||
|
||||
Arguments:
|
||||
token {str} -- an api token
|
||||
"""
|
||||
self.authenticate_with_account(get_account_from_token(token))
|
||||
|
||||
def authenticate_with_token(self, token: str) -> None:
|
||||
"""
|
||||
Authenticate the client using a personal access token.
|
||||
@@ -251,41 +228,3 @@ class SpeckleClient:
|
||||
basepath=self.ws_url,
|
||||
client=self.wsclient,
|
||||
)
|
||||
# Deprecated Resources
|
||||
self.user = user.Resource(
|
||||
account=self.account,
|
||||
basepath=self.url,
|
||||
client=self.httpclient,
|
||||
server_version=server_version,
|
||||
)
|
||||
self.stream = stream.Resource(
|
||||
account=self.account,
|
||||
basepath=self.url,
|
||||
client=self.httpclient,
|
||||
server_version=server_version,
|
||||
)
|
||||
self.commit = commit.Resource(
|
||||
account=self.account, basepath=self.url, client=self.httpclient
|
||||
)
|
||||
self.branch = branch.Resource(
|
||||
account=self.account, basepath=self.url, client=self.httpclient
|
||||
)
|
||||
self.object = object.Resource(
|
||||
account=self.account, basepath=self.url, client=self.httpclient
|
||||
)
|
||||
self.subscribe = subscriptions.Resource(
|
||||
account=self.account,
|
||||
basepath=self.ws_url,
|
||||
client=self.wsclient,
|
||||
)
|
||||
|
||||
def __getattr__(self, name):
|
||||
try:
|
||||
attr = getattr(resources, name)
|
||||
return attr.Resource(
|
||||
account=self.account, basepath=self.url, client=self.httpclient
|
||||
)
|
||||
except AttributeError as ex:
|
||||
raise SpeckleException(
|
||||
f"Method {name} is not supported by the SpeckleClient class"
|
||||
) from ex
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -17,18 +17,6 @@ from specklepy.core.api.models.current import (
|
||||
UserSearchResultCollection,
|
||||
Version,
|
||||
)
|
||||
from specklepy.core.api.models.deprecated import (
|
||||
Activity,
|
||||
ActivityCollection,
|
||||
Branch,
|
||||
Branches,
|
||||
Collaborator,
|
||||
Commit,
|
||||
Commits,
|
||||
Object,
|
||||
Stream,
|
||||
Streams,
|
||||
)
|
||||
from specklepy.core.api.models.subscription_messages import (
|
||||
ProjectModelsUpdatedMessage,
|
||||
ProjectUpdatedMessage,
|
||||
@@ -58,14 +46,4 @@ __all__ = [
|
||||
"ProjectModelsUpdatedMessage",
|
||||
"ProjectUpdatedMessage",
|
||||
"ProjectVersionsUpdatedMessage",
|
||||
"Collaborator",
|
||||
"Commit",
|
||||
"Commits",
|
||||
"Object",
|
||||
"Branch",
|
||||
"Branches",
|
||||
"Stream",
|
||||
"Streams",
|
||||
"Activity",
|
||||
"ActivityCollection",
|
||||
]
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
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
|
||||
@@ -18,7 +16,6 @@ class User(BaseModel):
|
||||
avatar: Optional[str] = None
|
||||
verified: Optional[bool] = None
|
||||
role: Optional[str] = None
|
||||
streams: Optional["Streams"] = None
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
@@ -30,18 +27,18 @@ class User(BaseModel):
|
||||
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 +46,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 +71,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 +83,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 +107,48 @@ class PendingStreamCollaborator(BaseModel):
|
||||
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 +156,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
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
from datetime import datetime
|
||||
from typing import List, Optional
|
||||
|
||||
from deprecated import deprecated
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
FE1_DEPRECATION_REASON = (
|
||||
"Stream/Branch/Commit API is now deprecated, "
|
||||
"Use the new Project/Model/Version API functions in Client"
|
||||
)
|
||||
FE1_DEPRECATION_VERSION = "2.20"
|
||||
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
class Collaborator(BaseModel):
|
||||
id: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
role: Optional[str] = None
|
||||
avatar: Optional[str] = None
|
||||
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
class Commit(BaseModel):
|
||||
id: Optional[str] = None
|
||||
message: Optional[str] = None
|
||||
authorName: Optional[str] = None
|
||||
authorId: Optional[str] = None
|
||||
authorAvatar: Optional[str] = None
|
||||
branchName: Optional[str] = None
|
||||
createdAt: Optional[datetime] = None
|
||||
sourceApplication: Optional[str] = None
|
||||
referencedObject: Optional[str] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
parents: Optional[List[str]] = None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"Commit( id: {self.id}, message: {self.message}, referencedObject:"
|
||||
f" {self.referencedObject}, authorName: {self.authorName}, branchName:"
|
||||
f" {self.branchName}, createdAt: {self.createdAt} )"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
class Commits(BaseModel):
|
||||
totalCount: Optional[int] = None
|
||||
cursor: Optional[datetime] = None
|
||||
items: List[Commit] = []
|
||||
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
class Object(BaseModel):
|
||||
id: Optional[str] = None
|
||||
speckleType: Optional[str] = None
|
||||
applicationId: Optional[str] = None
|
||||
totalChildrenCount: Optional[int] = None
|
||||
createdAt: Optional[datetime] = None
|
||||
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
class Branch(BaseModel):
|
||||
id: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
commits: Optional[Commits] = None
|
||||
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
class Branches(BaseModel):
|
||||
totalCount: Optional[int] = None
|
||||
cursor: Optional[datetime] = None
|
||||
items: List[Branch] = []
|
||||
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
class Stream(BaseModel):
|
||||
id: Optional[str] = None
|
||||
name: Optional[str] = None
|
||||
role: Optional[str] = None
|
||||
isPublic: Optional[bool] = None
|
||||
description: Optional[str] = None
|
||||
createdAt: Optional[datetime] = None
|
||||
updatedAt: Optional[datetime] = None
|
||||
collaborators: List[Collaborator] = Field(default_factory=list)
|
||||
branches: Optional[Branches] = None
|
||||
commit: Optional[Commit] = None
|
||||
object: Optional[Object] = None
|
||||
commentCount: Optional[int] = None
|
||||
favoritedDate: Optional[datetime] = None
|
||||
favoritesCount: Optional[int] = None
|
||||
|
||||
def __repr__(self):
|
||||
return (
|
||||
f"Stream( id: {self.id}, name: {self.name}, description:"
|
||||
f" {self.description}, isPublic: {self.isPublic})"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
class Streams(BaseModel):
|
||||
totalCount: Optional[int] = None
|
||||
cursor: Optional[datetime] = None
|
||||
items: List[Stream] = []
|
||||
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
class Activity(BaseModel):
|
||||
actionType: Optional[str] = None
|
||||
info: Optional[dict] = None
|
||||
userId: Optional[str] = None
|
||||
streamId: Optional[str] = None
|
||||
resourceId: Optional[str] = None
|
||||
resourceType: Optional[str] = None
|
||||
message: Optional[str] = None
|
||||
time: Optional[datetime] = None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"Activity( streamId: {self.streamId}, actionType: {self.actionType},"
|
||||
f" message: {self.message}, userId: {self.userId} )"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
class ActivityCollection(BaseModel):
|
||||
totalCount: Optional[int] = None
|
||||
items: Optional[List[Activity]] = None
|
||||
cursor: Optional[datetime] = None
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"ActivityCollection( totalCount: {self.totalCount}, items:"
|
||||
f" {len(self.items) if self.items else 0}, cursor:"
|
||||
f" {self.cursor.isoformat() if self.cursor else None} )"
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.__repr__()
|
||||
@@ -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,
|
||||
)
|
||||
@@ -1,7 +1,5 @@
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from specklepy.core.api.enums import (
|
||||
ProjectModelsUpdatedMessageType,
|
||||
ProjectUpdatedMessageType,
|
||||
@@ -9,28 +7,29 @@ from specklepy.core.api.enums import (
|
||||
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: str
|
||||
version: Optional[Version]
|
||||
|
||||
@@ -10,17 +10,6 @@ from specklepy.core.api.resources.current.subscription_resource import (
|
||||
SubscriptionResource,
|
||||
)
|
||||
from specklepy.core.api.resources.current.version_resource import VersionResource
|
||||
from specklepy.core.api.resources.deprecated import (
|
||||
active_user,
|
||||
branch,
|
||||
commit,
|
||||
object,
|
||||
other_user,
|
||||
server,
|
||||
stream,
|
||||
subscriptions,
|
||||
user,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"ActiveUserResource",
|
||||
@@ -31,13 +20,4 @@ __all__ = [
|
||||
"ServerResource",
|
||||
"SubscriptionResource",
|
||||
"VersionResource",
|
||||
"active_user",
|
||||
"branch",
|
||||
"commit",
|
||||
"object",
|
||||
"other_user",
|
||||
"server",
|
||||
"stream",
|
||||
"subscriptions",
|
||||
"user",
|
||||
]
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Optional, overload
|
||||
from typing import List, Optional
|
||||
|
||||
from deprecated import deprecated
|
||||
from gql import gql
|
||||
|
||||
from specklepy.core.api.inputs.user_inputs import UserProjectsFilter, UserUpdateInput
|
||||
from specklepy.core.api.models import (
|
||||
ActivityCollection,
|
||||
PendingStreamCollaborator,
|
||||
Project,
|
||||
ResourceCollection,
|
||||
User,
|
||||
)
|
||||
from specklepy.core.api.models.deprecated import (
|
||||
FE1_DEPRECATION_REASON,
|
||||
FE1_DEPRECATION_VERSION,
|
||||
)
|
||||
from specklepy.core.api.resource import ResourceBase
|
||||
from specklepy.core.api.responses import DataResponse
|
||||
from specklepy.logging.exceptions import GraphQLException
|
||||
@@ -67,7 +60,7 @@ class ActiveUserResource(ResourceBase):
|
||||
DataResponse[Optional[User]], QUERY, variables
|
||||
).data
|
||||
|
||||
def _update(self, input: UserUpdateInput) -> User:
|
||||
def update(self, input: UserUpdateInput) -> User:
|
||||
QUERY = gql(
|
||||
"""
|
||||
mutation ActiveUserMutations($input: UserUpdateInput!) {
|
||||
@@ -87,46 +80,12 @@ class ActiveUserResource(ResourceBase):
|
||||
"""
|
||||
)
|
||||
|
||||
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
|
||||
).data.data
|
||||
|
||||
@deprecated("Use UserUpdateInput overload", version=FE1_DEPRECATION_VERSION)
|
||||
@overload
|
||||
def update(
|
||||
self,
|
||||
name: Optional[str] = None,
|
||||
company: Optional[str] = None,
|
||||
bio: Optional[str] = None,
|
||||
avatar: Optional[str] = None,
|
||||
) -> User: ...
|
||||
|
||||
@overload
|
||||
def update(self, *, input: UserUpdateInput) -> User: ...
|
||||
|
||||
def update(
|
||||
self,
|
||||
name: Optional[str] = None,
|
||||
company: Optional[str] = None,
|
||||
bio: Optional[str] = None,
|
||||
avatar: Optional[str] = None,
|
||||
*,
|
||||
input: Optional[UserUpdateInput] = None,
|
||||
) -> User:
|
||||
if isinstance(input, UserUpdateInput):
|
||||
return self._update(input=input)
|
||||
else:
|
||||
return self._update(
|
||||
input=UserUpdateInput(
|
||||
name=name,
|
||||
company=company,
|
||||
bio=bio,
|
||||
avatar=avatar,
|
||||
)
|
||||
)
|
||||
|
||||
def get_projects(
|
||||
self,
|
||||
*,
|
||||
@@ -162,7 +121,9 @@ class ActiveUserResource(ResourceBase):
|
||||
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(
|
||||
@@ -229,184 +190,3 @@ class ActiveUserResource(ResourceBase):
|
||||
)
|
||||
|
||||
return response.data.data
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def activity(
|
||||
self,
|
||||
limit: int = 20,
|
||||
action_type: Optional[str] = None,
|
||||
before: Optional[datetime] = None,
|
||||
after: Optional[datetime] = None,
|
||||
cursor: Optional[datetime] = None,
|
||||
) -> ActivityCollection:
|
||||
"""
|
||||
Get the activity from a given stream in an Activity collection.
|
||||
Step into the activity `items` for the list of activity.
|
||||
If no id argument is provided, will return the current authenticated user's
|
||||
activity (as extracted from the authorization header).
|
||||
|
||||
Note: all timestamps arguments should be `datetime` of any tz as they will be
|
||||
converted to UTC ISO format strings
|
||||
|
||||
user_id {str} -- the id of the user to get the activity from
|
||||
action_type {str} -- filter results to a single action type
|
||||
(eg: `commit_create` or `commit_receive`)
|
||||
limit {int} -- max number of Activity items to return
|
||||
before {datetime} -- latest cutoff for activity
|
||||
(ie: return all activity _before_ this time)
|
||||
after {datetime} -- oldest cutoff for activity
|
||||
(ie: return all activity _after_ this time)
|
||||
cursor {datetime} -- timestamp cursor for pagination
|
||||
"""
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
query UserActivity(
|
||||
$action_type: String,
|
||||
$before:DateTime,
|
||||
$after: DateTime,
|
||||
$cursor: DateTime,
|
||||
$limit: Int
|
||||
){
|
||||
activeUser {
|
||||
activity(
|
||||
actionType: $action_type,
|
||||
before: $before,
|
||||
after: $after,
|
||||
cursor: $cursor,
|
||||
limit: $limit
|
||||
) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
actionType
|
||||
info
|
||||
userId
|
||||
streamId
|
||||
resourceId
|
||||
resourceType
|
||||
message
|
||||
time
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {
|
||||
"limit": limit,
|
||||
"action_type": action_type,
|
||||
"before": before.astimezone(timezone.utc).isoformat() if before else before,
|
||||
"after": after.astimezone(timezone.utc).isoformat() if after else after,
|
||||
"cursor": cursor.astimezone(timezone.utc).isoformat() if cursor else cursor,
|
||||
}
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type=["activeUser", "activity"],
|
||||
schema=ActivityCollection,
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def get_all_pending_invites(self) -> List[PendingStreamCollaborator]:
|
||||
"""Get all of the active user's pending stream invites
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
|
||||
Returns:
|
||||
List[PendingStreamCollaborator]
|
||||
-- a list of pending invites for the current user
|
||||
"""
|
||||
self._check_invites_supported()
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
query StreamInvites {
|
||||
streamInvites{
|
||||
id
|
||||
token
|
||||
inviteId
|
||||
streamId
|
||||
streamName
|
||||
projectId
|
||||
projectName
|
||||
title
|
||||
role
|
||||
invitedBy {
|
||||
id
|
||||
name
|
||||
bio
|
||||
company
|
||||
avatar
|
||||
verified
|
||||
role
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
return_type="streamInvites",
|
||||
schema=PendingStreamCollaborator,
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def get_pending_invite(
|
||||
self, stream_id: str, token: Optional[str] = None
|
||||
) -> Optional[PendingStreamCollaborator]:
|
||||
"""Get a particular pending invite for the active user on a given stream.
|
||||
If no invite_id is provided, any valid invite will be returned.
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to look for invites on
|
||||
token {str} -- the token of the invite to look for (optional)
|
||||
|
||||
Returns:
|
||||
PendingStreamCollaborator
|
||||
-- the invite for the given stream (or None if it isn't found)
|
||||
"""
|
||||
self._check_invites_supported()
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
query StreamInvite($streamId: String!, $token: String) {
|
||||
streamInvite(streamId: $streamId, token: $token) {
|
||||
id
|
||||
token
|
||||
inviteId
|
||||
streamId
|
||||
streamName
|
||||
projectId
|
||||
projectName
|
||||
title
|
||||
role
|
||||
invitedBy {
|
||||
id
|
||||
name
|
||||
bio
|
||||
company
|
||||
avatar
|
||||
verified
|
||||
role
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"streamId": stream_id}
|
||||
if token:
|
||||
params["token"] = token
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type="streamInvite",
|
||||
schema=PendingStreamCollaborator,
|
||||
)
|
||||
|
||||
@@ -138,7 +138,7 @@ class ModelResource(ResourceBase):
|
||||
"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 @@ class ModelResource(ResourceBase):
|
||||
"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 @@ class ModelResource(ResourceBase):
|
||||
)
|
||||
|
||||
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 @@ class ModelResource(ResourceBase):
|
||||
"""
|
||||
)
|
||||
|
||||
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 @@ class ModelResource(ResourceBase):
|
||||
)
|
||||
|
||||
variables = {
|
||||
"input": input.model_dump(warnings="error"),
|
||||
"input": input.model_dump(warnings="error", by_alias=True),
|
||||
}
|
||||
|
||||
return self.make_request_and_parse_response(
|
||||
|
||||
@@ -1,21 +1,13 @@
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Optional, Union
|
||||
from typing import Optional
|
||||
|
||||
from deprecated import deprecated
|
||||
from gql import gql
|
||||
|
||||
from specklepy.core.api.models import (
|
||||
ActivityCollection,
|
||||
LimitedUser,
|
||||
UserSearchResultCollection,
|
||||
)
|
||||
from specklepy.core.api.models.deprecated import (
|
||||
FE1_DEPRECATION_REASON,
|
||||
FE1_DEPRECATION_VERSION,
|
||||
)
|
||||
from specklepy.core.api.resource import ResourceBase
|
||||
from specklepy.core.api.responses import DataResponse
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
|
||||
NAME = "other_user"
|
||||
|
||||
@@ -130,124 +122,3 @@ class OtherUserResource(ResourceBase):
|
||||
return self.make_request_and_parse_response(
|
||||
DataResponse[UserSearchResultCollection], QUERY, variables
|
||||
).data
|
||||
|
||||
@deprecated(reason="Use user_search instead", version=FE1_DEPRECATION_VERSION)
|
||||
def search(
|
||||
self, search_query: str, limit: int = 25
|
||||
) -> Union[List[LimitedUser], SpeckleException]:
|
||||
"""Searches for user by name or email. The search query must be at least
|
||||
3 characters long
|
||||
|
||||
Arguments:
|
||||
search_query {str} -- a string to search for
|
||||
limit {int} -- the maximum number of results to return
|
||||
Returns:
|
||||
List[LimitedUser] -- a list of User objects that match the search query
|
||||
"""
|
||||
if len(search_query) < 3:
|
||||
return SpeckleException(
|
||||
message="User search query must be at least 3 characters"
|
||||
)
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
query UserSearch($search_query: String!, $limit: Int!) {
|
||||
userSearch(query: $search_query, limit: $limit) {
|
||||
items {
|
||||
id
|
||||
name
|
||||
bio
|
||||
company
|
||||
avatar
|
||||
verified
|
||||
role
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {"search_query": search_query, "limit": limit}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type=["userSearch", "items"]
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def activity(
|
||||
self,
|
||||
user_id: str,
|
||||
limit: int = 20,
|
||||
action_type: Optional[str] = None,
|
||||
before: Optional[datetime] = None,
|
||||
after: Optional[datetime] = None,
|
||||
cursor: Optional[datetime] = None,
|
||||
) -> ActivityCollection:
|
||||
"""
|
||||
Get the activity from a given stream in an Activity collection.
|
||||
Step into the activity `items` for the list of activity.
|
||||
|
||||
Note: all timestamps arguments should be `datetime` of
|
||||
any tz as they will be converted to UTC ISO format strings
|
||||
|
||||
user_id {str} -- the id of the user to get the activity from
|
||||
action_type {str} -- filter results to a single action type
|
||||
(eg: `commit_create` or `commit_receive`)
|
||||
limit {int} -- max number of Activity items to return
|
||||
before {datetime} -- latest cutoff for activity
|
||||
(ie: return all activity _before_ this time)
|
||||
after {datetime} -- oldest cutoff for activity
|
||||
(ie: return all activity _after_ this time)
|
||||
cursor {datetime} -- timestamp cursor for pagination
|
||||
"""
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
query UserActivity(
|
||||
$user_id: String!,
|
||||
$action_type: String,
|
||||
$before:DateTime,
|
||||
$after: DateTime,
|
||||
$cursor: DateTime,
|
||||
$limit: Int
|
||||
){
|
||||
otherUser(id: $user_id) {
|
||||
activity(
|
||||
actionType: $action_type,
|
||||
before: $before,
|
||||
after: $after,
|
||||
cursor: $cursor,
|
||||
limit: $limit
|
||||
) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
actionType
|
||||
info
|
||||
userId
|
||||
streamId
|
||||
resourceId
|
||||
resourceType
|
||||
message
|
||||
time
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {
|
||||
"user_id": user_id,
|
||||
"limit": limit,
|
||||
"action_type": action_type,
|
||||
"before": before.astimezone(timezone.utc).isoformat() if before else before,
|
||||
"after": after.astimezone(timezone.utc).isoformat() if after else after,
|
||||
"cursor": cursor.astimezone(timezone.utc).isoformat() if cursor else cursor,
|
||||
}
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type=["otherUser", "activity"],
|
||||
schema=ActivityCollection,
|
||||
)
|
||||
|
||||
@@ -103,7 +103,7 @@ class ProjectInviteResource(ResourceBase):
|
||||
|
||||
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 @@ class ProjectInviteResource(ResourceBase):
|
||||
)
|
||||
|
||||
variables = {
|
||||
"input": input.model_dump(warnings="error"),
|
||||
"input": input.model_dump(warnings="error", by_alias=True),
|
||||
}
|
||||
|
||||
return self.make_request_and_parse_response(
|
||||
|
||||
@@ -118,7 +118,9 @@ class ProjectResource(ResourceBase):
|
||||
"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 @@ class ProjectResource(ResourceBase):
|
||||
)
|
||||
|
||||
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 @@ class ProjectResource(ResourceBase):
|
||||
)
|
||||
|
||||
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 @@ class ProjectResource(ResourceBase):
|
||||
)
|
||||
|
||||
variables = {
|
||||
"input": input.model_dump(warnings="error"),
|
||||
"input": input.model_dump(warnings="error", by_alias=True),
|
||||
}
|
||||
|
||||
return self.make_request_and_parse_response(
|
||||
|
||||
@@ -61,10 +61,10 @@ class ServerResource(ResourceBase):
|
||||
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
|
||||
|
||||
@@ -117,7 +117,9 @@ class VersionResource(ResourceBase):
|
||||
"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 @@ class VersionResource(ResourceBase):
|
||||
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 @@ class VersionResource(ResourceBase):
|
||||
"""
|
||||
)
|
||||
|
||||
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 @@ class VersionResource(ResourceBase):
|
||||
)
|
||||
|
||||
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 @@ class VersionResource(ResourceBase):
|
||||
)
|
||||
|
||||
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 @@ class VersionResource(ResourceBase):
|
||||
)
|
||||
|
||||
variables = {
|
||||
"input": input.model_dump(warnings="error"),
|
||||
"input": input.model_dump(warnings="error", by_alias=True),
|
||||
}
|
||||
|
||||
return self.make_request_and_parse_response(
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
from deprecated import deprecated
|
||||
|
||||
from specklepy.core.api.models.deprecated import FE1_DEPRECATION_VERSION
|
||||
from specklepy.core.api.resources import ActiveUserResource
|
||||
|
||||
|
||||
@deprecated(
|
||||
reason="Class renamed to ActiveUserResource", version=FE1_DEPRECATION_VERSION
|
||||
)
|
||||
class Resource(ActiveUserResource):
|
||||
"""
|
||||
Class renamed to ActiveUserResource
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -1,235 +0,0 @@
|
||||
from typing import Optional
|
||||
|
||||
from deprecated import deprecated
|
||||
from gql import gql
|
||||
|
||||
from specklepy.core.api.models.deprecated import (
|
||||
FE1_DEPRECATION_REASON,
|
||||
FE1_DEPRECATION_VERSION,
|
||||
Branch,
|
||||
)
|
||||
from specklepy.core.api.resource import ResourceBase
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
|
||||
NAME = "branch"
|
||||
|
||||
|
||||
class Resource(ResourceBase):
|
||||
"""
|
||||
API Access class for branches
|
||||
Branch resource is deprecated, please use model resource instead
|
||||
"""
|
||||
|
||||
def __init__(self, account, basepath, client) -> None:
|
||||
super().__init__(
|
||||
account=account,
|
||||
basepath=basepath,
|
||||
client=client,
|
||||
name=NAME,
|
||||
)
|
||||
self.schema = Branch
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def create(
|
||||
self, stream_id: str, name: str, description: str = "No description provided"
|
||||
) -> str:
|
||||
"""Create a new branch on this stream
|
||||
|
||||
Arguments:
|
||||
name {str} -- the name of the new branch
|
||||
description {str} -- a short description of the branch
|
||||
|
||||
Returns:
|
||||
id {str} -- the newly created branch's id
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
mutation BranchCreate($branch: BranchCreateInput!) {
|
||||
branchCreate(branch: $branch)
|
||||
}
|
||||
"""
|
||||
)
|
||||
if len(name) < 3:
|
||||
return SpeckleException(message="Branch Name must be at least 3 characters")
|
||||
params = {
|
||||
"branch": {
|
||||
"streamId": stream_id,
|
||||
"name": name,
|
||||
"description": description,
|
||||
}
|
||||
}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="branchCreate", parse_response=False
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def get(self, stream_id: str, name: str, commits_limit: int = 10):
|
||||
"""Get a branch by name from a stream
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to get the branch from
|
||||
name {str} -- the name of the branch to get
|
||||
commits_limit {int} -- maximum number of commits to get
|
||||
|
||||
Returns:
|
||||
Branch -- the fetched branch with its latest commits
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
query BranchGet($stream_id: String!, $name: String!, $commits_limit: Int!) {
|
||||
stream(id: $stream_id) {
|
||||
branch(name: $name) {
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
commits (limit: $commits_limit) {
|
||||
totalCount,
|
||||
cursor,
|
||||
items {
|
||||
id,
|
||||
referencedObject,
|
||||
sourceApplication,
|
||||
totalChildrenCount,
|
||||
message,
|
||||
authorName,
|
||||
authorId,
|
||||
branchName,
|
||||
parents,
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"stream_id": stream_id, "name": name, "commits_limit": commits_limit}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type=["stream", "branch"]
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def list(self, stream_id: str, branches_limit: int = 10, commits_limit: int = 10):
|
||||
"""Get a list of branches from a given stream
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to get the branches from
|
||||
branches_limit {int} -- maximum number of branches to get
|
||||
commits_limit {int} -- maximum number of commits to get
|
||||
|
||||
Returns:
|
||||
List[Branch] -- the branches on the stream
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
query BranchesGet(
|
||||
$stream_id: String!,
|
||||
$branches_limit: Int!,
|
||||
$commits_limit: Int!
|
||||
) {
|
||||
stream(id: $stream_id) {
|
||||
branches(limit: $branches_limit) {
|
||||
items {
|
||||
id
|
||||
name
|
||||
description
|
||||
commits(limit: $commits_limit) {
|
||||
totalCount
|
||||
items{
|
||||
id
|
||||
message
|
||||
referencedObject
|
||||
sourceApplication
|
||||
parents
|
||||
authorId
|
||||
authorName
|
||||
branchName
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {
|
||||
"stream_id": stream_id,
|
||||
"branches_limit": branches_limit,
|
||||
"commits_limit": commits_limit,
|
||||
}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type=["stream", "branches", "items"]
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def update(
|
||||
self,
|
||||
stream_id: str,
|
||||
branch_id: str,
|
||||
name: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
):
|
||||
"""Update a branch
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream containing the branch to update
|
||||
branch_id {str} -- the id of the branch to update
|
||||
name {str} -- optional: the updated branch name
|
||||
description {str} -- optional: the updated branch description
|
||||
|
||||
Returns:
|
||||
bool -- True if update is successful
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
mutation BranchUpdate($branch: BranchUpdateInput!) {
|
||||
branchUpdate(branch: $branch)
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {
|
||||
"branch": {
|
||||
"streamId": stream_id,
|
||||
"id": branch_id,
|
||||
}
|
||||
}
|
||||
|
||||
if name:
|
||||
params["branch"]["name"] = name
|
||||
if description:
|
||||
params["branch"]["description"] = description
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="branchUpdate", parse_response=False
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def delete(self, stream_id: str, branch_id: str):
|
||||
"""Delete a branch
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream containing the branch to delete
|
||||
branch_id {str} -- the branch to delete
|
||||
|
||||
Returns:
|
||||
bool -- True if deletion is successful
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
mutation BranchDelete($branch: BranchDeleteInput!) {
|
||||
branchDelete(branch: $branch)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"branch": {"streamId": stream_id, "id": branch_id}}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="branchDelete", parse_response=False
|
||||
)
|
||||
@@ -1,252 +0,0 @@
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from deprecated import deprecated
|
||||
from gql import gql
|
||||
|
||||
from specklepy.core.api.models.deprecated import (
|
||||
FE1_DEPRECATION_REASON,
|
||||
FE1_DEPRECATION_VERSION,
|
||||
Commit,
|
||||
)
|
||||
from specklepy.core.api.resource import ResourceBase
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
|
||||
NAME = "commit"
|
||||
|
||||
|
||||
class Resource(ResourceBase):
|
||||
"""
|
||||
API Access class for commits
|
||||
Commit resource is deprecated, please use version resource instead
|
||||
"""
|
||||
|
||||
def __init__(self, account, basepath, client) -> None:
|
||||
super().__init__(
|
||||
account=account,
|
||||
basepath=basepath,
|
||||
client=client,
|
||||
name=NAME,
|
||||
)
|
||||
self.schema = Commit
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def get(self, stream_id: str, commit_id: str) -> Commit:
|
||||
"""
|
||||
Gets a commit given a stream and the commit id
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the stream where we can find the commit
|
||||
commit_id {str} -- the id of the commit you want to get
|
||||
|
||||
Returns:
|
||||
Commit -- the retrieved commit object
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
query Commit($stream_id: String!, $commit_id: String!) {
|
||||
stream(id: $stream_id) {
|
||||
commit(id: $commit_id) {
|
||||
id
|
||||
message
|
||||
referencedObject
|
||||
authorId
|
||||
authorName
|
||||
authorAvatar
|
||||
branchName
|
||||
createdAt
|
||||
sourceApplication
|
||||
totalChildrenCount
|
||||
parents
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {"stream_id": stream_id, "commit_id": commit_id}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type=["stream", "commit"]
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def list(self, stream_id: str, limit: int = 10) -> List[Commit]:
|
||||
"""
|
||||
Get a list of commits on a given stream
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the stream where the commits are
|
||||
limit {int} -- the maximum number of commits to fetch (default = 10)
|
||||
|
||||
Returns:
|
||||
List[Commit] -- a list of the most recent commit objects
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
query Commits($stream_id: String!, $limit: Int!) {
|
||||
stream(id: $stream_id) {
|
||||
commits(limit: $limit) {
|
||||
items {
|
||||
id
|
||||
message
|
||||
referencedObject
|
||||
authorName
|
||||
authorId
|
||||
authorName
|
||||
authorAvatar
|
||||
branchName
|
||||
createdAt
|
||||
sourceApplication
|
||||
totalChildrenCount
|
||||
parents
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {"stream_id": stream_id, "limit": limit}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type=["stream", "commits", "items"]
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def create(
|
||||
self,
|
||||
stream_id: str,
|
||||
object_id: str,
|
||||
branch_name: str = "main",
|
||||
message: str = "",
|
||||
source_application: str = "python",
|
||||
parents: Optional[List[str]] = None,
|
||||
) -> Union[str, SpeckleException]:
|
||||
"""
|
||||
Creates a commit on a branch
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the stream you want to commit to
|
||||
object_id {str} -- the hash of your commit object
|
||||
branch_name {str}
|
||||
-- the name of the branch to commit to (defaults to "main")
|
||||
message {str}
|
||||
-- optional: a message to give more information about the commit
|
||||
source_application{str}
|
||||
-- optional: the application from which the commit was created
|
||||
(defaults to "python")
|
||||
parents {List[str]} -- optional: the id of the parent commits
|
||||
|
||||
Returns:
|
||||
str -- the id of the created commit
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
mutation CommitCreate ($commit: CommitCreateInput!)
|
||||
{ commitCreate(commit: $commit)}
|
||||
"""
|
||||
)
|
||||
params = {
|
||||
"commit": {
|
||||
"streamId": stream_id,
|
||||
"branchName": branch_name,
|
||||
"objectId": object_id,
|
||||
"message": message,
|
||||
"sourceApplication": source_application,
|
||||
}
|
||||
}
|
||||
if parents:
|
||||
params["commit"]["parents"] = parents
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="commitCreate", parse_response=False
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def update(self, stream_id: str, commit_id: str, message: str) -> bool:
|
||||
"""
|
||||
Update a commit
|
||||
|
||||
Arguments:
|
||||
stream_id {str}
|
||||
-- the id of the stream that contains the commit you'd like to update
|
||||
commit_id {str} -- the id of the commit you'd like to update
|
||||
message {str} -- the updated commit message
|
||||
|
||||
Returns:
|
||||
bool -- True if the operation succeeded
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
mutation CommitUpdate($commit: CommitUpdateInput!)
|
||||
{ commitUpdate(commit: $commit)}
|
||||
"""
|
||||
)
|
||||
params = {
|
||||
"commit": {"streamId": stream_id, "id": commit_id, "message": message}
|
||||
}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="commitUpdate", parse_response=False
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def delete(self, stream_id: str, commit_id: str) -> bool:
|
||||
"""
|
||||
Delete a commit
|
||||
|
||||
Arguments:
|
||||
stream_id {str}
|
||||
-- the id of the stream that contains the commit you'd like to delete
|
||||
commit_id {str} -- the id of the commit you'd like to delete
|
||||
|
||||
Returns:
|
||||
bool -- True if the operation succeeded
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
mutation CommitDelete($commit: CommitDeleteInput!)
|
||||
{ commitDelete(commit: $commit)}
|
||||
"""
|
||||
)
|
||||
params = {"commit": {"streamId": stream_id, "id": commit_id}}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="commitDelete", parse_response=False
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def received(
|
||||
self,
|
||||
stream_id: str,
|
||||
commit_id: str,
|
||||
source_application: str = "python",
|
||||
message: Optional[str] = None,
|
||||
) -> bool:
|
||||
"""
|
||||
Mark a commit object a received by the source application.
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
mutation CommitReceive($receivedInput:CommitReceivedInput!){
|
||||
commitReceive(input:$receivedInput)
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {
|
||||
"receivedInput": {
|
||||
"sourceApplication": source_application,
|
||||
"streamId": stream_id,
|
||||
"commitId": commit_id,
|
||||
"message": "message",
|
||||
}
|
||||
}
|
||||
|
||||
try:
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type="commitReceive",
|
||||
parse_response=False,
|
||||
)
|
||||
except Exception as ex:
|
||||
print(ex.with_traceback)
|
||||
return False
|
||||
@@ -1,92 +0,0 @@
|
||||
from typing import Dict, List
|
||||
|
||||
from gql import gql
|
||||
|
||||
from specklepy.core.api.resource import ResourceBase
|
||||
from specklepy.objects.base import Base
|
||||
|
||||
NAME = "object"
|
||||
|
||||
|
||||
class Resource(ResourceBase):
|
||||
"""API Access class for objects"""
|
||||
|
||||
def __init__(self, account, basepath, client) -> None:
|
||||
super().__init__(
|
||||
account=account,
|
||||
basepath=basepath,
|
||||
client=client,
|
||||
name=NAME,
|
||||
)
|
||||
self.schema = Base
|
||||
|
||||
def get(self, stream_id: str, object_id: str) -> Base:
|
||||
"""
|
||||
Get a stream object
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream for the object
|
||||
object_id {str} -- the hash of the object you want to get
|
||||
|
||||
Returns:
|
||||
Base -- the returned Base object
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
query Object($stream_id: String!, $object_id: String!) {
|
||||
stream(id: $stream_id) {
|
||||
id
|
||||
name
|
||||
object(id: $object_id) {
|
||||
id
|
||||
speckleType
|
||||
applicationId
|
||||
createdAt
|
||||
totalChildrenCount
|
||||
data
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {"stream_id": stream_id, "object_id": object_id}
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type=["stream", "object", "data"],
|
||||
)
|
||||
|
||||
def create(self, stream_id: str, objects: List[Dict]) -> str:
|
||||
"""
|
||||
Not advised - generally, you want to use `operations.send()`.
|
||||
|
||||
Create a new object on a stream.
|
||||
To send a base object, you can prepare it by running it through the
|
||||
`BaseObjectSerializer.traverse_base()` function to get a valid (serialisable)
|
||||
object to send.
|
||||
|
||||
NOTE: this does not create a commit - you can create one with
|
||||
`SpeckleClient.commit.create`.
|
||||
Dynamic fields will be located in the 'data' dict of the received `Base` object
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream you want to send the object to
|
||||
objects {List[Dict]}
|
||||
-- a list of base dictionary objects (NOTE: must be json serialisable)
|
||||
|
||||
Returns:
|
||||
str -- the id of the object
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
mutation ObjectCreate($object_input: ObjectCreateInput!) {
|
||||
objectCreate(objectInput: $object_input)
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {"object_input": {"streamId": stream_id, "objects": objects}}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="objectCreate", parse_response=False
|
||||
)
|
||||
@@ -1,15 +0,0 @@
|
||||
from deprecated import deprecated
|
||||
|
||||
from specklepy.core.api.models.deprecated import FE1_DEPRECATION_VERSION
|
||||
from specklepy.core.api.resources import OtherUserResource
|
||||
|
||||
|
||||
@deprecated(
|
||||
reason="Class renamed to OtherUserResource", version=FE1_DEPRECATION_VERSION
|
||||
)
|
||||
class Resource(OtherUserResource):
|
||||
"""
|
||||
Class renamed to OtherUserResource
|
||||
"""
|
||||
|
||||
pass
|
||||
@@ -1,11 +0,0 @@
|
||||
from deprecated import deprecated
|
||||
|
||||
from specklepy.core.api.models.deprecated import FE1_DEPRECATION_VERSION
|
||||
from specklepy.core.api.resources import ServerResource
|
||||
|
||||
NAME = "server"
|
||||
|
||||
|
||||
@deprecated(reason="Renamed to ServerResource", version=FE1_DEPRECATION_VERSION)
|
||||
class Resource(ServerResource):
|
||||
"""API Access class for the server"""
|
||||
@@ -1,785 +0,0 @@
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Optional
|
||||
|
||||
from deprecated import deprecated
|
||||
from gql import gql
|
||||
|
||||
from specklepy.core.api.models import (
|
||||
ActivityCollection,
|
||||
PendingStreamCollaborator,
|
||||
Stream,
|
||||
)
|
||||
from specklepy.core.api.models.deprecated import (
|
||||
FE1_DEPRECATION_REASON,
|
||||
FE1_DEPRECATION_VERSION,
|
||||
)
|
||||
from specklepy.core.api.resource import ResourceBase
|
||||
from specklepy.logging.exceptions import SpeckleException, UnsupportedException
|
||||
|
||||
NAME = "stream"
|
||||
|
||||
|
||||
class Resource(ResourceBase):
|
||||
"""
|
||||
API Access class for streams
|
||||
Stream resource is deprecated, please use project resource instead
|
||||
"""
|
||||
|
||||
def __init__(self, account, basepath, client, server_version) -> None:
|
||||
super().__init__(
|
||||
account=account,
|
||||
basepath=basepath,
|
||||
client=client,
|
||||
name=NAME,
|
||||
server_version=server_version,
|
||||
)
|
||||
|
||||
self.schema = Stream
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def get(self, id: str, branch_limit: int = 10, commit_limit: int = 10) -> Stream:
|
||||
"""Get the specified stream from the server
|
||||
|
||||
Arguments:
|
||||
id {str} -- the stream id
|
||||
branch_limit {int} -- the maximum number of branches to return
|
||||
commit_limit {int} -- the maximum number of commits to return
|
||||
|
||||
Returns:
|
||||
Stream -- the retrieved stream
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
query Stream($id: String!, $branch_limit: Int!, $commit_limit: Int!) {
|
||||
stream(id: $id) {
|
||||
id
|
||||
name
|
||||
role
|
||||
description
|
||||
isPublic
|
||||
createdAt
|
||||
updatedAt
|
||||
commentCount
|
||||
favoritesCount
|
||||
collaborators {
|
||||
id
|
||||
name
|
||||
role
|
||||
avatar
|
||||
}
|
||||
branches(limit: $branch_limit) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
id
|
||||
name
|
||||
description
|
||||
commits(limit: $commit_limit) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
id
|
||||
message
|
||||
authorId
|
||||
createdAt
|
||||
authorName
|
||||
referencedObject
|
||||
sourceApplication
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"id": id, "branch_limit": branch_limit, "commit_limit": commit_limit}
|
||||
|
||||
return self.make_request(query=query, params=params, return_type="stream")
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def list(self, stream_limit: int = 10) -> List[Stream]:
|
||||
"""Get a list of the user's streams
|
||||
|
||||
Arguments:
|
||||
stream_limit {int} -- The maximum number of streams to return
|
||||
|
||||
Returns:
|
||||
List[Stream] -- A list of Stream objects
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
query User($stream_limit: Int!) {
|
||||
activeUser {
|
||||
id
|
||||
bio
|
||||
name
|
||||
email
|
||||
avatar
|
||||
company
|
||||
verified
|
||||
profiles
|
||||
role
|
||||
streams(limit: $stream_limit) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
id
|
||||
name
|
||||
role
|
||||
isPublic
|
||||
createdAt
|
||||
updatedAt
|
||||
description
|
||||
commentCount
|
||||
favoritesCount
|
||||
collaborators {
|
||||
id
|
||||
name
|
||||
role
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"stream_limit": stream_limit}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type=["activeUser", "streams", "items"]
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def create(
|
||||
self,
|
||||
name: str = "Anonymous Python Stream",
|
||||
description: str = "No description provided",
|
||||
is_public: bool = True,
|
||||
) -> str:
|
||||
"""Create a new stream
|
||||
|
||||
Arguments:
|
||||
name {str} -- the name of the string
|
||||
description {str} -- a short description of the stream
|
||||
is_public {bool}
|
||||
-- whether or not the stream can be viewed by anyone with the id
|
||||
|
||||
Returns:
|
||||
id {str} -- the id of the newly created stream
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamCreate($stream: StreamCreateInput!) {
|
||||
streamCreate(stream: $stream)
|
||||
}
|
||||
"""
|
||||
)
|
||||
if len(name) < 3 and len(name) != 0:
|
||||
return SpeckleException(message="Stream Name must be at least 3 characters")
|
||||
params = {
|
||||
"stream": {"name": name, "description": description, "isPublic": is_public}
|
||||
}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="streamCreate", parse_response=False
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def update(
|
||||
self,
|
||||
id: str,
|
||||
name: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
is_public: Optional[bool] = None,
|
||||
) -> bool:
|
||||
"""Update an existing stream
|
||||
|
||||
Arguments:
|
||||
id {str} -- the id of the stream to be updated
|
||||
name {str} -- the name of the string
|
||||
description {str} -- a short description of the stream
|
||||
is_public {bool}
|
||||
-- whether or not the stream can be viewed by anyone with the id
|
||||
|
||||
Returns:
|
||||
bool -- whether the stream update was successful
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamUpdate($stream: StreamUpdateInput!) {
|
||||
streamUpdate(stream: $stream)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {
|
||||
"id": id,
|
||||
"name": name,
|
||||
"description": description,
|
||||
"isPublic": is_public,
|
||||
}
|
||||
# remove None values so graphql doesn't cry
|
||||
params = {"stream": {k: v for k, v in params.items() if v is not None}}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="streamUpdate", parse_response=False
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def delete(self, id: str) -> bool:
|
||||
"""Delete a stream given its id
|
||||
|
||||
Arguments:
|
||||
id {str} -- the id of the stream to delete
|
||||
|
||||
Returns:
|
||||
bool -- whether the deletion was successful
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamDelete($id: String!) {
|
||||
streamDelete(id: $id)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"id": id}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="streamDelete", parse_response=False
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def search(
|
||||
self,
|
||||
search_query: str,
|
||||
limit: int = 25,
|
||||
branch_limit: int = 10,
|
||||
commit_limit: int = 10,
|
||||
):
|
||||
"""Search for streams by name, description, or id
|
||||
|
||||
Arguments:
|
||||
search_query {str} -- a string to search for
|
||||
limit {int} -- the maximum number of results to return
|
||||
branch_limit {int} -- the maximum number of branches to return
|
||||
commit_limit {int} -- the maximum number of commits to return
|
||||
|
||||
Returns:
|
||||
List[Stream] -- a list of Streams that match the search query
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
query StreamSearch(
|
||||
$search_query: String!,
|
||||
$limit: Int!,
|
||||
$branch_limit:Int!,
|
||||
$commit_limit:Int!
|
||||
) {
|
||||
streams(query: $search_query, limit: $limit) {
|
||||
items {
|
||||
id
|
||||
name
|
||||
role
|
||||
description
|
||||
isPublic
|
||||
createdAt
|
||||
updatedAt
|
||||
collaborators {
|
||||
id
|
||||
name
|
||||
role
|
||||
avatar
|
||||
}
|
||||
branches(limit: $branch_limit) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
id
|
||||
name
|
||||
description
|
||||
commits(limit: $commit_limit) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
id
|
||||
referencedObject
|
||||
message
|
||||
authorName
|
||||
authorId
|
||||
createdAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {
|
||||
"search_query": search_query,
|
||||
"limit": limit,
|
||||
"branch_limit": branch_limit,
|
||||
"commit_limit": commit_limit,
|
||||
}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type=["streams", "items"]
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def favorite(self, stream_id: str, favorited: bool = True):
|
||||
"""Favorite or unfavorite the given stream.
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to favorite / unfavorite
|
||||
favorited {bool}
|
||||
-- whether to favorite (True) or unfavorite (False) the stream
|
||||
|
||||
Returns:
|
||||
Stream -- the stream with its `id`, `name`, and `favoritedDate`
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamFavorite($stream_id: String!, $favorited: Boolean!) {
|
||||
streamFavorite(streamId: $stream_id, favorited: $favorited) {
|
||||
id
|
||||
name
|
||||
favoritedDate
|
||||
favoritesCount
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {
|
||||
"stream_id": stream_id,
|
||||
"favorited": favorited,
|
||||
}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type=["streamFavorite"]
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def get_all_pending_invites(
|
||||
self, stream_id: str
|
||||
) -> List[PendingStreamCollaborator]:
|
||||
"""Get all of the pending invites on a stream.
|
||||
You must be a `stream:owner` to query this.
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the stream id from which to get the pending invites
|
||||
|
||||
Returns:
|
||||
List[PendingStreamCollaborator]
|
||||
-- a list of pending invites for the specified stream
|
||||
"""
|
||||
self._check_invites_supported()
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
query StreamInvites($streamId: String!) {
|
||||
stream(id: $streamId){
|
||||
pendingCollaborators {
|
||||
id
|
||||
token
|
||||
inviteId
|
||||
streamId
|
||||
streamName
|
||||
projectName
|
||||
projectId
|
||||
title
|
||||
role
|
||||
invitedBy{
|
||||
id
|
||||
name
|
||||
bio
|
||||
company
|
||||
avatar
|
||||
verified
|
||||
role
|
||||
}
|
||||
user {
|
||||
id
|
||||
name
|
||||
bio
|
||||
company
|
||||
avatar
|
||||
verified
|
||||
role
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {"streamId": stream_id}
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type=["stream", "pendingCollaborators"],
|
||||
schema=PendingStreamCollaborator,
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def invite(
|
||||
self,
|
||||
stream_id: str,
|
||||
email: Optional[str] = None,
|
||||
user_id: Optional[str] = None,
|
||||
role: str = "stream:contributor", # should default be reviewer?
|
||||
message: Optional[str] = None,
|
||||
):
|
||||
"""Invite someone to a stream using either their email or user id
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to invite the user to
|
||||
email {str} -- the email of the user to invite (use this OR `user_id`)
|
||||
user_id {str} -- the id of the user to invite (use this OR `email`)
|
||||
role {str}
|
||||
-- the role to assign to the user (defaults to `stream:contributor`)
|
||||
message {str}
|
||||
-- a message to send along with this invite to the specified user
|
||||
|
||||
Returns:
|
||||
bool -- True if the operation was successful
|
||||
"""
|
||||
self._check_invites_supported()
|
||||
|
||||
if email is None and user_id is None:
|
||||
raise SpeckleException(
|
||||
"You must provide either an email or a user id to use the"
|
||||
" `stream.invite` method"
|
||||
)
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamInviteCreate($input: StreamInviteCreateInput!) {
|
||||
streamInviteCreate(input: $input)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {
|
||||
"email": email,
|
||||
"userId": user_id,
|
||||
"streamId": stream_id,
|
||||
"message": message,
|
||||
"role": role,
|
||||
}
|
||||
params = {"input": {k: v for k, v in params.items() if v is not None}}
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type="streamInviteCreate",
|
||||
parse_response=False,
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def invite_batch(
|
||||
self,
|
||||
stream_id: str,
|
||||
emails: Optional[List[str]] = None,
|
||||
user_ids: Optional[List[None]] = None,
|
||||
message: Optional[str] = None,
|
||||
) -> bool:
|
||||
"""Invite a batch of users to a specified stream.
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to invite the user to
|
||||
emails {List[str]}
|
||||
-- the email of the user to invite (use this and/or `user_ids`)
|
||||
user_id {List[str]}
|
||||
-- the id of the user to invite (use this and/or `emails`)
|
||||
message {str}
|
||||
-- a message to send along with this invite to the specified user
|
||||
|
||||
Returns:
|
||||
bool -- True if the operation was successful
|
||||
"""
|
||||
self._check_invites_supported()
|
||||
if emails is None and user_ids is None:
|
||||
raise SpeckleException(
|
||||
"You must provide either an email or a user id to use the"
|
||||
" `stream.invite` method"
|
||||
)
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamInviteBatchCreate($input: [StreamInviteCreateInput!]!) {
|
||||
streamInviteBatchCreate(input: $input)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
email_invites = [
|
||||
{"streamId": stream_id, "message": message, "email": email}
|
||||
for email in (emails if emails is not None else [])
|
||||
if email is not None
|
||||
]
|
||||
|
||||
user_invites = [
|
||||
{"streamId": stream_id, "message": message, "userId": user_id}
|
||||
for user_id in (user_ids if user_ids is not None else [])
|
||||
if user_id is not None
|
||||
]
|
||||
|
||||
params = {"input": [*email_invites, *user_invites]}
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type="streamInviteBatchCreate",
|
||||
parse_response=False,
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def invite_cancel(self, stream_id: str, invite_id: str) -> bool:
|
||||
"""Cancel an existing stream invite
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream invite
|
||||
invite_id {str} -- the id of the invite to use
|
||||
|
||||
Returns:
|
||||
bool -- true if the operation was successful
|
||||
"""
|
||||
self._check_invites_supported()
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamInviteCancel($streamId: String!, $inviteId: String!) {
|
||||
streamInviteCancel(streamId: $streamId, inviteId: $inviteId)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"streamId": stream_id, "inviteId": invite_id}
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type="streamInviteCancel",
|
||||
parse_response=False,
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def invite_use(self, stream_id: str, token: str, accept: bool = True) -> bool:
|
||||
"""Accept or decline a stream invite
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
|
||||
Arguments:
|
||||
stream_id {str}
|
||||
-- the id of the stream for which the user has a pending invite
|
||||
token {str} -- the token of the invite to use
|
||||
accept {bool} -- whether or not to accept the invite (defaults to True)
|
||||
|
||||
Returns:
|
||||
bool -- true if the operation was successful
|
||||
"""
|
||||
self._check_invites_supported()
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamInviteUse(
|
||||
$accept: Boolean!,
|
||||
$streamId: String!,
|
||||
$token: String!
|
||||
) {
|
||||
streamInviteUse(accept: $accept, streamId: $streamId, token: $token)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"streamId": stream_id, "token": token, "accept": accept}
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type="streamInviteUse",
|
||||
parse_response=False,
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def update_permission(self, stream_id: str, user_id: str, role: str):
|
||||
"""Updates permissions for a user on a given stream
|
||||
|
||||
Valid for Speckle Server >=2.6.4
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to grant permissions to
|
||||
user_id {str} -- the id of the user to grant permissions for
|
||||
role {str} -- the role to grant the user
|
||||
|
||||
Returns:
|
||||
bool -- True if the operation was successful
|
||||
"""
|
||||
if self.server_version and (
|
||||
self.server_version != ("dev",) and self.server_version < (2, 6, 4)
|
||||
):
|
||||
raise UnsupportedException(
|
||||
"Server mutation `update_permission` is only supported as of Speckle"
|
||||
" Server v2.6.4. Please update your Speckle Server to use this method"
|
||||
" or use the `grant_permission` method instead."
|
||||
)
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamUpdatePermission(
|
||||
$permission_params: StreamUpdatePermissionInput!
|
||||
) {
|
||||
streamUpdatePermission(permissionParams: $permission_params)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {
|
||||
"permission_params": {
|
||||
"streamId": stream_id,
|
||||
"userId": user_id,
|
||||
"role": role,
|
||||
}
|
||||
}
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type="streamUpdatePermission",
|
||||
parse_response=False,
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def revoke_permission(self, stream_id: str, user_id: str):
|
||||
"""Revoke permissions from a user on a given stream
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to revoke permissions from
|
||||
user_id {str} -- the id of the user to revoke permissions from
|
||||
|
||||
Returns:
|
||||
bool -- True if the operation was successful
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
mutation StreamRevokePermission(
|
||||
$permission_params: StreamRevokePermissionInput!
|
||||
) {
|
||||
streamRevokePermission(permissionParams: $permission_params)
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"permission_params": {"streamId": stream_id, "userId": user_id}}
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type="streamRevokePermission",
|
||||
parse_response=False,
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
def activity(
|
||||
self,
|
||||
stream_id: str,
|
||||
action_type: Optional[str] = None,
|
||||
limit: int = 20,
|
||||
before: Optional[datetime] = None,
|
||||
after: Optional[datetime] = None,
|
||||
cursor: Optional[datetime] = None,
|
||||
):
|
||||
"""
|
||||
Get the activity from a given stream in an Activity collection.
|
||||
Step into the activity `items` for the list of activity.
|
||||
|
||||
Note: all timestamps arguments should be `datetime` of any tz
|
||||
as they will be converted to UTC ISO format strings
|
||||
|
||||
stream_id {str} -- the id of the stream to get activity from
|
||||
action_type {str}
|
||||
-- filter results to a single action type
|
||||
(eg: `commit_create` or `commit_receive`)
|
||||
limit {int} -- max number of Activity items to return
|
||||
before {datetime}
|
||||
-- latest cutoff for activity (ie: return all activity _before_ this time)
|
||||
after {datetime}
|
||||
-- oldest cutoff for activity (ie: return all activity _after_ this time)
|
||||
cursor {datetime} -- timestamp cursor for pagination
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
query StreamActivity(
|
||||
$stream_id: String!,
|
||||
$action_type: String,
|
||||
$before:DateTime,
|
||||
$after: DateTime,
|
||||
$cursor: DateTime,
|
||||
$limit: Int
|
||||
){
|
||||
stream(id: $stream_id) {
|
||||
activity(
|
||||
actionType: $action_type,
|
||||
before: $before,
|
||||
after: $after,
|
||||
cursor: $cursor,
|
||||
limit: $limit
|
||||
) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
actionType
|
||||
info
|
||||
userId
|
||||
streamId
|
||||
resourceId
|
||||
resourceType
|
||||
message
|
||||
time
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
try:
|
||||
params = {
|
||||
"stream_id": stream_id,
|
||||
"limit": limit,
|
||||
"action_type": action_type,
|
||||
"before": (
|
||||
before.astimezone(timezone.utc).isoformat() if before else before
|
||||
),
|
||||
"after": after.astimezone(timezone.utc).isoformat() if after else after,
|
||||
"cursor": (
|
||||
cursor.astimezone(timezone.utc).isoformat() if cursor else cursor
|
||||
),
|
||||
}
|
||||
except AttributeError as e:
|
||||
raise SpeckleException(
|
||||
"Could not get stream activity - `before`, `after`, and `cursor` must"
|
||||
" be in `datetime` format if provided",
|
||||
ValueError(),
|
||||
) from e
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type=["stream", "activity"],
|
||||
schema=ActivityCollection,
|
||||
)
|
||||
@@ -1,144 +0,0 @@
|
||||
from functools import wraps
|
||||
from typing import Callable, Dict, List, Optional, Union
|
||||
|
||||
from deprecated import deprecated
|
||||
from gql import gql
|
||||
from graphql import DocumentNode
|
||||
|
||||
from specklepy.core.api.models.deprecated import (
|
||||
FE1_DEPRECATION_REASON,
|
||||
FE1_DEPRECATION_VERSION,
|
||||
Stream,
|
||||
)
|
||||
from specklepy.core.api.resource import ResourceBase
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
|
||||
NAME = "subscribe"
|
||||
|
||||
|
||||
def check_wsclient(function):
|
||||
@wraps(function)
|
||||
async def check_wsclient_wrapper(self, *args, **kwargs):
|
||||
if self.client is None:
|
||||
raise SpeckleException(
|
||||
"You must authenticate before you can subscribe to events"
|
||||
)
|
||||
else:
|
||||
return await function(self, *args, **kwargs)
|
||||
|
||||
return check_wsclient_wrapper
|
||||
|
||||
|
||||
class Resource(ResourceBase):
|
||||
"""API Access class for subscriptions"""
|
||||
|
||||
def __init__(self, account, basepath, client) -> None:
|
||||
super().__init__(
|
||||
account=account,
|
||||
basepath=basepath,
|
||||
client=client,
|
||||
name=NAME,
|
||||
)
|
||||
|
||||
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
|
||||
@check_wsclient
|
||||
async def stream_added(self, callback: Optional[Callable] = None):
|
||||
"""Subscribes to new stream added event for your profile.
|
||||
Use this to display an up-to-date list of streams.
|
||||
|
||||
Arguments:
|
||||
callback {Callable[Stream]} -- a function that takes the updated stream
|
||||
as an argument and executes each time a stream is added
|
||||
|
||||
Returns:
|
||||
Stream -- the update stream
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
subscription { userStreamAdded }
|
||||
"""
|
||||
)
|
||||
return await self.subscribe(
|
||||
query=query, callback=callback, return_type="userStreamAdded", schema=Stream
|
||||
)
|
||||
|
||||
@check_wsclient
|
||||
async def stream_updated(self, id: str, callback: Optional[Callable] = None):
|
||||
"""
|
||||
Subscribes to stream updated event.
|
||||
Use this in clients/components that pertain only to this stream.
|
||||
|
||||
Arguments:
|
||||
id {str} -- the stream id of the stream to subscribe to
|
||||
callback {Callable[Stream]}
|
||||
-- a function that takes the updated stream
|
||||
as an argument and executes each time the stream is updated
|
||||
|
||||
Returns:
|
||||
Stream -- the update stream
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
subscription Update($id: String!) { streamUpdated(streamId: $id) }
|
||||
"""
|
||||
)
|
||||
params = {"id": id}
|
||||
|
||||
return await self.subscribe(
|
||||
query=query,
|
||||
params=params,
|
||||
callback=callback,
|
||||
return_type="streamUpdated",
|
||||
schema=Stream,
|
||||
)
|
||||
|
||||
@check_wsclient
|
||||
async def stream_removed(self, callback: Optional[Callable] = None):
|
||||
"""Subscribes to stream removed event for your profile.
|
||||
Use this to display an up-to-date list of streams for your profile.
|
||||
NOTE: If someone revokes your permissions on a stream,
|
||||
this subscription will be triggered with an extra value of revokedBy
|
||||
in the payload.
|
||||
|
||||
Arguments:
|
||||
callback {Callable[Dict]}
|
||||
-- a function that takes the returned dict as an argument
|
||||
and executes each time a stream is removed
|
||||
|
||||
Returns:
|
||||
dict -- dict containing 'id' of stream removed and optionally 'revokedBy'
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
subscription { userStreamRemoved }
|
||||
"""
|
||||
)
|
||||
|
||||
return await self.subscribe(
|
||||
query=query,
|
||||
callback=callback,
|
||||
return_type="userStreamRemoved",
|
||||
parse_response=False,
|
||||
)
|
||||
|
||||
@check_wsclient
|
||||
async def subscribe(
|
||||
self,
|
||||
query: DocumentNode,
|
||||
params: Optional[Dict] = None,
|
||||
callback: Optional[Callable] = None,
|
||||
return_type: Optional[Union[str, List]] = None,
|
||||
schema=None,
|
||||
parse_response: bool = True,
|
||||
):
|
||||
# if self.client.transport.websocket is None:
|
||||
# TODO: add multiple subs to the same ws connection
|
||||
async with self.client as session:
|
||||
async for res in session.subscribe(query, variable_values=params):
|
||||
res = self._step_into_response(response=res, return_type=return_type)
|
||||
if parse_response:
|
||||
res = self._parse_response(response=res, schema=schema)
|
||||
if callback is not None:
|
||||
callback(res)
|
||||
else:
|
||||
return res
|
||||
@@ -1,325 +0,0 @@
|
||||
from datetime import datetime, timezone
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from deprecated import deprecated
|
||||
from gql import gql
|
||||
|
||||
from specklepy.core.api.models import (
|
||||
ActivityCollection,
|
||||
PendingStreamCollaborator,
|
||||
User,
|
||||
)
|
||||
from specklepy.core.api.resource import ResourceBase
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
|
||||
NAME = "user"
|
||||
|
||||
DEPRECATION_VERSION = "2.9.0"
|
||||
DEPRECATION_TEXT = (
|
||||
"The user resource is deprecated, please use the active_user or other_user"
|
||||
" resources"
|
||||
)
|
||||
|
||||
|
||||
class Resource(ResourceBase):
|
||||
"""API Access class for users"""
|
||||
|
||||
def __init__(self, account, basepath, client, server_version) -> None:
|
||||
super().__init__(
|
||||
account=account,
|
||||
basepath=basepath,
|
||||
client=client,
|
||||
name=NAME,
|
||||
server_version=server_version,
|
||||
)
|
||||
self.schema = User
|
||||
|
||||
@deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT)
|
||||
def get(self, id: Optional[str] = None) -> User:
|
||||
"""
|
||||
Gets the profile of a user.
|
||||
If no id argument is provided, will return the current authenticated
|
||||
user's profile (as extracted from the authorization header).
|
||||
|
||||
Arguments:
|
||||
id {str} -- the user id
|
||||
|
||||
Returns:
|
||||
User -- the retrieved user
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
query User($id: String) {
|
||||
user(id: $id) {
|
||||
id
|
||||
email
|
||||
name
|
||||
bio
|
||||
company
|
||||
avatar
|
||||
verified
|
||||
profiles
|
||||
role
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"id": id}
|
||||
|
||||
return self.make_request(query=query, params=params, return_type="user")
|
||||
|
||||
@deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT)
|
||||
def search(
|
||||
self, search_query: str, limit: int = 25
|
||||
) -> Union[List[User], SpeckleException]:
|
||||
"""
|
||||
Searches for user by name or email.
|
||||
The search query must be at least 3 characters long
|
||||
|
||||
Arguments:
|
||||
search_query {str} -- a string to search for
|
||||
limit {int} -- the maximum number of results to return
|
||||
Returns:
|
||||
List[User] -- a list of User objects that match the search query
|
||||
"""
|
||||
if len(search_query) < 3:
|
||||
return SpeckleException(
|
||||
message="User search query must be at least 3 characters"
|
||||
)
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
query UserSearch($search_query: String!, $limit: Int!) {
|
||||
userSearch(query: $search_query, limit: $limit) {
|
||||
items {
|
||||
id
|
||||
name
|
||||
bio
|
||||
company
|
||||
avatar
|
||||
verified
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {"search_query": search_query, "limit": limit}
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type=["userSearch", "items"]
|
||||
)
|
||||
|
||||
@deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT)
|
||||
def update(
|
||||
self,
|
||||
name: Optional[str] = None,
|
||||
company: Optional[str] = None,
|
||||
bio: Optional[str] = None,
|
||||
avatar: Optional[str] = None,
|
||||
):
|
||||
"""Updates your user profile. All arguments are optional.
|
||||
|
||||
Arguments:
|
||||
name {str} -- your name
|
||||
company {str} -- the company you may or may not work for
|
||||
bio {str} -- tell us about yourself
|
||||
avatar {str} -- a nice photo of yourself
|
||||
|
||||
Returns:
|
||||
bool -- True if your profile was updated successfully
|
||||
"""
|
||||
query = gql(
|
||||
"""
|
||||
mutation UserUpdate($user: UserUpdateInput!) {
|
||||
userUpdate(user: $user)
|
||||
}
|
||||
"""
|
||||
)
|
||||
params = {"name": name, "company": company, "bio": bio, "avatar": avatar}
|
||||
|
||||
params = {"user": {k: v for k, v in params.items() if v is not None}}
|
||||
|
||||
if not params["user"]:
|
||||
return SpeckleException(
|
||||
message=(
|
||||
"You must provide at least one field to update your user profile"
|
||||
)
|
||||
)
|
||||
|
||||
return self.make_request(
|
||||
query=query, params=params, return_type="userUpdate", parse_response=False
|
||||
)
|
||||
|
||||
@deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT)
|
||||
def activity(
|
||||
self,
|
||||
user_id: Optional[str] = None,
|
||||
limit: int = 20,
|
||||
action_type: Optional[str] = None,
|
||||
before: Optional[datetime] = None,
|
||||
after: Optional[datetime] = None,
|
||||
cursor: Optional[datetime] = None,
|
||||
):
|
||||
"""
|
||||
Get the activity from a given stream in an Activity collection.
|
||||
Step into the activity `items` for the list of activity.
|
||||
If no id argument is provided, will return the current authenticated
|
||||
user's activity (as extracted from the authorization header).
|
||||
|
||||
Note: all timestamps arguments should be `datetime` of any tz as
|
||||
they will be converted to UTC ISO format strings
|
||||
|
||||
user_id {str} -- the id of the user to get the activity from
|
||||
action_type {str} -- filter results to a single action type
|
||||
(eg: `commit_create` or `commit_receive`)
|
||||
limit {int} -- max number of Activity items to return
|
||||
before {datetime}
|
||||
-- latest cutoff for activity (ie: return all activity _before_ this time)
|
||||
after {datetime}
|
||||
-- oldest cutoff for activity (ie: return all activity _after_ this time)
|
||||
cursor {datetime} -- timestamp cursor for pagination
|
||||
"""
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
query UserActivity(
|
||||
$user_id: String,
|
||||
$action_type: String,
|
||||
$before:DateTime,
|
||||
$after: DateTime,
|
||||
$cursor: DateTime,
|
||||
$limit: Int
|
||||
){
|
||||
user(id: $user_id) {
|
||||
activity(
|
||||
actionType: $action_type,
|
||||
before: $before,
|
||||
after: $after,
|
||||
cursor: $cursor,
|
||||
limit: $limit
|
||||
) {
|
||||
totalCount
|
||||
cursor
|
||||
items {
|
||||
actionType
|
||||
info
|
||||
userId
|
||||
streamId
|
||||
resourceId
|
||||
resourceType
|
||||
message
|
||||
time
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {
|
||||
"user_id": user_id,
|
||||
"limit": limit,
|
||||
"action_type": action_type,
|
||||
"before": before.astimezone(timezone.utc).isoformat() if before else before,
|
||||
"after": after.astimezone(timezone.utc).isoformat() if after else after,
|
||||
"cursor": cursor.astimezone(timezone.utc).isoformat() if cursor else cursor,
|
||||
}
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type=["user", "activity"],
|
||||
schema=ActivityCollection,
|
||||
)
|
||||
|
||||
@deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT)
|
||||
def get_all_pending_invites(self) -> List[PendingStreamCollaborator]:
|
||||
"""Get all of the active user's pending stream invites
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
|
||||
Returns:
|
||||
List[PendingStreamCollaborator]
|
||||
-- a list of pending invites for the current user
|
||||
"""
|
||||
self._check_invites_supported()
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
query StreamInvites {
|
||||
streamInvites{
|
||||
id
|
||||
token
|
||||
inviteId
|
||||
streamId
|
||||
streamName
|
||||
title
|
||||
role
|
||||
invitedBy {
|
||||
id
|
||||
name
|
||||
company
|
||||
avatar
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
return_type="streamInvites",
|
||||
schema=PendingStreamCollaborator,
|
||||
)
|
||||
|
||||
@deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT)
|
||||
def get_pending_invite(
|
||||
self, stream_id: str, token: Optional[str] = None
|
||||
) -> Optional[PendingStreamCollaborator]:
|
||||
"""Get a particular pending invite for the active user on a given stream.
|
||||
If no invite_id is provided, any valid invite will be returned.
|
||||
|
||||
Requires Speckle Server version >= 2.6.4
|
||||
|
||||
Arguments:
|
||||
stream_id {str} -- the id of the stream to look for invites on
|
||||
token {str} -- the token of the invite to look for (optional)
|
||||
|
||||
Returns:
|
||||
PendingStreamCollaborator
|
||||
-- the invite for the given stream (or None if it isn't found)
|
||||
"""
|
||||
self._check_invites_supported()
|
||||
|
||||
query = gql(
|
||||
"""
|
||||
query StreamInvite($streamId: String!, $token: String) {
|
||||
streamInvite(streamId: $streamId, token: $token) {
|
||||
id
|
||||
token
|
||||
streamId
|
||||
streamName
|
||||
title
|
||||
role
|
||||
invitedBy {
|
||||
id
|
||||
name
|
||||
company
|
||||
avatar
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
)
|
||||
|
||||
params = {"streamId": stream_id}
|
||||
if token:
|
||||
params["token"] = token
|
||||
|
||||
return self.make_request(
|
||||
query=query,
|
||||
params=params,
|
||||
return_type="streamInvite",
|
||||
schema=PendingStreamCollaborator,
|
||||
)
|
||||
@@ -18,7 +18,7 @@ class StreamWrapper:
|
||||
The `StreamWrapper` gives you some handy helpers to deal with urls and
|
||||
get authenticated clients and transports.
|
||||
|
||||
Construct a `StreamWrapper` with a stream, branch, commit, or object URL.
|
||||
Construct a `StreamWrapper` with a URL of a model, version, or object.
|
||||
The corresponding ids will be stored
|
||||
in the wrapper. If you have local accounts on the machine,
|
||||
you can use the `get_account` and `get_client` methods
|
||||
@@ -29,8 +29,8 @@ class StreamWrapper:
|
||||
```py
|
||||
from specklepy.api.wrapper import StreamWrapper
|
||||
|
||||
# provide any stream, branch, commit, object, or globals url
|
||||
wrapper = StreamWrapper("https://app.speckle.systems/streams/3073b96e86/commits/604bea8cc6")
|
||||
# provide a url for a model, version, or object
|
||||
wrapper = StreamWrapper("https://app.speckle.systems/projects/3073b96e86/models/0fe47c9dca@604bea8cc6")
|
||||
|
||||
# get an authenticated SpeckleClient if you have a local account for the server
|
||||
client = wrapper.get_client()
|
||||
@@ -163,7 +163,7 @@ class StreamWrapper:
|
||||
|
||||
if not self.stream_id:
|
||||
raise SpeckleException(
|
||||
f"Cannot parse {url} into a stream wrapper class - no {key_stream} ",
|
||||
f"Cannot parse {url} into a stream wrapper class - no {key_stream} "
|
||||
"id found.",
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from .data_objects import DataObject, QgisObject
|
||||
from .data_objects import Base, DataObject, QgisObject
|
||||
|
||||
__all__ = [
|
||||
"Base",
|
||||
"DataObject",
|
||||
"QgisObject",
|
||||
]
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, List
|
||||
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.interfaces import IDataObject, IGisObject, IHasUnits
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
@@ -12,7 +13,6 @@ class DataObject(
|
||||
speckle_type="Objects.Data.DataObject",
|
||||
detachable={"displayValue"},
|
||||
):
|
||||
|
||||
name: str
|
||||
properties: Dict[str, object]
|
||||
displayValue: List[Base]
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
from specklepy.objects.geometry.arc import Arc
|
||||
from specklepy.objects.geometry.line import Line
|
||||
from specklepy.objects.geometry.mesh import Mesh
|
||||
from specklepy.objects.geometry.plane import Plane
|
||||
from specklepy.objects.geometry.point import Point
|
||||
from specklepy.objects.geometry.polyline import Polyline
|
||||
from specklepy.objects.geometry.vector import Vector
|
||||
from .arc import Arc
|
||||
from .box import Box
|
||||
from .circle import Circle
|
||||
from .control_point import ControlPoint
|
||||
from .ellipse import Ellipse
|
||||
from .line import Line
|
||||
from .mesh import Mesh
|
||||
from .plane import Plane
|
||||
from .point import Point
|
||||
from .point_cloud import PointCloud
|
||||
from .polycurve import Polycurve
|
||||
from .polyline import Polyline
|
||||
from .spiral import Spiral
|
||||
from .surface import Surface
|
||||
from .vector import Vector
|
||||
|
||||
# re-export them at the geometry package level
|
||||
__all__ = [
|
||||
@@ -14,5 +22,13 @@ __all__ = [
|
||||
"Plane",
|
||||
"Point",
|
||||
"Polyline",
|
||||
"Vector"
|
||||
"Vector",
|
||||
"Box",
|
||||
"Circle",
|
||||
"ControlPoint",
|
||||
"Ellipse",
|
||||
"PointCloud",
|
||||
"Polycurve",
|
||||
"Spiral",
|
||||
"Surface",
|
||||
]
|
||||
|
||||
@@ -23,8 +23,9 @@ class Arc(Base, IHasUnits, ICurve, speckle_type="Objects.Geometry.Arc"):
|
||||
start_to_mid = self.startPoint.distance_to(self.midPoint)
|
||||
mid_to_end = self.midPoint.distance_to(self.endPoint)
|
||||
r = self.radius
|
||||
angle = (2 * math.asin(start_to_mid / (2 * r))) + \
|
||||
(2 * math.asin(mid_to_end / (2 * r)))
|
||||
angle = (2 * math.asin(start_to_mid / (2 * r))) + (
|
||||
2 * math.asin(mid_to_end / (2 * r))
|
||||
)
|
||||
return r * angle
|
||||
|
||||
@property
|
||||
@@ -32,5 +33,6 @@ class Arc(Base, IHasUnits, ICurve, speckle_type="Objects.Geometry.Arc"):
|
||||
start_to_mid = self.startPoint.distance_to(self.midPoint)
|
||||
mid_to_end = self.midPoint.distance_to(self.endPoint)
|
||||
r = self.radius
|
||||
return (2 * math.asin(start_to_mid / (2 * r))) + \
|
||||
(2 * math.asin(mid_to_end / (2 * r)))
|
||||
return (2 * math.asin(start_to_mid / (2 * r))) + (
|
||||
2 * math.asin(mid_to_end / (2 * r))
|
||||
)
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.geometry.plane import Plane
|
||||
from specklepy.objects.interfaces import IHasArea, IHasUnits, IHasVolume
|
||||
from specklepy.objects.primitive import Interval
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Box(Base, IHasUnits, IHasArea, IHasVolume, speckle_type="Objects.Geometry.Box"):
|
||||
"""
|
||||
a 3-dimensional box oriented on a plane
|
||||
"""
|
||||
|
||||
basePlane: Plane
|
||||
xSize: Interval
|
||||
ySize: Interval
|
||||
zSize: Interval
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"{self.__class__.__name__}("
|
||||
f"basePlane: {self.basePlane}, "
|
||||
f"xSize: {self.xSize}, "
|
||||
f"ySize: {self.ySize}, "
|
||||
f"zSize: {self.zSize}, "
|
||||
f"units: {self.units})"
|
||||
)
|
||||
|
||||
@property
|
||||
def area(self) -> float:
|
||||
return 2 * (
|
||||
self.xSize.length * self.ySize.length
|
||||
+ self.xSize.length * self.zSize.length
|
||||
+ self.ySize.length * self.zSize.length
|
||||
)
|
||||
|
||||
@property
|
||||
def volume(self) -> float:
|
||||
return self.xSize.length * self.ySize.length * self.zSize.length
|
||||
@@ -0,0 +1,35 @@
|
||||
import math
|
||||
from dataclasses import dataclass
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.geometry.plane import Plane
|
||||
from specklepy.objects.geometry.point import Point
|
||||
from specklepy.objects.interfaces import ICurve, IHasArea, IHasUnits
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Circle(Base, IHasUnits, ICurve, IHasArea, speckle_type="Objects.Geometry.Circle"):
|
||||
"""
|
||||
a circular curve based on a plane
|
||||
"""
|
||||
|
||||
plane: Plane
|
||||
center: Point
|
||||
radius: float
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"{self.__class__.__name__}("
|
||||
f"plane: {self.plane}, "
|
||||
f"center: {self.center}, "
|
||||
f"radius: {self.radius}, "
|
||||
f"units: {self.units})"
|
||||
)
|
||||
|
||||
@property
|
||||
def length(self) -> float:
|
||||
return 2 * math.pi * self.radius
|
||||
|
||||
@property
|
||||
def area(self) -> float:
|
||||
return math.pi * self.radius**2
|
||||
@@ -0,0 +1,22 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from specklepy.objects.geometry.point import Point
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class ControlPoint(Point, speckle_type="Objects.Geometry.ControlPoint"):
|
||||
"""
|
||||
a single 3-dimensional point with weight
|
||||
"""
|
||||
|
||||
weight: float
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"{self.__class__.__name__}("
|
||||
f"x: {self.x}, "
|
||||
f"y: {self.y}, "
|
||||
f"z: {self.z}, "
|
||||
f"weight: {self.weight}, "
|
||||
f"units: {self.units})"
|
||||
)
|
||||
@@ -0,0 +1,34 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.geometry.plane import Plane
|
||||
from specklepy.objects.interfaces import ICurve, IHasArea, IHasUnits
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Ellipse(
|
||||
Base, IHasUnits, ICurve, IHasArea, speckle_type="Objects.Geometry.Ellipse"
|
||||
):
|
||||
"""
|
||||
an ellipse
|
||||
"""
|
||||
|
||||
plane: Plane
|
||||
first_radius: float
|
||||
second_radius: float
|
||||
|
||||
@property
|
||||
def length(self) -> float:
|
||||
return self.__dict__.get("_length", 0.0)
|
||||
|
||||
@length.setter
|
||||
def length(self, value: float) -> None:
|
||||
self.__dict__["_length"] = value
|
||||
|
||||
@property
|
||||
def area(self) -> float:
|
||||
return self.__dict__.get("_area", 0.0)
|
||||
|
||||
@area.setter
|
||||
def area(self, value: float) -> None:
|
||||
self.__dict__["_area"] = value
|
||||
@@ -6,12 +6,7 @@ from specklepy.objects.interfaces import ICurve, IHasUnits
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Line(
|
||||
Base,
|
||||
IHasUnits,
|
||||
ICurve,
|
||||
speckle_type="Objects.Geometry.Line"
|
||||
):
|
||||
class Line(Base, IHasUnits, ICurve, speckle_type="Objects.Geometry.Line"):
|
||||
start: Point
|
||||
end: Point
|
||||
|
||||
|
||||
@@ -23,8 +23,10 @@ class Mesh(
|
||||
serialize_ignore={"vertices_count", "texture_coordinates_count"},
|
||||
):
|
||||
"""
|
||||
a 3D mesh consisting of vertices and faces with optional colors and texture coordinates
|
||||
a 3D mesh consisting of vertices and faces
|
||||
with optional colors and texture coordinates
|
||||
"""
|
||||
|
||||
vertices: List[float]
|
||||
faces: List[int]
|
||||
colors: List[int] = field(default_factory=list)
|
||||
@@ -34,7 +36,6 @@ class Mesh(
|
||||
return (
|
||||
f"{self.__class__.__name__}("
|
||||
f"vertices: {self.vertices_count}, "
|
||||
f"faces: {self.faces_count}, "
|
||||
f"units: {self.units}, "
|
||||
f"has_colors: {len(self.colors) > 0}, "
|
||||
f"has_texture_coords: {len(self.textureCoordinates) > 0})"
|
||||
@@ -48,8 +49,9 @@ class Mesh(
|
||||
|
||||
if len(self.vertices) % 3 != 0:
|
||||
raise ValueError(
|
||||
f"Invalid vertices list: length ({len(
|
||||
self.vertices)}) must be a multiple of 3"
|
||||
f"Invalid vertices list: length ({
|
||||
len(self.vertices)
|
||||
}) must be a multiple of 3"
|
||||
)
|
||||
return len(self.vertices) // 3
|
||||
|
||||
@@ -62,19 +64,19 @@ class Mesh(
|
||||
|
||||
@property
|
||||
def area(self) -> float:
|
||||
return self.__dict__.get('_area', 0.0)
|
||||
return self.__dict__.get("_area", 0.0)
|
||||
|
||||
@area.setter
|
||||
def area(self, value: float) -> None:
|
||||
self.__dict__['_area'] = value
|
||||
self.__dict__["_area"] = value
|
||||
|
||||
@property
|
||||
def volume(self) -> float:
|
||||
return self.__dict__.get('_volume', 0.0)
|
||||
return self.__dict__.get("_volume", 0.0)
|
||||
|
||||
@volume.setter
|
||||
def volume(self, value: float) -> None:
|
||||
self.__dict__['_volume'] = value
|
||||
self.__dict__["_volume"] = value
|
||||
|
||||
def calculate_area(self) -> float:
|
||||
"""
|
||||
@@ -180,8 +182,7 @@ class Mesh(
|
||||
for j in range(vertex_count):
|
||||
vertex_index = self.faces[i + j + 1]
|
||||
if vertex_index >= self.vertices_count:
|
||||
raise IndexError(
|
||||
f"Vertex index {vertex_index} out of range")
|
||||
raise IndexError(f"Vertex index {vertex_index} out of range")
|
||||
vertices.append(self.get_point(vertex_index))
|
||||
return vertices
|
||||
|
||||
|
||||
@@ -15,7 +15,13 @@ class Point(Base, IHasUnits, speckle_type="Objects.Geometry.Point"):
|
||||
z: float
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}(x: {self.x}, y: {self.y}, z: {self.z}, units: {self.units})"
|
||||
return (
|
||||
f"{self.__class__.__name__}("
|
||||
f"x: {self.x}, "
|
||||
f"y: {self.y}, "
|
||||
f"z: {self.z}, "
|
||||
f"units: {self.units})"
|
||||
)
|
||||
|
||||
def distance_to(self, other: "Point") -> float:
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.geometry.point import Point
|
||||
from specklepy.objects.interfaces import IHasUnits
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class PointCloud(Base, IHasUnits, speckle_type="Objects.Geometry.PointCloud"):
|
||||
"""
|
||||
a collection of 3-dimensional points
|
||||
"""
|
||||
|
||||
points: List[Point]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"{self.__class__.__name__}("
|
||||
f"points: {len(self.points)}, "
|
||||
f"units: {self.units})"
|
||||
)
|
||||
|
||||
# sizes and colors could be added in the future
|
||||
@@ -0,0 +1,97 @@
|
||||
from dataclasses import dataclass, field
|
||||
from typing import List
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.geometry.line import Line
|
||||
from specklepy.objects.geometry.point import Point
|
||||
from specklepy.objects.geometry.polyline import Polyline
|
||||
from specklepy.objects.interfaces import ICurve, IHasArea, IHasUnits
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Polycurve(
|
||||
Base, IHasUnits, ICurve, IHasArea, speckle_type="Objects.Geometry.Polycurve"
|
||||
):
|
||||
"""
|
||||
a curve that is comprised of multiple curves connected
|
||||
"""
|
||||
|
||||
segments: List[ICurve] = field(default_factory=list)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"{self.__class__.__name__}("
|
||||
f"segments: {len(self.segments)}, "
|
||||
f"closed: {self.is_closed()}, "
|
||||
f"units: {self.units})"
|
||||
)
|
||||
|
||||
def is_closed(self, tolerance: float = 1e-6) -> bool:
|
||||
"""
|
||||
checks if the polycurve is closed
|
||||
(comparing start of first segment to end of last segment)
|
||||
"""
|
||||
if len(self.segments) < 1:
|
||||
return False
|
||||
|
||||
first_segment = self.segments[0]
|
||||
last_segment = self.segments[-1]
|
||||
|
||||
if not (hasattr(first_segment, "start") and hasattr(last_segment, "end")):
|
||||
return False
|
||||
|
||||
start_pt = first_segment.start
|
||||
end_pt = last_segment.end
|
||||
|
||||
if not (isinstance(start_pt, Point) and isinstance(end_pt, Point)):
|
||||
return False
|
||||
|
||||
return start_pt.distance_to(end_pt) <= tolerance
|
||||
|
||||
@property
|
||||
def length(self) -> float:
|
||||
return self.__dict__.get("_length", 0.0)
|
||||
|
||||
@length.setter
|
||||
def length(self, value: float) -> None:
|
||||
self.__dict__["_length"] = value
|
||||
|
||||
def calculate_length(self) -> float:
|
||||
"""
|
||||
calculate total length of all segments
|
||||
"""
|
||||
total_length = 0.0
|
||||
for segment in self.segments:
|
||||
if hasattr(segment, "length"):
|
||||
total_length += segment.length
|
||||
return total_length
|
||||
|
||||
@property
|
||||
def area(self) -> float:
|
||||
return self.__dict__.get("_area", 0.0)
|
||||
|
||||
@area.setter
|
||||
def area(self, value: float) -> None:
|
||||
self.__dict__["_area"] = value
|
||||
|
||||
@classmethod
|
||||
def from_polyline(cls, polyline: Polyline) -> "Polycurve":
|
||||
"""
|
||||
constructs a new polycurve instance from an existing polyline curve
|
||||
"""
|
||||
polycurve = cls(units=polyline.units)
|
||||
points = polyline.get_points()
|
||||
for i in range(len(points) - 1):
|
||||
line = Line(start=points[i], end=points[i + 1], units=polyline.units)
|
||||
polycurve.segments.append(line)
|
||||
|
||||
if polyline.is_closed():
|
||||
line = Line(start=points[-1], end=points[0], units=polyline.units)
|
||||
polycurve.segments.append(line)
|
||||
|
||||
if hasattr(polyline, "_length"):
|
||||
polycurve.length = polyline.length
|
||||
if hasattr(polyline, "_area"):
|
||||
polycurve.area = polyline.area
|
||||
|
||||
return polycurve
|
||||
@@ -11,6 +11,7 @@ class Polyline(Base, IHasUnits, ICurve, speckle_type="Objects.Geometry.Polyline"
|
||||
"""
|
||||
a polyline curve, defined by a set of vertices.
|
||||
"""
|
||||
|
||||
value: List[float]
|
||||
|
||||
def __repr__(self) -> str:
|
||||
@@ -25,26 +26,20 @@ class Polyline(Base, IHasUnits, ICurve, speckle_type="Objects.Geometry.Polyline"
|
||||
|
||||
# compare first and last points
|
||||
start = Point(
|
||||
x=self.value[0],
|
||||
y=self.value[1],
|
||||
z=self.value[2],
|
||||
units=self.units
|
||||
x=self.value[0], y=self.value[1], z=self.value[2], units=self.units
|
||||
)
|
||||
end = Point(
|
||||
x=self.value[-3],
|
||||
y=self.value[-2],
|
||||
z=self.value[-1],
|
||||
units=self.units
|
||||
x=self.value[-3], y=self.value[-2], z=self.value[-1], units=self.units
|
||||
)
|
||||
return start.distance_to(end) <= tolerance
|
||||
|
||||
@property
|
||||
def length(self) -> float:
|
||||
return self.__dict__.get('_length', 0.0)
|
||||
return self.__dict__.get("_length", 0.0)
|
||||
|
||||
@length.setter
|
||||
def length(self, value: float) -> None:
|
||||
self.__dict__['_length'] = value
|
||||
self.__dict__["_length"] = value
|
||||
|
||||
def calculate_length(self) -> float:
|
||||
points = self.get_points()
|
||||
@@ -70,7 +65,7 @@ class Polyline(Base, IHasUnits, ICurve, speckle_type="Objects.Geometry.Polyline"
|
||||
x=self.value[i],
|
||||
y=self.value[i + 1],
|
||||
z=self.value[i + 2],
|
||||
units=self.units
|
||||
units=self.units,
|
||||
)
|
||||
points.append(point)
|
||||
return points
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.geometry.plane import Plane
|
||||
from specklepy.objects.geometry.point import Point
|
||||
from specklepy.objects.geometry.vector import Vector
|
||||
from specklepy.objects.interfaces import ICurve, IHasArea, IHasUnits
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Spiral(Base, IHasUnits, ICurve, IHasArea, speckle_type="Objects.Geometry.Spiral"):
|
||||
"""
|
||||
a spiral
|
||||
"""
|
||||
|
||||
start_point: Point
|
||||
end_point: Point
|
||||
plane: Plane # plane with origin at spiral center
|
||||
turns: float # total angle of spiral.
|
||||
pitch: float
|
||||
pitch_axis: Vector
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"{self.__class__.__name__}("
|
||||
f"start_point: {self.start_point}, "
|
||||
f"end_point: {self.end_point}, "
|
||||
f"plane: {self.plane}, "
|
||||
f"turns: {self.turns}, "
|
||||
f"pitch: {self.pitch}, "
|
||||
f"pitch_axis: {self.pitch_axis}, "
|
||||
f"units: {self.units})"
|
||||
)
|
||||
|
||||
@property
|
||||
def length(self) -> float:
|
||||
return self.__dict__.get("_length", 0.0)
|
||||
|
||||
@length.setter
|
||||
def length(self, value: float) -> None:
|
||||
self.__dict__["_length"] = value
|
||||
|
||||
@property
|
||||
def area(self) -> float:
|
||||
return self.__dict__.get("_area", 0.0)
|
||||
|
||||
@area.setter
|
||||
def area(self, value: float) -> None:
|
||||
self.__dict__["_area"] = value
|
||||
@@ -0,0 +1,65 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import List
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.geometry.control_point import ControlPoint
|
||||
from specklepy.objects.interfaces import IHasArea, IHasUnits
|
||||
from specklepy.objects.primitive import Interval
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Surface(Base, IHasArea, IHasUnits, speckle_type="Objects.Geometry.Surface"):
|
||||
"""
|
||||
a surface in nurbs form
|
||||
"""
|
||||
|
||||
degreeU: int
|
||||
degreeV: int
|
||||
rational: bool
|
||||
pointData: List[float]
|
||||
countU: int
|
||||
countV: int
|
||||
knotsU: List[float]
|
||||
knotsV: List[float]
|
||||
domainU: Interval
|
||||
domainV: Interval
|
||||
closedU: bool
|
||||
closedV: bool
|
||||
|
||||
@property
|
||||
def area(self) -> float:
|
||||
return self.__dict__.get("_area", 0.0)
|
||||
|
||||
@area.setter
|
||||
def area(self, value: float) -> None:
|
||||
self.__dict__["_area"] = value
|
||||
|
||||
def get_control_points(self) -> List:
|
||||
"""
|
||||
gets the control points of this surface
|
||||
"""
|
||||
|
||||
matrix = [[] for _ in range(self.countU)]
|
||||
|
||||
for i in range(0, len(self.pointData), 4):
|
||||
u_index = i // (self.countV * 4)
|
||||
x, y, z, w = self.pointData[i : i + 4]
|
||||
matrix[u_index].append(
|
||||
ControlPoint(x=x, y=y, z=z, weight=w, units=self.units)
|
||||
)
|
||||
|
||||
return matrix
|
||||
|
||||
def set_control_points(self, value: List) -> None:
|
||||
"""
|
||||
sets the control points of this surface
|
||||
"""
|
||||
data = []
|
||||
self.countU = len(value)
|
||||
self.countV = len(value[0])
|
||||
|
||||
for row in value:
|
||||
for pt in row:
|
||||
data.extend([pt.x, pt.y, pt.z, pt.weight])
|
||||
|
||||
self.pointData = data
|
||||
@@ -5,7 +5,9 @@ from specklepy.objects.interfaces import IHasUnits
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Vector(Base, IHasUnits, speckle_type="Objects.Geometry.Vector", serialize_ignore = {"length"}):
|
||||
class Vector(
|
||||
Base, IHasUnits, speckle_type="Objects.Geometry.Vector", serialize_ignore={"length"}
|
||||
):
|
||||
"""
|
||||
a 3-dimensional vector
|
||||
"""
|
||||
@@ -15,8 +17,14 @@ class Vector(Base, IHasUnits, speckle_type="Objects.Geometry.Vector", serialize_
|
||||
z: float
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"{self.__class__.__name__}(x: {self.x}, y: {self.y}, z: {self.z}, units: {self.units})"
|
||||
return (
|
||||
f"{self.__class__.__name__}("
|
||||
f"x: {self.x}, "
|
||||
f"y: {self.y}, "
|
||||
f"z: {self.z}, "
|
||||
f"units: {self.units})"
|
||||
)
|
||||
|
||||
@property
|
||||
def length(self) -> float:
|
||||
return (self.x ** 2 + self.y ** 2 + self.z ** 2) ** 0.5
|
||||
return (self.x**2 + self.y**2 + self.z**2) ** 0.5
|
||||
|
||||
@@ -41,7 +41,6 @@ class IDisplayValue(Generic[T], metaclass=ABCMeta):
|
||||
# field interfaces
|
||||
@dataclass(kw_only=True)
|
||||
class IHasUnits(metaclass=ABCMeta):
|
||||
|
||||
units: str | Units
|
||||
_units: str = field(repr=False, init=False)
|
||||
|
||||
@@ -63,32 +62,32 @@ class IHasUnits(metaclass=ABCMeta):
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class IHasArea(metaclass=ABCMeta):
|
||||
|
||||
_area: float = field(init=False, repr=False)
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def area(self) -> float:
|
||||
return self._area
|
||||
pass
|
||||
|
||||
@area.setter
|
||||
def area(self, value: float):
|
||||
if not isinstance(value, (int, float)):
|
||||
if not isinstance(value, int | float):
|
||||
raise ValueError(f"Area must be a number, got {type(value)}")
|
||||
self._area = float(value)
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class IHasVolume(metaclass=ABCMeta):
|
||||
|
||||
_volume: float = field(init=False, repr=False)
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def volume(self) -> float:
|
||||
return self._volume
|
||||
pass
|
||||
|
||||
@volume.setter
|
||||
def volume(self, value: float):
|
||||
if not isinstance(value, (int, float)):
|
||||
if not isinstance(value, int | float):
|
||||
raise ValueError(f"Volume must be a number, got {type(value)}")
|
||||
self._volume = float(value)
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
from dataclasses import dataclass
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class RenderMaterial(
|
||||
Base,
|
||||
speckle_type="Objects.Other.RenderMaterial",
|
||||
serialize_ignore={"diffuse", "emissive"},
|
||||
):
|
||||
"""
|
||||
Minimal physically based material DTO class. Based on references from
|
||||
https://threejs.org/docs/index.html#api/en/materials/MeshStandardMaterial
|
||||
"""
|
||||
|
||||
name: str
|
||||
opacity: float = 1.0
|
||||
metalness: float = 0.0
|
||||
roughness: float = 1.0
|
||||
diffuse: int # ARGB color as int
|
||||
emissive: int = 0 # ARGB color as int, defaults to black
|
||||
@@ -4,7 +4,9 @@ from specklepy.objects.base import Base
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class Interval(Base, speckle_type="Objects.Primitive.Interval", serialize_ignore={"length"}):
|
||||
class Interval(
|
||||
Base, speckle_type="Objects.Primitive.Interval", serialize_ignore={"length"}
|
||||
):
|
||||
start: float = 0.0
|
||||
end: float = 0.0
|
||||
|
||||
@@ -13,7 +15,7 @@ class Interval(Base, speckle_type="Objects.Primitive.Interval", serialize_ignore
|
||||
|
||||
@property
|
||||
def length(self) -> float:
|
||||
abs(self.end - self.start)
|
||||
return abs(self.end - self.start)
|
||||
|
||||
@classmethod
|
||||
def unit_interval(cls) -> "Interval":
|
||||
|
||||
@@ -3,12 +3,13 @@ from typing import List, Optional
|
||||
|
||||
from specklepy.objects.base import Base
|
||||
from specklepy.objects.interfaces import IHasUnits
|
||||
from specklepy.objects.other import RenderMaterial
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class ColorProxy(
|
||||
Base,
|
||||
speckle_type="Models.Proxies.ColorProxy",
|
||||
speckle_type="Speckle.Core.Models.Proxies.ColorProxy",
|
||||
detachable={"objects"},
|
||||
):
|
||||
objects: List[str]
|
||||
@@ -19,7 +20,7 @@ class ColorProxy(
|
||||
@dataclass(kw_only=True)
|
||||
class GroupProxy(
|
||||
Base,
|
||||
speckle_type="Models.Proxies.GroupProxy",
|
||||
speckle_type="Speckle.Core.Models.Proxies.GroupProxy",
|
||||
detachable={"objects"},
|
||||
):
|
||||
objects: List[str]
|
||||
@@ -30,7 +31,7 @@ class GroupProxy(
|
||||
class InstanceProxy(
|
||||
Base,
|
||||
IHasUnits,
|
||||
speckle_type="Models.Proxies.InstanceProxy",
|
||||
speckle_type="Speckle.Core.Models.Instances.InstanceProxy",
|
||||
):
|
||||
definition_id: str
|
||||
transform: List[float]
|
||||
@@ -40,9 +41,27 @@ class InstanceProxy(
|
||||
@dataclass(kw_only=True)
|
||||
class InstanceDefinitionProxy(
|
||||
Base,
|
||||
speckle_type="Models.Proxies.InstanceDefinitionProxy",
|
||||
speckle_type="Speckle.Core.Models.Instances.InstanceDefinitionProxy",
|
||||
detachable={"objects"},
|
||||
):
|
||||
objects: List[str]
|
||||
max_depth: int
|
||||
name: str
|
||||
|
||||
|
||||
@dataclass(kw_only=True)
|
||||
class RenderMaterialProxy(
|
||||
Base,
|
||||
speckle_type="Objects.Other.RenderMaterialProxy",
|
||||
detachable={"objects"},
|
||||
):
|
||||
"""
|
||||
used to store render material to object relationships in root collections
|
||||
|
||||
Args:
|
||||
objects (list): the list of application ids of objects used by render material
|
||||
value (RenderMaterial): the render material used by the objects
|
||||
"""
|
||||
|
||||
objects: List[str]
|
||||
value: RenderMaterial
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
from specklepy.objects.geometry import Point, Line
|
||||
from specklepy.objects.models.units import Units
|
||||
|
||||
p_1 = Point(x=0, y=0, z=0, units=Units.m)
|
||||
p_2 = Point(x=3, y=0, z=0, units=Units.m)
|
||||
|
||||
line = Line(start=p_1, end=p_2, units=Units.m)
|
||||
line.length = line.calculate_length()
|
||||
print(line.length)
|
||||
@@ -1,8 +1,13 @@
|
||||
# ignoring "line too long" check from linter
|
||||
# to match with SpeckleExceptions
|
||||
# ruff: noqa: E501
|
||||
|
||||
import math
|
||||
|
||||
import pytest
|
||||
|
||||
from specklepy.core.api.operations import deserialize, serialize
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
from specklepy.objects.geometry import Arc, Plane, Point, Vector
|
||||
from specklepy.objects.models.units import Units
|
||||
from specklepy.objects.primitive import Interval
|
||||
@@ -27,8 +32,7 @@ def sample_plane():
|
||||
|
||||
ydir = Vector(x=0.0, y=1.0, z=0.0, units=Units.m)
|
||||
|
||||
plane = Plane(origin=origin, normal=normal,
|
||||
xdir=xdir, ydir=ydir, units=Units.m)
|
||||
plane = Plane(origin=origin, normal=normal, xdir=xdir, ydir=ydir, units=Units.m)
|
||||
|
||||
return plane
|
||||
|
||||
@@ -37,11 +41,7 @@ def sample_plane():
|
||||
def sample_arc(sample_points, sample_plane):
|
||||
start, mid, end = sample_points
|
||||
arc = Arc(
|
||||
plane=sample_plane,
|
||||
startPoint=start,
|
||||
midPoint=mid,
|
||||
endPoint=end,
|
||||
units=Units.m
|
||||
plane=sample_plane, startPoint=start, midPoint=mid, endPoint=end, units=Units.m
|
||||
)
|
||||
|
||||
return arc
|
||||
@@ -50,11 +50,7 @@ def sample_arc(sample_points, sample_plane):
|
||||
def test_arc_creation(sample_points, sample_plane):
|
||||
start, mid, end = sample_points
|
||||
arc = Arc(
|
||||
plane=sample_plane,
|
||||
startPoint=start,
|
||||
midPoint=mid,
|
||||
endPoint=end,
|
||||
units=Units.m
|
||||
plane=sample_plane, startPoint=start, midPoint=mid, endPoint=end, units=Units.m
|
||||
)
|
||||
|
||||
assert arc.startPoint == start
|
||||
@@ -71,69 +67,84 @@ def test_arc_domain(sample_arc):
|
||||
|
||||
|
||||
def test_arc_radius(sample_arc):
|
||||
|
||||
assert sample_arc.radius == pytest.approx(1.0)
|
||||
|
||||
|
||||
def test_arc_length(sample_arc):
|
||||
|
||||
assert sample_arc.length == pytest.approx(math.pi)
|
||||
|
||||
|
||||
def test_arc_units(sample_points, sample_plane):
|
||||
@pytest.mark.parametrize(
|
||||
"invalid_param, test_params, error_msg",
|
||||
[
|
||||
(
|
||||
"plane",
|
||||
{
|
||||
"plane": "not a plane",
|
||||
"startPoint": None,
|
||||
"midPoint": None,
|
||||
"endPoint": None,
|
||||
},
|
||||
"Cannot set 'Arc.plane':it expects type '<class 'specklepy.objects.geometry.plane.Plane'>',but received type 'str'",
|
||||
),
|
||||
(
|
||||
"startPoint",
|
||||
{
|
||||
"plane": None,
|
||||
"startPoint": "not a point",
|
||||
"midPoint": None,
|
||||
"endPoint": None,
|
||||
},
|
||||
"Cannot set 'Arc.startPoint':it expects type '<class 'specklepy.objects.geometry.point.Point'>',but received type 'str'",
|
||||
),
|
||||
(
|
||||
"midPoint",
|
||||
{
|
||||
"plane": None,
|
||||
"startPoint": None,
|
||||
"midPoint": "not a point",
|
||||
"endPoint": None,
|
||||
},
|
||||
"Cannot set 'Arc.midPoint':it expects type '<class 'specklepy.objects.geometry.point.Point'>',but received type 'str'",
|
||||
),
|
||||
(
|
||||
"endPoint",
|
||||
{
|
||||
"plane": None,
|
||||
"startPoint": None,
|
||||
"midPoint": None,
|
||||
"endPoint": "not a point",
|
||||
},
|
||||
"Cannot set 'Arc.endPoint':it expects type '<class 'specklepy.objects.geometry.point.Point'>',but received type 'str'",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_arc_inval(sample_points, sample_plane, invalid_param, test_params, error_msg):
|
||||
start, mid, end = sample_points
|
||||
|
||||
if invalid_param != "plane":
|
||||
test_params["plane"] = sample_plane
|
||||
if invalid_param != "startPoint":
|
||||
test_params["startPoint"] = start
|
||||
if invalid_param != "midPoint":
|
||||
test_params["midPoint"] = mid
|
||||
if invalid_param != "endPoint":
|
||||
test_params["endPoint"] = end
|
||||
|
||||
with pytest.raises(SpeckleException) as exc_info:
|
||||
Arc(**test_params, units=Units.m)
|
||||
assert str(exc_info.value) == f"SpeckleException: {error_msg}"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("new_units", ["mm", "cm", "in"])
|
||||
def test_arc_units(sample_points, sample_plane, new_units):
|
||||
start, mid, end = sample_points
|
||||
arc = Arc(
|
||||
plane=sample_plane,
|
||||
startPoint=start,
|
||||
midPoint=mid,
|
||||
endPoint=end,
|
||||
units=Units.m
|
||||
plane=sample_plane, startPoint=start, midPoint=mid, endPoint=end, units=Units.m
|
||||
)
|
||||
|
||||
assert arc.units == Units.m.value
|
||||
|
||||
arc.units = "mm"
|
||||
assert arc.units == "mm"
|
||||
|
||||
|
||||
def test_arc_invalid_construction(sample_points, sample_plane):
|
||||
start, mid, end = sample_points
|
||||
|
||||
with pytest.raises(Exception):
|
||||
Arc(
|
||||
plane="not a plane",
|
||||
startPoint=start,
|
||||
midPoint=mid,
|
||||
endPoint=end,
|
||||
units=Units.m
|
||||
)
|
||||
|
||||
with pytest.raises(Exception):
|
||||
Arc(
|
||||
plane=sample_plane,
|
||||
startPoint="not a point",
|
||||
midPoint=mid,
|
||||
endPoint=end,
|
||||
units=Units.m
|
||||
)
|
||||
|
||||
with pytest.raises(Exception):
|
||||
Arc(
|
||||
plane=sample_plane,
|
||||
startPoint=start,
|
||||
midPoint="not a point",
|
||||
endPoint=end,
|
||||
units=Units.m
|
||||
)
|
||||
|
||||
with pytest.raises(Exception):
|
||||
Arc(
|
||||
plane=sample_plane,
|
||||
startPoint=start,
|
||||
midPoint=mid,
|
||||
endPoint="not a point",
|
||||
units=Units.m
|
||||
)
|
||||
arc.units = new_units
|
||||
assert arc.units == new_units
|
||||
|
||||
|
||||
def test_arc_serialization(sample_arc):
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
# ignoring "line too long" check from linter
|
||||
# to match with SpeckleExceptions
|
||||
# ruff: noqa: E501
|
||||
|
||||
import pytest
|
||||
|
||||
from specklepy.core.api.operations import deserialize, serialize
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
from specklepy.objects.geometry import Box, Plane, Point, Vector
|
||||
from specklepy.objects.models.units import Units
|
||||
from specklepy.objects.primitive import Interval
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_plane():
|
||||
origin = Point(x=0.0, y=0.0, z=0.0, units=Units.m)
|
||||
normal = Vector(x=0.0, y=0.0, z=1.0, units=Units.m)
|
||||
xdir = Vector(x=1.0, y=0.0, z=0.0, units=Units.m)
|
||||
ydir = Vector(x=0.0, y=1.0, z=0.0, units=Units.m)
|
||||
return Plane(origin=origin, normal=normal, xdir=xdir, ydir=ydir, units=Units.m)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_intervals():
|
||||
return (
|
||||
Interval(start=0.0, end=1.0),
|
||||
Interval(start=0.0, end=1.0),
|
||||
Interval(start=0.0, end=1.0),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_box(sample_plane, sample_intervals):
|
||||
xsize, ysize, zsize = sample_intervals
|
||||
return Box(
|
||||
basePlane=sample_plane, xSize=xsize, ySize=ysize, zSize=zsize, units=Units.m
|
||||
)
|
||||
|
||||
|
||||
def test_box_creation(sample_plane, sample_intervals):
|
||||
xsize, ysize, zsize = sample_intervals
|
||||
box = Box(
|
||||
basePlane=sample_plane, xSize=xsize, ySize=ysize, zSize=zsize, units=Units.m
|
||||
)
|
||||
|
||||
assert box.basePlane == sample_plane
|
||||
assert box.xSize == xsize
|
||||
assert box.ySize == ysize
|
||||
assert box.zSize == zsize
|
||||
assert box.units == Units.m.value
|
||||
|
||||
|
||||
@pytest.mark.parametrize("expected_area", [6.0]) # 6 faces, each 1x1
|
||||
def test_box_area(sample_box, expected_area):
|
||||
sample_box.area = sample_box.area
|
||||
assert sample_box.area == pytest.approx(expected_area)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("expected_volume", [1.0]) # 1x1x1 cube
|
||||
def test_box_volume(sample_box, expected_volume):
|
||||
sample_box.volume = sample_box.volume
|
||||
assert sample_box.volume == pytest.approx(expected_volume)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("new_units", ["mm", "cm", "in"])
|
||||
def test_box_units(sample_plane, sample_intervals, new_units):
|
||||
xsize, ysize, zsize = sample_intervals
|
||||
box = Box(
|
||||
basePlane=sample_plane, xSize=xsize, ySize=ysize, zSize=zsize, units=Units.m
|
||||
)
|
||||
assert box.units == Units.m.value
|
||||
box.units = new_units
|
||||
assert box.units == new_units
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"invalid_param, test_params, error_msg",
|
||||
[
|
||||
(
|
||||
"basePlane",
|
||||
{"basePlane": "not a plane", "xSize": None, "ySize": None, "zSize": None},
|
||||
"Cannot set 'Box.basePlane':it expects type '<class 'specklepy.objects.geometry.plane.Plane'>',but received type 'str'",
|
||||
),
|
||||
(
|
||||
"xSize",
|
||||
{
|
||||
"basePlane": None,
|
||||
"xSize": "not an interval",
|
||||
"ySize": None,
|
||||
"zSize": None,
|
||||
},
|
||||
"Cannot set 'Box.xSize':it expects type '<class 'specklepy.objects.primitive.Interval'>',but received type 'str'",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_box_inval(
|
||||
sample_plane, sample_intervals, invalid_param, test_params, error_msg
|
||||
):
|
||||
xsize, ysize, zsize = sample_intervals
|
||||
|
||||
if invalid_param != "basePlane":
|
||||
test_params["basePlane"] = sample_plane
|
||||
if invalid_param != "xSize":
|
||||
test_params["xSize"] = xsize
|
||||
if invalid_param != "ySize":
|
||||
test_params["ySize"] = ysize
|
||||
if invalid_param != "zSize":
|
||||
test_params["zSize"] = zsize
|
||||
|
||||
with pytest.raises(SpeckleException) as exc_info:
|
||||
Box(**test_params, units=Units.m)
|
||||
assert str(exc_info.value) == f"SpeckleException: {error_msg}"
|
||||
|
||||
|
||||
def test_box_serialization(sample_box):
|
||||
serialized = serialize(sample_box)
|
||||
deserialized = deserialize(serialized)
|
||||
|
||||
assert deserialized.basePlane.origin.x == sample_box.basePlane.origin.x
|
||||
assert deserialized.basePlane.origin.y == sample_box.basePlane.origin.y
|
||||
assert deserialized.basePlane.origin.z == sample_box.basePlane.origin.z
|
||||
|
||||
assert deserialized.xSize.start == sample_box.xSize.start
|
||||
assert deserialized.xSize.end == sample_box.xSize.end
|
||||
assert deserialized.ySize.start == sample_box.ySize.start
|
||||
assert deserialized.ySize.end == sample_box.ySize.end
|
||||
assert deserialized.zSize.start == sample_box.zSize.start
|
||||
assert deserialized.zSize.end == sample_box.zSize.end
|
||||
|
||||
assert deserialized.units == sample_box.units
|
||||
@@ -0,0 +1,123 @@
|
||||
# ignoring "line too long" check from linter
|
||||
# to match with SpeckleExceptions
|
||||
# ruff: noqa: E501
|
||||
|
||||
import math
|
||||
|
||||
import pytest
|
||||
|
||||
from specklepy.core.api.operations import deserialize, serialize
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
from specklepy.objects.geometry import Circle, Plane, Point, Vector
|
||||
from specklepy.objects.models.units import Units
|
||||
from specklepy.objects.primitive import Interval
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_plane():
|
||||
origin = Point(x=0.0, y=0.0, z=0.0, units=Units.m)
|
||||
normal = Vector(x=0.0, y=0.0, z=1.0, units=Units.m)
|
||||
xdir = Vector(x=1.0, y=0.0, z=0.0, units=Units.m)
|
||||
ydir = Vector(x=0.0, y=1.0, z=0.0, units=Units.m)
|
||||
return Plane(origin=origin, normal=normal, xdir=xdir, ydir=ydir, units=Units.m)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_center():
|
||||
return Point(x=0.0, y=0.0, z=0.0, units=Units.m)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_circle(sample_plane, sample_center):
|
||||
return Circle(plane=sample_plane, center=sample_center, radius=1.0, units=Units.m)
|
||||
|
||||
|
||||
def test_circle_creation(sample_plane, sample_center):
|
||||
circle = Circle(plane=sample_plane, center=sample_center, radius=1.0, units=Units.m)
|
||||
assert circle.plane == sample_plane
|
||||
assert circle.center == sample_center
|
||||
assert circle.radius == 1.0
|
||||
assert circle.units == Units.m.value
|
||||
|
||||
|
||||
def test_circle_domain(sample_circle):
|
||||
assert isinstance(sample_circle.domain, Interval)
|
||||
assert sample_circle.domain.start == 0.0
|
||||
assert sample_circle.domain.end == 1.0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"radius,expected_length", [(1.0, 2 * math.pi), (2.0, 4 * math.pi), (0.5, math.pi)]
|
||||
)
|
||||
def test_circle_length(sample_circle, radius, expected_length):
|
||||
sample_circle.radius = radius
|
||||
assert sample_circle.length == pytest.approx(expected_length)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"radius,expected_area", [(1.0, math.pi), (2.0, 4 * math.pi), (0.5, math.pi * 0.25)]
|
||||
)
|
||||
def test_circle_area(sample_circle, radius, expected_area):
|
||||
sample_circle.radius = radius
|
||||
assert sample_circle.area == pytest.approx(expected_area)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("new_units", ["mm", "cm", "in"])
|
||||
def test_circle_units(sample_plane, sample_center, new_units):
|
||||
circle = Circle(plane=sample_plane, center=sample_center, radius=1.0, units=Units.m)
|
||||
assert circle.units == Units.m.value
|
||||
circle.units = new_units
|
||||
assert circle.units == new_units
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"invalid_param, test_params, error_msg",
|
||||
[
|
||||
(
|
||||
"plane",
|
||||
{"plane": "not a plane", "center": None, "radius": 1.0},
|
||||
"Cannot set 'Circle.plane':it expects type '<class 'specklepy.objects.geometry.plane.Plane'>',but received type 'str'",
|
||||
),
|
||||
(
|
||||
"center",
|
||||
{"plane": None, "center": "not a point", "radius": 1.0},
|
||||
"Cannot set 'Circle.center':it expects type '<class 'specklepy.objects.geometry.point.Point'>',but received type 'str'",
|
||||
),
|
||||
(
|
||||
"radius",
|
||||
{"plane": None, "center": None, "radius": "not a number"},
|
||||
"Cannot set 'Circle.radius':it expects type '<class 'float'>',but received type 'str'",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_circle_inval(
|
||||
sample_plane, sample_center, invalid_param, test_params, error_msg
|
||||
):
|
||||
if invalid_param != "plane":
|
||||
test_params["plane"] = sample_plane
|
||||
if invalid_param != "center":
|
||||
test_params["center"] = sample_center
|
||||
|
||||
with pytest.raises(SpeckleException) as exc_info:
|
||||
Circle(**test_params, units=Units.m)
|
||||
assert str(exc_info.value) == f"SpeckleException: {error_msg}"
|
||||
|
||||
|
||||
def test_circle_serialization(sample_circle):
|
||||
serialized = serialize(sample_circle)
|
||||
deserialized = deserialize(serialized)
|
||||
|
||||
assert deserialized.plane.origin.x == sample_circle.plane.origin.x
|
||||
assert deserialized.plane.origin.y == sample_circle.plane.origin.y
|
||||
assert deserialized.plane.origin.z == sample_circle.plane.origin.z
|
||||
|
||||
assert deserialized.plane.normal.x == sample_circle.plane.normal.x
|
||||
assert deserialized.plane.normal.y == sample_circle.plane.normal.y
|
||||
assert deserialized.plane.normal.z == sample_circle.plane.normal.z
|
||||
|
||||
assert deserialized.center.x == sample_circle.center.x
|
||||
assert deserialized.center.y == sample_circle.center.y
|
||||
assert deserialized.center.z == sample_circle.center.z
|
||||
|
||||
assert deserialized.radius == sample_circle.radius
|
||||
assert deserialized.units == sample_circle.units
|
||||
@@ -0,0 +1,77 @@
|
||||
# ignoring "line too long" check from linter
|
||||
# to match with SpeckleExceptions
|
||||
# ruff: noqa: E501
|
||||
|
||||
import pytest
|
||||
|
||||
from specklepy.core.api.operations import deserialize, serialize
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
from specklepy.objects.geometry import ControlPoint
|
||||
from specklepy.objects.models.units import Units
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_control_point():
|
||||
return ControlPoint(x=1.0, y=2.0, z=3.0, weight=1.0, units=Units.m)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"x,y,z,weight,expected_units", [(1.0, 2.0, 3.0, 1.0, Units.m.value)]
|
||||
)
|
||||
def test_control_point_creation(x, y, z, weight, expected_units):
|
||||
control_point = ControlPoint(x=x, y=y, z=z, weight=weight, units=Units.m)
|
||||
assert control_point.x == x
|
||||
assert control_point.y == y
|
||||
assert control_point.z == z
|
||||
assert control_point.weight == weight
|
||||
assert control_point.units == expected_units
|
||||
|
||||
|
||||
@pytest.mark.parametrize("new_units", ["mm", "cm", "in"])
|
||||
def test_control_point_units(new_units):
|
||||
control_point = ControlPoint(x=1.0, y=2.0, z=3.0, weight=1.0, units=Units.m)
|
||||
assert control_point.units == Units.m.value
|
||||
control_point.units = new_units
|
||||
assert control_point.units == new_units
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"invalid_param, test_params, error_msg",
|
||||
[
|
||||
(
|
||||
"x",
|
||||
{"x": "not a number", "y": 2.0, "z": 3.0, "weight": 1.0},
|
||||
"Cannot set 'ControlPoint.x':it expects type '<class 'float'>',but received type 'str'",
|
||||
),
|
||||
(
|
||||
"y",
|
||||
{"x": 1.0, "y": "not a number", "z": 3.0, "weight": 1.0},
|
||||
"Cannot set 'ControlPoint.y':it expects type '<class 'float'>',but received type 'str'",
|
||||
),
|
||||
(
|
||||
"z",
|
||||
{"x": 1.0, "y": 2.0, "z": "not a number", "weight": 1.0},
|
||||
"Cannot set 'ControlPoint.z':it expects type '<class 'float'>',but received type 'str'",
|
||||
),
|
||||
(
|
||||
"weight",
|
||||
{"x": 1.0, "y": 2.0, "z": 3.0, "weight": "not a number"},
|
||||
"Cannot set 'ControlPoint.weight':it expects type '<class 'float'>',but received type 'str'",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_control_point_invalid_construction(invalid_param, test_params, error_msg):
|
||||
with pytest.raises(SpeckleException) as exc_info:
|
||||
ControlPoint(**test_params, units=Units.m)
|
||||
assert str(exc_info.value) == f"SpeckleException: {error_msg}"
|
||||
|
||||
|
||||
def test_control_point_serialization(sample_control_point):
|
||||
serialized = serialize(sample_control_point)
|
||||
deserialized = deserialize(serialized)
|
||||
|
||||
assert deserialized.x == sample_control_point.x
|
||||
assert deserialized.y == sample_control_point.y
|
||||
assert deserialized.z == sample_control_point.z
|
||||
assert deserialized.weight == sample_control_point.weight
|
||||
assert deserialized.units == sample_control_point.units
|
||||
@@ -0,0 +1,113 @@
|
||||
# ignoring "line too long" check from linter
|
||||
# to match with SpeckleExceptions
|
||||
# ruff: noqa: E501
|
||||
|
||||
import math
|
||||
|
||||
import pytest
|
||||
|
||||
from specklepy.core.api.operations import deserialize, serialize
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
from specklepy.objects.geometry import Ellipse, Plane, Point, Vector
|
||||
from specklepy.objects.models.units import Units
|
||||
from specklepy.objects.primitive import Interval
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_plane():
|
||||
origin = Point(x=0.0, y=0.0, z=0.0, units=Units.m)
|
||||
normal = Vector(x=0.0, y=0.0, z=1.0, units=Units.m)
|
||||
xdir = Vector(x=1.0, y=0.0, z=0.0, units=Units.m)
|
||||
ydir = Vector(x=0.0, y=1.0, z=0.0, units=Units.m)
|
||||
|
||||
return Plane(origin=origin, normal=normal, xdir=xdir, ydir=ydir, units=Units.m)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_ellipse(sample_plane):
|
||||
return Ellipse(
|
||||
plane=sample_plane, first_radius=2.0, second_radius=1.0, units=Units.m
|
||||
)
|
||||
|
||||
|
||||
def test_ellipse_creation(sample_plane):
|
||||
ellipse = Ellipse(
|
||||
plane=sample_plane, first_radius=2.0, second_radius=1.0, units=Units.m
|
||||
)
|
||||
|
||||
assert ellipse.plane == sample_plane
|
||||
assert ellipse.first_radius == 2.0
|
||||
assert ellipse.second_radius == 1.0
|
||||
assert ellipse.units == Units.m.value
|
||||
|
||||
|
||||
def test_ellipse_domain(sample_ellipse):
|
||||
assert isinstance(sample_ellipse.domain, Interval)
|
||||
assert sample_ellipse.domain.start == 0.0
|
||||
assert sample_ellipse.domain.end == 1.0
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"area_value",
|
||||
[
|
||||
10.0,
|
||||
math.pi * 2.0, # area for circle with radius 2
|
||||
0.0,
|
||||
],
|
||||
)
|
||||
def test_ellipse_area(sample_ellipse, area_value):
|
||||
sample_ellipse.area = area_value
|
||||
assert sample_ellipse.area == pytest.approx(area_value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("new_units", ["mm", "cm", "in"])
|
||||
def test_ellipse_units(sample_plane, new_units):
|
||||
ellipse = Ellipse(
|
||||
plane=sample_plane, first_radius=2.0, second_radius=1.0, units=Units.m
|
||||
)
|
||||
assert ellipse.units == Units.m.value
|
||||
|
||||
ellipse.units = new_units
|
||||
assert ellipse.units == new_units
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"invalid_param, test_params, error_msg",
|
||||
[
|
||||
(
|
||||
"plane",
|
||||
{"plane": "not a plane", "first_radius": 2.0, "second_radius": 1.0},
|
||||
"Cannot set 'Ellipse.plane':it expects type '<class 'specklepy.objects.geometry.plane.Plane'>',but received type 'str'",
|
||||
),
|
||||
(
|
||||
"first_radius",
|
||||
{"plane": None, "first_radius": "not a number", "second_radius": 1.0},
|
||||
"Cannot set 'Ellipse.first_radius':it expects type '<class 'float'>',but received type 'str'",
|
||||
),
|
||||
(
|
||||
"second_radius",
|
||||
{"plane": None, "first_radius": 2.0, "second_radius": "not number"},
|
||||
"Cannot set 'Ellipse.second_radius':it expects type '<class 'float'>',but received type 'str'",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_ellipse_invalid(sample_plane, invalid_param, test_params, error_msg):
|
||||
if invalid_param != "plane":
|
||||
test_params["plane"] = sample_plane
|
||||
|
||||
with pytest.raises(SpeckleException) as exc_info:
|
||||
Ellipse(**test_params, units=Units.m)
|
||||
assert str(exc_info.value) == f"SpeckleException: {error_msg}"
|
||||
|
||||
|
||||
def test_ellipse_serialization(sample_ellipse):
|
||||
serialized = serialize(sample_ellipse)
|
||||
deserialized = deserialize(serialized)
|
||||
|
||||
assert deserialized.plane.origin.x == sample_ellipse.plane.origin.x
|
||||
assert deserialized.plane.origin.y == sample_ellipse.plane.origin.y
|
||||
assert deserialized.plane.origin.z == sample_ellipse.plane.origin.z
|
||||
|
||||
assert deserialized.first_radius == sample_ellipse.first_radius
|
||||
assert deserialized.second_radius == sample_ellipse.second_radius
|
||||
assert deserialized.units == sample_ellipse.units
|
||||
@@ -1,6 +1,11 @@
|
||||
# ignoring "line too long" check from linter
|
||||
# to match with SpeckleExceptions
|
||||
# ruff: noqa: E501
|
||||
|
||||
import pytest
|
||||
|
||||
from specklepy.core.api.operations import deserialize, serialize
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
from specklepy.objects.geometry import Line, Point
|
||||
from specklepy.objects.models.units import Units
|
||||
from specklepy.objects.primitive import Interval
|
||||
@@ -8,7 +13,6 @@ from specklepy.objects.primitive import Interval
|
||||
|
||||
@pytest.fixture
|
||||
def sample_points():
|
||||
|
||||
p1 = Point(x=0.0, y=0.0, z=0.0, units=Units.m)
|
||||
p2 = Point(x=3.0, y=4.0, z=0.0, units=Units.m)
|
||||
return p1, p2
|
||||
@@ -16,49 +20,66 @@ def sample_points():
|
||||
|
||||
@pytest.fixture
|
||||
def sample_line(sample_points):
|
||||
|
||||
start, end = sample_points
|
||||
line = Line(start=start, end=end, units=Units.m)
|
||||
return line
|
||||
return Line(start=start, end=end, units=Units.m)
|
||||
|
||||
|
||||
def test_line_creation(sample_points):
|
||||
|
||||
start, end = sample_points
|
||||
line = Line(start=start, end=end, units=Units.m)
|
||||
|
||||
assert line.start == start
|
||||
assert line.end == end
|
||||
assert line.units == Units.m.value
|
||||
|
||||
|
||||
def test_line_domain(sample_line):
|
||||
|
||||
# Domain should be automatically initialized to unit interval by ICurve
|
||||
assert isinstance(sample_line.domain, Interval)
|
||||
assert sample_line.domain.start == 0.0
|
||||
assert sample_line.domain.end == 1.0
|
||||
|
||||
|
||||
def test_line_length(sample_line):
|
||||
|
||||
assert sample_line.length == 5.0
|
||||
@pytest.mark.parametrize("expected_length", [5.0])
|
||||
def test_line_length(sample_line, expected_length):
|
||||
assert sample_line.length == expected_length
|
||||
|
||||
|
||||
def test_line_units(sample_points):
|
||||
|
||||
@pytest.mark.parametrize("new_units", ["mm", "cm", "in"])
|
||||
def test_line_units(sample_points, new_units):
|
||||
start, end = sample_points
|
||||
line = Line(start=start, end=end, units=Units.m)
|
||||
|
||||
assert line.units == Units.m.value
|
||||
line.units = new_units
|
||||
assert line.units == new_units
|
||||
|
||||
# Test setting units with string
|
||||
line.units = "mm"
|
||||
assert line.units == "mm"
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"invalid_param, test_params, error_msg",
|
||||
[
|
||||
(
|
||||
"start",
|
||||
{"start": "not a point", "end": None},
|
||||
"Cannot set 'Line.start':it expects type '<class 'specklepy.objects.geometry.point.Point'>',but received type 'str'",
|
||||
),
|
||||
(
|
||||
"end",
|
||||
{"start": None, "end": "not a point"},
|
||||
"Cannot set 'Line.end':it expects type '<class 'specklepy.objects.geometry.point.Point'>',but received type 'str'",
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_line_invalid(sample_points, invalid_param, test_params, error_msg):
|
||||
start, end = sample_points
|
||||
if invalid_param != "start":
|
||||
test_params["start"] = start
|
||||
if invalid_param != "end":
|
||||
test_params["end"] = end
|
||||
|
||||
with pytest.raises(SpeckleException) as exc_info:
|
||||
Line(**test_params, units=Units.m)
|
||||
assert str(exc_info.value) == f"SpeckleException: {error_msg}"
|
||||
|
||||
|
||||
def test_line_serialization(sample_line):
|
||||
|
||||
serialized = serialize(sample_line)
|
||||
deserialized = deserialize(serialized)
|
||||
|
||||
@@ -71,16 +92,3 @@ def test_line_serialization(sample_line):
|
||||
assert deserialized.units == sample_line.units
|
||||
assert deserialized.domain.start == sample_line.domain.start
|
||||
assert deserialized.domain.end == sample_line.domain.end
|
||||
|
||||
|
||||
def test_line_invalid_construction():
|
||||
"""Test error cases"""
|
||||
p1 = Point(x=0.0, y=0.0, z=0.0, units=Units.m)
|
||||
|
||||
# Test with invalid start point
|
||||
with pytest.raises(Exception):
|
||||
Line(start="not a point", end=p1)
|
||||
|
||||
# Test with invalid end point
|
||||
with pytest.raises(Exception):
|
||||
Line(start=p1, end="not a point")
|
||||
|
||||
@@ -8,77 +8,143 @@ from specklepy.objects.models.units import Units
|
||||
|
||||
@pytest.fixture
|
||||
def cube_vertices():
|
||||
|
||||
return [
|
||||
-0.5, -0.5, -0.5,
|
||||
0.5, -0.5, -0.5,
|
||||
0.5, 0.5, -0.5,
|
||||
-0.5, 0.5, -0.5,
|
||||
-0.5, -0.5, 0.5,
|
||||
0.5, -0.5, 0.5,
|
||||
0.5, 0.5, 0.5,
|
||||
-0.5, 0.5, 0.5
|
||||
-0.5,
|
||||
-0.5,
|
||||
-0.5,
|
||||
0.5,
|
||||
-0.5,
|
||||
-0.5,
|
||||
0.5,
|
||||
0.5,
|
||||
-0.5,
|
||||
-0.5,
|
||||
0.5,
|
||||
-0.5,
|
||||
-0.5,
|
||||
-0.5,
|
||||
0.5,
|
||||
0.5,
|
||||
-0.5,
|
||||
0.5,
|
||||
0.5,
|
||||
0.5,
|
||||
0.5,
|
||||
-0.5,
|
||||
0.5,
|
||||
0.5,
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cube_faces():
|
||||
|
||||
return [
|
||||
4, 0, 3, 2, 1, # bottom (-z)
|
||||
4, 4, 5, 6, 7, # top (+z)
|
||||
4, 0, 1, 5, 4, # front (-y)
|
||||
4, 3, 7, 6, 2, # back (+y)
|
||||
4, 0, 4, 7, 3, # left (-x)
|
||||
4, 1, 2, 6, 5 # right (+x)
|
||||
4,
|
||||
0,
|
||||
3,
|
||||
2,
|
||||
1, # bottom (-z)
|
||||
4,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7, # top (+z)
|
||||
4,
|
||||
0,
|
||||
1,
|
||||
5,
|
||||
4, # front (-y)
|
||||
4,
|
||||
3,
|
||||
7,
|
||||
6,
|
||||
2, # back (+y)
|
||||
4,
|
||||
0,
|
||||
4,
|
||||
7,
|
||||
3, # left (-x)
|
||||
4,
|
||||
1,
|
||||
2,
|
||||
6,
|
||||
5, # right (+x)
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cube_colors():
|
||||
|
||||
return [
|
||||
255, 0, 0, 255, # red
|
||||
0, 255, 0, 255, # green
|
||||
0, 0, 255, 255, # blue
|
||||
255, 255, 0, 255, # yellow
|
||||
255, 0, 255, 255, # magenta
|
||||
0, 255, 255, 255, # cyan
|
||||
255, 255, 255, 255, # white
|
||||
0, 0, 0, 255 # black
|
||||
255,
|
||||
0,
|
||||
0,
|
||||
255, # red
|
||||
0,
|
||||
255,
|
||||
0,
|
||||
255, # green
|
||||
0,
|
||||
0,
|
||||
255,
|
||||
255, # blue
|
||||
255,
|
||||
255,
|
||||
0,
|
||||
255, # yellow
|
||||
255,
|
||||
0,
|
||||
255,
|
||||
255, # magenta
|
||||
0,
|
||||
255,
|
||||
255,
|
||||
255, # cyan
|
||||
255,
|
||||
255,
|
||||
255,
|
||||
255, # white
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
255, # black
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cube_texture_coords():
|
||||
|
||||
return [
|
||||
0.0, 0.0,
|
||||
1.0, 0.0,
|
||||
1.0, 1.0,
|
||||
0.0, 1.0,
|
||||
0.0, 0.0,
|
||||
1.0, 0.0,
|
||||
1.0, 1.0,
|
||||
0.0, 1.0
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
1.0,
|
||||
1.0,
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
1.0,
|
||||
1.0,
|
||||
0.0,
|
||||
1.0,
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_mesh(cube_vertices, cube_faces):
|
||||
|
||||
return Mesh(vertices=cube_vertices, faces=cube_faces, units=Units.m)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def full_mesh(cube_vertices, cube_faces, cube_colors, cube_texture_coords):
|
||||
|
||||
return Mesh(
|
||||
vertices=cube_vertices,
|
||||
faces=cube_faces,
|
||||
colors=cube_colors,
|
||||
textureCoordinates=cube_texture_coords,
|
||||
units=Units.m
|
||||
units=Units.m,
|
||||
)
|
||||
|
||||
|
||||
@@ -101,7 +167,6 @@ def test_mesh_texture_coordinates_count(full_mesh):
|
||||
|
||||
|
||||
def test_mesh_get_point(sample_mesh):
|
||||
|
||||
point = sample_mesh.get_point(0)
|
||||
assert isinstance(point, Point)
|
||||
assert point.x == -0.5
|
||||
@@ -142,14 +207,12 @@ def test_mesh_is_closed(sample_mesh):
|
||||
|
||||
|
||||
def test_mesh_area(sample_mesh):
|
||||
|
||||
calculated_area = sample_mesh.calculate_area()
|
||||
sample_mesh.area = calculated_area
|
||||
assert sample_mesh.area == pytest.approx(6.0)
|
||||
|
||||
|
||||
def test_mesh_volume(sample_mesh):
|
||||
|
||||
calculated_volume = sample_mesh.calculate_volume()
|
||||
sample_mesh.volume = calculated_volume
|
||||
|
||||
@@ -158,15 +221,13 @@ def test_mesh_volume(sample_mesh):
|
||||
|
||||
|
||||
def test_mesh_invalid_vertices():
|
||||
|
||||
mesh = Mesh(vertices=[0.0, 0.0], faces=[3, 0, 1, 2], units=Units.m)
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
mesh.vertices_count
|
||||
_ = mesh.vertices_count
|
||||
|
||||
|
||||
def test_mesh_invalid_faces():
|
||||
|
||||
vertices = [0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0]
|
||||
with pytest.raises(IndexError):
|
||||
# Face references vertex index out of range
|
||||
|
||||
@@ -1,117 +1,132 @@
|
||||
from typing import Any, Tuple
|
||||
|
||||
import pytest
|
||||
|
||||
from specklepy.core.api.operations import deserialize, serialize
|
||||
from specklepy.objects.geometry import Plane, Point, Vector
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
from specklepy.objects.geometry import Plane, Point, Spiral, Vector
|
||||
from specklepy.objects.models.units import Units
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_point():
|
||||
|
||||
point = Point(x=1.0, y=2.0, z=3.0, units=Units.m)
|
||||
return point
|
||||
def sample_points() -> Tuple[Point, Point]:
|
||||
return Point(x=0.0, y=0.0, z=0.0, units=Units.m), Point(
|
||||
x=0.0, y=0.0, z=2.0, units=Units.m
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_vectors():
|
||||
|
||||
def sample_plane() -> Plane:
|
||||
origin = Point(x=0.0, y=0.0, z=0.0, units=Units.m)
|
||||
normal = Vector(x=0.0, y=0.0, z=1.0, units=Units.m)
|
||||
xdir = Vector(x=1.0, y=0.0, z=0.0, units=Units.m)
|
||||
ydir = Vector(x=0.0, y=1.0, z=0.0, units=Units.m)
|
||||
|
||||
return normal, xdir, ydir
|
||||
return Plane(origin=origin, normal=normal, xdir=xdir, ydir=ydir, units=Units.m)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_plane(sample_point, sample_vectors):
|
||||
|
||||
normal, xdir, ydir = sample_vectors
|
||||
plane = Plane(
|
||||
origin=sample_point,
|
||||
normal=normal,
|
||||
xdir=xdir,
|
||||
ydir=ydir,
|
||||
units=Units.m
|
||||
)
|
||||
return plane
|
||||
|
||||
|
||||
def test_plane_creation(sample_point, sample_vectors):
|
||||
|
||||
normal, xdir, ydir = sample_vectors
|
||||
plane = Plane(
|
||||
origin=sample_point,
|
||||
normal=normal,
|
||||
xdir=xdir,
|
||||
ydir=ydir,
|
||||
units=Units.m
|
||||
def sample_spiral(sample_points: Tuple[Point, Point], sample_plane: Plane) -> Spiral:
|
||||
start, end = sample_points
|
||||
pitch_axis = Vector(x=0.0, y=0.0, z=1.0, units=Units.m)
|
||||
return Spiral(
|
||||
start_point=start,
|
||||
end_point=end,
|
||||
plane=sample_plane,
|
||||
turns=2.0,
|
||||
pitch=1.0,
|
||||
pitch_axis=pitch_axis,
|
||||
units=Units.m,
|
||||
)
|
||||
|
||||
assert plane.origin == sample_point
|
||||
assert plane.normal == normal
|
||||
assert plane.xdir == xdir
|
||||
assert plane.ydir == ydir
|
||||
assert plane.units == Units.m.value
|
||||
|
||||
|
||||
def test_plane_units(sample_point, sample_vectors):
|
||||
|
||||
normal, xdir, ydir = sample_vectors
|
||||
plane = Plane(
|
||||
origin=sample_point,
|
||||
normal=normal,
|
||||
xdir=xdir,
|
||||
ydir=ydir,
|
||||
units=Units.m
|
||||
def test_spiral_creation(sample_points: Tuple[Point, Point], sample_plane: Plane):
|
||||
start, end = sample_points
|
||||
pitch_axis = Vector(x=0.0, y=0.0, z=1.0, units=Units.m)
|
||||
spiral = Spiral(
|
||||
start_point=start,
|
||||
end_point=end,
|
||||
plane=sample_plane,
|
||||
turns=2.0,
|
||||
pitch=1.0,
|
||||
pitch_axis=pitch_axis,
|
||||
units=Units.m,
|
||||
)
|
||||
|
||||
assert plane.units == Units.m.value
|
||||
|
||||
plane.units = "mm"
|
||||
assert plane.units == "mm"
|
||||
assert spiral.start_point == start
|
||||
assert spiral.end_point == end
|
||||
assert spiral.plane == sample_plane
|
||||
assert spiral.turns == 2.0
|
||||
assert spiral.pitch == 1.0
|
||||
assert spiral.pitch_axis == pitch_axis
|
||||
assert spiral.units == Units.m.value
|
||||
|
||||
|
||||
def test_plane_invalid_construction():
|
||||
@pytest.mark.parametrize(
|
||||
"invalid_param,invalid_value",
|
||||
[
|
||||
("start_point", "not a point"),
|
||||
("end_point", "not a point"),
|
||||
("plane", "not a plane"),
|
||||
("turns", "not a number"),
|
||||
],
|
||||
)
|
||||
def test_spiral_invalid_construction(
|
||||
sample_points: Tuple[Point, Point],
|
||||
sample_plane: Plane,
|
||||
invalid_param: str,
|
||||
invalid_value: Any,
|
||||
):
|
||||
start, end = sample_points
|
||||
pitch_axis = Vector(x=0.0, y=0.0, z=1.0, units=Units.m)
|
||||
|
||||
point = Point(x=1.0, y=2.0, z=3.0, units=Units.m)
|
||||
normal = Vector(x=0.0, y=0.0, z=1.0, units=Units.m)
|
||||
xdir = Vector(x=1.0, y=0.0, z=0.0, units=Units.m)
|
||||
ydir = Vector(x=0.0, y=1.0, z=0.0, units=Units.m)
|
||||
valid_params = {
|
||||
"start_point": start,
|
||||
"end_point": end,
|
||||
"plane": sample_plane,
|
||||
"turns": 2.0,
|
||||
"pitch": 1.0,
|
||||
"pitch_axis": pitch_axis,
|
||||
"units": Units.m,
|
||||
}
|
||||
|
||||
with pytest.raises(Exception):
|
||||
Plane(origin="not a point", normal=normal, xdir=xdir, ydir=ydir)
|
||||
valid_params[invalid_param] = invalid_value
|
||||
|
||||
with pytest.raises(Exception):
|
||||
Plane(origin=point, normal="not a vector", xdir=xdir, ydir=ydir)
|
||||
|
||||
with pytest.raises(Exception):
|
||||
Plane(origin=point, normal=normal, xdir="not a vector", ydir=ydir)
|
||||
|
||||
# Test with invalid ydir vector
|
||||
with pytest.raises(Exception):
|
||||
Plane(origin=point, normal=normal, xdir=xdir, ydir="not a vector")
|
||||
with pytest.raises(SpeckleException):
|
||||
Spiral(**valid_params)
|
||||
|
||||
|
||||
def test_plane_serialization(sample_plane):
|
||||
@pytest.mark.parametrize("test_value", [10.0])
|
||||
def test_spiral_length(sample_spiral: Spiral, test_value: float):
|
||||
sample_spiral.length = test_value
|
||||
assert sample_spiral.length == test_value
|
||||
|
||||
serialized = serialize(sample_plane)
|
||||
|
||||
@pytest.mark.parametrize("test_value", [15.0])
|
||||
def test_spiral_area(sample_spiral: Spiral, test_value: float):
|
||||
sample_spiral.area = test_value
|
||||
assert sample_spiral.area == test_value
|
||||
|
||||
|
||||
def test_spiral_serialization(sample_spiral: Spiral):
|
||||
serialized = serialize(sample_spiral)
|
||||
deserialized = deserialize(serialized)
|
||||
|
||||
# Check all properties are preserved
|
||||
assert deserialized.origin.x == sample_plane.origin.x
|
||||
assert deserialized.origin.y == sample_plane.origin.y
|
||||
assert deserialized.origin.z == sample_plane.origin.z
|
||||
assert deserialized.start_point.x == sample_spiral.start_point.x
|
||||
assert deserialized.start_point.y == sample_spiral.start_point.y
|
||||
assert deserialized.start_point.z == sample_spiral.start_point.z
|
||||
|
||||
assert deserialized.normal.x == sample_plane.normal.x
|
||||
assert deserialized.normal.y == sample_plane.normal.y
|
||||
assert deserialized.normal.z == sample_plane.normal.z
|
||||
assert deserialized.end_point.x == sample_spiral.end_point.x
|
||||
assert deserialized.end_point.y == sample_spiral.end_point.y
|
||||
assert deserialized.end_point.z == sample_spiral.end_point.z
|
||||
|
||||
assert deserialized.xdir.x == sample_plane.xdir.x
|
||||
assert deserialized.xdir.y == sample_plane.xdir.y
|
||||
assert deserialized.xdir.z == sample_plane.xdir.z
|
||||
assert deserialized.plane.origin.x == sample_spiral.plane.origin.x
|
||||
assert deserialized.plane.origin.y == sample_spiral.plane.origin.y
|
||||
assert deserialized.plane.origin.z == sample_spiral.plane.origin.z
|
||||
|
||||
assert deserialized.ydir.x == sample_plane.ydir.x
|
||||
assert deserialized.ydir.y == sample_plane.ydir.y
|
||||
assert deserialized.ydir.z == sample_plane.ydir.z
|
||||
assert deserialized.turns == sample_spiral.turns
|
||||
assert deserialized.pitch == sample_spiral.pitch
|
||||
assert deserialized.pitch_axis.x == sample_spiral.pitch_axis.x
|
||||
assert deserialized.pitch_axis.y == sample_spiral.pitch_axis.y
|
||||
assert deserialized.pitch_axis.z == sample_spiral.pitch_axis.z
|
||||
|
||||
assert deserialized.units == sample_plane.units
|
||||
assert deserialized.units == sample_spiral.units
|
||||
|
||||
@@ -18,7 +18,7 @@ def test_point_distance_calculation():
|
||||
p2 = Point(x=4.0, y=6.0, z=8.0, units=Units.m)
|
||||
|
||||
distance = p1.distance_to(p2)
|
||||
expected = ((3.0**2 + 4.0**2 + 5.0**2) ** 0.5)
|
||||
expected = (3.0**2 + 4.0**2 + 5.0**2) ** 0.5
|
||||
assert distance == pytest.approx(expected)
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import pytest
|
||||
|
||||
from specklepy.core.api.operations import deserialize, serialize
|
||||
from specklepy.objects.geometry import Point, PointCloud
|
||||
from specklepy.objects.models.units import Units
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_points():
|
||||
return [
|
||||
Point(x=0.0, y=0.0, z=0.0, units=Units.m),
|
||||
Point(x=1.0, y=0.0, z=0.0, units=Units.m),
|
||||
Point(x=0.0, y=1.0, z=0.0, units=Units.m),
|
||||
Point(x=1.0, y=1.0, z=0.0, units=Units.m),
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_point_cloud(sample_points):
|
||||
return PointCloud(points=sample_points, units=Units.m)
|
||||
|
||||
|
||||
def test_point_cloud_creation(sample_points):
|
||||
point_cloud = PointCloud(points=sample_points, units=Units.m)
|
||||
|
||||
assert len(point_cloud.points) == 4
|
||||
assert isinstance(point_cloud.points, list)
|
||||
assert all(isinstance(p, Point) for p in point_cloud.points)
|
||||
assert point_cloud.units == Units.m.value
|
||||
|
||||
|
||||
def test_point_cloud_units(sample_points):
|
||||
point_cloud = PointCloud(points=sample_points, units=Units.m)
|
||||
assert point_cloud.units == Units.m.value
|
||||
|
||||
point_cloud.units = "mm"
|
||||
assert point_cloud.units == "mm"
|
||||
|
||||
|
||||
def test_point_cloud_empty_points():
|
||||
point_cloud = PointCloud(points=[], units=Units.m)
|
||||
assert len(point_cloud.points) == 0
|
||||
assert isinstance(point_cloud.points, list)
|
||||
|
||||
|
||||
def test_point_cloud_serialization(sample_point_cloud):
|
||||
serialized = serialize(sample_point_cloud)
|
||||
deserialized = deserialize(serialized)
|
||||
|
||||
assert len(deserialized.points) == len(sample_point_cloud.points)
|
||||
|
||||
for orig_point, deserial_point in zip(
|
||||
sample_point_cloud.points, deserialized.points, strict=True
|
||||
):
|
||||
assert deserial_point.x == orig_point.x
|
||||
assert deserial_point.y == orig_point.y
|
||||
assert deserial_point.z == orig_point.z
|
||||
assert deserial_point.units == orig_point.units
|
||||
|
||||
assert deserialized.units == sample_point_cloud.units
|
||||
@@ -0,0 +1,108 @@
|
||||
from typing import List, Tuple
|
||||
|
||||
import pytest
|
||||
|
||||
from specklepy.core.api.operations import deserialize, serialize
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
from specklepy.objects.geometry import Line, Point, Polycurve, Polyline
|
||||
from specklepy.objects.models.units import Units
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_points() -> Tuple[Point, Point, Point]:
|
||||
return (
|
||||
Point(x=0.0, y=0.0, z=0.0, units=Units.m),
|
||||
Point(x=1.0, y=0.0, z=0.0, units=Units.m),
|
||||
Point(x=1.0, y=1.0, z=0.0, units=Units.m),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_lines(sample_points: Tuple[Point, Point, Point]) -> List[Line]:
|
||||
p1, p2, p3 = sample_points
|
||||
return [
|
||||
Line(start=p1, end=p2, units=Units.m),
|
||||
Line(start=p2, end=p3, units=Units.m),
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_polycurve(sample_lines: List[Line]) -> Polycurve:
|
||||
return Polycurve(segments=sample_lines, units=Units.m)
|
||||
|
||||
|
||||
def test_polycurve_creation(sample_lines: List[Line]):
|
||||
polycurve = Polycurve(segments=sample_lines, units=Units.m)
|
||||
assert len(polycurve.segments) == 2
|
||||
assert polycurve.units == Units.m.value
|
||||
assert isinstance(polycurve.segments[0], Line)
|
||||
|
||||
|
||||
def test_polycurve_is_closed(sample_points: Tuple[Point, Point, Point]):
|
||||
p1, p2, p3 = sample_points
|
||||
lines = [
|
||||
Line(start=p1, end=p2, units=Units.m),
|
||||
Line(start=p2, end=p3, units=Units.m),
|
||||
Line(start=p3, end=p1, units=Units.m),
|
||||
]
|
||||
closed_polycurve = Polycurve(segments=lines, units=Units.m)
|
||||
assert closed_polycurve.is_closed()
|
||||
|
||||
|
||||
def test_polycurve_not_closed(sample_polycurve: Polycurve):
|
||||
assert not sample_polycurve.is_closed()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("expected_length", [2.0])
|
||||
def test_polycurve_length(sample_polycurve: Polycurve, expected_length: float):
|
||||
sample_polycurve.length = sample_polycurve.calculate_length()
|
||||
assert sample_polycurve.length == pytest.approx(expected_length)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"points,expected_segments,expected_closed",
|
||||
[
|
||||
([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0], 2, False),
|
||||
(
|
||||
[0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0],
|
||||
4,
|
||||
True,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_polycurve_from_polyline(
|
||||
points: List[float], expected_segments: int, expected_closed: bool
|
||||
):
|
||||
polyline = Polyline(value=points, units=Units.m)
|
||||
polycurve = Polycurve.from_polyline(polyline)
|
||||
|
||||
assert len(polycurve.segments) == expected_segments
|
||||
assert polycurve.units == Units.m.value
|
||||
assert isinstance(polycurve.segments[0], Line)
|
||||
assert polycurve.is_closed() == expected_closed
|
||||
|
||||
|
||||
def test_polycurve_serialization(sample_polycurve: Polycurve):
|
||||
serialized = serialize(sample_polycurve)
|
||||
deserialized = deserialize(serialized)
|
||||
|
||||
assert len(deserialized.segments) == len(sample_polycurve.segments)
|
||||
assert deserialized.units == sample_polycurve.units
|
||||
|
||||
assert deserialized.segments[0].start.x == sample_polycurve.segments[0].start.x
|
||||
assert deserialized.segments[0].start.y == sample_polycurve.segments[0].start.y
|
||||
assert deserialized.segments[0].start.z == sample_polycurve.segments[0].start.z
|
||||
assert deserialized.segments[0].end.x == sample_polycurve.segments[0].end.x
|
||||
assert deserialized.segments[0].end.y == sample_polycurve.segments[0].end.y
|
||||
assert deserialized.segments[0].end.z == sample_polycurve.segments[0].end.z
|
||||
|
||||
|
||||
def test_polycurve_empty():
|
||||
polycurve = Polycurve(segments=[], units=Units.m)
|
||||
assert not polycurve.is_closed()
|
||||
assert polycurve.calculate_length() == 0.0
|
||||
|
||||
|
||||
def test_polycurve_invalid_segment():
|
||||
with pytest.raises(SpeckleException):
|
||||
Polycurve(segments=["not a curve"], units=Units.m)
|
||||
@@ -8,24 +8,40 @@ from specklepy.objects.primitive import Interval
|
||||
|
||||
@pytest.fixture
|
||||
def open_square_coords():
|
||||
|
||||
return [
|
||||
0.0, 0.0, 0.0, # point 1
|
||||
1.0, 0.0, 0.0, # point 2
|
||||
1.0, 1.0, 0.0, # point 3
|
||||
0.0, 1.0, 0.0 # point 4
|
||||
0.0,
|
||||
0.0,
|
||||
0.0, # point 1
|
||||
1.0,
|
||||
0.0,
|
||||
0.0, # point 2
|
||||
1.0,
|
||||
1.0,
|
||||
0.0, # point 3
|
||||
0.0,
|
||||
1.0,
|
||||
0.0, # point 4
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def closed_square_coords():
|
||||
|
||||
return [
|
||||
0.0, 0.0, 0.0, # point 1
|
||||
1.0, 0.0, 0.0, # point 2
|
||||
1.0, 1.0, 0.0, # point 3
|
||||
0.0, 1.0, 0.0, # point 4
|
||||
0.0, 0.0, 0.0 # point 5 (same as point 1)
|
||||
0.0,
|
||||
0.0,
|
||||
0.0, # point 1
|
||||
1.0,
|
||||
0.0,
|
||||
0.0, # point 2
|
||||
1.0,
|
||||
1.0,
|
||||
0.0, # point 3
|
||||
0.0,
|
||||
1.0,
|
||||
0.0, # point 4
|
||||
0.0,
|
||||
0.0,
|
||||
0.0, # point 5 (same as point 1)
|
||||
]
|
||||
|
||||
|
||||
@@ -55,9 +71,11 @@ def test_polyline_is_closed(open_square_coords, closed_square_coords):
|
||||
|
||||
|
||||
def test_polyline_is_closed_with_tolerance(open_square_coords):
|
||||
|
||||
almost_closed = open_square_coords + \
|
||||
[0.0, 0.0, 0.001] # last point slightly above start
|
||||
almost_closed = open_square_coords + [
|
||||
0.0,
|
||||
0.0,
|
||||
0.001,
|
||||
] # last point slightly above start
|
||||
poly = Polyline(value=almost_closed, units=Units.m)
|
||||
|
||||
assert not poly.is_closed(tolerance=1e-6)
|
||||
@@ -87,7 +105,7 @@ def test_polyline_get_points(sample_polyline):
|
||||
Point(x=0.0, y=0.0, z=0.0, units=Units.m),
|
||||
Point(x=1.0, y=0.0, z=0.0, units=Units.m),
|
||||
Point(x=1.0, y=1.0, z=0.0, units=Units.m),
|
||||
Point(x=0.0, y=1.0, z=0.0, units=Units.m)
|
||||
Point(x=0.0, y=1.0, z=0.0, units=Units.m),
|
||||
]
|
||||
|
||||
# Check coordinates match
|
||||
@@ -98,7 +116,6 @@ def test_polyline_get_points(sample_polyline):
|
||||
|
||||
|
||||
def test_polyline_invalid_coordinates():
|
||||
|
||||
invalid_coords = [0.0, 0.0, 0.0, 1.0, 1.0] # missing one coordinate
|
||||
with pytest.raises(ValueError):
|
||||
polyline = Polyline(value=invalid_coords, units=Units.m)
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
from typing import Any, Tuple
|
||||
|
||||
import pytest
|
||||
|
||||
from specklepy.core.api.operations import deserialize, serialize
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
from specklepy.objects.geometry import Plane, Point, Spiral, Vector
|
||||
from specklepy.objects.models.units import Units
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_points() -> Tuple[Point, Point]:
|
||||
return (
|
||||
Point(x=0.0, y=0.0, z=0.0, units=Units.m),
|
||||
Point(x=0.0, y=0.0, z=2.0, units=Units.m),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_plane() -> Plane:
|
||||
origin = Point(x=0.0, y=0.0, z=0.0, units=Units.m)
|
||||
normal = Vector(x=0.0, y=0.0, z=1.0, units=Units.m)
|
||||
xdir = Vector(x=1.0, y=0.0, z=0.0, units=Units.m)
|
||||
ydir = Vector(x=0.0, y=1.0, z=0.0, units=Units.m)
|
||||
return Plane(origin=origin, normal=normal, xdir=xdir, ydir=ydir, units=Units.m)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_spiral(sample_points: Tuple[Point, Point], sample_plane: Plane) -> Spiral:
|
||||
start, end = sample_points
|
||||
pitch_axis = Vector(x=0.0, y=0.0, z=1.0, units=Units.m)
|
||||
return Spiral(
|
||||
start_point=start,
|
||||
end_point=end,
|
||||
plane=sample_plane,
|
||||
turns=2.0,
|
||||
pitch=1.0,
|
||||
pitch_axis=pitch_axis,
|
||||
units=Units.m,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("units", [Units.m])
|
||||
def test_spiral_creation(
|
||||
sample_points: Tuple[Point, Point], sample_plane: Plane, units: Units
|
||||
):
|
||||
start, end = sample_points
|
||||
pitch_axis = Vector(x=0.0, y=0.0, z=1.0, units=units)
|
||||
spiral = Spiral(
|
||||
start_point=start,
|
||||
end_point=end,
|
||||
plane=sample_plane,
|
||||
turns=2.0,
|
||||
pitch=1.0,
|
||||
pitch_axis=pitch_axis,
|
||||
units=units,
|
||||
)
|
||||
|
||||
assert spiral.start_point == start
|
||||
assert spiral.end_point == end
|
||||
assert spiral.plane == sample_plane
|
||||
assert spiral.turns == 2.0
|
||||
assert spiral.pitch == 1.0
|
||||
assert spiral.pitch_axis == pitch_axis
|
||||
assert spiral.units == units.value
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"invalid_param,invalid_value",
|
||||
[
|
||||
("start_point", "not a point"),
|
||||
("end_point", "not a point"),
|
||||
("plane", "not a plane"),
|
||||
("turns", "not a number"),
|
||||
],
|
||||
)
|
||||
def test_spiral_invalid_construction(
|
||||
sample_points: Tuple[Point, Point],
|
||||
sample_plane: Plane,
|
||||
invalid_param: str,
|
||||
invalid_value: Any,
|
||||
):
|
||||
start, end = sample_points
|
||||
pitch_axis = Vector(x=0.0, y=0.0, z=1.0, units=Units.m)
|
||||
valid_params = {
|
||||
"start_point": start,
|
||||
"end_point": end,
|
||||
"plane": sample_plane,
|
||||
"turns": 2.0,
|
||||
"pitch": 1.0,
|
||||
"pitch_axis": pitch_axis,
|
||||
"units": Units.m,
|
||||
}
|
||||
valid_params[invalid_param] = invalid_value
|
||||
|
||||
with pytest.raises(SpeckleException):
|
||||
Spiral(**valid_params)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("test_value", [10.0])
|
||||
def test_spiral_length(sample_spiral: Spiral, test_value: float):
|
||||
sample_spiral.length = test_value
|
||||
assert sample_spiral.length == test_value
|
||||
|
||||
|
||||
@pytest.mark.parametrize("test_value", [15.0])
|
||||
def test_spiral_area(sample_spiral: Spiral, test_value: float):
|
||||
sample_spiral.area = test_value
|
||||
assert sample_spiral.area == test_value
|
||||
|
||||
|
||||
def test_spiral_serialization(sample_spiral: Spiral):
|
||||
serialized = serialize(sample_spiral)
|
||||
deserialized = deserialize(serialized)
|
||||
|
||||
assert deserialized.start_point.x == sample_spiral.start_point.x
|
||||
assert deserialized.start_point.y == sample_spiral.start_point.y
|
||||
assert deserialized.start_point.z == sample_spiral.start_point.z
|
||||
assert deserialized.end_point.x == sample_spiral.end_point.x
|
||||
assert deserialized.end_point.y == sample_spiral.end_point.y
|
||||
assert deserialized.end_point.z == sample_spiral.end_point.z
|
||||
assert deserialized.plane.origin.x == sample_spiral.plane.origin.x
|
||||
assert deserialized.plane.origin.y == sample_spiral.plane.origin.y
|
||||
assert deserialized.plane.origin.z == sample_spiral.plane.origin.z
|
||||
assert deserialized.turns == sample_spiral.turns
|
||||
assert deserialized.pitch == sample_spiral.pitch
|
||||
assert deserialized.pitch_axis.x == sample_spiral.pitch_axis.x
|
||||
assert deserialized.pitch_axis.y == sample_spiral.pitch_axis.y
|
||||
assert deserialized.pitch_axis.z == sample_spiral.pitch_axis.z
|
||||
assert deserialized.units == sample_spiral.units
|
||||
@@ -0,0 +1,189 @@
|
||||
from typing import Any, List, Tuple
|
||||
|
||||
import pytest
|
||||
|
||||
from specklepy.core.api.operations import deserialize, serialize
|
||||
from specklepy.logging.exceptions import SpeckleException
|
||||
from specklepy.objects.geometry import ControlPoint, Surface
|
||||
from specklepy.objects.models.units import Units
|
||||
from specklepy.objects.primitive import Interval
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_intervals() -> Tuple[Interval, Interval]:
|
||||
return (Interval(start=0.0, end=1.0), Interval(start=0.0, end=1.0))
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_point_data() -> List[float]:
|
||||
return [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0, # point 1
|
||||
1.0,
|
||||
0.0,
|
||||
0.0,
|
||||
1.0, # point 2
|
||||
0.0,
|
||||
1.0,
|
||||
0.0,
|
||||
1.0, # point 3
|
||||
1.0,
|
||||
1.0,
|
||||
0.0,
|
||||
1.0, # point 4
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_surface(
|
||||
sample_intervals: Tuple[Interval, Interval], sample_point_data: List[float]
|
||||
) -> Surface:
|
||||
domain_u, domain_v = sample_intervals
|
||||
return Surface(
|
||||
degreeU=1,
|
||||
degreeV=1,
|
||||
rational=True,
|
||||
pointData=sample_point_data,
|
||||
countU=2,
|
||||
countV=2,
|
||||
knotsU=[0.0, 0.0, 1.0, 1.0],
|
||||
knotsV=[0.0, 0.0, 1.0, 1.0],
|
||||
domainU=domain_u,
|
||||
domainV=domain_v,
|
||||
closedU=False,
|
||||
closedV=False,
|
||||
units=Units.m,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("units", [Units.m])
|
||||
def test_surface_creation(
|
||||
sample_intervals: Tuple[Interval, Interval],
|
||||
sample_point_data: List[float],
|
||||
units: Units,
|
||||
):
|
||||
domain_u, domain_v = sample_intervals
|
||||
surface = Surface(
|
||||
degreeU=1,
|
||||
degreeV=1,
|
||||
rational=True,
|
||||
pointData=sample_point_data,
|
||||
countU=2,
|
||||
countV=2,
|
||||
knotsU=[0.0, 0.0, 1.0, 1.0],
|
||||
knotsV=[0.0, 0.0, 1.0, 1.0],
|
||||
domainU=domain_u,
|
||||
domainV=domain_v,
|
||||
closedU=False,
|
||||
closedV=False,
|
||||
units=units,
|
||||
)
|
||||
|
||||
assert surface.degreeU == 1
|
||||
assert surface.degreeV == 1
|
||||
assert surface.rational
|
||||
assert surface.pointData == sample_point_data
|
||||
assert surface.countU == 2
|
||||
assert surface.countV == 2
|
||||
assert surface.knotsU == [0.0, 0.0, 1.0, 1.0]
|
||||
assert surface.knotsV == [0.0, 0.0, 1.0, 1.0]
|
||||
assert surface.domainU == domain_u
|
||||
assert surface.domainV == domain_v
|
||||
assert not surface.closedU
|
||||
assert not surface.closedV
|
||||
assert surface.units == units.value
|
||||
|
||||
|
||||
@pytest.mark.parametrize("test_value", [1.0])
|
||||
def test_surface_area(sample_surface: Surface, test_value: float):
|
||||
sample_surface.area = test_value
|
||||
assert sample_surface.area == test_value
|
||||
|
||||
|
||||
def test_surface_get_control_points(sample_surface: Surface):
|
||||
control_points = sample_surface.get_control_points()
|
||||
|
||||
assert len(control_points) == 2
|
||||
assert len(control_points[0]) == 2
|
||||
assert isinstance(control_points[0][0], ControlPoint)
|
||||
assert control_points[0][0].x == 0.0
|
||||
assert control_points[0][0].y == 0.0
|
||||
assert control_points[0][0].z == 0.0
|
||||
assert control_points[0][0].weight == 1.0
|
||||
assert control_points[0][0].units == Units.m.value
|
||||
|
||||
|
||||
@pytest.mark.parametrize("units", [Units.m])
|
||||
def test_surface_set_control_points(sample_surface: Surface, units: Units):
|
||||
control_points = [
|
||||
[
|
||||
ControlPoint(x=0.0, y=0.0, z=0.0, weight=1.0, units=units),
|
||||
ControlPoint(x=1.0, y=0.0, z=0.0, weight=1.0, units=units),
|
||||
],
|
||||
[
|
||||
ControlPoint(x=0.0, y=1.0, z=0.0, weight=1.0, units=units),
|
||||
ControlPoint(x=1.0, y=1.0, z=0.0, weight=1.0, units=units),
|
||||
],
|
||||
]
|
||||
|
||||
sample_surface.set_control_points(control_points)
|
||||
|
||||
assert sample_surface.countU == 2
|
||||
assert sample_surface.countV == 2
|
||||
assert len(sample_surface.pointData) == 16
|
||||
assert sample_surface.pointData[0:4] == [0.0, 0.0, 0.0, 1.0]
|
||||
|
||||
|
||||
def test_surface_serialization(sample_surface: Surface):
|
||||
serialized = serialize(sample_surface)
|
||||
deserialized = deserialize(serialized)
|
||||
|
||||
assert deserialized.degreeU == sample_surface.degreeU
|
||||
assert deserialized.degreeV == sample_surface.degreeV
|
||||
assert deserialized.rational == sample_surface.rational
|
||||
assert deserialized.pointData == sample_surface.pointData
|
||||
assert deserialized.countU == sample_surface.countU
|
||||
assert deserialized.countV == sample_surface.countV
|
||||
assert deserialized.knotsU == sample_surface.knotsU
|
||||
assert deserialized.knotsV == sample_surface.knotsV
|
||||
assert deserialized.domainU.start == sample_surface.domainU.start
|
||||
assert deserialized.domainU.end == sample_surface.domainU.end
|
||||
assert deserialized.domainV.start == sample_surface.domainV.start
|
||||
assert deserialized.domainV.end == sample_surface.domainV.end
|
||||
assert deserialized.closedU == sample_surface.closedU
|
||||
assert deserialized.closedV == sample_surface.closedV
|
||||
assert deserialized.units == sample_surface.units
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"invalid_param,invalid_value",
|
||||
[("degreeU", "not a number")],
|
||||
)
|
||||
def test_surface_invalid_construction(
|
||||
sample_intervals: Tuple[Interval, Interval],
|
||||
invalid_param: str,
|
||||
invalid_value: Any,
|
||||
):
|
||||
domain_u, domain_v = sample_intervals
|
||||
|
||||
valid_params = {
|
||||
"degreeU": 1,
|
||||
"degreeV": 1,
|
||||
"rational": True,
|
||||
"pointData": [0.0, 0.0, 0.0, 1.0],
|
||||
"countU": 1,
|
||||
"countV": 1,
|
||||
"knotsU": [0.0, 1.0],
|
||||
"knotsV": [0.0, 1.0],
|
||||
"domainU": domain_u,
|
||||
"domainV": domain_v,
|
||||
"closedU": False,
|
||||
"closedV": False,
|
||||
"units": Units.m,
|
||||
}
|
||||
valid_params[invalid_param] = invalid_value
|
||||
|
||||
with pytest.raises(SpeckleException):
|
||||
Surface(**valid_params)
|
||||
@@ -24,24 +24,31 @@ class ServerTransport(AbstractTransport):
|
||||
|
||||
```py
|
||||
from specklepy.api import operations
|
||||
from specklepy.api.client import SpeckleClient
|
||||
from specklepy.core.api.inputs.version_inputs import CreateVersionInput
|
||||
from specklepy.transports.server import ServerTransport
|
||||
|
||||
# here's the data you want to send
|
||||
block = Block(length=2, height=4)
|
||||
|
||||
# next create the server transport - this is the vehicle through which
|
||||
# here's the project and model you want to send to
|
||||
project_id = "abcdefghi"
|
||||
model_id = "ihgfedcba"
|
||||
|
||||
# next, create the server transport - this is the vehicle through which
|
||||
# you will send and receive
|
||||
transport = ServerTransport(stream_id=new_stream_id, client=client)
|
||||
transport = ServerTransport(stream_id=project_id, client=client)
|
||||
|
||||
# this serialises the block and sends it to the transport
|
||||
hash = operations.send(base=block, transports=[transport])
|
||||
|
||||
# you can now create a commit on your stream with this object
|
||||
commit_id = client.commit.create(
|
||||
stream_id=new_stream_id,
|
||||
obj_id=hash,
|
||||
message="this is a block I made in speckle-py",
|
||||
# you can now create tag this version of the model with this object
|
||||
input = CreateVersionInput(
|
||||
objectId = hash,
|
||||
modelId = model_id,
|
||||
projectId = project_id,
|
||||
)
|
||||
version = client.version.create(input)
|
||||
```
|
||||
"""
|
||||
|
||||
@@ -127,8 +134,7 @@ class ServerTransport(AbstractTransport):
|
||||
|
||||
raise SpeckleException(
|
||||
"Getting a single object using `ServerTransport.get_object()` is not"
|
||||
" implemented. To get an object from the server, please use the"
|
||||
" `SpeckleClient.object.get()` route",
|
||||
" implemented.",
|
||||
NotImplementedError(),
|
||||
)
|
||||
|
||||
|
||||
+6
-4
@@ -6,7 +6,7 @@ from specklepy.objects.base import Base
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def base():
|
||||
def base() -> Base:
|
||||
base = Base()
|
||||
base.name = "my_base"
|
||||
base.units = "millimetres"
|
||||
@@ -15,7 +15,9 @@ def base():
|
||||
base.tuple = (1, 2, "3")
|
||||
base.set = {1, 2, "3"}
|
||||
base.vertices = [random.uniform(0, 10) for _ in range(1, 120)]
|
||||
base.test_bases = [Base(name=i) for i in range(1, 22)]
|
||||
base["@detach"] = Base(name="detached base")
|
||||
base["@revit_thing"] = Base.of_type("SpecialRevitFamily", name="secret tho")
|
||||
base.test_bases = [Base(applicationId=str(i)) for i in range(1, 22)]
|
||||
base["@detach"] = Base(applicationId="detached base")
|
||||
base["@revit_thing"] = Base.of_type(
|
||||
"SpecialRevitFamily", applicationId="secret tho"
|
||||
)
|
||||
return base
|
||||
|
||||
@@ -59,5 +59,5 @@ class TestActiveUserResource:
|
||||
|
||||
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
|
||||
|
||||
@@ -32,7 +32,7 @@ class TestModelResource:
|
||||
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 @@ class TestModelResource:
|
||||
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 @@ class TestModelResource:
|
||||
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 @@ class TestModelResource:
|
||||
|
||||
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 @@ class TestModelResource:
|
||||
|
||||
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 @@ class TestModelResource:
|
||||
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 @@ class TestModelResource:
|
||||
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 @@ class TestModelResource:
|
||||
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 @@ class TestModelResource:
|
||||
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
|
||||
|
||||
@@ -32,7 +32,7 @@ class TestProjectInviteResource:
|
||||
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 @@ class TestProjectInviteResource:
|
||||
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 @@ class TestProjectInviteResource:
|
||||
|
||||
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 @@ class TestProjectInviteResource:
|
||||
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 @@ class TestProjectInviteResource:
|
||||
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 @@ class TestProjectInviteResource:
|
||||
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 @@ class TestProjectInviteResource:
|
||||
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 @@ class TestProjectInviteResource:
|
||||
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)
|
||||
|
||||
@@ -58,7 +58,7 @@ class TestProjectResource:
|
||||
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"
|
||||
|
||||
@@ -50,7 +50,7 @@ class TestSubscriptionResource:
|
||||
) -> 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 @@ class TestSubscriptionResource:
|
||||
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)
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ class TestVersionResource:
|
||||
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 @@ class TestVersionResource:
|
||||
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 @@ class TestVersionResource:
|
||||
|
||||
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 @@ class TestVersionResource:
|
||||
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 @@ class TestVersionResource:
|
||||
|
||||
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 @@ class TestVersionResource:
|
||||
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 @@ class TestVersionResource:
|
||||
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 @@ class TestVersionResource:
|
||||
|
||||
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 @@ class TestVersionResource:
|
||||
):
|
||||
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 @@ class TestVersionResource:
|
||||
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 @@ class TestVersionResource:
|
||||
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)
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from specklepy.api.client import SpeckleClient
|
||||
from specklepy.api.models import Activity, ActivityCollection, User
|
||||
from specklepy.logging.exceptions import GraphQLException
|
||||
|
||||
|
||||
@pytest.mark.run(order=2)
|
||||
class TestUser:
|
||||
def test_user_get_self(self, client: SpeckleClient, user_dict):
|
||||
fetched_user = client.active_user.get()
|
||||
|
||||
assert isinstance(fetched_user, User)
|
||||
assert fetched_user.name == user_dict["name"]
|
||||
assert fetched_user.email == user_dict["email"]
|
||||
|
||||
user_dict["id"] = fetched_user.id
|
||||
|
||||
def test_user_update(self, client: SpeckleClient):
|
||||
bio = "i am a ghost in the machine"
|
||||
|
||||
with pytest.raises(GraphQLException):
|
||||
client.active_user.update(bio=None)
|
||||
|
||||
updated = client.active_user.update(bio=bio)
|
||||
|
||||
assert isinstance(updated, User)
|
||||
assert isinstance(updated, User)
|
||||
assert updated.bio == bio
|
||||
|
||||
def test_user_activity(self, client: SpeckleClient, second_user_dict):
|
||||
my_activity = client.active_user.activity(limit=10)
|
||||
their_activity = client.other_user.activity(second_user_dict["id"])
|
||||
|
||||
assert isinstance(my_activity, ActivityCollection)
|
||||
assert my_activity.items
|
||||
assert isinstance(my_activity.items[0], Activity)
|
||||
assert my_activity.totalCount
|
||||
assert isinstance(their_activity, ActivityCollection)
|
||||
|
||||
older_activity = client.active_user.activity(before=my_activity.items[0].time)
|
||||
|
||||
assert isinstance(older_activity, ActivityCollection)
|
||||
assert older_activity.totalCount
|
||||
assert older_activity.totalCount < my_activity.totalCount
|
||||
@@ -1,83 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from specklepy.api import operations
|
||||
from specklepy.api.models import Branch, Commit, Stream
|
||||
from specklepy.transports.server import ServerTransport
|
||||
|
||||
|
||||
class TestBranch:
|
||||
@pytest.fixture(scope="module")
|
||||
def branch(self):
|
||||
return Branch(name="olive branch 🌿", description="a test branch")
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def updated_branch(self):
|
||||
return Branch(name="eucalyptus branch 🌿", description="an updated test branch")
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def stream(self, client):
|
||||
stream = Stream(
|
||||
name="a sample stream for testing",
|
||||
description="a stream created for testing",
|
||||
isPublic=True,
|
||||
)
|
||||
stream.id = client.stream.create(
|
||||
stream.name, stream.description, stream.isPublic
|
||||
)
|
||||
return stream
|
||||
|
||||
def test_branch_create(self, client, stream, branch):
|
||||
branch.id = client.branch.create(
|
||||
stream_id=stream.id, name=branch.name, description=branch.description
|
||||
)
|
||||
|
||||
assert isinstance(branch.id, str)
|
||||
|
||||
def test_branch_get(self, client, mesh, stream, branch):
|
||||
transport = ServerTransport(client=client, stream_id=stream.id)
|
||||
mesh.id = operations.send(mesh, transports=[transport])
|
||||
|
||||
client.commit.create(
|
||||
stream_id=stream.id,
|
||||
branch_name=branch.name,
|
||||
object_id=mesh.id,
|
||||
message="a commit for testing branch get",
|
||||
)
|
||||
|
||||
fetched_branch = client.branch.get(stream_id=stream.id, name=branch.name)
|
||||
|
||||
assert isinstance(fetched_branch, Branch)
|
||||
assert fetched_branch.name == branch.name
|
||||
assert fetched_branch.description == branch.description
|
||||
assert isinstance(fetched_branch.commits.items, list)
|
||||
assert isinstance(fetched_branch.commits.items[0], Commit)
|
||||
|
||||
def test_branch_list(self, client, stream, branch):
|
||||
branches = client.branch.list(stream_id=stream.id)
|
||||
print(branches)
|
||||
|
||||
assert isinstance(branches, list)
|
||||
assert len(branches) == 2
|
||||
assert isinstance(branches[0], Branch)
|
||||
assert branches[1].name == branch.name
|
||||
|
||||
def test_branch_update(self, client, stream, branch, updated_branch):
|
||||
updated = client.branch.update(
|
||||
stream_id=stream.id,
|
||||
branch_id=branch.id,
|
||||
name=updated_branch.name,
|
||||
description=updated_branch.description,
|
||||
)
|
||||
|
||||
fetched_branch = client.branch.get(
|
||||
stream_id=stream.id, name=updated_branch.name
|
||||
)
|
||||
|
||||
assert updated is True
|
||||
assert fetched_branch.name == updated_branch.name
|
||||
assert fetched_branch.description == updated_branch.description
|
||||
|
||||
def test_branch_delete(self, client, stream, branch):
|
||||
deleted = client.branch.delete(stream_id=stream.id, branch_id=branch.id)
|
||||
|
||||
assert deleted is True
|
||||
@@ -1,88 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from specklepy.api import operations
|
||||
from specklepy.api.models import Commit, Stream
|
||||
from specklepy.transports.server.server import ServerTransport
|
||||
|
||||
|
||||
@pytest.mark.run(order=6)
|
||||
class TestCommit:
|
||||
@pytest.fixture(scope="module")
|
||||
def commit(self):
|
||||
return Commit(message="a fun little test commit")
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def updated_commit(
|
||||
self,
|
||||
):
|
||||
return Commit(message="a fun little updated commit")
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def stream(self, client):
|
||||
stream = Stream(
|
||||
name="a sample stream for testing",
|
||||
description="a stream created for testing",
|
||||
isPublic=True,
|
||||
)
|
||||
stream.id = client.stream.create(
|
||||
stream.name, stream.description, stream.isPublic
|
||||
)
|
||||
return stream
|
||||
|
||||
def test_commit_create(self, client, stream, mesh, commit):
|
||||
transport = ServerTransport(client=client, stream_id=stream.id)
|
||||
mesh.id = operations.send(mesh, transports=[transport])
|
||||
|
||||
commit.id = client.commit.create(
|
||||
stream_id=stream.id, object_id=mesh.id, message=commit.message
|
||||
)
|
||||
|
||||
assert isinstance(commit.id, str)
|
||||
|
||||
def test_commit_get(self, client, stream, mesh, commit):
|
||||
fetched_commit = client.commit.get(stream_id=stream.id, commit_id=commit.id)
|
||||
|
||||
assert fetched_commit.message == commit.message
|
||||
assert fetched_commit.referencedObject == mesh.id
|
||||
|
||||
def test_commit_list(self, client, stream):
|
||||
commits = client.commit.list(stream_id=stream.id)
|
||||
|
||||
assert isinstance(commits, list)
|
||||
assert isinstance(commits[0], Commit)
|
||||
|
||||
def test_commit_update(self, client, stream, commit, updated_commit):
|
||||
updated = client.commit.update(
|
||||
stream_id=stream.id, commit_id=commit.id, message=updated_commit.message
|
||||
)
|
||||
|
||||
fetched_commit = client.commit.get(stream_id=stream.id, commit_id=commit.id)
|
||||
|
||||
assert updated is True
|
||||
assert fetched_commit.message == updated_commit.message
|
||||
|
||||
def test_commit_delete(self, client, stream, mesh):
|
||||
commit_id = client.commit.create(
|
||||
stream_id=stream.id, object_id=mesh.id, message="a great commit to delete"
|
||||
)
|
||||
|
||||
deleted = client.commit.delete(stream_id=stream.id, commit_id=commit_id)
|
||||
|
||||
assert deleted is True
|
||||
|
||||
def test_commit_marked_as_received(self, client, stream, mesh) -> None:
|
||||
commit = Commit(message="this commit should be received")
|
||||
commit.id = client.commit.create(
|
||||
stream_id=stream.id,
|
||||
object_id=mesh.id,
|
||||
message=commit.message,
|
||||
)
|
||||
|
||||
commit_marked_received = client.commit.received(
|
||||
stream.id,
|
||||
commit.id,
|
||||
source_application="pytest",
|
||||
message="testing received",
|
||||
)
|
||||
|
||||
assert commit_marked_received is True
|
||||
@@ -1,49 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from specklepy.api.client import SpeckleClient
|
||||
from specklepy.api.models import Activity, ActivityCollection, LimitedUser
|
||||
|
||||
|
||||
@pytest.mark.run(order=4)
|
||||
class TestOtherUser:
|
||||
def test_user_get_self(self, client):
|
||||
"""
|
||||
Test, that a limited user query cannot query the active user.
|
||||
"""
|
||||
with pytest.raises(TypeError):
|
||||
client.other_user.get()
|
||||
|
||||
def test_user_search(self, client: SpeckleClient, second_user_dict):
|
||||
search_results = client.other_user.search(
|
||||
search_query=second_user_dict["name"][:5]
|
||||
)
|
||||
|
||||
assert isinstance(search_results, list)
|
||||
assert len(search_results) > 0
|
||||
result_user = search_results[0]
|
||||
assert isinstance(result_user, LimitedUser)
|
||||
assert result_user.name == second_user_dict["name"]
|
||||
|
||||
second_user_dict["id"] = result_user.id
|
||||
assert getattr(result_user, "email", None) is None
|
||||
|
||||
def test_user_get(self, client: SpeckleClient, second_user_dict):
|
||||
fetched_user = client.other_user.get(id=second_user_dict["id"])
|
||||
|
||||
assert isinstance(fetched_user, LimitedUser)
|
||||
assert fetched_user.name == second_user_dict["name"]
|
||||
# changed in the server, now you cannot get emails of other users
|
||||
# not checking this, since the first user could or could not be an admin
|
||||
# on the server, admins can get emails of others, regular users can't
|
||||
# assert fetched_user.email == None
|
||||
|
||||
second_user_dict["id"] = fetched_user.id
|
||||
|
||||
def test_user_activity(self, client: SpeckleClient, second_user_dict):
|
||||
their_activity = client.other_user.activity(second_user_dict["id"])
|
||||
|
||||
assert isinstance(their_activity, ActivityCollection)
|
||||
assert isinstance(their_activity.items, list)
|
||||
assert isinstance(their_activity.items[0], Activity)
|
||||
assert their_activity.totalCount
|
||||
assert their_activity.totalCount > 0
|
||||
@@ -1,52 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from specklepy.api.client import SpeckleClient
|
||||
from specklepy.api.models import ServerInfo
|
||||
|
||||
|
||||
class TestServer:
|
||||
@pytest.fixture(scope="module")
|
||||
def token_info(self):
|
||||
return {
|
||||
"token": None,
|
||||
"name": "super secret token",
|
||||
"scopes": ["streams:read", "streams:write"],
|
||||
"lifespan": 9001,
|
||||
}
|
||||
|
||||
def test_server_get(self, client: SpeckleClient):
|
||||
server = client.server.get()
|
||||
|
||||
assert isinstance(server, ServerInfo)
|
||||
assert isinstance(server.frontend2, bool)
|
||||
|
||||
def test_server_version(self, client: SpeckleClient):
|
||||
version = client.server.version()
|
||||
|
||||
assert isinstance(version, tuple)
|
||||
if len(version) == 1:
|
||||
assert version[0] == "dev"
|
||||
else:
|
||||
assert isinstance(version[0], int)
|
||||
assert len(version) >= 3
|
||||
|
||||
def test_server_apps(self, client: SpeckleClient):
|
||||
apps = client.server.apps()
|
||||
|
||||
assert isinstance(apps, list)
|
||||
assert len(apps) >= 1
|
||||
assert any(app["name"] == "Speckle Web Manager" for app in apps)
|
||||
|
||||
def test_server_create_token(self, client, token_info):
|
||||
token_info["token"] = client.server.create_token(
|
||||
name=token_info["name"],
|
||||
scopes=token_info["scopes"],
|
||||
lifespan=token_info["lifespan"],
|
||||
)
|
||||
|
||||
assert isinstance(token_info["token"], str)
|
||||
|
||||
def test_server_revoke_token(self, client, token_info):
|
||||
revoked = client.server.revoke_token(token=token_info["token"])
|
||||
|
||||
assert revoked is True
|
||||
@@ -1,224 +0,0 @@
|
||||
import pytest
|
||||
|
||||
from specklepy.api.client import SpeckleClient
|
||||
from specklepy.api.models import (
|
||||
Activity,
|
||||
ActivityCollection,
|
||||
PendingStreamCollaborator,
|
||||
Stream,
|
||||
User,
|
||||
)
|
||||
from specklepy.logging.exceptions import GraphQLException, SpeckleException
|
||||
|
||||
|
||||
@pytest.mark.run(order=3)
|
||||
class TestStream:
|
||||
@pytest.fixture(scope="session")
|
||||
def stream(self):
|
||||
return Stream(
|
||||
name="a wonderful stream",
|
||||
description="a stream created for testing",
|
||||
isPublic=True,
|
||||
)
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def updated_stream(
|
||||
self,
|
||||
):
|
||||
return Stream(
|
||||
name="a wonderful updated stream",
|
||||
description="an updated stream description for testing",
|
||||
isPublic=False,
|
||||
)
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def second_user(self, second_client: SpeckleClient):
|
||||
return second_client.active_user.get()
|
||||
|
||||
def test_stream_create(self, client, stream, updated_stream):
|
||||
stream.id = updated_stream.id = client.stream.create(
|
||||
name=stream.name,
|
||||
description=stream.description,
|
||||
is_public=stream.isPublic,
|
||||
)
|
||||
|
||||
assert isinstance(stream.id, str)
|
||||
|
||||
def test_stream_create_short_name(self, client, stream, updated_stream):
|
||||
new_stream_id = client.stream.create(
|
||||
name="x",
|
||||
description=stream.description,
|
||||
is_public=stream.isPublic,
|
||||
)
|
||||
assert isinstance(new_stream_id, SpeckleException)
|
||||
|
||||
def test_stream_get(self, client, stream):
|
||||
fetched_stream = client.stream.get(stream.id)
|
||||
|
||||
assert fetched_stream.name == stream.name
|
||||
assert fetched_stream.description == stream.description
|
||||
assert fetched_stream.isPublic == stream.isPublic
|
||||
|
||||
def test_stream_update(self, client, updated_stream):
|
||||
updated = client.stream.update(
|
||||
id=updated_stream.id,
|
||||
name=updated_stream.name,
|
||||
description=updated_stream.description,
|
||||
is_public=updated_stream.isPublic,
|
||||
)
|
||||
fetched_stream = client.stream.get(updated_stream.id)
|
||||
|
||||
assert updated is True
|
||||
assert fetched_stream.name == updated_stream.name
|
||||
assert fetched_stream.description == updated_stream.description
|
||||
assert fetched_stream.isPublic == updated_stream.isPublic
|
||||
|
||||
def test_stream_list(self, client):
|
||||
client.stream.create(name="a second wonderful stream")
|
||||
client.stream.create(name="a third fantastic stream")
|
||||
|
||||
streams = client.stream.list()
|
||||
|
||||
assert len(streams) >= 3
|
||||
|
||||
def test_stream_search(self, client, updated_stream):
|
||||
search_results = client.stream.search(updated_stream.name)
|
||||
|
||||
assert len(search_results) == 1
|
||||
assert search_results[0].name == updated_stream.name
|
||||
|
||||
def test_stream_favorite(self, client, stream):
|
||||
favorited = client.stream.favorite(stream.id)
|
||||
|
||||
assert isinstance(favorited, Stream)
|
||||
assert favorited.favoritedDate is not None
|
||||
|
||||
unfavorited = client.stream.favorite(stream.id, False)
|
||||
assert isinstance(favorited, Stream)
|
||||
assert unfavorited.favoritedDate is None
|
||||
|
||||
def test_stream_invite(
|
||||
self, client: SpeckleClient, stream: Stream, second_user_dict: dict
|
||||
):
|
||||
invited = client.stream.invite(
|
||||
stream_id=stream.id,
|
||||
email=second_user_dict["email"],
|
||||
role="stream:reviewer",
|
||||
message="welcome to my stream!",
|
||||
)
|
||||
|
||||
assert invited is True
|
||||
|
||||
# fail if no email or id
|
||||
with pytest.raises(SpeckleException):
|
||||
client.stream.invite(stream_id=stream.id)
|
||||
|
||||
def test_stream_invite_get_all_for_user(
|
||||
self, second_client: SpeckleClient, stream: Stream
|
||||
):
|
||||
# NOTE: these are user queries, but testing here to contain the flow
|
||||
invites = second_client.active_user.get_all_pending_invites()
|
||||
|
||||
assert isinstance(invites, list)
|
||||
assert isinstance(invites[0], PendingStreamCollaborator)
|
||||
assert len(invites) == 1
|
||||
|
||||
invite = second_client.active_user.get_pending_invite(stream_id=stream.id)
|
||||
assert isinstance(invite, PendingStreamCollaborator)
|
||||
|
||||
def test_stream_invite_use(self, second_client: SpeckleClient, stream: Stream):
|
||||
invite: PendingStreamCollaborator = (
|
||||
second_client.active_user.get_all_pending_invites()[0]
|
||||
)
|
||||
|
||||
accepted = second_client.stream.invite_use(
|
||||
stream_id=stream.id, token=invite.token
|
||||
)
|
||||
|
||||
assert accepted is True
|
||||
|
||||
def test_stream_update_permission(
|
||||
self, client: SpeckleClient, stream: Stream, second_user: User
|
||||
):
|
||||
updated = client.stream.update_permission(
|
||||
stream_id=stream.id, user_id=second_user.id, role="stream:contributor"
|
||||
)
|
||||
|
||||
assert updated is True
|
||||
|
||||
def test_stream_revoke_permission(self, client, stream, second_user):
|
||||
revoked = client.stream.revoke_permission(
|
||||
stream_id=stream.id, user_id=second_user.id
|
||||
)
|
||||
|
||||
fetched_stream = client.stream.get(stream.id)
|
||||
|
||||
assert revoked is True
|
||||
assert len(fetched_stream.collaborators) == 1
|
||||
|
||||
def test_stream_invite_cancel(
|
||||
self,
|
||||
client: SpeckleClient,
|
||||
stream: Stream,
|
||||
second_user: User,
|
||||
):
|
||||
invited = client.stream.invite(
|
||||
stream_id=stream.id,
|
||||
user_id=second_user.id,
|
||||
message="welcome to my stream!",
|
||||
)
|
||||
assert invited is True
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
assert cancelled is True
|
||||
|
||||
def test_stream_invite_batch(
|
||||
self, client: SpeckleClient, stream: Stream, second_user: User
|
||||
):
|
||||
# NOTE: only works for server admins
|
||||
# invited = client.stream.invite_batch(
|
||||
# stream_id=stream.id,
|
||||
# emails=["userA@example.org", "userB@example.org"],
|
||||
# user_ids=[second_user.id],
|
||||
# message="yeehaw 🤠",
|
||||
# )
|
||||
|
||||
# assert invited is True
|
||||
|
||||
# invited_only_email = client.stream.invite_batch(
|
||||
# stream_id=stream.id,
|
||||
# emails=["userC@example.org"],
|
||||
# message="yeehaw 🤠",
|
||||
# )
|
||||
|
||||
# assert invited_only_email is True
|
||||
|
||||
# fail if no emails or user ids
|
||||
with pytest.raises(SpeckleException):
|
||||
client.stream.invite_batch(stream_id=stream.id)
|
||||
|
||||
def test_stream_activity(self, client: SpeckleClient, stream: Stream):
|
||||
activity = client.stream.activity(stream.id)
|
||||
|
||||
older_activity = client.stream.activity(
|
||||
stream.id, before=activity.items[0].time
|
||||
)
|
||||
|
||||
assert isinstance(activity, ActivityCollection)
|
||||
assert isinstance(older_activity, ActivityCollection)
|
||||
assert older_activity.totalCount < activity.totalCount
|
||||
assert activity.items is not None
|
||||
assert isinstance(activity.items[0], Activity)
|
||||
|
||||
def test_stream_delete(self, client, stream):
|
||||
deleted = client.stream.delete(stream.id)
|
||||
|
||||
stream_get = client.stream.get(stream.id)
|
||||
|
||||
assert deleted is True
|
||||
assert isinstance(stream_get, GraphQLException)
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user