Compare commits

..

68 Commits

Author SHA1 Message Date
izzy lyseggen ee55680b03 feat(objects): add and test Mesh.create() (#161) 2022-02-08 16:56:33 +00:00
Reynold Chan 0728239915 Merge pull request #141 from specklesystems/structural/objects
Python Structural Objects Classes
2022-01-31 08:49:29 -05:00
izzy lyseggen 77016e6f0b chore(dev): update py and fix EOL errors (#157)
* chore(dev): fix eol probs on win

* chore(dev): bump py to 3.9
2022-01-24 15:17:23 +00:00
Antoine Dao ce39aa5101 chore(dev): add devcontainer configuration (#121)
This makes it easier to develop and run tests against an environment that has the right python virtualenv + a local speckle server + postgres + redis instance
2022-01-24 10:55:39 +00:00
izzy lyseggen f32196ce1b feat(client/ops): handle invalid token errors (#156)
* feat(client): validate token in `authenticate`

* feat(server transport): catch invalid token

* test(client/ops): error handling of invalid tokens

* feat(client): warning rather than exception
2022-01-21 16:32:33 +00:00
izzylys 6b24e187a5 fix(wrapper): quick acct field fix 2022-01-12 11:33:01 -08:00
izzy lyseggen 129d25df0e Merge pull request #154 from specklesystems/izzy/ujson-fix
fix(serializer): ujson big int issue
2022-01-10 19:34:14 +00:00
izzy lyseggen fa31bd0223 Merge pull request #153 from specklesystems/izzy/streamwrapper-alignment
feat(wrapper): make acct/client private
2022-01-10 19:33:33 +00:00
izzylys 21209b384d feat(wrapper): make acct/client private
closes #139
realised that this doesn't really apply to py since acct is set
automatically by looking for the right server url, but have made this
more explicit by making the acct and client "private"
2022-01-10 11:30:39 -08:00
izzylys 1a9c95871f test(serializer): big int fix 2022-01-10 11:19:11 -08:00
izzylys dc4d583121 fix(serializer): fall back to json lib for big int 2022-01-10 11:19:05 -08:00
izzy lyseggen ed39f0288f Merge pull request #151 from specklesystems/cristi/surface_client_error
surface server validation errors
2022-01-03 16:11:29 +00:00
cristi8 3830706eb1 surface server validation errors 2022-01-02 11:18:56 +02:00
izzy lyseggen f7ae62ade2 Merge pull request #149 from specklesystems/izzy/null-units-hotfix
fix(units): warn and don't set for invalid args
2021-12-22 09:07:40 +00:00
izzy lyseggen 38ffbc27b7 test(units): goddamnit codecov 2021-12-22 09:06:28 +00:00
izzy lyseggen 8cebccf250 fix(units): warn and don't set for invalid args 2021-12-22 09:00:39 +00:00
izzy lyseggen 17aac0b552 Merge pull request #148 from specklesystems/izzy/allow-nulls
feat(serialisation): allow null values
2021-12-16 16:59:51 +00:00
izzy lyseggen c281a329a4 fix(serialisation): nulls & things ^^ 2021-12-16 16:57:36 +00:00
izzy lyseggen ca472716db feat(serialisation): allow null values 2021-12-16 16:41:09 +00:00
izzy lyseggen af50afe3ff Merge pull request #144 from specklesystems/izzy/transforms-rework
feat(objects): transforms and blocks!
2021-12-13 12:03:50 +00:00
izzy lyseggen b6493df77f test(transform): serialisation and vector transform tests 2021-12-13 11:48:05 +00:00
izzy lyseggen 59d3c8c3ea feat(objects): transform vectors & ignore fields 2021-12-13 11:47:49 +00:00
izzy lyseggen 4e3405f1fb fix(base): props with no setter 2021-12-13 11:47:10 +00:00
Reynold Chan 3772c10b31 Create results.py 2021-12-12 13:30:50 -05:00
Reynold Chan 242be2fa60 fixed per izzy's suggestion for naming convention + circular dependency 2021-12-10 18:08:41 -05:00
izzy lyseggen 49eabdd712 test(objects): transform create w malformed input 2021-12-10 18:34:40 +00:00
izzy lyseggen 96a31f0678 test(objects): transform methods 2021-12-10 18:29:35 +00:00
izzy lyseggen 91506b0b20 feat(objects): transforms and blocks! 2021-12-10 17:52:25 +00:00
izzy lyseggen b0de9e31b5 Merge pull request #143 from specklesystems/izzy/commits-hotfix
fix(client): add `branchName` to commits query
2021-12-10 11:50:24 +00:00
izzy lyseggen 2075783134 feat(models): add branchName to commit repr 2021-12-10 11:44:07 +00:00
izzy lyseggen 071f2449c3 fix(client): oopsie missing field in commit.list
thanks @mortenengen for the spot!
closes #142
2021-12-10 11:40:03 +00:00
izzy lyseggen ffa4f29200 fix(structural): correct import path for Axis 2021-12-10 11:18:24 +00:00
izzy lyseggen 40a691b098 style(structural): formatting pass
@Reynold-Chan please use Black for code formatting to keep the style
consistent with the rest of the repo. you can set your editor to
automatically format on save
2021-12-10 11:16:38 +00:00
Reynold Chan 487ce3aeb4 structural enums for geometry 2021-12-09 08:25:34 -05:00
Reynold Chan 6c0f10ae45 loading + analysis structural python + enums 2021-12-09 03:43:18 -05:00
Reynold Chan 436b26c91c structural geometries python classes 2021-12-08 08:47:22 -05:00
Reynold Chan f7bac26aed finished structural properties 2021-12-07 21:37:55 -05:00
Reynold Chan a31c049b51 materials done 2021-12-06 14:02:52 -05:00
izzy lyseggen a419664461 Merge pull request #140 from specklesystems/izzy/bump-deps
chore: update deps
2021-11-30 11:52:09 +00:00
izzy lyseggen 4a0c07009b chore: update deps 2021-11-30 11:50:34 +00:00
Gergő Jedlicska 682bcbfa9f Merge pull request #138 from specklesystems/gergo/ci_test_report
add codecov badge
2021-11-24 18:56:11 +01:00
Gergő Jedlicska ccf284e8fa add codecov badge 2021-11-24 18:53:44 +01:00
Gergő Jedlicska 23102a28ff Merge pull request #137 from specklesystems/gergo/ci_test_report
add circle_ci reporting
2021-11-24 18:46:25 +01:00
Gergő Jedlicska 5475edb253 coverage report to xml 2021-11-24 18:28:36 +01:00
Gergő Jedlicska c52f80c1ef add codecov upload 2021-11-24 18:21:39 +01:00
Gergő Jedlicska 21eecfa24c add circle_ci reporting 2021-11-24 16:47:48 +01:00
izzy lyseggen 5dde1bfcf1 Merge pull request #136 from specklesystems/izzy/stream-wrapper-fix
fix(wrapper): fix for nested branches
2021-11-17 16:12:42 +00:00
izzy lyseggen 82c9d874c9 test(branches): server now returns main first 2021-11-17 16:11:22 +00:00
izzy lyseggen 9acf2c8a92 fix(wrapper): fix for nested branches 2021-11-17 15:59:51 +00:00
Gergő Jedlicska 95012e60c1 Merge pull request #132 from specklesystems/commit-recieved
Commit recieved
2021-10-29 11:01:21 +02:00
Gergő Jedlicska 19b6500bbd Merge branch 'main' of github.com:specklesystems/specklepy into commit-recieved 2021-10-29 10:53:28 +02:00
Gergő Jedlicska 47a06e4630 version bump 2021-10-29 10:49:36 +02:00
Alan Rynne e5a8b40bb2 Merge pull request #131 from specklesystems/commit-recieved
commit recieved service implementation
2021-10-29 10:48:55 +02:00
Gergő Jedlicska 219456f5f8 circleci py310 is not working 2021-10-27 20:28:01 +02:00
Gergő Jedlicska d1544ae89f Merge branch 'commit-recieved' of github.com:specklesystems/specklepy into commit-recieved 2021-10-27 19:20:15 +02:00
Gergő Jedlicska 8f7d4b2ca7 add 3.10 as target 2021-10-27 19:20:04 +02:00
Gergő Jedlicska a7d31d4983 Delete stream_copy.py 2021-10-27 19:18:45 +02:00
Gergő Jedlicska a89b12a02c remove receive server test 2021-10-27 19:16:08 +02:00
Gergő Jedlicska 15ae68f5d7 commit received implementation 2021-10-27 14:13:49 +02:00
izzy lyseggen 0709cd99b5 Merge pull request #129 from specklesystems/izzy/token-auth
feat(wrapper/transport): token auth
2021-10-22 11:21:23 +02:00
izzy lyseggen faf06f7141 fix(server): set transport url 2021-10-21 13:12:19 +01:00
izzy lyseggen b54e09f811 test: server transport & wrapper token auth 2021-10-21 12:20:09 +01:00
izzy lyseggen 55b7e0d732 feat(wrapper): token auth for client and transport 2021-10-21 12:19:49 +01:00
izzy lyseggen 45c922679b feat(server): allow construction with token & url 2021-10-21 12:19:19 +01:00
izzy lyseggen b1c149382a Merge pull request #128 from specklesystems/izzy/cereal-fixes
fix(deserialisation): type check bug and brep encoding hotfix
2021-10-14 17:09:27 +02:00
izzy lyseggen 393e98c8c2 fix(encoding): add none unit type 2021-10-14 16:07:34 +01:00
izzy lyseggen 8376329cbb fix(base): type check error with optional generics
reported by rob on the forum:
https://speckle.community/t/issue-with-type-checking-in-pyhton/1861
2021-10-14 15:41:30 +01:00
izzy lyseggen 1567fe9e68 fix(breps): temp hotfix for curve encoding fail
addresses 🥒 Bug with brep receiving (curve encoding) #127
2021-10-14 15:38:51 +01:00
40 changed files with 2378 additions and 455 deletions
+13 -4
View File
@@ -2,14 +2,15 @@ version: 2.1
orbs:
python: circleci/python@1.3.2
codecov: codecov/codecov@3.2.2
jobs:
test:
docker:
- image: "cimg/python:<<parameters.tag>>"
- image: "circleci/node:12"
- image: "circleci/redis:6"
- image: "circleci/postgres:12"
- image: 'cimg/node:14.18'
- image: 'circleci/redis:6'
- image: 'cimg/postgres:12.8'
environment:
POSTGRES_DB: speckle2_test
POSTGRES_PASSWORD: speckle
@@ -38,7 +39,15 @@ jobs:
name: upgrade pip
- python/install-packages:
pkg-manager: poetry
- run: poetry run pytest
- run: poetry run pytest --cov --cov-report xml:reports/coverage.xml --junitxml=reports/test-results.xml
- store_test_results:
path: reports
- store_artifacts:
path: reports
- codecov/upload
deploy:
docker:
+27
View File
@@ -0,0 +1,27 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.191.1/containers/python-3/.devcontainer/base.Dockerfile
# [Choice] Python version: 3, 3.9, 3.8, 3.7, 3.6
ARG VARIANT="3.9"
FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT}
# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10
ARG NODE_VERSION="none"
RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi
# [Optional] If your pip requirements rarely change, uncomment this section to add them to the image.
# COPY requirements.txt /tmp/pip-tmp/
# RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \
# && rm -rf /tmp/pip-tmp
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>
# [Optional] Uncomment this line to install global node packages.
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1
USER vscode
RUN curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -
ENV PATH=$PATH:$HOME/.poetry/env
+52
View File
@@ -0,0 +1,52 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.191.1/containers/python-3
{
"name": "Python 3",
// "build": {
// "dockerfile": "Dockerfile",
// "context": "..",
// "args": {
// // Update 'VARIANT' to pick a Python version: 3, 3.6, 3.7, 3.8, 3.9
// "VARIANT": "3.6",
// // Options
// "NODE_VERSION": "lts/*"
// }
// },
"dockerComposeFile": "./docker-compose.yaml",
"service": "specklepy",
"workspaceFolder": "/workspaces/specklepy",
"shutdownAction": "stopCompose",
// Set *default* container specific settings.json values on container create.
"settings": {
"python.pythonPath": "/usr/local/bin/python",
"python.languageServer": "Pylance",
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8",
"python.formatting.blackPath": "/usr/local/py-utils/bin/black",
"python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf",
"python.linting.banditPath": "/usr/local/py-utils/bin/bandit",
"python.linting.flake8Path": "/usr/local/py-utils/bin/flake8",
"python.linting.mypyPath": "/usr/local/py-utils/bin/mypy",
"python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle",
"python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle",
"python.linting.pylintPath": "/usr/local/py-utils/bin/pylint",
"python.testing.pytestArgs": [
"tests/",
"-s"
],
"python.testing.pytestEnabled": true,
"editor.formatOnSave": true,
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance"
],
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
"postCreateCommand": "poetry config virtualenvs.create false && poetry install",
// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"
}
+49
View File
@@ -0,0 +1,49 @@
version: "3.3" # optional since v1.27.0
services:
postgres:
image: circleci/postgres:12
environment:
POSTGRES_DB: speckle2_test
POSTGRES_PASSWORD: speckle
POSTGRES_USER: speckle
# ports:
# - "5432:5432"
network_mode: host
redis:
image: circleci/redis:6
# ports:
# - "6379:6379"
network_mode: host
speckle-server:
image: speckle/speckle-server
command: ["bash", "-c", "/wait && node bin/www"]
environment:
POSTGRES_URL: "localhost"
POSTGRES_USER: "speckle"
POSTGRES_PASSWORD: "speckle"
POSTGRES_DB: "speckle2_test"
REDIS_URL: "redis://localhost"
SESSION_SECRET: "keyboard cat"
STRATEGY_LOCAL: "true"
CANONICAL_URL: "http://localhost:3000"
WAIT_HOSTS: localhost:5432, localhost:6379
# ports:
# - "3000:3000"
network_mode: host
specklepy:
build:
dockerfile: Dockerfile
context: .
args:
VARIANT: 3.9
NODE_VERSION: lts/*
volumes:
# Mounts the project folder to '/workspace'. While this file is in .devcontainer,
# mounts are relative to the first file in the list, which is a level up.
- ..:/workspaces/specklepy:cached
# Overrides default command so things don't shut down after the process ends.
command: /bin/sh -c "while sleep 1000; do :; done"
network_mode: host
# networks:
# default:
+3
View File
@@ -0,0 +1,3 @@
* text=auto eol=lf
*.{cmd,[cC][mM][dD]} text eol=crlf
*.{bat,[bB][aA][tT]} text eol=crlf
+4
View File
@@ -1,3 +1,7 @@
.tool-versions
.envrc
reports/
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
+3 -1
View File
@@ -8,7 +8,9 @@
<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> </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
Generated
+531 -281
View File
File diff suppressed because it is too large Load Diff
+5 -4
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "specklepy"
version = "2.1.0"
version = "2.4.0"
description = "The Python SDK for Speckle 2.0"
readme = "README.md"
authors = ["Speckle Systems <devops@speckle.systems>"]
@@ -12,16 +12,17 @@ homepage = "https://speckle.systems/"
[tool.poetry.dependencies]
python = "^3.6.5"
pydantic = "^1.7.3"
pydantic = "^1.8.2"
appdirs = "^1.4.4"
gql = {version = ">=3.0.0a6", extras = ["all"], allow-prereleases = true}
ujson = "^4.1.0"
gql = {version = ">=3.0.0b1", extras = ["all"], allow-prereleases = true}
ujson = "^4.3.0"
[tool.poetry.dev-dependencies]
black = "^20.8b1"
isort = "^5.7.0"
pytest = "^6.2.2"
pytest-ordering = "^0.6"
pytest-cov = "^3.0.0"
[tool.black]
+16 -4
View File
@@ -1,6 +1,10 @@
import re
from gql.client import SyncClientSession
from specklepy.logging.exceptions import SpeckleException
from warnings import warn
from specklepy.logging.exceptions import (
GraphQLException,
SpeckleException,
SpeckleWarning,
)
from typing import Dict
from specklepy.api import resources
@@ -14,9 +18,8 @@ from specklepy.api.resources import (
subscriptions,
)
from specklepy.api.models import ServerInfo
from gql import Client, gql
from gql import Client
from gql.transport.requests import RequestsHTTPTransport
from gql.transport.aiohttp import AIOHTTPTransport
from gql.transport.websockets import WebsocketsTransport
@@ -77,6 +80,8 @@ class SpeckleClient:
# Check compatibility with the server
try:
serverInfo = self.server.get()
if isinstance(serverInfo, Exception):
raise serverInfo
if not isinstance(serverInfo, ServerInfo):
raise Exception("Couldn't get ServerInfo")
except Exception as ex:
@@ -111,6 +116,13 @@ class SpeckleClient:
self._init_resources()
if isinstance(self.user.get(), GraphQLException):
warn(
SpeckleWarning(
f"Invalid token - could not authenticate Speckle Client for server {self.url}"
)
)
def execute_query(self, query: str) -> Dict:
return self.httpclient.execute(query)
+65 -21
View File
@@ -89,6 +89,28 @@ def get_default_account(base_path: str = None) -> Account:
class StreamWrapper:
"""
The `StreamWrapper` gives you some handy helpers to deal with urls and get authenticated clients and transports.
Construct a `StreamWrapper` with a stream, branch, commit, or object URL. The corresponding ids will be stored
in the wrapper. If you have local accounts on the machine, you can use the `get_account` and `get_client` methods
to get a local account for the server. You can also pass a token into `get_client` if you don't have a corresponding
local account for the server.
```py
from specklepy.api.credentials import StreamWrapper
# provide any stream, branch, commit, object, or globals url
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()
# get an authenticated ServerTransport if you have a local account for the server
transport = wrapper.get_transport()
```
"""
stream_url: str = None
use_ssl: bool = True
host: str = None
@@ -96,8 +118,8 @@ class StreamWrapper:
commit_id: str = None
object_id: str = None
branch_name: str = None
client: SpeckleClient = None
account: Account = None
_client: SpeckleClient = None
_account: Account = None
def __repr__(self):
return f"StreamWrapper( server: {self.host}, stream_id: {self.stream_id}, type: {self.type} )"
@@ -122,9 +144,9 @@ class StreamWrapper:
parsed = urlparse(url)
self.host = parsed.netloc
self.use_ssl = parsed.scheme == "https"
segments = parsed.path.strip("/").split("/")
segments = parsed.path.strip("/").split("/", 3)
if not segments or len(segments) > 4 or len(segments) < 2:
if not segments or len(segments) < 2:
raise SpeckleException(
f"Cannot parse {url} into a stream wrapper class - invalid URL provided."
)
@@ -154,33 +176,55 @@ class StreamWrapper:
)
def get_account(self) -> Account:
if self.account:
return self.account
"""
Gets an account object for this server from the local accounts db (added via Speckle Manager or a json file)
"""
if self._account:
return self._account
self.account = next(
self._account = next(
(a for a in get_local_accounts() if self.host in a.serverInfo.url),
None,
)
return self.account
return self._account
def get_client(self) -> SpeckleClient:
if self.client:
return self.client
def get_client(self, token: str = None) -> SpeckleClient:
"""
Gets an authenticated client for this server. You may provide a token if there aren't any local accounts on this
machine. If no account is found and no token is provided, an unauthenticated client is returned.
if not self.account:
Arguments:
token {str} -- optional token if no local account is available (defaults to None)
Returns:
SpeckleClient -- authenticated with a corresponding local account or the provided token
"""
if self._client and token is None:
return self._client
if not self._account:
self.get_account()
self.client = SpeckleClient(host=self.host, use_ssl=self.use_ssl)
if not self._client:
self._client = SpeckleClient(host=self.host, use_ssl=self.use_ssl)
if self.account is None:
if self._account is None and token is None:
warn(f"No local account found for server {self.host}", SpeckleWarning)
return self.client
return self._client
self.client.authenticate(self.account.token)
return self.client
self._client.authenticate(self._account.token if self._account else token)
def get_transport(self) -> ServerTransport:
if not self.client:
self.get_client()
return ServerTransport(self.client, self.stream_id)
return self._client
def get_transport(self, token: str = None) -> ServerTransport:
"""
Gets a server transport for this stream using an authenticated client. If there is no local account for this
server and the client was not authenticated with a token, this will throw an exception.
Returns:
ServerTransport -- constructed for this stream with a pre-authenticated client
"""
if not self._client or not self._client.me:
self.get_client(token)
return ServerTransport(self.stream_id, self._client)
+1 -1
View File
@@ -30,7 +30,7 @@ class Commit(BaseModel):
parents: Optional[List[str]]
def __repr__(self) -> str:
return f"Commit( id: {self.id}, message: {self.message}, referencedObject: {self.referencedObject}, authorName: {self.authorName}, createdAt: {self.createdAt} )"
return f"Commit( id: {self.id}, message: {self.message}, referencedObject: {self.referencedObject}, authorName: {self.authorName}, branchName: {self.branchName}, createdAt: {self.createdAt} )"
def __str__(self) -> str:
return self.__repr__()
+39 -1
View File
@@ -34,8 +34,8 @@ class Resource(ResourceBase):
stream(id: $stream_id) {
commit(id: $commit_id) {
id
referencedObject
message
referencedObject
authorId
authorName
authorAvatar
@@ -79,6 +79,7 @@ class Resource(ResourceBase):
authorId
authorName
authorAvatar
branchName
createdAt
sourceApplication
totalChildrenCount
@@ -185,3 +186,40 @@ class Resource(ResourceBase):
return self.make_request(
query=query, params=params, return_type="commitDelete", parse_response=False
)
def received(
self,
stream_id: str,
commit_id: str,
source_application: str = "python",
message: Optional[str] = None,
) -> bool:
"""
Mark a commit object a received by the source application.
"""
query = gql(
"""
mutation CommitReceive($receivedInput:CommitReceivedInput!){
commitReceive(input:$receivedInput)
}
"""
)
params = {
"receivedInput": {
"sourceApplication": source_application,
"streamId": stream_id,
"commitId": commit_id,
"message": "message",
}
}
try:
return self.make_request(
query=query,
params=params,
return_type="commitReceive",
parse_response=False,
)
except Exception as ex:
print(ex.with_traceback)
return False
+1 -1
View File
@@ -32,4 +32,4 @@ class GraphQLException(SpeckleException):
class SpeckleWarning(Warning):
def __init__(self, *args: object) -> None:
super().__init__(*args)
super().__init__(*args)
+1 -1
View File
@@ -122,4 +122,4 @@ class MetricsTracker(metaclass=Singleton):
except Exception as ex:
LOG.error("Error sending metrics request: " + str(ex))
self.queue.task_done()
self.queue.task_done()
+40 -22
View File
@@ -1,6 +1,15 @@
import typing
from typing import (Any, Callable, ClassVar, Dict, List, Optional, Set, Type,
get_type_hints)
from typing import (
Any,
Callable,
ClassVar,
Dict,
List,
Optional,
Set,
Type,
get_type_hints,
)
from warnings import warn
from specklepy.logging.exceptions import SpeckleException
@@ -118,14 +127,12 @@ class _RegisteringBase:
except Exception:
cls._attr_types = getattr(cls, "__annotations__", {})
if chunkable:
chunkable = {k: v for k, v in chunkable.items()
if isinstance(v, int)}
chunkable = {k: v for k, v in chunkable.items() if isinstance(v, int)}
cls._chunkable = dict(cls._chunkable, **chunkable)
if detachable:
cls._detachable = cls._detachable.union(detachable)
if serialize_ignore:
cls._serialize_ignore = cls._serialize_ignore.union(
serialize_ignore)
cls._serialize_ignore = cls._serialize_ignore.union(serialize_ignore)
super().__init_subclass__(**kwargs)
@@ -200,7 +207,7 @@ class Base(_RegisteringBase):
try:
attr.__set__(self, value)
except AttributeError:
pass # the prop probably doesn't have a setter
return # the prop probably doesn't have a setter
super().__setattr__(name, value)
@classmethod
@@ -215,15 +222,13 @@ class Base(_RegisteringBase):
try:
cls._attr_types = get_type_hints(cls)
except Exception as e:
warn(
f"Could not update forward refs for class {cls.__name__}: {e}")
warn(f"Could not update forward refs for class {cls.__name__}: {e}")
@classmethod
def validate_prop_name(cls, name: str) -> None:
"""Validator for dynamic attribute names."""
if name in {"", "@"}:
raise ValueError(
"Invalid Name: Base member names cannot be empty strings")
raise ValueError("Invalid Name: Base member names cannot be empty strings")
if name.startswith("@@"):
raise ValueError(
"Invalid Name: Base member names cannot start with more than one '@'",
@@ -247,9 +252,17 @@ class Base(_RegisteringBase):
if t is None:
return value
if value is None:
return None
if t.__module__ == "typing":
origin = getattr(t, "__origin__")
t = t.__args__ if origin is typing.Union else origin
t = (
tuple(getattr(sub_t, "__origin__", sub_t) for sub_t in t.__args__)
if origin is typing.Union
else origin
)
if not isinstance(t, (type, tuple)):
warn(
f"Unrecognised type '{t}' provided for attribute '{name}'. Type will not been validated."
@@ -260,13 +273,16 @@ class Base(_RegisteringBase):
# to be friendly, we'll parse ints and strs into floats, but not the other way around
# (to avoid unexpected rounding)
if t is float and isinstance(value, (int, str, float)):
try:
if isinstance(t, tuple):
t = t[0]
try:
if t is float:
return float(value)
except ValueError:
pass
if t is str and value is not None:
return str(value)
if t is str and value:
return str(value)
except ValueError:
pass
raise SpeckleException(
f"Cannot set '{self.__class__.__name__}.{name}': it expects type '{t.__name__}', but received type '{type(value).__name__}'"
@@ -297,7 +313,9 @@ class Base(_RegisteringBase):
@units.setter
def units(self, value: str):
self._units = get_units_from_string(value)
units = get_units_from_string(value)
if units:
self._units = units
def get_member_names(self) -> List[str]:
"""Get all of the property names on this object, dynamic or not"""
@@ -327,7 +345,8 @@ class Base(_RegisteringBase):
def get_id(self, decompose: bool = False) -> str:
"""
Gets the id (a unique hash) of this object. ⚠️ This method fully serializes the object, which in the case of large objects (with many sub-objects), has a tangible cost. Avoid using it!
Gets the id (a unique hash) of this object. ⚠️ This method fully serializes the object which,
in the case of large objects (with many sub-objects), has a tangible cost. Avoid using it!
Note: the hash of a decomposed object differs from that of a non-decomposed object
@@ -337,8 +356,7 @@ class Base(_RegisteringBase):
Returns:
str -- the hash (id) of the fully serialized object
"""
from specklepy.serialization.base_object_serializer import \
BaseObjectSerializer
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
serializer = BaseObjectSerializer()
if decompose:
+35 -26
View File
@@ -17,6 +17,7 @@ class CurveTypeEncoding(int, Enum):
@property
def object_class(self) -> Type:
from . import geometry
if self == self.Arc:
return geometry.Arc
elif self == self.Circle:
@@ -32,7 +33,8 @@ class CurveTypeEncoding(int, Enum):
elif self == self.Polycurve:
return geometry.Polycurve
raise SpeckleException(
f'No corresponding object class for CurveTypeEncoding: {self}')
f"No corresponding object class for CurveTypeEncoding: {self}"
)
def curve_from_list(args: List[float]):
@@ -41,61 +43,68 @@ def curve_from_list(args: List[float]):
class ObjectArray:
def __init__(self) -> None:
self.data = []
@classmethod
def from_objects(cls, objects: List[Base]) -> 'ObjectArray':
data_chunk = cls()
if len(objects) == 0:
return data_chunk
def from_objects(cls, objects: List[Base]) -> "ObjectArray":
data_list = cls()
if not objects:
return data_list
speckle_type = objects[0].speckle_type
for obj in objects:
if speckle_type != obj.speckle_type:
raise SpeckleException(
'All objects in chunk should have the same speckle_type. '
f'Found {speckle_type} and {obj.speckle_type}'
"All objects in chunk should have the same speckle_type. "
f"Found {speckle_type} and {obj.speckle_type}"
)
data_chunk.encode_object(object=obj)
data_list.encode_object(object=obj)
return data_chunk
return data_list
@staticmethod
def decode_data(data: List[Any], decoder: Callable[[List[Any]], Base]) -> List[Base]:
def decode_data(
data: List[Any], decoder: Callable[[List[Any]], Base]
) -> List[Base]:
bases = []
if not data:
return bases
index = 0
unchunked_data = []
while index < len(data):
chunk_length = data[index]
chunk_start = int(index + 1)
chunk_end = int(chunk_start + chunk_length)
chunk_data = data[chunk_start:chunk_end]
decoded_data = decoder(chunk_data)
unchunked_data.append(decoded_data)
index = chunk_end
return unchunked_data
item_length = data[index]
item_start = index + 1
item_end = item_start + item_length
item_data = data[item_start:item_end]
index = item_end
# TODO: investigate what's going on w this fail
try:
decoded_data = decoder(item_data)
bases.append(decoded_data)
except ValueError:
continue
return bases
def decode(self, decoder: Callable[[List[Any]], Any]):
return self.decode_data(data=self.data, decoder=decoder)
def encode_object(self, object: Base):
chunk = object.to_list()
chunk.insert(0, len(chunk))
self.data.extend(chunk)
encoded = object.to_list()
encoded.insert(0, len(encoded))
self.data.extend(encoded)
class CurveArray(ObjectArray):
@classmethod
def from_curve(cls, curve: Base) -> 'CurveArray':
def from_curve(cls, curve: Base) -> "CurveArray":
crv_array = cls()
crv_array.data = curve.to_list()
return crv_array
@classmethod
def from_curves(cls, curves: List[Base]) -> 'CurveArray':
def from_curves(cls, curves: List[Base]) -> "CurveArray":
data = []
for curve in curves:
curve_list = curve.to_list()
+25
View File
@@ -33,6 +33,7 @@ class Point(Base, speckle_type=GEOMETRY + "Point"):
@classmethod
def from_list(cls, args: List[float]) -> "Point":
"""Create a new Point from a list of three floats representing the x, y, and z coordinates"""
return cls(x=args[0], y=args[1], z=args[2])
def to_list(self) -> List[Any]:
@@ -40,6 +41,7 @@ class Point(Base, speckle_type=GEOMETRY + "Point"):
@classmethod
def from_coords(cls, x: float = 0.0, y: float = 0.0, z: float = 0.0):
"""Create a new Point from x, y, and z values"""
pt = Point()
pt.x, pt.y, pt.z = x, y, z
return pt
@@ -216,6 +218,7 @@ class Polyline(Base, speckle_type=GEOMETRY + "Polyline", chunkable={"value": 200
@classmethod
def from_points(cls, points: List[Point]):
"""Create a new Polyline from a list of Points"""
polyline = cls()
polyline.units = points[0].units
polyline.value = []
@@ -391,6 +394,28 @@ class Mesh(
area: float = None
volume: float = None
@classmethod
def create(
cls,
vertices: List[float],
faces: List[int],
colors: List[int] = None,
texture_coordinates: List[float] = None,
) -> "Mesh":
"""
Create a new Mesh from lists representing its vertices, faces,
colors (optional), and texture coordinates (optional).
This will initialise empty lists for colors and texture coordinates
if you do not provide any.
"""
return cls(
vertices=vertices,
faces=faces,
colors=colors or [],
textureCoordinates=texture_coordinates or [],
)
class Surface(Base, speckle_type=GEOMETRY + "Surface"):
degreeU: int = None
+187
View File
@@ -1,7 +1,28 @@
from typing import List
from specklepy.objects.geometry import Point, Vector
from .base import Base
OTHER = "Objects.Other."
IDENTITY_TRANSFORM = [
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
0.0,
0.0,
0.0,
0.0,
1.0,
]
class RenderMaterial(Base, speckle_type=OTHER + "RenderMaterial"):
name: str = None
@@ -10,3 +31,169 @@ class RenderMaterial(Base, speckle_type=OTHER + "RenderMaterial"):
roughness: float = 1
diffuse: int = -2894893 # light gray arbg
emissive: int = -16777216 # black arbg
class Transform(
Base,
speckle_type=OTHER + "Transform",
serialize_ignore={"translation", "scaling", "is_identity"},
):
"""The 4x4 transformation matrix
The 3x3 sub-matrix determines scaling.
The 4th column defines translation, where the last value is a divisor (usually equal to 1).
"""
_value: List[float] = None
@property
def value(self) -> List[float]:
"""The transform matrix represented as a flat list of 16 floats"""
return self._value
@value.setter
def value(self, value: List[float]) -> None:
try:
value = [float(x) for x in value]
except (ValueError, TypeError):
raise ValueError(
f"Could not create a Transform object with the requested value. Input must be a 16 element list of numbers. Value provided: {value}"
)
if len(value) != 16:
raise ValueError(
f"Could not create a Transform object: input list should be 16 floats long, but was {len(value)} long"
)
self._value = value
@property
def translation(self) -> List[float]:
"""The final column of the matrix which defines the translation"""
return [self._value[i] for i in (3, 7, 11, 15)]
@property
def scaling(self) -> List[float]:
"""The 3x3 scaling sub-matrix"""
return [self._value[i] for i in (0, 1, 2, 4, 5, 6, 8, 9, 10)]
@property
def is_identity(self) -> bool:
return self.value == IDENTITY_TRANSFORM
def apply_to_point(self, point: Point) -> Point:
"""Transform a single speckle Point
Arguments:
point {Point} -- the speckle Point to transform
Returns:
Point -- a new transformed point
"""
coords = self.apply_to_point_value([point.x, point.y, point.z])
return Point(x=coords[0], y=coords[1], z=coords[2], units=point.units)
def apply_to_point_value(self, point_value: List[float]) -> List[float]:
"""Transform a list of three floats representing a point
Arguments:
point_value {List[float]} -- a list of 3 floats
Returns:
List[float] -- the list with the transform applied
"""
transformed = [
point_value[0] * self._value[i]
+ point_value[1] * self._value[i + 1]
+ point_value[2] * self._value[i + 2]
+ self._value[i + 3]
for i in range(0, 15, 4)
]
return [transformed[i] / transformed[3] for i in range(3)]
def apply_to_points(self, points: List[Point]) -> List[Point]:
"""Transform a list of speckle Points
Arguments:
points {List[Point]} -- the list of speckle Points to transform
Returns:
List[Point] -- a new list of transformed points
"""
return [self.apply_to_point(point) for point in points]
def apply_to_points_values(self, points_value: List[float]) -> List[float]:
"""Transform a list of speckle Points
Arguments:
points {List[float]} -- a flat list of floats representing points to transform
Returns:
List[float] -- a new transformed list
"""
if len(points_value) % 3 != 0:
raise ValueError(
"Cannot apply transform as the points list is malformed: expected length to be multiple of 3"
)
transformed = []
for i in range(0, len(points_value), 3):
transformed.extend(self.apply_to_point_value(points_value[i : i + 3]))
return transformed
def apply_to_vector(self, vector: Vector) -> Vector:
"""Transform a single speckle Vector
Arguments:
point {Vector} -- the speckle Vector to transform
Returns:
Vector -- a new transformed point
"""
coords = self.apply_to_vector_value([vector.x, vector.y, vector.z])
return Vector(x=coords[0], y=coords[1], z=coords[2], units=vector.units)
def apply_to_vector_value(self, vector_value: List[float]) -> List[float]:
"""Transform a list of three floats representing a vector
Arguments:
vector_value {List[float]} -- a list of 3 floats
Returns:
List[float] -- the list with the transform applied
"""
return [
vector_value[0] * self._value[i]
+ vector_value[1] * self._value[i + 1]
+ vector_value[2] * self._value[i + 2]
for i in range(0, 15, 4)
][:3]
@classmethod
def from_list(cls, value: List[float] = None) -> "Transform":
"""Returns a Transform object from a list of 16 numbers. If no value is provided, an identity transform will be returned.
Arguments:
value {List[float]} -- the matrix as a flat list of 16 numbers (defaults to the identity transform)
Returns:
Transform -- a complete transform object
"""
if not value:
value = IDENTITY_TRANSFORM
return cls(value=value)
class BlockDefinition(
Base, speckle_type=OTHER + "BlockDefinition", detachable={"geometry"}
):
name: str = None
basePoint: Point = None
geometry: List[Base] = None
class BlockInstance(
Base, speckle_type=OTHER + "BlockInstance", detachable={"blockDefinition"}
):
blockDefinition: BlockDefinition = None
transform: Transform = None
+40
View File
@@ -0,0 +1,40 @@
"""Builtin Speckle object kit."""
from specklepy.objects.structural.analysis import *
from specklepy.objects.structural.properties import *
from specklepy.objects.structural.material import *
from specklepy.objects.structural.geometry import *
from specklepy.objects.structural.loading import *
from specklepy.objects.structural.axis import Axis
__all__ = [
"Element1D",
"Element2D",
"Element3D",
"Axis",
"Node",
"Restraint",
"Load",
"LoadBeam",
"LoadCase",
"LoadCombinations",
"LoadFace",
"LoadGravity",
"LoadNode",
"Model",
"ModelInfo",
"ModelSettings",
"ModelUnits",
"Concrete",
"Material",
"Steel",
"Timber",
"Property",
"Property1D",
"Property2D",
"Property3D",
"PropertyDamper",
"PropertyMass",
"PropertySpring",
"SectionProfile",
]
+53
View File
@@ -0,0 +1,53 @@
from enum import Enum
import enum
from typing import Any, List, Optional
from ..base import Base
from ..geometry import *
from .properties import *
STRUCTURAL_ANALYSIS = "Objects.Structural.Analysis."
class ModelUnits(Base, speckle_type=STRUCTURAL_ANALYSIS + "ModelUnits"):
length: str = None
sections: str = None
displacements: str = None
stress: str = None
force: str = None
mass: str = None
time: str = None
temperature: str = None
velocity: str = None
acceleration: str = None
energy: str = None
angle: str = None
strain: str = None
class ModelSettings(Base, speckle_type=STRUCTURAL_ANALYSIS + "ModelSettings"):
modelUnits: ModelUnits = None
steelCode: str = None
concreteCode: str = None
coincidenceTolerance: float = 0.0
class ModelInfo(Base, speckle_type=STRUCTURAL_ANALYSIS + "ModelInfo"):
name: str = None
description: str = None
projectNumber: str = None
projectName: str = None
settings: ModelSettings = None
initials: str = None
application: str = None
class Model(Base, speckle_type=STRUCTURAL_ANALYSIS + "Model"):
specs: ModelInfo = None
nodes: List = None
elements: List = None
loads: List = None
restraints: List = None
properties: List = None
materials: List = None
layerDescription: str = None
+8
View File
@@ -0,0 +1,8 @@
from ..base import Base
from ..geometry import Plane
class Axis(Base, speckle_type="Objects.Structural.Geometry.Axis"):
name: str = None
axisType: str = None
plane: Plane = None
+109
View File
@@ -0,0 +1,109 @@
from enum import Enum
import enum
from typing import Any, List, Optional
from ..base import Base
from ..geometry import *
from .properties import *
from .axis import Axis
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: 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
units: str = None
class Node(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Node"):
name: str = None
basePoint: Point = None
constraintAxis: Axis = None
restraint: Restraint = None
springProperty: PropertySpring = None
massProperty: PropertyMass = None
damperProperty: PropertyDamper = None
units: str = None
class Element1D(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Element1D"):
name: str = None
baseLine: Line = None
property: Property1D = None
type: ElementType1D = None
end1Releases: Restraint = None
end2Releases: Restraint = None
end1Offset: Vector = None
end2Offset: Vector = None
orientationNode: Node = None
orinetationAngle: float = 0.0
localAxis: Plane = None
parent: Base = None
end1Node: Node = Node
end2Node: Node = Node
topology: List = None
displayMesh: Mesh = None
units: str = None
class Element2D(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Element2D"):
name: str = None
property: Property2D = None
type: ElementType2D = None
offset: float = 0.0
orientationAngle: float = 0.0
parent: Base = None
topology: List = None
displayMesh: Mesh = None
units: str = None
class Element3D(Base, speckle_type=STRUCTURAL_GEOMETRY + ".Element3D"):
name: str = None
baseMesh: Mesh = None
property: Property3D = None
type: ElementType3D = None
orientationAngle: float = 0.0
parent: Base = None
topology: List
units: str = None
# class Storey needs ependency on built elements first
+144
View File
@@ -0,0 +1,144 @@
from enum import Enum
from typing import Any, List, Optional
from ..base import Base
from .geometry import *
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: str = None
loadType: LoadType = None
group: str = None
actionType: ActionType = None
description: str = None
class Load(Base, speckle_type=STRUCTURAL_LOADING + "Load"):
name: str = None
units: str = None
loadCase: LoadCase = None
class LoadBeam(Load, speckle_type=STRUCTURAL_LOADING + "LoadBeam"):
elements: List = None
loadType: BeamLoadType = None
direction: LoadDirection = None
loadAxis: Axis = None
loadAxisType: LoadAxisType = None
isProjected: bool = None
values: List = None
positions: List = None
class LoadCombinations(Base, speckle_type=STRUCTURAL_LOADING + "LoadCombination"):
name: str = None
loadCases: List
loadFactors: List
combinationType: CombinationType
class LoadFace(Load, speckle_type=STRUCTURAL_LOADING + "LoadFace"):
elements: List = None
loadType: FaceLoadType = None
direction: LoadDirection2D = None
loadAxis: Axis = None
loadAxisType: LoadAxisType = None
isProjected: bool = None
values: List = None
positions: List = None
class LoadGravity(Load, speckle_type=STRUCTURAL_LOADING + "LoadGravity"):
elements: List = None
nodes: List = None
gravityFactors: Vector = None
class LoadNode(Load, speckle_type=STRUCTURAL_LOADING + "LoadNode"):
nodes: List = None
loadAxis: Axis = None
direction: LoadDirection = None
value: float = 0.0
+60
View File
@@ -0,0 +1,60 @@
from enum import Enum
from typing import Any, List, Optional
from ..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 Material(Base, speckle_type=STRUCTURAL_MATERIALS):
name: str = None
grade: str = None
materialType: MaterialType = None
designCode: str = None
codeYear: 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(Material, speckle_type=STRUCTURAL_MATERIALS + ".Concrete"):
compressiveStrength: float = 0.0
tensileStrength: float = 0.0
flexuralStrength: float = 0.0
maxCompressiveStrength: float = 0.0
maxTensileStrength: float = 0.0
maxAggregateSize: float = 0.0
lightweight: bool = None
class Steel(Material, speckle_type=STRUCTURAL_MATERIALS + ".Steel"):
yieldStrength: float = 0.0
ultimateStrength: float = 0.0
maxStrain: float = 0.0
strainHardeningModulus: float = 0.0
class Timber(Material, speckle_type=STRUCTURAL_MATERIALS + ".Timber"):
species: str = None
+214
View File
@@ -0,0 +1,214 @@
from enum import Enum
from typing import Any, List, Optional
from ..base import Base
from .material import *
from .axis import Axis
STRUCTURAL_PROPERTY = "Objectives.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
Tee = 3
Angle = 4
Channel = 5
Perimeter = 6
Box = 7
Catalogue = 8
Explicit = 9
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: str = None
class SectionProfile(Base, speckle_type=STRUCTURAL_PROPERTY + ".SectionProfile"):
name: str = None
shapeType: 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
units: str = None
class Property1D(Property, speckle_type=STRUCTURAL_PROPERTY + ".Property1D"):
memberType: MemberType = None
Material: Material = None
SectionProfile: SectionProfile = None
BaseReferencePoint: BaseReferencePoint = None
offsetY: float = 0.0
offsetZ: float = 0.0
class Property2D(Property, speckle_type=STRUCTURAL_PROPERTY + ".Property2D"):
PropertyType2D: PropertyType2D = None
thickness: float = 0.0
Material: Material = None
axis: Axis = None
referenceSurface: 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"):
PropertyType3D: PropertyType3D = None
Material: Material = None
axis: Axis = None
class PropertyDamper(Property, speckle_type=STRUCTURAL_PROPERTY + ".PropertyDamper"):
damperType: 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: bool = None
massModifierX: float = 0.0
massModifierY: float = 0.0
massModifierZ: float = 0.0
class PropertySpring(Property, speckle_type=STRUCTURAL_PROPERTY + ".PropertySpring"):
springType: 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
+176
View File
@@ -0,0 +1,176 @@
from enum import Enum
import enum
from typing import Any, List, Optional
from ..base import Base
from ..geometry import *
from .loading import *
from .geometry import *
from .analysis import Model
STRUCTURAL_RESULTS = "Objects.Structural.Results."
class Result(Base, speckle_type=STRUCTURAL_RESULTS + "Result"):
resultCase: Base = None
permutation: str = None
description: str = None
class ResultSet1D(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSet1D"):
results1D: List
class Result1D(Result, speckle_type=STRUCTURAL_RESULTS + "Result1D"):
element: Element1D = None
position: float = 0.0
dispX: float = 0.0
dispY: float = 0.0
dispZ: float = 0.0
rotXX: float = 0.0
rotYY: float = 0.0
rotZZ: float = 0.0
forceX: float = 0.0
forceY: float = 0.0
forceZ: float = 0.0
momentXX: float = 0.0
momentYY: float = 0.0
momentZZ: float = 0.0
axialStress: float = 0.0
shearStressY: float = 0.0
shearStressZ: float = 0.0
bendingStressYPos: float = 0.0
bendingStressYNeg: float = 0.0
bendingStressZPos: float = 0.0
bendingStressZNeg: float = 0.0
combinedStressMax: float = 0.0
combinedStressMin: float = 0.0
class ResultSet2D(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSet2D"):
results2D: List
class Result2D(Result, speckle_type=STRUCTURAL_RESULTS + "Result2D"):
element: Element2D = None
position: List
dispX: float = 0.0
dispY: float = 0.0
dispZ: float = 0.0
forceXX: float = 0.0
forceYY: float = 0.0
forceXY: float = 0.0
momentXX: float = 0.0
momentYY: float = 0.0
momentXY: float = 0.0
shearX: float = 0.0
shearY: float = 0.0
stressTopXX: float = 0.0
stressTopYY: float = 0.0
stressTopZZ: float = 0.0
stressTopXY: float = 0.0
stressTopYZ: float = 0.0
stressTopZX: float = 0.0
stressMidXX: float = 0.0
stressMidYY: float = 0.0
stressMidZZ: float = 0.0
stressMidXY: float = 0.0
stressMidYZ: float = 0.0
stressMidZX: float = 0.0
stressBotXX: float = 0.0
stressBotYY: float = 0.0
stressBotZZ: float = 0.0
stressBotXY: float = 0.0
stressBotYZ: float = 0.0
stressBotZX: float = 0.0
class ResultSet3D(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSet3D"):
results3D: List
class Result3D(Result, speckle_type=STRUCTURAL_RESULTS + "Result3D"):
element: Element3D = None
position: List
dispX: float = 0.0
dispY: float = 0.0
dispZ: float = 0.0
stressXX: float = 0.0
stressYY: float = 0.0
stressZZ: float = 0.0
stressXY: float = 0.0
stressYZ: float = 0.0
stressZX: float = 0.0
class ResultGlobal(Result, speckle_type=STRUCTURAL_RESULTS + "ResultGlobal"):
model: Model = None
loadX: float = 0.0
loadY: float = 0.0
loadZ: float = 0.0
loadXX: float = 0.0
loadYY: float = 0.0
loadZZ: float = 0.0
reactionX: float = 0.0
reactionY: float = 0.0
reactionZ: float = 0.0
reactionXX: float = 0.0
reactionYY: float = 0.0
reactionZZ: float = 0.0
mode: float = 0.0
frequency: float = 0.0
loadFactor: float = 0.0
modalStiffness: float = 0.0
modalGeoStiffness: float = 0.0
effMassX: float = 0.0
effMassY: float = 0.0
effMassZ: float = 0.0
effMassXX: float = 0.0
effMassYY: float = 0.0
effMassZZ: float = 0.0
class ResultSetNode(Result, speckle_type=STRUCTURAL_RESULTS + "ResultSetNode"):
resultsNode: List
class ResultNode(Result, speckle_type=STRUCTURAL_RESULTS + " ResultNode"):
node: Node = None
dispX: float = 0.0
dispY: float = 0.0
dispZ: float = 0.0
rotXX: float = 0.0
rotYY: float = 0.0
rotZZ: float = 0.0
reactionX: float = 0.0
reactionY: float = 0.0
reactionZ: float = 0.0
reactionXX: float = 0.0
reactionYY: float = 0.0
reactionZZ: float = 0.0
constraintX: float = 0.0
constraintY: float = 0.0
constraintZ: float = 0.0
constraintXX: float = 0.0
constraintYY: float = 0.0
constraintZZ: float = 0.0
velX: float = 0.0
velY: float = 0.0
velZ: float = 0.0
velXX: float = 0.0
velYY: float = 0.0
velZZ: float = 0.0
accX: float = 0.0
accY: float = 0.0
accZ: float = 0.0
accXX: float = 0.0
accYY: float = 0.0
accZZ: float = 0.0
class ResultSetAll(Base, speckle_type=None):
resultSet1D: ResultSet1D = None
resultSet2D: ResultSet2D = None
resultSet3D: ResultSet3D = None
resultsGlobal: ResultGlobal = None
resultsNode: ResultSetNode = None
+9 -1
View File
@@ -1,4 +1,5 @@
from specklepy.logging.exceptions import SpeckleException
from warnings import warn
from specklepy.logging.exceptions import SpeckleException, SpeckleWarning
UNITS = ["mm", "cm", "m", "in", "ft", "yd", "mi"]
@@ -15,6 +16,7 @@ UNITS_STRINGS = {
}
UNITS_ENCODINGS = {
"none": 0,
"mm": 1,
"cm": 2,
"m": 3,
@@ -27,6 +29,12 @@ UNITS_ENCODINGS = {
def get_units_from_string(unit: str):
if not isinstance(unit, str):
warn(
f"Invalid units: expected type str but received {type(unit)} ({unit}). Skipping - no units will be set.",
SpeckleWarning,
)
return
unit = str.lower(unit)
for name, alternates in UNITS_STRINGS.items():
if unit in alternates:
@@ -1,11 +1,15 @@
import ujson
import hashlib
import re
from uuid import uuid4
from warnings import warn
from typing import Any, Dict, List, Tuple
from specklepy.objects.base import Base, DataChunk
from specklepy.logging.exceptions import SerializationException, SpeckleException
from specklepy.logging.exceptions import (
SerializationException,
SpeckleException,
SpeckleWarning,
)
from specklepy.transports.abstract_transport import AbstractTransport
import specklepy.objects.geometry
import specklepy.objects.other
@@ -17,6 +21,19 @@ def hash_obj(obj: Any) -> str:
return hashlib.sha256(ujson.dumps(obj).encode()).hexdigest()[:32]
def safe_json_loads(obj: str, obj_id=None) -> Any:
try:
return ujson.loads(obj)
except ValueError as err:
import json
warn(
f"Failed to deserialise object (id: {obj_id}). This is likely a ujson big int error - falling back to json. \nError: {err}",
SpeckleWarning,
)
return json.loads(obj)
class BaseObjectSerializer:
read_transport: AbstractTransport
write_transports: List[AbstractTransport]
@@ -60,14 +77,19 @@ class BaseObjectSerializer:
chunkable = False
detach = False
# skip nulls or props marked to be ignored with "__" or "_"
if value is None or prop.startswith(("__", "_")):
# skip props marked to be ignored with "__" or "_"
if prop.startswith(("__", "_")):
continue
# don't prepopulate id as this will mess up hashing
if prop == "id":
continue
# allow serialisation of nulls
if value is None:
object_builder[prop] = value
continue
# only bother with chunking and detaching if there is a write transport
if self.write_transports:
dynamic_chunk_match = prop.startswith("@") and re.match(
@@ -238,7 +260,7 @@ class BaseObjectSerializer:
"""
if not obj_string:
return None
obj = ujson.loads(obj_string)
obj = safe_json_loads(obj_string)
return self.recompose_base(obj=obj)
def recompose_base(self, obj: dict) -> Base:
@@ -254,7 +276,7 @@ class BaseObjectSerializer:
if not obj:
return
if isinstance(obj, str):
obj = ujson.loads(obj)
obj = safe_json_loads(obj)
if "speckle_type" in obj and obj["speckle_type"] == "reference":
obj = self.get_child(obj=obj)
@@ -292,7 +314,7 @@ class BaseObjectSerializer:
raise SpeckleException(
f"Could not find the referenced child object of id `{ref_hash}` in the given read transport: {self.read_transport.name}"
)
ref_obj = ujson.loads(ref_obj_str)
ref_obj = safe_json_loads(ref_obj_str, ref_hash)
base.__setattr__(prop, self.recompose_base(obj=ref_obj))
# 3. handle all other cases (base objects, lists, and dicts)
@@ -350,4 +372,5 @@ class BaseObjectSerializer:
raise SpeckleException(
f"Could not find the referenced child object of id `{ref_hash}` in the given read transport: {self.read_transport.name}"
)
return ujson.loads(ref_obj_str)
return safe_json_loads(ref_obj_str, ref_hash)
+9 -4
View File
@@ -92,10 +92,15 @@ class BatchSender(object):
def _bg_send_batch(self, session, batch):
object_ids = [obj[0] for obj in batch]
server_has_object = session.post(
url=f"{self.server_url}/api/diff/{self.stream_id}",
data={"objects": json.dumps(object_ids)},
).json()
try:
server_has_object = session.post(
url=f"{self.server_url}/api/diff/{self.stream_id}",
data={"objects": json.dumps(object_ids)},
).json()
except Exception as ex:
raise SpeckleException(
f"Invalid credentials - cannot send objects to server {self.server_url}"
) from ex
new_object_ids = [x for x in object_ids if not server_has_object[x]]
new_object_ids = set(new_object_ids)
+53 -6
View File
@@ -13,21 +13,68 @@ from .batch_sender import BatchSender
class ServerTransport(AbstractTransport):
"""
The `ServerTransport` is the vehicle through which you transport objects to and from a Speckle Server. Provide it to
`operations.send()` or `operations.receive()`.
The `ServerTransport` can be authenticted two different ways:
1. by providing a `SpeckleClient`
2. by providing a `token` and `url`
```py
from specklepy.api import operations
from specklepy.transports.server import ServerTransport
# here's the data you want to send
block = Block(length=2, height=4)
# next create the server transport - this is the vehicle through which you will send and receive
transport = ServerTransport(stream_id=new_stream_id, client=client)
# this serialises the block and sends it to the transport
hash = operations.send(base=block, transports=[transport])
# you can now create a commit on your stream with this object
commid_id = client.commit.create(
stream_id=new_stream_id,
obj_id=hash,
message="this is a block I made in speckle-py",
)
```
"""
_name = "RemoteTransport"
url: str = None
stream_id: str = None
saved_obj_count: int = 0
session: requests.Session = None
def __init__(self, client: SpeckleClient, stream_id: str, **data: Any) -> None:
def __init__(
self,
stream_id: str,
client: SpeckleClient = None,
token: str = None,
url: str = None,
**data: Any,
) -> None:
super().__init__(**data)
# TODO: replace client with account or some other auth avenue
if not client.me:
raise SpeckleException("The provided SpeckleClient was not authenticated.")
self.url = client.url
self.stream_id = stream_id
if client is None and token is None and url is None:
raise SpeckleException(
"You must provide either a client or a token and url to construct a ServerTransport."
)
if client:
if not client.me:
raise SpeckleException(
"The provided SpeckleClient was not authenticated."
)
token = client.me["token"]
url = client.url
self.stream_id = stream_id
self.url = url
token = client.me["token"]
self._batch_sender = BatchSender(
self.url, self.stream_id, token, max_batch_size_mb=1
)
+15 -5
View File
@@ -1,5 +1,5 @@
from contextlib import ExitStack as does_not_raise
from typing import Dict, List
from typing import Dict, List, Optional
import pytest
from specklepy.api import operations
@@ -77,6 +77,18 @@ def test_speckle_type_cannot_be_set(base: Base) -> None:
assert base.speckle_type == "Base"
def test_setting_units():
b = Base(units="foot")
assert b.units == "ft"
with pytest.raises(SpeckleException):
b.units = "big"
b.units = None # invalid args are skipped
b.units = 7
assert b.units == "ft"
def test_base_of_custom_speckle_type() -> None:
b1 = Base.of_type("BirdHouse", name="Tweety's Crib")
assert b1.speckle_type == "BirdHouse"
@@ -87,9 +99,9 @@ class FrozenYoghurt(Base):
"""Testing type checking"""
servings: int
flavours: List[str] = None # list item types won't be checked
flavours: List[str] # list item types won't be checked
customer: str
add_ons: Dict[str, float] # dict item types won't be checked
add_ons: Optional[Dict[str, float]] # dict item types won't be checked
price: float = 0.0
@@ -111,5 +123,3 @@ def test_type_checking() -> None:
order.flavours = ["strawberry", "lychee", "peach", "pineapple"]
assert order.price == 7.0
+1 -1
View File
@@ -58,7 +58,7 @@ class TestBranch:
assert isinstance(branches, list)
assert len(branches) == 2
assert isinstance(branches[0], Branch)
assert branches[0].name == branch.name
assert branches[1].name == branch.name
def test_branch_update(self, client, stream, branch, updated_branch):
updated = client.branch.update(
+31
View File
@@ -0,0 +1,31 @@
import pytest
from specklepy.api import operations
from specklepy.api.client import SpeckleClient
from specklepy.objects.base import Base
from specklepy.transports.server import ServerTransport
from specklepy.logging.exceptions import SpeckleException, SpeckleWarning
def test_invalid_authentication():
client = SpeckleClient()
with pytest.warns(SpeckleWarning):
client.authenticate("fake token")
def test_invalid_send():
client = SpeckleClient()
client.me = {"token": "fake token"}
transport = ServerTransport("3073b96e86", client)
with pytest.raises(SpeckleException):
operations.send(Base(), [transport])
def test_invalid_receive():
client = SpeckleClient()
client.me = {"token": "fake token"}
transport = ServerTransport("fake stream", client)
with pytest.raises(SpeckleException):
operations.receive("fake object", transport)
+17
View File
@@ -68,3 +68,20 @@ class TestCommit:
deleted = client.commit.delete(stream_id=stream.id, commit_id=commit_id)
assert deleted is True
def test_commit_marked_as_received(self, client, stream, mesh) -> None:
commit = Commit(message="this commit should be received")
commit.id = client.commit.create(
stream_id=stream.id,
object_id=mesh.id,
message=commit.message,
)
commit_marked_received = client.commit.received(
stream.id,
commit.id,
source_application="pytest",
message="testing received",
)
assert commit_marked_received == True
+126 -50
View File
@@ -3,13 +3,31 @@ from typing import Callable
import pytest
from specklepy.api import operations
from specklepy.logging.exceptions import SpeckleException
from specklepy.objects.base import Base
from specklepy.objects.encoding import CurveArray, ObjectArray
from specklepy.objects.geometry import (Arc, Box, Brep, BrepEdge, BrepFace,
BrepLoop, BrepTrim, BrepTrimTypeEnum,
Circle, Curve, Ellipse, Interval, Line,
Mesh, Plane, Point, Polycurve,
Polyline, Surface, Vector)
from specklepy.objects.geometry import (
Arc,
Box,
Brep,
BrepEdge,
BrepFace,
BrepLoop,
BrepTrim,
BrepTrimTypeEnum,
Circle,
Curve,
Ellipse,
Interval,
Line,
Mesh,
Plane,
Point,
Polycurve,
Polyline,
Surface,
Vector,
)
from specklepy.transports.memory import MemoryTransport
@@ -71,7 +89,7 @@ def arc(plane, interval):
angleRadians=33,
plane=plane,
domain=interval,
units='m',
units="m",
# These attributes are not handled in C#
# bbox=None,
# area=None,
@@ -88,7 +106,7 @@ def circle(plane, interval):
radius=22,
plane=plane,
domain=interval,
units='m',
units="m",
# These attributes are not handled in C#
# bbox=None,
# area=None,
@@ -103,7 +121,7 @@ def ellipse(plane, interval):
secondRadius=22,
plane=plane,
domain=interval,
units='m',
units="m",
# These attributes are not handled in C#
# trimDomain=None,
# bbox=None,
@@ -118,7 +136,7 @@ def polyline(interval):
value=[22, 44, 54.3, 99, 232, 21],
closed=True,
domain=interval,
units='m',
units="m",
# These attributes are not handled in C#
# bbox=None,
# area=None,
@@ -137,7 +155,7 @@ def curve(interval):
points=[23, 21, 44, 43, 56, 76, 1, 3, 2],
weights=[23, 11, 23],
knots=[22, 45, 76, 11],
units='m',
units="m",
# These attributes are not handled in C#
# displayValue=None,
# bbox=None,
@@ -152,7 +170,7 @@ def polycurve(interval, curve, polyline):
segments=[curve, polyline],
domain=interval,
closed=True,
units='m',
units="m",
# These attributes are not handled in C#
# bbox=None,
# area=None,
@@ -187,7 +205,7 @@ def surface(interval):
domainV=interval,
knotsU=[1.1, 2.2, 3.3, 4.4],
knotsV=[9, 8, 7, 6, 5, 4.4],
units='m',
units="m",
# These attributes are not handled in C#
# bbox=None,
# area=None,
@@ -218,11 +236,7 @@ def brep_edge(interval):
@pytest.fixture()
def brep_loop():
return BrepLoop(
FaceIndex=5,
TrimIndices=[3, 4, 5],
Type='unknown'
)
return BrepLoop(FaceIndex=5, TrimIndices=[3, 4, 5], Type="unknown")
@pytest.fixture()
@@ -235,7 +249,7 @@ def brep_trim():
LoopIndex=4,
CurveIndex=7,
IsoStatus=6,
TrimType='Mated',
TrimType="Mated",
IsReversed=False,
# These attributes are not handled in C#
# Domain=None,
@@ -243,10 +257,21 @@ def brep_trim():
@pytest.fixture
def brep(mesh, box, surface, curve, polyline, circle, point,
brep_edge, brep_loop, brep_trim, brep_face):
def brep(
mesh,
box,
surface,
curve,
polyline,
circle,
point,
brep_edge,
brep_loop,
brep_trim,
brep_face,
):
return Brep(
provenance='pytest',
provenance="pytest",
bbox=box,
area=32,
volume=54,
@@ -265,33 +290,57 @@ def brep(mesh, box, surface, curve, polyline, circle, point,
@pytest.fixture
def geometry_objects_dict(point, vector, plane, line, arc,
circle, ellipse, polyline, curve,
polycurve, surface, brep_trim):
def geometry_objects_dict(
point,
vector,
plane,
line,
arc,
circle,
ellipse,
polyline,
curve,
polycurve,
surface,
brep_trim,
):
return {
'point': point,
'vector': vector,
'plane': plane,
'line': line,
'arc': arc,
'circle': circle,
'ellipse': ellipse,
'polyline': polyline,
'curve': curve,
'polycurve': polycurve,
'surface': surface,
'brep_trim': brep_trim
"point": point,
"vector": vector,
"plane": plane,
"line": line,
"arc": arc,
"circle": circle,
"ellipse": ellipse,
"polyline": polyline,
"curve": curve,
"polycurve": polycurve,
"surface": surface,
"brep_trim": brep_trim,
}
@pytest.mark.parametrize('object_name', [
'point', 'vector', 'plane', 'line', 'arc', 'circle',
'ellipse', 'polyline', 'curve', 'polycurve', 'surface', 'brep_trim'
])
@pytest.mark.parametrize(
"object_name",
[
"point",
"vector",
"plane",
"line",
"arc",
"circle",
"ellipse",
"polyline",
"curve",
"polycurve",
"surface",
"brep_trim",
],
)
def test_to_and_from_list(object_name: str, geometry_objects_dict):
object = geometry_objects_dict[object_name]
assert hasattr(object, 'to_list')
assert hasattr(object, 'from_list')
assert hasattr(object, "to_list")
assert hasattr(object, "from_list")
chunks = object.to_list()
assert isinstance(chunks, list)
@@ -306,8 +355,7 @@ def test_brep_surfaces_value_serialization(surface):
assert brep.Surfaces == None
assert brep.SurfacesValue == None
brep.Surfaces = [surface, surface]
assert brep.SurfacesValue == ObjectArray.from_objects(
[surface, surface]).data
assert brep.SurfacesValue == ObjectArray.from_objects([surface, surface]).data
brep.SurfacesValue = ObjectArray.from_objects([surface]).data
assert len(brep.Surfaces) == 1
@@ -341,16 +389,32 @@ def test_brep_curve3d_values_serialization(curve, polyline, circle):
def test_brep_vertices_values_serialization():
brep = Brep()
brep.VerticesValue = [1, 1, 1, 1, 2, 2, 2, 3, 3, 3]
brep.Vertices[0].get_id() == Point(x=1, y=1, z=1, _units='mm').get_id()
brep.Vertices[1].get_id() == Point(x=2, y=2, z=2, _units='mm').get_id()
brep.Vertices[2].get_id() == Point(x=3, y=3, z=3, _units='mm').get_id()
brep.Vertices[0].get_id() == Point(x=1, y=1, z=1, _units="mm").get_id()
brep.Vertices[1].get_id() == Point(x=2, y=2, z=2, _units="mm").get_id()
brep.Vertices[2].get_id() == Point(x=3, y=3, z=3, _units="mm").get_id()
def test_trims_value_serialization():
brep = Brep()
brep.TrimsValue = [
0, 0, 0, 0, 0, 0, 1, 1, 1,
1, 0, 0, 0, 0, 1, 2, 1, 0,
0,
0,
0,
0,
0,
0,
1,
1,
1,
1,
0,
0,
0,
0,
1,
2,
1,
0,
]
brep.Trims[0].get_id() == BrepTrim(
@@ -383,7 +447,19 @@ def test_serialized_brep_attributes(brep: Brep):
serialized = operations.serialize(brep, [transport])
serialized_dict = json.loads(serialized)
removed_keys = ['Surfaces', 'Curve3D', 'Curve2D', 'Vertices', 'Trims']
removed_keys = ["Surfaces", "Curve3D", "Curve2D", "Vertices", "Trims"]
for k in removed_keys:
assert k not in serialized_dict.keys()
def test_mesh_create():
vertices = [2, 1, 2, 4, 77.3, 5, 33, 4, 2]
faces = [1, 2, 3, 4, 5, 6, 7]
mesh = Mesh.create(vertices, faces)
with pytest.raises(SpeckleException):
bad_mesh = Mesh.create(vertices=7, faces=faces)
assert mesh.vertices == vertices
assert mesh.textureCoordinates == []
+3 -11
View File
@@ -21,11 +21,9 @@ class TestObject:
def test_object_create(self, client, stream, base):
transport = SQLiteTransport()
s = BaseObjectSerializer(
write_transports=[transport], read_transport=transport)
s = BaseObjectSerializer(write_transports=[transport], read_transport=transport)
_, base_dict = s.traverse_base(base)
obj_id = client.object.create(
stream_id=stream.id, objects=[base_dict])[0]
obj_id = client.object.create(stream_id=stream.id, objects=[base_dict])[0]
assert isinstance(obj_id, str)
assert base_dict["@detach"]["speckle_type"] == "reference"
@@ -43,12 +41,6 @@ class TestObject:
def test_object_array_decoder(self):
array = ObjectArray()
array.data = [
5, 1, 1, 1, 1, 1,
4, 1, 1, 1, 1,
3, 1, 1, 1,
2, 1, 1,
1, 1
]
array.data = [5, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 3, 1, 1, 1, 2, 1, 1, 1, 1]
assert array.decode(decoder=sum) == [5, 4, 3, 2, 1]
+12 -2
View File
@@ -1,5 +1,4 @@
import json
from attr import has
import pytest
from specklepy.api import operations
from specklepy.transports.server import ServerTransport
@@ -53,6 +52,11 @@ class TestSerialization:
def test_send_and_receive(self, client, sample_stream, mesh):
transport = ServerTransport(client=client, stream_id=sample_stream.id)
hash = operations.send(mesh, transports=[transport])
# also try constructing server transport with token and url
transport = ServerTransport(
stream_id=sample_stream.id, token=client.me["token"], url=client.url
)
# use a fresh memory transport to force receiving from remote
received = operations.receive(
hash, remote_transport=transport, local_transport=MemoryTransport()
@@ -84,4 +88,10 @@ class TestSerialization:
untyped = '{"foo": "bar"}'
deserialised = operations.deserialize(untyped)
assert deserialised == {"foo": "bar"}
assert deserialised == {"foo": "bar"}
def test_big_int(self):
big_int = '{"big": ' + str(2 ** 64) + "}"
deserialised = operations.deserialize(big_int)
assert deserialised == {"big": 2 ** 64}
+132
View File
@@ -0,0 +1,132 @@
from typing import List
import pytest
from specklepy.api import operations
from specklepy.objects.geometry import Point, Vector
from specklepy.objects.other import (
Transform,
BlockInstance,
BlockDefinition,
IDENTITY_TRANSFORM,
)
@pytest.fixture()
def point():
return Point(x=1, y=10, z=2)
@pytest.fixture()
def points():
return [Point(x=1 + i, y=10 + i, z=2 + i) for i in range(5)]
@pytest.fixture()
def point_value():
return [1, 10, 2]
@pytest.fixture()
def points_values():
coords = []
for i in range(5):
coords.extend([1 + i, 10 + i, 2 + 1])
return coords
@pytest.fixture()
def vector():
return Vector(x=1, y=10, z=2)
@pytest.fixture()
def vector_value():
return [1, 1, 2]
@pytest.fixture()
def transform():
"""Translates to [1, 2, 0] and scales z by 0.5"""
return Transform.from_list(
[
1.0,
0.0,
0.0,
1.0,
0.0,
1.0,
0.0,
2.0,
0.0,
0.0,
0.5,
0.0,
0.0,
0.0,
0.0,
1.0,
]
)
def test_point_transform(point: Point, transform: Transform):
new_point = transform.apply_to_point(point)
assert new_point.x == point.x + 1
assert new_point.y == point.y + 2
assert new_point.z == point.z * 0.5
def test_points_transform(points: List[Point], transform: Transform):
new_points = transform.apply_to_points(points)
for (i, new_point) in enumerate(new_points):
assert new_point.x == points[i].x + 1
assert new_point.y == points[i].y + 2
assert new_point.z == points[i].z * 0.5
def test_point_value_transform(point_value: List[float], transform: Transform):
new_coords = transform.apply_to_point_value(point_value)
assert new_coords[0] == point_value[0] + 1
assert new_coords[1] == point_value[1] + 2
assert new_coords[2] == point_value[2] * 0.5
def test_points_values_transform(points_values: List[float], transform: Transform):
new_coords = transform.apply_to_points_values(points_values)
for i in range(0, len(points_values), 3):
assert new_coords[i] == points_values[i] + 1
assert new_coords[i + 1] == points_values[i + 1] + 2
assert new_coords[i + 2] == points_values[i + 2] * 0.5
def test_vector_transform(vector: Vector, transform: Transform):
new_vector = transform.apply_to_vector(vector)
assert new_vector.x == vector.x
assert new_vector.y == vector.y
assert new_vector.z == vector.z * 0.5
def test_vector_value_transform(vector_value: List[float], transform: Transform):
new_coords = transform.apply_to_vector_value(vector_value)
assert new_coords[0] == vector_value[0]
assert new_coords[1] == vector_value[1]
assert new_coords[2] == vector_value[2] * 0.5
def test_transform_fails_with_malformed_value():
with pytest.raises(ValueError):
Transform.from_list("asdf")
with pytest.raises(ValueError):
Transform.from_list([7, 8, 9])
def test_transform_serialisation(transform: Transform):
serialized = operations.serialize(transform)
deserialized = operations.deserialize(serialized)
assert transform.get_id() == deserialized.get_id()
+38
View File
@@ -18,6 +18,14 @@ class TestWrapper:
assert wacky_wrap.branch_name == "🍕⬅🌟 you wat?"
assert wrap.type == "branch"
def test_parse_nested_branch(self):
wrap = StreamWrapper(
"https://testing.speckle.dev/streams/4c3ce1459c/branches/izzy/dev"
)
assert wrap.branch_name == "izzy/dev"
assert wrap.type == "branch"
def test_parse_commit(self):
wrap = StreamWrapper(
"https://testing.speckle.dev/streams/4c3ce1459c/commits/8b9b831792"
@@ -39,3 +47,33 @@ class TestWrapper:
"https://testing.speckle.dev/streams/0c6ad366c4/globals/abd3787893"
)
assert wrap.type == "commit"
#! NOTE: the following three tests may not pass locally if you have a `speckle.xyz` account in manager
def test_get_client_without_auth(self):
wrap = StreamWrapper(
"https://speckle.xyz/streams/4c3ce1459c/commits/8b9b831792"
)
client = wrap.get_client()
assert client is not None
def test_get_new_client_with_token(self):
wrap = StreamWrapper(
"https://speckle.xyz/streams/4c3ce1459c/commits/8b9b831792"
)
client = wrap.get_client()
client = wrap.get_client(token="super-secret-token")
assert client.me["token"] == "super-secret-token"
def test_get_transport_with_token(self):
wrap = StreamWrapper(
"https://speckle.xyz/streams/4c3ce1459c/commits/8b9b831792"
)
client = wrap.get_client()
assert not client.me # unauthenticated bc no local accounts
transport = wrap.get_transport(token="super-secret-token")
assert transport is not None
assert client.me["token"] == "super-secret-token"