Compare commits

..

36 Commits

Author SHA1 Message Date
Gergő Jedlicska 62e342b2cb Merge pull request #302 from specklesystems/gergo/objects_init
fix: object initialization
2024-02-12 17:38:04 +01:00
Gergő Jedlicska 804dd37639 Merge branch 'main' into gergo/objects_init 2024-02-12 17:30:03 +01:00
Gergő Jedlicska 64b61f54f5 Merge pull request #322 from specklesystems/kate-2.17-hotfix
.url attribute was used before assignment; assign account to unauthen…
2024-02-12 17:29:50 +01:00
KatKatKateryna 58789ab234 undo 2024-02-12 15:33:21 +00:00
KatKatKateryna 2696fb74ba raise instream of returning Exception 2024-02-12 15:30:00 +00:00
KatKatKateryna 57e176af91 typo 2024-02-12 15:23:37 +00:00
KatKatKateryna 437483641c extra test 2024-02-12 15:23:01 +00:00
KatKatKateryna 1e971b57c3 formatting 2024-02-12 15:14:10 +00:00
KatKatKateryna f04be12ec8 formatting 2024-02-12 15:11:11 +00:00
KatKatKateryna 51242928ca remove circular import 2024-02-12 14:56:47 +00:00
KatKatKateryna 77b3be9145 formatting 2024-02-12 14:52:31 +00:00
KatKatKateryna cc5abdf9cb Merge branch 'main' into kate-2.17-hotfix 2024-02-12 13:52:34 +00:00
KatKatKateryna 4eca5144a8 unused import 2024-02-12 13:52:29 +00:00
KatKatKateryna 8589663049 don't get default account 2024-02-12 13:47:24 +00:00
KatKatKateryna 956f72dd6a formatting 2024-02-12 13:09:09 +00:00
KatKatKateryna a2daa68c1c Merge branch 'main' into gergo/objects_init 2024-02-12 13:00:00 +00:00
Gergő Jedlicska d60feb73a2 Merge pull request #259 from specklesystems/kate/branch_create_fix
gql minimum characters restriction for consistent behavior with frontend
2024-02-12 13:42:32 +01:00
KatKatKateryna a0ca10ad20 add to_string; add cases for object url in fe2 (#327)
* add to_string; add cases for object url in fe2

* cover exceptions

* add federated model exception, reorder conditions

* formatting

* reformatting

* update black formatter

* resolving dependencies
2024-02-09 18:35:20 +01:00
Gergő Jedlicska f6118f3336 Merge pull request #326 from specklesystems/isFrontend2
add frontend2 property to ServerInfo
2024-02-05 13:25:33 +01:00
KatKatKateryna c7cd2f3e91 test 2024-02-05 11:23:22 +00:00
KatKatKateryna b374bfefd0 reorder import 2024-02-05 11:17:51 +00:00
KatKatKateryna d716db251f add frontend2 property to ServerInfo 2024-02-05 10:40:13 +00:00
Gergő Jedlicska 6d7e7c5c4b Merge pull request #324 from specklesystems/gergo/expose_ssl_verification
Update client.py
2023-12-15 16:15:45 +01:00
Gergő Jedlicska 7dcd9288ca Merge branch 'main' into gergo/expose_ssl_verification 2023-12-15 11:19:09 +01:00
Jedd Morgan 7d99f48def Merge pull request #323 from specklesystems/gergo/init_subclass_fix
fix: CNX-8350 remove unnecessary kwargs from init subclass call chain
2023-12-14 14:46:40 +00:00
Jedd Morgan 4332a8faef Merge branch 'main' into gergo/init_subclass_fix 2023-12-14 14:44:13 +00:00
Gergő Jedlicska deb8ad50c5 fix: client certificate verification 2023-12-11 17:34:26 +01:00
Gergő Jedlicska 4db0fa69fa Update client.py 2023-12-11 17:15:07 +01:00
Gergő Jedlicska 1eca211c96 fix: remove debug print statement 2023-12-06 11:50:07 +01:00
Gergő Jedlicska f65173581a fix: pre-commit config 2023-12-05 16:07:23 +01:00
Gergő Jedlicska 223c776c63 fix: remove unnecessary kwargs from init subclass call chain 2023-12-05 15:03:25 +01:00
KatKatKateryna ccccc53f59 .url attribute was used before assignment; assign account to unauthenticated client to get token 2023-12-05 05:21:28 +08:00
KatKatKateryna 541e3d961f moving restrictions to core 2023-09-19 10:56:39 +01:00
KatKatKateryna b02f183533 Merge branch 'main' into kate/branch_create_fix 2023-09-19 10:52:12 +01:00
Gergő Jedlicska 589198f5f1 fix: object initialization
now every time specklepy is imported, all object definitions are initialized.
 This ensures, that all speckle types are registered.
2023-09-19 11:31:57 +02:00
KatKatKateryna 6a9f4bf89b gql minimum characters restriction for consistent behavior with frontend 2023-02-08 07:29:02 +08:00
33 changed files with 704 additions and 489 deletions
Generated
+460 -421
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -16,7 +16,7 @@ packages = [
[tool.poetry.dependencies]
python = ">=3.8.0, <4.0"
pydantic = "^2.0"
pydantic = "^2.5"
appdirs = "^1.4.4"
gql = { extras = ["requests", "websockets"], version = "^3.3.0" }
ujson = "^5.3.0"
@@ -26,7 +26,7 @@ attrs = "^23.1.0"
httpx = "^0.25.0"
[tool.poetry.group.dev.dependencies]
black = "^22.8.0"
black = "23.11.0"
isort = "^5.7.0"
pytest = "^7.1.3"
pytest-ordering = "^0.6"
+1 -1
View File
@@ -18,7 +18,7 @@ from speckle_automate.schema import (
from specklepy.api import operations
from specklepy.api.client import SpeckleClient
from specklepy.logging.exceptions import SpeckleException
from specklepy.objects import Base
from specklepy.objects.base import Base
from specklepy.transports.memory import MemoryTransport
from specklepy.transports.server import ServerTransport
+3
View File
@@ -0,0 +1,3 @@
from specklepy import objects
__all__ = ["objects"]
+7 -1
View File
@@ -50,10 +50,16 @@ class SpeckleClient(CoreSpeckleClient):
DEFAULT_HOST = "speckle.xyz"
USE_SSL = True
def __init__(self, host: str = DEFAULT_HOST, use_ssl: bool = USE_SSL) -> None:
def __init__(
self,
host: str = DEFAULT_HOST,
use_ssl: bool = USE_SSL,
verify_certificate: bool = True,
) -> None:
super().__init__(
host=host,
use_ssl=use_ssl,
verify_certificate=verify_certificate,
)
self.account = Account()
+1 -2
View File
@@ -58,13 +58,12 @@ class SpeckleClient:
DEFAULT_HOST = "speckle.xyz"
USE_SSL = True
VERIFY_CERTIFICATE = True
def __init__(
self,
host: str = DEFAULT_HOST,
use_ssl: bool = USE_SSL,
verify_certificate: bool = VERIFY_CERTIFICATE,
verify_certificate: bool = True,
) -> None:
ws_protocol = "ws"
http_protocol = "http"
+1
View File
@@ -196,3 +196,4 @@ class ServerInfo(BaseModel):
scopes: Optional[List[dict]] = None
authStrategies: Optional[List[dict]] = None
version: Optional[str] = None
frontend2: Optional[bool] = None
@@ -4,6 +4,7 @@ from gql import gql
from specklepy.core.api.models import Branch
from specklepy.core.api.resource import ResourceBase
from specklepy.logging.exceptions import SpeckleException
NAME = "branch"
@@ -39,6 +40,8 @@ class Resource(ResourceBase):
}
"""
)
if len(name) < 3:
return SpeckleException(message="Branch Name must be at least 3 characters")
params = {
"branch": {
"streamId": stream_id,
+14 -1
View File
@@ -1,6 +1,7 @@
import re
from typing import Any, Dict, List, Tuple
import requests
from gql import gql
from specklepy.core.api.models import ServerInfo
@@ -56,9 +57,21 @@ class Resource(ResourceBase):
"""
)
return self.make_request(
server_info = self.make_request(
query=query, return_type="serverInfo", schema=ServerInfo
)
if isinstance(server_info, ServerInfo) and isinstance(
server_info.canonicalUrl, str
):
r = requests.get(
server_info.canonicalUrl, headers={"User-Agent": "specklepy SDK"}
)
if "x-speckle-frontend-2" in r.headers:
server_info.frontend2 = True
else:
server_info.frontend2 = False
return server_info
def version(self) -> Tuple[Any, ...]:
"""Get the server version
+8 -7
View File
@@ -166,7 +166,8 @@ class Resource(ResourceBase):
}
"""
)
if len(name) < 3 and len(name) != 0:
return SpeckleException(message="Stream Name must be at least 3 characters")
params = {
"stream": {"name": name, "description": description, "isPublic": is_public}
}
@@ -730,13 +731,13 @@ class Resource(ResourceBase):
"stream_id": stream_id,
"limit": limit,
"action_type": action_type,
"before": before.astimezone(timezone.utc).isoformat()
if before
else before,
"before": (
before.astimezone(timezone.utc).isoformat() if before else before
),
"after": after.astimezone(timezone.utc).isoformat() if after else after,
"cursor": cursor.astimezone(timezone.utc).isoformat()
if cursor
else cursor,
"cursor": (
cursor.astimezone(timezone.utc).isoformat() if cursor else cursor
),
}
except AttributeError as e:
raise SpeckleException(
+91 -34
View File
@@ -1,4 +1,4 @@
from urllib.parse import unquote, urlparse
from urllib.parse import quote, unquote, urlparse
from warnings import warn
from gql import gql
@@ -47,6 +47,7 @@ class StreamWrapper:
commit_id: str = None
object_id: str = None
branch_name: str = None
model_id: str = None
_client: SpeckleClient = None
_account: Account = None
@@ -86,45 +87,58 @@ class StreamWrapper:
# check for fe2 URL
if "/projects/" in parsed.path:
use_fe2 = True
key_stream = "project"
else:
use_fe2 = False
key_stream = "stream"
while segments:
segment = segments.pop(0)
if segments and (
(use_fe2 is False and segment.lower() == "streams")
or (use_fe2 is True and segment.lower() == "projects")
):
self.stream_id = segments.pop(0)
elif segments and segment.lower() == "commits":
self.commit_id = segments.pop(0)
elif segments and (
(use_fe2 is False and segment.lower() == "branches")
or (use_fe2 is True and segment.lower() == "models")
):
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:
if use_fe2 is False:
if segments and segment.lower() == "streams":
self.stream_id = segments.pop(0)
elif segments and segment.lower() == "commits":
self.commit_id = segments.pop(0)
else:
raise SpeckleException(
f"Cannot parse {url} into a stream wrapper class - invalid URL"
" provided."
)
elif segments and segment.lower() == "branches":
self.branch_name = unquote(segments.pop(0))
elif segments and segment.lower() == "objects":
self.object_id = segments.pop(0)
elif segment.lower() == "globals":
self.branch_name = "globals"
if segments:
self.commit_id = segments.pop(0)
else:
raise SpeckleException(
f"Cannot parse {url} into a stream wrapper class - invalid URL"
" provided."
)
elif segments and use_fe2 is True:
if segment.lower() == "projects":
self.stream_id = segments.pop(0)
elif segment.lower() == "models":
next_segment = segments.pop(0)
if "," in next_segment:
raise SpeckleException("Multi-model urls are not supported yet")
elif unquote(next_segment).startswith("$"):
raise SpeckleException(
"Federation model urls are not supported"
)
elif len(next_segment) == 32:
self.object_id = next_segment
else:
self.branch_name = unquote(next_segment).split("@")[0]
if "@" in unquote(next_segment):
self.commit_id = unquote(next_segment).split("@")[1]
else:
raise SpeckleException(
f"Cannot parse {url} into a stream wrapper class - invalid URL"
" provided."
)
if use_fe2 is True and self.branch_name is not None:
if "," in self.branch_name:
raise SpeckleException("Multi-model urls are not supported yet")
if "@" in self.branch_name:
model_id = self.branch_name.split("@")[0]
self.commit_id = self.branch_name.split("@")[1]
else:
model_id = self.branch_name
self.model_id = self.branch_name
# get branch name
query = gql(
"""
@@ -139,7 +153,7 @@ class StreamWrapper:
"""
)
self._client = self.get_client()
params = {"project_id": self.stream_id, "model_id": model_id}
params = {"project_id": self.stream_id, "model_id": self.model_id}
project = self._client.httpclient.execute(query, params)
try:
@@ -149,7 +163,7 @@ class StreamWrapper:
if not self.stream_id:
raise SpeckleException(
f"Cannot parse {url} into a stream wrapper class - no stream id found."
f"Cannot parse {url} into a stream wrapper class - no {key_stream} id found."
)
@property
@@ -230,3 +244,46 @@ class StreamWrapper:
if not self._account or not self._account.token:
self.get_account(token)
return ServerTransport(self.stream_id, account=self._account)
def to_string(self) -> str:
"""
Constructs a URL depending on the StreamWrapper type and FE version.
"""
use_fe2 = False
key_streams = "/streams/"
key_branches = "/branches/"
if isinstance(self.branch_name, str):
value_branch = quote(self.branch_name)
if self.branch_name == "globals":
key_branches = "/"
key_commits = "/commits/"
if isinstance(self.commit_id, str) and self.branch_name == "globals":
key_commits = "/globals/"
key_objects = "/objects/"
if "/projects/" in self.stream_url:
use_fe2 = True
key_streams = "/projects/"
key_branches = "/models/"
value_branch = self.model_id
key_commits = "@"
key_objects = "/models/"
wrapper_type = self.type
if use_fe2 is False or (use_fe2 is True and not self.model_id):
base_url = f"{self.server_url}{key_streams}{self.stream_id}"
else: # fe2 is True and model_id available
base_url = f"{self.server_url}{key_streams}{self.stream_id}{key_branches}{value_branch}"
if wrapper_type == "object":
return f"{base_url}{key_objects}{self.object_id}"
elif wrapper_type == "commit":
return f"{base_url}{key_commits}{self.commit_id}"
elif wrapper_type == "branch":
return f"{self.server_url}{key_streams}{self.stream_id}{key_branches}{value_branch}"
elif wrapper_type == "stream":
return f"{self.server_url}{key_streams}{self.stream_id}"
else:
raise SpeckleException(
f"Cannot parse StreamWrapper of type '{wrapper_type}'"
)
+1 -1
View File
@@ -1,6 +1,6 @@
from typing import Optional
from specklepy.objects import Base
from specklepy.objects.base import Base
class CRS(Base, speckle_type="Objects.GIS.CRS"):
+1 -1
View File
@@ -1,6 +1,6 @@
from typing import List, Optional, Union
from specklepy.objects import Base
from specklepy.objects.base import Base
from specklepy.objects.geometry import (
Arc,
Circle,
+2 -6
View File
@@ -13,8 +13,8 @@ class Layer(Base, detachable={"features"}):
def __init__(
self,
name: str = None,
crs: CRS = None,
name: Optional[str] = None,
crs: Optional[CRS] = None,
units: str = "m",
features: Optional[List[Base]] = None,
layerType: str = "None",
@@ -39,7 +39,6 @@ class VectorLayer(
speckle_type="VectorLayer",
serialize_ignore={"features"},
):
"""GIS Vector Layer"""
name: Optional[str] = None
@@ -68,7 +67,6 @@ class RasterLayer(
speckle_type="RasterLayer",
serialize_ignore={"features"},
):
"""GIS Raster Layer"""
name: Optional[str] = None
@@ -96,7 +94,6 @@ class VectorLayer( # noqa: F811
speckle_type="Objects.GIS.VectorLayer",
serialize_ignore={"features"},
):
"""GIS Vector Layer"""
name: Optional[str] = None
@@ -124,7 +121,6 @@ class RasterLayer( # noqa: F811
speckle_type="Objects.GIS.RasterLayer",
serialize_ignore={"features"},
):
"""GIS Raster Layer"""
name: Optional[str] = None
+19 -2
View File
@@ -1,6 +1,23 @@
"""Builtin Speckle object kit."""
from specklepy.objects import encoding, geometry, other, primitive, structural, units
from specklepy.objects import (
GIS,
encoding,
geometry,
other,
primitive,
structural,
units,
)
from specklepy.objects.base import Base
__all__ = ["Base", "encoding", "geometry", "other", "units", "structural", "primitive"]
__all__ = [
"Base",
"encoding",
"geometry",
"other",
"units",
"structural",
"primitive",
"GIS",
]
+2 -1
View File
@@ -188,7 +188,8 @@ class _RegisteringBase:
cls._detachable = cls._detachable.union(detachable)
if serialize_ignore:
cls._serialize_ignore = cls._serialize_ignore.union(serialize_ignore)
super().__init_subclass__(**kwargs)
# we know, that the super here is object, that takes no args on init subclass
return super().__init_subclass__()
# T = TypeVar("T")
@@ -3,7 +3,7 @@ from typing import Any, Callable, Collection, Iterable, Iterator, List, Optional
from attrs import define
from typing_extensions import Protocol, final
from specklepy.objects import Base
from specklepy.objects.base import Base
class ITraversalRule(Protocol):
+12 -8
View File
@@ -73,7 +73,7 @@ class ServerTransport(AbstractTransport):
warn(
SpeckleWarning(
"Unauthenticated Speckle Client provided to Server Transport"
f" for {self.url}. Receiving from private streams will fail."
f" for {url}. Receiving from private streams will fail."
)
)
else:
@@ -84,14 +84,18 @@ class ServerTransport(AbstractTransport):
self.stream_id = stream_id
self.url = url
self._batch_sender = BatchSender(
self.url, self.stream_id, self.account.token, max_batch_size_mb=1
)
self.session = requests.Session()
self.session.headers.update(
{"Authorization": f"Bearer {self.account.token}", "Accept": "text/plain"}
)
if self.account is not None:
self._batch_sender = BatchSender(
self.url, self.stream_id, self.account.token, max_batch_size_mb=1
)
self.session.headers.update(
{
"Authorization": f"Bearer {self.account.token}",
"Accept": "text/plain",
}
)
@property
def name(self) -> str:
@@ -18,6 +18,7 @@ class TestServer:
server = client.server.get()
assert isinstance(server, ServerInfo)
assert isinstance(server.frontend2, bool)
def test_server_version(self, client: SpeckleClient):
version = client.server.version()
@@ -44,6 +44,14 @@ class TestStream:
assert isinstance(stream.id, str)
def test_stream_create_short_name(self, client, stream, updated_stream):
new_stream_id = client.stream.create(
name="x",
description=stream.description,
is_public=stream.isPublic,
)
assert isinstance(new_stream_id, SpeckleException)
def test_stream_get(self, client, stream):
fetched_stream = client.stream.get(stream.id)
@@ -2,11 +2,13 @@ import json
import tempfile
from pathlib import Path
from typing import Iterable
from urllib.parse import unquote
import pytest
from specklepy.api.wrapper import StreamWrapper
from specklepy.core.helpers import speckle_path_provider
from specklepy.logging.exceptions import SpeckleException
@pytest.fixture(scope="module", autouse=True)
@@ -29,6 +31,22 @@ def user_path() -> Iterable[Path]:
speckle_path_provider.override_application_data_path(None)
def test_parse_empty():
try:
StreamWrapper("https://testing.speckle.dev/streams")
assert False
except SpeckleException:
assert True
def test_parse_empty_fe2():
try:
StreamWrapper("https://latest.speckle.systems/projects")
assert False
except SpeckleException:
assert True
def test_parse_stream():
wrap = StreamWrapper("https://testing.speckle.dev/streams/a75ab4f10f")
assert wrap.type == "stream"
@@ -142,8 +160,56 @@ def test_parse_model():
assert wrap.type == "branch"
def test_parse_federated_model():
try:
StreamWrapper("https://latest.speckle.systems/projects/843d07eb10/models/$main")
assert False
except SpeckleException:
assert True
def test_parse_multi_model():
try:
StreamWrapper(
"https://latest.speckle.systems/projects/2099ac4b5f/models/1870f279e3,a9cfdddc79"
)
assert False
except SpeckleException:
assert True
def test_parse_object_fe2():
wrap = StreamWrapper(
"https://latest.speckle.systems/projects/24c3741255/models/b48d1b10f5a732f4ca4144286391282c"
)
assert wrap.type == "object"
def test_parse_version():
wrap = StreamWrapper(
"https://latest.speckle.systems/projects/843d07eb10/models/4e7345c838@c42d5cbac1"
)
wrap_quoted = StreamWrapper(
"https://latest.speckle.systems/projects/843d07eb10/models/4e7345c838%40c42d5cbac1"
)
assert wrap.type == "commit"
assert wrap_quoted.type == "commit"
def test_to_string():
urls = [
"https://testing.speckle.dev/streams/a75ab4f10f",
"https://testing.speckle.dev/streams/4c3ce1459c/branches/%F0%9F%8D%95%E2%AC%85%F0%9F%8C%9F%20you%20wat%3F",
"https://testing.speckle.dev/streams/0c6ad366c4/globals",
"https://testing.speckle.dev/streams/0c6ad366c4/globals/abd3787893",
"https://testing.speckle.dev/streams/4c3ce1459c/commits/8b9b831792",
"https://testing.speckle.dev/streams/a75ab4f10f/objects/5530363e6d51c904903dafc3ea1d2ec6",
"https://latest.speckle.systems/projects/843d07eb10",
"https://latest.speckle.systems/projects/843d07eb10/models/4e7345c838",
"https://latest.speckle.systems/projects/843d07eb10/models/4e7345c838@c42d5cbac1",
"https://latest.speckle.systems/projects/843d07eb10/models/4e7345c838%40c42d5cbac1",
"https://latest.speckle.systems/projects/24c3741255/models/b48d1b10f5a732f4ca4144286391282c",
]
for url in urls:
wrap = StreamWrapper(url)
assert unquote(wrap.to_string()) == unquote(url)