Compare commits

..

10 Commits

Author SHA1 Message Date
izzy lyseggen 3f09cd9d77 chore: bump version 2021-08-06 17:02:19 +01:00
izzy lyseggen 29a361892b Merge pull request #113 from specklesystems/izzy/stream-wrapper
🌯 Stream Wrapper with Client & Server Transport helper methods
2021-08-06 16:54:53 +01:00
izzy lyseggen 2672b40aff ci: remove speckle-server version 2021-08-06 16:53:09 +01:00
izzy lyseggen 35b6911b27 ci: quick fix 2021-08-06 16:43:53 +01:00
izzy lyseggen a4f0a2cc2b feat(server): transport init exception if no token 2021-08-06 16:39:02 +01:00
izzy lyseggen 1970890ecc test: stream wrapper tests 2021-08-06 16:38:40 +01:00
izzy lyseggen 13df5135b8 feat(credentials): stream wrapper!
can provide client and transport for you
2021-08-06 16:38:10 +01:00
izzy lyseggen 4e206b5c60 style: formatting 2021-08-06 16:09:12 +01:00
izzy lyseggen e696091555 feat(client): add string repr 2021-08-06 16:08:46 +01:00
izzy lyseggen a512dbb4e4 feat(logging): add SpeckleWarning class 2021-08-06 16:08:18 +01:00
7 changed files with 161 additions and 10 deletions
+2 -2
View File
@@ -14,7 +14,7 @@ jobs:
POSTGRES_DB: speckle2_test
POSTGRES_PASSWORD: speckle
POSTGRES_USER: speckle
- image: "speckle/speckle-server:5f8cf11cba07ea6a54000243f9cb343b61cbba13"
- image: "speckle/speckle-server"
command: ["bash", "-c", "/wait && node bin/www"]
environment:
POSTGRES_URL: "localhost"
@@ -38,7 +38,7 @@ jobs:
name: upgrade pip
- python/install-packages:
pkg-manager: poetry
- run: poetry run pytest --version
- run: poetry run pytest
deploy:
docker:
+1 -1
View File
@@ -1,6 +1,6 @@
[tool.poetry]
name = "specklepy"
version = "2.2.5"
version = "2.2.6"
description = "The Python SDK for Speckle 2.0"
readme = "README.md"
authors = ["Speckle Systems <devops@speckle.systems>"]
+5
View File
@@ -82,6 +82,11 @@ class SpeckleClient:
except Exception as ex:
raise SpeckleException(f"{self.url} is not a compatible Speckle Server", ex)
def __repr__(self):
return (
f"SpeckleClient( server: {self.url}, authenticated: {self.me is not None} )"
)
def authenticate(self, token: str) -> None:
"""Authenticate the client using a personal access token
The token is saved in the client object and a synchronous GraphQL entrypoint is created
+92 -2
View File
@@ -1,9 +1,13 @@
import os
from typing import List, Optional
from specklepy.transports.server.server import ServerTransport
from warnings import warn
from pydantic import BaseModel
from typing import List, Optional
from urllib.parse import urlparse, unquote
from specklepy.api.models import ServerInfo
from specklepy.api.client import SpeckleClient
from specklepy.transports.sqlite import SQLiteTransport
from specklepy.logging.exceptions import SpeckleException
from specklepy.logging.exceptions import SpeckleException, SpeckleWarning
class UserInfo(BaseModel):
@@ -79,3 +83,89 @@ def get_default_account(base_path: str = None) -> Account:
default.isDefault = True
return default
class StreamWrapper:
stream_url: str = None
use_ssl: bool = True
host: str = None
stream_id: str = None
commit_id: str = None
object_id: str = None
branch_name: str = None
client: SpeckleClient = None
def __repr__(self):
return f"StreamWrapper( server: {self.host}, stream_id: {self.stream_id}, type: {self.type} )"
def __str__(self) -> str:
return self.__repr__()
@property
def type(self) -> str:
if self.object_id:
return "object"
elif self.commit_id:
return "commit"
elif self.branch_name:
return "branch"
else:
return "stream" if self.stream_id else "invalid"
def __init__(self, url: str) -> None:
self.stream_url = url
parsed = urlparse(url)
self.host = parsed.netloc
self.use_ssl = parsed.scheme == "https"
segments = parsed.path.strip("/").split("/")
if not segments or len(segments) > 4 or len(segments) < 2:
raise SpeckleException(
f"Cannot parse {url} into a stream wrapper class - invalid URL provided."
)
while segments:
segment = segments.pop(0)
if segments and segment.lower() == "streams":
self.stream_id = segments.pop(0)
elif segments and segment.lower() == "commits":
self.commit_id = segments.pop(0)
elif segments and segment.lower() == "branches":
self.branch_name = unquote(segments.pop(0))
elif segments and segment.lower() == "objects":
self.object_id = segments.pop(0)
elif segment.lower() == "globals":
self.branch_name = "globals"
if segments:
self.commit_id = segments.pop(0)
else:
raise SpeckleException(
f"Cannot parse {url} into a stream wrapper class - invalid URL provided."
)
if not self.stream_id:
raise SpeckleException(
f"Cannot parse {url} into a stream wrapper class - no stream id found."
)
def get_client(self) -> SpeckleClient:
if self.client:
return self.client
acct = next(
(a for a in get_local_accounts() if self.host in a.serverInfo.url),
None,
)
self.client = SpeckleClient(host=self.host, use_ssl=self.use_ssl)
if not acct:
warn(f"No local account found for server {self.host}", SpeckleWarning)
return self.client
self.client.authenticate(acct.token)
return self.client
def get_transport(self) -> ServerTransport:
if not self.client:
self.get_client()
return ServerTransport(self.client, self.stream_id)
+5
View File
@@ -28,3 +28,8 @@ class GraphQLException(SpeckleException):
def __str__(self) -> str:
return f"GraphQLException: {self.message}"
class SpeckleWarning(Warning):
def __init__(self, *args: object) -> None:
super().__init__(*args)
+15 -5
View File
@@ -22,11 +22,15 @@ class ServerTransport(AbstractTransport):
def __init__(self, client: SpeckleClient, stream_id: str, **data: Any) -> None:
super().__init__(**data)
# TODO: replace client with account or some other auth avenue
if not client.me:
raise SpeckleException("The provided SpeckleClient was not authenticated.")
self.url = client.url
self.stream_id = stream_id
token = client.me["token"]
self._batch_sender = BatchSender(self.url, self.stream_id, token, max_batch_size_mb=1)
self._batch_sender = BatchSender(
self.url, self.stream_id, token, max_batch_size_mb=1
)
self.session = requests.Session()
self.session.headers.update(
@@ -73,19 +77,25 @@ class ServerTransport(AbstractTransport):
r.encoding = "utf-8"
if r.status_code != 200:
raise SpeckleException(f"Can't get object {self.stream_id}/{id}: HTTP error {r.status_code} ({r.text[:1000]})")
raise SpeckleException(
f"Can't get object {self.stream_id}/{id}: HTTP error {r.status_code} ({r.text[:1000]})"
)
root_obj_serialized = r.text
root_obj = json.loads(root_obj_serialized)
closures = root_obj.get('__closure', {})
closures = root_obj.get("__closure", {})
# Check which children are not already in the target transport
children_ids = list(closures.keys())
children_found_map = target_transport.has_objects(children_ids)
new_children_ids = [id for id in children_found_map if not children_found_map[id]]
new_children_ids = [
id for id in children_found_map if not children_found_map[id]
]
# Get the new children
endpoint = f"{self.url}/api/getobjects/{self.stream_id}"
r = self.session.post(endpoint, data={"objects": json.dumps(new_children_ids)}, stream=True)
r = self.session.post(
endpoint, data={"objects": json.dumps(new_children_ids)}, stream=True
)
if r.encoding is None:
r.encoding = "utf-8"
lines = r.iter_lines(decode_unicode=True)
+41
View File
@@ -0,0 +1,41 @@
import pytest
from specklepy.api.credentials import StreamWrapper
class TestWrapper:
def test_parse_stream(self):
wrap = StreamWrapper("https://testing.speckle.dev/streams/a75ab4f10f")
assert wrap.type == "stream"
def test_parse_branch(self):
wacky_wrap = StreamWrapper(
"https://testing.speckle.dev/streams/4c3ce1459c/branches/%F0%9F%8D%95%E2%AC%85%F0%9F%8C%9F%20you%20wat%3F"
)
wrap = StreamWrapper(
"https://testing.speckle.dev/streams/4c3ce1459c/branches/next%20level"
)
assert wacky_wrap.type == "branch"
assert wacky_wrap.branch_name == "🍕⬅🌟 you wat?"
assert wrap.type == "branch"
def test_parse_commit(self):
wrap = StreamWrapper(
"https://testing.speckle.dev/streams/4c3ce1459c/commits/8b9b831792"
)
assert wrap.type == "commit"
def test_parse_object(self):
wrap = StreamWrapper(
"https://testing.speckle.dev/streams/a75ab4f10f/objects/5530363e6d51c904903dafc3ea1d2ec6"
)
assert wrap.type == "object"
def test_parse_globals_as_branch(self):
wrap = StreamWrapper("https://testing.speckle.dev/streams/0c6ad366c4/globals/")
assert wrap.type == "branch"
def test_parse_globals_as_commit(self):
wrap = StreamWrapper(
"https://testing.speckle.dev/streams/0c6ad366c4/globals/abd3787893"
)
assert wrap.type == "commit"