Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e26e1077e0 | |||
| aa739c30c6 | |||
| 0a321c66fe | |||
| c112f46f01 | |||
| a4f764e178 | |||
| 59f5ee5452 | |||
| f8b057b990 | |||
| e2ba8b144a | |||
| 8d320abe00 | |||
| b77e346736 | |||
| 0d1c2735d8 | |||
| 8f3a683851 | |||
| aa8c7b6f42 | |||
| 68036ee130 | |||
| 447f28c9f1 | |||
| 1e7291277e | |||
| 46773aa9d3 | |||
| 480ea91ebb | |||
| 1c0d6ce8f4 | |||
| 1431e306b8 | |||
| 83bca13c8b | |||
| 1bcef9faf6 | |||
| 8d3e511d18 | |||
| 162f999100 | |||
| 2765c4fa69 | |||
| 69cb2c79c7 | |||
| e2daad36e9 | |||
| d6b06298ed | |||
| 7ddd827340 | |||
| 2a30278e04 | |||
| ecd9089e29 | |||
| bcecaef380 | |||
| 8e986e59aa | |||
| 9e110a125b | |||
| ec8635401b | |||
| f7b867c219 | |||
| e69310619e | |||
| 4a924593b3 | |||
| 1f57e81ddc | |||
| e42a3d4147 | |||
| d3d53ef6a5 | |||
| acb7156bf2 | |||
| 42cda6a477 | |||
| c1dfe5f11f | |||
| 7e57b4cfb6 | |||
| b87237b88f | |||
| fb797e64cb | |||
| 040a49baea | |||
| 105ae0316c |
@@ -1,39 +0,0 @@
|
|||||||
from typing import List
|
|
||||||
from specklepy.api.wrapper import StreamWrapper
|
|
||||||
from specklepy.objects import Base
|
|
||||||
from specklepy.api import operations
|
|
||||||
import string
|
|
||||||
import random
|
|
||||||
|
|
||||||
|
|
||||||
class Sub(Base):
|
|
||||||
bar: List[str]
|
|
||||||
|
|
||||||
|
|
||||||
def random_string():
|
|
||||||
letters = string.ascii_lowercase
|
|
||||||
return "".join(random.choice(letters) for _ in range(10))
|
|
||||||
|
|
||||||
|
|
||||||
def create_object(child_count: int) -> Base:
|
|
||||||
foo = Base()
|
|
||||||
for i in range(child_count):
|
|
||||||
stuff = random_string()
|
|
||||||
foo[f"@child_{i}"] = Sub(bar=["asdf", "bar", i, stuff])
|
|
||||||
return foo
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
|
|
||||||
stream_url = "http://hyperion:3000/streams/2372b54c35"
|
|
||||||
|
|
||||||
child_count = 10
|
|
||||||
foo = create_object(child_count)
|
|
||||||
|
|
||||||
wrapper = StreamWrapper(stream_url)
|
|
||||||
transport = wrapper.get_transport()
|
|
||||||
|
|
||||||
hash = operations.send(base=foo, transports=[transport], use_default_cache=False)
|
|
||||||
|
|
||||||
rec = operations.receive(hash, transport)
|
|
||||||
print(rec)
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
from specklepy.api import operations
|
|
||||||
from specklepy.objects.geometry import Base
|
|
||||||
from specklepy.objects.units import Units
|
|
||||||
|
|
||||||
dct = {
|
|
||||||
"id": "1234abcd",
|
|
||||||
"units": None,
|
|
||||||
"speckle_type": "Base",
|
|
||||||
"applicationId": None,
|
|
||||||
"totalChildrenCount": 0,
|
|
||||||
}
|
|
||||||
base = Base()
|
|
||||||
for prop, value in dct.items():
|
|
||||||
base.__setattr__(prop, value)
|
|
||||||
|
|
||||||
from devtools import debug
|
|
||||||
|
|
||||||
debug(base)
|
|
||||||
debug(base.units)
|
|
||||||
|
|
||||||
base.units = "m"
|
|
||||||
debug(base.units)
|
|
||||||
base.units = None
|
|
||||||
|
|
||||||
debug(base.units)
|
|
||||||
|
|
||||||
foo = operations.serialize(base)
|
|
||||||
|
|
||||||
base.units = 10
|
|
||||||
|
|
||||||
debug(base.units)
|
|
||||||
debug(foo)
|
|
||||||
|
|
||||||
base.units = Units.mm
|
|
||||||
debug(base.units)
|
|
||||||
Generated
+448
-504
File diff suppressed because it is too large
Load Diff
+5
-8
@@ -1,6 +1,6 @@
|
|||||||
[tool.poetry]
|
[tool.poetry]
|
||||||
name = "specklepy"
|
name = "specklepy"
|
||||||
version = "2.9.1"
|
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>"]
|
||||||
@@ -8,9 +8,6 @@ license = "Apache-2.0"
|
|||||||
repository = "https://github.com/specklesystems/speckle-py"
|
repository = "https://github.com/specklesystems/speckle-py"
|
||||||
documentation = "https://speckle.guide/dev/py-examples.html"
|
documentation = "https://speckle.guide/dev/py-examples.html"
|
||||||
homepage = "https://speckle.systems/"
|
homepage = "https://speckle.systems/"
|
||||||
packages = [
|
|
||||||
{ include = "specklepy", from = "src" },
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
@@ -21,15 +18,15 @@ gql = {extras = ["requests", "websockets"], version = "^3.3.0"}
|
|||||||
ujson = "^5.3.0"
|
ujson = "^5.3.0"
|
||||||
Deprecated = "^1.2.13"
|
Deprecated = "^1.2.13"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
black = "^22.8.0"
|
black = "^20.8b1"
|
||||||
isort = "^5.7.0"
|
isort = "^5.7.0"
|
||||||
pytest = "^7.1.3"
|
pytest = "^6.2.2"
|
||||||
pytest-ordering = "^0.6"
|
pytest-ordering = "^0.6"
|
||||||
pytest-cov = "^3.0.0"
|
pytest-cov = "^3.0.0"
|
||||||
devtools = "^0.8.0"
|
devtools = "^0.8.0"
|
||||||
pylint = "^2.14.4"
|
pylint = "^2.14.4"
|
||||||
mypy = "^0.982"
|
|
||||||
|
|
||||||
[tool.black]
|
[tool.black]
|
||||||
exclude = '''
|
exclude = '''
|
||||||
|
|||||||
@@ -18,9 +18,8 @@ from specklepy.api.resources import (
|
|||||||
server,
|
server,
|
||||||
user,
|
user,
|
||||||
subscriptions,
|
subscriptions,
|
||||||
other_user,
|
|
||||||
active_user
|
|
||||||
)
|
)
|
||||||
|
from specklepy.api.models import ServerInfo
|
||||||
from gql import Client
|
from gql import Client
|
||||||
from gql.transport.requests import RequestsHTTPTransport
|
from gql.transport.requests import RequestsHTTPTransport
|
||||||
from gql.transport.websockets import WebsocketsTransport
|
from gql.transport.websockets import WebsocketsTransport
|
||||||
@@ -137,8 +136,6 @@ class SpeckleClient:
|
|||||||
headers = {
|
headers = {
|
||||||
"Authorization": f"Bearer {self.account.token}",
|
"Authorization": f"Bearer {self.account.token}",
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
"apollographql-client-name": metrics.HOST_APP,
|
|
||||||
"apollographql-client-version": metrics.HOST_APP_VERSION,
|
|
||||||
}
|
}
|
||||||
httptransport = RequestsHTTPTransport(
|
httptransport = RequestsHTTPTransport(
|
||||||
url=self.graphql, headers=headers, verify=True, retries=3
|
url=self.graphql, headers=headers, verify=True, retries=3
|
||||||
@@ -177,18 +174,6 @@ class SpeckleClient:
|
|||||||
client=self.httpclient,
|
client=self.httpclient,
|
||||||
server_version=server_version,
|
server_version=server_version,
|
||||||
)
|
)
|
||||||
self.other_user = other_user.Resource(
|
|
||||||
account=self.account,
|
|
||||||
basepath=self.url,
|
|
||||||
client=self.httpclient,
|
|
||||||
server_version=server_version,
|
|
||||||
)
|
|
||||||
self.active_user = active_user.Resource(
|
|
||||||
account=self.account,
|
|
||||||
basepath=self.url,
|
|
||||||
client=self.httpclient,
|
|
||||||
server_version=server_version,
|
|
||||||
)
|
|
||||||
self.stream = stream.Resource(
|
self.stream = stream.Resource(
|
||||||
account=self.account,
|
account=self.account,
|
||||||
basepath=self.url,
|
basepath=self.url,
|
||||||
@@ -5,14 +5,13 @@ from specklepy.logging import metrics
|
|||||||
from specklepy.api.models import ServerInfo
|
from specklepy.api.models import ServerInfo
|
||||||
from specklepy.transports.sqlite import SQLiteTransport
|
from specklepy.transports.sqlite import SQLiteTransport
|
||||||
from specklepy.logging.exceptions import SpeckleException
|
from specklepy.logging.exceptions import SpeckleException
|
||||||
from specklepy import paths
|
|
||||||
|
|
||||||
|
|
||||||
class UserInfo(BaseModel):
|
class UserInfo(BaseModel):
|
||||||
name: Optional[str] = None
|
name: Optional[str]
|
||||||
email: Optional[str] = None
|
email: Optional[str]
|
||||||
company: Optional[str] = None
|
company: Optional[str]
|
||||||
id: Optional[str] = None
|
id: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
class Account(BaseModel):
|
class Account(BaseModel):
|
||||||
@@ -36,7 +35,7 @@ class Account(BaseModel):
|
|||||||
return acct
|
return acct
|
||||||
|
|
||||||
|
|
||||||
def get_local_accounts(base_path: Optional[str] = None) -> List[Account]:
|
def get_local_accounts(base_path: str = None) -> List[Account]:
|
||||||
"""Gets all the accounts present in this environment
|
"""Gets all the accounts present in this environment
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -45,30 +44,18 @@ def get_local_accounts(base_path: Optional[str] = None) -> List[Account]:
|
|||||||
Returns:
|
Returns:
|
||||||
List[Account] -- list of all local accounts or an empty list if no accounts were found
|
List[Account] -- list of all local accounts or an empty list if no accounts were found
|
||||||
"""
|
"""
|
||||||
|
account_storage = SQLiteTransport(scope="Accounts", base_path=base_path)
|
||||||
|
# pylint: disable=protected-access
|
||||||
|
json_path = os.path.join(account_storage._base_path, "Accounts")
|
||||||
|
os.makedirs(json_path, exist_ok=True)
|
||||||
|
json_acct_files = [file for file in os.listdir(json_path) if file.endswith(".json")]
|
||||||
|
|
||||||
accounts: List[Account] = []
|
accounts: List[Account] = []
|
||||||
try:
|
res = account_storage.get_all_objects()
|
||||||
account_storage = SQLiteTransport(scope="Accounts", base_path=base_path)
|
account_storage.close()
|
||||||
res = account_storage.get_all_objects()
|
|
||||||
account_storage.close()
|
|
||||||
if res:
|
|
||||||
accounts.extend(Account.parse_raw(r[1]) for r in res)
|
|
||||||
except SpeckleException:
|
|
||||||
# cannot open SQLiteTransport, probably because of the lack
|
|
||||||
# of disk write permissions
|
|
||||||
pass
|
|
||||||
|
|
||||||
json_acct_files = []
|
|
||||||
json_path = paths.accounts_path()
|
|
||||||
try:
|
|
||||||
os.makedirs(json_path, exist_ok=True)
|
|
||||||
json_acct_files.extend(
|
|
||||||
file for file in os.listdir(json_path) if file.endswith(".json")
|
|
||||||
)
|
|
||||||
|
|
||||||
except Exception:
|
|
||||||
# cannot find or get the json account paths
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
if res:
|
||||||
|
accounts.extend(Account.parse_raw(r[1]) for r in res)
|
||||||
if json_acct_files:
|
if json_acct_files:
|
||||||
try:
|
try:
|
||||||
accounts.extend(
|
accounts.extend(
|
||||||
@@ -92,7 +79,7 @@ def get_local_accounts(base_path: Optional[str] = None) -> List[Account]:
|
|||||||
return accounts
|
return accounts
|
||||||
|
|
||||||
|
|
||||||
def get_default_account(base_path: Optional[str] = None) -> Optional[Account]:
|
def get_default_account(base_path: str = None) -> Account:
|
||||||
"""Gets this environment's default account if any. If there is no default, the first found will be returned and set as default.
|
"""Gets this environment's default account if any. If there is no default, the first found will be returned and set as default.
|
||||||
Arguments:
|
Arguments:
|
||||||
base_path {str} -- custom base path if you are not using the system default
|
base_path {str} -- custom base path if you are not using the system default
|
||||||
@@ -1,8 +1,12 @@
|
|||||||
|
# generated by datamodel-codegen:
|
||||||
|
# filename: stream_schema.json
|
||||||
|
# timestamp: 2020-11-17T14:33:13+00:00
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
|
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel # pylint: disable=no-name-in-module
|
||||||
|
|
||||||
|
|
||||||
class Collaborator(BaseModel):
|
class Collaborator(BaseModel):
|
||||||
@@ -60,20 +64,20 @@ class Branches(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class Stream(BaseModel):
|
class Stream(BaseModel):
|
||||||
id: Optional[str] = None
|
id: Optional[str]
|
||||||
name: Optional[str]
|
name: Optional[str]
|
||||||
role: Optional[str] = None
|
role: Optional[str]
|
||||||
isPublic: Optional[bool] = None
|
isPublic: Optional[bool]
|
||||||
description: Optional[str] = None
|
description: Optional[str]
|
||||||
createdAt: Optional[datetime] = None
|
createdAt: Optional[datetime]
|
||||||
updatedAt: Optional[datetime] = None
|
updatedAt: Optional[datetime]
|
||||||
collaborators: List[Collaborator] = Field(default_factory=list)
|
collaborators: List[Collaborator] = []
|
||||||
branches: Optional[Branches] = None
|
branches: Optional[Branches]
|
||||||
commit: Optional[Commit] = None
|
commit: Optional[Commit]
|
||||||
object: Optional[Object] = None
|
object: Optional[Object]
|
||||||
commentCount: Optional[int] = None
|
commentCount: Optional[int]
|
||||||
favoritedDate: Optional[datetime] = None
|
favoritedDate: Optional[datetime]
|
||||||
favoritesCount: Optional[int] = None
|
favoritesCount: Optional[int]
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return f"Stream( id: {self.id}, name: {self.name}, description: {self.description}, isPublic: {self.isPublic})"
|
return f"Stream( id: {self.id}, name: {self.name}, description: {self.description}, isPublic: {self.isPublic})"
|
||||||
@@ -106,18 +110,6 @@ class User(BaseModel):
|
|||||||
return self.__repr__()
|
return self.__repr__()
|
||||||
|
|
||||||
|
|
||||||
class LimitedUser(BaseModel):
|
|
||||||
"""Limited user type, for showing public info about a user to another user."""
|
|
||||||
|
|
||||||
id: str
|
|
||||||
name: Optional[str]
|
|
||||||
bio: Optional[str]
|
|
||||||
company: Optional[str]
|
|
||||||
avatar: Optional[str]
|
|
||||||
verified: Optional[bool]
|
|
||||||
role: Optional[str]
|
|
||||||
|
|
||||||
|
|
||||||
class PendingStreamCollaborator(BaseModel):
|
class PendingStreamCollaborator(BaseModel):
|
||||||
id: Optional[str]
|
id: Optional[str]
|
||||||
inviteId: Optional[str]
|
inviteId: Optional[str]
|
||||||
@@ -166,13 +158,13 @@ class ActivityCollection(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class ServerInfo(BaseModel):
|
class ServerInfo(BaseModel):
|
||||||
name: Optional[str] = None
|
name: Optional[str]
|
||||||
company: Optional[str] = None
|
company: Optional[str]
|
||||||
url: Optional[str] = None
|
url: Optional[str]
|
||||||
description: Optional[str] = None
|
description: Optional[str]
|
||||||
adminContact: Optional[str] = None
|
adminContact: Optional[str]
|
||||||
canonicalUrl: Optional[str] = None
|
canonicalUrl: Optional[str]
|
||||||
roles: Optional[List[dict]] = None
|
roles: Optional[List[dict]]
|
||||||
scopes: Optional[List[dict]] = None
|
scopes: Optional[List[dict]]
|
||||||
authStrategies: Optional[List[dict]] = None
|
authStrategies: Optional[List[dict]]
|
||||||
version: Optional[str] = None
|
version: Optional[str]
|
||||||
@@ -2,6 +2,7 @@ from typing import List
|
|||||||
from specklepy.logging import metrics
|
from specklepy.logging import metrics
|
||||||
from specklepy.objects.base import Base
|
from specklepy.objects.base import Base
|
||||||
from specklepy.transports.sqlite import SQLiteTransport
|
from specklepy.transports.sqlite import SQLiteTransport
|
||||||
|
from specklepy.transports.server import ServerTransport
|
||||||
from specklepy.logging.exceptions import SpeckleException
|
from specklepy.logging.exceptions import SpeckleException
|
||||||
from specklepy.transports.abstract_transport import AbstractTransport
|
from specklepy.transports.abstract_transport import AbstractTransport
|
||||||
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
|
from specklepy.serialization.base_object_serializer import BaseObjectSerializer
|
||||||
@@ -52,16 +53,6 @@ def receive(
|
|||||||
remote_transport: AbstractTransport = None,
|
remote_transport: AbstractTransport = None,
|
||||||
local_transport: AbstractTransport = None,
|
local_transport: AbstractTransport = None,
|
||||||
) -> Base:
|
) -> Base:
|
||||||
metrics.track(metrics.RECEIVE, getattr(remote_transport, "account", None))
|
|
||||||
return _untracked_receive(obj_id, remote_transport, local_transport)
|
|
||||||
|
|
||||||
|
|
||||||
def _untracked_receive(
|
|
||||||
obj_id: str,
|
|
||||||
remote_transport: AbstractTransport = None,
|
|
||||||
local_transport: AbstractTransport = None,
|
|
||||||
) -> Base:
|
|
||||||
|
|
||||||
"""Receives an object from a transport.
|
"""Receives an object from a transport.
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
@@ -73,12 +64,13 @@ def _untracked_receive(
|
|||||||
Returns:
|
Returns:
|
||||||
Base -- the base object
|
Base -- the base object
|
||||||
"""
|
"""
|
||||||
|
metrics.track(metrics.RECEIVE, getattr(remote_transport, "account", None))
|
||||||
if not local_transport:
|
if not local_transport:
|
||||||
local_transport = SQLiteTransport()
|
local_transport = SQLiteTransport()
|
||||||
|
|
||||||
serializer = BaseObjectSerializer(read_transport=local_transport)
|
serializer = BaseObjectSerializer(read_transport=local_transport)
|
||||||
|
|
||||||
# try local transport first. if the parent is there, we assume all the children are there and continue with deserialization using the local transport
|
# try local transport first. if the parent is there, we assume all the children are there and continue with deserialisation using the local transport
|
||||||
obj_string = local_transport.get_object(obj_id)
|
obj_string = local_transport.get_object(obj_id)
|
||||||
if obj_string:
|
if obj_string:
|
||||||
return serializer.read_json(obj_string=obj_string)
|
return serializer.read_json(obj_string=obj_string)
|
||||||
@@ -132,6 +124,3 @@ def deserialize(obj_string: str, read_transport: AbstractTransport = None) -> Ba
|
|||||||
serializer = BaseObjectSerializer(read_transport=read_transport)
|
serializer = BaseObjectSerializer(read_transport=read_transport)
|
||||||
|
|
||||||
return serializer.read_json(obj_string=obj_string)
|
return serializer.read_json(obj_string=obj_string)
|
||||||
|
|
||||||
|
|
||||||
__all__ = [receive.__name__, send.__name__, serialize.__name__, deserialize.__name__]
|
|
||||||
@@ -97,10 +97,7 @@ class ResourceBase(object):
|
|||||||
eg (2, 6, 3) for a stable build and (2, 6, 4, 'alpha', 4711) for alpha
|
eg (2, 6, 3) for a stable build and (2, 6, 4, 'alpha', 4711) for alpha
|
||||||
"""
|
"""
|
||||||
if not unsupported_message:
|
if not unsupported_message:
|
||||||
unsupported_message = f"The client method used is not supported on Speckle Server versions prior to v{'.'.join(target_version)}"
|
unsupported_message = f"The client method used is not supported on Speckle Server versios prior to v{'.'.join(target_version)}"
|
||||||
# if version is dev, it should be supported... (or not)
|
|
||||||
if self.server_version == ("dev",):
|
|
||||||
return
|
|
||||||
if self.server_version and self.server_version < target_version:
|
if self.server_version and self.server_version < target_version:
|
||||||
raise UnsupportedException(unsupported_message)
|
raise UnsupportedException(unsupported_message)
|
||||||
|
|
||||||
@@ -363,11 +363,7 @@ class Resource(ResourceBase):
|
|||||||
bool -- True if the operation was successful
|
bool -- True if the operation was successful
|
||||||
"""
|
"""
|
||||||
metrics.track(metrics.PERMISSION, self.account, {"name": "add", "role": role})
|
metrics.track(metrics.PERMISSION, self.account, {"name": "add", "role": role})
|
||||||
# we're checking for the actual version info, and if the version is 'dev' we treat it
|
if self.server_version and self.server_version >= (2, 6, 4):
|
||||||
# as an up to date instance
|
|
||||||
if self.server_version and (
|
|
||||||
self.server_version == ("dev",) or self.server_version >= (2, 6, 4)
|
|
||||||
):
|
|
||||||
raise UnsupportedException(
|
raise UnsupportedException(
|
||||||
(
|
(
|
||||||
"Server mutation `grant_permission` is no longer supported as of Speckle Server v2.6.4. "
|
"Server mutation `grant_permission` is no longer supported as of Speckle Server v2.6.4. "
|
||||||
@@ -470,7 +466,7 @@ class Resource(ResourceBase):
|
|||||||
stream_id {str} -- the id of the stream to invite the user to
|
stream_id {str} -- the id of the stream to invite the user to
|
||||||
email {str} -- the email of the user to invite (use this OR `user_id`)
|
email {str} -- the email of the user to invite (use this OR `user_id`)
|
||||||
user_id {str} -- the id of the user to invite (use this OR `email`)
|
user_id {str} -- the id of the user to invite (use this OR `email`)
|
||||||
role {str} -- the role to assign to the user (defaults to `stream:contributor`)
|
role {str} -- the role to assing to the user (defaults to `stream:contributor`)
|
||||||
message {str} -- a message to send along with this invite to the specified user
|
message {str} -- a message to send along with this invite to the specified user
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
@@ -645,9 +641,7 @@ class Resource(ResourceBase):
|
|||||||
metrics.track(
|
metrics.track(
|
||||||
metrics.PERMISSION, self.account, {"name": "update", "role": role}
|
metrics.PERMISSION, self.account, {"name": "update", "role": role}
|
||||||
)
|
)
|
||||||
if self.server_version and (
|
if self.server_version and self.server_version < (2, 6, 4):
|
||||||
self.server_version != ("dev",) and self.server_version < (2, 6, 4)
|
|
||||||
):
|
|
||||||
raise UnsupportedException(
|
raise UnsupportedException(
|
||||||
(
|
(
|
||||||
"Server mutation `update_permission` is only supported as of Speckle Server v2.6.4. "
|
"Server mutation `update_permission` is only supported as of Speckle Server v2.6.4. "
|
||||||
@@ -5,14 +5,9 @@ from specklepy.logging import metrics
|
|||||||
from specklepy.logging.exceptions import SpeckleException
|
from specklepy.logging.exceptions import SpeckleException
|
||||||
from specklepy.api.resource import ResourceBase
|
from specklepy.api.resource import ResourceBase
|
||||||
from specklepy.api.models import ActivityCollection, PendingStreamCollaborator, User
|
from specklepy.api.models import ActivityCollection, PendingStreamCollaborator, User
|
||||||
from deprecated import deprecated
|
|
||||||
|
|
||||||
|
|
||||||
NAME = "user"
|
NAME = "user"
|
||||||
|
|
||||||
DEPRECATION_VERSION = "2.9.0"
|
|
||||||
DEPRECATION_TEXT = "The user resource is deprecated, please use the active_user or other_user resources"
|
|
||||||
|
|
||||||
|
|
||||||
class Resource(ResourceBase):
|
class Resource(ResourceBase):
|
||||||
"""API Access class for users"""
|
"""API Access class for users"""
|
||||||
@@ -27,12 +22,8 @@ class Resource(ResourceBase):
|
|||||||
)
|
)
|
||||||
self.schema = User
|
self.schema = User
|
||||||
|
|
||||||
@deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT)
|
def get(self, id: str = None) -> User:
|
||||||
def get(self, id: Optional[str] = None) -> User:
|
"""Gets the profile of a user. If no id argument is provided, will return the current authenticated user's profile (as extracted from the authorization header).
|
||||||
"""
|
|
||||||
Gets the profile of a user.
|
|
||||||
If no id argument is provided, will return the current authenticated
|
|
||||||
user's profile (as extracted from the authorization header).
|
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
id {str} -- the user id
|
id {str} -- the user id
|
||||||
@@ -63,7 +54,6 @@ class Resource(ResourceBase):
|
|||||||
|
|
||||||
return self.make_request(query=query, params=params, return_type="user")
|
return self.make_request(query=query, params=params, return_type="user")
|
||||||
|
|
||||||
@deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT)
|
|
||||||
def search(
|
def search(
|
||||||
self, search_query: str, limit: int = 25
|
self, search_query: str, limit: int = 25
|
||||||
) -> Union[List[User], SpeckleException]:
|
) -> Union[List[User], SpeckleException]:
|
||||||
@@ -103,13 +93,8 @@ class Resource(ResourceBase):
|
|||||||
query=query, params=params, return_type=["userSearch", "items"]
|
query=query, params=params, return_type=["userSearch", "items"]
|
||||||
)
|
)
|
||||||
|
|
||||||
@deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT)
|
|
||||||
def update(
|
def update(
|
||||||
self,
|
self, name: str = None, company: str = None, bio: str = None, avatar: str = None
|
||||||
name: Optional[str] = None,
|
|
||||||
company: Optional[str] = None,
|
|
||||||
bio: Optional[str] = None,
|
|
||||||
avatar: Optional[str] = None,
|
|
||||||
):
|
):
|
||||||
"""Updates your user profile. All arguments are optional.
|
"""Updates your user profile. All arguments are optional.
|
||||||
|
|
||||||
@@ -143,15 +128,14 @@ class Resource(ResourceBase):
|
|||||||
query=query, params=params, return_type="userUpdate", parse_response=False
|
query=query, params=params, return_type="userUpdate", parse_response=False
|
||||||
)
|
)
|
||||||
|
|
||||||
@deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT)
|
|
||||||
def activity(
|
def activity(
|
||||||
self,
|
self,
|
||||||
user_id: Optional[str] = None,
|
user_id: str = None,
|
||||||
limit: int = 20,
|
limit: int = 20,
|
||||||
action_type: Optional[str] = None,
|
action_type: str = None,
|
||||||
before: Optional[datetime] = None,
|
before: datetime = None,
|
||||||
after: Optional[datetime] = None,
|
after: datetime = None,
|
||||||
cursor: Optional[datetime] = None,
|
cursor: datetime = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Get the activity from a given stream in an Activity collection. Step into the activity `items` for the list of activity.
|
Get the activity from a given stream in an Activity collection. Step into the activity `items` for the list of activity.
|
||||||
@@ -206,7 +190,6 @@ class Resource(ResourceBase):
|
|||||||
schema=ActivityCollection,
|
schema=ActivityCollection,
|
||||||
)
|
)
|
||||||
|
|
||||||
@deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT)
|
|
||||||
def get_all_pending_invites(self) -> List[PendingStreamCollaborator]:
|
def get_all_pending_invites(self) -> List[PendingStreamCollaborator]:
|
||||||
"""Get all of the active user's pending stream invites
|
"""Get all of the active user's pending stream invites
|
||||||
|
|
||||||
@@ -246,9 +229,8 @@ class Resource(ResourceBase):
|
|||||||
schema=PendingStreamCollaborator,
|
schema=PendingStreamCollaborator,
|
||||||
)
|
)
|
||||||
|
|
||||||
@deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT)
|
|
||||||
def get_pending_invite(
|
def get_pending_invite(
|
||||||
self, stream_id: str, token: Optional[str] = None
|
self, stream_id: str, token: str = None
|
||||||
) -> Optional[PendingStreamCollaborator]:
|
) -> Optional[PendingStreamCollaborator]:
|
||||||
"""Get a particular pending invite for the active user on a given stream.
|
"""Get a particular pending invite for the active user on a given stream.
|
||||||
If no invite_id is provided, any valid invite will be returned.
|
If no invite_id is provided, any valid invite will be returned.
|
||||||
@@ -0,0 +1,640 @@
|
|||||||
|
|
||||||
|
|
||||||
|
scalar DateTime
|
||||||
|
|
||||||
|
scalar EmailAddress
|
||||||
|
|
||||||
|
scalar BigInt
|
||||||
|
|
||||||
|
scalar JSONObject
|
||||||
|
|
||||||
|
|
||||||
|
directive @hasScope(scope: String!) on FIELD_DEFINITION
|
||||||
|
directive @hasRole(role: String!) on FIELD_DEFINITION
|
||||||
|
|
||||||
|
type Query {
|
||||||
|
"""
|
||||||
|
Stare into the void.
|
||||||
|
"""
|
||||||
|
_: String
|
||||||
|
}
|
||||||
|
type Mutation{
|
||||||
|
"""
|
||||||
|
The void stares back.
|
||||||
|
"""
|
||||||
|
_: String
|
||||||
|
}
|
||||||
|
type Subscription{
|
||||||
|
"""
|
||||||
|
It's lonely in the void.
|
||||||
|
"""
|
||||||
|
_: String
|
||||||
|
},extend type Query {
|
||||||
|
"""
|
||||||
|
Gets a specific app from the server.
|
||||||
|
"""
|
||||||
|
app( id: String! ): ServerApp
|
||||||
|
|
||||||
|
"""
|
||||||
|
Returns all the publicly available apps on this server.
|
||||||
|
"""
|
||||||
|
apps: [ServerAppListItem]
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerApp {
|
||||||
|
id: String!
|
||||||
|
secret: String!
|
||||||
|
name: String!
|
||||||
|
description: String
|
||||||
|
termsAndConditionsLink: String
|
||||||
|
logo: String
|
||||||
|
public: Boolean
|
||||||
|
trustByDefault: Boolean
|
||||||
|
author: AppAuthor
|
||||||
|
createdAt: DateTime!
|
||||||
|
redirectUrl: String!
|
||||||
|
scopes: [Scope]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type ServerAppListItem {
|
||||||
|
id: String!
|
||||||
|
name: String!
|
||||||
|
description: String
|
||||||
|
termsAndConditionsLink: String
|
||||||
|
logo: String
|
||||||
|
author: AppAuthor
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppAuthor {
|
||||||
|
name: String
|
||||||
|
id: String
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type User {
|
||||||
|
"""
|
||||||
|
Returns the apps you have authorized.
|
||||||
|
"""
|
||||||
|
authorizedApps: [ServerAppListItem]
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "apps:read")
|
||||||
|
|
||||||
|
"""
|
||||||
|
Returns the apps you have created.
|
||||||
|
"""
|
||||||
|
createdApps: [ServerAppListItem]
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "apps:read")
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type Mutation {
|
||||||
|
"""
|
||||||
|
Register a new third party application.
|
||||||
|
"""
|
||||||
|
appCreate(app: AppCreateInput!): String!
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "apps:write")
|
||||||
|
|
||||||
|
"""
|
||||||
|
Update an existing third party application. **Note: This will invalidate all existing tokens, refresh tokens and access codes and will require existing users to re-authorize it.**
|
||||||
|
"""
|
||||||
|
appUpdate(app: AppUpdateInput!): Boolean!
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "apps:write")
|
||||||
|
|
||||||
|
"""
|
||||||
|
Deletes a thirty party application.
|
||||||
|
"""
|
||||||
|
appDelete(appId: String!): Boolean!
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "apps:write")
|
||||||
|
|
||||||
|
"""
|
||||||
|
Revokes (de-authorizes) an application that you have previously authorized.
|
||||||
|
"""
|
||||||
|
appRevokeAccess(appId: String!): Boolean
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "apps:write")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
input AppCreateInput {
|
||||||
|
name: String!
|
||||||
|
description: String!
|
||||||
|
termsAndConditionsLink: String
|
||||||
|
logo: String
|
||||||
|
public: Boolean
|
||||||
|
redirectUrl: String!
|
||||||
|
scopes: [String]!
|
||||||
|
}
|
||||||
|
|
||||||
|
input AppUpdateInput {
|
||||||
|
id: String!
|
||||||
|
name: String!
|
||||||
|
description: String!
|
||||||
|
termsAndConditionsLink: String
|
||||||
|
logo: String
|
||||||
|
public: Boolean
|
||||||
|
redirectUrl: String!
|
||||||
|
scopes: [String]!
|
||||||
|
}
|
||||||
|
,extend type ServerInfo {
|
||||||
|
"""
|
||||||
|
The authentication strategies available on this server.
|
||||||
|
"""
|
||||||
|
authStrategies: [AuthStrategy]
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthStrategy {
|
||||||
|
id: String!,
|
||||||
|
name: String!,
|
||||||
|
icon: String!,
|
||||||
|
url: String!,
|
||||||
|
color: String
|
||||||
|
}
|
||||||
|
,extend type User{
|
||||||
|
"""
|
||||||
|
Returns a list of your personal api tokens.
|
||||||
|
"""
|
||||||
|
apiTokens: [ApiToken]
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "tokens:read")
|
||||||
|
}
|
||||||
|
|
||||||
|
type ApiToken {
|
||||||
|
id: String!
|
||||||
|
name: String!
|
||||||
|
lastChars: String!
|
||||||
|
scopes: [String]!
|
||||||
|
createdAt: DateTime! #date
|
||||||
|
lifespan: BigInt!
|
||||||
|
lastUsed: String! #date
|
||||||
|
}
|
||||||
|
|
||||||
|
input ApiTokenCreateInput {
|
||||||
|
scopes: [String!]!,
|
||||||
|
name: String!,
|
||||||
|
lifespan: BigInt
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type Mutation {
|
||||||
|
"""
|
||||||
|
Creates an personal api token.
|
||||||
|
"""
|
||||||
|
apiTokenCreate(token: ApiTokenCreateInput!):String!
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "tokens:write")
|
||||||
|
"""
|
||||||
|
Revokes (deletes) an personal api token.
|
||||||
|
"""
|
||||||
|
apiTokenRevoke(token: String!):Boolean!
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "tokens:write")
|
||||||
|
}
|
||||||
|
,extend type Stream {
|
||||||
|
commits(limit: Int! = 25, cursor: String): CommitCollection
|
||||||
|
commit(id: String!): Commit
|
||||||
|
branches(limit: Int! = 25, cursor: String): BranchCollection
|
||||||
|
branch(name: String!): Branch
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type User {
|
||||||
|
commits(limit: Int! = 25, cursor: String): CommitCollectionUser
|
||||||
|
}
|
||||||
|
|
||||||
|
type Branch {
|
||||||
|
id: String!
|
||||||
|
name: String!
|
||||||
|
author: User!
|
||||||
|
description: String
|
||||||
|
commits(limit: Int! = 25, cursor: String): CommitCollection
|
||||||
|
}
|
||||||
|
|
||||||
|
type Commit {
|
||||||
|
id: String!
|
||||||
|
referencedObject: String!
|
||||||
|
message: String
|
||||||
|
authorName: String
|
||||||
|
authorId: String
|
||||||
|
createdAt: DateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommitCollectionUserNode {
|
||||||
|
id: String!
|
||||||
|
referencedObject: String!
|
||||||
|
message: String
|
||||||
|
streamId: String
|
||||||
|
streamName: String
|
||||||
|
createdAt: DateTime
|
||||||
|
}
|
||||||
|
|
||||||
|
type BranchCollection {
|
||||||
|
totalCount: Int!
|
||||||
|
cursor: String
|
||||||
|
items: [Branch]
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommitCollection {
|
||||||
|
totalCount: Int!
|
||||||
|
cursor: String
|
||||||
|
items: [Commit]
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommitCollectionUser {
|
||||||
|
totalCount: Int!
|
||||||
|
cursor: String
|
||||||
|
items: [CommitCollectionUserNode]
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type Mutation {
|
||||||
|
branchCreate(branch: BranchCreateInput!): String!
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "streams:write")
|
||||||
|
branchUpdate(branch: BranchUpdateInput!): Boolean!
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "streams:write")
|
||||||
|
branchDelete(branch: BranchDeleteInput!): Boolean!
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "streams:write")
|
||||||
|
|
||||||
|
commitCreate(commit: CommitCreateInput!): String!
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "streams:write")
|
||||||
|
commitUpdate(commit: CommitUpdateInput!): Boolean!
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "streams:write")
|
||||||
|
commitDelete(commit: CommitDeleteInput!): Boolean!
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "streams:write")
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type Subscription {
|
||||||
|
# TODO: auth for these subscriptions
|
||||||
|
"""
|
||||||
|
Subscribe to branch created event
|
||||||
|
"""
|
||||||
|
branchCreated(streamId: String!): JSONObject
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "streams:read")
|
||||||
|
"""
|
||||||
|
Subscribe to branch updated event.
|
||||||
|
"""
|
||||||
|
branchUpdated(streamId: String!, branchId: String): JSONObject
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "streams:read")
|
||||||
|
"""
|
||||||
|
Subscribe to branch deleted event
|
||||||
|
"""
|
||||||
|
branchDeleted(streamId: String!): JSONObject
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "streams:read")
|
||||||
|
|
||||||
|
"""
|
||||||
|
Subscribe to commit created event
|
||||||
|
"""
|
||||||
|
commitCreated(streamId: String!): JSONObject
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "streams:read")
|
||||||
|
"""
|
||||||
|
Subscribe to commit updated event.
|
||||||
|
"""
|
||||||
|
commitUpdated(streamId: String!, commitId: String): JSONObject
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "streams:read")
|
||||||
|
"""
|
||||||
|
Subscribe to commit deleted event
|
||||||
|
"""
|
||||||
|
commitDeleted(streamId: String!): JSONObject
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "streams:read")
|
||||||
|
}
|
||||||
|
|
||||||
|
input BranchCreateInput {
|
||||||
|
streamId: String!
|
||||||
|
name: String!
|
||||||
|
description: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input BranchUpdateInput {
|
||||||
|
streamId: String!
|
||||||
|
id: String!
|
||||||
|
name: String
|
||||||
|
description: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input BranchDeleteInput {
|
||||||
|
streamId: String!
|
||||||
|
id: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input CommitCreateInput {
|
||||||
|
streamId: String!
|
||||||
|
branchName: String!
|
||||||
|
objectId: String!
|
||||||
|
message: String
|
||||||
|
previousCommitIds: [String]
|
||||||
|
}
|
||||||
|
|
||||||
|
input CommitUpdateInput {
|
||||||
|
streamId: String!
|
||||||
|
id: String!
|
||||||
|
message: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input CommitDeleteInput {
|
||||||
|
streamId: String!
|
||||||
|
id: String!
|
||||||
|
}
|
||||||
|
,extend type Stream {
|
||||||
|
object( id: String! ): Object
|
||||||
|
}
|
||||||
|
|
||||||
|
type Object {
|
||||||
|
id: String!
|
||||||
|
speckleType: String!
|
||||||
|
applicationId: String
|
||||||
|
createdAt: DateTime
|
||||||
|
totalChildrenCount: Int
|
||||||
|
"""
|
||||||
|
The full object, with all its props & other things. **NOTE:** If you're requesting objects for the purpose of recreating & displaying, you probably only want to request this specific field.
|
||||||
|
"""
|
||||||
|
data: JSONObject
|
||||||
|
"""
|
||||||
|
Get any objects that this object references. In the case of commits, this will give you a commit's constituent objects.
|
||||||
|
**NOTE**: Providing any of the two last arguments ( `query`, `orderBy` ) will trigger a different code branch that executes a much more expensive SQL query. It is not recommended to do so for basic clients that are interested in purely getting all the objects of a given commit.
|
||||||
|
"""
|
||||||
|
children(
|
||||||
|
limit: Int! = 100,
|
||||||
|
depth: Int! = 50,
|
||||||
|
select: [String],
|
||||||
|
cursor: String,
|
||||||
|
query: [JSONObject!],
|
||||||
|
orderBy: JSONObject ): ObjectCollection!
|
||||||
|
}
|
||||||
|
|
||||||
|
type ObjectCollection {
|
||||||
|
totalCount: Int!
|
||||||
|
cursor: String
|
||||||
|
objects: [Object]!
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type Mutation {
|
||||||
|
objectCreate( objectInput: ObjectCreateInput! ): [String]!
|
||||||
|
}
|
||||||
|
|
||||||
|
input ObjectCreateInput {
|
||||||
|
"""
|
||||||
|
The stream against which these objects will be created.
|
||||||
|
"""
|
||||||
|
streamId: String!
|
||||||
|
"""
|
||||||
|
The objects you want to create.
|
||||||
|
"""
|
||||||
|
objects: [JSONObject]!
|
||||||
|
},extend type Query {
|
||||||
|
serverInfo: ServerInfo!
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Information about this server.
|
||||||
|
"""
|
||||||
|
type ServerInfo {
|
||||||
|
name: String!
|
||||||
|
company: String
|
||||||
|
description: String
|
||||||
|
adminContact: String
|
||||||
|
canonicalUrl: String
|
||||||
|
termsOfService: String
|
||||||
|
roles: [Role]!
|
||||||
|
scopes: [Scope]!
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Available roles.
|
||||||
|
"""
|
||||||
|
type Role {
|
||||||
|
name: String!
|
||||||
|
description: String!
|
||||||
|
resourceTarget: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Available scopes.
|
||||||
|
"""
|
||||||
|
type Scope {
|
||||||
|
name: String!
|
||||||
|
description: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type Mutation {
|
||||||
|
serverInfoUpdate(info: ServerInfoUpdateInput!): Boolean
|
||||||
|
@hasRole(role: "server:admin")
|
||||||
|
@hasScope(scope: "server:setup")
|
||||||
|
}
|
||||||
|
|
||||||
|
input ServerInfoUpdateInput {
|
||||||
|
name: String!
|
||||||
|
company: String
|
||||||
|
description: String
|
||||||
|
adminContact: String
|
||||||
|
termsOfService: String
|
||||||
|
}
|
||||||
|
,extend type Query {
|
||||||
|
"""
|
||||||
|
Returns a specific stream.
|
||||||
|
"""
|
||||||
|
stream( id: String! ): Stream
|
||||||
|
|
||||||
|
"""
|
||||||
|
All the streams of the current user, pass in the `query` parameter to search by name, description or ID.
|
||||||
|
"""
|
||||||
|
streams( query: String, limit: Int = 25, cursor: String ): StreamCollection
|
||||||
|
@hasScope(scope: "streams:read")
|
||||||
|
}
|
||||||
|
|
||||||
|
type Stream {
|
||||||
|
id: String!
|
||||||
|
name: String!
|
||||||
|
description: String
|
||||||
|
isPublic: Boolean!
|
||||||
|
createdAt: DateTime!
|
||||||
|
updatedAt: DateTime!
|
||||||
|
collaborators: [ StreamCollaborator ]!
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type User {
|
||||||
|
"""
|
||||||
|
All the streams that a user has access to.
|
||||||
|
"""
|
||||||
|
streams( limit: Int! = 25, cursor: String ): StreamCollection
|
||||||
|
}
|
||||||
|
|
||||||
|
type StreamCollaborator {
|
||||||
|
id: String!
|
||||||
|
name: String!
|
||||||
|
role: String!
|
||||||
|
company: String
|
||||||
|
avatar: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type StreamCollection {
|
||||||
|
totalCount: Int!
|
||||||
|
cursor: String
|
||||||
|
items: [ Stream ]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extend type Mutation {
|
||||||
|
"""
|
||||||
|
Creates a new stream.
|
||||||
|
"""
|
||||||
|
streamCreate( stream: StreamCreateInput! ): String
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "streams:write")
|
||||||
|
"""
|
||||||
|
Updates an existing stream.
|
||||||
|
"""
|
||||||
|
streamUpdate( stream: StreamUpdateInput! ): Boolean!
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "streams:write")
|
||||||
|
"""
|
||||||
|
Deletes an existing stream.
|
||||||
|
"""
|
||||||
|
streamDelete( id: String! ): Boolean!
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "streams:write")
|
||||||
|
"""
|
||||||
|
Grants permissions to a user on a given stream.
|
||||||
|
"""
|
||||||
|
streamGrantPermission( permissionParams: StreamGrantPermissionInput! ): Boolean
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "streams:write")
|
||||||
|
"""
|
||||||
|
Revokes the permissions of a user on a given stream.
|
||||||
|
"""
|
||||||
|
streamRevokePermission( permissionParams: StreamRevokePermissionInput! ): Boolean
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "streams:write")
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type Subscription {
|
||||||
|
|
||||||
|
#
|
||||||
|
# User bound subscriptions that operate on the stream collection of an user
|
||||||
|
# Example relevant view/usecase: updating the list of streams for a user.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Subscribes to new stream added event for your profile. Use this to display an up-to-date list of streams.
|
||||||
|
**NOTE**: If someone shares a stream with you, this subscription will be triggered with an extra value of `sharedBy` in the payload.
|
||||||
|
"""
|
||||||
|
userStreamAdded: JSONObject
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "profile:read")
|
||||||
|
|
||||||
|
"""
|
||||||
|
Subscribes to stream removed event for your profile. Use this to display an up-to-date list of streams for your profile.
|
||||||
|
**NOTE**: If someone revokes your permissions on a stream, this subscription will be triggered with an extra value of `revokedBy` in the payload.
|
||||||
|
"""
|
||||||
|
userStreamRemoved: JSONObject
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "profile:read")
|
||||||
|
|
||||||
|
#
|
||||||
|
# Stream bound subscriptions that operate on the stream itself.
|
||||||
|
# Example relevant view/usecase: a single stream connector, or view, or component in a web app
|
||||||
|
#
|
||||||
|
|
||||||
|
"""
|
||||||
|
Subscribes to stream updated event. Use this in clients/components that pertain only to this stream.
|
||||||
|
"""
|
||||||
|
streamUpdated( streamId: String ): JSONObject
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "streams:read")
|
||||||
|
|
||||||
|
"""
|
||||||
|
Subscribes to stream deleted event. Use this in clients/components that pertain only to this stream.
|
||||||
|
"""
|
||||||
|
streamDeleted( streamId: String ): JSONObject
|
||||||
|
@hasRole(role: "server:user")
|
||||||
|
@hasScope(scope: "streams:read")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
input StreamCreateInput {
|
||||||
|
name: String
|
||||||
|
description: String
|
||||||
|
isPublic: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
input StreamUpdateInput {
|
||||||
|
id: String!
|
||||||
|
name: String
|
||||||
|
description: String
|
||||||
|
isPublic: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
input StreamGrantPermissionInput {
|
||||||
|
streamId: String!,
|
||||||
|
userId: String!,
|
||||||
|
role: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input StreamRevokePermissionInput {
|
||||||
|
streamId: String!,
|
||||||
|
userId: String!
|
||||||
|
}
|
||||||
|
,extend type Query {
|
||||||
|
"""
|
||||||
|
Gets the profile of a user. If no id argument is provided, will return the current authenticated user's profile (as extracted from the authorization header).
|
||||||
|
"""
|
||||||
|
user(id: String): User
|
||||||
|
userSearch(
|
||||||
|
query: String!
|
||||||
|
limit: Int! = 25
|
||||||
|
cursor: String
|
||||||
|
): UserSearchResultCollection
|
||||||
|
userPwdStrength(pwd: String!): JSONObject
|
||||||
|
}
|
||||||
|
|
||||||
|
"""
|
||||||
|
Base user type.
|
||||||
|
"""
|
||||||
|
type User {
|
||||||
|
id: String!
|
||||||
|
suuid: String
|
||||||
|
email: String
|
||||||
|
name: String
|
||||||
|
bio: String
|
||||||
|
company: String
|
||||||
|
avatar: String
|
||||||
|
verified: Boolean
|
||||||
|
profiles: JSONObject
|
||||||
|
role: String
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserSearchResultCollection {
|
||||||
|
cursor: String
|
||||||
|
items: [UserSearchResult]
|
||||||
|
}
|
||||||
|
|
||||||
|
type UserSearchResult {
|
||||||
|
id: String!
|
||||||
|
name: String
|
||||||
|
bio: String
|
||||||
|
company: String
|
||||||
|
avatar: String
|
||||||
|
verified: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
extend type Mutation {
|
||||||
|
"""
|
||||||
|
Edits a user's profile.
|
||||||
|
"""
|
||||||
|
userUpdate(user: UserUpdateInput!): Boolean!
|
||||||
|
}
|
||||||
|
|
||||||
|
input UserUpdateInput {
|
||||||
|
name: String
|
||||||
|
company: String
|
||||||
|
bio: String
|
||||||
|
}
|
||||||
@@ -110,11 +110,7 @@ class StreamWrapper:
|
|||||||
return 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),
|
||||||
a
|
|
||||||
for a in get_local_accounts()
|
|
||||||
if self.host == urlparse(a.serverInfo.url).netloc
|
|
||||||
),
|
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -11,15 +11,6 @@ class SpeckleException(Exception):
|
|||||||
return f"SpeckleException: {self.message}"
|
return f"SpeckleException: {self.message}"
|
||||||
|
|
||||||
|
|
||||||
class SpeckleInvalidUnitException(SpeckleException):
|
|
||||||
def __init__(self, invalid_unit: Any) -> None:
|
|
||||||
|
|
||||||
super().__init__(
|
|
||||||
message=f"Invalid units: expected type str but received {type(invalid_unit)} ({invalid_unit}).",
|
|
||||||
exception=None,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class SerializationException(SpeckleException):
|
class SerializationException(SpeckleException):
|
||||||
def __init__(self, message: str, obj: Any, exception: Exception = None) -> None:
|
def __init__(self, message: str, obj: Any, exception: Exception = None) -> None:
|
||||||
super().__init__(message=message, exception=exception)
|
super().__init__(message=message, exception=exception)
|
||||||
@@ -3,7 +3,6 @@ import queue
|
|||||||
import hashlib
|
import hashlib
|
||||||
import getpass
|
import getpass
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
|
||||||
import requests
|
import requests
|
||||||
import threading
|
import threading
|
||||||
import platform
|
import platform
|
||||||
@@ -31,7 +30,6 @@ INVITE = "Invite Action"
|
|||||||
COMMIT = "Commit Action"
|
COMMIT = "Commit Action"
|
||||||
BRANCH = "Branch Action"
|
BRANCH = "Branch Action"
|
||||||
USER = "User Action"
|
USER = "User Action"
|
||||||
OTHER_USER = "Other User Action"
|
|
||||||
SERVER = "Server Action"
|
SERVER = "Server Action"
|
||||||
CLIENT = "Speckle Client"
|
CLIENT = "Speckle Client"
|
||||||
STREAM_WRAPPER = "Stream Wrapper"
|
STREAM_WRAPPER = "Stream Wrapper"
|
||||||
@@ -52,13 +50,13 @@ def enable():
|
|||||||
TRACK = True
|
TRACK = True
|
||||||
|
|
||||||
|
|
||||||
def set_host_app(host_app: str, host_app_version: Optional[str] = None):
|
def set_host_app(host_app: str, host_app_version: str = None):
|
||||||
global HOST_APP, HOST_APP_VERSION
|
global HOST_APP, HOST_APP_VERSION
|
||||||
HOST_APP = host_app
|
HOST_APP = host_app
|
||||||
HOST_APP_VERSION = host_app_version or HOST_APP_VERSION
|
HOST_APP_VERSION = host_app_version or HOST_APP_VERSION
|
||||||
|
|
||||||
|
|
||||||
def track(action: str, account: "Account" = None, custom_props: Optional[dict] = None):
|
def track(action: str, account: "Account" = None, custom_props: dict = None):
|
||||||
if not TRACK:
|
if not TRACK:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
@@ -81,7 +79,7 @@ def track(action: str, account: "Account" = None, custom_props: Optional[dict] =
|
|||||||
METRICS_TRACKER.queue.put_nowait(event_params)
|
METRICS_TRACKER.queue.put_nowait(event_params)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
# wrapping this whole thing in a try except as we never want a failure here to annoy users!
|
# wrapping this whole thing in a try except as we never want a failure here to annoy users!
|
||||||
LOG.debug(f"Error queueing metrics request: {str(ex)}")
|
LOG.error(f"Error queueing metrics request: {str(ex)}")
|
||||||
|
|
||||||
|
|
||||||
def initialise_tracker(account: "Account" = None):
|
def initialise_tracker(account: "Account" = None):
|
||||||
@@ -145,6 +143,6 @@ class MetricsTracker(metaclass=Singleton):
|
|||||||
try:
|
try:
|
||||||
session.post(self.analytics_url, json=event_params)
|
session.post(self.analytics_url, json=event_params)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
LOG.debug(f"Error sending metrics request: {str(ex)}")
|
LOG.error(f"Error sending metrics request: {str(ex)}")
|
||||||
|
|
||||||
self.queue.task_done()
|
self.queue.task_done()
|
||||||
@@ -14,8 +14,8 @@ import contextlib
|
|||||||
from enum import EnumMeta
|
from enum import EnumMeta
|
||||||
from warnings import warn
|
from warnings import warn
|
||||||
|
|
||||||
from specklepy.logging.exceptions import SpeckleException, SpeckleInvalidUnitException
|
from specklepy.logging.exceptions import SpeckleException
|
||||||
from specklepy.objects.units import get_units_from_string, Units
|
from specklepy.objects.units import get_units_from_string
|
||||||
from specklepy.transports.memory import MemoryTransport
|
from specklepy.transports.memory import MemoryTransport
|
||||||
|
|
||||||
PRIMITIVES = (int, float, str, bool)
|
PRIMITIVES = (int, float, str, bool)
|
||||||
@@ -146,10 +146,10 @@ class _RegisteringBase:
|
|||||||
|
|
||||||
|
|
||||||
class Base(_RegisteringBase):
|
class Base(_RegisteringBase):
|
||||||
id: Union[str, None] = None
|
id: Optional[str] = None
|
||||||
totalChildrenCount: Union[int, None] = None
|
totalChildrenCount: Optional[int] = None
|
||||||
applicationId: Union[str, None] = None
|
applicationId: Optional[str] = None
|
||||||
_units: Union[Units, None] = None
|
_units: Union[str, None] = None
|
||||||
|
|
||||||
def __init__(self, **kwargs) -> None:
|
def __init__(self, **kwargs) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
@@ -313,23 +313,14 @@ class Base(_RegisteringBase):
|
|||||||
self._detachable = self._detachable.union(names)
|
self._detachable = self._detachable.union(names)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def units(self) -> Union[str, None]:
|
def units(self):
|
||||||
if self._units:
|
return self._units
|
||||||
return self._units.value
|
|
||||||
return None
|
|
||||||
|
|
||||||
@units.setter
|
@units.setter
|
||||||
def units(self, value: Union[str, Units, None]):
|
def units(self, value: str):
|
||||||
if value == None:
|
units = get_units_from_string(value)
|
||||||
units = value
|
if units:
|
||||||
elif isinstance(value, Units):
|
self._units = units
|
||||||
units: Units = value
|
|
||||||
else:
|
|
||||||
units = get_units_from_string(value)
|
|
||||||
self._units = units
|
|
||||||
# except SpeckleInvalidUnitException as ex:
|
|
||||||
# warn(f"Units are reset to None. Reason {ex.message}")
|
|
||||||
# self._units = None
|
|
||||||
|
|
||||||
def get_member_names(self) -> List[str]:
|
def get_member_names(self) -> List[str]:
|
||||||
"""Get all of the property names on this object, dynamic or not"""
|
"""Get all of the property names on this object, dynamic or not"""
|
||||||
@@ -77,7 +77,7 @@ class Plane(Base, speckle_type=GEOMETRY + "Plane"):
|
|||||||
*self.normal.to_list(),
|
*self.normal.to_list(),
|
||||||
*self.xdir.to_list(),
|
*self.xdir.to_list(),
|
||||||
*self.ydir.to_list(),
|
*self.ydir.to_list(),
|
||||||
get_encoding_from_units(self._units),
|
get_encoding_from_units(self.units),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ class Line(Base, speckle_type=GEOMETRY + "Line"):
|
|||||||
*self.start.to_list(),
|
*self.start.to_list(),
|
||||||
*self.end.to_list(),
|
*self.end.to_list(),
|
||||||
*domain,
|
*domain,
|
||||||
get_encoding_from_units(self._units),
|
get_encoding_from_units(self.units),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@ class Arc(Base, speckle_type=GEOMETRY + "Arc"):
|
|||||||
*self.startPoint.to_list(),
|
*self.startPoint.to_list(),
|
||||||
*self.midPoint.to_list(),
|
*self.midPoint.to_list(),
|
||||||
*self.endPoint.to_list(),
|
*self.endPoint.to_list(),
|
||||||
get_encoding_from_units(self._units),
|
get_encoding_from_units(self.units),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -185,7 +185,7 @@ class Circle(Base, speckle_type=GEOMETRY + "Circle"):
|
|||||||
self.radius,
|
self.radius,
|
||||||
*self.domain.to_list(),
|
*self.domain.to_list(),
|
||||||
*self.plane.to_list(),
|
*self.plane.to_list(),
|
||||||
get_encoding_from_units(self._units),
|
get_encoding_from_units(self.units),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -216,7 +216,7 @@ class Ellipse(Base, speckle_type=GEOMETRY + "Ellipse"):
|
|||||||
self.secondRadius,
|
self.secondRadius,
|
||||||
*self.domain.to_list(),
|
*self.domain.to_list(),
|
||||||
*self.plane.to_list(),
|
*self.plane.to_list(),
|
||||||
get_encoding_from_units(self._units),
|
get_encoding_from_units(self.units),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -255,7 +255,7 @@ class Polyline(Base, speckle_type=GEOMETRY + "Polyline", chunkable={"value": 200
|
|||||||
*self.domain.to_list(),
|
*self.domain.to_list(),
|
||||||
len(self.value),
|
len(self.value),
|
||||||
*self.value,
|
*self.value,
|
||||||
get_encoding_from_units(self._units),
|
get_encoding_from_units(self.units),
|
||||||
]
|
]
|
||||||
|
|
||||||
def as_points(self) -> List[Point]:
|
def as_points(self) -> List[Point]:
|
||||||
@@ -340,7 +340,7 @@ class Curve(
|
|||||||
*self.points,
|
*self.points,
|
||||||
*self.weights,
|
*self.weights,
|
||||||
*self.knots,
|
*self.knots,
|
||||||
get_encoding_from_units(self._units),
|
get_encoding_from_units(self.units),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -370,7 +370,7 @@ class Polycurve(Base, speckle_type=GEOMETRY + "Polycurve"):
|
|||||||
*self.domain.to_list(),
|
*self.domain.to_list(),
|
||||||
len(curve_array),
|
len(curve_array),
|
||||||
*curve_array,
|
*curve_array,
|
||||||
get_encoding_from_units(self._units),
|
get_encoding_from_units(self.units),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -488,7 +488,7 @@ class Surface(Base, speckle_type=GEOMETRY + "Surface"):
|
|||||||
*self.pointData,
|
*self.pointData,
|
||||||
*self.knotsU,
|
*self.knotsU,
|
||||||
*self.knotsV,
|
*self.knotsV,
|
||||||
get_encoding_from_units(self._units),
|
get_encoding_from_units(self.units),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -590,6 +590,7 @@ class BrepEdge(Base, speckle_type=GEOMETRY + "BrepEdge"):
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BrepLoopType(int, Enum):
|
class BrepLoopType(int, Enum):
|
||||||
Unknown = 0
|
Unknown = 0
|
||||||
Outer = 1
|
Outer = 1
|
||||||
@@ -841,7 +842,7 @@ class Brep(
|
|||||||
def VerticesValue(self) -> List[Point]:
|
def VerticesValue(self) -> List[Point]:
|
||||||
if self.Vertices is None:
|
if self.Vertices is None:
|
||||||
return None
|
return None
|
||||||
encoded_unit = get_encoding_from_units(self.Vertices[0]._units)
|
encoded_unit = get_encoding_from_units(self.Vertices[0].units)
|
||||||
values = [encoded_unit]
|
values = [encoded_unit]
|
||||||
for vertex in self.Vertices:
|
for vertex in self.Vertices:
|
||||||
values.extend(vertex.to_list())
|
values.extend(vertex.to_list())
|
||||||
+2
-2
@@ -42,8 +42,8 @@ class Concrete(Material, speckle_type=STRUCTURAL_MATERIALS + ".Concrete"):
|
|||||||
compressiveStrength: float = 0.0
|
compressiveStrength: float = 0.0
|
||||||
tensileStrength: float = 0.0
|
tensileStrength: float = 0.0
|
||||||
flexuralStrength: float = 0.0
|
flexuralStrength: float = 0.0
|
||||||
maxCompressiveStrain: float = 0.0
|
maxCompressiveStrength: float = 0.0
|
||||||
maxTensileStrain: float = 0.0
|
maxTensileStrength: float = 0.0
|
||||||
maxAggregateSize: float = 0.0
|
maxAggregateSize: float = 0.0
|
||||||
lightweight: bool = None
|
lightweight: bool = None
|
||||||
|
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
from warnings import warn
|
||||||
|
from specklepy.logging.exceptions import SpeckleException, SpeckleWarning
|
||||||
|
|
||||||
|
UNITS = ["mm", "cm", "m", "in", "ft", "yd", "mi"]
|
||||||
|
|
||||||
|
UNITS_STRINGS = {
|
||||||
|
"mm": ["mm", "mil", "millimeters", "millimetres"],
|
||||||
|
"cm": ["cm", "centimetre", "centimeter", "centimetres", "centimeters"],
|
||||||
|
"m": ["m", "meter", "meters", "metre", "metres"],
|
||||||
|
"km": ["km", "kilometer", "kilometre", "kilometers", "kilometres"],
|
||||||
|
"in": ["in", "inch", "inches"],
|
||||||
|
"ft": ["ft", "foot", "feet"],
|
||||||
|
"yd": ["yd", "yard", "yards"],
|
||||||
|
"mi": ["mi", "mile", "miles"],
|
||||||
|
"none": ["none", "null"],
|
||||||
|
}
|
||||||
|
|
||||||
|
UNITS_ENCODINGS = {
|
||||||
|
"none": 0,
|
||||||
|
None: 0,
|
||||||
|
"mm": 1,
|
||||||
|
"cm": 2,
|
||||||
|
"m": 3,
|
||||||
|
"km": 4,
|
||||||
|
"in": 5,
|
||||||
|
"ft": 6,
|
||||||
|
"yd": 7,
|
||||||
|
"mi": 8,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
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:
|
||||||
|
return name
|
||||||
|
|
||||||
|
raise SpeckleException(
|
||||||
|
message=f"Could not understand what unit {unit} is referring to. Please enter a valid unit (eg {UNITS})."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_units_from_encoding(unit: int):
|
||||||
|
for name, encoding in UNITS_ENCODINGS.items():
|
||||||
|
if unit == encoding:
|
||||||
|
return name
|
||||||
|
|
||||||
|
raise SpeckleException(
|
||||||
|
message=f"Could not understand what unit {unit} is referring to. Please enter a valid unit encoding (eg {UNITS_ENCODINGS})."
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_encoding_from_units(unit: str):
|
||||||
|
try:
|
||||||
|
return UNITS_ENCODINGS[unit]
|
||||||
|
except KeyError as e:
|
||||||
|
raise SpeckleException(message=f"No encoding exists for unit {unit}. Please enter a valid unit to encode (eg {UNITS_ENCODINGS}).") from e
|
||||||
+2
-6
@@ -44,13 +44,9 @@ class BaseObjectSerializer:
|
|||||||
lineage: List[str] # keeps track of hash chain through the object tree
|
lineage: List[str] # keeps track of hash chain through the object tree
|
||||||
family_tree: Dict[str, Dict[str, int]]
|
family_tree: Dict[str, Dict[str, int]]
|
||||||
closure_table: Dict[str, Dict[str, int]]
|
closure_table: Dict[str, Dict[str, int]]
|
||||||
deserialized: Dict[
|
deserialized: Dict[str, Base] # holds deserialized objects so objects with same id return the same instance
|
||||||
str, Base
|
|
||||||
] # holds deserialized objects so objects with same id return the same instance
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(self, write_transports: List[AbstractTransport] = None, read_transport=None) -> None:
|
||||||
self, write_transports: List[AbstractTransport] = None, read_transport=None
|
|
||||||
) -> None:
|
|
||||||
self.write_transports = write_transports or []
|
self.write_transports = write_transports or []
|
||||||
self.read_transport = read_transport
|
self.read_transport = read_transport
|
||||||
self.detach_lineage = []
|
self.detach_lineage = []
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Optional, List, Dict
|
from typing import Optional, List, Dict
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from pydantic.config import Extra
|
from pydantic.main import Extra
|
||||||
|
|
||||||
# __________________
|
# __________________
|
||||||
# | |
|
# | |
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
import os
|
import os
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import sched
|
||||||
import sqlite3
|
import sqlite3
|
||||||
from typing import Any, List, Dict, Optional, Tuple
|
from typing import Any, List, Dict, Tuple
|
||||||
|
from appdirs import user_data_dir
|
||||||
from contextlib import closing
|
from contextlib import closing
|
||||||
from specklepy.transports.abstract_transport import AbstractTransport
|
from specklepy.transports.abstract_transport import AbstractTransport
|
||||||
from specklepy.logging.exceptions import SpeckleException
|
from specklepy.logging.exceptions import SpeckleException
|
||||||
from specklepy.paths import base_path
|
|
||||||
|
|
||||||
|
|
||||||
class SQLiteTransport(AbstractTransport):
|
class SQLiteTransport(AbstractTransport):
|
||||||
@@ -21,8 +24,8 @@ class SQLiteTransport(AbstractTransport):
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
base_path: Optional[str] = None,
|
base_path: str = None,
|
||||||
app_name: Optional[str] = None,
|
app_name: str = None,
|
||||||
scope: str = None,
|
scope: str = None,
|
||||||
max_batch_size_mb: float = 10.0,
|
max_batch_size_mb: float = 10.0,
|
||||||
**data: Any,
|
**data: Any,
|
||||||
@@ -53,23 +56,21 @@ class SQLiteTransport(AbstractTransport):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_base_path(app_name):
|
def get_base_path(app_name):
|
||||||
# # from appdirs https://github.com/ActiveState/appdirs/blob/master/appdirs.py
|
# from appdirs https://github.com/ActiveState/appdirs/blob/master/appdirs.py
|
||||||
# # default mac path is not the one we use (we use unix path), so using special case for this
|
# default mac path is not the one we use (we use unix path), so using special case for this
|
||||||
# system = sys.platform
|
system = sys.platform
|
||||||
# if system.startswith("java"):
|
if system.startswith("java"):
|
||||||
# import platform
|
import platform
|
||||||
|
|
||||||
# os_name = platform.java_ver()[3][0]
|
os_name = platform.java_ver()[3][0]
|
||||||
# if os_name.startswith("Mac"):
|
if os_name.startswith("Mac"):
|
||||||
# system = "darwin"
|
system = "darwin"
|
||||||
|
|
||||||
# if system != "darwin":
|
if system != "darwin":
|
||||||
# return user_data_dir(appname=app_name, appauthor=False, roaming=True)
|
return user_data_dir(appname=app_name, appauthor=False, roaming=True)
|
||||||
|
|
||||||
# path = os.path.expanduser("~/.config/")
|
path = os.path.expanduser("~/.config/")
|
||||||
# return os.path.join(path, app_name)
|
return os.path.join(path, app_name)
|
||||||
|
|
||||||
return str(base_path(app_name))
|
|
||||||
|
|
||||||
def save_object_from_transport(
|
def save_object_from_transport(
|
||||||
self, id: str, source_transport: AbstractTransport
|
self, id: str, source_transport: AbstractTransport
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
from enum import Enum
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from unicodedata import name
|
|
||||||
|
|
||||||
|
|
||||||
class HostAppVersion(Enum):
|
|
||||||
v = "v"
|
|
||||||
v6 = "v6"
|
|
||||||
v7 = "v7"
|
|
||||||
v2019 = "v2019"
|
|
||||||
v2020 = "v2020"
|
|
||||||
v2021 = "v2021"
|
|
||||||
v2022 = "v2022"
|
|
||||||
v2023 = "v2023"
|
|
||||||
v2024 = "v2024"
|
|
||||||
v2025 = "v2025"
|
|
||||||
vSandbox = "vSandbox"
|
|
||||||
vRevit = "vRevit"
|
|
||||||
vRevit2021 = "vRevit2021"
|
|
||||||
vRevit2022 = "vRevit2022"
|
|
||||||
vRevit2023 = "vRevit2023"
|
|
||||||
vRevit2024 = "vRevit2024"
|
|
||||||
vRevit2025 = "vRevit2025"
|
|
||||||
v25 = "v25"
|
|
||||||
v26 = "v26"
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return self.value
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return self.value
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class HostApplication:
|
|
||||||
name: str
|
|
||||||
slug: str
|
|
||||||
|
|
||||||
def get_version(self, version: HostAppVersion) -> str:
|
|
||||||
return f"{name.replace(' ', '')}{str(version).strip('v')}"
|
|
||||||
|
|
||||||
|
|
||||||
RHINO = HostApplication("Rhino", "rhino")
|
|
||||||
GRASSHOPPER = HostApplication("Grasshopper", "grasshopper")
|
|
||||||
REVIT = HostApplication("Revit", "revit")
|
|
||||||
DYNAMO = HostApplication("Dynamo", "dynamo")
|
|
||||||
UNITY = HostApplication("Unity", "unity")
|
|
||||||
GSA = HostApplication("GSA", "gsa")
|
|
||||||
CIVIL = HostApplication("Civil 3D", "civil3d")
|
|
||||||
AUTOCAD = HostApplication("AutoCAD", "autocad")
|
|
||||||
MICROSTATION = HostApplication("MicroStation", "microstation")
|
|
||||||
OPENROADS = HostApplication("OpenRoads", "openroads")
|
|
||||||
OPENRAIL = HostApplication("OpenRail", "openrail")
|
|
||||||
OPENBUILDINGS = HostApplication("OpenBuildings", "openbuildings")
|
|
||||||
ETABS = HostApplication("ETABS", "etabs")
|
|
||||||
SAP2000 = HostApplication("SAP2000", "sap2000")
|
|
||||||
CSIBRIDGE = HostApplication("CSIBridge", "csibridge")
|
|
||||||
SAFE = HostApplication("SAFE", "safe")
|
|
||||||
TEKLASTRUCTURES = HostApplication("Tekla Structures", "teklastructures")
|
|
||||||
DXF = HostApplication("DXF Converter", "dxf")
|
|
||||||
EXCEL = HostApplication("Excel", "excel")
|
|
||||||
UNREAL = HostApplication("Unreal", "unreal")
|
|
||||||
POWERBI = HostApplication("Power BI", "powerbi")
|
|
||||||
BLENDER = HostApplication("Blender", "blender")
|
|
||||||
QGIS = HostApplication("QGIS", "qgis")
|
|
||||||
ARCGIS = HostApplication("ArcGIS", "arcgis")
|
|
||||||
SKETCHUP = HostApplication("SketchUp", "sketchup")
|
|
||||||
ARCHICAD = HostApplication("Archicad", "archicad")
|
|
||||||
TOPSOLID = HostApplication("TopSolid", "topsolid")
|
|
||||||
PYTHON = HostApplication("Python", "python")
|
|
||||||
NET = HostApplication(".NET", "net")
|
|
||||||
OTHER = HostApplication("Other", "other")
|
|
||||||
|
|
||||||
_app_name_host_app_mapping = {
|
|
||||||
"dynamo": DYNAMO,
|
|
||||||
"revit": REVIT,
|
|
||||||
"autocad": AUTOCAD,
|
|
||||||
"civil": CIVIL,
|
|
||||||
"rhino": RHINO,
|
|
||||||
"grasshopper": GRASSHOPPER,
|
|
||||||
"unity": UNITY,
|
|
||||||
"gsa": GSA,
|
|
||||||
"microstation": MICROSTATION,
|
|
||||||
"openroads": OPENROADS,
|
|
||||||
"openrail": OPENRAIL,
|
|
||||||
"openbuildings": OPENBUILDINGS,
|
|
||||||
"etabs": ETABS,
|
|
||||||
"sap": SAP2000,
|
|
||||||
"csibridge": CSIBRIDGE,
|
|
||||||
"safe": SAFE,
|
|
||||||
"teklastructures": TEKLASTRUCTURES,
|
|
||||||
"dxf": DXF,
|
|
||||||
"excel": EXCEL,
|
|
||||||
"unreal": UNREAL,
|
|
||||||
"powerbi": POWERBI,
|
|
||||||
"blender": BLENDER,
|
|
||||||
"qgis": QGIS,
|
|
||||||
"arcgis": ARCGIS,
|
|
||||||
"sketchup": SKETCHUP,
|
|
||||||
"archicad": ARCHICAD,
|
|
||||||
"topsolid": TOPSOLID,
|
|
||||||
"python": PYTHON,
|
|
||||||
"net": NET,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_host_app_from_string(app_name: str) -> HostApplication:
|
|
||||||
app_name = app_name.lower().replace(" ", "")
|
|
||||||
for partial_app_name, host_app in _app_name_host_app_mapping.items():
|
|
||||||
if partial_app_name in app_name:
|
|
||||||
return host_app
|
|
||||||
return HostApplication(app_name, app_name)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
print(HostAppVersion.v)
|
|
||||||
@@ -1,244 +0,0 @@
|
|||||||
from typing import List, Optional
|
|
||||||
from datetime import datetime, timezone
|
|
||||||
from gql import gql
|
|
||||||
from specklepy.logging import metrics
|
|
||||||
from specklepy.logging.exceptions import SpeckleException
|
|
||||||
from specklepy.api.resource import ResourceBase
|
|
||||||
from specklepy.api.models import ActivityCollection, PendingStreamCollaborator, User
|
|
||||||
|
|
||||||
|
|
||||||
NAME = "active_user"
|
|
||||||
|
|
||||||
|
|
||||||
class Resource(ResourceBase):
|
|
||||||
"""API Access class for users"""
|
|
||||||
|
|
||||||
def __init__(self, account, basepath, client, server_version) -> None:
|
|
||||||
super().__init__(
|
|
||||||
account=account,
|
|
||||||
basepath=basepath,
|
|
||||||
client=client,
|
|
||||||
name=NAME,
|
|
||||||
server_version=server_version,
|
|
||||||
)
|
|
||||||
self.schema = User
|
|
||||||
|
|
||||||
def get(self) -> User:
|
|
||||||
"""Gets the profile of a user. If no id argument is provided, will return the current authenticated user's profile (as extracted from the authorization header).
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
id {str} -- the user id
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
User -- the retrieved user
|
|
||||||
"""
|
|
||||||
metrics.track(metrics.USER, self.account, {"name": "get"})
|
|
||||||
query = gql(
|
|
||||||
"""
|
|
||||||
query User {
|
|
||||||
activeUser {
|
|
||||||
id
|
|
||||||
email
|
|
||||||
name
|
|
||||||
bio
|
|
||||||
company
|
|
||||||
avatar
|
|
||||||
verified
|
|
||||||
profiles
|
|
||||||
role
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
params = {}
|
|
||||||
|
|
||||||
return self.make_request(query=query, params=params, return_type="activeUser")
|
|
||||||
|
|
||||||
def update(
|
|
||||||
self,
|
|
||||||
name: Optional[str] = None,
|
|
||||||
company: Optional[str] = None,
|
|
||||||
bio: Optional[str] = None,
|
|
||||||
avatar: Optional[str] = None,
|
|
||||||
):
|
|
||||||
"""Updates your user profile. All arguments are optional.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
name {str} -- your name
|
|
||||||
company {str} -- the company you may or may not work for
|
|
||||||
bio {str} -- tell us about yourself
|
|
||||||
avatar {str} -- a nice photo of yourself
|
|
||||||
|
|
||||||
Returns @deprecated(version=DEPRECATION_VERSION, reason=DEPRECATION_TEXT):
|
|
||||||
bool -- True if your profile was updated successfully
|
|
||||||
"""
|
|
||||||
metrics.track(metrics.USER, self.account, {"name": "update"})
|
|
||||||
query = gql(
|
|
||||||
"""
|
|
||||||
mutation UserUpdate($user: UserUpdateInput!) {
|
|
||||||
userUpdate(user: $user)
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
params = {"name": name, "company": company, "bio": bio, "avatar": avatar}
|
|
||||||
|
|
||||||
params = {"user": {k: v for k, v in params.items() if v is not None}}
|
|
||||||
|
|
||||||
if not params["user"]:
|
|
||||||
return SpeckleException(
|
|
||||||
message="You must provide at least one field to update your user profile"
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.make_request(
|
|
||||||
query=query, params=params, return_type="userUpdate", parse_response=False
|
|
||||||
)
|
|
||||||
|
|
||||||
def activity(
|
|
||||||
self,
|
|
||||||
limit: int = 20,
|
|
||||||
action_type: Optional[str] = None,
|
|
||||||
before: Optional[datetime] = None,
|
|
||||||
after: Optional[datetime] = None,
|
|
||||||
cursor: Optional[datetime] = None,
|
|
||||||
):
|
|
||||||
"""
|
|
||||||
Get the activity from a given stream in an Activity collection. Step into the activity `items` for the list of activity.
|
|
||||||
If no id argument is provided, will return the current authenticated user's activity (as extracted from the authorization header).
|
|
||||||
|
|
||||||
Note: all timestamps arguments should be `datetime` of any tz as they will be converted to UTC ISO format strings
|
|
||||||
|
|
||||||
user_id {str} -- the id of the user to get the activity from
|
|
||||||
action_type {str} -- filter results to a single action type (eg: `commit_create` or `commit_receive`)
|
|
||||||
limit {int} -- max number of Activity items to return
|
|
||||||
before {datetime} -- latest cutoff for activity (ie: return all activity _before_ this time)
|
|
||||||
after {datetime} -- oldest cutoff for activity (ie: return all activity _after_ this time)
|
|
||||||
cursor {datetime} -- timestamp cursor for pagination
|
|
||||||
"""
|
|
||||||
|
|
||||||
query = gql(
|
|
||||||
"""
|
|
||||||
query UserActivity($action_type: String, $before:DateTime, $after: DateTime, $cursor: DateTime, $limit: Int){
|
|
||||||
activeUser {
|
|
||||||
activity(actionType: $action_type, before: $before, after: $after, cursor: $cursor, limit: $limit) {
|
|
||||||
totalCount
|
|
||||||
cursor
|
|
||||||
items {
|
|
||||||
actionType
|
|
||||||
info
|
|
||||||
userId
|
|
||||||
streamId
|
|
||||||
resourceId
|
|
||||||
resourceType
|
|
||||||
message
|
|
||||||
time
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
params = {
|
|
||||||
"limit": limit,
|
|
||||||
"action_type": action_type,
|
|
||||||
"before": before.astimezone(timezone.utc).isoformat() if before else before,
|
|
||||||
"after": after.astimezone(timezone.utc).isoformat() if after else after,
|
|
||||||
"cursor": cursor.astimezone(timezone.utc).isoformat() if cursor else cursor,
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.make_request(
|
|
||||||
query=query,
|
|
||||||
params=params,
|
|
||||||
return_type=["activeUser", "activity"],
|
|
||||||
schema=ActivityCollection,
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_all_pending_invites(self) -> List[PendingStreamCollaborator]:
|
|
||||||
"""Get all of the active user's pending stream invites
|
|
||||||
|
|
||||||
Requires Speckle Server version >= 2.6.4
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
List[PendingStreamCollaborator] -- a list of pending invites for the current user
|
|
||||||
"""
|
|
||||||
metrics.track(metrics.INVITE, self.account, {"name": "get"})
|
|
||||||
self._check_invites_supported()
|
|
||||||
|
|
||||||
query = gql(
|
|
||||||
"""
|
|
||||||
query StreamInvites {
|
|
||||||
streamInvites{
|
|
||||||
id
|
|
||||||
token
|
|
||||||
inviteId
|
|
||||||
streamId
|
|
||||||
streamName
|
|
||||||
title
|
|
||||||
role
|
|
||||||
invitedBy {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
company
|
|
||||||
avatar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
return self.make_request(
|
|
||||||
query=query,
|
|
||||||
return_type="streamInvites",
|
|
||||||
schema=PendingStreamCollaborator,
|
|
||||||
)
|
|
||||||
|
|
||||||
def get_pending_invite(
|
|
||||||
self, stream_id: str, token: Optional[str] = None
|
|
||||||
) -> Optional[PendingStreamCollaborator]:
|
|
||||||
"""Get a particular pending invite for the active user on a given stream.
|
|
||||||
If no invite_id is provided, any valid invite will be returned.
|
|
||||||
|
|
||||||
Requires Speckle Server version >= 2.6.4
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
stream_id {str} -- the id of the stream to look for invites on
|
|
||||||
token {str} -- the token of the invite to look for (optional)
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
PendingStreamCollaborator -- the invite for the given stream (or None if it isn't found)
|
|
||||||
"""
|
|
||||||
metrics.track(metrics.INVITE, self.account, {"name": "get"})
|
|
||||||
self._check_invites_supported()
|
|
||||||
|
|
||||||
query = gql(
|
|
||||||
"""
|
|
||||||
query StreamInvite($streamId: String!, $token: String) {
|
|
||||||
streamInvite(streamId: $streamId, token: $token) {
|
|
||||||
id
|
|
||||||
token
|
|
||||||
streamId
|
|
||||||
streamName
|
|
||||||
title
|
|
||||||
role
|
|
||||||
invitedBy {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
company
|
|
||||||
avatar
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
params = {"streamId": stream_id}
|
|
||||||
if token:
|
|
||||||
params["token"] = token
|
|
||||||
|
|
||||||
return self.make_request(
|
|
||||||
query=query,
|
|
||||||
params=params,
|
|
||||||
return_type="streamInvite",
|
|
||||||
schema=PendingStreamCollaborator,
|
|
||||||
)
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
from typing import List, Optional, Union
|
|
||||||
from datetime import datetime, timezone
|
|
||||||
from gql import gql
|
|
||||||
from specklepy.logging import metrics
|
|
||||||
from specklepy.logging.exceptions import SpeckleException
|
|
||||||
from specklepy.api.resource import ResourceBase
|
|
||||||
from specklepy.api.models import (
|
|
||||||
ActivityCollection,
|
|
||||||
LimitedUser,
|
|
||||||
)
|
|
||||||
|
|
||||||
NAME = "other_user"
|
|
||||||
|
|
||||||
|
|
||||||
class Resource(ResourceBase):
|
|
||||||
"""API Access class for other users, that are not the currently active user."""
|
|
||||||
|
|
||||||
def __init__(self, account, basepath, client, server_version) -> None:
|
|
||||||
super().__init__(
|
|
||||||
account=account,
|
|
||||||
basepath=basepath,
|
|
||||||
client=client,
|
|
||||||
name=NAME,
|
|
||||||
server_version=server_version,
|
|
||||||
)
|
|
||||||
self.schema = LimitedUser
|
|
||||||
|
|
||||||
def get(self, id: str) -> LimitedUser:
|
|
||||||
"""
|
|
||||||
Gets the profile of another user.
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
id {str} -- the user id
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
LimitedUser -- the retrieved profile of another user
|
|
||||||
"""
|
|
||||||
metrics.track(metrics.OTHER_USER, self.account, {"name": "get"})
|
|
||||||
query = gql(
|
|
||||||
"""
|
|
||||||
query OtherUser($id: String!) {
|
|
||||||
otherUser(id: $id) {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
bio
|
|
||||||
company
|
|
||||||
avatar
|
|
||||||
verified
|
|
||||||
role
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
params = {"id": id}
|
|
||||||
|
|
||||||
return self.make_request(query=query, params=params, return_type="otherUser")
|
|
||||||
|
|
||||||
def search(
|
|
||||||
self, search_query: str, limit: int = 25
|
|
||||||
) -> Union[List[LimitedUser], SpeckleException]:
|
|
||||||
"""Searches for user by name or email. The search query must be at least 3 characters long
|
|
||||||
|
|
||||||
Arguments:
|
|
||||||
search_query {str} -- a string to search for
|
|
||||||
limit {int} -- the maximum number of results to return
|
|
||||||
Returns:
|
|
||||||
List[LimitedUser] -- a list of User objects that match the search query
|
|
||||||
"""
|
|
||||||
if len(search_query) < 3:
|
|
||||||
return SpeckleException(
|
|
||||||
message="User search query must be at least 3 characters"
|
|
||||||
)
|
|
||||||
|
|
||||||
metrics.track(metrics.OTHER_USER, self.account, {"name": "search"})
|
|
||||||
query = gql(
|
|
||||||
"""
|
|
||||||
query UserSearch($search_query: String!, $limit: Int!) {
|
|
||||||
userSearch(query: $search_query, limit: $limit) {
|
|
||||||
items {
|
|
||||||
id
|
|
||||||
name
|
|
||||||
bio
|
|
||||||
company
|
|
||||||
avatar
|
|
||||||
verified
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
params = {"search_query": search_query, "limit": limit}
|
|
||||||
|
|
||||||
return self.make_request(
|
|
||||||
query=query, params=params, return_type=["userSearch", "items"]
|
|
||||||
)
|
|
||||||
|
|
||||||
def activity(
|
|
||||||
self,
|
|
||||||
user_id: str,
|
|
||||||
limit: int = 20,
|
|
||||||
action_type: Optional[str] = None,
|
|
||||||
before: Optional[datetime] = None,
|
|
||||||
after: Optional[datetime] = None,
|
|
||||||
cursor: Optional[datetime] = None,
|
|
||||||
) -> ActivityCollection:
|
|
||||||
"""
|
|
||||||
Get the activity from a given stream in an Activity collection. Step into the activity `items` for the list of activity.
|
|
||||||
|
|
||||||
Note: all timestamps arguments should be `datetime` of any tz as they will be converted to UTC ISO format strings
|
|
||||||
|
|
||||||
user_id {str} -- the id of the user to get the activity from
|
|
||||||
action_type {str} -- filter results to a single action type (eg: `commit_create` or `commit_receive`)
|
|
||||||
limit {int} -- max number of Activity items to return
|
|
||||||
before {datetime} -- latest cutoff for activity (ie: return all activity _before_ this time)
|
|
||||||
after {datetime} -- oldest cutoff for activity (ie: return all activity _after_ this time)
|
|
||||||
cursor {datetime} -- timestamp cursor for pagination
|
|
||||||
"""
|
|
||||||
|
|
||||||
query = gql(
|
|
||||||
"""
|
|
||||||
query UserActivity($user_id: String!, $action_type: String, $before:DateTime, $after: DateTime, $cursor: DateTime, $limit: Int){
|
|
||||||
otherUser(id: $user_id) {
|
|
||||||
activity(actionType: $action_type, before: $before, after: $after, cursor: $cursor, limit: $limit) {
|
|
||||||
totalCount
|
|
||||||
cursor
|
|
||||||
items {
|
|
||||||
actionType
|
|
||||||
info
|
|
||||||
userId
|
|
||||||
streamId
|
|
||||||
resourceId
|
|
||||||
resourceType
|
|
||||||
message
|
|
||||||
time
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
|
|
||||||
params = {
|
|
||||||
"user_id": user_id,
|
|
||||||
"limit": limit,
|
|
||||||
"action_type": action_type,
|
|
||||||
"before": before.astimezone(timezone.utc).isoformat() if before else before,
|
|
||||||
"after": after.astimezone(timezone.utc).isoformat() if after else after,
|
|
||||||
"cursor": cursor.astimezone(timezone.utc).isoformat() if cursor else cursor,
|
|
||||||
}
|
|
||||||
|
|
||||||
return self.make_request(
|
|
||||||
query=query,
|
|
||||||
params=params,
|
|
||||||
return_type=["otherUser", "activity"],
|
|
||||||
schema=ActivityCollection,
|
|
||||||
)
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
from typing import Union
|
|
||||||
from specklepy.logging.exceptions import SpeckleException, SpeckleInvalidUnitException
|
|
||||||
from enum import Enum
|
|
||||||
|
|
||||||
|
|
||||||
class Units(Enum):
|
|
||||||
mm = "mm"
|
|
||||||
cm = "cm"
|
|
||||||
m = "m"
|
|
||||||
km = "km"
|
|
||||||
inches = "in"
|
|
||||||
feet = "ft"
|
|
||||||
yards = "yd"
|
|
||||||
miles = "mi"
|
|
||||||
none = "none"
|
|
||||||
|
|
||||||
|
|
||||||
UNITS_STRINGS = {
|
|
||||||
Units.mm: ["mm", "mil", "millimeters", "millimetres"],
|
|
||||||
Units.cm: ["cm", "centimetre", "centimeter", "centimetres", "centimeters"],
|
|
||||||
Units.m: ["m", "meter", "meters", "metre", "metres"],
|
|
||||||
Units.km: ["km", "kilometer", "kilometre", "kilometers", "kilometres"],
|
|
||||||
Units.inches: ["in", "inch", "inches"],
|
|
||||||
Units.feet: ["ft", "foot", "feet"],
|
|
||||||
Units.yards: ["yd", "yard", "yards"],
|
|
||||||
Units.miles: ["mi", "mile", "miles"],
|
|
||||||
Units.none: ["none", "null"],
|
|
||||||
}
|
|
||||||
|
|
||||||
UNITS_ENCODINGS = {
|
|
||||||
Units.none: 0,
|
|
||||||
None: 0,
|
|
||||||
Units.mm: 1,
|
|
||||||
Units.cm: 2,
|
|
||||||
Units.m: 3,
|
|
||||||
Units.km: 4,
|
|
||||||
Units.inches: 5,
|
|
||||||
Units.feet: 6,
|
|
||||||
Units.yards: 7,
|
|
||||||
Units.miles: 8,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def get_units_from_string(unit: str) -> Units:
|
|
||||||
if not isinstance(unit, str):
|
|
||||||
raise SpeckleInvalidUnitException(unit)
|
|
||||||
unit = str.lower(unit)
|
|
||||||
for name, alternates in UNITS_STRINGS.items():
|
|
||||||
if unit in alternates:
|
|
||||||
return name
|
|
||||||
raise SpeckleInvalidUnitException(unit)
|
|
||||||
|
|
||||||
|
|
||||||
def get_units_from_encoding(unit: int):
|
|
||||||
for name, encoding in UNITS_ENCODINGS.items():
|
|
||||||
if unit == encoding:
|
|
||||||
return name
|
|
||||||
|
|
||||||
raise SpeckleException(
|
|
||||||
message=f"Could not understand what unit {unit} is referring to. Please enter a valid unit encoding (eg {UNITS_ENCODINGS})."
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def get_encoding_from_units(unit: Union[Units, None]):
|
|
||||||
try:
|
|
||||||
return UNITS_ENCODINGS[unit]
|
|
||||||
except KeyError as e:
|
|
||||||
raise SpeckleException(
|
|
||||||
message=f"No encoding exists for unit {unit}. Please enter a valid unit to encode (eg {UNITS_ENCODINGS})."
|
|
||||||
) from e
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
from appdirs import user_data_dir
|
|
||||||
|
|
||||||
|
|
||||||
def base_path(app_name) -> Path:
|
|
||||||
# from appdirs https://github.com/ActiveState/appdirs/blob/master/appdirs.py
|
|
||||||
# default mac path is not the one we use (we use unix path), so using special case for this
|
|
||||||
system = sys.platform
|
|
||||||
if system.startswith("java"):
|
|
||||||
import platform
|
|
||||||
|
|
||||||
os_name = platform.java_ver()[3][0]
|
|
||||||
if os_name.startswith("Mac"):
|
|
||||||
system = "darwin"
|
|
||||||
|
|
||||||
if system == "darwin":
|
|
||||||
return Path(Path.home(), ".config", app_name)
|
|
||||||
|
|
||||||
return Path(user_data_dir(appname=app_name, appauthor=False, roaming=True))
|
|
||||||
|
|
||||||
|
|
||||||
def accounts_path(app_name: str = "Speckle") -> Path:
|
|
||||||
"""
|
|
||||||
Gets the path where the Speckle applications are looking for accounts.
|
|
||||||
"""
|
|
||||||
return base_path(app_name).joinpath("Accounts")
|
|
||||||
+2
-7
@@ -29,14 +29,9 @@ def seed_user(host):
|
|||||||
r = requests.post(
|
r = requests.post(
|
||||||
url=f"http://{host}/auth/local/register?challenge=pyspeckletests",
|
url=f"http://{host}/auth/local/register?challenge=pyspeckletests",
|
||||||
data=user_dict,
|
data=user_dict,
|
||||||
# do not follow redirects here, they lead to the frontend, which might not be
|
|
||||||
# running in a test environment
|
|
||||||
# causing the response to not be OK in the end
|
|
||||||
allow_redirects=False
|
|
||||||
)
|
)
|
||||||
if not r.ok:
|
print(r.url)
|
||||||
raise Exception(f"Cannot seed user: {r.reason}")
|
access_code = r.url.split("access_code=")[1]
|
||||||
access_code = r.text.split("access_code=")[1]
|
|
||||||
|
|
||||||
r_tokens = requests.post(
|
r_tokens = requests.post(
|
||||||
url=f"http://{host}/auth/token",
|
url=f"http://{host}/auth/token",
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
import pytest
|
|
||||||
from specklepy.api.client import SpeckleClient
|
|
||||||
from specklepy.api.models import Activity, ActivityCollection, User
|
|
||||||
from specklepy.logging.exceptions import SpeckleException
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.run(order=2)
|
|
||||||
class TestUser:
|
|
||||||
def test_user_get_self(self, client: SpeckleClient, user_dict):
|
|
||||||
fetched_user = client.active_user.get()
|
|
||||||
|
|
||||||
assert isinstance(fetched_user, User)
|
|
||||||
assert fetched_user.name == user_dict["name"]
|
|
||||||
assert fetched_user.email == user_dict["email"]
|
|
||||||
|
|
||||||
user_dict["id"] = fetched_user.id
|
|
||||||
|
|
||||||
def test_user_update(self, client: SpeckleClient):
|
|
||||||
bio = "i am a ghost in the machine"
|
|
||||||
|
|
||||||
failed_update = client.active_user.update()
|
|
||||||
assert isinstance(failed_update, SpeckleException)
|
|
||||||
|
|
||||||
updated = client.active_user.update(bio=bio)
|
|
||||||
|
|
||||||
me = client.active_user.get()
|
|
||||||
|
|
||||||
assert updated is True
|
|
||||||
assert me.bio == bio
|
|
||||||
|
|
||||||
def test_user_activity(self, client: SpeckleClient, second_user_dict):
|
|
||||||
my_activity = client.active_user.activity(limit=10)
|
|
||||||
their_activity = client.other_user.activity(second_user_dict["id"])
|
|
||||||
|
|
||||||
assert isinstance(my_activity, ActivityCollection)
|
|
||||||
assert my_activity.items
|
|
||||||
assert isinstance(my_activity.items[0], Activity)
|
|
||||||
assert my_activity.totalCount
|
|
||||||
assert isinstance(their_activity, ActivityCollection)
|
|
||||||
|
|
||||||
older_activity = client.user.activity(before=my_activity.items[0].time)
|
|
||||||
|
|
||||||
assert isinstance(older_activity, ActivityCollection)
|
|
||||||
assert older_activity.totalCount
|
|
||||||
assert older_activity.totalCount < my_activity.totalCount
|
|
||||||
+4
-15
@@ -1,13 +1,11 @@
|
|||||||
from codecs import ascii_encode
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from typing import Dict, List, Optional, Union
|
from typing import Dict, List, Optional, Union
|
||||||
from contextlib import ExitStack as does_not_raise
|
from contextlib import ExitStack as does_not_raise
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from specklepy.api import operations
|
from specklepy.api import operations
|
||||||
from specklepy.logging.exceptions import SpeckleException, SpeckleInvalidUnitException
|
from specklepy.logging.exceptions import SpeckleException
|
||||||
from specklepy.objects.base import Base, DataChunk
|
from specklepy.objects.base import Base, DataChunk
|
||||||
from specklepy.objects.units import Units
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@@ -84,22 +82,13 @@ def test_setting_units():
|
|||||||
b = Base(units="foot")
|
b = Base(units="foot")
|
||||||
assert b.units == "ft"
|
assert b.units == "ft"
|
||||||
|
|
||||||
with pytest.raises(SpeckleInvalidUnitException):
|
with pytest.raises(SpeckleException):
|
||||||
b.units = "big"
|
b.units = "big"
|
||||||
|
|
||||||
with pytest.raises(SpeckleInvalidUnitException):
|
b.units = None # invalid args are skipped
|
||||||
b.units = 7 # invalid args are skipped
|
b.units = 7
|
||||||
assert b.units == "ft"
|
assert b.units == "ft"
|
||||||
|
|
||||||
b.units = None # None should be a valid arg
|
|
||||||
assert b.units == None
|
|
||||||
|
|
||||||
b.units = Units.none
|
|
||||||
assert b.units == "none"
|
|
||||||
|
|
||||||
b.units = Units.cm
|
|
||||||
assert b.units == Units.cm.value
|
|
||||||
|
|
||||||
|
|
||||||
def test_base_of_custom_speckle_type() -> None:
|
def test_base_of_custom_speckle_type() -> None:
|
||||||
b1 = Base.of_type("BirdHouse", name="Tweety's Crib")
|
b1 = Base.of_type("BirdHouse", name="Tweety's Crib")
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from specklepy.api.models import Commit, Stream
|
|||||||
from specklepy.transports.server.server import ServerTransport
|
from specklepy.transports.server.server import ServerTransport
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.run(order=6)
|
@pytest.mark.run(order=4)
|
||||||
class TestCommit:
|
class TestCommit:
|
||||||
@pytest.fixture(scope="module")
|
@pytest.fixture(scope="module")
|
||||||
def commit(self):
|
def commit(self):
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import pytest
|
|||||||
from specklepy.api import operations
|
from specklepy.api import operations
|
||||||
from specklepy.logging.exceptions import SpeckleException
|
from specklepy.logging.exceptions import SpeckleException
|
||||||
from specklepy.objects.base import Base
|
from specklepy.objects.base import Base
|
||||||
from specklepy.objects.units import Units
|
|
||||||
from specklepy.objects.encoding import CurveArray, ObjectArray
|
from specklepy.objects.encoding import CurveArray, ObjectArray
|
||||||
from specklepy.objects.geometry import (
|
from specklepy.objects.geometry import (
|
||||||
Arc,
|
Arc,
|
||||||
@@ -387,9 +386,9 @@ 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]
|
||||||
assert brep.Vertices[0].get_id() == Point(x=1, y=1, z=1, _units=Units.mm).get_id()
|
assert brep.Vertices[0].get_id() == Point(x=1, y=1, z=1, _units="mm").get_id()
|
||||||
assert brep.Vertices[1].get_id() == Point(x=2, y=2, z=2, _units=Units.mm).get_id()
|
assert brep.Vertices[1].get_id() == Point(x=2, y=2, z=2, _units="mm").get_id()
|
||||||
assert brep.Vertices[2].get_id() == Point(x=3, y=3, z=3, _units=Units.mm).get_id()
|
assert 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():
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import pytest
|
|
||||||
from specklepy.api.host_applications import (
|
|
||||||
get_host_app_from_string,
|
|
||||||
_app_name_host_app_mapping,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_host_app_from_string_returns_fallback_app():
|
|
||||||
not_existing_app_name = "gmail"
|
|
||||||
host_app = get_host_app_from_string(not_existing_app_name)
|
|
||||||
assert host_app.name == not_existing_app_name
|
|
||||||
assert host_app.slug == not_existing_app_name
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("app_name", _app_name_host_app_mapping.keys())
|
|
||||||
def test_get_host_app_from_string_matches_for_predefined_apps(app_name) -> None:
|
|
||||||
host_app = get_host_app_from_string(app_name)
|
|
||||||
assert app_name in host_app.slug.lower()
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import pytest
|
|
||||||
from specklepy.api.client import SpeckleClient
|
|
||||||
from specklepy.api.models import Activity, ActivityCollection, LimitedUser
|
|
||||||
from specklepy.logging.exceptions import SpeckleException
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.run(order=4)
|
|
||||||
class TestOtherUser:
|
|
||||||
def test_user_get_self(self, client):
|
|
||||||
"""
|
|
||||||
Test, that a limited user query cannot query the active user.
|
|
||||||
"""
|
|
||||||
with pytest.raises(TypeError):
|
|
||||||
client.other_user.get()
|
|
||||||
|
|
||||||
def test_user_search(self, client, second_user_dict):
|
|
||||||
search_results = client.other_user.search(
|
|
||||||
search_query=second_user_dict["name"][:5]
|
|
||||||
)
|
|
||||||
|
|
||||||
assert isinstance(search_results, list)
|
|
||||||
assert len(search_results) > 0
|
|
||||||
result_user = search_results[0]
|
|
||||||
assert isinstance(result_user, LimitedUser)
|
|
||||||
assert result_user.name == second_user_dict["name"]
|
|
||||||
|
|
||||||
second_user_dict["id"] = result_user.id
|
|
||||||
assert getattr(result_user, "email", None) is None
|
|
||||||
|
|
||||||
def test_user_get(self, client, second_user_dict):
|
|
||||||
fetched_user = client.other_user.get(id=second_user_dict["id"])
|
|
||||||
|
|
||||||
assert isinstance(fetched_user, LimitedUser)
|
|
||||||
assert fetched_user.name == second_user_dict["name"]
|
|
||||||
# changed in the server, now you cannot get emails of other users
|
|
||||||
# not checking this, since the first user could or could not be an admin on the server
|
|
||||||
# admins can get emails of others, regular users can't
|
|
||||||
# assert fetched_user.email == None
|
|
||||||
|
|
||||||
second_user_dict["id"] = fetched_user.id
|
|
||||||
|
|
||||||
def test_user_activity(self, client: SpeckleClient, second_user_dict):
|
|
||||||
their_activity = client.other_user.activity(second_user_dict["id"])
|
|
||||||
|
|
||||||
assert isinstance(their_activity, ActivityCollection)
|
|
||||||
assert isinstance(their_activity.items, list)
|
|
||||||
assert isinstance(their_activity.items[0], Activity)
|
|
||||||
assert their_activity.totalCount
|
|
||||||
assert their_activity.totalCount > 0
|
|
||||||
@@ -9,7 +9,7 @@ from specklepy.objects.geometry import Point
|
|||||||
from specklepy.objects.fakemesh import FakeMesh
|
from specklepy.objects.fakemesh import FakeMesh
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.run(order=5)
|
@pytest.mark.run(order=3)
|
||||||
class TestSerialization:
|
class TestSerialization:
|
||||||
def test_serialize(self, base):
|
def test_serialize(self, base):
|
||||||
serialized = operations.serialize(base)
|
serialized = operations.serialize(base)
|
||||||
@@ -91,7 +91,7 @@ class TestSerialization:
|
|||||||
assert deserialised == {"foo": "bar"}
|
assert deserialised == {"foo": "bar"}
|
||||||
|
|
||||||
def test_big_int(self):
|
def test_big_int(self):
|
||||||
big_int = '{"big": ' + str(2**64) + "}"
|
big_int = '{"big": ' + str(2 ** 64) + "}"
|
||||||
deserialised = operations.deserialize(big_int)
|
deserialised = operations.deserialize(big_int)
|
||||||
|
|
||||||
assert deserialised == {"big": 2**64}
|
assert deserialised == {"big": 2 ** 64}
|
||||||
|
|||||||
@@ -22,11 +22,8 @@ class TestServer:
|
|||||||
version = client.server.version()
|
version = client.server.version()
|
||||||
|
|
||||||
assert isinstance(version, tuple)
|
assert isinstance(version, tuple)
|
||||||
if len(version) == 1:
|
assert isinstance(version[0], int)
|
||||||
assert version[0] == "dev"
|
assert len(version) >= 3
|
||||||
else:
|
|
||||||
assert isinstance(version[0], int)
|
|
||||||
assert len(version) >= 3
|
|
||||||
|
|
||||||
def test_server_apps(self, client: SpeckleClient):
|
def test_server_apps(self, client: SpeckleClient):
|
||||||
apps = client.server.apps()
|
apps = client.server.apps()
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ from specklepy.logging.exceptions import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.run(order=3)
|
@pytest.mark.run(order=2)
|
||||||
class TestStream:
|
class TestStream:
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def stream(self):
|
def stream(self):
|
||||||
|
|||||||
+31
-43
@@ -6,67 +6,55 @@ from specklepy.logging.exceptions import SpeckleException
|
|||||||
|
|
||||||
@pytest.mark.run(order=1)
|
@pytest.mark.run(order=1)
|
||||||
class TestUser:
|
class TestUser:
|
||||||
def test_user_get_self(self, client: SpeckleClient, user_dict):
|
def test_user_get_self(self, client, user_dict):
|
||||||
with pytest.deprecated_call():
|
fetched_user = client.user.get()
|
||||||
fetched_user = client.user.get()
|
|
||||||
|
|
||||||
assert isinstance(fetched_user, User)
|
assert isinstance(fetched_user, User)
|
||||||
assert fetched_user.name == user_dict["name"]
|
assert fetched_user.name == user_dict["name"]
|
||||||
assert fetched_user.email == user_dict["email"]
|
assert fetched_user.email == user_dict["email"]
|
||||||
|
|
||||||
user_dict["id"] = fetched_user.id
|
user_dict["id"] = fetched_user.id
|
||||||
|
|
||||||
def test_user_search(self, client, second_user_dict):
|
def test_user_search(self, client, second_user_dict):
|
||||||
with pytest.deprecated_call():
|
search_results = client.user.search(search_query=second_user_dict["name"][:5])
|
||||||
search_results = client.user.search(search_query=second_user_dict["name"][:5])
|
|
||||||
|
|
||||||
assert isinstance(search_results, list)
|
assert isinstance(search_results, list)
|
||||||
assert isinstance(search_results[0], User)
|
assert isinstance(search_results[0], User)
|
||||||
assert search_results[0].name == second_user_dict["name"]
|
assert search_results[0].name == second_user_dict["name"]
|
||||||
|
|
||||||
second_user_dict["id"] = search_results[0].id
|
second_user_dict["id"] = search_results[0].id
|
||||||
|
|
||||||
def test_user_get(self, client, second_user_dict):
|
def test_user_get(self, client, second_user_dict):
|
||||||
with pytest.deprecated_call():
|
fetched_user = client.user.get(id=second_user_dict["id"])
|
||||||
fetched_user = client.user.get(id=second_user_dict["id"])
|
|
||||||
|
|
||||||
assert isinstance(fetched_user, User)
|
assert isinstance(fetched_user, User)
|
||||||
assert fetched_user.name == second_user_dict["name"]
|
assert fetched_user.name == second_user_dict["name"]
|
||||||
# changed in the server, now you cannot get emails of other users
|
assert fetched_user.email == second_user_dict["email"]
|
||||||
# not checking this, since the first user could or could not be an admin on the server
|
|
||||||
# admins can get emails of others, regular users can't
|
|
||||||
# assert fetched_user.email == None
|
|
||||||
|
|
||||||
second_user_dict["id"] = fetched_user.id
|
second_user_dict["id"] = fetched_user.id
|
||||||
|
|
||||||
def test_user_update(self, client):
|
def test_user_update(self, client):
|
||||||
bio = "i am a ghost in the machine"
|
bio = "i am a ghost in the machine"
|
||||||
|
|
||||||
with pytest.deprecated_call():
|
failed_update = client.user.update()
|
||||||
failed_update = client.user.update()
|
updated = client.user.update(bio=bio)
|
||||||
assert isinstance(failed_update, SpeckleException)
|
|
||||||
with pytest.deprecated_call():
|
|
||||||
updated = client.user.update(bio=bio)
|
|
||||||
assert updated is True
|
|
||||||
|
|
||||||
|
me = client.user.get()
|
||||||
|
|
||||||
with pytest.deprecated_call():
|
assert isinstance(failed_update, SpeckleException)
|
||||||
me = client.user.get()
|
assert updated is True
|
||||||
assert me.bio == bio
|
assert me.bio == bio
|
||||||
|
|
||||||
def test_user_activity(self, client: SpeckleClient, second_user_dict):
|
def test_user_activity(self, client: SpeckleClient, second_user_dict):
|
||||||
with pytest.deprecated_call():
|
my_activity = client.user.activity(limit=10)
|
||||||
my_activity = client.user.activity(limit=10)
|
their_activity = client.user.activity(second_user_dict["id"])
|
||||||
their_activity = client.user.activity(second_user_dict["id"])
|
|
||||||
|
|
||||||
assert isinstance(my_activity, ActivityCollection)
|
assert isinstance(my_activity, ActivityCollection)
|
||||||
assert my_activity.items
|
assert isinstance(my_activity.items[0], Activity)
|
||||||
assert isinstance(my_activity.items[0], Activity)
|
assert my_activity.totalCount > 0
|
||||||
assert my_activity.totalCount
|
assert isinstance(their_activity, ActivityCollection)
|
||||||
assert isinstance(their_activity, ActivityCollection)
|
|
||||||
|
|
||||||
older_activity = client.user.activity(before=my_activity.items[0].time)
|
older_activity = client.user.activity(before=my_activity.items[0].time)
|
||||||
|
|
||||||
assert isinstance(older_activity, ActivityCollection)
|
assert isinstance(older_activity, ActivityCollection)
|
||||||
assert older_activity.totalCount
|
assert older_activity.totalCount < my_activity.totalCount
|
||||||
assert older_activity.totalCount < my_activity.totalCount
|
|
||||||
|
|||||||
+1
-41
@@ -1,9 +1,5 @@
|
|||||||
import json
|
|
||||||
from specklepy.api.wrapper import StreamWrapper
|
|
||||||
from specklepy.transports.sqlite import SQLiteTransport
|
|
||||||
from specklepy.paths import accounts_path
|
|
||||||
from pathlib import Path
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from specklepy.api.wrapper import StreamWrapper
|
||||||
|
|
||||||
|
|
||||||
def test_parse_stream():
|
def test_parse_stream():
|
||||||
@@ -83,39 +79,3 @@ def test_get_transport_with_token():
|
|||||||
|
|
||||||
assert transport is not None
|
assert transport is not None
|
||||||
assert client.account.token == "super-secret-token"
|
assert client.account.token == "super-secret-token"
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
|
||||||
def user_path() -> Path:
|
|
||||||
path = accounts_path().joinpath("test_acc.json")
|
|
||||||
# hey, py37 doesn't support the missing_ok argument
|
|
||||||
try:
|
|
||||||
path.unlink()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
try:
|
|
||||||
path.unlink(missing_ok=True)
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
path.parent.absolute().mkdir(exist_ok=True)
|
|
||||||
yield path
|
|
||||||
path.unlink()
|
|
||||||
|
|
||||||
|
|
||||||
def test_wrapper_url_match(user_path) -> None:
|
|
||||||
"""
|
|
||||||
The stream wrapper should only recognize exact url matches for the account
|
|
||||||
definitions and not match for subdomains.
|
|
||||||
"""
|
|
||||||
account = {
|
|
||||||
"token": "foobar",
|
|
||||||
"serverInfo": {"name": "foo", "url": "http://foo.bar.baz", "company": "Foo"},
|
|
||||||
"userInfo": {"id": "bla", "name": "A rando tester", "email": "rando@tester.me"},
|
|
||||||
}
|
|
||||||
|
|
||||||
user_path.write_text(json.dumps(account))
|
|
||||||
wrap = StreamWrapper("http://bar.baz/streams/bogus")
|
|
||||||
|
|
||||||
account = wrap.get_account()
|
|
||||||
|
|
||||||
assert account.userInfo.email is None
|
|
||||||
|
|||||||
Reference in New Issue
Block a user