Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 54cd1e585a |
@@ -1,43 +0,0 @@
|
|||||||
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
|
|
||||||
// README at: https://github.com/devcontainers/templates/tree/main/src/python
|
|
||||||
{
|
|
||||||
"name": "Python 3",
|
|
||||||
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
|
|
||||||
"image": "mcr.microsoft.com/devcontainers/python:1-3.11-bullseye",
|
|
||||||
"features": {
|
|
||||||
"ghcr.io/devcontainers-contrib/features/poetry:2": {}
|
|
||||||
},
|
|
||||||
|
|
||||||
"remoteEnv": {
|
|
||||||
"SPECKLE_TOKEN": "foobar"
|
|
||||||
},
|
|
||||||
"containerEnv": {
|
|
||||||
"SPECKLE_TOKEN": "asdfasdf"
|
|
||||||
},
|
|
||||||
|
|
||||||
// Features to add to the dev container. More info: https://containers.dev/features.
|
|
||||||
// "features": {},
|
|
||||||
|
|
||||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
|
||||||
// "forwardPorts": [],
|
|
||||||
|
|
||||||
// Use 'postCreateCommand' to run commands after the container is created.
|
|
||||||
"postCreateCommand": "cp .env.example .env && POETRY_VIRTUALENVS_IN_PROJECT=true poetry install --no-root",
|
|
||||||
|
|
||||||
// Configure tool-specific properties.
|
|
||||||
"customizations": {
|
|
||||||
"vscode": {
|
|
||||||
// Add the IDs of extensions you want installed when the container is created.
|
|
||||||
"extensions": [
|
|
||||||
"ms-python.vscode-pylance",
|
|
||||||
"ms-python.python",
|
|
||||||
"ms-python.black-formatter",
|
|
||||||
"streetsidesoftware.code-spell-checker",
|
|
||||||
"mikestead.dotenv"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
|
||||||
// "remoteUser": "root"
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
SPECKLE_TOKEN=mytoken
|
|
||||||
+18
-15
@@ -1,17 +1,21 @@
|
|||||||
name: 'build and deploy Speckle functions'
|
name: 'build and deploy Speckle functions'
|
||||||
on:
|
on: # rebuild any PRs and any branch changes
|
||||||
workflow_dispatch:
|
pull_request:
|
||||||
push:
|
push:
|
||||||
tags:
|
branches:
|
||||||
- '*'
|
- main
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish-automate-function-version: # make sure the action works on a clean machine without building
|
publish-automate-function-version: # make sure the action works on a clean machine without building
|
||||||
env:
|
|
||||||
FUNCTION_SCHEMA_FILE_NAME: functionSchema.json
|
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3.4.0
|
- uses: actions/checkout@v3.4.0
|
||||||
|
- uses: actions/checkout@v3.4.0
|
||||||
|
with:
|
||||||
|
repository: 'specklesystems/speckle-automate-github-composite-action'
|
||||||
|
path: 'github-action'
|
||||||
|
ref: main
|
||||||
|
|
||||||
- uses: actions/setup-python@v4
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: '3.11'
|
python-version: '3.11'
|
||||||
@@ -25,14 +29,13 @@ jobs:
|
|||||||
- name: Restore dependencies
|
- name: Restore dependencies
|
||||||
run: poetry install --no-root
|
run: poetry install --no-root
|
||||||
- name: Extract functionInputSchema
|
- name: Extract functionInputSchema
|
||||||
id: extract_schema
|
|
||||||
run: |
|
run: |
|
||||||
python main.py generate_schema ${HOME}/${{ env.FUNCTION_SCHEMA_FILE_NAME }}
|
echo "function_input_schema=$(python schema_generation.py)" >> "$GITHUB_ENV"
|
||||||
- name: Speckle Automate Function - Build and Publish
|
- uses: ./github-action
|
||||||
uses: specklesystems/speckle-automate-github-composite-action@0.6.7
|
id: function_publish
|
||||||
with:
|
with:
|
||||||
speckle_automate_url: ${{ env.SPECKLE_AUTOMATE_URL || 'https://automate.speckle.dev' }}
|
speckle_server_url: 'https://automate.speckle.dev'
|
||||||
speckle_token: ${{ secrets.SPECKLE_FUNCTION_TOKEN }}
|
speckle_token: ${{ secrets.SPECKLE_AUTOMATE_FUNCTION_PUBLISH_TOKEN }}
|
||||||
speckle_function_id: ${{ secrets.SPECKLE_FUNCTION_ID }}
|
speckle_function_id: ${{ secrets.SPECKLE_AUTOMATE_FUNCTION_ID }}
|
||||||
speckle_function_input_schema_file_path: ${{ env.FUNCTION_SCHEMA_FILE_NAME }}
|
speckle_function_input_schema: ${{ env.function_input_schema }}
|
||||||
speckle_function_command: 'python main.py run'
|
speckle_function_command: 'python main.py'
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,python,pycharm
|
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,python,pycharm
|
||||||
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,python,pycharm
|
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,python,pycharm
|
||||||
|
|
||||||
**/.env
|
|
||||||
**/.envrc
|
|
||||||
|
|
||||||
**/.tool-versions
|
|
||||||
|
|
||||||
### PyCharm ###
|
### PyCharm ###
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
python 3.11.0
|
||||||
Vendored
+4
-11
@@ -5,19 +5,12 @@
|
|||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"configurations": [
|
"configurations": [
|
||||||
{
|
{
|
||||||
"name": "Speckle Automate function",
|
"name": "Python: Current File",
|
||||||
"type": "python",
|
"type": "python",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"program": "main.py",
|
"program": "${file}",
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
"justMyCode": true,
|
"justMyCode": false
|
||||||
"envFile": "${workspaceFolder}/.env",
|
|
||||||
"args": [
|
|
||||||
"run",
|
|
||||||
"{\"projectId\": \"843d07eb10\", \"modelId\": \"base design\", \"versionId\": \"2a32ccfee1\", \"speckleServerUrl\": \"https://latest.speckle.systems\"}",
|
|
||||||
// make sure to use camelCase for variable names
|
|
||||||
"{\"forbiddenSpeckleType\": \"Objects.Geometry.Brep\"}"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Vendored
+1
-7
@@ -1,9 +1,3 @@
|
|||||||
{
|
{
|
||||||
"cSpell.words": [
|
"cSpell.words": ["camelcase", "pydantic", "stringcase", "typer"]
|
||||||
"camelcase",
|
|
||||||
"pydantic",
|
|
||||||
"stringcase",
|
|
||||||
"typer"
|
|
||||||
],
|
|
||||||
"python.defaultInterpreterPath": ".venv/bin/python"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ RUN pip install poetry
|
|||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN poetry export -f requirements.txt --output requirements.txt && pip install -r requirements.txt
|
RUN poetry export -f requirements.txt --output requirements.txt && pip install -r requirements.txt
|
||||||
|
# RUN poetry install --no-root --no-dev
|
||||||
|
|||||||
@@ -1,73 +1 @@
|
|||||||
# Speckle Automate function template - Python
|
# Go Automate Go
|
||||||
|
|
||||||
|
|
||||||
This is a template repository for a Speckle Automate functions written in python
|
|
||||||
using the [specklepy](https://pypi.org/project/specklepy/) SDK to interact with Speckle data.
|
|
||||||
|
|
||||||
This template contains the full scaffolding required to publish a function to the automate environment.
|
|
||||||
Also has some sane defaults for a development environment setups.
|
|
||||||
|
|
||||||
## Getting started
|
|
||||||
|
|
||||||
1. Use this template repository to create a new repository in your own / organization's profile.
|
|
||||||
|
|
||||||
Register the function
|
|
||||||
|
|
||||||
### Add new dependencies
|
|
||||||
|
|
||||||
To add new python package dependencies to the project, use:
|
|
||||||
`$ poetry add pandas`
|
|
||||||
|
|
||||||
### Change launch variables
|
|
||||||
|
|
||||||
describe how the launch.json should be edited
|
|
||||||
|
|
||||||
### Github Codespaces
|
|
||||||
|
|
||||||
create new repo from template, and use the create new code
|
|
||||||
|
|
||||||
|
|
||||||
### Local dev environment
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Archive
|
|
||||||
|
|
||||||
This is a simple example of how to use the Speckle Automate Python package to automate the creation of a Speckle stream.
|
|
||||||
|
|
||||||
|
|
||||||
## Using this Speckle Function
|
|
||||||
|
|
||||||
1. [Create](https://automate.speckle.dev/) a new Speckle Automation.
|
|
||||||
1. Select your Speckle Project and Speckle Model.
|
|
||||||
1. Select the existing Speckle Function named [`Random comment on IFC beam`](https://automate.speckle.dev/functions/e110be8fad).
|
|
||||||
1. Enter a phrase to use in the comment.
|
|
||||||
1. Click `Create Automation`.
|
|
||||||
|
|
||||||
## Getting Started with creating your own Speckle Function
|
|
||||||
|
|
||||||
1. [Fork](https://docs.github.com/en/get-started/quickstart/fork-a-repo) this repository.
|
|
||||||
1. [Clone](https://docs.github.com/en/get-started/quickstart/fork-a-repo#cloning-your-forked-repository) your forked repository to your development environment, or use [GitHub CodeSpaces](https://github.com/features/codespaces).
|
|
||||||
1. [Register](https://automate.speckle.dev/) your Function with [Speckle Automate](https://automate.speckle.dev/).
|
|
||||||
1. After completing the registration of the Function you will be shown a Function Publish Token and a Function ID. You will need these later.
|
|
||||||
1. Save your Function Publish Token as a [GitHub Action Secret](https://docs.github.com/en/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository) named `SPECKLE_AUTOMATE_FUNCTION_PUBLISH_TOKEN`.
|
|
||||||
1. Save your Function ID as a [GitHub Action Secret](https://docs.github.com/en/actions/security-guides/encrypted-secrets#creating-encrypted-secrets-for-a-repository) named `SPECKLE_AUTOMATE_FUNCTION_ID`.
|
|
||||||
1. Make changes to your Function in `main.py`. See below for the Developer Requirements, and instructions on how to test.
|
|
||||||
1. Every commit to `main` branch will create a new version of your Speckle Function.
|
|
||||||
|
|
||||||
## Developer Requirements
|
|
||||||
|
|
||||||
1. Install the following:
|
|
||||||
- [Python 3](https://www.python.org/downloads/)
|
|
||||||
- [Poetry](https://python-poetry.org/docs/#installing-with-the-official-installer)
|
|
||||||
1. Run `poetry shell && poetry install` to install the required Python packages.
|
|
||||||
|
|
||||||
## Building and Testing
|
|
||||||
|
|
||||||
The code can be tested locally by running `poetry run pytest`.
|
|
||||||
The code should also be packaged into the format required by Speckle Automate, a Docker Container Image, and that should also be tested.
|
|
||||||
|
|
||||||
## Resources
|
|
||||||
|
|
||||||
- [Learn](https://speckle.guide/dev/python.html) more about SpecklePy, and interacting with Speckle from Python.
|
|
||||||
+3
-7
@@ -1,13 +1,9 @@
|
|||||||
"""Helper module for a simple speckle object tree flattening."""
|
from typing import Iterable
|
||||||
|
|
||||||
from collections.abc import Iterable
|
|
||||||
|
|
||||||
from specklepy.objects import Base
|
from specklepy.objects import Base
|
||||||
|
|
||||||
|
|
||||||
def flatten_base(base: Base) -> Iterable[Base]:
|
def flatten_base(base: Base) -> Iterable[Base]:
|
||||||
"""Take a base and flatten it to an iterable of bases."""
|
|
||||||
if hasattr(base, "elements"):
|
if hasattr(base, "elements"):
|
||||||
for element in base["elements"]:
|
for element in base.elements:
|
||||||
yield from flatten_base(element)
|
yield from flatten_base(element)
|
||||||
yield base
|
yield base
|
||||||
@@ -1,97 +1,75 @@
|
|||||||
"""This module contains the business logic of the function.
|
import typer
|
||||||
|
from pydantic import BaseModel
|
||||||
use the automation_context module to wrap your function in an Autamate context helper
|
from stringcase import camelcase
|
||||||
"""
|
from specklepy.transports.memory import MemoryTransport
|
||||||
|
from specklepy.transports.server import ServerTransport
|
||||||
from pydantic import Field
|
from specklepy.api.operations import receive
|
||||||
from speckle_automate import (
|
from specklepy.api.client import SpeckleClient
|
||||||
AutomateBase,
|
import random
|
||||||
AutomationContext,
|
|
||||||
execute_automate_function,
|
|
||||||
)
|
|
||||||
|
|
||||||
from flatten import flatten_base
|
from flatten import flatten_base
|
||||||
|
from make_comment import make_comment
|
||||||
|
|
||||||
|
|
||||||
class FunctionInputs(AutomateBase):
|
class SpeckleProjectData(BaseModel):
|
||||||
"""These are function author defined values.
|
"""Values of the project / model that triggered the run of this function."""
|
||||||
|
|
||||||
Automate will make sure to supply them matching the types specified here.
|
project_id: str
|
||||||
Please use the pydantic model schema to define your inputs:
|
model_id: str
|
||||||
https://docs.pydantic.dev/latest/usage/models/
|
version_id: str
|
||||||
|
speckle_server_url: str
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
alias_generator = camelcase
|
||||||
|
|
||||||
|
|
||||||
|
class FunctionInputs(BaseModel):
|
||||||
|
"""
|
||||||
|
These are function author defined values, automate will make sure to supply them.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
forbidden_speckle_type: str = Field(
|
comment_text: str
|
||||||
title="Forbidden speckle type",
|
|
||||||
description=(
|
class Config:
|
||||||
"If a object has the following speckle_type,"
|
alias_generator = camelcase
|
||||||
" it will be marked with an error."
|
|
||||||
),
|
|
||||||
|
def main(speckle_project_data: str, function_inputs: str, speckle_token: str):
|
||||||
|
project_data = SpeckleProjectData.parse_raw(speckle_project_data)
|
||||||
|
inputs = FunctionInputs.parse_raw(function_inputs)
|
||||||
|
|
||||||
|
client = SpeckleClient(project_data.speckle_server_url, use_ssl=False)
|
||||||
|
client.authenticate_with_token(speckle_token)
|
||||||
|
commit = client.commit.get(project_data.project_id, project_data.version_id)
|
||||||
|
branch = client.branch.get(project_data.project_id, project_data.model_id, 1)
|
||||||
|
|
||||||
|
memory_transport = MemoryTransport()
|
||||||
|
server_transport = ServerTransport(project_data.project_id, client)
|
||||||
|
base = receive(commit.referencedObject, server_transport, memory_transport)
|
||||||
|
|
||||||
|
random_beam = random.choice(
|
||||||
|
[b for b in flatten_base(base) if b.speckle_type == "IFCBEAM"]
|
||||||
|
)
|
||||||
|
|
||||||
|
make_comment(
|
||||||
|
client,
|
||||||
|
project_data.project_id,
|
||||||
|
branch.id,
|
||||||
|
project_data.version_id,
|
||||||
|
inputs.comment_text,
|
||||||
|
random_beam.id,
|
||||||
|
)
|
||||||
|
|
||||||
|
print(
|
||||||
|
"Ran function with",
|
||||||
|
f"{speckle_project_data} {function_inputs}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def automate_function(
|
|
||||||
automate_context: AutomationContext,
|
|
||||||
function_inputs: FunctionInputs,
|
|
||||||
) -> None:
|
|
||||||
"""This is an example Speckle Automate function.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
automate_context: A context helper object, that carries relevant information
|
|
||||||
about the runtime context of this function.
|
|
||||||
It gives access to the Speckle project data, that triggered this run.
|
|
||||||
It also has conveniece methods attach result data to the Speckle model.
|
|
||||||
function_inputs: An instance object matching the defined schema.
|
|
||||||
"""
|
|
||||||
# the context provides a conveniet way, to receive the triggering version
|
|
||||||
version_root_object = automate_context.receive_version()
|
|
||||||
|
|
||||||
count = 0
|
|
||||||
for b in flatten_base(version_root_object):
|
|
||||||
if b.speckle_type == function_inputs.forbidden_speckle_type:
|
|
||||||
if not b.id:
|
|
||||||
raise ValueError("Cannot operate on objects without their id's.")
|
|
||||||
|
|
||||||
automate_context.attach_error_to_objects(
|
|
||||||
category="Forbidden speckle_type",
|
|
||||||
object_ids=b.id,
|
|
||||||
message="This project should not contain the type: "
|
|
||||||
f"{b.speckle_type}",
|
|
||||||
)
|
|
||||||
count += 1
|
|
||||||
|
|
||||||
if count > 0:
|
|
||||||
# this is how a run is marked with a failure cause
|
|
||||||
automate_context.mark_run_failed(
|
|
||||||
"Automation failed: "
|
|
||||||
f"Found {count} object that have one of the forbidden speckle types: "
|
|
||||||
f"{function_inputs.forbidden_speckle_type}"
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
automate_context.mark_run_success("No forbidden types found.")
|
|
||||||
|
|
||||||
# if the function generates file results, this is how it can be
|
|
||||||
# attached to the Speckle project / model
|
|
||||||
# automate_context.store_file_result("./report.pdf")
|
|
||||||
|
|
||||||
|
|
||||||
def automate_function_without_inputs(automate_context: AutomationContext) -> None:
|
|
||||||
"""A function example without inputs.
|
|
||||||
|
|
||||||
If your function does not need any input variables,
|
|
||||||
besides what the automation context provides,
|
|
||||||
the inputs argument can be omitted.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# make sure to call the function with the executor
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# NOTE: always pass in the automate function by its reference, do not invoke it!
|
# main(
|
||||||
|
# '{"projectId":"bbb3aba8d4", "modelId":"automateTest", "versionId": "d37ee808db", "speckleServerUrl": "http://hyperion:3000" }',
|
||||||
# pass in the function reference with the inputs schema to the executor
|
# '{"commentText": "automate made me to do this"}',
|
||||||
execute_automate_function(automate_function, FunctionInputs)
|
# "c3e6536e570a94e5d84590c51b29198b26dce89439",
|
||||||
|
# )
|
||||||
# if the function has no arguments, the executor can handle it like so
|
typer.run(main)
|
||||||
# execute_automate_function(automate_function_without_inputs)
|
|
||||||
|
|||||||
+103
@@ -0,0 +1,103 @@
|
|||||||
|
from specklepy.api.client import SpeckleClient
|
||||||
|
from gql import gql
|
||||||
|
|
||||||
|
|
||||||
|
def make_comment(
|
||||||
|
client: SpeckleClient,
|
||||||
|
project_id: str,
|
||||||
|
model_id: str,
|
||||||
|
version_id: str,
|
||||||
|
comment_text: str,
|
||||||
|
selected_object_id: str,
|
||||||
|
) -> None:
|
||||||
|
client.httpclient.execute(
|
||||||
|
gql(
|
||||||
|
"""
|
||||||
|
mutation createComment($input: CreateCommentInput!) {
|
||||||
|
commentMutations {
|
||||||
|
create(input: $input) {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
),
|
||||||
|
{
|
||||||
|
"input": {
|
||||||
|
"content": {
|
||||||
|
"blobIds": [],
|
||||||
|
"doc": {
|
||||||
|
"content": [
|
||||||
|
{
|
||||||
|
"content": [{"text": comment_text, "type": "text"}],
|
||||||
|
"type": "paragraph",
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"type": "doc",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"projectId": project_id,
|
||||||
|
"resourceIdString": model_id,
|
||||||
|
"screenshot": None,
|
||||||
|
"viewerState": {
|
||||||
|
"projectId": project_id,
|
||||||
|
"resources": {
|
||||||
|
"request": {
|
||||||
|
"resourceIdString": f"{model_id}@{version_id}",
|
||||||
|
"threadFilters": {},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sessionId": "fooobarbaz",
|
||||||
|
"ui": {
|
||||||
|
"camera": {
|
||||||
|
"isOrthoProjection": False,
|
||||||
|
"position": [
|
||||||
|
-13.959975903859306,
|
||||||
|
109.21340462426888,
|
||||||
|
19.00868018548827,
|
||||||
|
],
|
||||||
|
"target": [
|
||||||
|
-28.304303646087646,
|
||||||
|
99.69336318969727,
|
||||||
|
2.3997000455856323,
|
||||||
|
],
|
||||||
|
"zoom": 1,
|
||||||
|
},
|
||||||
|
"explodeFactor": 0,
|
||||||
|
"filters": {
|
||||||
|
"hiddenObjectIds": [],
|
||||||
|
"isolatedObjectIds": [selected_object_id],
|
||||||
|
"propertyFilter": {"isApplied": False, "key": None},
|
||||||
|
"selectedObjectIds": [selected_object_id],
|
||||||
|
},
|
||||||
|
"lightConfig": {
|
||||||
|
"azimuth": 0.75,
|
||||||
|
"castShadow": True,
|
||||||
|
"color": 16777215,
|
||||||
|
"elevation": 1.33,
|
||||||
|
"enabled": True,
|
||||||
|
"indirectLightIntensity": 1.2,
|
||||||
|
"intensity": 5,
|
||||||
|
"radius": 0,
|
||||||
|
"shadowcatcher": True,
|
||||||
|
},
|
||||||
|
"sectionBox": None,
|
||||||
|
"selection": [
|
||||||
|
-31.355755138199026,
|
||||||
|
101.06821903317298,
|
||||||
|
4.250507316347136,
|
||||||
|
],
|
||||||
|
"spotlightUserSessionId": None,
|
||||||
|
"threads": {
|
||||||
|
"openThread": {
|
||||||
|
"isTyping": False,
|
||||||
|
"newThreadEditor": True,
|
||||||
|
"threadId": None,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"viewer": {"metadata": {"filteringState": {}}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
Generated
+341
-532
File diff suppressed because it is too large
Load Diff
+5
-15
@@ -7,27 +7,17 @@ readme = "README.md"
|
|||||||
packages = [{include = "src/speckle_automate_py"}]
|
packages = [{include = "src/speckle_automate_py"}]
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.11"
|
python = "^3.10"
|
||||||
specklepy = "^2.17.5"
|
specklepy = "^2.14.1"
|
||||||
|
typer = "^0.9.0"
|
||||||
|
pydantic = "^1.10.8"
|
||||||
|
stringcase = "^1.2.0"
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
black = "^23.3.0"
|
black = "^23.3.0"
|
||||||
mypy = "^1.3.0"
|
mypy = "^1.3.0"
|
||||||
ruff = "^0.0.271"
|
ruff = "^0.0.271"
|
||||||
pytest = "^7.4.2"
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core"]
|
requires = ["poetry-core"]
|
||||||
build-backend = "poetry.core.masonry.api"
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
[tool.ruff]
|
|
||||||
select = [
|
|
||||||
"E", # pycodestyle
|
|
||||||
"F", # pyflakes
|
|
||||||
"UP", # pyupgrade
|
|
||||||
"D", # pydocstyle
|
|
||||||
"I", # isort
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.ruff.pydocstyle]
|
|
||||||
convention = "google"
|
|
||||||
|
|||||||
Executable
+6
@@ -0,0 +1,6 @@
|
|||||||
|
import json
|
||||||
|
from main import FunctionInputs
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print(json.dumps(FunctionInputs.schema()))
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
"""Run integration tests with a speckle server."""
|
|
||||||
import os
|
|
||||||
import secrets
|
|
||||||
import string
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from gql import gql
|
|
||||||
from speckle_automate import (
|
|
||||||
AutomationRunData,
|
|
||||||
AutomationStatus,
|
|
||||||
run_function,
|
|
||||||
)
|
|
||||||
from specklepy.api import operations
|
|
||||||
from specklepy.api.client import SpeckleClient
|
|
||||||
from specklepy.objects.base import Base
|
|
||||||
from specklepy.transports.server import ServerTransport
|
|
||||||
|
|
||||||
from main import FunctionInputs, automate_function
|
|
||||||
|
|
||||||
|
|
||||||
def crypto_random_string(length: int) -> str:
|
|
||||||
"""Generate a semi crypto random string of a given length."""
|
|
||||||
alphabet = string.ascii_letters + string.digits
|
|
||||||
return "".join(secrets.choice(alphabet) for _ in range(length))
|
|
||||||
|
|
||||||
|
|
||||||
def register_new_automation(
|
|
||||||
project_id: str,
|
|
||||||
model_id: str,
|
|
||||||
speckle_client: SpeckleClient,
|
|
||||||
automation_id: str,
|
|
||||||
automation_name: str,
|
|
||||||
automation_revision_id: str,
|
|
||||||
):
|
|
||||||
"""Register a new automation in the speckle server."""
|
|
||||||
query = gql(
|
|
||||||
"""
|
|
||||||
mutation CreateAutomation(
|
|
||||||
$projectId: String!
|
|
||||||
$modelId: String!
|
|
||||||
$automationName: String!
|
|
||||||
$automationId: String!
|
|
||||||
$automationRevisionId: String!
|
|
||||||
) {
|
|
||||||
automationMutations {
|
|
||||||
create(
|
|
||||||
input: {
|
|
||||||
projectId: $projectId
|
|
||||||
modelId: $modelId
|
|
||||||
automationName: $automationName
|
|
||||||
automationId: $automationId
|
|
||||||
automationRevisionId: $automationRevisionId
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
params = {
|
|
||||||
"projectId": project_id,
|
|
||||||
"modelId": model_id,
|
|
||||||
"automationName": automation_name,
|
|
||||||
"automationId": automation_id,
|
|
||||||
"automationRevisionId": automation_revision_id,
|
|
||||||
}
|
|
||||||
speckle_client.httpclient.execute(query, params)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def speckle_token() -> str:
|
|
||||||
"""Provide a speckle token for the test suite."""
|
|
||||||
env_var = "SPECKLE_TOKEN"
|
|
||||||
token = os.getenv(env_var)
|
|
||||||
if not token:
|
|
||||||
raise ValueError(f"Cannot run tests without a {env_var} environment variable")
|
|
||||||
return token
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def speckle_server_url() -> str:
|
|
||||||
"""Provide a speckle server url for the test suite, default to localhost."""
|
|
||||||
return os.getenv("SPECKLE_SERVER_URL", "http://127.0.0.1:3000")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def test_client(speckle_server_url: str, speckle_token: str) -> SpeckleClient:
|
|
||||||
"""Initialize a SpeckleClient for testing."""
|
|
||||||
test_client = SpeckleClient(
|
|
||||||
speckle_server_url, speckle_server_url.startswith("https")
|
|
||||||
)
|
|
||||||
test_client.authenticate_with_token(speckle_token)
|
|
||||||
return test_client
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def test_object() -> Base:
|
|
||||||
"""Create a Base model for testing."""
|
|
||||||
root_object = Base()
|
|
||||||
root_object.foo = "bar"
|
|
||||||
return root_object
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture()
|
|
||||||
def automation_run_data(
|
|
||||||
test_object: Base, test_client: SpeckleClient, speckle_server_url: str
|
|
||||||
) -> AutomationRunData:
|
|
||||||
"""Set up an automation context for testing."""
|
|
||||||
project_id = test_client.stream.create("Automate function e2e test")
|
|
||||||
branch_name = "main"
|
|
||||||
|
|
||||||
model = test_client.branch.get(project_id, branch_name, commits_limit=1)
|
|
||||||
model_id: str = model.id
|
|
||||||
|
|
||||||
root_obj_id = operations.send(
|
|
||||||
test_object, [ServerTransport(project_id, test_client)]
|
|
||||||
)
|
|
||||||
version_id = test_client.commit.create(project_id, root_obj_id)
|
|
||||||
|
|
||||||
automation_name = crypto_random_string(10)
|
|
||||||
automation_id = crypto_random_string(10)
|
|
||||||
automation_revision_id = crypto_random_string(10)
|
|
||||||
|
|
||||||
register_new_automation(
|
|
||||||
project_id,
|
|
||||||
model_id,
|
|
||||||
test_client,
|
|
||||||
automation_id,
|
|
||||||
automation_name,
|
|
||||||
automation_revision_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
automation_run_id = crypto_random_string(10)
|
|
||||||
function_id = crypto_random_string(10)
|
|
||||||
function_revision = crypto_random_string(10)
|
|
||||||
return AutomationRunData(
|
|
||||||
project_id=project_id,
|
|
||||||
model_id=model_id,
|
|
||||||
branch_name=branch_name,
|
|
||||||
version_id=version_id,
|
|
||||||
speckle_server_url=speckle_server_url,
|
|
||||||
automation_id=automation_id,
|
|
||||||
automation_revision_id=automation_revision_id,
|
|
||||||
automation_run_id=automation_run_id,
|
|
||||||
function_id=function_id,
|
|
||||||
function_revision=function_revision,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_function_run(automation_run_data: AutomationRunData, speckle_token: str):
|
|
||||||
"""Run an integration test for the automate function."""
|
|
||||||
automate_sdk = run_function(
|
|
||||||
automate_function,
|
|
||||||
automation_run_data,
|
|
||||||
speckle_token,
|
|
||||||
FunctionInputs(forbidden_speckle_type="Base"),
|
|
||||||
)
|
|
||||||
|
|
||||||
assert automate_sdk.run_status == AutomationStatus.FAILED
|
|
||||||
Reference in New Issue
Block a user