Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f5631cd90 | |||
| af50afe3ff | |||
| b6493df77f | |||
| 59d3c8c3ea | |||
| 4e3405f1fb | |||
| 49eabdd712 | |||
| 96a31f0678 | |||
| 91506b0b20 | |||
| b0de9e31b5 | |||
| 2075783134 | |||
| 071f2449c3 | |||
| a419664461 | |||
| 4a0c07009b | |||
| 682bcbfa9f | |||
| ccf284e8fa | |||
| 23102a28ff | |||
| 5475edb253 | |||
| c52f80c1ef | |||
| 21eecfa24c | |||
| 5dde1bfcf1 | |||
| 82c9d874c9 | |||
| 9acf2c8a92 | |||
| 95012e60c1 | |||
| 19b6500bbd | |||
| 47a06e4630 | |||
| e5a8b40bb2 | |||
| 219456f5f8 | |||
| d1544ae89f | |||
| 8f7d4b2ca7 | |||
| a7d31d4983 | |||
| a89b12a02c | |||
| 15ae68f5d7 | |||
| 0709cd99b5 | |||
| faf06f7141 | |||
| b54e09f811 | |||
| 55b7e0d732 | |||
| 45c922679b | |||
| b1c149382a | |||
| 393e98c8c2 | |||
| 8376329cbb | |||
| 1567fe9e68 | |||
| 364b826a1b | |||
| 297dbab479 |
+13
-4
@@ -2,14 +2,15 @@ version: 2.1
|
|||||||
|
|
||||||
orbs:
|
orbs:
|
||||||
python: circleci/python@1.3.2
|
python: circleci/python@1.3.2
|
||||||
|
codecov: codecov/codecov@3.2.2
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
docker:
|
docker:
|
||||||
- image: "cimg/python:<<parameters.tag>>"
|
- image: "cimg/python:<<parameters.tag>>"
|
||||||
- image: "circleci/node:12"
|
- image: 'cimg/node:14.18'
|
||||||
- image: "circleci/redis:6"
|
- image: 'circleci/redis:6'
|
||||||
- image: "circleci/postgres:12"
|
- image: 'cimg/postgres:12.8'
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_DB: speckle2_test
|
POSTGRES_DB: speckle2_test
|
||||||
POSTGRES_PASSWORD: speckle
|
POSTGRES_PASSWORD: speckle
|
||||||
@@ -38,7 +39,15 @@ jobs:
|
|||||||
name: upgrade pip
|
name: upgrade pip
|
||||||
- python/install-packages:
|
- python/install-packages:
|
||||||
pkg-manager: poetry
|
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:
|
deploy:
|
||||||
docker:
|
docker:
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
.tool-versions
|
||||||
|
.envrc
|
||||||
|
reports/
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
# Byte-compiled / optimized / DLL files
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.py[cod]
|
*.py[cod]
|
||||||
|
|||||||
@@ -8,7 +8,9 @@
|
|||||||
<p align="center"><b>Speckle</b> is the data infrastructure for the AEC industry.</p><br/>
|
<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&style=flat-square&logo=discourse&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&logo=read-the-docs&logoColor=white" alt="docs"></a></p>
|
<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&style=flat-square&logo=discourse&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&logo=read-the-docs&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&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&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
|
# About Speckle
|
||||||
|
|
||||||
|
|||||||
Generated
+531
-281
File diff suppressed because it is too large
Load Diff
+5
-4
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "specklepy"
|
name = "specklepy"
|
||||||
version = "2.1.0"
|
version = "2.4.0"
|
||||||
description = "The Python SDK for Speckle 2.0"
|
description = "The Python SDK for Speckle 2.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
authors = ["Speckle Systems <devops@speckle.systems>"]
|
authors = ["Speckle Systems <devops@speckle.systems>"]
|
||||||
@@ -12,16 +12,17 @@ homepage = "https://speckle.systems/"
|
|||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.6.5"
|
python = "^3.6.5"
|
||||||
pydantic = "^1.7.3"
|
pydantic = "^1.8.2"
|
||||||
appdirs = "^1.4.4"
|
appdirs = "^1.4.4"
|
||||||
gql = {version = ">=3.0.0a6", extras = ["all"], allow-prereleases = true}
|
gql = {version = ">=3.0.0b1", extras = ["all"], allow-prereleases = true}
|
||||||
ujson = "^4.1.0"
|
ujson = "^4.3.0"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
black = "^20.8b1"
|
black = "^20.8b1"
|
||||||
isort = "^5.7.0"
|
isort = "^5.7.0"
|
||||||
pytest = "^6.2.2"
|
pytest = "^6.2.2"
|
||||||
pytest-ordering = "^0.6"
|
pytest-ordering = "^0.6"
|
||||||
|
pytest-cov = "^3.0.0"
|
||||||
|
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
|
|||||||
@@ -89,6 +89,28 @@ def get_default_account(base_path: str = None) -> Account:
|
|||||||
|
|
||||||
|
|
||||||
class StreamWrapper:
|
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
|
stream_url: str = None
|
||||||
use_ssl: bool = True
|
use_ssl: bool = True
|
||||||
host: str = None
|
host: str = None
|
||||||
@@ -122,9 +144,9 @@ class StreamWrapper:
|
|||||||
parsed = urlparse(url)
|
parsed = urlparse(url)
|
||||||
self.host = parsed.netloc
|
self.host = parsed.netloc
|
||||||
self.use_ssl = parsed.scheme == "https"
|
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(
|
raise SpeckleException(
|
||||||
f"Cannot parse {url} into a stream wrapper class - invalid URL provided."
|
f"Cannot parse {url} into a stream wrapper class - invalid URL provided."
|
||||||
)
|
)
|
||||||
@@ -154,6 +176,9 @@ class StreamWrapper:
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_account(self) -> Account:
|
def get_account(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:
|
if self.account:
|
||||||
return self.account
|
return self.account
|
||||||
|
|
||||||
@@ -164,23 +189,42 @@ class StreamWrapper:
|
|||||||
|
|
||||||
return self.account
|
return self.account
|
||||||
|
|
||||||
def get_client(self) -> SpeckleClient:
|
def get_client(self, token: str = None) -> SpeckleClient:
|
||||||
if self.client:
|
"""
|
||||||
|
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.
|
||||||
|
|
||||||
|
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
|
return self.client
|
||||||
|
|
||||||
if not self.account:
|
if not self.account:
|
||||||
self.get_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)
|
warn(f"No local account found for server {self.host}", SpeckleWarning)
|
||||||
return self.client
|
return self.client
|
||||||
|
|
||||||
self.client.authenticate(self.account.token)
|
self.client.authenticate(self.account.token if self.account else token)
|
||||||
|
|
||||||
return self.client
|
return self.client
|
||||||
|
|
||||||
def get_transport(self) -> ServerTransport:
|
def get_transport(self, token: str = None) -> ServerTransport:
|
||||||
if not self.client:
|
"""
|
||||||
self.get_client()
|
Gets a server transport for this stream using an authenticated client. If there is no local account for this
|
||||||
return ServerTransport(self.client, self.stream_id)
|
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)
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class Commit(BaseModel):
|
|||||||
parents: Optional[List[str]]
|
parents: Optional[List[str]]
|
||||||
|
|
||||||
def __repr__(self) -> 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:
|
def __str__(self) -> str:
|
||||||
return self.__repr__()
|
return self.__repr__()
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ def send(
|
|||||||
Returns:
|
Returns:
|
||||||
str -- the object id of the sent object
|
str -- the object id of the sent object
|
||||||
"""
|
"""
|
||||||
metrics.track(metrics.RECEIVE)
|
metrics.track(metrics.SEND)
|
||||||
if not transports and not use_default_cache:
|
if not transports and not use_default_cache:
|
||||||
raise SpeckleException(
|
raise SpeckleException(
|
||||||
message="You need to provide at least one transport: cannot send with an empty transport list and no default cache"
|
message="You need to provide at least one transport: cannot send with an empty transport list and no default cache"
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ class Resource(ResourceBase):
|
|||||||
stream(id: $stream_id) {
|
stream(id: $stream_id) {
|
||||||
commit(id: $commit_id) {
|
commit(id: $commit_id) {
|
||||||
id
|
id
|
||||||
referencedObject
|
|
||||||
message
|
message
|
||||||
|
referencedObject
|
||||||
authorId
|
authorId
|
||||||
authorName
|
authorName
|
||||||
authorAvatar
|
authorAvatar
|
||||||
@@ -79,6 +79,7 @@ class Resource(ResourceBase):
|
|||||||
authorId
|
authorId
|
||||||
authorName
|
authorName
|
||||||
authorAvatar
|
authorAvatar
|
||||||
|
branchName
|
||||||
createdAt
|
createdAt
|
||||||
sourceApplication
|
sourceApplication
|
||||||
totalChildrenCount
|
totalChildrenCount
|
||||||
@@ -185,3 +186,40 @@ class Resource(ResourceBase):
|
|||||||
return self.make_request(
|
return self.make_request(
|
||||||
query=query, params=params, return_type="commitDelete", parse_response=False
|
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
|
||||||
|
|||||||
@@ -32,4 +32,4 @@ class GraphQLException(SpeckleException):
|
|||||||
|
|
||||||
class SpeckleWarning(Warning):
|
class SpeckleWarning(Warning):
|
||||||
def __init__(self, *args: object) -> None:
|
def __init__(self, *args: object) -> None:
|
||||||
super().__init__(*args)
|
super().__init__(*args)
|
||||||
|
|||||||
@@ -122,4 +122,4 @@ class MetricsTracker(metaclass=Singleton):
|
|||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.error("Error sending metrics request: " + str(ex))
|
LOG.error("Error sending metrics request: " + str(ex))
|
||||||
|
|
||||||
self.queue.task_done()
|
self.queue.task_done()
|
||||||
|
|||||||
+34
-21
@@ -1,6 +1,15 @@
|
|||||||
import typing
|
import typing
|
||||||
from typing import (Any, Callable, ClassVar, Dict, List, Optional, Set, Type,
|
from typing import (
|
||||||
get_type_hints)
|
Any,
|
||||||
|
Callable,
|
||||||
|
ClassVar,
|
||||||
|
Dict,
|
||||||
|
List,
|
||||||
|
Optional,
|
||||||
|
Set,
|
||||||
|
Type,
|
||||||
|
get_type_hints,
|
||||||
|
)
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
|
|
||||||
from specklepy.logging.exceptions import SpeckleException
|
from specklepy.logging.exceptions import SpeckleException
|
||||||
@@ -118,14 +127,12 @@ class _RegisteringBase:
|
|||||||
except Exception:
|
except Exception:
|
||||||
cls._attr_types = getattr(cls, "__annotations__", {})
|
cls._attr_types = getattr(cls, "__annotations__", {})
|
||||||
if chunkable:
|
if chunkable:
|
||||||
chunkable = {k: v for k, v in chunkable.items()
|
chunkable = {k: v for k, v in chunkable.items() if isinstance(v, int)}
|
||||||
if isinstance(v, int)}
|
|
||||||
cls._chunkable = dict(cls._chunkable, **chunkable)
|
cls._chunkable = dict(cls._chunkable, **chunkable)
|
||||||
if detachable:
|
if detachable:
|
||||||
cls._detachable = cls._detachable.union(detachable)
|
cls._detachable = cls._detachable.union(detachable)
|
||||||
if serialize_ignore:
|
if serialize_ignore:
|
||||||
cls._serialize_ignore = cls._serialize_ignore.union(
|
cls._serialize_ignore = cls._serialize_ignore.union(serialize_ignore)
|
||||||
serialize_ignore)
|
|
||||||
super().__init_subclass__(**kwargs)
|
super().__init_subclass__(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
@@ -200,7 +207,7 @@ class Base(_RegisteringBase):
|
|||||||
try:
|
try:
|
||||||
attr.__set__(self, value)
|
attr.__set__(self, value)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass # the prop probably doesn't have a setter
|
return # the prop probably doesn't have a setter
|
||||||
super().__setattr__(name, value)
|
super().__setattr__(name, value)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@@ -215,15 +222,13 @@ class Base(_RegisteringBase):
|
|||||||
try:
|
try:
|
||||||
cls._attr_types = get_type_hints(cls)
|
cls._attr_types = get_type_hints(cls)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
warn(
|
warn(f"Could not update forward refs for class {cls.__name__}: {e}")
|
||||||
f"Could not update forward refs for class {cls.__name__}: {e}")
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def validate_prop_name(cls, name: str) -> None:
|
def validate_prop_name(cls, name: str) -> None:
|
||||||
"""Validator for dynamic attribute names."""
|
"""Validator for dynamic attribute names."""
|
||||||
if name in {"", "@"}:
|
if name in {"", "@"}:
|
||||||
raise ValueError(
|
raise ValueError("Invalid Name: Base member names cannot be empty strings")
|
||||||
"Invalid Name: Base member names cannot be empty strings")
|
|
||||||
if name.startswith("@@"):
|
if name.startswith("@@"):
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"Invalid Name: Base member names cannot start with more than one '@'",
|
"Invalid Name: Base member names cannot start with more than one '@'",
|
||||||
@@ -249,7 +254,12 @@ class Base(_RegisteringBase):
|
|||||||
|
|
||||||
if t.__module__ == "typing":
|
if t.__module__ == "typing":
|
||||||
origin = getattr(t, "__origin__")
|
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)):
|
if not isinstance(t, (type, tuple)):
|
||||||
warn(
|
warn(
|
||||||
f"Unrecognised type '{t}' provided for attribute '{name}'. Type will not been validated."
|
f"Unrecognised type '{t}' provided for attribute '{name}'. Type will not been validated."
|
||||||
@@ -260,13 +270,16 @@ class Base(_RegisteringBase):
|
|||||||
|
|
||||||
# to be friendly, we'll parse ints and strs into floats, but not the other way around
|
# to be friendly, we'll parse ints and strs into floats, but not the other way around
|
||||||
# (to avoid unexpected rounding)
|
# (to avoid unexpected rounding)
|
||||||
if t is float and isinstance(value, (int, str, float)):
|
if isinstance(t, tuple):
|
||||||
try:
|
t = t[0]
|
||||||
|
|
||||||
|
try:
|
||||||
|
if t is float:
|
||||||
return float(value)
|
return float(value)
|
||||||
except ValueError:
|
if t is str and value:
|
||||||
pass
|
return str(value)
|
||||||
if t is str and value is not None:
|
except ValueError:
|
||||||
return str(value)
|
pass
|
||||||
|
|
||||||
raise SpeckleException(
|
raise SpeckleException(
|
||||||
f"Cannot set '{self.__class__.__name__}.{name}': it expects type '{t.__name__}', but received type '{type(value).__name__}'"
|
f"Cannot set '{self.__class__.__name__}.{name}': it expects type '{t.__name__}', but received type '{type(value).__name__}'"
|
||||||
@@ -327,7 +340,8 @@ class Base(_RegisteringBase):
|
|||||||
|
|
||||||
def get_id(self, decompose: bool = False) -> str:
|
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
|
Note: the hash of a decomposed object differs from that of a non-decomposed object
|
||||||
|
|
||||||
@@ -337,8 +351,7 @@ class Base(_RegisteringBase):
|
|||||||
Returns:
|
Returns:
|
||||||
str -- the hash (id) of the fully serialized object
|
str -- the hash (id) of the fully serialized object
|
||||||
"""
|
"""
|
||||||
from specklepy.serialization.base_object_serializer import \
|
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
|
||||||
BaseObjectSerializer
|
|
||||||
|
|
||||||
serializer = BaseObjectSerializer()
|
serializer = BaseObjectSerializer()
|
||||||
if decompose:
|
if decompose:
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ class CurveTypeEncoding(int, Enum):
|
|||||||
@property
|
@property
|
||||||
def object_class(self) -> Type:
|
def object_class(self) -> Type:
|
||||||
from . import geometry
|
from . import geometry
|
||||||
|
|
||||||
if self == self.Arc:
|
if self == self.Arc:
|
||||||
return geometry.Arc
|
return geometry.Arc
|
||||||
elif self == self.Circle:
|
elif self == self.Circle:
|
||||||
@@ -32,7 +33,8 @@ class CurveTypeEncoding(int, Enum):
|
|||||||
elif self == self.Polycurve:
|
elif self == self.Polycurve:
|
||||||
return geometry.Polycurve
|
return geometry.Polycurve
|
||||||
raise SpeckleException(
|
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]):
|
def curve_from_list(args: List[float]):
|
||||||
@@ -41,61 +43,68 @@ def curve_from_list(args: List[float]):
|
|||||||
|
|
||||||
|
|
||||||
class ObjectArray:
|
class ObjectArray:
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.data = []
|
self.data = []
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_objects(cls, objects: List[Base]) -> 'ObjectArray':
|
def from_objects(cls, objects: List[Base]) -> "ObjectArray":
|
||||||
data_chunk = cls()
|
data_list = cls()
|
||||||
if len(objects) == 0:
|
if not objects:
|
||||||
return data_chunk
|
return data_list
|
||||||
|
|
||||||
speckle_type = objects[0].speckle_type
|
speckle_type = objects[0].speckle_type
|
||||||
|
|
||||||
for obj in objects:
|
for obj in objects:
|
||||||
if speckle_type != obj.speckle_type:
|
if speckle_type != obj.speckle_type:
|
||||||
raise SpeckleException(
|
raise SpeckleException(
|
||||||
'All objects in chunk should have the same speckle_type. '
|
"All objects in chunk should have the same speckle_type. "
|
||||||
f'Found {speckle_type} and {obj.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
|
@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
|
index = 0
|
||||||
unchunked_data = []
|
|
||||||
while index < len(data):
|
while index < len(data):
|
||||||
chunk_length = data[index]
|
item_length = data[index]
|
||||||
chunk_start = int(index + 1)
|
item_start = index + 1
|
||||||
chunk_end = int(chunk_start + chunk_length)
|
item_end = item_start + item_length
|
||||||
chunk_data = data[chunk_start:chunk_end]
|
item_data = data[item_start:item_end]
|
||||||
decoded_data = decoder(chunk_data)
|
index = item_end
|
||||||
unchunked_data.append(decoded_data)
|
# TODO: investigate what's going on w this fail
|
||||||
index = chunk_end
|
try:
|
||||||
return unchunked_data
|
decoded_data = decoder(item_data)
|
||||||
|
bases.append(decoded_data)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
return bases
|
||||||
|
|
||||||
def decode(self, decoder: Callable[[List[Any]], Any]):
|
def decode(self, decoder: Callable[[List[Any]], Any]):
|
||||||
return self.decode_data(data=self.data, decoder=decoder)
|
return self.decode_data(data=self.data, decoder=decoder)
|
||||||
|
|
||||||
def encode_object(self, object: Base):
|
def encode_object(self, object: Base):
|
||||||
chunk = object.to_list()
|
encoded = object.to_list()
|
||||||
chunk.insert(0, len(chunk))
|
encoded.insert(0, len(encoded))
|
||||||
self.data.extend(chunk)
|
self.data.extend(encoded)
|
||||||
|
|
||||||
|
|
||||||
class CurveArray(ObjectArray):
|
class CurveArray(ObjectArray):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_curve(cls, curve: Base) -> 'CurveArray':
|
def from_curve(cls, curve: Base) -> "CurveArray":
|
||||||
crv_array = cls()
|
crv_array = cls()
|
||||||
crv_array.data = curve.to_list()
|
crv_array.data = curve.to_list()
|
||||||
return crv_array
|
return crv_array
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_curves(cls, curves: List[Base]) -> 'CurveArray':
|
def from_curves(cls, curves: List[Base]) -> "CurveArray":
|
||||||
data = []
|
data = []
|
||||||
for curve in curves:
|
for curve in curves:
|
||||||
curve_list = curve.to_list()
|
curve_list = curve.to_list()
|
||||||
|
|||||||
@@ -391,6 +391,15 @@ class Mesh(
|
|||||||
area: float = None
|
area: float = None
|
||||||
volume: float = None
|
volume: float = None
|
||||||
|
|
||||||
|
def transform_to(self, transform: "Transform") -> "Mesh":
|
||||||
|
mesh = Mesh(vertices=transform.apply_to_points_values(self.vertices))
|
||||||
|
for attr in set(self.get_serializable_attributes()) - {"vertices"}:
|
||||||
|
orig_val = getattr(self, attr, None)
|
||||||
|
if orig_val:
|
||||||
|
setattr(mesh, attr, orig_val)
|
||||||
|
|
||||||
|
return mesh
|
||||||
|
|
||||||
|
|
||||||
class Surface(Base, speckle_type=GEOMETRY + "Surface"):
|
class Surface(Base, speckle_type=GEOMETRY + "Surface"):
|
||||||
degreeU: int = None
|
degreeU: int = None
|
||||||
|
|||||||
@@ -1,7 +1,28 @@
|
|||||||
|
from typing import List
|
||||||
|
from specklepy.objects.geometry import Point, Vector
|
||||||
from .base import Base
|
from .base import Base
|
||||||
|
|
||||||
OTHER = "Objects.Other."
|
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"):
|
class RenderMaterial(Base, speckle_type=OTHER + "RenderMaterial"):
|
||||||
name: str = None
|
name: str = None
|
||||||
@@ -10,3 +31,169 @@ class RenderMaterial(Base, speckle_type=OTHER + "RenderMaterial"):
|
|||||||
roughness: float = 1
|
roughness: float = 1
|
||||||
diffuse: int = -2894893 # light gray arbg
|
diffuse: int = -2894893 # light gray arbg
|
||||||
emissive: int = -16777216 # black 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
|
||||||
@@ -15,6 +15,7 @@ UNITS_STRINGS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
UNITS_ENCODINGS = {
|
UNITS_ENCODINGS = {
|
||||||
|
"none": 0,
|
||||||
"mm": 1,
|
"mm": 1,
|
||||||
"cm": 2,
|
"cm": 2,
|
||||||
"m": 3,
|
"m": 3,
|
||||||
|
|||||||
@@ -13,21 +13,68 @@ from .batch_sender import BatchSender
|
|||||||
|
|
||||||
|
|
||||||
class ServerTransport(AbstractTransport):
|
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"
|
_name = "RemoteTransport"
|
||||||
url: str = None
|
url: str = None
|
||||||
stream_id: str = None
|
stream_id: str = None
|
||||||
saved_obj_count: int = 0
|
saved_obj_count: int = 0
|
||||||
session: requests.Session = None
|
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)
|
super().__init__(**data)
|
||||||
# TODO: replace client with account or some other auth avenue
|
# TODO: replace client with account or some other auth avenue
|
||||||
if not client.me:
|
if client is None and token is None and url is None:
|
||||||
raise SpeckleException("The provided SpeckleClient was not authenticated.")
|
raise SpeckleException(
|
||||||
self.url = client.url
|
"You must provide either a client or a token and url to construct a ServerTransport."
|
||||||
self.stream_id = stream_id
|
)
|
||||||
|
|
||||||
|
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._batch_sender = BatchSender(
|
||||||
self.url, self.stream_id, token, max_batch_size_mb=1
|
self.url, self.stream_id, token, max_batch_size_mb=1
|
||||||
)
|
)
|
||||||
|
|||||||
+3
-5
@@ -1,5 +1,5 @@
|
|||||||
from contextlib import ExitStack as does_not_raise
|
from contextlib import ExitStack as does_not_raise
|
||||||
from typing import Dict, List
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from specklepy.api import operations
|
from specklepy.api import operations
|
||||||
@@ -87,9 +87,9 @@ class FrozenYoghurt(Base):
|
|||||||
"""Testing type checking"""
|
"""Testing type checking"""
|
||||||
|
|
||||||
servings: int
|
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
|
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
|
price: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
@@ -111,5 +111,3 @@ def test_type_checking() -> None:
|
|||||||
order.flavours = ["strawberry", "lychee", "peach", "pineapple"]
|
order.flavours = ["strawberry", "lychee", "peach", "pineapple"]
|
||||||
|
|
||||||
assert order.price == 7.0
|
assert order.price == 7.0
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class TestBranch:
|
|||||||
assert isinstance(branches, list)
|
assert isinstance(branches, list)
|
||||||
assert len(branches) == 2
|
assert len(branches) == 2
|
||||||
assert isinstance(branches[0], Branch)
|
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):
|
def test_branch_update(self, client, stream, branch, updated_branch):
|
||||||
updated = client.branch.update(
|
updated = client.branch.update(
|
||||||
|
|||||||
@@ -68,3 +68,20 @@ class TestCommit:
|
|||||||
deleted = client.commit.delete(stream_id=stream.id, commit_id=commit_id)
|
deleted = client.commit.delete(stream_id=stream.id, commit_id=commit_id)
|
||||||
|
|
||||||
assert deleted is True
|
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
|
||||||
|
|||||||
+113
-50
@@ -5,11 +5,28 @@ import pytest
|
|||||||
from specklepy.api import operations
|
from specklepy.api import operations
|
||||||
from specklepy.objects.base import Base
|
from specklepy.objects.base import Base
|
||||||
from specklepy.objects.encoding import CurveArray, ObjectArray
|
from specklepy.objects.encoding import CurveArray, ObjectArray
|
||||||
from specklepy.objects.geometry import (Arc, Box, Brep, BrepEdge, BrepFace,
|
from specklepy.objects.geometry import (
|
||||||
BrepLoop, BrepTrim, BrepTrimTypeEnum,
|
Arc,
|
||||||
Circle, Curve, Ellipse, Interval, Line,
|
Box,
|
||||||
Mesh, Plane, Point, Polycurve,
|
Brep,
|
||||||
Polyline, Surface, Vector)
|
BrepEdge,
|
||||||
|
BrepFace,
|
||||||
|
BrepLoop,
|
||||||
|
BrepTrim,
|
||||||
|
BrepTrimTypeEnum,
|
||||||
|
Circle,
|
||||||
|
Curve,
|
||||||
|
Ellipse,
|
||||||
|
Interval,
|
||||||
|
Line,
|
||||||
|
Mesh,
|
||||||
|
Plane,
|
||||||
|
Point,
|
||||||
|
Polycurve,
|
||||||
|
Polyline,
|
||||||
|
Surface,
|
||||||
|
Vector,
|
||||||
|
)
|
||||||
from specklepy.transports.memory import MemoryTransport
|
from specklepy.transports.memory import MemoryTransport
|
||||||
|
|
||||||
|
|
||||||
@@ -71,7 +88,7 @@ def arc(plane, interval):
|
|||||||
angleRadians=33,
|
angleRadians=33,
|
||||||
plane=plane,
|
plane=plane,
|
||||||
domain=interval,
|
domain=interval,
|
||||||
units='m',
|
units="m",
|
||||||
# These attributes are not handled in C#
|
# These attributes are not handled in C#
|
||||||
# bbox=None,
|
# bbox=None,
|
||||||
# area=None,
|
# area=None,
|
||||||
@@ -88,7 +105,7 @@ def circle(plane, interval):
|
|||||||
radius=22,
|
radius=22,
|
||||||
plane=plane,
|
plane=plane,
|
||||||
domain=interval,
|
domain=interval,
|
||||||
units='m',
|
units="m",
|
||||||
# These attributes are not handled in C#
|
# These attributes are not handled in C#
|
||||||
# bbox=None,
|
# bbox=None,
|
||||||
# area=None,
|
# area=None,
|
||||||
@@ -103,7 +120,7 @@ def ellipse(plane, interval):
|
|||||||
secondRadius=22,
|
secondRadius=22,
|
||||||
plane=plane,
|
plane=plane,
|
||||||
domain=interval,
|
domain=interval,
|
||||||
units='m',
|
units="m",
|
||||||
# These attributes are not handled in C#
|
# These attributes are not handled in C#
|
||||||
# trimDomain=None,
|
# trimDomain=None,
|
||||||
# bbox=None,
|
# bbox=None,
|
||||||
@@ -118,7 +135,7 @@ def polyline(interval):
|
|||||||
value=[22, 44, 54.3, 99, 232, 21],
|
value=[22, 44, 54.3, 99, 232, 21],
|
||||||
closed=True,
|
closed=True,
|
||||||
domain=interval,
|
domain=interval,
|
||||||
units='m',
|
units="m",
|
||||||
# These attributes are not handled in C#
|
# These attributes are not handled in C#
|
||||||
# bbox=None,
|
# bbox=None,
|
||||||
# area=None,
|
# area=None,
|
||||||
@@ -137,7 +154,7 @@ def curve(interval):
|
|||||||
points=[23, 21, 44, 43, 56, 76, 1, 3, 2],
|
points=[23, 21, 44, 43, 56, 76, 1, 3, 2],
|
||||||
weights=[23, 11, 23],
|
weights=[23, 11, 23],
|
||||||
knots=[22, 45, 76, 11],
|
knots=[22, 45, 76, 11],
|
||||||
units='m',
|
units="m",
|
||||||
# These attributes are not handled in C#
|
# These attributes are not handled in C#
|
||||||
# displayValue=None,
|
# displayValue=None,
|
||||||
# bbox=None,
|
# bbox=None,
|
||||||
@@ -152,7 +169,7 @@ def polycurve(interval, curve, polyline):
|
|||||||
segments=[curve, polyline],
|
segments=[curve, polyline],
|
||||||
domain=interval,
|
domain=interval,
|
||||||
closed=True,
|
closed=True,
|
||||||
units='m',
|
units="m",
|
||||||
# These attributes are not handled in C#
|
# These attributes are not handled in C#
|
||||||
# bbox=None,
|
# bbox=None,
|
||||||
# area=None,
|
# area=None,
|
||||||
@@ -187,7 +204,7 @@ def surface(interval):
|
|||||||
domainV=interval,
|
domainV=interval,
|
||||||
knotsU=[1.1, 2.2, 3.3, 4.4],
|
knotsU=[1.1, 2.2, 3.3, 4.4],
|
||||||
knotsV=[9, 8, 7, 6, 5, 4.4],
|
knotsV=[9, 8, 7, 6, 5, 4.4],
|
||||||
units='m',
|
units="m",
|
||||||
# These attributes are not handled in C#
|
# These attributes are not handled in C#
|
||||||
# bbox=None,
|
# bbox=None,
|
||||||
# area=None,
|
# area=None,
|
||||||
@@ -218,11 +235,7 @@ def brep_edge(interval):
|
|||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
def brep_loop():
|
def brep_loop():
|
||||||
return BrepLoop(
|
return BrepLoop(FaceIndex=5, TrimIndices=[3, 4, 5], Type="unknown")
|
||||||
FaceIndex=5,
|
|
||||||
TrimIndices=[3, 4, 5],
|
|
||||||
Type='unknown'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
@pytest.fixture()
|
||||||
@@ -235,7 +248,7 @@ def brep_trim():
|
|||||||
LoopIndex=4,
|
LoopIndex=4,
|
||||||
CurveIndex=7,
|
CurveIndex=7,
|
||||||
IsoStatus=6,
|
IsoStatus=6,
|
||||||
TrimType='Mated',
|
TrimType="Mated",
|
||||||
IsReversed=False,
|
IsReversed=False,
|
||||||
# These attributes are not handled in C#
|
# These attributes are not handled in C#
|
||||||
# Domain=None,
|
# Domain=None,
|
||||||
@@ -243,10 +256,21 @@ def brep_trim():
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def brep(mesh, box, surface, curve, polyline, circle, point,
|
def brep(
|
||||||
brep_edge, brep_loop, brep_trim, brep_face):
|
mesh,
|
||||||
|
box,
|
||||||
|
surface,
|
||||||
|
curve,
|
||||||
|
polyline,
|
||||||
|
circle,
|
||||||
|
point,
|
||||||
|
brep_edge,
|
||||||
|
brep_loop,
|
||||||
|
brep_trim,
|
||||||
|
brep_face,
|
||||||
|
):
|
||||||
return Brep(
|
return Brep(
|
||||||
provenance='pytest',
|
provenance="pytest",
|
||||||
bbox=box,
|
bbox=box,
|
||||||
area=32,
|
area=32,
|
||||||
volume=54,
|
volume=54,
|
||||||
@@ -265,33 +289,57 @@ def brep(mesh, box, surface, curve, polyline, circle, point,
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def geometry_objects_dict(point, vector, plane, line, arc,
|
def geometry_objects_dict(
|
||||||
circle, ellipse, polyline, curve,
|
point,
|
||||||
polycurve, surface, brep_trim):
|
vector,
|
||||||
|
plane,
|
||||||
|
line,
|
||||||
|
arc,
|
||||||
|
circle,
|
||||||
|
ellipse,
|
||||||
|
polyline,
|
||||||
|
curve,
|
||||||
|
polycurve,
|
||||||
|
surface,
|
||||||
|
brep_trim,
|
||||||
|
):
|
||||||
return {
|
return {
|
||||||
'point': point,
|
"point": point,
|
||||||
'vector': vector,
|
"vector": vector,
|
||||||
'plane': plane,
|
"plane": plane,
|
||||||
'line': line,
|
"line": line,
|
||||||
'arc': arc,
|
"arc": arc,
|
||||||
'circle': circle,
|
"circle": circle,
|
||||||
'ellipse': ellipse,
|
"ellipse": ellipse,
|
||||||
'polyline': polyline,
|
"polyline": polyline,
|
||||||
'curve': curve,
|
"curve": curve,
|
||||||
'polycurve': polycurve,
|
"polycurve": polycurve,
|
||||||
'surface': surface,
|
"surface": surface,
|
||||||
'brep_trim': brep_trim
|
"brep_trim": brep_trim,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('object_name', [
|
@pytest.mark.parametrize(
|
||||||
'point', 'vector', 'plane', 'line', 'arc', 'circle',
|
"object_name",
|
||||||
'ellipse', 'polyline', 'curve', 'polycurve', 'surface', 'brep_trim'
|
[
|
||||||
])
|
"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):
|
def test_to_and_from_list(object_name: str, geometry_objects_dict):
|
||||||
object = geometry_objects_dict[object_name]
|
object = geometry_objects_dict[object_name]
|
||||||
assert hasattr(object, 'to_list')
|
assert hasattr(object, "to_list")
|
||||||
assert hasattr(object, 'from_list')
|
assert hasattr(object, "from_list")
|
||||||
|
|
||||||
chunks = object.to_list()
|
chunks = object.to_list()
|
||||||
assert isinstance(chunks, list)
|
assert isinstance(chunks, list)
|
||||||
@@ -306,8 +354,7 @@ def test_brep_surfaces_value_serialization(surface):
|
|||||||
assert brep.Surfaces == None
|
assert brep.Surfaces == None
|
||||||
assert brep.SurfacesValue == None
|
assert brep.SurfacesValue == None
|
||||||
brep.Surfaces = [surface, surface]
|
brep.Surfaces = [surface, surface]
|
||||||
assert brep.SurfacesValue == ObjectArray.from_objects(
|
assert brep.SurfacesValue == ObjectArray.from_objects([surface, surface]).data
|
||||||
[surface, surface]).data
|
|
||||||
|
|
||||||
brep.SurfacesValue = ObjectArray.from_objects([surface]).data
|
brep.SurfacesValue = ObjectArray.from_objects([surface]).data
|
||||||
assert len(brep.Surfaces) == 1
|
assert len(brep.Surfaces) == 1
|
||||||
@@ -341,16 +388,32 @@ def test_brep_curve3d_values_serialization(curve, polyline, circle):
|
|||||||
def test_brep_vertices_values_serialization():
|
def test_brep_vertices_values_serialization():
|
||||||
brep = Brep()
|
brep = Brep()
|
||||||
brep.VerticesValue = [1, 1, 1, 1, 2, 2, 2, 3, 3, 3]
|
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[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[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[2].get_id() == Point(x=3, y=3, z=3, _units="mm").get_id()
|
||||||
|
|
||||||
|
|
||||||
def test_trims_value_serialization():
|
def test_trims_value_serialization():
|
||||||
brep = Brep()
|
brep = Brep()
|
||||||
brep.TrimsValue = [
|
brep.TrimsValue = [
|
||||||
0, 0, 0, 0, 0, 0, 1, 1, 1,
|
0,
|
||||||
1, 0, 0, 0, 0, 1, 2, 1, 0,
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
]
|
]
|
||||||
|
|
||||||
brep.Trims[0].get_id() == BrepTrim(
|
brep.Trims[0].get_id() == BrepTrim(
|
||||||
@@ -383,7 +446,7 @@ def test_serialized_brep_attributes(brep: Brep):
|
|||||||
serialized = operations.serialize(brep, [transport])
|
serialized = operations.serialize(brep, [transport])
|
||||||
serialized_dict = json.loads(serialized)
|
serialized_dict = json.loads(serialized)
|
||||||
|
|
||||||
removed_keys = ['Surfaces', 'Curve3D', 'Curve2D', 'Vertices', 'Trims']
|
removed_keys = ["Surfaces", "Curve3D", "Curve2D", "Vertices", "Trims"]
|
||||||
|
|
||||||
for k in removed_keys:
|
for k in removed_keys:
|
||||||
assert k not in serialized_dict.keys()
|
assert k not in serialized_dict.keys()
|
||||||
|
|||||||
+3
-11
@@ -21,11 +21,9 @@ class TestObject:
|
|||||||
|
|
||||||
def test_object_create(self, client, stream, base):
|
def test_object_create(self, client, stream, base):
|
||||||
transport = SQLiteTransport()
|
transport = SQLiteTransport()
|
||||||
s = BaseObjectSerializer(
|
s = BaseObjectSerializer(write_transports=[transport], read_transport=transport)
|
||||||
write_transports=[transport], read_transport=transport)
|
|
||||||
_, base_dict = s.traverse_base(base)
|
_, base_dict = s.traverse_base(base)
|
||||||
obj_id = client.object.create(
|
obj_id = client.object.create(stream_id=stream.id, objects=[base_dict])[0]
|
||||||
stream_id=stream.id, objects=[base_dict])[0]
|
|
||||||
|
|
||||||
assert isinstance(obj_id, str)
|
assert isinstance(obj_id, str)
|
||||||
assert base_dict["@detach"]["speckle_type"] == "reference"
|
assert base_dict["@detach"]["speckle_type"] == "reference"
|
||||||
@@ -43,12 +41,6 @@ class TestObject:
|
|||||||
|
|
||||||
def test_object_array_decoder(self):
|
def test_object_array_decoder(self):
|
||||||
array = ObjectArray()
|
array = ObjectArray()
|
||||||
array.data = [
|
array.data = [5, 1, 1, 1, 1, 1, 4, 1, 1, 1, 1, 3, 1, 1, 1, 2, 1, 1, 1, 1]
|
||||||
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]
|
assert array.decode(decoder=sum) == [5, 4, 3, 2, 1]
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import json
|
import json
|
||||||
from attr import has
|
|
||||||
import pytest
|
import pytest
|
||||||
from specklepy.api import operations
|
from specklepy.api import operations
|
||||||
from specklepy.transports.server import ServerTransport
|
from specklepy.transports.server import ServerTransport
|
||||||
@@ -53,6 +52,11 @@ class TestSerialization:
|
|||||||
def test_send_and_receive(self, client, sample_stream, mesh):
|
def test_send_and_receive(self, client, sample_stream, mesh):
|
||||||
transport = ServerTransport(client=client, stream_id=sample_stream.id)
|
transport = ServerTransport(client=client, stream_id=sample_stream.id)
|
||||||
hash = operations.send(mesh, transports=[transport])
|
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
|
# use a fresh memory transport to force receiving from remote
|
||||||
received = operations.receive(
|
received = operations.receive(
|
||||||
hash, remote_transport=transport, local_transport=MemoryTransport()
|
hash, remote_transport=transport, local_transport=MemoryTransport()
|
||||||
@@ -84,4 +88,4 @@ class TestSerialization:
|
|||||||
untyped = '{"foo": "bar"}'
|
untyped = '{"foo": "bar"}'
|
||||||
deserialised = operations.deserialize(untyped)
|
deserialised = operations.deserialize(untyped)
|
||||||
|
|
||||||
assert deserialised == {"foo": "bar"}
|
assert deserialised == {"foo": "bar"}
|
||||||
|
|||||||
@@ -0,0 +1,151 @@
|
|||||||
|
from typing import List
|
||||||
|
import pytest
|
||||||
|
from specklepy.api import operations
|
||||||
|
from specklepy.objects.geometry import Mesh, 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 mesh():
|
||||||
|
return Mesh(
|
||||||
|
vertices=[-7, 5, 1, -8, 4, 0, -7, 3, 0, -6, 4, 0],
|
||||||
|
faces=[1, 1, 2, 3, 0],
|
||||||
|
units="feet",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@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()
|
||||||
|
|
||||||
|
|
||||||
|
def test_mesh_transform(mesh: Mesh, transform: Transform):
|
||||||
|
new_mesh = mesh.transform_to(transform)
|
||||||
|
|
||||||
|
assert mesh.vertices != new_mesh.vertices
|
||||||
|
|
||||||
|
new_mesh.vertices = mesh.vertices
|
||||||
|
|
||||||
|
assert mesh.get_id() == new_mesh.get_id()
|
||||||
@@ -18,6 +18,14 @@ class TestWrapper:
|
|||||||
assert wacky_wrap.branch_name == "🍕⬅🌟 you wat?"
|
assert wacky_wrap.branch_name == "🍕⬅🌟 you wat?"
|
||||||
assert wrap.type == "branch"
|
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):
|
def test_parse_commit(self):
|
||||||
wrap = StreamWrapper(
|
wrap = StreamWrapper(
|
||||||
"https://testing.speckle.dev/streams/4c3ce1459c/commits/8b9b831792"
|
"https://testing.speckle.dev/streams/4c3ce1459c/commits/8b9b831792"
|
||||||
@@ -39,3 +47,33 @@ class TestWrapper:
|
|||||||
"https://testing.speckle.dev/streams/0c6ad366c4/globals/abd3787893"
|
"https://testing.speckle.dev/streams/0c6ad366c4/globals/abd3787893"
|
||||||
)
|
)
|
||||||
assert wrap.type == "commit"
|
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"
|
||||||
|
|||||||
Reference in New Issue
Block a user