Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,37 @@ jobs:
- name: Run tests
run: npm run test

python:
runs-on: ubuntu-latest

defaults:
run:
shell: bash
working-directory: py

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeScript has been kind of an exception in the other packages. I would prefer python here


env:
# code0-definition release the built-in definitions are generated from.
DEFINITIONS_VERSION: ${{ vars.HERCULES_DEFINITIONS_VERSION || 'def-0.0.34' }}

steps:
- uses: actions/checkout@v6
- name: Set up uv
uses: astral-sh/setup-uv@v6
with:
python-version: '3.12'
- name: Set up Node
# Node powers the Pydantic <-> TypeScript schema conversion (hercules/_tsgen).
uses: actions/setup-node@v6
with:
node-version: '25.9.x'
- name: Install dependencies
run: uv sync
- name: Install Node helper dependencies
run: npm ci --prefix hercules/_tsgen
- name: Generate definitions
run: uv run python scripts/build_definitions.py --version "$DEFINITIONS_VERSION"
- name: Run tests
run: uv run pytest
- name: Build package
run: uv build

45 changes: 45 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,48 @@ jobs:
run: npm run test
- name: Publish package
run: npm publish

pypi:
runs-on: ubuntu-latest

environment: packages

permissions:
# Required for PyPI trusted publishing (OIDC); no API token needed.
id-token: write

defaults:
run:
shell: bash
working-directory: py

env:
# code0-definition release the built-in definitions are generated from.
DEFINITIONS_VERSION: ${{ vars.HERCULES_DEFINITIONS_VERSION || 'def-0.0.34' }}

steps:
- uses: actions/checkout@v6
- name: Set up uv
uses: astral-sh/setup-uv@v6
with:
python-version: '3.12'
- name: Set up Node
# Node powers the Pydantic <-> TypeScript schema conversion (hercules/_tsgen).
uses: actions/setup-node@v6
with:
node-version: '25.9.x'
Comment thread
nicosammito marked this conversation as resolved.
- name: Install dependencies
run: uv sync
- name: Install Node helper dependencies
run: npm ci --prefix hercules/_tsgen
- name: Set version
# The git tag (minus any leading "v") becomes the package version.
run: uv version "${GITHUB_REF_NAME#v}"
- name: Generate definitions
run: uv run python scripts/build_definitions.py --version "$DEFINITIONS_VERSION"
- name: Run tests
run: uv run pytest
- name: Build package
run: uv build
- name: Publish package
run: uv publish --trusted-publishing always
24 changes: 24 additions & 0 deletions py/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Python
__pycache__/
*.py[cod]
*.egg-info/
.eggs/
build/
dist/
.venv/
venv/

# Tooling caches
.pytest_cache/
.mypy_cache/

# IDE (machine-specific: interpreter paths, module config)
.idea/
*.iml

# Node helper (hercules/_tsgen): keep sources + lockfile, ignore installed deps
hercules/_tsgen/node_modules/

# Generated definitions (built from a code0-definition release via
# scripts/build_definitions.py); regenerated, not committed.
hercules/definitions/
1 change: 1 addition & 0 deletions py/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12
64 changes: 64 additions & 0 deletions py/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# code0-hercules (Python)

Python SDK for the hercules action runner — the counterpart to the TypeScript
`@code0-tech/hercules` package. Define data types, functions, and events, and
connect them to aquila.

## Install

```bash
pip install code0-hercules
```

Requirements:

- **Python 3.10+**
- **Node.js** — data-type schemas are converted to/from TypeScript via a bundled
Node helper (`hercules/_tsgen`). Its npm dependencies are installed automatically
on first use.

## Develop

This project uses [uv](https://docs.astral.sh/uv/).

```bash
uv sync # create .venv + install (incl. dev deps)
npm ci --prefix hercules/_tsgen # install the Node schema helper
uv run pytest # run the tests
```

The runnable example lives in [`examples/simple-example-py`](examples/simple-example-py).

## Built-in definitions

The built-in data types / functions in `hercules/definitions/` are **generated**
(not committed) from a [`code0-definition`](https://github.com/code0-tech/code0-definition)
release:

```bash
uv run python scripts/build_definitions.py --version def-0.0.34
```

## Build & release

Packages are built with `uv build` and published to PyPI from CI:

- **`build.yml`** builds and tests the package on every push.
- **`publish.yml`** runs on a pushed git tag: it sets the package version from the
tag, regenerates the definitions, builds, and publishes to PyPI via
[trusted publishing](https://docs.pypi.org/trusted-publishers/) (OIDC — no token).

To cut a release, push a PEP 440 version tag (e.g. `0.1.0`):

```bash
git tag 0.1.0 && git push origin 0.1.0
```

The `code0-definition` release used for the built-in definitions is controlled by
the `HERCULES_DEFINITIONS_VERSION` repository variable (default `def-0.0.34`).

Build locally:

```bash
uv build # -> dist/*.whl and dist/*.tar.gz
```
5 changes: 5 additions & 0 deletions py/examples/simple-example-py/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.venv/
__pycache__/
*.py[cod]
.env
module.json
1 change: 1 addition & 0 deletions py/examples/simple-example-py/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12
38 changes: 38 additions & 0 deletions py/examples/simple-example-py/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# simple-example-py

A minimal example action built with the Python `hercules` SDK.

This is a self-contained [uv](https://docs.astral.sh/uv/) project that depends on
the SDK from the local checkout (`../..`), mirroring the TypeScript example's
`"@code0-tech/hercules": "file:../.."` setup.

## Setup

```bash
uv sync # creates .venv and installs hercules (editable) + deps
```

## Run

```bash
uv run python index.py
```

Environment variables (see `example.env`): `ACTION_ID`, `VERSION`, `AQUILA_URL`,
`AUTH_TOKEN`.

## Export the action as a Module JSON

Without connecting to aquila:

```bash
uv run hercules export index.py # print to stdout
uv run hercules export index.py -o module.json
uv run hercules export index.py --compact
```

## Notes

- Requires **Python 3.10+** (pinned to 3.12 via `.python-version`).
- The SDK converts data-type schemas to TypeScript via a bundled Node helper, so
**Node.js** must be installed for data types to build.
Empty file.
13 changes: 13 additions & 0 deletions py/examples/simple-example-py/data_types/email_data_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from hercules import Identifier, Name, Schema
from hercules.schema import RootModel, Annotated, StringConstraints


class EmailModel(RootModel[Annotated[str, StringConstraints(pattern=r"^[^@]+@[^@]+\.[^@]+$")]]):
"""An email address, described as a constrained string."""


@Identifier("email_address")
@Name({"code": "en-US", "content": "Email Address"})
@Schema(EmailModel)
class EmailDataType:
pass
Empty file.
15 changes: 15 additions & 0 deletions py/examples/simple-example-py/events/user_created_runtime_event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from hercules import Description, DisplayMessage, Identifier, Name, Signature


@Identifier("user_created")
@Signature("(): {userId: number}")
@Name({"code": "en-US", "content": "User created event"})
@DisplayMessage({"code": "en-US", "content": "Triggers on user creation"})
@Description(
{
"code": "en-US",
"content": "Triggers on user creation and has a payload including the user database id",
}
)
class UserCreatedRuntimeEvent:
pass
4 changes: 4 additions & 0 deletions py/examples/simple-example-py/example.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
ACTION_ID=testing-action
VERSION=0.0.0
AQUILA_URL=127.0.0.1:8081
AUTH_TOKEN=your_auth_token_here
Empty file.
19 changes: 19 additions & 0 deletions py/examples/simple-example-py/functions/fibonacci_function.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from hercules import Identifier, Name, Parameter

from functions.fibonacci_runtime_function import FibonacciRuntimeFunction


@Identifier("fibonacci")
@Name({"code": "en-US", "content": "Compute Fibonacci Number"})
@Parameter(
{
"runtime_name": "test",
"name": [{"code": "en-US", "content": "Input Number"}],
"description": [
{"code": "en-US", "content": "The position in the Fibonacci sequence"}
],
"default_value": 10,
}
)
class FibonacciFunction(FibonacciRuntimeFunction):
pass
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from hercules import (
DisplayMessage,
FunctionContext,
Identifier,
Name,
OmitRuntimeFunction,
Parameter,
RuntimeFunctionRunnable,
Signature,
)


@Identifier("fibonacci_runtime")
@Signature("(test: number): number")
@Name({"code": "en-US", "content": "Fibonacci (Runtime)"})
@DisplayMessage({"code": "en-US", "content": "Computes the n-th Fibonacci number"})
@OmitRuntimeFunction()
@Parameter({"runtime_name": "test", "name": [{"code": "en-US", "content": "N"}]})
class FibonacciRuntimeFunction(RuntimeFunctionRunnable):
def run(self, context: FunctionContext, test):
print(f"[fibonacci] project={context.project_id} execution={context.execution_id}")
return self._fib(test)

def _fib(self, n):
if n <= 1:
return n
return self._fib(n - 1) + self._fib(n - 2)
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from hercules import (
DisplayMessage,
FunctionContext,
Identifier,
Name,
Parameter,
RuntimeFunctionRunnable,
Signature,
)


@Identifier("for_each_runtime")
@Signature("<T>(list: LIST<T>, consumer: CONSUMER<T>): void")
@Name({"code": "en-US", "content": "For Each"})
@DisplayMessage({"code": "en-US", "content": "For each element of ${list} do ${consumer}"})
@Parameter(
{
"runtime_name": "list",
"name": [{"code": "en-US", "content": "List"}],
"description": [
{"code": "en-US", "content": "The list whose elements are iterated over"}
],
}
)
@Parameter(
{
"runtime_name": "consumer",
"name": [{"code": "en-US", "content": "Consumer"}],
"description": [
{
"code": "en-US",
"content": "The sub flow (item) => void executed once per element",
}
],
}
)
class ForEachRuntimeFunction(RuntimeFunctionRunnable):
async def run(self, context: FunctionContext, items, consumer):
for element in items:
result = await consumer(element)
print("[for_each] sub flow result:", result)
Loading
Loading