Compare commits

..

1 Commits

Author SHA1 Message Date
Gergő Jedlicska 13e0b65c31 make test fail louder 2023-09-07 14:43:49 +02:00
133 changed files with 3232 additions and 7308 deletions
+3 -41
View File
@@ -1,47 +1,14 @@
version: 2.1
orbs:
codecov: codecov/codecov@3.3.0
python: circleci/python@2.0.3
codecov: codecov/codecov@3.2.2
jobs:
pre-commit:
parameters:
config_file:
default: ./.pre-commit-config.yaml
description: Optional, path to pre-commit config file.
type: string
cache_prefix:
default: ''
description: |
Optional cache prefix to be used on CircleCI. Can be used for cache busting or to ensure multiple jobs use different caches.
type: string
docker:
- image: speckle/pre-commit-runner:latest
resource_class: medium
steps:
- checkout
- restore_cache:
keys:
- cache-pre-commit-<<parameters.cache_prefix>>-{{ checksum "<<parameters.config_file>>" }}
- run:
name: Install pre-commit hooks
command: pre-commit install-hooks --config <<parameters.config_file>>
- save_cache:
key: cache-pre-commit-<<parameters.cache_prefix>>-{{ checksum "<<parameters.config_file>>" }}
paths:
- ~/.cache/pre-commit
- run:
name: Run pre-commit
command: pre-commit run --all-files
- run:
command: git --no-pager diff
name: git diff
when: on_fail
test:
machine:
image: ubuntu-2204:2023.02.1
docker_layer_caching: false
docker_layer_caching: true
resource_class: medium
parameters:
tag:
@@ -85,10 +52,6 @@ jobs:
workflows:
main:
jobs:
- pre-commit:
filters:
tags:
only: /.*/
- test:
matrix:
parameters:
@@ -99,7 +62,6 @@ workflows:
- deploy:
context: pypi
requires:
- pre-commit
- test
filters:
tags:
+1 -1
View File
@@ -22,6 +22,6 @@ RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/
USER vscode
RUN curl -sSL https://install.python-poetry.org | python3 -
RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
ENV PATH=$PATH:$HOME/.poetry/env
+9 -9
View File
@@ -2,23 +2,23 @@ repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
hooks:
- id: ruff
rev: v0.1.6
rev: v0.0.186
- repo: https://github.com/commitizen-tools/commitizen
hooks:
- id: commitizen
- id: commitizen-branch
stages:
- push
rev: v3.13.0
- id: commitizen
- id: commitizen-branch
stages:
- push
rev: v2.38.0
- repo: https://github.com/pycqa/isort
rev: 5.12.0
rev: v5.11.3
hooks:
- id: isort
- repo: https://github.com/psf/black
rev: 23.11.0
rev: 22.12.0
hooks:
- id: black
# It is recommended to specify the latest version of Python
@@ -27,7 +27,7 @@ repos:
# https://pre-commit.com/#top_level-default_language_version
# language_version: python3.11
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v4.4.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
+36 -6
View File
@@ -2,16 +2,46 @@
<img src="https://user-images.githubusercontent.com/2679513/131189167-18ea5fe1-c578-47f6-9785-3748178e4312.png" width="150px"/><br/>
Speckle | specklepy 🐍
</h1>
<p align="center"><a href="https://twitter.com/SpeckleSystems"><img src="https://img.shields.io/twitter/follow/SpeckleSystems?style=social" alt="Twitter Follow"></a> <a href="https://speckle.community"><img src="https://img.shields.io/discourse/users?server=https%3A%2F%2Fspeckle.community&amp;style=flat-square&amp;logo=discourse&amp;logoColor=white" alt="Community forum users"></a> <a href="https://speckle.systems"><img src="https://img.shields.io/badge/https://-speckle.systems-royalblue?style=flat-square" alt="website"></a> <a href="https://speckle.guide/dev/"><img src="https://img.shields.io/badge/docs-speckle.guide-orange?style=flat-square&amp;logo=read-the-docs&amp;logoColor=white" alt="docs"></a></p>
> Speckle is the first AEC data hub that connects with your favorite AEC tools. Speckle exists to overcome the challenges of working in a fragmented industry where communication, creative workflows, and the exchange of data are often hindered by siloed software and processes. It is here to make the industry better.
<h3 align="center">
The Python SDK
</h3>
<p align="center"><b>Speckle</b> is the data infrastructure for the AEC industry.</p><br/>
<p align="center"><a href="https://twitter.com/SpeckleSystems"><img src="https://img.shields.io/twitter/follow/SpeckleSystems?style=social" alt="Twitter Follow"></a> <a href="https://speckle.community"><img src="https://img.shields.io/discourse/users?server=https%3A%2F%2Fspeckle.community&amp;style=flat-square&amp;logo=discourse&amp;logoColor=white" alt="Community forum users"></a> <a href="https://speckle.systems"><img src="https://img.shields.io/badge/https://-speckle.systems-royalblue?style=flat-square" alt="website"></a> <a href="https://speckle.guide/dev/"><img src="https://img.shields.io/badge/docs-speckle.guide-orange?style=flat-square&amp;logo=read-the-docs&amp;logoColor=white" alt="docs"></a></p>
<p align="center"><a href="https://github.com/specklesystems/specklepy/"><img src="https://circleci.com/gh/specklesystems/specklepy.svg?style=svg&amp;circle-token=76eabd350ea243575cbb258b746ed3f471f7ac29" alt="Speckle-Next"></a><a href="https://codecov.io/gh/specklesystems/specklepy">
<img src="https://codecov.io/gh/specklesystems/specklepy/branch/main/graph/badge.svg?token=8KQFL5N0YF"/>
</a> </p>
# About Speckle
What is Speckle? Check our ![YouTube Video Views](https://img.shields.io/youtube/views/B9humiSpHzM?label=Speckle%20in%201%20minute%20video&style=social)
### Features
- **Object-based:** say goodbye to files! Speckle is the first object based platform for the AEC industry
- **Version control:** Speckle is the Git & Hub for geometry and BIM data
- **Collaboration:** share your designs collaborate with others
- **3D Viewer:** see your CAD and BIM models online, share and embed them anywhere
- **Interoperability:** get your CAD and BIM models into other software without exporting or importing
- **Real time:** get real time updates and notifications and changes
- **GraphQL API:** get what you need anywhere you want it
- **Webhooks:** the base for a automation and next-gen pipelines
- **Built for developers:** we are building Speckle with developers in mind and got tools for every stack
- **Built for the AEC industry:** Speckle connectors are plugins for the most common software used in the industry such as Revit, Rhino, Grasshopper, AutoCAD, Civil 3D, Excel, Unreal Engine, Unity, QGIS, Blender and more!
### Try Speckle now!
Give Speckle a try in no time by:
- [![speckle XYZ](https://img.shields.io/badge/https://-speckle.xyz-0069ff?style=flat-square&logo=hackthebox&logoColor=white)](https://speckle.xyz) ⇒ creating an account at our public server
- [![create a droplet](https://img.shields.io/badge/Create%20a%20Droplet-0069ff?style=flat-square&logo=digitalocean&logoColor=white)](https://marketplace.digitalocean.com/apps/speckle-server?refcode=947a2b5d7dc1) ⇒ deploying an instance in 1 click
### Resources
- [![Community forum users](https://img.shields.io/badge/community-forum-green?style=for-the-badge&logo=discourse&logoColor=white)](https://speckle.community) for help, feature requests or just to hang with other speckle enthusiasts, check out our community forum!
- [![website](https://img.shields.io/badge/tutorials-speckle.systems-royalblue?style=for-the-badge&logo=youtube)](https://speckle.systems) our tutorials portal is full of resources to get you started using Speckle
- [![docs](https://img.shields.io/badge/docs-speckle.guide-orange?style=for-the-badge&logo=read-the-docs&logoColor=white)](https://speckle.guide/dev/) reference on almost any end-user and developer functionality
<p align="center"><a href="https://codecov.io/gh/specklesystems/specklepy"><img src="https://codecov.io/gh/specklesystems/specklepy/branch/main/graph/badge.svg?token=8KQFL5N0YF" alt="Codecov"></a></p>
# Repo structure
+54 -12
View File
@@ -33,7 +33,7 @@ services:
retries: 30
minio:
image: "minio/minio:RELEASE.2023-10-25T06-33-25Z"
image: "minio/minio"
command: server /data --console-address ":9001"
restart: always
volumes:
@@ -52,26 +52,28 @@ services:
####
# Speckle Server
#######
speckle-frontend:
image: speckle/speckle-frontend-2:latest
image: speckle/speckle-frontend:2
restart: always
ports:
- "0.0.0.0:8080:8080"
environment:
FILE_SIZE_LIMIT_MB: 100
speckle-server:
image: speckle/speckle-server:latest
image: speckle/speckle-server:2
restart: always
healthcheck:
test:
- CMD
- /nodejs/bin/node
- -e
- "try { require('node:http').request({headers: {'Content-Type': 'application/json'}, port:3000, hostname:'127.0.0.1', path:'/readiness', method: 'GET', timeout: 2000 }, (res) => { body = ''; res.on('data', (chunk) => {body += chunk;}); res.on('end', () => {process.exit(res.statusCode != 200 || body.toLowerCase().includes('error'));}); }).end(); } catch { process.exit(1); }"
[
"CMD",
"node",
"-e",
"require('node:http').request({headers: {'Content-Type': 'application/json'}, port:3000, hostname:'127.0.0.1', path:'/graphql?query={serverInfo{version}}', method: 'GET' }, (res) => { body = ''; res.on('data', (chunk) => {body += chunk;}); res.on('end', () => {process.exit(res.statusCode != 200 || body.toLowerCase().includes('error'));}); }).end();",
]
interval: 10s
timeout: 10s
retries: 3
start_period: 90s
timeout: 3s
retries: 30
ports:
- "0.0.0.0:3000:3000"
depends_on:
@@ -96,7 +98,6 @@ services:
S3_CREATE_BUCKET: "true"
FILE_SIZE_LIMIT_MB: 100
MAX_PROJECT_MODELS_PER_PAGE: 500
# TODO: Change this to a unique secret for this server
SESSION_SECRET: "TODO:ReplaceWithLongString"
@@ -110,6 +111,47 @@ services:
POSTGRES_DB: "speckle"
ENABLE_MP: "false"
preview-service:
image: speckle/speckle-preview-service:2
restart: always
depends_on:
speckle-server:
condition: service_healthy
mem_limit: "1000m"
memswap_limit: "1000m"
environment:
DEBUG: "preview-service:*"
PG_CONNECTION_STRING: "postgres://speckle:speckle@postgres/speckle"
webhook-service:
image: speckle/speckle-webhook-service:2
restart: always
depends_on:
speckle-server:
condition: service_healthy
environment:
DEBUG: "webhook-service:*"
PG_CONNECTION_STRING: "postgres://speckle:speckle@postgres/speckle"
WAIT_HOSTS: postgres:5432
fileimport-service:
image: speckle/speckle-fileimport-service:2
restart: always
depends_on:
speckle-server:
condition: service_healthy
environment:
DEBUG: "fileimport-service:*"
PG_CONNECTION_STRING: "postgres://speckle:speckle@postgres/speckle"
WAIT_HOSTS: postgres:5432
S3_ENDPOINT: "http://minio:9000"
S3_ACCESS_KEY: "minioadmin"
S3_SECRET_KEY: "minioadmin"
S3_BUCKET: "speckle-server"
SPECKLE_SERVER_URL: "http://speckle-server:3000"
networks:
default:
name: speckle-server
Generated
+1012 -1218
View File
File diff suppressed because it is too large Load Diff
+6 -10
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "specklepy"
version = "2.17.14"
version = "2.9.1"
description = "The Python SDK for Speckle 2.0"
readme = "README.md"
authors = ["Speckle Systems <devops@speckle.systems>"]
@@ -10,35 +10,31 @@ documentation = "https://speckle.guide/dev/py-examples.html"
homepage = "https://speckle.systems/"
packages = [
{ include = "specklepy", from = "src" },
{ include = "speckle_automate", from = "src" },
]
[tool.poetry.dependencies]
python = ">=3.8.0, <4.0"
pydantic = "^2.5"
python = ">=3.7.2, <4.0"
pydantic = "^2.0"
appdirs = "^1.4.4"
gql = { extras = ["requests", "websockets"], version = "^3.3.0" }
gql = {extras = ["requests", "websockets"], version = "^3.3.0"}
ujson = "^5.3.0"
Deprecated = "^1.2.13"
stringcase = "^1.2.0"
attrs = "^23.1.0"
httpx = "^0.25.0"
[tool.poetry.group.dev.dependencies]
black = "23.11.0"
black = "^22.8.0"
isort = "^5.7.0"
pytest = "^7.1.3"
pytest-asyncio = "^0.23.0"
pytest-ordering = "^0.6"
pytest-cov = "^3.0.0"
devtools = "^0.8.0"
pylint = "^2.14.4"
pydantic-settings = "^2.3.0"
mypy = "^0.982"
pre-commit = "^2.20.0"
commitizen = "^2.38.0"
ruff = "^0.4.4"
ruff = "^0.0.187"
types-deprecated = "^1.2.9"
types-ujson = "^5.6.0.0"
types-requests = "^2.28.11.5"
-24
View File
@@ -1,24 +0,0 @@
"""This module contains an SDK for working with Speckle Automate."""
from speckle_automate.automation_context import AutomationContext
from speckle_automate.runner import execute_automate_function, run_function
from speckle_automate.schema import (
AutomateBase,
AutomationResult,
AutomationRunData,
AutomationStatus,
ObjectResultLevel,
ResultCase,
)
__all__ = [
"AutomationContext",
"AutomateBase",
"AutomationStatus",
"AutomationResult",
"AutomationRunData",
"ResultCase",
"ObjectResultLevel",
"run_function",
"execute_automate_function",
]
-427
View File
@@ -1,427 +0,0 @@
"""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
import httpx
from gql import gql
from speckle_automate.schema import (
AutomateBase,
AutomationResult,
AutomationRunData,
AutomationStatus,
ObjectResultLevel,
ResultCase,
)
from specklepy.api import operations
from specklepy.api.client import SpeckleClient
from specklepy.core.api.models import Branch
from specklepy.logging.exceptions import SpeckleException
from specklepy.objects.base import Base
from specklepy.transports.memory import MemoryTransport
from specklepy.transports.server import ServerTransport
@dataclass
class AutomationContext:
"""A context helper class.
This class exposes methods to work with the Speckle Automate context inside
Speckle Automate functions.
An instance of AutomationContext is injected into every run of a function.
"""
automation_run_data: AutomationRunData
speckle_client: SpeckleClient
_server_transport: ServerTransport
_speckle_token: str
#: keep a memory transponrt at hand, to speed up things if needed
_memory_transport: MemoryTransport = field(default_factory=MemoryTransport)
#: added for performance measuring
_init_time: float = field(default_factory=time.perf_counter)
_automation_result: AutomationResult = field(default_factory=AutomationResult)
@classmethod
def initialize(
cls, automation_run_data: Union[str, AutomationRunData], speckle_token: str
) -> "AutomationContext":
"""Bootstrap the AutomateSDK from raw data.
Todo:
----
* bootstrap a structlog logger instance
* expose a logger, that ppl can use instead of print
"""
# parse the json value if its not an initialized project data instance
automation_run_data = (
automation_run_data
if isinstance(automation_run_data, AutomationRunData)
else AutomationRunData.model_validate_json(automation_run_data)
)
speckle_client = SpeckleClient(
automation_run_data.speckle_server_url,
automation_run_data.speckle_server_url.startswith("https"),
)
speckle_client.authenticate_with_token(speckle_token)
if not speckle_client.account:
msg = (
f"Could not autenticate to {automation_run_data.speckle_server_url}",
"with the provided token",
)
raise ValueError(msg)
server_transport = ServerTransport(
automation_run_data.project_id, speckle_client
)
return cls(automation_run_data, speckle_client, server_transport, speckle_token)
@property
def run_status(self) -> AutomationStatus:
"""Get the status of the automation run."""
return self._automation_result.run_status
@property
def status_message(self) -> Optional[str]:
"""Get the current status message."""
return self._automation_result.status_message
def elapsed(self) -> float:
"""Return the elapsed time in seconds since the initialization time."""
return time.perf_counter() - self._init_time
def receive_version(self) -> Base:
"""Receive the Speckle project version that triggered this automation run."""
# 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.referencedObject:
raise ValueError("The commit has no referencedObject, cannot receive it.")
base = operations.receive(
commit.referencedObject, self._server_transport, self._memory_transport
)
print(
f"It took {self.elapsed():.2f} seconds to receive",
f" the speckle version {version_id}",
)
return base
def create_new_version_in_project(
self, root_object: Base, model_name: str, version_message: str = ""
) -> Tuple[str, str]:
"""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`!
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,
)
if isinstance(branch_create, Exception):
raise branch_create
model_id = branch_create
root_object_id = operations.send(
root_object,
[self._server_transport, self._memory_transport],
use_default_cache=False,
)
version_id = self.speckle_client.commit.create(
stream_id=self.automation_run_data.project_id,
object_id=root_object_id,
branch_name=model_name,
message=version_message,
source_application="SpeckleAutomate",
)
if isinstance(version_id, SpeckleException):
raise version_id
self._automation_result.result_versions.append(version_id)
return model_id, version_id
@property
def context_view(self) -> Optional[str]:
return self._automation_result.result_view
def set_context_view(
self,
# f"{model_id}@{version_id} or {model_id} "
resource_ids: Optional[List[str]] = None,
include_source_model_version: bool = True,
) -> None:
link_resources = (
[
f"{t.payload.model_id}@{t.payload.version_id}"
for t in self.automation_run_data.triggers
]
if include_source_model_version
else []
)
if resource_ids:
link_resources.extend(resource_ids)
if not link_resources:
raise Exception(
"We do not have enough resource ids to compose a context view"
)
self._automation_result.result_view = (
f"/projects/{self.automation_run_data.project_id}"
f"/models/{','.join(link_resources)}"
)
def report_run_status(self) -> None:
"""Report the current run status to the project of this automation."""
query = gql(
"""
mutation AutomateFunctionRunStatusReport(
$projectId: String!
$functionRunId: String!
$status: AutomateRunStatus!
$statusMessage: String
$results: JSONObject
$contextView: String
){
automateFunctionRunStatusReport(input: {
projectId: $projectId
functionRunId: $functionRunId
status: $status
statusMessage: $statusMessage
contextView: $contextView
results: $results
})
}
"""
)
if self.run_status in [AutomationStatus.SUCCEEDED, AutomationStatus.FAILED]:
object_results = {
"version": 1,
"values": {
"objectResults": self._automation_result.model_dump(by_alias=True)[
"objectResults"
],
"blobIds": self._automation_result.blobs,
},
}
else:
object_results = None
params = {
"projectId": self.automation_run_data.project_id,
"functionRunId": self.automation_run_data.function_run_id,
"status": self.run_status.value,
"statusMessage": self._automation_result.status_message,
"results": object_results,
"contextView": self._automation_result.result_view,
}
print(f"Reporting run status with content: {params}")
self.speckle_client.httpclient.execute(query, params)
def store_file_result(self, file_path: Union[Path, str]) -> None:
"""Save a file attached to the project of this automation."""
path_obj = (
Path(file_path).resolve() if isinstance(file_path, str) else file_path
)
class UploadResult(AutomateBase):
blob_id: str
file_name: str
upload_status: int
class BlobUploadResponse(AutomateBase):
upload_results: list[UploadResult]
if not path_obj.exists():
raise ValueError("The given file path doesn't exist")
files = {path_obj.name: open(str(path_obj), "rb")}
url = (
f"{self.automation_run_data.speckle_server_url}api/stream/"
f"{self.automation_run_data.project_id}/blob"
)
data = (
httpx.post(
url,
files=files,
headers={"authorization": f"Bearer {self._speckle_token}"},
)
.raise_for_status()
.json()
)
upload_response = BlobUploadResponse.model_validate(data)
if len(upload_response.upload_results) != 1:
raise ValueError("Expecting one upload result.")
self._automation_result.blobs.extend(
[upload_result.blob_id for upload_result in upload_response.upload_results]
)
def mark_run_failed(self, status_message: str) -> None:
"""Mark the current run a failure."""
self._mark_run(AutomationStatus.FAILED, status_message)
def mark_run_exception(self, status_message: str) -> None:
"""Mark the current run a failure."""
self._mark_run(AutomationStatus.EXCEPTION, status_message)
def mark_run_success(self, status_message: Optional[str]) -> None:
"""Mark the current run a success with an optional message."""
self._mark_run(AutomationStatus.SUCCEEDED, status_message)
def _mark_run(
self, status: AutomationStatus, status_message: Optional[str]
) -> None:
duration = self.elapsed()
self._automation_result.status_message = status_message
self._automation_result.run_status = status
self._automation_result.elapsed = duration
msg = f"Automation run {status.value} after {duration:.2f} seconds."
print("\n".join([msg, status_message]) if status_message else msg)
def attach_error_to_objects(
self,
category: str,
object_ids: Union[str, List[str]],
message: Optional[str] = None,
metadata: Optional[Dict[str, Any]] = None,
visual_overrides: Optional[Dict[str, Any]] = None,
) -> None:
"""Add a new error case to the run results.
If the error cause has already created an error case,
the error will be extended with a new case refering to the causing objects.
Args:
error_tag (str): A short tag for the error type.
causing_object_ids (str[]): A list of object_id-s that are causing the error
error_messagge (Optional[str]): Optional error message.
metadata: User provided metadata key value pairs
visual_overrides: Case specific 3D visual overrides.
"""
self.attach_result_to_objects(
ObjectResultLevel.ERROR,
category,
object_ids,
message,
metadata,
visual_overrides,
)
def attach_warning_to_objects(
self,
category: str,
object_ids: Union[str, List[str]],
message: Optional[str] = None,
metadata: Optional[Dict[str, Any]] = None,
visual_overrides: Optional[Dict[str, Any]] = None,
) -> None:
"""Add a new warning case to the run results."""
self.attach_result_to_objects(
ObjectResultLevel.WARNING,
category,
object_ids,
message,
metadata,
visual_overrides,
)
def attach_success_to_objects(
self,
category: str,
object_ids: Union[str, List[str]],
message: Optional[str] = None,
metadata: Optional[Dict[str, Any]] = None,
visual_overrides: Optional[Dict[str, Any]] = None,
) -> None:
"""Add a new success case to the run results."""
self.attach_result_to_objects(
ObjectResultLevel.SUCCESS,
category,
object_ids,
message,
metadata,
visual_overrides,
)
def attach_info_to_objects(
self,
category: str,
object_ids: Union[str, List[str]],
message: Optional[str] = None,
metadata: Optional[Dict[str, Any]] = None,
visual_overrides: Optional[Dict[str, Any]] = None,
) -> None:
"""Add a new info case to the run results."""
self.attach_result_to_objects(
ObjectResultLevel.INFO,
category,
object_ids,
message,
metadata,
visual_overrides,
)
def attach_result_to_objects(
self,
level: ObjectResultLevel,
category: str,
object_ids: Union[str, List[str]],
message: Optional[str] = None,
metadata: Optional[Dict[str, Any]] = None,
visual_overrides: Optional[Dict[str, Any]] = None,
) -> None:
if isinstance(object_ids, list):
if len(object_ids) < 1:
raise ValueError(
f"Need atleast one object_id to report a(n) {level.value.upper()}"
)
id_list = object_ids
else:
id_list = [object_ids]
print(
f"Created new {level.value.upper()}"
f" category: {category} caused by: {message}"
)
self._automation_result.object_results.append(
ResultCase(
category=category,
level=level,
object_ids=id_list,
message=message,
metadata=metadata,
visual_overrides=visual_overrides,
)
)
-154
View File
@@ -1,154 +0,0 @@
"""Some useful helpers for working with automation data."""
import secrets
import string
import pytest
from gql import gql
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
from speckle_automate.schema import AutomationRunData, TestAutomationRunData
from specklepy.api.client import SpeckleClient
class TestAutomationEnvironment(BaseSettings):
"""Get known environment variables from local `.env` file"""
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
env_prefix="speckle_",
extra="ignore",
)
token: str = Field()
server_url: str = Field()
project_id: str = Field()
automation_id: str = Field()
@pytest.fixture()
def test_automation_environment() -> TestAutomationEnvironment:
return TestAutomationEnvironment()
@pytest.fixture()
def test_automation_token(
test_automation_environment: TestAutomationEnvironment,
) -> str:
"""Provide a speckle token for the test suite."""
return test_automation_environment.token
@pytest.fixture()
def speckle_client(
test_automation_environment: TestAutomationEnvironment,
) -> SpeckleClient:
"""Initialize a SpeckleClient for testing."""
speckle_client = SpeckleClient(
test_automation_environment.server_url,
test_automation_environment.server_url.startswith("https"),
)
speckle_client.authenticate_with_token(test_automation_environment.token)
return speckle_client
def create_test_automation_run(
speckle_client: SpeckleClient, project_id: str, test_automation_id: str
) -> TestAutomationRunData:
"""Create test run to report local test results to"""
query = gql(
"""
mutation CreateTestRun(
$projectId: ID!,
$automationId: ID!
) {
projectMutations {
automationMutations(projectId: $projectId) {
createTestAutomationRun(automationId: $automationId) {
automationRunId
functionRunId
triggers {
payload {
modelId
versionId
}
triggerType
}
}
}
}
}
"""
)
params = {"automationId": test_automation_id, "projectId": project_id}
result = speckle_client.httpclient.execute(query, params)
print(result)
return (
result.get("projectMutations")
.get("automationMutations")
.get("createTestAutomationRun")
)
@pytest.fixture()
def test_automation_run(
speckle_client: SpeckleClient,
test_automation_environment: TestAutomationEnvironment,
) -> TestAutomationRunData:
return create_test_automation_run(
speckle_client,
test_automation_environment.project_id,
test_automation_environment.automation_id,
)
def create_test_automation_run_data(
speckle_client: SpeckleClient,
test_automation_environment: TestAutomationEnvironment,
) -> AutomationRunData:
"""Create automation run data for a new run for a given test automation"""
test_automation_run_data = create_test_automation_run(
speckle_client,
test_automation_environment.project_id,
test_automation_environment.automation_id,
)
return AutomationRunData(
project_id=test_automation_environment.project_id,
speckle_server_url=test_automation_environment.server_url,
automation_id=test_automation_environment.automation_id,
automation_run_id=test_automation_run_data["automationRunId"],
function_run_id=test_automation_run_data["functionRunId"],
triggers=test_automation_run_data["triggers"],
)
@pytest.fixture()
def test_automation_run_data(
speckle_client: SpeckleClient,
test_automation_environment: TestAutomationEnvironment,
) -> AutomationRunData:
return create_test_automation_run_data(speckle_client, test_automation_environment)
def crypto_random_string(length: int) -> str:
"""Generate a semi crypto random string of a given length."""
alphabet = string.ascii_letters + string.digits
return "".join(secrets.choice(alphabet) for _ in range(length)).lower()
__all__ = [
"test_automation_environment",
"test_automation_token",
"speckle_client",
"test_automation_run",
"test_automation_run_data",
]
-197
View File
@@ -1,197 +0,0 @@
"""Function execution module.
Provides mechanisms to execute any function,
that conforms to the AutomateFunction "interface"
"""
import json
import sys
import traceback
from pathlib import Path
from typing import Callable, Optional, Tuple, TypeVar, Union, overload
from pydantic import create_model
from pydantic.json_schema import GenerateJsonSchema
from speckle_automate.automation_context import AutomationContext
from speckle_automate.schema import AutomateBase, AutomationRunData, AutomationStatus
T = TypeVar("T", bound=AutomateBase)
AutomateFunction = Callable[[AutomationContext, T], None]
AutomateFunctionWithoutInputs = Callable[[AutomationContext], None]
def _read_input_data(inputs_location: str) -> str:
input_path = Path(inputs_location)
if not input_path.exists():
raise ValueError(f"Cannot find the function inputs file at {input_path}")
return input_path.read_text()
def _parse_input_data(
input_location: str, input_schema: Optional[type[T]]
) -> Tuple[AutomationRunData, Optional[T], str]:
input_json_string = _read_input_data(input_location)
class FunctionRunData(AutomateBase):
speckle_token: str
automation_run_data: AutomationRunData
function_inputs: None = None
parser_model = FunctionRunData
if input_schema:
parser_model = create_model(
"FunctionRunDataWithInputs",
function_inputs=(input_schema, ...),
__base__=FunctionRunData,
)
input_data = parser_model.model_validate_json(input_json_string)
return (
input_data.automation_run_data,
input_data.function_inputs,
input_data.speckle_token,
)
@overload
def execute_automate_function(
automate_function: AutomateFunction[T],
input_schema: type[T],
) -> None:
...
@overload
def execute_automate_function(
automate_function: AutomateFunctionWithoutInputs,
) -> None:
...
class AutomateGenerateJsonSchema(GenerateJsonSchema):
def generate(self, schema, mode="validation"):
json_schema = super().generate(schema, mode=mode)
json_schema["$schema"] = self.schema_dialect
return json_schema
def execute_automate_function(
automate_function: Union[AutomateFunction[T], AutomateFunctionWithoutInputs],
input_schema: Optional[type[T]] = None,
):
"""Runs the provided automate function with the input schema."""
# first arg is the python file name, we do not need that
args = sys.argv[1:]
if len(args) != 2:
raise ValueError("Incorrect number of arguments specified need 2")
# we rely on a command name convention to decide what to do.
# this is here, so that the function authors do not see any of this
command, argument = args
if command == "generate_schema":
path = Path(argument)
schema = json.dumps(
input_schema.model_json_schema(
by_alias=True, schema_generator=AutomateGenerateJsonSchema
)
if input_schema
else {}
)
path.write_text(schema)
elif command == "run":
automation_run_data, function_inputs, speckle_token = _parse_input_data(
argument, input_schema
)
automation_context = AutomationContext.initialize(
automation_run_data, speckle_token
)
if function_inputs:
automation_context = run_function(
automation_context,
automate_function, # type: ignore
function_inputs, # type: ignore
)
else:
automation_context = AutomationContext.initialize(
automation_run_data, speckle_token
)
automation_context = run_function(
automation_context,
automate_function, # type: ignore
)
# if we've gotten this far, the execution should technically be completed as expected
# thus exiting with 0 is the schemantically correct thing to do
exit_code = (
1 if automation_context.run_status == AutomationStatus.EXCEPTION else 0
)
exit(exit_code)
else:
raise NotImplementedError(f"Command: '{command}' is not supported.")
@overload
def run_function(
automation_context: AutomationContext,
automate_function: AutomateFunction[T],
inputs: T,
) -> AutomationContext:
...
@overload
def run_function(
automation_context: AutomationContext,
automate_function: AutomateFunctionWithoutInputs,
) -> AutomationContext:
...
def run_function(
automation_context: AutomationContext,
automate_function: Union[AutomateFunction[T], AutomateFunctionWithoutInputs],
inputs: Optional[T] = None,
) -> AutomationContext:
"""Run the provided function with the automate sdk context."""
automation_context.report_run_status()
try:
# avoiding complex type gymnastics here on the internals.
# the external type overloads make this correct
if inputs:
automate_function(automation_context, inputs) # type: ignore
else:
automate_function(automation_context) # type: ignore
# the function author forgot to mark the function success
if automation_context.run_status not in [
AutomationStatus.FAILED,
AutomationStatus.SUCCEEDED,
AutomationStatus.EXCEPTION,
]:
automation_context.mark_run_success(
"WARNING: Automate assumed a success status,"
" but it was not marked as so by the function."
)
except Exception:
trace = traceback.format_exc()
print(trace)
automation_context.mark_run_exception(
"Function error. Check the automation run logs for details."
)
finally:
if not automation_context.context_view:
automation_context.set_context_view()
automation_context.report_run_status()
return automation_context
-98
View File
@@ -1,98 +0,0 @@
""""""
from enum import Enum
from typing import Any, Dict, List, Literal, Optional
from pydantic import BaseModel, ConfigDict, Field
from stringcase import camelcase
class AutomateBase(BaseModel):
"""Use this class as a base model for automate related DTO."""
model_config = ConfigDict(alias_generator=camelcase, populate_by_name=True)
class VersionCreationTriggerPayload(AutomateBase):
"""Represents the version creation trigger payload."""
model_id: str
version_id: str
class VersionCreationTrigger(AutomateBase):
"""Represents a single version creation trigger for the automation run."""
trigger_type: Literal["versionCreation"]
payload: VersionCreationTriggerPayload
class AutomationRunData(BaseModel):
"""Values of the project / model that triggered the run of this function."""
project_id: str
speckle_server_url: str
automation_id: str
automation_run_id: str
function_run_id: str
triggers: List[VersionCreationTrigger]
model_config = ConfigDict(
alias_generator=camelcase, populate_by_name=True, protected_namespaces=()
)
class TestAutomationRunData(BaseModel):
"""Values of the run created in the test automation for local test results."""
automation_run_id: str
function_run_id: str
triggers: List[VersionCreationTrigger]
model_config = ConfigDict(
alias_generator=camelcase, populate_by_name=True, protected_namespaces=()
)
class AutomationStatus(str, Enum):
"""Set the status of the automation."""
INITIALIZING = "INITIALIZING"
RUNNING = "RUNNING"
FAILED = "FAILED"
SUCCEEDED = "SUCCEEDED"
EXCEPTION = "EXCEPTION"
class ObjectResultLevel(str, Enum):
"""Possible status message levels for object reports."""
SUCCESS = "SUCCESS"
INFO = "INFO"
WARNING = "WARNING"
ERROR = "ERROR"
class ResultCase(AutomateBase):
"""A result case."""
category: str
level: ObjectResultLevel
object_ids: List[str]
message: Optional[str]
metadata: Optional[Dict[str, Any]]
visual_overrides: Optional[Dict[str, Any]]
class AutomationResult(AutomateBase):
"""Schema accepted by the Speckle server as a result for an automation run."""
elapsed: float = 0
result_view: Optional[str] = None
result_versions: List[str] = Field(default_factory=list)
blobs: List[str] = Field(default_factory=list)
run_status: AutomationStatus = AutomationStatus.RUNNING
status_message: Optional[str] = None
object_results: list[ResultCase] = Field(default_factory=list)
-3
View File
@@ -1,3 +0,0 @@
from specklepy import objects
__all__ = ["objects"]
+36 -75
View File
@@ -1,24 +1,30 @@
from deprecated import deprecated
import re
from typing import Dict
from warnings import warn
from specklepy.api.credentials import Account
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.api import resources
from specklepy.api.credentials import Account, get_account_from_token
from specklepy.api.resources import (
ActiveUserResource,
ModelResource,
OtherUserResource,
ProjectInviteResource,
ProjectResource,
SubscriptionResource,
VersionResource,
user,
active_user,
branch,
commit,
object,
other_user,
server,
stream,
subscriptions,
user,
)
from specklepy.core.api.client import SpeckleClient as CoreSpeckleClient
from specklepy.logging import metrics
from specklepy.logging.exceptions import SpeckleException, SpeckleWarning
from specklepy.core.api.client import SpeckleClient as CoreSpeckleClient
class SpeckleClient(CoreSpeckleClient):
@@ -26,7 +32,7 @@ class SpeckleClient(CoreSpeckleClient):
The `SpeckleClient` is your entry point for interacting with
your Speckle Server's GraphQL API.
You'll need to have access to a server to use it,
or you can use our public server `app.speckle.systems`.
or you can use our public server `speckle.xyz`.
To authenticate the client, you'll need to have downloaded
the [Speckle Manager](https://speckle.guide/#speckle-manager)
@@ -37,7 +43,7 @@ class SpeckleClient(CoreSpeckleClient):
from specklepy.api.credentials import get_default_account
# initialise the client
client = SpeckleClient(host="app.speckle.systems") # or whatever your host is
client = SpeckleClient(host="speckle.xyz") # or whatever your host is
# client = SpeckleClient(host="localhost:3000", use_ssl=False) or use local server
# authenticate the client with an account (account has been added in Speckle Manager)
@@ -52,19 +58,13 @@ class SpeckleClient(CoreSpeckleClient):
```
"""
DEFAULT_HOST = "app.speckle.systems"
DEFAULT_HOST = "speckle.xyz"
USE_SSL = True
def __init__(
self,
host: str = DEFAULT_HOST,
use_ssl: bool = USE_SSL,
verify_certificate: bool = True,
) -> None:
def __init__(self, host: str = DEFAULT_HOST, use_ssl: bool = USE_SSL) -> None:
super().__init__(
host=host,
use_ssl=use_ssl,
verify_certificate=verify_certificate,
)
self.account = Account()
@@ -72,62 +72,29 @@ class SpeckleClient(CoreSpeckleClient):
self.server = server.Resource(
account=self.account, basepath=self.url, client=self.httpclient
)
server_version = None
try:
server_version = self.server.version()
except Exception:
pass
self.other_user = OtherUserResource(
account=self.account,
basepath=self.url,
client=self.httpclient,
server_version=server_version,
)
self.active_user = ActiveUserResource(
account=self.account,
basepath=self.url,
client=self.httpclient,
server_version=server_version,
)
self.project = ProjectResource(
account=self.account,
basepath=self.url,
client=self.httpclient,
server_version=server_version,
)
self.project_invite = ProjectInviteResource(
account=self.account,
basepath=self.url,
client=self.httpclient,
server_version=server_version,
)
self.model = ModelResource(
account=self.account,
basepath=self.url,
client=self.httpclient,
server_version=server_version,
)
self.version = VersionResource(
account=self.account,
basepath=self.url,
client=self.httpclient,
server_version=server_version,
)
self.subscription = SubscriptionResource(
account=self.account,
basepath=self.ws_url,
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.other_user = other_user.Resource(
account=self.account,
basepath=self.url,
client=self.httpclient,
server_version=server_version,
)
self.active_user = active_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,
@@ -164,9 +131,7 @@ class SpeckleClient(CoreSpeckleClient):
Arguments:
token {str} -- an api token
"""
metrics.track(
metrics.SDK, self.account, {"name": "Client Authenticate_deprecated"}
)
metrics.track(metrics.SDK, self.account, {"name": "Client Authenticate_deprecated"})
return super().authenticate(token)
def authenticate_with_token(self, token: str) -> None:
@@ -178,9 +143,7 @@ class SpeckleClient(CoreSpeckleClient):
Arguments:
token {str} -- an api token
"""
metrics.track(
metrics.SDK, self.account, {"name": "Client Authenticate With Token"}
)
metrics.track(metrics.SDK, self.account, {"name": "Client Authenticate With Token"})
return super().authenticate_with_token(token)
def authenticate_with_account(self, account: Account) -> None:
@@ -192,7 +155,5 @@ class SpeckleClient(CoreSpeckleClient):
account {Account} -- the account object which can be found with
`get_default_account` or `get_local_accounts`
"""
metrics.track(
metrics.SDK, self.account, {"name": "Client Authenticate With Account"}
)
metrics.track(metrics.SDK, self.account, {"name": "Client Authenticate With Account"})
return super().authenticate_with_account(account)
+17 -13
View File
@@ -1,14 +1,20 @@
import os
from typing import List, Optional
# 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.credentials import StreamWrapper # noqa: F401
from specklepy.core.api.credentials import Account, UserInfo # noqa: F401
from specklepy.core.api.credentials import (
get_account_from_token as core_get_account_from_token,
)
from specklepy.core.api.credentials import get_local_accounts as core_get_local_accounts
from pydantic import BaseModel, Field # pylint: disable=no-name-in-module
from specklepy.api.models import ServerInfo
from specklepy.core.helpers import speckle_path_provider
from specklepy.logging import metrics
from specklepy.logging.exceptions import SpeckleException
from specklepy.transports.sqlite import SQLiteTransport
# 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.credentials import (Account, UserInfo,
StreamWrapper, # deprecated
get_local_accounts as core_get_local_accounts,
get_account_from_token as core_get_account_from_token)
def get_local_accounts(base_path: Optional[str] = None) -> List[Account]:
@@ -29,12 +35,11 @@ def get_local_accounts(base_path: Optional[str] = None) -> List[Account]:
(acc for acc in accounts if acc.isDefault),
accounts[0] if accounts else None,
),
{"name": "Get Local Accounts"},
{"name": "Get Local Accounts"}
)
return accounts
def get_default_account(base_path: Optional[str] = None) -> Optional[Account]:
"""
Gets this environment's default account if any. If there is no default,
@@ -56,8 +61,7 @@ def get_default_account(base_path: Optional[str] = None) -> Optional[Account]:
metrics.initialise_tracker(default)
return default
def get_account_from_token(token: str, server_url: str = None) -> Account:
"""Gets the local account for the token if it exists
Arguments:
@@ -69,5 +73,5 @@ def get_account_from_token(token: str, server_url: str = None) -> Account:
"""
account = core_get_account_from_token(token, server_url)
metrics.track(metrics.SDK, account, {"name": "Get Account From Token"})
metrics.track( metrics.SDK, account, {"name": "Get Account From Token"} )
return account
+17 -73
View File
@@ -1,74 +1,18 @@
from specklepy.core.api.host_applications import (
ARCGIS,
ARCHICAD,
AUTOCAD,
BLENDER,
CIVIL,
CSIBRIDGE,
DXF,
DYNAMO,
ETABS,
EXCEL,
GRASSHOPPER,
GSA,
MICROSTATION,
NET,
OPENBUILDINGS,
OPENRAIL,
OPENROADS,
OTHER,
POWERBI,
PYTHON,
QGIS,
REVIT,
RHINO,
SAFE,
SAP2000,
SKETCHUP,
TEKLASTRUCTURES,
TOPSOLID,
UNITY,
UNREAL,
HostApplication,
HostAppVersion,
_app_name_host_app_mapping,
get_host_app_from_string,
)
from dataclasses import dataclass
from enum import Enum
from unicodedata import name
# re-exporting stuff from the moved api module
__all__ = [
"ARCGIS",
"ARCHICAD",
"AUTOCAD",
"BLENDER",
"CIVIL",
"CSIBRIDGE",
"DXF",
"DYNAMO",
"ETABS",
"EXCEL",
"GRASSHOPPER",
"GSA",
"MICROSTATION",
"NET",
"OPENBUILDINGS",
"OPENRAIL",
"OPENROADS",
"OTHER",
"POWERBI",
"PYTHON",
"QGIS",
"REVIT",
"RHINO",
"SAFE",
"SAP2000",
"SKETCHUP",
"TEKLASTRUCTURES",
"TOPSOLID",
"UNITY",
"UNREAL",
"HostApplication",
"HostAppVersion",
"_app_name_host_app_mapping",
"get_host_app_from_string",
]
# 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.host_applications import (HostApplication, HostAppVersion,
get_host_app_from_string,
_app_name_host_app_mapping,
RHINO,GRASSHOPPER,REVIT,DYNAMO,UNITY,GSA,
CIVIL,AUTOCAD,MICROSTATION,OPENROADS,
OPENRAIL,OPENBUILDINGS,ETABS,SAP2000,CSIBRIDGE,
SAFE,TEKLASTRUCTURES,DXF,EXCEL,UNREAL,POWERBI,
BLENDER,QGIS,ARCGIS,SKETCHUP,ARCHICAD,TOPSOLID,
PYTHON,NET,OTHER)
if __name__ == "__main__":
print(HostAppVersion.v)
+12
View File
@@ -0,0 +1,12 @@
from datetime import datetime
from typing import List, Optional
from pydantic import BaseModel, Field
# 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 (Collaborator, Commit,
Commits, Object, Branch, Branches,
Stream, Streams, User, LimitedUser,
PendingStreamCollaborator, Activity,
ActivityCollection, ServerInfo)
-35
View File
@@ -1,35 +0,0 @@
# 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 -5
View File
@@ -1,12 +1,16 @@
from typing import List, Optional
from specklepy.core.api.operations import deserialize as core_deserialize
from specklepy.core.api.operations import receive as _untracked_receive
from specklepy.core.api.operations import send as core_send
from specklepy.core.api.operations import serialize as core_serialize
from specklepy.logging import metrics
from specklepy.logging.exceptions import SpeckleException
from specklepy.objects.base import Base
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
from specklepy.transports.abstract_transport import AbstractTransport
from specklepy.transports.sqlite import SQLiteTransport
from specklepy.core.api.operations import (send as core_send,
receive as _untracked_receive,
serialize as core_serialize,
deserialize as core_deserialize)
def send(
@@ -70,7 +74,6 @@ def serialize(base: Base, write_transports: List[AbstractTransport] = []) -> str
metrics.track(metrics.SDK, custom_props={"name": "Serialize"})
return core_serialize(base, write_transports)
def deserialize(
obj_string: str, read_transport: Optional[AbstractTransport] = None
) -> Base:
+20 -6
View File
@@ -1,8 +1,21 @@
from typing import Any, Optional, Tuple
from threading import Lock
from typing import Any, Dict, List, Optional, Tuple, Type, Union
from gql.client import Client
from gql.transport.exceptions import TransportQueryError
from graphql import DocumentNode
from specklepy.api.credentials import Account
from specklepy.logging.exceptions import (
GraphQLException,
SpeckleException,
UnsupportedException,
)
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
from specklepy.transports.sqlite import SQLiteTransport
# 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.resource import ResourceBase as CoreResourceBase
@@ -16,9 +29,10 @@ class ResourceBase(CoreResourceBase):
server_version: Optional[Tuple[Any, ...]] = None,
) -> None:
super().__init__(
account=account,
basepath=basepath,
client=client,
name=name,
server_version=server_version,
account = account,
basepath = basepath,
client = client,
name = name,
server_version = server_version
)
+8 -40
View File
@@ -1,41 +1,9 @@
from specklepy.api.resources.current.active_user_resource import ActiveUserResource
from specklepy.api.resources.current.model_resource import ModelResource
from specklepy.api.resources.current.other_user_resource import OtherUserResource
from specklepy.api.resources.current.project_invite_resource import (
ProjectInviteResource,
)
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,
)
import pkgutil
import sys
from importlib import import_module
__all__ = [
"ActiveUserResource",
"ModelResource",
"OtherUserResource",
"ProjectInviteResource",
"ProjectResource",
"ServerResource",
"SubscriptionResource",
"VersionResource",
"active_user",
"branch",
"commit",
"object",
"other_user",
"server",
"stream",
"subscriptions",
"user",
]
for _, name, _ in pkgutil.iter_modules(__path__):
imported_module = import_module("." + name, package=__name__)
if hasattr(imported_module, "Resource"):
setattr(sys.modules[__name__], name, imported_module)
+120
View File
@@ -0,0 +1,120 @@
from datetime import datetime, timezone
from typing import List, Optional
from gql import gql
from specklepy.api.models import ActivityCollection, PendingStreamCollaborator, User
from specklepy.api.resource import ResourceBase
from specklepy.logging import metrics
from specklepy.logging.exceptions import SpeckleException
from specklepy.core.api.resources.active_user import Resource as CoreResource
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
def get(self) -> 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, custom_props={"name": "User Active Get"})
return super().get()
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 @deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT):
bool -- True if your profile was updated successfully
"""
metrics.track(metrics.SDK, self.account, {"name": "User Active Update"})
return super().update(name, company, bio, avatar)
def activity(
self,
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 Active Activity"})
return super().activity(limit, action_type, before, after, cursor)
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.SDK, self.account, {"name": "User Active Invites All Get"})
return super().get_all_pending_invites()
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.SDK, self.account, {"name": "User Active Invite Get"})
return super().get_pending_invite(stream_id, token)
@@ -1,15 +1,12 @@
from typing import Optional, Union
from typing import Optional
from deprecated import deprecated
from gql import gql
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.api.resource import ResourceBase
from specklepy.logging import metrics
from specklepy.logging.exceptions import SpeckleException
from specklepy.core.api.resources.branch import Resource as CoreResource
class Resource(CoreResource):
@@ -23,7 +20,6 @@ class Resource(CoreResource):
)
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:
@@ -39,10 +35,7 @@ class Resource(CoreResource):
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]:
def get(self, stream_id: str, name: str, commits_limit: int = 10):
"""Get a branch by name from a stream
Arguments:
@@ -56,7 +49,6 @@ class Resource(CoreResource):
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
@@ -71,7 +63,6 @@ class Resource(CoreResource):
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,
@@ -93,7 +84,6 @@ class Resource(CoreResource):
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
@@ -1,15 +1,12 @@
from typing import List, Optional, Union
from typing import List, Optional
from deprecated import deprecated
from gql import gql
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.api.resource import ResourceBase
from specklepy.logging import metrics
from specklepy.logging.exceptions import SpeckleException
from specklepy.core.api.resources.commit import Resource as CoreResource
class Resource(CoreResource):
@@ -23,7 +20,6 @@ class Resource(CoreResource):
)
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
@@ -38,7 +34,6 @@ class Resource(CoreResource):
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
@@ -53,7 +48,6 @@ class Resource(CoreResource):
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,
@@ -61,8 +55,8 @@ class Resource(CoreResource):
branch_name: str = "main",
message: str = "",
source_application: str = "python",
parents: Optional[List[str]] = None,
) -> Union[str, SpeckleException]:
parents: List[str] = None,
) -> str:
"""
Creates a commit on a branch
@@ -82,11 +76,8 @@ class Resource(CoreResource):
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
)
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
@@ -103,7 +94,6 @@ class Resource(CoreResource):
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
@@ -119,7 +109,6 @@ class Resource(CoreResource):
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,
@@ -1,147 +0,0 @@
from datetime import datetime
from typing import List, Optional, overload
from deprecated import deprecated
from specklepy.core.api.inputs.project_inputs import UserProjectsFilter
from specklepy.core.api.inputs.user_inputs import UserUpdateInput
from specklepy.core.api.models import (
PendingStreamCollaborator,
Project,
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
class ActiveUserResource(CoreResource):
"""API Access class for users. This class provides methods to get and update
the user profile, fetch user activity, and manage pending stream invitations."""
def __init__(self, account, basepath, client, server_version) -> None:
super().__init__(
account=account,
basepath=basepath,
client=client,
server_version=server_version,
)
self.schema = User
def get(self) -> Optional[User]:
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,
) -> 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,
)
)
def get_projects(
self,
*,
limit: int = 25,
cursor: Optional[str] = None,
filter: Optional[UserProjectsFilter] = None,
) -> ResourceCollection[Project]:
metrics.track(metrics.SDK, self.account, {"name": "Active User Get Projects"})
return super().get_projects(limit=limit, cursor=cursor, filter=filter)
def get_project_invites(self) -> List[PendingStreamCollaborator]:
metrics.track(
metrics.SDK, self.account, {"name": "Active User Get Project Invites"}
)
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,74 +0,0 @@
from typing import Optional
from specklepy.core.api.inputs.model_inputs import (
CreateModelInput,
DeleteModelInput,
ModelVersionsFilter,
UpdateModelInput,
)
from specklepy.core.api.inputs.project_inputs import ProjectModelsFilter
from specklepy.core.api.models import Model, ModelWithVersions, ResourceCollection
from specklepy.core.api.resources import ModelResource as CoreResource
from specklepy.logging import metrics
class ModelResource(CoreResource):
"""API Access class for models"""
def __init__(self, account, basepath, client, server_version) -> None:
super().__init__(
account=account,
basepath=basepath,
client=client,
server_version=server_version,
)
def get(self, model_id: str, project_id: str) -> Model:
metrics.track(metrics.SDK, self.account, {"name": "Model Get"})
return super().get(model_id, project_id)
def get_with_versions(
self,
model_id: str,
project_id: str,
*,
versions_limit: int = 25,
versions_cursor: Optional[str] = None,
versions_filter: Optional[ModelVersionsFilter] = None,
) -> ModelWithVersions:
metrics.track(metrics.SDK, self.account, {"name": "Model Get With Versions"})
return super().get_with_versions(
model_id,
project_id,
versions_limit=versions_limit,
versions_cursor=versions_cursor,
versions_filter=versions_filter,
)
def get_models(
self,
project_id: str,
*,
models_limit: int = 25,
models_cursor: Optional[str] = None,
models_filter: Optional[ProjectModelsFilter] = None,
) -> ResourceCollection[Model]:
metrics.track(metrics.SDK, self.account, {"name": "Model Get Models"})
return super().get_models(
project_id,
models_limit=models_limit,
models_cursor=models_cursor,
models_filter=models_filter,
)
def create(self, input: CreateModelInput) -> Model:
metrics.track(metrics.SDK, self.account, {"name": "Model Create"})
return super().create(input)
def delete(self, input: DeleteModelInput) -> bool:
metrics.track(metrics.SDK, self.account, {"name": "Model Delete"})
return super().delete(input)
def update(self, input: UpdateModelInput) -> Model:
metrics.track(metrics.SDK, self.account, {"name": "Model Update"})
return super().update(input)
@@ -1,104 +0,0 @@
from datetime import datetime
from typing import List, Optional, Union
from deprecated import deprecated
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):
"""
Provides API access to other users' profiles and activities on the platform.
This class enables fetching limited information about users, searching for users by name or email,
and accessing user activity logs with appropriate privacy and access control measures in place.
"""
def __init__(self, account, basepath, client, server_version) -> None:
super().__init__(
account=account,
basepath=basepath,
client=client,
server_version=(server_version,),
)
self.schema = LimitedUser
def get(self, id: str) -> Optional[LimitedUser]:
metrics.track(metrics.SDK, self.account, {"name": "Other User Get"})
return super().get(id)
def user_search(
self,
query: str,
*,
limit: int = 25,
cursor: Optional[str] = None,
archived: bool = False,
emailOnly: bool = False,
) -> UserSearchResultCollection:
metrics.track(metrics.SDK, self.account, {"name": "Other User Search"})
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)
@@ -1,54 +0,0 @@
from typing import Any, Optional, Tuple
from gql import Client
from specklepy.core.api.credentials import Account
from specklepy.core.api.inputs.project_inputs import (
ProjectInviteCreateInput,
ProjectInviteUseInput,
)
from specklepy.core.api.models import PendingStreamCollaborator, ProjectWithTeam
from specklepy.core.api.resources import ProjectInviteResource as CoreResource
from specklepy.logging import metrics
class ProjectInviteResource(CoreResource):
"""API Access class for project invites"""
def __init__(
self,
account: Account,
basepath: str,
client: Client,
server_version: Optional[Tuple[Any, ...]],
) -> None:
super().__init__(
account=account,
basepath=basepath,
client=client,
server_version=server_version,
)
def create(
self, project_id: str, input: ProjectInviteCreateInput
) -> ProjectWithTeam:
metrics.track(metrics.SDK, self.account, {"name": "Project Invite Create"})
return super().create(project_id, input)
def use(self, input: ProjectInviteUseInput) -> bool:
metrics.track(metrics.SDK, self.account, {"name": "Project Invite Use"})
return super().use(input)
def get(
self, project_id: str, token: Optional[str]
) -> Optional[PendingStreamCollaborator]:
metrics.track(metrics.SDK, self.account, {"name": "Project Invite Get"})
return super().get(project_id, token)
def cancel(
self,
project_id: str,
invite_id: str,
) -> ProjectWithTeam:
metrics.track(metrics.SDK, self.account, {"name": "Project Invite Cancel"})
return super().cancel(project_id, invite_id)
@@ -1,63 +0,0 @@
from typing import Optional
from specklepy.core.api.inputs.project_inputs import (
ProjectCreateInput,
ProjectModelsFilter,
ProjectUpdateInput,
ProjectUpdateRoleInput,
)
from specklepy.core.api.models import Project, ProjectWithModels, ProjectWithTeam
from specklepy.core.api.resources import ProjectResource as CoreResource
from specklepy.logging import metrics
class ProjectResource(CoreResource):
"""API Access class for projects"""
def __init__(self, account, basepath, client, server_version) -> None:
super().__init__(
account=account,
basepath=basepath,
client=client,
server_version=server_version,
)
def get(self, project_id: str) -> Project:
metrics.track(metrics.SDK, self.account, {"name": "Project Get "})
return super().get(project_id)
def get_with_models(
self,
project_id: str,
*,
models_limit: int = 25,
models_cursor: Optional[str] = None,
models_filter: Optional[ProjectModelsFilter] = None,
) -> ProjectWithModels:
metrics.track(metrics.SDK, self.account, {"name": "Project Get With Models"})
return super().get_with_models(
project_id,
models_limit=models_limit,
models_cursor=models_cursor,
models_filter=models_filter,
)
def get_with_team(self, project_id: str) -> ProjectWithTeam:
metrics.track(metrics.SDK, self.account, {"name": "Project Get With Team"})
return super().get_with_team(project_id)
def create(self, input: ProjectCreateInput) -> Project:
metrics.track(metrics.SDK, self.account, {"name": "Project Create"})
return super().create(input)
def update(self, input: ProjectUpdateInput) -> Project:
metrics.track(metrics.SDK, self.account, {"name": "Project Update"})
return super().update(input)
def delete(self, project_id: str) -> bool:
metrics.track(metrics.SDK, self.account, {"name": "Project Delete"})
return super().delete(project_id)
def update_role(self, input: ProjectUpdateRoleInput) -> ProjectWithTeam:
metrics.track(metrics.SDK, self.account, {"name": "Project Update Role"})
return super().update_role(input)
@@ -1,64 +0,0 @@
from typing import Callable, Optional, Sequence
from pydantic import BaseModel
from typing_extensions import TypeVar
from specklepy.core.api.models import (
ProjectModelsUpdatedMessage,
ProjectUpdatedMessage,
ProjectVersionsUpdatedMessage,
UserProjectsUpdatedMessage,
)
from specklepy.core.api.resources import SubscriptionResource as CoreResource
from specklepy.logging import metrics
TEventArgs = TypeVar("TEventArgs", bound=BaseModel)
class SubscriptionResource(CoreResource):
def __init__(self, account, basepath, client) -> None:
super().__init__(
account=account,
basepath=basepath,
client=client,
)
async def user_projects_updated(
self, callback: Callable[[UserProjectsUpdatedMessage], None]
) -> None:
metrics.track(
metrics.SDK, self.account, {"name": "Subscription Project Models Updated"}
)
return await super().user_projects_updated(callback)
async def project_models_updated(
self,
callback: Callable[[ProjectModelsUpdatedMessage], None],
id: str,
*,
model_ids: Optional[Sequence[str]] = None,
) -> None:
metrics.track(
metrics.SDK, self.account, {"name": "Subscription Project Models Updated"}
)
return await super().project_models_updated(callback, id, model_ids=model_ids)
async def project_updated(
self,
callback: Callable[[ProjectUpdatedMessage], None],
id: str,
) -> None:
metrics.track(
metrics.SDK, self.account, {"name": "Subscription Project Updated"}
)
return await super().project_updated(callback, id)
async def project_versions_updated(
self,
callback: Callable[[ProjectVersionsUpdatedMessage], None],
id: str,
) -> None:
metrics.track(
metrics.SDK, self.account, {"name": "Subscription Project Versions Updated"}
)
return await super().project_versions_updated(callback, id)
@@ -1,63 +0,0 @@
from typing import Optional
from specklepy.core.api.inputs.model_inputs import ModelVersionsFilter
from specklepy.core.api.inputs.version_inputs import (
CreateVersionInput,
DeleteVersionsInput,
MarkReceivedVersionInput,
MoveVersionsInput,
UpdateVersionInput,
)
from specklepy.core.api.models import ResourceCollection, Version
from specklepy.core.api.resources import VersionResource as CoreResource
from specklepy.logging import metrics
class VersionResource(CoreResource):
"""API Access class for model versions"""
def __init__(self, account, basepath, client, server_version) -> None:
super().__init__(
account=account,
basepath=basepath,
client=client,
server_version=server_version,
)
def get(self, version_id: str, project_id: str) -> Version:
metrics.track(metrics.SDK, self.account, {"name": "Version Get"})
return super().get(version_id, project_id)
def get_versions(
self,
model_id: str,
project_id: str,
*,
limit: int = 25,
cursor: Optional[str] = None,
filter: Optional[ModelVersionsFilter] = None,
) -> ResourceCollection[Version]:
metrics.track(metrics.SDK, self.account, {"name": "Version Get Versions"})
return super().get_versions(
model_id, project_id, limit=limit, cursor=cursor, filter=filter
)
def create(self, input: CreateVersionInput) -> str:
metrics.track(metrics.SDK, self.account, {"name": "Version Create"})
return super().create(input)
def update(self, input: UpdateVersionInput) -> Version:
metrics.track(metrics.SDK, self.account, {"name": "Version Update"})
return super().update(input)
def move_to_model(self, input: MoveVersionsInput) -> str:
metrics.track(metrics.SDK, self.account, {"name": "Version Move To Model"})
return super().move_to_model(input)
def delete(self, input: DeleteVersionsInput) -> bool:
metrics.track(metrics.SDK, self.account, {"name": "Version Delete"})
return super().delete(input)
def received(self, input: MarkReceivedVersionInput) -> bool:
metrics.track(metrics.SDK, self.account, {"name": "Version Received"})
return super().received(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,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 ActiveUserResource", version=FE1_DEPRECATION_VERSION)
class Resource(ServerResource):
"""Renamed to ServerResource"""
@@ -1,15 +1,14 @@
from typing import Dict, List
from deprecated import deprecated
from gql import gql
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.api.resource import ResourceBase
from specklepy.objects.base import Base
from specklepy.logging import metrics
from specklepy.core.api.resources.object import Resource as CoreResource
class Resource(CoreResource):
"""API Access class for objects"""
@@ -22,7 +21,6 @@ class Resource(CoreResource):
)
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
@@ -37,7 +35,6 @@ class Resource(CoreResource):
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()`.
@@ -61,3 +58,4 @@ class Resource(CoreResource):
"""
metrics.track(metrics.SDK, self.account, {"name": "Object Create"})
return super().create(stream_id, objects)
+87
View File
@@ -0,0 +1,87 @@
from datetime import datetime, timezone
from typing import List, Optional, Union
from gql import gql
from specklepy.api.models import ActivityCollection, LimitedUser
from specklepy.api.resource import ResourceBase
from specklepy.logging import metrics
from specklepy.logging.exceptions import SpeckleException
from specklepy.core.api.resources.other_user import Resource as CoreResource
class Resource(CoreResource):
"""API Access class for other users, that are not the currently active user."""
def __init__(self, account, basepath, client, server_version) -> None:
super().__init__(
account=account,
basepath=basepath,
client=client,
server_version=server_version,
)
self.schema = LimitedUser
def get(self, id: str) -> LimitedUser:
"""
Gets the profile of another user.
Arguments:
id {str} -- the user id
Returns:
LimitedUser -- the retrieved profile of another user
"""
metrics.track(metrics.SDK, self.account, {"name": "Other User Get"})
return super().get(id)
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"
)
metrics.track(metrics.SDK, self.account, {"name": "Other User Search"})
return super().search(search_query, limit)
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
"""
metrics.track(metrics.SDK, self.account, {"name": "Other User Activity"})
return super().activity(user_id, limit, action_type, before, after, cursor)
@@ -1,11 +1,17 @@
import re
from typing import Any, Dict, List, Tuple
from gql import gql
from specklepy.api.models import ServerInfo
from specklepy.core.api.resources import ServerResource as CoreResource
from specklepy.api.resource import ResourceBase
from specklepy.logging import metrics
from specklepy.logging.exceptions import GraphQLException
from specklepy.core.api.resources.server import Resource as CoreResource
class ServerResource(CoreResource):
class Resource(CoreResource):
"""API Access class for the server"""
def __init__(self, account, basepath, client) -> None:
@@ -67,4 +73,4 @@ class ServerResource(CoreResource):
bool -- True if the token was successfully deleted
"""
metrics.track(metrics.SDK, self.account, {"name": "Server Revoke Token"})
return super().revoke_token(token)
return super().revoke_token(token)
@@ -1,15 +1,15 @@
from datetime import datetime
from datetime import datetime, timezone
from typing import List, Optional
from deprecated import deprecated
from gql import gql
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.api.models import ActivityCollection, PendingStreamCollaborator, Stream
from specklepy.api.resource import ResourceBase
from specklepy.logging import metrics
from specklepy.logging.exceptions import SpeckleException, UnsupportedException
from specklepy.core.api.resources.stream import Resource as CoreResource
class Resource(CoreResource):
@@ -25,7 +25,6 @@ class Resource(CoreResource):
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
@@ -40,7 +39,6 @@ class Resource(CoreResource):
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
@@ -53,7 +51,6 @@ class Resource(CoreResource):
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",
@@ -74,7 +71,6 @@ class Resource(CoreResource):
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,
@@ -97,7 +93,6 @@ class Resource(CoreResource):
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
@@ -110,7 +105,6 @@ class Resource(CoreResource):
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,
@@ -132,7 +126,6 @@ class Resource(CoreResource):
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.
@@ -147,7 +140,6 @@ class Resource(CoreResource):
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]:
@@ -166,7 +158,6 @@ class Resource(CoreResource):
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,
@@ -194,7 +185,6 @@ class Resource(CoreResource):
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,
@@ -221,7 +211,6 @@ class Resource(CoreResource):
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
@@ -237,7 +226,6 @@ class Resource(CoreResource):
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
@@ -255,7 +243,6 @@ class Resource(CoreResource):
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
@@ -269,14 +256,9 @@ class Resource(CoreResource):
Returns:
bool -- True if the operation was successful
"""
metrics.track(
metrics.SDK,
self.account,
{"name": "Stream Permission Update", "role": role},
)
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
@@ -290,7 +272,6 @@ class Resource(CoreResource):
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,
@@ -320,3 +301,4 @@ class Resource(CoreResource):
"""
metrics.track(metrics.SDK, self.account, {"name": "Stream Activity"})
return super().activity(stream_id, action_type, limit, before, after, cursor)
@@ -1,17 +1,27 @@
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,
)
from specklepy.core.api.resources.current.subscription_resource import check_wsclient
from specklepy.core.api.resources.deprecated.subscriptions import (
Resource as CoreResource,
)
from specklepy.api.resource import ResourceBase
from specklepy.api.resources.stream import Stream
from specklepy.logging.exceptions import SpeckleException
from specklepy.logging import metrics
from specklepy.core.api.resources.subscriptions import Resource as CoreResource
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(CoreResource):
@@ -24,7 +34,6 @@ class Resource(CoreResource):
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.
@@ -40,7 +49,6 @@ class Resource(CoreResource):
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):
"""
@@ -56,12 +64,9 @@ class Resource(CoreResource):
Returns:
Stream -- the update stream
"""
metrics.track(
metrics.SDK, self.account, {"name": "Subscription Stream Updated"}
)
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.
@@ -78,12 +83,9 @@ class Resource(CoreResource):
Returns:
dict -- dict containing 'id' of stream removed and optionally 'revokedBy'
"""
metrics.track(
metrics.SDK, self.account, {"name": "Subscription Stream Removed"}
)
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,
@@ -1,13 +1,17 @@
from datetime import datetime
from datetime import datetime, timezone
from typing import List, Optional, Union
from deprecated import deprecated
from gql import gql
from specklepy.api.models import ActivityCollection, PendingStreamCollaborator, User
from specklepy.api.resource import ResourceBase
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
from specklepy.logging import metrics
from specklepy.core.api.resources.user import Resource as CoreResource
DEPRECATION_VERSION = "2.9.0"
DEPRECATION_TEXT = (
"The user resource is deprecated, please use the active_user or other_user"
@@ -42,7 +46,7 @@ class Resource(CoreResource):
"""
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
@@ -79,7 +83,7 @@ class Resource(CoreResource):
Returns:
bool -- True if your profile was updated successfully
"""
# metrics.track(metrics.USER, self.account, {"name": "update"})
#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)
@@ -114,6 +118,7 @@ class Resource(CoreResource):
"""
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]:
@@ -125,11 +130,10 @@ class Resource(CoreResource):
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"}
)
#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(
@@ -148,6 +152,7 @@ class Resource(CoreResource):
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.INVITE, self.account, {"name": "get"})
metrics.track(metrics.SDK, self.account, {"name": "User GetInvite_deprecated"})
return super().get_pending_invite(stream_id, token)
+14 -8
View File
@@ -1,9 +1,17 @@
from urllib.parse import unquote, urlparse
from warnings import warn
from specklepy.api.client import SpeckleClient
from specklepy.api.credentials import Account
from specklepy.core.api.wrapper import StreamWrapper as CoreStreamWrapper
from specklepy.logging import metrics
from specklepy.api.credentials import (
Account,
get_account_from_token,
get_local_accounts,
)
from specklepy.logging.exceptions import SpeckleException, SpeckleWarning
from specklepy.transports.server.server import ServerTransport
from specklepy.logging import metrics
from specklepy.core.api.wrapper import StreamWrapper as CoreStreamWrapper
class StreamWrapper(CoreStreamWrapper):
"""
@@ -22,7 +30,7 @@ class StreamWrapper(CoreStreamWrapper):
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")
wrapper = StreamWrapper("https://speckle.xyz/streams/3073b96e86/commits/604bea8cc6")
# get an authenticated SpeckleClient if you have a local account for the server
client = wrapper.get_client()
@@ -43,7 +51,7 @@ class StreamWrapper(CoreStreamWrapper):
_account: Account = None
def __init__(self, url: str) -> None:
super().__init__(url=url)
super().__init__(url = url)
def get_account(self, token: str = None) -> Account:
"""
@@ -82,7 +90,5 @@ class StreamWrapper(CoreStreamWrapper):
ServerTransport -- constructed for this stream
with a pre-authenticated client
"""
metrics.track(
metrics.SDK, custom_props={"name": "Stream Wrapper Get Transport"}
)
metrics.track(metrics.SDK, custom_props={"name": "Stream Wrapper Get Transport"})
return super().get_transport(token)
+41 -88
View File
@@ -11,20 +11,15 @@ 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.resources import (
ActiveUserResource,
ModelResource,
OtherUserResource,
ProjectInviteResource,
ProjectResource,
ServerResource,
SubscriptionResource,
VersionResource,
user,
active_user,
branch,
commit,
object,
other_user,
server,
stream,
subscriptions,
user,
)
from specklepy.logging import metrics
from specklepy.logging.exceptions import SpeckleException, SpeckleWarning
@@ -35,7 +30,7 @@ class SpeckleClient:
The `SpeckleClient` is your entry point for interacting with
your Speckle Server's GraphQL API.
You'll need to have access to a server to use it,
or you can use our public server `app.speckle.systems`.
or you can use our public server `speckle.xyz`.
To authenticate the client, you'll need to have downloaded
the [Speckle Manager](https://speckle.guide/#speckle-manager)
@@ -46,7 +41,7 @@ class SpeckleClient:
from specklepy.api.credentials import get_default_account
# initialise the client
client = SpeckleClient(host="app.speckle.systems") # or whatever your host is
client = SpeckleClient(host="speckle.xyz") # or whatever your host is
# client = SpeckleClient(host="localhost:3000", use_ssl=False) or use local server
# authenticate the client with an account (account has been added in Speckle Manager)
@@ -61,17 +56,10 @@ class SpeckleClient:
```
"""
DEFAULT_HOST = "app.speckle.systems"
DEFAULT_HOST = "speckle.xyz"
USE_SSL = True
def __init__(
self,
host: str = DEFAULT_HOST,
use_ssl: bool = USE_SSL,
verify_certificate: bool = True,
connection_retries: int = 3,
connection_timeout: int = 10,
) -> None:
def __init__(self, host: str = DEFAULT_HOST, use_ssl: bool = USE_SSL) -> None:
ws_protocol = "ws"
http_protocol = "http"
@@ -86,17 +74,9 @@ class SpeckleClient:
self.graphql = f"{self.url}/graphql"
self.ws_url = f"{ws_protocol}://{host}/graphql"
self.account = Account()
self.verify_certificate = verify_certificate
self.connection_retries = connection_retries
self.connection_timeout = connection_timeout
self.httpclient = Client(
transport=RequestsHTTPTransport(
url=self.graphql,
verify=self.verify_certificate,
retries=self.connection_retries,
timeout=self.connection_timeout,
)
transport=RequestsHTTPTransport(url=self.graphql, verify=True, retries=3)
)
self.wsclient = None
@@ -135,7 +115,8 @@ class SpeckleClient:
Arguments:
token {str} -- an api token
"""
self.authenticate_with_account(get_account_from_token(token))
self.authenticate_with_token(token)
self._set_up_client()
def authenticate_with_token(self, token: str) -> None:
"""
@@ -146,7 +127,7 @@ class SpeckleClient:
Arguments:
token {str} -- an api token
"""
self.account = Account.from_token(token, self.url)
self.account = get_account_from_token(token, self.url)
self._set_up_client()
def authenticate_with_account(self, account: Account) -> None:
@@ -169,7 +150,7 @@ class SpeckleClient:
"apollographql-client-version": metrics.HOST_APP_VERSION,
}
httptransport = RequestsHTTPTransport(
url=self.graphql, headers=headers, verify=self.verify_certificate, retries=3
url=self.graphql, headers=headers, verify=True, retries=3
)
wstransport = WebsocketsTransport(
url=self.ws_url,
@@ -181,81 +162,53 @@ class SpeckleClient:
self._init_resources()
try:
_ = self.active_user.get()
except SpeckleException as ex:
if isinstance(ex.exception, TransportServerError):
if ex.exception.code == 403:
warn(
SpeckleWarning(
"Possibly invalid token - could not authenticate Speckle Client"
f" for server {self.url}"
)
)
user_or_error = self.active_user.get()
if isinstance(user_or_error, SpeckleException):
if isinstance(user_or_error.exception, TransportServerError):
raise user_or_error.exception
else:
raise ex
raise user_or_error
except TransportServerError as ex:
if ex.code == 403:
warn(
SpeckleWarning(
"Possibly invalid token - could not authenticate Speckle Client"
f" for server {self.url}"
)
)
else:
raise ex
def execute_query(self, query: str) -> Dict:
return self.httpclient.execute(query)
def _init_resources(self) -> None:
self.server = ServerResource(
self.server = server.Resource(
account=self.account, basepath=self.url, client=self.httpclient
)
server_version = None
try:
server_version = self.server.version()
except Exception:
pass
self.other_user = OtherUserResource(
account=self.account,
basepath=self.url,
client=self.httpclient,
server_version=server_version,
)
self.active_user = ActiveUserResource(
account=self.account,
basepath=self.url,
client=self.httpclient,
server_version=server_version,
)
self.project = ProjectResource(
account=self.account,
basepath=self.url,
client=self.httpclient,
server_version=server_version,
)
self.project_invite = ProjectInviteResource(
account=self.account,
basepath=self.url,
client=self.httpclient,
server_version=server_version,
)
self.model = ModelResource(
account=self.account,
basepath=self.url,
client=self.httpclient,
server_version=server_version,
)
self.version = VersionResource(
account=self.account,
basepath=self.url,
client=self.httpclient,
server_version=server_version,
)
self.subscription = SubscriptionResource(
account=self.account,
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.other_user = other_user.Resource(
account=self.account,
basepath=self.url,
client=self.httpclient,
server_version=server_version,
)
self.active_user = active_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,
+3 -26
View File
@@ -1,22 +1,21 @@
import os
from pathlib import Path
from typing import List, Optional
from urllib.parse import urlparse
from pydantic import BaseModel, Field # pylint: disable=no-name-in-module
from specklepy.core.api.models import ServerInfo
from specklepy.core.helpers import speckle_path_provider
from specklepy.logging import metrics
from specklepy.logging.exceptions import SpeckleException
from specklepy.transports.sqlite import SQLiteTransport
class UserInfo(BaseModel):
id: Optional[str] = None
name: Optional[str] = None
email: Optional[str] = None
company: Optional[str] = None
avatar: Optional[str] = None
id: Optional[str] = None
class Account(BaseModel):
@@ -111,7 +110,7 @@ def get_default_account(base_path: Optional[str] = None) -> Optional[Account]:
if not default:
default = accounts[0]
default.isDefault = True
# metrics.initialise_tracker(default)
#metrics.initialise_tracker(default)
return default
@@ -144,28 +143,6 @@ def get_account_from_token(token: str, server_url: str = None) -> Account:
return Account.from_token(token, server_url)
def get_accounts_for_server(host: str) -> List[Account]:
all_accounts = get_local_accounts()
filtered: List[Account] = []
for acc in all_accounts:
moved_from = (
acc.serverInfo.migration.movedFrom if acc.serverInfo.migration else None
)
if moved_from and host == urlparse(moved_from).netloc:
filtered.append(acc)
for acc in all_accounts:
if any([x for x in filtered if x.userInfo.id == acc.userInfo.id]):
continue
if host == urlparse(acc.serverInfo.url).netloc:
filtered.append(acc)
return filtered
class StreamWrapper:
def __init__(self, url: str = None) -> None:
raise SpeckleException(
-29
View File
@@ -1,29 +0,0 @@
from enum import Enum
class ProjectVisibility(str, Enum):
PRIVATE = "PRIVATE"
PUBLIC = "PUBLIC"
UNLISTEd = "UNLISTED"
class UserProjectsUpdatedMessageType(str, Enum):
ADDED = "ADDED"
REMOVED = "REMOVED"
class ProjectModelsUpdatedMessageType(str, Enum):
CREATED = "CREATED"
DELETED = "DELETED"
UPDATED = "UPDATED"
class ProjectUpdatedMessageType(str, Enum):
DELETED = "DELETED"
UPDATED = "UPDATED"
class ProjectVersionsUpdatedMessageType(str, Enum):
CREATED = "CREATED"
DELETED = "DELETED"
UPDATED = "UPDATED"
@@ -1,26 +0,0 @@
from typing import Optional, Sequence
from pydantic import BaseModel
class CreateModelInput(BaseModel):
name: str
description: Optional[str] = None
projectId: str
class DeleteModelInput(BaseModel):
id: str
projectId: str
class UpdateModelInput(BaseModel):
id: str
name: Optional[str] = None
description: Optional[str] = None
projectId: str
class ModelVersionsFilter(BaseModel):
priorityIds: Sequence[str]
priorityIdsOnly: Optional[bool] = None
@@ -1,52 +0,0 @@
from typing import Optional, Sequence
from pydantic import BaseModel
from specklepy.core.api.enums import ProjectVisibility
class ProjectCreateInput(BaseModel):
name: Optional[str]
description: Optional[str]
visibility: Optional[ProjectVisibility]
class ProjectInviteCreateInput(BaseModel):
email: Optional[str]
role: Optional[str]
serverRole: Optional[str]
userId: Optional[str]
class ProjectInviteUseInput(BaseModel):
accept: bool
projectId: str
token: str
class ProjectModelsFilter(BaseModel):
contributors: Optional[Sequence[str]] = None
excludeIds: Optional[Sequence[str]] = None
ids: Optional[Sequence[str]] = None
onlyWithVersions: Optional[bool] = None
search: Optional[str] = None
sourceApps: Optional[Sequence[str]] = None
class ProjectUpdateInput(BaseModel):
id: str
name: Optional[str] = None
description: Optional[str] = None
allowPublicComments: Optional[bool] = None
visibility: Optional[ProjectVisibility] = None
class ProjectUpdateRoleInput(BaseModel):
userId: str
projectId: str
role: Optional[str]
class UserProjectsFilter(BaseModel):
search: str
onlyWithRole: Optional[Sequence[str]] = None
@@ -1,10 +0,0 @@
from typing import Optional
from pydantic import BaseModel
class UserUpdateInput(BaseModel):
avatar: Optional[str] = None
bio: Optional[str] = None
company: Optional[str] = None
name: Optional[str] = None
@@ -1,37 +0,0 @@
from typing import Optional, Sequence
from pydantic import BaseModel
class UpdateVersionInput(BaseModel):
versionId: str
projectId: str
message: Optional[str]
class MoveVersionsInput(BaseModel):
targetModelName: str
versionIds: Sequence[str]
projectId: str
class DeleteVersionsInput(BaseModel):
versionIds: Sequence[str]
projectId: str
class CreateVersionInput(BaseModel):
objectId: str
modelId: str
projectId: str
message: Optional[str] = None
sourceApplication: Optional[str] = "py"
totalChildrenCount: Optional[int] = None
parents: Optional[Sequence[str]] = None
class MarkReceivedVersionInput(BaseModel):
versionId: str
projectId: str
sourceApplication: str
message: Optional[str] = None
@@ -1,14 +1,9 @@
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
@@ -16,7 +11,6 @@ class Collaborator(BaseModel):
avatar: Optional[str] = None
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
class Commit(BaseModel):
id: Optional[str] = None
message: Optional[str] = None
@@ -41,14 +35,12 @@ class Commit(BaseModel):
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
@@ -57,7 +49,6 @@ class Object(BaseModel):
createdAt: Optional[datetime] = None
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
class Branch(BaseModel):
id: Optional[str] = None
name: Optional[str] = None
@@ -65,14 +56,12 @@ class Branch(BaseModel):
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
@@ -99,14 +88,67 @@ class Stream(BaseModel):
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 User(BaseModel):
id: Optional[str] = None
email: Optional[str] = None
name: Optional[str] = None
bio: Optional[str] = None
company: Optional[str] = None
avatar: Optional[str] = None
verified: Optional[bool] = None
role: Optional[str] = None
streams: Optional[Streams] = None
def __repr__(self):
return (
f"User( id: {self.id}, name: {self.name}, email: {self.email}, company:"
f" {self.company} )"
)
def __str__(self) -> str:
return self.__repr__()
class LimitedUser(BaseModel):
"""Limited user type, for showing public info about a user to another user."""
id: str
name: Optional[str] = None
bio: Optional[str] = None
company: Optional[str] = None
avatar: Optional[str] = None
verified: Optional[bool] = None
role: Optional[str] = None
class PendingStreamCollaborator(BaseModel):
id: Optional[str] = None
inviteId: Optional[str] = None
streamId: Optional[str] = None
streamName: Optional[str] = None
title: Optional[str] = None
role: Optional[str] = None
invitedBy: Optional[User] = None
user: Optional[User] = None
token: Optional[str] = None
def __repr__(self):
return (
f"PendingStreamCollaborator( inviteId: {self.inviteId}, streamId:"
f" {self.streamId}, role: {self.role}, title: {self.title}, invitedBy:"
f" {self.user.name if self.user else None})"
)
def __str__(self) -> str:
return self.__repr__()
class Activity(BaseModel):
actionType: Optional[str] = None
info: Optional[dict] = None
@@ -127,7 +169,6 @@ class Activity(BaseModel):
return self.__repr__()
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
class ActivityCollection(BaseModel):
totalCount: Optional[int] = None
items: Optional[List[Activity]] = None
@@ -142,3 +183,16 @@ class ActivityCollection(BaseModel):
def __str__(self) -> str:
return self.__repr__()
class ServerInfo(BaseModel):
name: Optional[str] = None
company: Optional[str] = None
url: Optional[str] = None
description: Optional[str] = None
adminContact: Optional[str] = None
canonicalUrl: Optional[str] = None
roles: Optional[List[dict]] = None
scopes: Optional[List[dict]] = None
authStrategies: Optional[List[dict]] = None
version: Optional[str] = None
-77
View File
@@ -1,77 +0,0 @@
from specklepy.core.api.models.current import (
AuthStrategy,
LimitedUser,
Model,
ModelWithVersions,
PendingStreamCollaborator,
Project,
ProjectCollaborator,
ProjectCommentCollection,
ProjectWithModels,
ProjectWithTeam,
ResourceCollection,
ServerConfiguration,
ServerInfo,
ServerMigration,
User,
UserSearchResultCollection,
Version,
)
from specklepy.core.api.models.deprecated import (
Activity,
ActivityCollection,
Branch,
Branches,
Collaborator,
Commit,
Commits,
Object,
Stream,
Streams,
)
from specklepy.core.api.models.instances import InstanceDefinitionProxy, InstanceProxy
from specklepy.core.api.models.proxies import ColorProxy, GroupProxy
from specklepy.core.api.models.subscription_messages import (
ProjectModelsUpdatedMessage,
ProjectUpdatedMessage,
ProjectVersionsUpdatedMessage,
UserProjectsUpdatedMessage,
)
__all__ = [
"User",
"ResourceCollection",
"ServerMigration",
"AuthStrategy",
"ServerConfiguration",
"ServerInfo",
"LimitedUser",
"PendingStreamCollaborator",
"ProjectCollaborator",
"Version",
"Model",
"ModelWithVersions",
"Project",
"ProjectWithModels",
"ProjectWithTeam",
"ProjectCommentCollection",
"UserSearchResultCollection",
"UserProjectsUpdatedMessage",
"ProjectModelsUpdatedMessage",
"ProjectUpdatedMessage",
"ProjectVersionsUpdatedMessage",
"Collaborator",
"Commit",
"Commits",
"Object",
"Branch",
"Branches",
"Stream",
"Streams",
"Activity",
"ActivityCollection",
"InstanceProxy",
"InstanceDefinitionProxy",
"ColorProxy",
"GroupProxy",
]
-171
View File
@@ -1,171 +0,0 @@
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
T = TypeVar("T")
class User(BaseModel):
id: str
email: Optional[str] = None
name: str
bio: Optional[str] = None
company: Optional[str] = None
avatar: Optional[str] = None
verified: Optional[bool] = None
role: Optional[str] = None
streams: Optional["Streams"] = None
def __repr__(self):
return (
f"User( id: {self.id}, name: {self.name}, email: {self.email}, company:"
f" {self.company} )"
)
def __str__(self) -> str:
return self.__repr__()
class ResourceCollection(BaseModel, Generic[T]):
totalCount: int
items: List[T]
cursor: Optional[str] = None
class ServerMigration(BaseModel):
movedFrom: Optional[str]
movedTo: Optional[str]
class AuthStrategy(BaseModel):
color: Optional[str]
icon: str
id: str
name: str
url: str
class ServerConfiguration(BaseModel):
blobSizeLimitBytes: int
objectMultipartUploadSizeLimitBytes: int
objectSizeLimitBytes: 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):
name: Optional[str] = None
company: Optional[str] = None
url: Optional[str] = None
adminContact: Optional[str] = None
description: Optional[str] = None
canonicalUrl: Optional[str] = None
roles: Optional[List[dict]] = None
scopes: Optional[List[dict]] = None
authStrategies: Optional[List[dict]] = None
version: Optional[str] = None
frontend2: Optional[bool] = None
migration: Optional[ServerMigration] = None
# TODO separate gql model from account management model
class LimitedUser(BaseModel):
"""Limited user type, for showing public info about a user to another user."""
id: str
name: str
bio: Optional[str]
company: Optional[str]
avatar: Optional[str]
verified: Optional[bool]
role: Optional[str]
class PendingStreamCollaborator(BaseModel):
id: str
inviteId: str
streamId: Optional[str] = None
projectId: str
streamName: Optional[str] = None
projectName: str
title: str
role: str
invitedBy: 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" {self.user.name if self.user else None})"
)
def __str__(self) -> str:
return self.__repr__()
class ProjectCollaborator(BaseModel):
id: str
role: str
user: LimitedUser
class Version(BaseModel):
authorUser: Optional[LimitedUser]
createdAt: datetime
id: str
message: Optional[str]
previewUrl: str
referencedObject: str
sourceApplication: Optional[str]
class Model(BaseModel):
author: LimitedUser
createdAt: datetime
description: Optional[str]
displayName: str
id: str
name: str
previewUrl: Optional[str]
updatedAt: datetime
class ModelWithVersions(Model):
versions: ResourceCollection[Version]
class Project(BaseModel):
allowPublicComments: bool
createdAt: datetime
description: Optional[str]
id: str
name: str
role: Optional[str]
sourceApps: List[str]
updatedAt: datetime
visibility: ProjectVisibility
workspaceId: Optional[str]
class ProjectWithModels(Project):
models: ResourceCollection[Model]
class ProjectWithTeam(Project):
invitedTeam: List[PendingStreamCollaborator]
team: List[ProjectCollaborator]
class ProjectCommentCollection(ResourceCollection[T], Generic[T]):
totalArchivedCount: int
class UserSearchResultCollection(BaseModel):
items: List[LimitedUser]
cursor: Optional[str] = None
@@ -1,28 +0,0 @@
from specklepy.objects.base import Base
class InstanceProxy(
Base,
speckle_type="Speckle.Core.Models.Instances.InstanceProxy",
):
"""
A proxy class for an instance (e.g, a rhino block, or an autocad block reference).
"""
definitionId: str
transform: list[float]
units: str
maxDepth: int
class InstanceDefinitionProxy(
Base,
speckle_type="Speckle.Core.Models.Instances.InstanceDefinitionProxy",
):
"""
A proxy class for an instance definition.
"""
objects: list[str]
maxDepth: int
name: str
-27
View File
@@ -1,27 +0,0 @@
from specklepy.objects.base import Base
class ColorProxy(
Base,
speckle_type="Speckle.Core.Models.Proxies.ColorProxy",
):
"""
Represents a color that is found on objects and collections in a root collection.
"""
objects: list[str]
value: int
name: str | None # nullable but required
class GroupProxy(
Base,
speckle_type="Speckle.Core.Models.Proxies.GroupProxy",
):
"""
Grouped objects with a meaningful way for host application so use this proxy if you want to group object references for any purpose.
i.e. in rhino -> creating group make objects selectable/moveable/editable together.
"""
objects: list[str]
name: str
@@ -1,36 +0,0 @@
from typing import Optional
from pydantic import BaseModel
from specklepy.core.api.enums import (
ProjectModelsUpdatedMessageType,
ProjectUpdatedMessageType,
ProjectVersionsUpdatedMessageType,
UserProjectsUpdatedMessageType,
)
from specklepy.core.api.models.current import Model, Project, Version
class UserProjectsUpdatedMessage(BaseModel):
id: str
type: UserProjectsUpdatedMessageType
project: Optional[Project]
class ProjectModelsUpdatedMessage(BaseModel):
id: str
type: ProjectModelsUpdatedMessageType
model: Optional[Model]
class ProjectUpdatedMessage(BaseModel):
id: str
type: ProjectUpdatedMessageType
project: Optional[Project]
class ProjectVersionsUpdatedMessage(BaseModel):
id: str
type: ProjectVersionsUpdatedMessageType
modelId: Optional[str]
version: Optional[Version]
+1 -1
View File
@@ -1,6 +1,6 @@
from typing import List, Optional
# from specklepy.logging import metrics
#from specklepy.logging import metrics
from specklepy.logging.exceptions import SpeckleException
from specklepy.objects.base import Base
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
+1 -35
View File
@@ -1,10 +1,9 @@
from threading import Lock
from typing import Any, Dict, List, Optional, Tuple, Type, TypeVar, Union
from typing import Any, Dict, List, Optional, Tuple, Type, Union
from gql.client import Client
from gql.transport.exceptions import TransportQueryError
from graphql import DocumentNode
from pydantic import BaseModel
from specklepy.core.api.credentials import Account
from specklepy.logging.exceptions import (
@@ -15,8 +14,6 @@ from specklepy.logging.exceptions import (
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
from specklepy.transports.sqlite import SQLiteTransport
T = TypeVar("T", bound=BaseModel)
class ResourceBase(object):
def __init__(
@@ -46,35 +43,6 @@ class ResourceBase(object):
response = response[key]
return response
def make_request_and_parse_response(
self,
schema: Type[T],
query: DocumentNode,
variables: Optional[Dict[str, Any]] = None,
) -> T:
try:
with self.__lock:
response = self.client.execute(query, variable_values=variables)
except TransportQueryError as ex:
raise GraphQLException(
message=(
f"Failed to execute the GraphQL {self.name} request. Errors:"
f" {ex.errors}"
),
errors=ex.errors,
data=ex.data,
) from ex
except Exception as ex:
raise SpeckleException(
message=(
f"Failed to execute the GraphQL {self.name} request. Inner"
f" exception: {ex}"
),
exception=ex,
) from ex
return schema.model_validate(response)
def _parse_response(self, response: Union[dict, list, None], schema=None):
"""Try to create a class instance from the response"""
if response is None:
@@ -101,8 +69,6 @@ class ResourceBase(object):
parse_response: bool = True,
) -> Any:
"""Executes the GraphQL query"""
# This method has quite complex and ambiguous typing, and counter-intuitive error handling
# We are going to phase it out in favour of `make_request_and_parse_response`
try:
with self.__lock:
response = self.client.execute(query, variable_values=params)
@@ -1,43 +0,0 @@
from specklepy.core.api.resources.current.active_user_resource import ActiveUserResource
from specklepy.core.api.resources.current.model_resource import ModelResource
from specklepy.core.api.resources.current.other_user_resource import OtherUserResource
from specklepy.core.api.resources.current.project_invite_resource import (
ProjectInviteResource,
)
from specklepy.core.api.resources.current.project_resource import ProjectResource
from specklepy.core.api.resources.current.server_resource import ServerResource
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",
"ModelResource",
"OtherUserResource",
"ProjectInviteResource",
"ProjectResource",
"ServerResource",
"SubscriptionResource",
"VersionResource",
"active_user",
"branch",
"commit",
"object",
"other_user",
"server",
"stream",
"subscriptions",
"user",
]
@@ -1,31 +1,17 @@
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.project_inputs import UserProjectsFilter
from specklepy.core.api.inputs.user_inputs import 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.models import ActivityCollection, PendingStreamCollaborator, User
from specklepy.core.api.resource import ResourceBase
from specklepy.core.api.responses import DataResponse
from specklepy.logging.exceptions import GraphQLException
from specklepy.logging.exceptions import SpeckleException
NAME = "active_user"
class ActiveUserResource(ResourceBase):
"""API Access class for the active user"""
class Resource(ResourceBase):
"""API Access class for users"""
def __init__(self, account, basepath, client, server_version) -> None:
super().__init__(
@@ -37,75 +23,38 @@ class ActiveUserResource(ResourceBase):
)
self.schema = User
def get(self) -> Optional[User]:
"""Gets the currently active user profile (as extracted from the authorization header)
def get(self) -> 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 requested user, or none if no authentication token is provided to the Client
User -- the retrieved user
"""
QUERY = gql(
query = gql(
"""
query User {
data:activeUser {
id
email
name
bio
company
avatar
verified
role
}
}
"""
)
variables = {}
return self.make_request_and_parse_response(
DataResponse[Optional[User]], QUERY, variables
).data
def _update(self, input: UserUpdateInput) -> User:
QUERY = gql(
"""
mutation ActiveUserMutations($input: UserUpdateInput!) {
data:activeUserMutations {
data:update(user: $input) {
id
email
name
bio
company
avatar
verified
role
activeUser {
id
email
name
bio
company
avatar
verified
profiles
role
}
}
}
"""
"""
)
variables = {"input": input.model_dump(warnings="error")}
params = {}
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:
...
return self.make_request(query=query, params=params, return_type="activeUser")
def update(
self,
@@ -113,125 +62,40 @@ class ActiveUserResource(ResourceBase):
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,
):
"""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 @deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT):
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"
)
)
def get_projects(
self,
*,
limit: int = 25,
cursor: Optional[str] = None,
filter: Optional[UserProjectsFilter] = None,
) -> ResourceCollection[Project]:
QUERY = gql(
"""
query User($limit : Int!, $cursor: String, $filter: UserProjectsFilter) {
data:activeUser {
data:projects(limit: $limit, cursor: $cursor, filter: $filter) {
totalCount
cursor
items {
id
name
description
visibility
allowPublicComments
role
createdAt
updatedAt
sourceApps
workspaceId
}
}
}
}
"""
return self.make_request(
query=query, params=params, return_type="userUpdate", parse_response=False
)
variables = {
"limit": limit,
"cursor": cursor,
"filter": filter.model_dump(warnings="error") if filter else None,
}
response = self.make_request_and_parse_response(
DataResponse[Optional[DataResponse[ResourceCollection[Project]]]],
QUERY,
variables,
)
if response.data is None:
raise GraphQLException(
"GraphQL response indicated that the ActiveUser could not be found"
)
return response.data.data
def get_project_invites(self) -> List[PendingStreamCollaborator]:
QUERY = gql(
"""
query ProjectInvites {
data:activeUser {
data:projectInvites {
id
inviteId
invitedBy {
avatar
bio
company
id
name
role
verified
}
projectId
projectName
role
title
token
user {
id
name
bio
company
verified
avatar
role
}
}
}
}
"""
)
variables = {}
response = self.make_request_and_parse_response(
DataResponse[Optional[DataResponse[List[PendingStreamCollaborator]]]],
QUERY,
variables,
)
if response.data is None:
raise GraphQLException(
"GraphQL response indicated that the ActiveUser could not be found"
)
return response.data.data
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
def activity(
self,
limit: int = 20,
@@ -239,7 +103,7 @@ class ActiveUserResource(ResourceBase):
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.
@@ -310,7 +174,6 @@ class ActiveUserResource(ResourceBase):
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
@@ -331,18 +194,13 @@ class ActiveUserResource(ResourceBase):
inviteId
streamId
streamName
projectId
projectName
title
role
invitedBy {
id
name
bio
company
avatar
verified
role
}
}
}
@@ -355,7 +213,6 @@ class ActiveUserResource(ResourceBase):
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]:
@@ -380,21 +237,15 @@ class ActiveUserResource(ResourceBase):
streamInvite(streamId: $streamId, token: $token) {
id
token
inviteId
streamId
streamName
projectId
projectName
title
role
invitedBy {
id
name
bio
company
avatar
verified
role
}
}
}
@@ -1,24 +1,15 @@
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.models import 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
"""
"""API Access class for branches"""
def __init__(self, account, basepath, client) -> None:
super().__init__(
@@ -29,7 +20,6 @@ class Resource(ResourceBase):
)
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:
@@ -49,8 +39,6 @@ class Resource(ResourceBase):
}
"""
)
if len(name) < 3:
return SpeckleException(message="Branch Name must be at least 3 characters")
params = {
"branch": {
"streamId": stream_id,
@@ -63,7 +51,6 @@ class Resource(ResourceBase):
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
@@ -111,7 +98,6 @@ class Resource(ResourceBase):
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
@@ -167,7 +153,6 @@ class Resource(ResourceBase):
query=query, params=params, return_type=["stream", "branches", "items"]
)
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
def update(
self,
stream_id: str,
@@ -209,7 +194,6 @@ class Resource(ResourceBase):
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
@@ -1,24 +1,15 @@
from typing import List, Optional, Union
from typing import List, Optional
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.models import 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
"""
"""API Access class for commits"""
def __init__(self, account, basepath, client) -> None:
super().__init__(
@@ -29,7 +20,6 @@ class Resource(ResourceBase):
)
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
@@ -68,7 +58,6 @@ class Resource(ResourceBase):
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
@@ -110,7 +99,6 @@ class Resource(ResourceBase):
query=query, params=params, return_type=["stream", "commits", "items"]
)
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
def create(
self,
stream_id: str,
@@ -118,8 +106,8 @@ class Resource(ResourceBase):
branch_name: str = "main",
message: str = "",
source_application: str = "python",
parents: Optional[List[str]] = None,
) -> Union[str, SpeckleException]:
parents: List[str] = None,
) -> str:
"""
Creates a commit on a branch
@@ -160,7 +148,6 @@ class Resource(ResourceBase):
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
@@ -188,7 +175,6 @@ class Resource(ResourceBase):
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
@@ -213,7 +199,6 @@ class Resource(ResourceBase):
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,
@@ -1,278 +0,0 @@
from typing import Optional
from gql import gql
from specklepy.core.api.inputs.model_inputs import (
CreateModelInput,
DeleteModelInput,
ModelVersionsFilter,
UpdateModelInput,
)
from specklepy.core.api.inputs.project_inputs import ProjectModelsFilter
from specklepy.core.api.models import Model, ModelWithVersions, ResourceCollection
from specklepy.core.api.resource import ResourceBase
from specklepy.core.api.responses import DataResponse
NAME = "model"
class ModelResource(ResourceBase):
"""API Access class for models"""
def __init__(self, account, basepath, client, server_version) -> None:
super().__init__(
account=account,
basepath=basepath,
client=client,
name=NAME,
server_version=server_version,
)
def get(self, model_id: str, project_id: str) -> Model:
QUERY = gql(
"""
query ModelGet($modelId: String!, $projectId: String!) {
data:project(id: $projectId) {
data:model(id: $modelId) {
id
name
previewUrl
updatedAt
description
displayName
createdAt
author {
avatar
bio
company
id
name
role
verified
}
}
}
}
"""
)
variables = {
"modelId": model_id,
"projectId": project_id,
}
return self.make_request_and_parse_response(
DataResponse[DataResponse[Model]], QUERY, variables
).data.data
def get_with_versions(
self,
model_id: str,
project_id: str,
*,
versions_limit: int = 25,
versions_cursor: Optional[str] = None,
versions_filter: Optional[ModelVersionsFilter] = None,
) -> ModelWithVersions:
QUERY = gql(
"""
query ModelGetWithVersions($modelId: String!, $projectId: String!, $versionsLimit: Int!, $versionsCursor: String, $versionsFilter: ModelVersionsFilter) {
data:project(id: $projectId) {
data:model(id: $modelId) {
id
name
previewUrl
updatedAt
versions(limit: $versionsLimit, cursor: $versionsCursor, filter: $versionsFilter) {
items {
id
referencedObject
message
sourceApplication
createdAt
previewUrl
authorUser {
avatar
id
name
bio
company
verified
role
}
}
totalCount
cursor
}
description
displayName
createdAt
author {
avatar
bio
company
id
name
role
verified
}
}
}
}
"""
)
variables = {
"projectId": project_id,
"modelId": model_id,
"versionsLimit": versions_limit,
"versionsCursor": versions_cursor,
"versionsFilter": versions_filter.model_dump(warnings="error")
if versions_filter
else None,
}
return self.make_request_and_parse_response(
DataResponse[DataResponse[ModelWithVersions]], QUERY, variables
).data.data
def get_models(
self,
project_id: str,
*,
models_limit: int = 25,
models_cursor: Optional[str] = None,
models_filter: Optional[ProjectModelsFilter] = None,
) -> ResourceCollection[Model]:
QUERY = gql(
"""
query ProjectGetWithModels($projectId: String!, $modelsLimit: Int!, $modelsCursor: String, $modelsFilter: ProjectModelsFilter) {
data:project(id: $projectId) {
data:models(limit: $modelsLimit, cursor: $modelsCursor, filter: $modelsFilter) {
items {
id
name
previewUrl
updatedAt
displayName
description
createdAt
author {
avatar
bio
company
id
name
role
verified
}
}
totalCount
cursor
}
}
}
"""
)
variables = {
"projectId": project_id,
"modelsLimit": models_limit,
"modelsCursor": models_cursor,
"modelsFilter": models_filter.model_dump(warnings="error")
if models_filter
else None,
}
return self.make_request_and_parse_response(
DataResponse[DataResponse[ResourceCollection[Model]]], QUERY, variables
).data.data
def create(self, input: CreateModelInput) -> Model:
QUERY = gql(
"""
mutation ModelCreate($input: CreateModelInput!) {
data:modelMutations {
data:create(input: $input) {
id
displayName
name
description
createdAt
updatedAt
previewUrl
author {
avatar
bio
company
id
name
role
verified
}
}
}
}
"""
)
variables = {
"input": input.model_dump(warnings="error"),
}
return self.make_request_and_parse_response(
DataResponse[DataResponse[Model]], QUERY, variables
).data.data
def delete(self, input: DeleteModelInput) -> bool:
QUERY = gql(
"""
mutation ModelDelete($input: DeleteModelInput!) {
data:modelMutations {
data:delete(input: $input)
}
}
"""
)
variables = {"input": input.model_dump(warnings="error")}
return self.make_request_and_parse_response(
DataResponse[DataResponse[bool]], QUERY, variables
).data.data
def update(self, input: UpdateModelInput) -> Model:
QUERY = gql(
"""
mutation ModelUpdate($input: UpdateModelInput!) {
data:modelMutations {
data:update(input: $input) {
id
name
displayName
description
createdAt
updatedAt
previewUrl
author {
avatar
bio
company
id
name
role
verified
}
}
}
}
"""
)
variables = {
"input": input.model_dump(warnings="error"),
}
return self.make_request_and_parse_response(
DataResponse[DataResponse[Model]], QUERY, variables
).data.data
@@ -1,254 +0,0 @@
from typing import Any, Optional, Tuple
from gql import Client, gql
from specklepy.core.api.credentials import Account
from specklepy.core.api.inputs.project_inputs import (
ProjectInviteCreateInput,
ProjectInviteUseInput,
)
from specklepy.core.api.models import PendingStreamCollaborator, ProjectWithTeam
from specklepy.core.api.resource import ResourceBase
from specklepy.core.api.responses import DataResponse
NAME = "project_invite"
class ProjectInviteResource(ResourceBase):
"""API Access class for project invites"""
def __init__(
self,
account: Account,
basepath: str,
client: Client,
server_version: Optional[Tuple[Any, ...]],
) -> None:
super().__init__(
account=account,
basepath=basepath,
client=client,
name=NAME,
server_version=server_version,
)
def create(
self, project_id: str, input: ProjectInviteCreateInput
) -> ProjectWithTeam:
QUERY = gql(
"""
mutation ProjectInviteCreate($projectId: ID!, $input: ProjectInviteCreateInput!) {
data:projectMutations {
data:invites {
data:create(projectId: $projectId, input: $input) {
id
name
description
visibility
allowPublicComments
role
createdAt
updatedAt
workspaceId
sourceApps
team {
id
role
user {
id
name
bio
company
avatar
verified
role
}
}
invitedTeam {
id
inviteId
projectId
projectName
title
role
token
user {
id
name
bio
company
avatar
verified
role
}
invitedBy {
id
name
bio
company
avatar
verified
role
}
}
}
}
}
}
"""
)
variables = {
"projectId": project_id,
"input": input.model_dump(warnings="error"),
}
return self.make_request_and_parse_response(
DataResponse[DataResponse[DataResponse[ProjectWithTeam]]], QUERY, variables
).data.data.data
def use(self, input: ProjectInviteUseInput) -> bool:
QUERY = gql(
"""
mutation ProjectInviteUse($input: ProjectInviteUseInput!) {
data:projectMutations {
data:invites {
data:use(input: $input)
}
}
}
"""
)
variables = {
"input": input.model_dump(warnings="error"),
}
return self.make_request_and_parse_response(
DataResponse[DataResponse[DataResponse[bool]]], QUERY, variables
).data.data.data
def get(
self, project_id: str, token: Optional[str]
) -> Optional[PendingStreamCollaborator]:
"""Returns: The invite, or None if no invite exists"""
QUERY = gql(
"""
query ProjectInvite($projectId: String!, $token: String) {
data:projectInvite(projectId: $projectId, token: $token) {
id
inviteId
invitedBy {
avatar
bio
company
id
name
role
verified
}
projectId
projectName
role
title
token
user {
avatar
bio
company
id
name
role
verified
}
}
}
"""
)
variables = {
"projectId": project_id,
"token": token,
}
return self.make_request_and_parse_response(
DataResponse[Optional[PendingStreamCollaborator]], QUERY, variables
).data
def cancel(
self,
project_id: str,
invite_id: str,
) -> ProjectWithTeam:
QUERY = gql(
"""
mutation ProjectInviteCancel($projectId: ID!, $inviteId: String!) {
data:projectMutations {
data:invites {
data:cancel(projectId: $projectId, inviteId: $inviteId) {
id
name
description
visibility
allowPublicComments
role
createdAt
updatedAt
sourceApps
workspaceId
team {
id
role
user {
id
name
bio
company
avatar
verified
role
}
}
invitedTeam {
id
inviteId
projectId
projectName
title
role
token
user {
id
name
bio
company
avatar
verified
role
}
invitedBy {
id
name
bio
company
avatar
verified
role
}
}
}
}
}
}
"""
)
variables = {
"projectId": project_id,
"inviteId": invite_id,
}
return self.make_request_and_parse_response(
DataResponse[DataResponse[DataResponse[ProjectWithTeam]]], QUERY, variables
).data.data.data
@@ -1,336 +0,0 @@
from typing import Optional
from gql import gql
from specklepy.core.api.inputs.project_inputs import (
ProjectCreateInput,
ProjectModelsFilter,
ProjectUpdateInput,
ProjectUpdateRoleInput,
)
from specklepy.core.api.models import Project, ProjectWithModels, ProjectWithTeam
from specklepy.core.api.resource import ResourceBase
from specklepy.core.api.responses import DataResponse
NAME = "project"
class ProjectResource(ResourceBase):
"""API Access class for projects"""
def __init__(self, account, basepath, client, server_version) -> None:
super().__init__(
account=account,
basepath=basepath,
client=client,
name=NAME,
server_version=server_version,
)
def get(self, project_id: str) -> Project:
QUERY = gql(
"""
query Project($projectId: String!) {
data:project(id: $projectId) {
allowPublicComments
createdAt
description
id
name
role
sourceApps
updatedAt
visibility
workspaceId
}
}
"""
)
variables = {
"projectId": project_id,
}
return self.make_request_and_parse_response(
DataResponse[Project], QUERY, variables
).data
def get_with_models(
self,
project_id: str,
*,
models_limit: int = 25,
models_cursor: Optional[str] = None,
models_filter: Optional[ProjectModelsFilter] = None,
) -> ProjectWithModels:
QUERY = gql(
"""
query ProjectGetWithModels($projectId: String!, $modelsLimit: Int!, $modelsCursor: String, $modelsFilter: ProjectModelsFilter) {
data:project(id: $projectId) {
id
name
description
visibility
allowPublicComments
role
createdAt
updatedAt
sourceApps
workspaceId
models(limit: $modelsLimit, cursor: $modelsCursor, filter: $modelsFilter) {
items {
id
name
previewUrl
updatedAt
displayName
description
createdAt
author {
avatar
bio
company
id
name
role
verified
}
}
cursor
totalCount
}
}
}
"""
)
variables = {
"projectId": project_id,
"modelsLimit": models_limit,
"modelsCursor": models_cursor,
"modelsFilter": models_filter.model_dump(warnings="error")
if models_filter
else None,
}
return self.make_request_and_parse_response(
DataResponse[ProjectWithModels], QUERY, variables
).data
def get_with_team(self, project_id: str) -> ProjectWithTeam:
QUERY = gql(
"""
query ProjectGetWithTeam($projectId: String!) {
data:project(id: $projectId) {
id
name
description
visibility
allowPublicComments
role
createdAt
updatedAt
workspaceId
sourceApps
team {
id
role
user {
id
name
bio
company
avatar
verified
role
}
}
invitedTeam {
id
inviteId
projectId
projectName
title
role
token
user {
id
name
bio
company
avatar
verified
role
}
invitedBy {
id
name
bio
company
avatar
verified
role
}
}
workspaceId
}
}
"""
)
variables = {
"projectId": project_id,
}
return self.make_request_and_parse_response(
DataResponse[ProjectWithTeam], QUERY, variables
).data
def create(self, input: ProjectCreateInput) -> Project:
QUERY = gql(
"""
mutation ProjectCreate($input: ProjectCreateInput) {
data:projectMutations {
data:create(input: $input) {
id
name
description
visibility
allowPublicComments
role
createdAt
updatedAt
sourceApps
workspaceId
}
}
}
"""
)
variables = {
"input": input.model_dump(warnings="error"),
}
return self.make_request_and_parse_response(
DataResponse[DataResponse[Project]], QUERY, variables
).data.data
def update(self, input: ProjectUpdateInput) -> Project:
QUERY = gql(
"""
mutation ProjectUpdate($input: ProjectUpdateInput!) {
data:projectMutations{
data:update(update: $input) {
allowPublicComments
createdAt
description
id
name
role
sourceApps
updatedAt
visibility
workspaceId
}
}
}
"""
)
variables = {
"input": input.model_dump(warnings="error"),
}
return self.make_request_and_parse_response(
DataResponse[DataResponse[Project]], QUERY, variables
).data.data
def delete(self, project_id: str) -> bool:
QUERY = gql(
"""
mutation ProjectDelete($projectId: String!) {
data:projectMutations {
data:delete(id: $projectId)
}
}
"""
)
variables = {
"projectId": project_id,
}
return self.make_request_and_parse_response(
DataResponse[DataResponse[bool]], QUERY, variables
).data.data
def update_role(self, input: ProjectUpdateRoleInput) -> ProjectWithTeam:
QUERY = gql(
"""
mutation ProjectUpdateRole($input: ProjectUpdateRoleInput!) {
data:projectMutations {
data:updateRole(input: $input) {
id
name
description
visibility
allowPublicComments
role
createdAt
updatedAt
sourceApps
workspaceId
team {
id
role
user {
id
name
bio
company
avatar
verified
role
}
}
invitedTeam {
id
inviteId
projectId
projectName
title
role
token
user {
id
name
bio
company
avatar
verified
role
}
invitedBy {
id
name
bio
company
avatar
verified
role
}
}
}
}
}
"""
)
variables = {
"input": input.model_dump(warnings="error"),
}
return self.make_request_and_parse_response(
DataResponse[DataResponse[ProjectWithTeam]], QUERY, variables
).data.data
@@ -1,218 +0,0 @@
from functools import wraps
from typing import Any, Callable, Dict, Optional, Sequence, Type
from gql import gql
from graphql import DocumentNode
from pydantic import BaseModel
from typing_extensions import TypeVar
from specklepy.core.api.models import (
ProjectModelsUpdatedMessage,
ProjectUpdatedMessage,
ProjectVersionsUpdatedMessage,
UserProjectsUpdatedMessage,
)
from specklepy.core.api.resource import ResourceBase
from specklepy.core.api.responses import DataResponse
from specklepy.logging.exceptions import SpeckleException
NAME = "subscribe"
TEventArgs = TypeVar("TEventArgs", bound=BaseModel)
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 SubscriptionResource(ResourceBase):
"""API Access class for subscriptions"""
def __init__(self, account, basepath, client) -> None:
super().__init__(
account=account,
basepath=basepath,
client=client,
name=NAME,
)
async def user_projects_updated(
self, callback: Callable[[UserProjectsUpdatedMessage], None]
) -> None:
QUERY = gql(
"""
subscription UserProjectsUpdated {
data:userProjectsUpdated {
id
project {
id
name
description
visibility
allowPublicComments
role
createdAt
updatedAt
sourceApps
workspaceId
}
type
}
}
"""
)
await self.subscribe_2(
DataResponse[UserProjectsUpdatedMessage],
QUERY,
None,
callback=lambda d: callback(d.data),
)
async def project_models_updated(
self,
callback: Callable[[ProjectModelsUpdatedMessage], None],
id: str,
model_ids: Optional[Sequence[str]] = None,
) -> None:
QUERY = gql(
"""
subscription ProjectModelsUpdated($id: String!, $modelIds: [String!]) {
data:projectModelsUpdated(id: $id, modelIds: $modelIds) {
id
model {
id
name
previewUrl
updatedAt
description
displayName
createdAt
author {
avatar
bio
company
id
name
role
verified
}
}
type
}
}
"""
)
variables = {"id": id, "modelIds": model_ids}
await self.subscribe_2(
DataResponse[ProjectModelsUpdatedMessage],
QUERY,
variables,
callback=lambda d: callback(d.data),
)
async def project_updated(
self,
callback: Callable[[ProjectUpdatedMessage], None],
id: str,
) -> None:
QUERY = gql(
"""
subscription ProjectUpdated($id: String!) {
data:projectUpdated(id: $id) {
id
project {
id
name
description
visibility
allowPublicComments
role
createdAt
updatedAt
sourceApps
workspaceId
}
type
}
}
"""
)
variables = {"id": id}
await self.subscribe_2(
DataResponse[ProjectUpdatedMessage],
QUERY,
variables,
callback=lambda d: callback(d.data),
)
async def project_versions_updated(
self,
callback: Callable[[ProjectVersionsUpdatedMessage], None],
id: str,
) -> None:
QUERY = gql(
"""
subscription ProjectVersionsUpdated($id: String!) {
data:projectVersionsUpdated(id: $id) {
id
modelId
type
version {
id
referencedObject
message
sourceApplication
createdAt
previewUrl
authorUser {
id
name
bio
company
verified
role
avatar
}
}
}
}
"""
)
variables = {"id": id}
await self.subscribe_2(
DataResponse[ProjectVersionsUpdatedMessage],
QUERY,
variables,
callback=lambda d: callback(d.data),
)
@check_wsclient
async def subscribe_2(
self,
response_type: Type[TEventArgs],
query: DocumentNode,
variables: Optional[Dict[str, Any]],
callback: Callable[[TEventArgs], None],
) -> None:
async with self.client as session:
self.session = session
gen = session.subscribe(query, variable_values=variables)
async for res in gen:
event_arg = response_type.model_validate(res)
callback(event_arg)
@@ -1,234 +0,0 @@
from typing import Optional
from gql import gql
from specklepy.core.api.inputs.model_inputs import ModelVersionsFilter
from specklepy.core.api.inputs.version_inputs import (
CreateVersionInput,
DeleteVersionsInput,
MarkReceivedVersionInput,
MoveVersionsInput,
UpdateVersionInput,
)
from specklepy.core.api.models import ResourceCollection, Version
from specklepy.core.api.resource import ResourceBase
from specklepy.core.api.responses import DataResponse
NAME = "model"
class VersionResource(ResourceBase):
"""API Access class for model versions"""
def __init__(self, account, basepath, client, server_version) -> None:
super().__init__(
account=account,
basepath=basepath,
client=client,
name=NAME,
server_version=server_version,
)
def get(self, version_id: str, project_id: str) -> Version:
QUERY = gql(
"""
query VersionGet($projectId: String!, $versionId: String!) {
data:project(id: $projectId) {
data:version(id: $versionId) {
id
referencedObject
message
sourceApplication
createdAt
previewUrl
authorUser {
id
name
bio
company
verified
role
avatar
}
}
}
}
"""
)
variables = {
"projectId": project_id,
"versionId": version_id,
}
return self.make_request_and_parse_response(
DataResponse[DataResponse[Version]], QUERY, variables
).data.data
def get_versions(
self,
model_id: str,
project_id: str,
*,
limit: int = 25,
cursor: Optional[str] = None,
filter: Optional[ModelVersionsFilter] = None,
) -> ResourceCollection[Version]:
QUERY = gql(
"""
query VersionGetVersions($projectId: String!, $modelId: String!, $limit: Int!, $cursor: String, $filter: ModelVersionsFilter) {
data:project(id: $projectId) {
data:model(id: $modelId) {
data:versions(limit: $limit, cursor: $cursor, filter: $filter) {
items {
id
referencedObject
message
sourceApplication
createdAt
previewUrl
authorUser {
id
name
bio
company
verified
role
avatar
}
}
cursor
totalCount
}
}
}
}
"""
)
variables = {
"projectId": project_id,
"modelId": model_id,
"limit": limit,
"cursor": cursor,
"filter": filter.model_dump(warnings="error") if filter else None,
}
return self.make_request_and_parse_response(
DataResponse[DataResponse[DataResponse[ResourceCollection[Version]]]],
QUERY,
variables,
).data.data.data
def create(self, input: CreateVersionInput) -> str:
QUERY = gql(
"""
mutation Create($input: CreateVersionInput!) {
data:versionMutations {
data:create(input: $input) {
data:id
}
}
}
"""
)
variables = {
"input": input.model_dump(warnings="error"),
}
return self.make_request_and_parse_response(
DataResponse[DataResponse[DataResponse[str]]], QUERY, variables
).data.data.data
def update(self, input: UpdateVersionInput) -> Version:
QUERY = gql(
"""
mutation VersionUpdate($input: UpdateVersionInput!) {
data:versionMutations {
data:update(input: $input) {
id
referencedObject
message
sourceApplication
createdAt
previewUrl
authorUser {
id
name
bio
company
verified
role
avatar
}
}
}
}
"""
)
variables = {"input": input.model_dump(warnings="error")}
return self.make_request_and_parse_response(
DataResponse[DataResponse[Version]], QUERY, variables
).data.data
def move_to_model(self, input: MoveVersionsInput) -> str:
QUERY = gql(
"""
mutation VersionMoveToModel($input: MoveVersionsInput!) {
data:versionMutations {
data:moveToModel(input: $input) {
data:id
}
}
}
"""
)
variables = {
"input": input.model_dump(warnings="error"),
}
return self.make_request_and_parse_response(
DataResponse[DataResponse[DataResponse[str]]], QUERY, variables
).data.data.data
def delete(self, input: DeleteVersionsInput) -> bool:
QUERY = gql(
"""
mutation VersionDelete($input: DeleteVersionsInput!) {
data:versionMutations {
data:delete(input: $input)
}
}
"""
)
variables = {
"input": input.model_dump(warnings="error"),
}
return self.make_request_and_parse_response(
DataResponse[DataResponse[bool]], QUERY, variables
).data.data
def received(self, input: MarkReceivedVersionInput) -> bool:
QUERY = gql(
"""
mutation MarkReceived($input: MarkReceivedVersionInput!) {
data:versionMutations {
data:markReceived(input: $input)
}
}
"""
)
variables = {
"input": input.model_dump(warnings="error"),
}
return self.make_request_and_parse_response(
DataResponse[DataResponse[bool]], QUERY, variables
).data.data
@@ -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,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,2 +0,0 @@
schema: https://app.speckle.systems/graphql
documents: '**/*.graphql'
@@ -1,26 +1,16 @@
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,
LimitedUser,
UserSearchResultCollection,
)
from specklepy.core.api.models.deprecated import (
FE1_DEPRECATION_REASON,
FE1_DEPRECATION_VERSION,
)
from specklepy.core.api.models import ActivityCollection, LimitedUser
from specklepy.core.api.resource import ResourceBase
from specklepy.core.api.responses import DataResponse
from specklepy.logging.exceptions import SpeckleException
NAME = "other_user"
class OtherUserResource(ResourceBase):
class Resource(ResourceBase):
"""API Access class for other users, that are not the currently active user."""
def __init__(self, account, basepath, client, server_version) -> None:
@@ -33,7 +23,7 @@ class OtherUserResource(ResourceBase):
)
self.schema = LimitedUser
def get(self, id: str) -> Optional[LimitedUser]:
def get(self, id: str) -> LimitedUser:
"""
Gets the profile of another user.
@@ -43,81 +33,26 @@ class OtherUserResource(ResourceBase):
Returns:
LimitedUser -- the retrieved profile of another user
"""
QUERY = gql(
query = gql(
"""
query LimitedUser($id: String!) {
data:otherUser(id: $id){
id
name
bio
company
avatar
verified
role
query OtherUser($id: String!) {
otherUser(id: $id) {
id
name
bio
company
avatar
verified
role
}
}
}
"""
)
variables = {"id": id}
params = {"id": id}
return self.make_request_and_parse_response(
DataResponse[Optional[LimitedUser]], QUERY, variables
).data
return self.make_request(query=query, params=params, return_type="otherUser")
def user_search(
self,
query: str,
*,
limit: int = 25,
cursor: Optional[str] = None,
archived: bool = False,
emailOnly: bool = False,
) -> UserSearchResultCollection:
"""Searches for a user on the server, 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
cursor {Optional[str]} --
archived {bool} --
emailOnly {bool} --
Returns:
ResourceCollection[LimitedUser] -- User objects that match the search query
"""
QUERY = gql(
"""
query UserSearch($query: String!, $limit: Int!, $cursor: String, $archived: Boolean, $emailOnly: Boolean) {
data:userSearch(query: $query, limit: $limit, cursor: $cursor, archived: $archived, emailOnly: $emailOnly) {
cursor
items {
id
name
bio
company
avatar
verified
role
}
}
}
"""
)
variables = {
"query": query,
"limit": limit,
"cursor": cursor,
"archived": archived,
"emailOnly": emailOnly,
}
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]:
@@ -140,13 +75,12 @@ class OtherUserResource(ResourceBase):
query UserSearch($search_query: String!, $limit: Int!) {
userSearch(query: $search_query, limit: $limit) {
items {
id
name
bio
company
avatar
verified
role
id
name
bio
company
avatar
verified
}
}
}
@@ -158,7 +92,6 @@ class OtherUserResource(ResourceBase):
query=query, params=params, return_type=["userSearch", "items"]
)
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
def activity(
self,
user_id: str,
@@ -1,7 +1,6 @@
import re
from typing import Any, Dict, List, Tuple
import requests
from gql import gql
from specklepy.core.api.models import ServerInfo
@@ -11,7 +10,7 @@ from specklepy.logging.exceptions import GraphQLException
NAME = "server"
class ServerResource(ResourceBase):
class Resource(ResourceBase):
"""API Access class for the server"""
def __init__(self, account, basepath, client) -> None:
@@ -57,21 +56,9 @@ class ServerResource(ResourceBase):
"""
)
server_info = self.make_request(
return self.make_request(
query=query, return_type="serverInfo", schema=ServerInfo
)
if isinstance(server_info, ServerInfo) and isinstance(
server_info.canonicalUrl, str
):
r = requests.get(
server_info.canonicalUrl, headers={"User-Agent": "specklepy SDK"}
)
if "x-speckle-frontend-2" in r.headers:
server_info.frontend2 = True
else:
server_info.frontend2 = False
return server_info
def version(self) -> Tuple[Any, ...]:
"""Get the server version
@@ -4,15 +4,7 @@ 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.models import ActivityCollection, PendingStreamCollaborator, Stream
from specklepy.core.api.resource import ResourceBase
from specklepy.logging.exceptions import SpeckleException, UnsupportedException
@@ -20,10 +12,7 @@ NAME = "stream"
class Resource(ResourceBase):
"""
API Access class for streams
Stream resource is deprecated, please use project resource instead
"""
"""API Access class for streams"""
def __init__(self, account, basepath, client, server_version) -> None:
super().__init__(
@@ -36,7 +25,6 @@ class Resource(ResourceBase):
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
@@ -98,7 +86,6 @@ class Resource(ResourceBase):
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
@@ -152,7 +139,6 @@ class Resource(ResourceBase):
query=query, params=params, return_type=["user", "streams", "items"]
)
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
def create(
self,
name: str = "Anonymous Python Stream",
@@ -177,8 +163,7 @@ class Resource(ResourceBase):
}
"""
)
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}
}
@@ -187,7 +172,6 @@ class Resource(ResourceBase):
query=query, params=params, return_type="streamCreate", parse_response=False
)
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
def update(
self,
id: str,
@@ -228,7 +212,6 @@ class Resource(ResourceBase):
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
@@ -252,7 +235,6 @@ class Resource(ResourceBase):
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,
@@ -332,7 +314,6 @@ class Resource(ResourceBase):
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.
@@ -366,7 +347,6 @@ class Resource(ResourceBase):
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]:
@@ -394,27 +374,19 @@ class Resource(ResourceBase):
inviteId
streamId
streamName
projectName
projectId
title
role
invitedBy{
id
name
bio
company
avatar
verified
role
}
user {
id
name
bio
company
avatar
verified
role
}
}
}
@@ -430,7 +402,6 @@ class Resource(ResourceBase):
schema=PendingStreamCollaborator,
)
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
def invite(
self,
stream_id: str,
@@ -487,7 +458,6 @@ class Resource(ResourceBase):
parse_response=False,
)
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
def invite_batch(
self,
stream_id: str,
@@ -534,10 +504,11 @@ class Resource(ResourceBase):
user_invites = [
{"streamId": stream_id, "message": message, "userId": user_id}
for user_id in (user_ids if user_ids is not None else [])
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(
@@ -547,7 +518,6 @@ class Resource(ResourceBase):
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
@@ -579,7 +549,6 @@ class Resource(ResourceBase):
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
@@ -617,7 +586,6 @@ class Resource(ResourceBase):
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
@@ -664,7 +632,6 @@ class Resource(ResourceBase):
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
@@ -694,7 +661,6 @@ class Resource(ResourceBase):
parse_response=False,
)
@deprecated(reason=FE1_DEPRECATION_REASON, version=FE1_DEPRECATION_VERSION)
def activity(
self,
stream_id: str,
@@ -762,13 +728,13 @@ class Resource(ResourceBase):
"stream_id": stream_id,
"limit": limit,
"action_type": action_type,
"before": (
before.astimezone(timezone.utc).isoformat() if before else before
),
"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
),
"cursor": cursor.astimezone(timezone.utc).isoformat()
if cursor
else cursor,
}
except AttributeError as e:
raise SpeckleException(
@@ -1,16 +1,11 @@
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.core.api.resources.stream import Stream
from specklepy.logging.exceptions import SpeckleException
NAME = "subscribe"
@@ -40,7 +35,6 @@ class Resource(ResourceBase):
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.
@@ -4,12 +4,9 @@ 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.models import ActivityCollection, PendingStreamCollaborator, User
from specklepy.core.api.resource import ResourceBase
from specklepy.logging.exceptions import SpeckleException
NAME = "user"
-9
View File
@@ -1,9 +0,0 @@
from typing import Generic, TypeVar
from pydantic import BaseModel
T = TypeVar("T")
class DataResponse(BaseModel, Generic[T]):
data: T
+28 -124
View File
@@ -1,13 +1,11 @@
from urllib.parse import quote, unquote, urlparse
from urllib.parse import unquote, urlparse
from warnings import warn
from gql import gql
from specklepy.core.api.client import SpeckleClient
from specklepy.core.api.credentials import (
Account,
get_account_from_token,
get_accounts_for_server,
get_local_accounts,
)
from specklepy.logging.exceptions import SpeckleException, SpeckleWarning
from specklepy.transports.server.server import ServerTransport
@@ -30,7 +28,7 @@ class StreamWrapper:
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")
wrapper = StreamWrapper("https://speckle.xyz/streams/3073b96e86/commits/604bea8cc6")
# get an authenticated SpeckleClient if you have a local account for the server
client = wrapper.get_client()
@@ -47,7 +45,6 @@ class StreamWrapper:
commit_id: str = None
object_id: str = None
branch_name: str = None
model_id: str = None
_client: SpeckleClient = None
_account: Account = None
@@ -84,86 +81,29 @@ class StreamWrapper:
" provided."
)
# check for fe2 URL
if "/projects/" in parsed.path:
use_fe2 = True
key_stream = "project"
else:
use_fe2 = False
key_stream = "stream"
while segments:
segment = segments.pop(0)
if use_fe2 is False:
if segments and segment.lower() == "streams":
self.stream_id = segments.pop(0)
elif segments and segment.lower() == "commits":
if segments and segment.lower() == "streams":
self.stream_id = segments.pop(0)
elif segments and segment.lower() == "commits":
self.commit_id = segments.pop(0)
elif segments and segment.lower() == "branches":
self.branch_name = unquote(segments.pop(0))
elif segments and segment.lower() == "objects":
self.object_id = segments.pop(0)
elif segment.lower() == "globals":
self.branch_name = "globals"
if segments:
self.commit_id = segments.pop(0)
elif segments and segment.lower() == "branches":
self.branch_name = unquote(segments.pop(0))
elif segments and segment.lower() == "objects":
self.object_id = segments.pop(0)
elif segment.lower() == "globals":
self.branch_name = "globals"
if segments:
self.commit_id = segments.pop(0)
else:
raise SpeckleException(
f"Cannot parse {url} into a stream wrapper class - invalid URL"
" provided."
)
elif segments and use_fe2 is True:
if segment.lower() == "projects":
self.stream_id = segments.pop(0)
elif segment.lower() == "models":
next_segment = segments.pop(0)
if "," in next_segment:
raise SpeckleException("Multi-model urls are not supported yet")
elif unquote(next_segment).startswith("$"):
raise SpeckleException(
"Federation model urls are not supported"
)
elif len(next_segment) == 32:
self.object_id = next_segment
else:
self.branch_name = unquote(next_segment).split("@")[0]
if "@" in unquote(next_segment):
self.commit_id = unquote(next_segment).split("@")[1]
else:
raise SpeckleException(
f"Cannot parse {url} into a stream wrapper class - invalid URL"
" provided."
)
if use_fe2 is True and self.branch_name is not None:
self.model_id = self.branch_name
# get branch name
query = gql(
"""
query Project($project_id: String!, $model_id: String!) {
project(id: $project_id) {
id
model(id: $model_id) {
name
}
}
}
"""
)
self._client = self.get_client()
params = {"project_id": self.stream_id, "model_id": self.model_id}
project = self._client.httpclient.execute(query, params)
try:
self.branch_name = project["project"]["model"]["name"]
except KeyError as ke:
raise SpeckleException("Project model name is not found", ke)
else:
raise SpeckleException(
f"Cannot parse {url} into a stream wrapper class - invalid URL"
" provided."
)
if not self.stream_id:
raise SpeckleException(
f"Cannot parse {url} into a stream wrapper class - no {key_stream} id found."
f"Cannot parse {url} into a stream wrapper class - no stream id found."
)
@property
@@ -178,7 +118,14 @@ class StreamWrapper:
if self._account and self._account.token:
return self._account
self._account = next(iter(get_accounts_for_server(self.host)), None)
self._account = next(
(
a
for a in get_local_accounts()
if self.host == urlparse(a.serverInfo.url).netloc
),
None,
)
if not self._account:
self._account = get_account_from_token(token, self.server_url)
@@ -237,46 +184,3 @@ class StreamWrapper:
if not self._account or not self._account.token:
self.get_account(token)
return ServerTransport(self.stream_id, account=self._account)
def to_string(self) -> str:
"""
Constructs a URL depending on the StreamWrapper type and FE version.
"""
use_fe2 = False
key_streams = "/streams/"
key_branches = "/branches/"
if isinstance(self.branch_name, str):
value_branch = quote(self.branch_name)
if self.branch_name == "globals":
key_branches = "/"
key_commits = "/commits/"
if isinstance(self.commit_id, str) and self.branch_name == "globals":
key_commits = "/globals/"
key_objects = "/objects/"
if "/projects/" in self.stream_url:
use_fe2 = True
key_streams = "/projects/"
key_branches = "/models/"
value_branch = self.model_id
key_commits = "@"
key_objects = "/models/"
wrapper_type = self.type
if use_fe2 is False or (use_fe2 is True and not self.model_id):
base_url = f"{self.server_url}{key_streams}{self.stream_id}"
else: # fe2 is True and model_id available
base_url = f"{self.server_url}{key_streams}{self.stream_id}{key_branches}{value_branch}"
if wrapper_type == "object":
return f"{base_url}{key_objects}{self.object_id}"
elif wrapper_type == "commit":
return f"{base_url}{key_commits}{self.commit_id}"
elif wrapper_type == "branch":
return f"{self.server_url}{key_streams}{self.stream_id}{key_branches}{value_branch}"
elif wrapper_type == "stream":
return f"{self.server_url}{key_streams}{self.stream_id}"
else:
raise SpeckleException(
f"Cannot parse StreamWrapper of type '{wrapper_type}'"
)
+2 -2
View File
@@ -28,7 +28,7 @@ CONNECTOR = "Connector Action"
RECEIVE = "Receive"
SEND = "Send"
# not in use since 2.15
# not in use since 2.15
ACCOUNTS = "Get Local Accounts"
BRANCH = "Branch Action"
CLIENT = "Speckle Client"
@@ -142,7 +142,7 @@ class MetricsTracker(metaclass=Singleton):
def hash(self, value: str):
inputList = value.lower().split("://")
input = inputList[len(inputList) - 1].split("/")[0].split("?")[0]
input = inputList[len(inputList)-1].split("/")[0].split('?')[0]
return hashlib.md5(input.encode("utf-8")).hexdigest().upper()
def _send_tracking_requests(self):
+16
View File
@@ -0,0 +1,16 @@
from typing import Optional
from specklepy.objects import Base
class CRS(Base, speckle_type="Objects.GIS.CRS"):
"""A Coordinate Reference System stored in wkt format"""
name: Optional[str] = None
authority_id: Optional[str] = None
wkt: Optional[str] = None
units_native: Optional[str] = None
offset_x: Optional[float] = None
offset_y: Optional[float] = None
rotation: Optional[float] = None
+22
View File
@@ -0,0 +1,22 @@
"""Builtin Speckle object kit."""
from specklepy.objects.GIS.layers import (
VectorLayer,
RasterLayer,
)
from specklepy.objects.GIS.geometry import (
GisPolygonGeometry,
GisPolygonElement,
GisLineElement,
GisPointElement,
GisRasterElement,
)
from specklepy.objects.GIS.CRS import (
CRS,
)
__all__ = ["VectorLayer", "RasterLayer",
"GisPolygonGeometry", "GisPolygonElement", "GisLineElement", "GisPointElement", "GisRasterElement",
"CRS"]
+53
View File
@@ -0,0 +1,53 @@
from typing import Optional, Union, List
from specklepy.objects.geometry import Point, Line, Polyline, Circle, Arc, Polycurve, Mesh
from specklepy.objects import Base
from deprecated import deprecated
class GisPolygonGeometry(Base, speckle_type="Objects.GIS.PolygonGeometry", detachable={"displayValue"}):
"""GIS Polygon Geometry"""
boundary: Optional[Union[Polyline, Arc, Line, Circle, Polycurve]] = None
voids: Optional[List[Union[Polyline, Arc, Line, Circle, Polycurve]] ] = None
displayValue: Optional[List[Mesh]] = None
class GisPolygonElement(Base, speckle_type="Objects.GIS.PolygonElement"):
"""GIS Polygon element"""
geometry: Optional[List[GisPolygonGeometry]] = None
attributes: Optional[Base] = None
class GisLineElement(Base, speckle_type="Objects.GIS.LineElement"):
"""GIS Polyline element"""
geometry: Optional[List[Union[Polyline, Arc, Line, Circle, Polycurve]]] = None,
attributes: Optional[Base] = None,
class GisPointElement(Base, speckle_type="Objects.GIS.PointElement"):
"""GIS Point element"""
geometry: Optional[List[Point]] = None,
attributes: Optional[Base] = None,
class GisRasterElement(Base, speckle_type="Objects.GIS.RasterElement", detachable={"displayValue"}):
"""GIS Raster element"""
band_count: Optional[int] = None
band_names: Optional[List[str]] = None
x_origin: Optional[float] = None
y_origin: Optional[float] = None
x_size: Optional[int] = None
y_size: Optional[int] = None
x_resolution: Optional[float] = None
y_resolution: Optional[float] = None
noDataValue: Optional[List[float]] = None
displayValue: Optional[List[Mesh]] = None
class GisTopography(GisRasterElement, speckle_type="Objects.GIS.GisTopography", detachable={"displayValue"}):
"""GIS Raster element with 3d Topography representation"""
class GisNonGeometryElement(Base, speckle_type="Objects.GIS.NonGeometryElement"):
"""GIS Table feature"""
attributes: Optional[Base] = None
+83
View File
@@ -0,0 +1,83 @@
from typing import Any, Dict, List, Optional
from specklepy.objects.base import Base
from specklepy.objects.other import Collection
from specklepy.objects.GIS.CRS import CRS
from deprecated import deprecated
@deprecated(version="2.15", reason="Use VectorLayer or RasterLayer instead")
class Layer(Base, detachable={"features"}):
"""A GIS Layer"""
def __init__(
self,
name:str=None,
crs:CRS=None,
units: str = "m",
features: Optional[List[Base]] = None,
layerType: str = "None",
geomType: str = "None",
renderer: Optional[dict[str, Any]] = None,
**kwargs
) -> None:
super().__init__(**kwargs)
self.name = name
self.crs = crs
self.units = units
self.type = layerType
self.features = features or []
self.geomType = geomType
self.renderer = renderer or {}
class VectorLayer(
Collection,
detachable={"elements"},
speckle_type="Objects.GIS.VectorLayer",
serialize_ignore={"features"}):
"""GIS Vector Layer"""
name: Optional[str]=None
crs: Optional[CRS]=None
units: Optional[str] = None
elements: Optional[List[Base]] = None
attributes: Optional[Base] = None
geomType: Optional[str] = "None"
renderer: Optional[Dict[str, Any]] = None
collectionType = "VectorLayer"
@property
@deprecated(version="2.14", reason="Use elements")
def features(self) -> Optional[List[Base]]:
return self.elements
@features.setter
def features(self, value: Optional[List[Base]]) -> None:
self.elements = value
class RasterLayer(
Collection,
detachable={"elements"},
speckle_type="Objects.GIS.RasterLayer",
serialize_ignore={"features"}):
"""GIS Raster Layer"""
name: Optional[str] = None
crs: Optional[CRS]=None
units: Optional[str] = None
rasterCrs: Optional[CRS]=None
elements: Optional[List[Base]] = None
geomType: Optional[str] = "None"
renderer: Optional[Dict[str, Any]] = None
collectionType = "RasterLayer"
@property
@deprecated(version="2.14", reason="Use elements")
def features(self) -> Optional[List[Base]]:
return self.elements
@features.setter
def features(self, value: Optional[List[Base]]) -> None:
self.elements = value
+2 -9
View File
@@ -1,13 +1,6 @@
"""Builtin Speckle object kit."""
from specklepy.objects import encoding, geometry, other, primitive, units
from specklepy.objects import encoding, geometry, other, primitive, structural, units
from specklepy.objects.base import Base
__all__ = [
"Base",
"encoding",
"geometry",
"other",
"units",
"primitive",
]
__all__ = ["Base", "encoding", "geometry", "other", "units", "structural", "primitive"]
+2 -3
View File
@@ -19,7 +19,7 @@ from warnings import warn
from stringcase import pascalcase
from specklepy.logging.exceptions import SpeckleException, SpeckleInvalidUnitException
from specklepy.objects.units import Units
from specklepy.objects.units import Units, get_units_from_string
from specklepy.transports.memory import MemoryTransport
PRIMITIVES = (int, float, str, bool)
@@ -188,8 +188,7 @@ class _RegisteringBase:
cls._detachable = cls._detachable.union(detachable)
if serialize_ignore:
cls._serialize_ignore = cls._serialize_ignore.union(serialize_ignore)
# we know, that the super here is object, that takes no args on init subclass
return super().__init_subclass__()
super().__init_subclass__(**kwargs)
# T = TypeVar("T")
+14 -14
View File
@@ -40,9 +40,9 @@ class Point(Base, speckle_type=GEOMETRY + "Point"):
class Pointcloud(
Base,
Base,
speckle_type=GEOMETRY + "Pointcloud",
chunkable={"points": 31250, "colors": 62500, "sizes": 62500},
chunkable={"points": 31250, "colors": 62500, "sizes": 62500},
):
points: Optional[List[float]] = None
colors: Optional[List[int]] = None
@@ -303,15 +303,15 @@ class Polyline(Base, speckle_type=GEOMETRY + "Polyline", chunkable={"value": 200
class SpiralType(Enum):
Biquadratic = 0
BiquadraticParabola = 1
Bloss = 2
Clothoid = 3
Cosine = 4
Cubic = 5
CubicParabola = 6
Radioid = 7
Sinusoid = 8
Biquadratic = (0,)
BiquadraticParabola = (1,)
Bloss = (2,)
Clothoid = (3,)
Cosine = (4,)
Cubic = (5,)
CubicParabola = (6,)
Radioid = (7,)
Sinusoid = (8,)
Unknown = 9
@@ -319,7 +319,7 @@ class Spiral(Base, speckle_type=GEOMETRY + "Spiral", detachable={"displayValue"}
startPoint: Optional[Point] = None
endPoint: Optional[Point]
plane: Optional[Plane]
turns: Optional[float]
turns: Optional[int]
pitchAxis: Optional[Vector] = Vector()
pitch: float = 0
spiralType: Optional[SpiralType] = None
@@ -898,7 +898,7 @@ class Brep(
def VerticesValue(self) -> List[Point]:
if self.Vertices is None:
return None
encoded_unit = get_encoding_from_units(self.Vertices[0].units)
encoded_unit = get_encoding_from_units(self.Vertices[0]._units)
values = [encoded_unit]
for vertex in self.Vertices:
values.extend(vertex.to_list())
@@ -913,7 +913,7 @@ class Brep(
for i in range(0, len(value), 3):
vertex = Point.from_list(value[i : i + 3])
vertex.units = units
vertex._units = units
vertices.append(vertex)
self.Vertices = vertices
@@ -1,25 +1,24 @@
from abc import ABC, abstractmethod
from typing import Any, Collection, Dict, Generic, Iterable, Optional, Tuple, TypeVar
from dataclasses import dataclass
from typing import Any, Collection, Dict, Generic, Iterable, List, Optional, Tuple, TypeVar
from attrs import define
from specklepy.objects.base import Base
ROOT: str = "__Root"
T = TypeVar("T")
T = TypeVar('T')
PARENT_INFO = Tuple[Optional[str], str]
@define(slots=True)
class CommitObjectBuilder(ABC, Generic[T]):
converted: Dict[str, Base]
_parent_infos: Dict[str, Collection[PARENT_INFO]]
def __init__(self) -> None:
self.converted = {}
self._parent_infos = {}
@abstractmethod
def include_object(self, conversion_result: Base, native_object: T) -> None:
pass
@@ -27,17 +26,14 @@ class CommitObjectBuilder(ABC, Generic[T]):
def build_commit_object(self, root_commit_object: Base) -> None:
self.apply_relationships(self.converted.values(), root_commit_object)
def set_relationship(
self, app_id: Optional[str], *parent_info: PARENT_INFO
) -> None:
def set_relationship(self, app_id: Optional[str], *parent_info : PARENT_INFO) -> None:
if not app_id:
return
self._parent_infos[app_id] = parent_info
def apply_relationships(
self, to_add: Iterable[Base], root_commit_object: Base
) -> None:
def apply_relationships(self, to_add: Iterable[Base], root_commit_object: Base) -> None:
for c in to_add:
try:
self.apply_relationship(c, root_commit_object)
@@ -45,25 +41,20 @@ class CommitObjectBuilder(ABC, Generic[T]):
print(f"Failed to add object {type(c)} to commit object: {ex}")
def apply_relationship(self, current: Base, root_commit_object: Base):
if not current.applicationId:
raise Exception("Expected applicationId to have been set")
if not current.applicationId: raise Exception(f"Expected applicationId to have been set")
parents = self._parent_infos[current.applicationId]
for parent_id, prop_name in parents:
if not parent_id:
continue
for (parent_id, prop_name) in parents:
if not parent_id: continue
parent: Optional[Base]
if parent_id == ROOT:
parent = root_commit_object
else:
parent = (
self.converted[parent_id] if parent_id in self.converted else None
)
if not parent:
continue
parent = self.converted[parent_id] if parent_id in self.converted else None
if not parent: continue
try:
elements = get_detached_prop(parent, prop_name)
@@ -75,26 +66,18 @@ class CommitObjectBuilder(ABC, Generic[T]):
return
except Exception as ex:
# A parent was found, but it was invalid (Likely because of a type mismatch on a `elements` property)
print(
f"Failed to add object {type(current)} to a converted parent; {ex}"
)
raise Exception(
f"Could not find a valid parent for object of type {type(current)}. Checked {len(parents)} potential parent, and non were converted!"
)
print(f"Failed to add object {type(current)} to a converted parent; {ex}")
raise Exception(f"Could not find a valid parent for object of type {type(current)}. Checked {len(parents)} potential parent, and non were converted!")
def get_detached_prop(speckle_object: Base, prop_name: str) -> Optional[Any]:
detached_prop_name = get_detached_prop_name(speckle_object, prop_name)
return getattr(speckle_object, detached_prop_name, None)
def set_detached_prop(
speckle_object: Base, prop_name: str, value: Optional[Any]
) -> None:
def set_detached_prop(speckle_object: Base, prop_name: str, value: Optional[Any]) -> None:
detached_prop_name = get_detached_prop_name(speckle_object, prop_name)
setattr(speckle_object, detached_prop_name, value)
def get_detached_prop_name(speckle_object: Base, prop_name: str) -> str:
return prop_name if hasattr(speckle_object, prop_name) else f"@{prop_name}"
return prop_name if hasattr(speckle_object, prop_name) else f"@{prop_name}"
@@ -3,7 +3,7 @@ from typing import Any, Callable, Collection, Iterable, Iterator, List, Optional
from attrs import define
from typing_extensions import Protocol, final
from specklepy.objects.base import Base
from specklepy.objects import Base
class ITraversalRule(Protocol):
@@ -41,6 +41,7 @@ class TraversalContext:
@final
@define(slots=True, frozen=True)
class GraphTraversal:
_rules: List[ITraversalRule]
def traverse(self, root: Base) -> Iterator[TraversalContext]:
@@ -57,15 +58,15 @@ class GraphTraversal:
members_to_traverse = active_rule.get_members_to_traverse(current)
for child_prop in members_to_traverse:
try:
if child_prop in {"speckle_type", "units", "applicationId"}:
continue # debug: to avoid noisy exceptions, explicitly avoid checking ones we know will fail, this is not exhaustive
if child_prop in {"speckle_type", "units", "applicationId"}: continue #debug: to avoid noisy exceptions, explicitly avoid checking ones we know will fail, this is not exhaustive
if getattr(current, child_prop, None):
value = current[child_prop]
self._traverse_member_to_stack(stack, value, child_prop, head)
except KeyError:
self._traverse_member_to_stack(
stack, value, child_prop, head
)
except KeyError as ex:
# Unset application ids, and class variables like SpeckleType will throw when __getitem__ is called
pass
@staticmethod
def _traverse_member_to_stack(
stack: List[TraversalContext],
@@ -77,14 +78,10 @@ class GraphTraversal:
stack.append(TraversalContext(value, member_name, parent))
elif isinstance(value, list):
for obj in value:
GraphTraversal._traverse_member_to_stack(
stack, obj, member_name, parent
)
GraphTraversal._traverse_member_to_stack(stack, obj, member_name, parent)
elif isinstance(value, dict):
for obj in value.values():
GraphTraversal._traverse_member_to_stack(
stack, obj, member_name, parent
)
GraphTraversal._traverse_member_to_stack(stack, obj, member_name, parent)
@staticmethod
def traverse_member(value: Optional[Any]) -> Iterator[Base]:
@@ -99,6 +96,7 @@ class GraphTraversal:
for o in GraphTraversal.traverse_member(obj):
yield o
def _get_active_rule_or_default_rule(self, o: Base) -> ITraversalRule:
return self._get_active_rule(o) or _default_rule
@@ -122,4 +120,4 @@ class TraversalRule:
for condition in self._conditions:
if condition(o):
return True
return False
return False
+86 -14
View File
@@ -1,5 +1,4 @@
from typing import List, Optional
from typing import Any, List, Optional
from deprecated import deprecated
from specklepy.objects.geometry import Point, Vector
@@ -7,6 +6,7 @@ from specklepy.objects.geometry import Point, Vector
from .base import Base
OTHER = "Objects.Other."
OTHER_REVIT = OTHER + "Revit."
IDENTITY_TRANSFORM = [
1.0,
@@ -28,6 +28,21 @@ IDENTITY_TRANSFORM = [
]
class Material(Base, speckle_type=OTHER + "Material"):
"""Generic class for materials containing generic parameters."""
name: Optional[str] = None
class RevitMaterial(Material, speckle_type="Objects.Other.Revit." + "RevitMaterial"):
materialCategory: Optional[str] = None
materialClass: Optional[str] = None
shininess: Optional[int] = None
smoothness: Optional[int] = None
transparency: Optional[int] = None
parameters: Optional[Base] = None
class RenderMaterial(Base, speckle_type=OTHER + "RenderMaterial"):
name: Optional[str] = None
opacity: float = 1
@@ -37,16 +52,23 @@ class RenderMaterial(Base, speckle_type=OTHER + "RenderMaterial"):
emissive: int = -16777216 # black arbg
class RenderMaterialProxy(
Base,
speckle_type="Speckle.Core.Models.Proxies.RenderMaterialProxy",
):
class MaterialQuantity(Base, speckle_type=OTHER + "MaterialQuantity"):
material: Optional[Material] = None
volume: Optional[float] = None
area: Optional[float] = None
class DisplayStyle(Base, speckle_type=OTHER + "DisplayStyle"):
"""
Used to store render material to object relationships in root collections.
Minimal display style class.
Developed primarily for display styles in Rhino and AutoCAD.
Rhino object attributes uses OpenNURBS definition for linetypes and lineweights.
"""
objects: list[str]
value: RenderMaterial
name: Optional[str] = None
color: int = -2894893 # light gray arbg
linetype: Optional[str] = None
lineweight: float = 0
class Transform(
@@ -217,10 +239,60 @@ class Transform(
return cls(value=value)
class Collection(
Base,
speckle_type="Speckle.Core.Models.Collections.Collection",
detachable={"elements"},
class BlockDefinition(
Base, speckle_type=OTHER + "BlockDefinition", detachable={"geometry"}
):
name: Optional[str] = None
elements: Optional[List[Base]] = None
basePoint: Optional[Point] = None
geometry: Optional[List[Base]] = None
class Instance(
Base, speckle_type=OTHER + "Instance", detachable={"definition"}
):
transform: Optional[Transform] = None
definition: Optional[Base] = None
class BlockInstance(
Instance, speckle_type=OTHER + "BlockInstance", serialize_ignore={"blockDefinition"}
):
@property
@deprecated(version="2.13", reason="Use definition")
def blockDefinition(self) -> Optional[BlockDefinition]:
if isinstance(self.definition, BlockDefinition):
return self.definition
return None
@blockDefinition.setter
def blockDefinition(self, value: Optional[BlockDefinition]) -> None:
self.definition = value
class RevitInstance(Instance, speckle_type=OTHER_REVIT + "RevitInstance"):
level: Optional[Base] = None
facingFlipped: bool
handFlipped: bool
parameters: Optional[Base] = None
elementId: Optional[str]
# TODO: prob move this into a built elements module, but just trialling this for now
class RevitParameter(
Base, speckle_type="Objects.BuiltElements.Revit.Parameter"
):
name: Optional[str] = None
value: Any = None
applicationUnitType: Optional[str] = None # eg UnitType UT_Length
applicationUnit: Optional[str] = None # DisplayUnitType eg DUT_MILLIMITERS
applicationInternalName: Optional[
str
] = None # BuiltInParameterName or GUID for shared parameter
isShared: bool = False
isReadOnly: bool = False
isTypeParameter: bool = False
class Collection(
Base, speckle_type="Speckle.Core.Models.Collection", detachable={"elements"}
):
name: Optional[str] = None
collectionType: Optional[str] = None
elements: Optional[List[Base]] = None
@@ -0,0 +1,145 @@
"""Builtin Speckle object kit."""
from specklepy.objects.structural.analysis import (
Model,
ModelInfo,
ModelSettings,
ModelUnits,
)
from specklepy.objects.structural.axis import (
AxisType,
Axis
)
from specklepy.objects.structural.geometry import (
Element1D,
Element2D,
Element3D,
ElementType1D,
ElementType2D,
ElementType3D,
Node,
Restraint,
)
from specklepy.objects.structural.loading import (
ActionType,
BeamLoadType,
CombinationType,
FaceLoadType,
Load,
LoadAxisType,
LoadBeam,
LoadCase,
LoadCombinations,
LoadDirection,
LoadDirection2D,
LoadFace,
LoadGravity,
LoadNode,
LoadType,
)
from specklepy.objects.structural.materials import (
Concrete,
MaterialType,
Steel,
StructuralMaterial,
Timber,
)
from specklepy.objects.structural.properties import (
BaseReferencePoint,
MemberType,
Property,
Property1D,
Property2D,
Property3D,
PropertyDamper,
PropertyMass,
PropertySpring,
PropertyType2D,
PropertyType3D,
PropertyTypeDamper,
PropertyTypeSpring,
ReferenceSurface,
ReferenceSurfaceEnum,
SectionProfile,
ShapeType,
shapeType,
)
from specklepy.objects.structural.results import (
Result,
Result1D,
Result2D,
Result3D,
ResultGlobal,
ResultNode,
ResultSet1D,
ResultSet2D,
ResultSet3D,
ResultSetAll,
ResultSetNode,
)
__all__ = [
"Element1D",
"Element2D",
"Element3D",
"ElementType1D",
"ElementType2D",
"ElementType3D",
"AxisType",
"Axis",
"Node",
"Restraint",
"Load",
"LoadType",
"ActionType",
"BeamLoadType",
"FaceLoadType",
"LoadDirection",
"LoadDirection2D",
"LoadAxisType",
"CombinationType",
"LoadBeam",
"LoadCase",
"LoadCombinations",
"LoadFace",
"LoadGravity",
"LoadNode",
"Model",
"ModelInfo",
"ModelSettings",
"ModelUnits",
"MaterialType",
"Concrete",
"StructuralMaterial",
"Steel",
"Timber",
"Property",
"Property1D",
"Property2D",
"Property3D",
"PropertyDamper",
"PropertyMass",
"PropertySpring",
"SectionProfile",
"MemberType",
"BaseReferencePoint",
"ReferenceSurface",
"PropertyType2D",
"PropertyType3D",
"ShapeType",
"PropertyTypeSpring",
"PropertyTypeDamper",
"ReferenceSurfaceEnum",
"shapeType",
"Result",
"Result1D",
"ResultSet1D",
"Result2D",
"ResultSet2D",
"Result3D",
"ResultSet3D",
"ResultGlobal",
"ResultSetNode",
"ResultNode",
"ResultSetAll",
]
@@ -0,0 +1,49 @@
from typing import List, Optional
from specklepy.objects.base import Base
STRUCTURAL_ANALYSIS = "Objects.Structural.Analysis."
class ModelUnits(Base, speckle_type=STRUCTURAL_ANALYSIS + "ModelUnits"):
length: Optional[str] = None
sections: Optional[str] = None
displacements: Optional[str] = None
stress: Optional[str] = None
force: Optional[str] = None
mass: Optional[str] = None
time: Optional[str] = None
temperature: Optional[str] = None
velocity: Optional[str] = None
acceleration: Optional[str] = None
energy: Optional[str] = None
angle: Optional[str] = None
strain: Optional[str] = None
class ModelSettings(Base, speckle_type=STRUCTURAL_ANALYSIS + "ModelSettings"):
modelUnits: Optional[ModelUnits] = None
steelCode: Optional[str] = None
concreteCode: Optional[str] = None
coincidenceTolerance: float = 0.0
class ModelInfo(Base, speckle_type=STRUCTURAL_ANALYSIS + "ModelInfo"):
name: Optional[str] = None
description: Optional[str] = None
projectNumber: Optional[str] = None
projectName: Optional[str] = None
settings: Optional[ModelSettings] = None
initials: Optional[str] = None
application: Optional[str] = None
class Model(Base, speckle_type=STRUCTURAL_ANALYSIS + "Model"):
specs: Optional[ModelInfo] = None
nodes: Optional[List] = None
elements: Optional[List] = None
loads: Optional[List] = None
restraints: Optional[List] = None
properties: Optional[List] = None
materials: Optional[List] = None
layerDescription: Optional[str] = None
+17
View File
@@ -0,0 +1,17 @@
from typing import Optional
from enum import Enum
from specklepy.objects.base import Base
from specklepy.objects.geometry import Plane
class AxisType(int, Enum):
Cartesian = 0
Cylindrical = 1
Spherical = 2
class Axis(Base, speckle_type="Objects.Structural.Geometry.Axis"):
name: Optional[str] = None
axisType: Optional[AxisType] = None
plane: Optional[Plane] = None
@@ -0,0 +1,110 @@
from enum import Enum
from typing import List, Optional
from specklepy.objects.base import Base
from specklepy.objects.geometry import Line, Mesh, Plane, Point, Vector
from specklepy.objects.structural.axis import Axis
from specklepy.objects.structural.properties import (
Property1D,
Property2D,
Property3D,
PropertyDamper,
PropertyMass,
PropertySpring,
)
STRUCTURAL_GEOMETRY = "Objects.Structural.Geometry"
class ElementType1D(int, Enum):
Beam = 0
Brace = 1
Bar = 2
Column = 3
Rod = 4
Spring = 5
Tie = 6
Strut = 7
Link = 8
Damper = 9
Cable = 10
Spacer = 11
Other = 12
Null = 13
class ElementType2D(int, Enum):
Quad4 = 0
Quad8 = 1
Triangle3 = 2
Triangle6 = 3
class ElementType3D(int, Enum):
Brick8 = 0
Wedge6 = 1
Pyramid5 = 2
Tetra4 = 3
class Restraint(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Restraint"):
code: Optional[str] = None
stiffnessX: float = 0.0
stiffnessY: float = 0.0
stiffnessZ: float = 0.0
stiffnessXX: float = 0.0
stiffnessYY: float = 0.0
stiffnessZZ: float = 0.0
class Node(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Node"):
name: Optional[str] = None
basePoint: Optional[Point] = None
constraintAxis: Optional[Axis] = None
restraint: Optional[Restraint] = None
springProperty: Optional[PropertySpring] = None
massProperty: Optional[PropertyMass] = None
damperProperty: Optional[PropertyDamper] = None
class Element1D(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Element1D"):
name: Optional[str] = None
baseLine: Optional[Line] = None
property: Optional[Property1D] = None
type: Optional[ElementType1D] = None
end1Releases: Optional[Restraint] = None
end2Releases: Optional[Restraint] = None
end1Offset: Optional[Vector] = None
end2Offset: Optional[Vector] = None
orientationNode: Optional[Node] = None
orinetationAngle: float = 0.0
localAxis: Optional[Plane] = None
parent: Optional[Base] = None
end1Node: Optional[Node] = None
end2Node: Optional[Node] = None
topology: Optional[List] = None
displayMesh: Optional[Mesh] = None
class Element2D(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Element2D"):
name: Optional[str] = None
property: Optional[Property2D] = None
type: Optional[ElementType2D] = None
offset: float = 0.0
orientationAngle: float = 0.0
parent: Optional[Base] = None
topology: Optional[List] = None
displayMesh: Optional[Mesh] = None
class Element3D(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Element3D"):
name: Optional[str] = None
baseMesh: Optional[Mesh] = None
property: Optional[Property3D] = None
type: Optional[ElementType3D] = None
orientationAngle: float = 0.0
parent: Optional[Base] = None
topology: List
# class Storey needs ependency on built elements first
+137
View File
@@ -0,0 +1,137 @@
from enum import Enum
from typing import List, Optional
from specklepy.objects.base import Base
from specklepy.objects.geometry import Vector
from specklepy.objects.structural.axis import Axis
STRUCTURAL_LOADING = "Objects.Structural.Loading."
class LoadType(int, Enum):
none = 0
Dead = 1
SuperDead = 2
Soil = 3
Live = 4
LiveRoof = 5
ReducibleLive = 6
Wind = 7
Snow = 8
Rain = 9
Thermal = 10
Notional = 11
Prestress = 12
Equivalent = 13
Accidental = 14
SeismicRSA = 15
SeismicAccTorsion = 16
SeismicStatic = 17
Other = 18
class ActionType(int, Enum):
none = 0
Permanent = 1
Variable = 2
Accidental = 3
class BeamLoadType(int, Enum):
Point = 0
Uniform = 1
Linear = 2
Patch = 3
TriLinear = 4
class FaceLoadType(int, Enum):
Constant = 0
Variable = 1
Point = 2
class LoadDirection2D(int, Enum):
X = 0
Y = 1
Z = 2
class LoadDirection(int, Enum):
X = 0
Y = 1
Z = 2
XX = 3
YY = 4
ZZ = 5
class LoadAxisType(int, Enum):
Global = 0
Local = 1 # local element axes
DeformedLocal = (
2 # element local axis that is embedded in the element as it deforms
)
class CombinationType(int, Enum):
LinearAdd = 0
Envelope = 1
AbsoluteAdd = 2
SRSS = 3
RangeAdd = 4
class LoadCase(Base, speckle_type=STRUCTURAL_LOADING + "LoadCase"):
name: Optional[str] = None
loadType: Optional[LoadType] = None
group: Optional[str] = None
actionType: Optional[ActionType] = None
description: Optional[str] = None
class Load(Base, speckle_type=STRUCTURAL_LOADING + "Load"):
name: Optional[str] = None
loadCase: Optional[LoadCase] = None
class LoadBeam(Load, speckle_type=STRUCTURAL_LOADING + "LoadBeam"):
elements: Optional[List] = None
loadType: Optional[BeamLoadType] = None
direction: Optional[LoadDirection] = None
loadAxis: Optional[Axis] = None
loadAxisType: Optional[LoadAxisType] = None
isProjected: Optional[bool] = None
values: Optional[List] = None
positions: Optional[List] = None
class LoadCombinations(Base, speckle_type=STRUCTURAL_LOADING + "LoadCombination"):
name: Optional[str] = None
loadCases: List
loadFactors: List
combinationType: CombinationType
class LoadFace(Load, speckle_type=STRUCTURAL_LOADING + "LoadFace"):
elements: Optional[List] = None
loadType: Optional[FaceLoadType] = None
direction: Optional[LoadDirection2D] = None
loadAxis: Optional[Axis] = None
loadAxisType: Optional[LoadAxisType] = None
isProjected: Optional[bool] = None
values: Optional[List] = None
positions: Optional[List] = None
class LoadGravity(Load, speckle_type=STRUCTURAL_LOADING + "LoadGravity"):
elements: Optional[List] = None
nodes: Optional[List] = None
gravityFactors: Optional[Vector] = None
class LoadNode(Load, speckle_type=STRUCTURAL_LOADING + "LoadNode"):
nodes: Optional[List] = None
loadAxis: Optional[Axis] = None
direction: Optional[LoadDirection] = None
value: float = 0.0
@@ -0,0 +1,61 @@
from enum import Enum
from typing import Optional
from specklepy.objects.base import Base
STRUCTURAL_MATERIALS = "Objects.Structural.Materials"
class MaterialType(int, Enum):
Concrete = 0
Steel = 1
Timber = 2
Aluminium = 3
Masonry = 4
FRP = 5
Glass = 6
Fabric = 7
Rebar = 8
Tendon = 9
ColdFormed = 10
Other = 11
class StructuralMaterial(
Base, speckle_type=STRUCTURAL_MATERIALS + ".StructuralMaterial"
):
name: Optional[str] = None
grade: Optional[str] = None
materialType: Optional[MaterialType] = None
designCode: Optional[str] = None
codeYear: Optional[str] = None
strength: float = 0.0
elasticModulus: float = 0.0
poissonsRatio: float = 0.0
shearModulus: float = 0.0
density: float = 0.0
thermalExpansivity: float = 0.0
dampingRatio: float = 0.0
cost: float = 0.0
materialSafetyFactor: float = 0.0
class Concrete(StructuralMaterial):
compressiveStrength: float = 0.0
tensileStrength: float = 0.0
flexuralStrength: float = 0.0
maxCompressiveStrain: float = 0.0
maxTensileStrain: float = 0.0
maxAggregateSize: float = 0.0
lightweight: Optional[bool] = None
class Steel(StructuralMaterial, speckle_type=STRUCTURAL_MATERIALS + ".Steel"):
yieldStrength: float = 0.0
ultimateStrength: float = 0.0
maxStrain: float = 0.0
strainHardeningModulus: float = 0.0
class Timber(StructuralMaterial, speckle_type=STRUCTURAL_MATERIALS + ".Timber"):
species: Optional[str] = None
@@ -0,0 +1,212 @@
from enum import Enum
from typing import Optional
from specklepy.objects.base import Base
from specklepy.objects.structural.axis import Axis
from specklepy.objects.structural.materials import StructuralMaterial
STRUCTURAL_PROPERTY = "Objects.Structural.Properties"
class MemberType(int, Enum):
Beam = 0
Column = 1
Generic1D = 2
Slab = 3
Wall = 4
Generic2D = 5
VoidCutter1D = 6
VoidCutter2D = 7
class BaseReferencePoint(int, Enum):
Centroid = 0
TopLeft = 1
TopCentre = 2
TopRight = 3
MidLeft = 4
MidRight = 5
BotLeft = 6
BotCentre = 7
BotRight = 8
class ReferenceSurface(int, Enum):
Top = 0
Middle = 1
Bottom = 2
class PropertyType2D(int, Enum):
Stress = 0
Fabric = 1
Plate = 2
Shell = 3
Curved = 4
Wall = 5
Strain = 6
Axi = 7
Load = 8
class PropertyType3D(int, Enum):
Solid = 0
Infinite = 1
class ShapeType(int, Enum):
Rectangular = 0
Circular = 1
I = 2 # noqa: E741
Tee = 3
Angle = 4
Channel = 5
Perimeter = 6
Box = 7
Catalogue = 8
Explicit = 9
Undefined = 10
class PropertyTypeSpring(int, Enum):
Axial = 0
Torsional = 1
General = 2
Matrix = 3
TensionOnly = 4
CompressionOnly = 5
Connector = 6
LockUp = 7
Gap = 8
Friction = 9
class PropertyTypeDamper(int, Enum):
Axial = 0
Torsional = 1
General = 2
class Property(Base, speckle_type=STRUCTURAL_PROPERTY):
name: Optional[str] = None
class SectionProfile(
Base, speckle_type=STRUCTURAL_PROPERTY + ".Profiles.SectionProfile"
):
name: Optional[str] = None
shapeType: Optional[ShapeType] = None
area: float = 0.0
Iyy: float = 0.0
Izz: float = 0.0
J: float = 0.0
Ky: float = 0.0
weight: float = 0.0
class Property1D(Property, speckle_type=STRUCTURAL_PROPERTY + ".Property1D"):
memberType: Optional[MemberType] = None
material: Optional[StructuralMaterial] = None
profile: Optional[SectionProfile] = None
referencePoint: Optional[BaseReferencePoint] = None
offsetY: float = 0.0
offsetZ: float = 0.0
class Property2D(Property, speckle_type=STRUCTURAL_PROPERTY + ".Property2D"):
type: Optional[PropertyType2D] = None
thickness: float = 0.0
material: Optional[StructuralMaterial] = None
orientationAxis: Optional[Axis] = None
refSurface: Optional[ReferenceSurface] = None
zOffset: float = 0.0
modifierInPlane: float = 0.0
modifierBending: float = 0.0
modifierShear: float = 0.0
modifierVolume: float = 0.0
class Property3D(Property, speckle_type=STRUCTURAL_PROPERTY + ".Property3D"):
type: Optional[PropertyType3D] = None
material: Optional[StructuralMaterial] = None
orientationAxis: Optional[Axis] = None
class PropertyDamper(Property, speckle_type=STRUCTURAL_PROPERTY + ".PropertyDamper"):
damperType: Optional[PropertyTypeDamper] = None
dampingX: float = 0.0
dampingY: float = 0.0
dampingZ: float = 0.0
dampingXX: float = 0.0
dampingYY: float = 0.0
dampingZZ: float = 0.0
class PropertyMass(Property, speckle_type=STRUCTURAL_PROPERTY + ".PropertyMass"):
mass: float = 0.0
inertiaXX: float = 0.0
inertiaYY: float = 0.0
inertiaZZ: float = 0.0
inertiaXY: float = 0.0
inertiaYZ: float = 0.0
inertiaZX: float = 0.0
massModified: Optional[bool] = None
massModifierX: float = 0.0
massModifierY: float = 0.0
massModifierZ: float = 0.0
class PropertySpring(Property, speckle_type=STRUCTURAL_PROPERTY + ".PropertySpring"):
springType: Optional[PropertyTypeSpring] = None
springCurveX: float = 0.0
stiffnessX: float = 0.0
springCurveY: float = 0.0
stiffnessY: float = 0.0
springCurveZ: float = 0.0
stiffnessZ: float = 0.0
springCurveXX: float = 0.0
stiffnessXX: float = 0.0
springCurveYY: float = 0.0
stiffnessYY: float = 0.0
springCurveZZ: float = 0.0
stiffnessZZ: float = 0.0
dampingRatio: float = 0.0
dampingX: float = 0.0
dampingY: float = 0.0
dampingZ: float = 0.0
dampingXX: float = 0.0
dampingYY: float = 0.0
dampingZZ: float = 0.0
matrix: float = 0.0
postiveLockup: float = 0.0
frictionCoefficient: float = 0.0
class ReferenceSurfaceEnum(int, Enum):
Concrete = 0
Steel = 1
Timber = 2
Aluminium = 3
Masonry = 4
FRP = 5
Glass = 6
Fabric = 7
Rebar = 8
Tendon = 9
ColdFormed = 10
Other = 11
class shapeType(int, Enum):
Concrete = 0
Steel = 1
Timber = 2
Aluminium = 3
Masonry = 4
FRP = 5
Glass = 6
Fabric = 7
Rebar = 8
Tendon = 9
ColdFormed = 10
Other = 11
+172
View File
@@ -0,0 +1,172 @@
from typing import List, Optional
from specklepy.objects.base import Base
from specklepy.objects.structural.analysis import Model
from specklepy.objects.structural.geometry import Element1D, Element2D, Element3D, Node
STRUCTURAL_RESULTS = "Objects.Structural.Results."
class Result(Base, speckle_type=STRUCTURAL_RESULTS + "Result"):
resultCase: Optional[Base] = None
permutation: Optional[str] = None
description: Optional[str] = None
class ResultSet1D(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSet1D"):
results1D: List
class Result1D(Result, speckle_type=STRUCTURAL_RESULTS + "Result1D"):
element: Optional[Element1D] = None
position: Optional[float] = None
dispX: Optional[float] = None
dispY: Optional[float] = None
dispZ: Optional[float] = None
rotXX: Optional[float] = None
rotYY: Optional[float] = None
rotZZ: Optional[float] = None
forceX: Optional[float] = None
forceY: Optional[float] = None
forceZ: Optional[float] = None
momentXX: Optional[float] = None
momentYY: Optional[float] = None
momentZZ: Optional[float] = None
axialStress: Optional[float] = None
shearStressY: Optional[float] = None
shearStressZ: Optional[float] = None
bendingStressYPos: Optional[float] = None
bendingStressYNeg: Optional[float] = None
bendingStressZPos: Optional[float] = None
bendingStressZNeg: Optional[float] = None
combinedStressMax: Optional[float] = None
combinedStressMin: Optional[float] = None
class ResultSet2D(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSet2D"):
results2D: List
class Result2D(Result, speckle_type=STRUCTURAL_RESULTS + "Result2D"):
element: Optional[Element2D] = None
position: List
dispX: Optional[float] = None
dispY: Optional[float] = None
dispZ: Optional[float] = None
forceXX: Optional[float] = None
forceYY: Optional[float] = None
forceXY: Optional[float] = None
momentXX: Optional[float] = None
momentYY: Optional[float] = None
momentXY: Optional[float] = None
shearX: Optional[float] = None
shearY: Optional[float] = None
stressTopXX: Optional[float] = None
stressTopYY: Optional[float] = None
stressTopZZ: Optional[float] = None
stressTopXY: Optional[float] = None
stressTopYZ: Optional[float] = None
stressTopZX: Optional[float] = None
stressMidXX: Optional[float] = None
stressMidYY: Optional[float] = None
stressMidZZ: Optional[float] = None
stressMidXY: Optional[float] = None
stressMidYZ: Optional[float] = None
stressMidZX: Optional[float] = None
stressBotXX: Optional[float] = None
stressBotYY: Optional[float] = None
stressBotZZ: Optional[float] = None
stressBotXY: Optional[float] = None
stressBotYZ: Optional[float] = None
stressBotZX: Optional[float] = None
class ResultSet3D(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSet3D"):
results3D: List
class Result3D(Result, speckle_type=STRUCTURAL_RESULTS + "Result3D"):
element: Optional[Element3D] = None
position: List
dispX: Optional[float] = None
dispY: Optional[float] = None
dispZ: Optional[float] = None
stressXX: Optional[float] = None
stressYY: Optional[float] = None
stressZZ: Optional[float] = None
stressXY: Optional[float] = None
stressYZ: Optional[float] = None
stressZX: Optional[float] = None
class ResultGlobal(Result, speckle_type=STRUCTURAL_RESULTS + "ResultGlobal"):
model: Optional[Model] = None
loadX: Optional[float] = None
loadY: Optional[float] = None
loadZ: Optional[float] = None
loadXX: Optional[float] = None
loadYY: Optional[float] = None
loadZZ: Optional[float] = None
reactionX: Optional[float] = None
reactionY: Optional[float] = None
reactionZ: Optional[float] = None
reactionXX: Optional[float] = None
reactionYY: Optional[float] = None
reactionZZ: Optional[float] = None
mode: Optional[float] = None
frequency: Optional[float] = None
loadFactor: Optional[float] = None
modalStiffness: Optional[float] = None
modalGeoStiffness: Optional[float] = None
effMassX: Optional[float] = None
effMassY: Optional[float] = None
effMassZ: Optional[float] = None
effMassXX: Optional[float] = None
effMassYY: Optional[float] = None
effMassZZ: Optional[float] = None
class ResultSetNode(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSetNode"):
resultsNode: List
class ResultNode(Result, speckle_type=STRUCTURAL_RESULTS + " ResultNode"):
node: Optional[Node] = None
dispX: Optional[float] = None
dispY: Optional[float] = None
dispZ: Optional[float] = None
rotXX: Optional[float] = None
rotYY: Optional[float] = None
rotZZ: Optional[float] = None
reactionX: Optional[float] = None
reactionY: Optional[float] = None
reactionZ: Optional[float] = None
reactionXX: Optional[float] = None
reactionYY: Optional[float] = None
reactionZZ: Optional[float] = None
constraintX: Optional[float] = None
constraintY: Optional[float] = None
constraintZ: Optional[float] = None
constraintXX: Optional[float] = None
constraintYY: Optional[float] = None
constraintZZ: Optional[float] = None
velX: Optional[float] = None
velY: Optional[float] = None
velZ: Optional[float] = None
velXX: Optional[float] = None
velYY: Optional[float] = None
velZZ: Optional[float] = None
accX: Optional[float] = None
accY: Optional[float] = None
accZ: Optional[float] = None
accXX: Optional[float] = None
accYY: Optional[float] = None
accZZ: Optional[float] = None
class ResultSetAll(Base, speckle_type=None):
resultSet1D: Optional[ResultSet1D] = None
resultSet2D: Optional[ResultSet2D] = None
resultSet3D: Optional[ResultSet3D] = None
resultsGlobal: Optional[ResultGlobal] = None
resultsNode: Optional[ResultSetNode] = None
+2 -4
View File
@@ -106,9 +106,7 @@ def get_encoding_from_units(unit: Union[Units, str, None]):
def get_scale_factor_from_string(fromUnits: str, toUnits: str) -> float:
"""Returns a scalar to convert distance values from one unit system to another"""
return get_scale_factor(
get_units_from_string(fromUnits), get_units_from_string(toUnits)
)
return get_scale_factor(get_units_from_string(fromUnits), get_units_from_string(toUnits))
def get_scale_factor(fromUnits: Units, toUnits: Units) -> float:
@@ -121,4 +119,4 @@ def get_scale_factor_to_meters(fromUnits: Units) -> float:
if fromUnits not in UNIT_SCALE:
raise ValueError(f"Invalid units provided: {fromUnits}")
return UNIT_SCALE[fromUnits]
return UNIT_SCALE[fromUnits]
+1 -1
View File
@@ -1,4 +1,4 @@
from typing import Dict, List
from typing import Any, Dict, List
from specklepy.transports.abstract_transport import AbstractTransport
@@ -137,7 +137,7 @@ class BatchSender(object):
raise SpeckleException(
message=(
"Could not save the object to the server - status code"
f" {r.status_code} ({r.text[:1000]})"
f" {r.status_code} ({r.text[:1000]})"
)
)
except json.JSONDecodeError as error:
+9 -13
View File
@@ -1,5 +1,5 @@
import json
from typing import Dict, List, Optional
from typing import Any, Dict, List, Optional
from warnings import warn
import requests
@@ -73,7 +73,7 @@ class ServerTransport(AbstractTransport):
warn(
SpeckleWarning(
"Unauthenticated Speckle Client provided to Server Transport"
f" for {url}. Receiving from private streams will fail."
f" for {self.url}. Receiving from private streams will fail."
)
)
else:
@@ -84,18 +84,14 @@ class ServerTransport(AbstractTransport):
self.stream_id = stream_id
self.url = url
self.session = requests.Session()
self._batch_sender = BatchSender(
self.url, self.stream_id, self.account.token, max_batch_size_mb=1
)
if self.account is not None:
self._batch_sender = BatchSender(
self.url, self.stream_id, self.account.token, max_batch_size_mb=1
)
self.session.headers.update(
{
"Authorization": f"Bearer {self.account.token}",
"Accept": "text/plain",
}
)
self.session = requests.Session()
self.session.headers.update(
{"Authorization": f"Bearer {self.account.token}", "Accept": "text/plain"}
)
@property
def name(self) -> str:

Some files were not shown because too many files have changed in this diff Show More