Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| abc62f0811 | |||
| fe03d96ae2 | |||
| 078a6c8da8 | |||
| 905377dea1 | |||
| 62c5114cb3 | |||
| 43a5302a90 | |||
| addaa996ea | |||
| 3b5421a5bc | |||
| 88e8c86fa6 |
@@ -1,5 +1,5 @@
|
||||
"""This module contains an SDK for working with Speckle Automate."""
|
||||
from speckle_automate import fixtures
|
||||
|
||||
from speckle_automate.automation_context import AutomationContext
|
||||
from speckle_automate.runner import execute_automate_function, run_function
|
||||
from speckle_automate.schema import (
|
||||
@@ -21,5 +21,4 @@ __all__ = [
|
||||
"ObjectResultLevel",
|
||||
"run_function",
|
||||
"execute_automate_function",
|
||||
"fixtures",
|
||||
]
|
||||
|
||||
@@ -264,7 +264,7 @@ class AutomationContext:
|
||||
files = {path_obj.name: open(str(path_obj), "rb")}
|
||||
|
||||
url = (
|
||||
f"{self.automation_run_data.speckle_server_url}/api/stream/"
|
||||
f"{self.automation_run_data.speckle_server_url}api/stream/"
|
||||
f"{self.automation_run_data.project_id}/blob"
|
||||
)
|
||||
data = (
|
||||
@@ -290,6 +290,10 @@ class AutomationContext:
|
||||
"""Mark the current run a failure."""
|
||||
self._mark_run(AutomationStatus.FAILED, status_message)
|
||||
|
||||
def mark_run_exception(self, status_message: str) -> None:
|
||||
"""Mark the current run a failure."""
|
||||
self._mark_run(AutomationStatus.EXCEPTION, status_message)
|
||||
|
||||
def mark_run_success(self, status_message: Optional[str]) -> None:
|
||||
"""Mark the current run a success with an optional message."""
|
||||
self._mark_run(AutomationStatus.SUCCEEDED, status_message)
|
||||
|
||||
@@ -9,6 +9,7 @@ import sys
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
from typing import Callable, Optional, Tuple, TypeVar, Union, overload
|
||||
from enum import Enum
|
||||
|
||||
from pydantic import create_model
|
||||
from pydantic.json_schema import GenerateJsonSchema
|
||||
@@ -70,14 +71,7 @@ def execute_automate_function(
|
||||
automate_function: AutomateFunctionWithoutInputs,
|
||||
) -> None:
|
||||
...
|
||||
|
||||
|
||||
class AutomateGenerateJsonSchema(GenerateJsonSchema):
|
||||
def generate(self, schema, mode="validation"):
|
||||
json_schema = super().generate(schema, mode=mode)
|
||||
json_schema["$schema"] = self.schema_dialect
|
||||
return json_schema
|
||||
|
||||
|
||||
|
||||
def execute_automate_function(
|
||||
automate_function: Union[AutomateFunction[T], AutomateFunctionWithoutInputs],
|
||||
@@ -132,7 +126,10 @@ def execute_automate_function(
|
||||
|
||||
# if we've gotten this far, the execution should technically be completed as expected
|
||||
# thus exiting with 0 is the schemantically correct thing to do
|
||||
exit(0)
|
||||
exit_code = (
|
||||
1 if automation_context.run_status == AutomationStatus.EXCEPTION else 0
|
||||
)
|
||||
exit(exit_code)
|
||||
|
||||
else:
|
||||
raise NotImplementedError(f"Command: '{command}' is not supported.")
|
||||
@@ -175,6 +172,7 @@ def run_function(
|
||||
if automation_context.run_status not in [
|
||||
AutomationStatus.FAILED,
|
||||
AutomationStatus.SUCCEEDED,
|
||||
AutomationStatus.EXCEPTION,
|
||||
]:
|
||||
automation_context.mark_run_success(
|
||||
"WARNING: Automate assumed a success status,"
|
||||
@@ -183,7 +181,7 @@ def run_function(
|
||||
except Exception:
|
||||
trace = traceback.format_exc()
|
||||
print(trace)
|
||||
automation_context.mark_run_failed(
|
||||
automation_context.mark_run_exception(
|
||||
"Function error. Check the automation run logs for details."
|
||||
)
|
||||
finally:
|
||||
@@ -191,3 +189,70 @@ def run_function(
|
||||
automation_context.set_context_view()
|
||||
automation_context.report_run_status()
|
||||
return automation_context
|
||||
|
||||
|
||||
class AutomateGenerateJsonSchema(GenerateJsonSchema):
|
||||
def __init__(self, by_alias: bool = True, ref_template: str = "#/$defs/{model}"):
|
||||
super().__init__(by_alias=by_alias, ref_template=ref_template)
|
||||
self.schema_dialect = "https://json-schema.org/draft/2020-12/schema"
|
||||
|
||||
def generate(self, schema, mode="validation"):
|
||||
json_schema = super().generate(schema, mode=mode)
|
||||
json_schema["$schema"] = self.schema_dialect
|
||||
|
||||
if "properties" in json_schema:
|
||||
for prop, details in json_schema["properties"].items():
|
||||
self._process_property(
|
||||
details, json_schema.get("$defs", {}), getattr(schema, prop, None)
|
||||
)
|
||||
|
||||
if "$defs" in json_schema:
|
||||
for def_name, def_schema in json_schema["$defs"].items():
|
||||
self._process_property(def_schema, json_schema["$defs"], None)
|
||||
|
||||
return json_schema
|
||||
|
||||
def _process_property(self, property_schema, defs, field):
|
||||
if "allOf" in property_schema and len(property_schema["allOf"]) == 1:
|
||||
ref = property_schema["allOf"][0].get("$ref")
|
||||
if ref and ref.startswith("#/$defs/"):
|
||||
enum_name = ref.split("/")[-1]
|
||||
if enum_name in defs:
|
||||
enum_schema = defs[enum_name]
|
||||
property_schema.update(enum_schema)
|
||||
del property_schema["allOf"]
|
||||
|
||||
if "enum" in property_schema:
|
||||
enum_values = property_schema["enum"]
|
||||
property_schema["oneOf"] = [
|
||||
{"const": value, "title": str(value).upper()} for value in enum_values
|
||||
]
|
||||
del property_schema["enum"]
|
||||
|
||||
if isinstance(field, Enum):
|
||||
property_schema["oneOf"] = [
|
||||
{"const": item.value, "title": item.name} for item in field.__class__
|
||||
]
|
||||
if "default" in property_schema:
|
||||
property_schema["default"] = property_schema["default"].value
|
||||
|
||||
if "type" not in property_schema:
|
||||
if "oneOf" in property_schema:
|
||||
property_schema["type"] = "string"
|
||||
elif "default" in property_schema:
|
||||
property_schema["type"] = self._infer_type(property_schema["default"])
|
||||
else:
|
||||
property_schema["type"] = "object"
|
||||
|
||||
@staticmethod
|
||||
def _infer_type(value):
|
||||
if isinstance(value, bool):
|
||||
return "boolean"
|
||||
elif isinstance(value, int):
|
||||
return "integer"
|
||||
elif isinstance(value, float):
|
||||
return "number"
|
||||
elif isinstance(value, str):
|
||||
return "string"
|
||||
else:
|
||||
return "object"
|
||||
|
||||
@@ -63,6 +63,7 @@ class AutomationStatus(str, Enum):
|
||||
RUNNING = "RUNNING"
|
||||
FAILED = "FAILED"
|
||||
SUCCEEDED = "SUCCEEDED"
|
||||
EXCEPTION = "EXCEPTION"
|
||||
|
||||
|
||||
class ObjectResultLevel(str, Enum):
|
||||
|
||||
@@ -21,7 +21,7 @@ class SpeckleClient(CoreSpeckleClient):
|
||||
The `SpeckleClient` is your entry point for interacting with
|
||||
your Speckle Server's GraphQL API.
|
||||
You'll need to have access to a server to use it,
|
||||
or you can use our public server `speckle.xyz`.
|
||||
or you can use our public server `app.speckle.systems`.
|
||||
|
||||
To authenticate the client, you'll need to have downloaded
|
||||
the [Speckle Manager](https://speckle.guide/#speckle-manager)
|
||||
@@ -32,7 +32,7 @@ class SpeckleClient(CoreSpeckleClient):
|
||||
from specklepy.api.credentials import get_default_account
|
||||
|
||||
# initialise the client
|
||||
client = SpeckleClient(host="speckle.xyz") # or whatever your host is
|
||||
client = SpeckleClient(host="app.speckle.systems") # or whatever your host is
|
||||
# client = SpeckleClient(host="localhost:3000", use_ssl=False) or use local server
|
||||
|
||||
# authenticate the client with an account (account has been added in Speckle Manager)
|
||||
@@ -47,7 +47,7 @@ class SpeckleClient(CoreSpeckleClient):
|
||||
```
|
||||
"""
|
||||
|
||||
DEFAULT_HOST = "speckle.xyz"
|
||||
DEFAULT_HOST = "app.speckle.systems"
|
||||
USE_SSL = True
|
||||
|
||||
def __init__(
|
||||
|
||||
@@ -22,7 +22,7 @@ class StreamWrapper(CoreStreamWrapper):
|
||||
from specklepy.api.wrapper import StreamWrapper
|
||||
|
||||
# provide any stream, branch, commit, object, or globals url
|
||||
wrapper = StreamWrapper("https://speckle.xyz/streams/3073b96e86/commits/604bea8cc6")
|
||||
wrapper = StreamWrapper("https://app.speckle.systems/streams/3073b96e86/commits/604bea8cc6")
|
||||
|
||||
# get an authenticated SpeckleClient if you have a local account for the server
|
||||
client = wrapper.get_client()
|
||||
|
||||
@@ -30,7 +30,7 @@ class SpeckleClient:
|
||||
The `SpeckleClient` is your entry point for interacting with
|
||||
your Speckle Server's GraphQL API.
|
||||
You'll need to have access to a server to use it,
|
||||
or you can use our public server `speckle.xyz`.
|
||||
or you can use our public server `app.speckle.systems`.
|
||||
|
||||
To authenticate the client, you'll need to have downloaded
|
||||
the [Speckle Manager](https://speckle.guide/#speckle-manager)
|
||||
@@ -41,7 +41,7 @@ class SpeckleClient:
|
||||
from specklepy.api.credentials import get_default_account
|
||||
|
||||
# initialise the client
|
||||
client = SpeckleClient(host="speckle.xyz") # or whatever your host is
|
||||
client = SpeckleClient(host="app.speckle.systems") # or whatever your host is
|
||||
# client = SpeckleClient(host="localhost:3000", use_ssl=False) or use local server
|
||||
|
||||
# authenticate the client with an account (account has been added in Speckle Manager)
|
||||
@@ -56,7 +56,7 @@ class SpeckleClient:
|
||||
```
|
||||
"""
|
||||
|
||||
DEFAULT_HOST = "speckle.xyz"
|
||||
DEFAULT_HOST = "app.speckle.systems"
|
||||
USE_SSL = True
|
||||
|
||||
def __init__(
|
||||
|
||||
@@ -30,7 +30,7 @@ class StreamWrapper:
|
||||
from specklepy.api.wrapper import StreamWrapper
|
||||
|
||||
# provide any stream, branch, commit, object, or globals url
|
||||
wrapper = StreamWrapper("https://speckle.xyz/streams/3073b96e86/commits/604bea8cc6")
|
||||
wrapper = StreamWrapper("https://app.speckle.systems/streams/3073b96e86/commits/604bea8cc6")
|
||||
|
||||
# get an authenticated SpeckleClient if you have a local account for the server
|
||||
client = wrapper.get_client()
|
||||
|
||||
@@ -23,7 +23,7 @@ def host():
|
||||
def seed_user(host):
|
||||
seed = uuid.uuid4().hex
|
||||
user_dict = {
|
||||
"email": f"{seed[0:7]}@spockle.com",
|
||||
"email": f"{seed[0:7]}@example.org",
|
||||
"password": "$uper$3cr3tP@ss",
|
||||
"name": f"{seed[0:7]} Name",
|
||||
"company": "test spockle",
|
||||
|
||||
@@ -183,7 +183,7 @@ class TestStream:
|
||||
# NOTE: only works for server admins
|
||||
# invited = client.stream.invite_batch(
|
||||
# stream_id=stream.id,
|
||||
# emails=["userA@speckle.xyz", "userB@speckle.xyz"],
|
||||
# emails=["userA@example.org", "userB@example.org"],
|
||||
# user_ids=[second_user.id],
|
||||
# message="yeehaw 🤠",
|
||||
# )
|
||||
@@ -192,7 +192,7 @@ class TestStream:
|
||||
|
||||
# invited_only_email = client.stream.invite_batch(
|
||||
# stream_id=stream.id,
|
||||
# emails=["userC@speckle.xyz"],
|
||||
# emails=["userC@example.org"],
|
||||
# message="yeehaw 🤠",
|
||||
# )
|
||||
|
||||
|
||||
@@ -100,16 +100,20 @@ def test_parse_globals_as_commit():
|
||||
|
||||
|
||||
#! NOTE: the following three tests may not pass locally
|
||||
# if you have a `speckle.xyz` account in manager
|
||||
# if you have a `app.speckle.systems` account in manager
|
||||
def test_get_client_without_auth():
|
||||
wrap = StreamWrapper("https://speckle.xyz/streams/4c3ce1459c/commits/8b9b831792")
|
||||
wrap = StreamWrapper(
|
||||
"https://app.speckle.systems/streams/4c3ce1459c/commits/8b9b831792"
|
||||
)
|
||||
client = wrap.get_client()
|
||||
|
||||
assert client is not None
|
||||
|
||||
|
||||
def test_get_new_client_with_token(user_path):
|
||||
wrap = StreamWrapper("https://speckle.xyz/streams/4c3ce1459c/commits/8b9b831792")
|
||||
wrap = StreamWrapper(
|
||||
"https://app.speckle.systems/streams/4c3ce1459c/commits/8b9b831792"
|
||||
)
|
||||
client = wrap.get_client()
|
||||
client = wrap.get_client(token="super-secret-token")
|
||||
|
||||
@@ -117,7 +121,9 @@ def test_get_new_client_with_token(user_path):
|
||||
|
||||
|
||||
def test_get_transport_with_token():
|
||||
wrap = StreamWrapper("https://speckle.xyz/streams/4c3ce1459c/commits/8b9b831792")
|
||||
wrap = StreamWrapper(
|
||||
"https://app.speckle.systems/streams/4c3ce1459c/commits/8b9b831792"
|
||||
)
|
||||
client = wrap.get_client()
|
||||
assert not client.account.token # unauthenticated bc no local accounts
|
||||
|
||||
|
||||
Reference in New Issue
Block a user