add function implementation
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
name: 'build and deploy Speckle functions'
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
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
|
||||
steps:
|
||||
- uses: actions/checkout@v3.4.0
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Install and configure Poetry
|
||||
uses: snok/install-poetry@v1
|
||||
with:
|
||||
version: 1.3.2
|
||||
virtualenvs-create: false
|
||||
virtualenvs-in-project: false
|
||||
installer-parallel: true
|
||||
- name: Restore dependencies
|
||||
run: poetry install --no-root
|
||||
- name: Extract functionInputSchema
|
||||
id: extract_schema
|
||||
run: |
|
||||
python schema_generation.py ${HOME}/${{ env.FUNCTION_SCHEMA_FILE_NAME }}
|
||||
- name: Speckle Automate Function - Build and Publish
|
||||
uses: specklesystems/speckle-automate-github-composite-action@0.4.2
|
||||
with:
|
||||
speckle_automate_url: 'https://automate.speckle.dev'
|
||||
speckle_token: ${{ secrets.SPECKLE_FUNCTION_TOKEN }}
|
||||
speckle_function_id: ${{ secrets.SPECKLE_FUNCTION_ID }}
|
||||
speckle_function_input_schema_file_path: ${{ env.FUNCTION_SCHEMA_FILE_NAME }}
|
||||
speckle_function_command: 'python main.py'
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Speckle Automate notebook example\n",
|
||||
"\n",
|
||||
"This is an example jupyter notebook, that will be executed by speckle automate\n",
|
||||
"\n",
|
||||
"## Inputs\n",
|
||||
"\n",
|
||||
"The first two block block are defining two input data structures, `SpeckleProjectData` and `FunctionInputs`.\n",
|
||||
"The project data class is following the data schema of Automate, you shouldn't modify it.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from pydantic import BaseModel, ConfigDict\n",
|
||||
"from stringcase import camelcase\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class SpeckleProjectData(BaseModel):\n",
|
||||
" \"\"\"\n",
|
||||
" Only modify this to follow upstream changes in automate!\n",
|
||||
"\n",
|
||||
" Values of the project / model that triggered the run of this function.\n",
|
||||
" \"\"\"\n",
|
||||
"\n",
|
||||
" project_id: str\n",
|
||||
" model_id: str\n",
|
||||
" version_id: str\n",
|
||||
" speckle_server_url: str\n",
|
||||
"\n",
|
||||
" model_config = ConfigDict(alias_generator=camelcase, protected_namespaces=())"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Parameters\n",
|
||||
"\n",
|
||||
"These values are supplied to the notebook, when its being run by automate.\n",
|
||||
"\n",
|
||||
"WANING: You really shouldn't modify this block, unless its following upstream changes from automate.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 8,
|
||||
"metadata": {
|
||||
"tags": [
|
||||
"parameters"
|
||||
]
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"speckle_project_data = \"\"\n",
|
||||
"function_inputs = \"\"\n",
|
||||
"token_env_var = \"\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Function inputs\n",
|
||||
"\n",
|
||||
"The `FunctionInputs` class defines the schema for the values, this function requires to run.\n",
|
||||
"These values will be provided by the users of the function.\n",
|
||||
"\n",
|
||||
"Automate uses the Json Schema, generated from the class, to validate user provided values.\n",
|
||||
"\n",
|
||||
"The schema block is tagget with the `function_input` tag, do not remove that!\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"metadata": {
|
||||
"tags": [
|
||||
"function_inputs"
|
||||
]
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{\"description\": \"User defined inputs to the function.\", \"properties\": {\"speckleType\": {\"title\": \"Speckletype\", \"type\": \"string\"}}, \"required\": [\"speckleType\"], \"title\": \"FunctionInputs\", \"type\": \"object\"}\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from pydantic import BaseModel, ConfigDict\n",
|
||||
"from stringcase import camelcase\n",
|
||||
"import json\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class FunctionInputs(BaseModel):\n",
|
||||
" \"\"\"User defined inputs to the function.\"\"\"\n",
|
||||
"\n",
|
||||
" speckle_type: str\n",
|
||||
"\n",
|
||||
" model_config = ConfigDict(alias_generator=camelcase)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"print(json.dumps(FunctionInputs.model_json_schema()))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Function logic\n",
|
||||
"\n",
|
||||
"in this block we're defining the actual business logic of the function.\n",
|
||||
"\n",
|
||||
"By all means modify this block but do keep the function signature compatible with the executing main function.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 10,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from specklepy.api.client import SpeckleClient\n",
|
||||
"from specklepy.objects import Base\n",
|
||||
"from specklepy.transports.memory import MemoryTransport\n",
|
||||
"from specklepy.transports.server import ServerTransport\n",
|
||||
"from specklepy.api.operations import receive\n",
|
||||
"from typing import Iterable\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def flatten_base(base: Base) -> Iterable[Base]:\n",
|
||||
" if hasattr(base, \"elements\"):\n",
|
||||
" for element in base.elements:\n",
|
||||
" yield from flatten_base(element)\n",
|
||||
" yield base\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def automate_function(\n",
|
||||
" project_data: SpeckleProjectData,\n",
|
||||
" function_inputs: FunctionInputs,\n",
|
||||
" speckle_token: str,\n",
|
||||
"):\n",
|
||||
" client = SpeckleClient(project_data.speckle_server_url)\n",
|
||||
" client.authenticate_with_token(speckle_token)\n",
|
||||
" commit = client.commit.get(project_data.project_id, project_data.version_id)\n",
|
||||
" branch = client.branch.get(project_data.project_id, project_data.model_id, 1)\n",
|
||||
"\n",
|
||||
" memory_transport = MemoryTransport()\n",
|
||||
" server_transport = ServerTransport(project_data.project_id, client)\n",
|
||||
" base = receive(commit.referencedObject, server_transport, memory_transport)\n",
|
||||
"\n",
|
||||
" matching_types = [\n",
|
||||
" b for b in flatten_base(base) if b.speckle_type == function_inputs.speckle_type\n",
|
||||
" ]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def main():\n",
|
||||
" inputs = FunctionInputs.model_validate_json(function_inputs)\n",
|
||||
" project_data = SpeckleProjectData.model_validate_json(speckle_project_data)\n",
|
||||
" speckle_token = os.environ[token_env_var]\n",
|
||||
" print(project_data)\n",
|
||||
" print(inputs)"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": ".venv",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.4"
|
||||
},
|
||||
"orig_nbformat": 4
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 2
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
import papermill as pm
|
||||
|
||||
if __name__ == "__main__":
|
||||
_, speckle_project_data, function_inputs, speckle_token = sys.argv
|
||||
|
||||
# we need to pass in the token via an env var, the params cell is executed
|
||||
# automatically exposing the token in the output file
|
||||
token_env_var = "SPECKLE_TOKEN"
|
||||
|
||||
os.environ[token_env_var] = speckle_token
|
||||
|
||||
output_file = "function.output.ipynb"
|
||||
pm.execute_notebook(
|
||||
"function.ipynb",
|
||||
output_file,
|
||||
parameters={
|
||||
"speckle_project_data": speckle_project_data,
|
||||
"function_inputs": function_inputs,
|
||||
"token_env_var": token_env_var,
|
||||
},
|
||||
)
|
||||
@@ -0,0 +1,42 @@
|
||||
"""
|
||||
This module is used to create the JSON schema from the FunctionInputs class defined in the notebook.
|
||||
|
||||
WARNING: you probably should not be modifying this unless you know what you are doing.
|
||||
"""
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import papermill as pm
|
||||
|
||||
|
||||
def save_schema_to_file(file_path: str):
|
||||
"""
|
||||
Executes the notebook without valid input values.
|
||||
|
||||
Its fine, since we're only interested in the result of the `function_inputs` cell.
|
||||
"""
|
||||
notebook_path = "function.ipynb"
|
||||
run_result = pm.execute_notebook(notebook_path, "-")
|
||||
|
||||
# find the cell by its tag
|
||||
function_inputs_cells = [
|
||||
c for c in run_result.cells if "function_inputs" in c.metadata.tags
|
||||
]
|
||||
|
||||
# should have only found 1
|
||||
if len(function_inputs_cells) != 1:
|
||||
msg = f"Expected 1 cell to have the tag `function_inputs`, found {len(function_inputs_cells)}"
|
||||
raise ValueError(msg)
|
||||
|
||||
# parse and dump the schema to make sure its a valid json object
|
||||
schema = json.dumps(json.loads(function_inputs_cells[0].outputs[0].text))
|
||||
|
||||
Path(file_path).write_text(schema)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
_, file_path = sys.argv
|
||||
|
||||
save_schema_to_file(file_path)
|
||||
Reference in New Issue
Block a user