Python Script Service (f8.pyscript)¶
Standalone python script runtime service with lifecycle/tick/command hooks.
- Service class:
f8.pyscript - Version:
0.0.1 - Source directory:
f8/pyscript - Tags:
python,script,service
When to Use¶
- Use
f8.pyscriptfor Python logic that has outgrown a single expression but is not yet formalized as dedicated operators. - It is useful for multi-line scripts, custom experimentation, and intermediate prototype logic.
- It works well as a temporary home for evolving graph behavior.
Common Wiring Patterns¶
- Use it as a bounded custom logic layer while the design is still changing.
- Once the behavior stabilizes, consider moving the logic into
PyEngineoperators or a dedicated service. - Keep inputs and outputs explicit so the script block does not become an unreadable center of gravity.
Pitfalls / Gotchas¶
- If the script keeps getting longer and harder to test, it is time to refactor.
- Clear input/output schemas help far more than "we will clean it up later".
- If the behavior should be reused across graphs, promote it into a more explicit module or operator.
Service Reference¶
How to Run¶
pixi run -e default f8pyscript
- Workdir:
../../../ - Environment overrides: none
Typical Inputs / Outputs¶
- Data inputs:
in - Data outputs:
out,monitor - Commands:
grant_local_exec,revoke_local_exec
Service State Fields¶
| Name | Access | Required | On Node | Schema | Description |
|---|---|---|---|---|---|
code |
rw |
true |
false |
string / default=# Hooks template (uncomment what you need):<br># - onStart(ctx)<br># - onStop(ctx)<br># - onPause(ctx, meta=None)<br># - onResume(ctx, meta=None)<br># - onState(ctx, field, value, ts_ms=None)<br># - onData(ctx, port, value, ts_ms=None)<br># - onTick(ctx, tick)<br># - onCommand(ctx, name, args, meta=None)<br>#<br># Useful context helpers:<br># - ctx.states.<field> reads cached rw/ro/wo state snapshot<br># - example: ctx.states.tickEnabled / ctx.states.lastError<br># - await ctx.read_state(field) # fresh runtime read<br># - ctx.states.get(field) # cached snapshot<br># - ctx.set_state(field, value)<br># - await ctx.set_state_async(field, value)<br># - ctx.emit(port, value)<br># - ctx.permission.local_exec_granted / ctx.permission.expires_ts_ms<br># - TypeGuard helpers are available from f8_dynamic_inputs<br># - example: from f8_dynamic_inputs import is_port_in<br># - optional: from f8_dynamic_inputs import *<br># - then: if is_port_in(value, port): ...<br># - State TypeGuard helpers are available from f8_dynamic_states<br># - example: from f8_dynamic_states import is_state_tickEnabled<br># - then: if is_state_tickEnabled(value, field): ...<br>#<br>from typing import TYPE_CHECKING, Any<br>if TYPE_CHECKING:<br> from f8_script_api import F8PyScriptContext, F8States, F8Tick<br>#<br>def onStart(ctx: 'F8PyScriptContext') -> None:<br> ctx.log('pyscript started')<br><br># def onStop(ctx: 'F8PyScriptContext') -> None:<br># ctx.log('pyscript stopped')<br>#<br># def onPause(ctx: 'F8PyScriptContext', meta: dict[str, Any] \| None = None) -> None:<br># ctx.log(f'paused: {meta}')<br>#<br># def onResume(ctx: 'F8PyScriptContext', meta: dict[str, Any] \| None = None) -> None:<br># ctx.log(f'resumed: {meta}')<br>#<br># def onState(<br># ctx: 'F8PyScriptContext',<br># field: str,<br># value: Any,<br># ts_ms: int \| None = None,<br># ) -> None:<br># ctx.log(f'state {field}={value} ts_ms={ts_ms}')<br>#<br># def onData(<br># ctx: 'F8PyScriptContext',<br># port: str,<br># value: Any,<br># ts_ms: int \| None = None,<br># ) -> None:<br># ctx.log(f'data port={port} value={value} ts_ms={ts_ms}')<br>#<br># def onTick(ctx: 'F8PyScriptContext', tick: 'F8Tick') -> None:<br># ctx.log(f'tick seq={tick.seq} tsMs={tick.tsMs} deltaMs={tick.deltaMs}')<br>#<br># def onCommand(<br># ctx: 'F8PyScriptContext',<br># name: str,<br># args: dict[str, Any],<br># meta: dict[str, Any] \| None = None,<br># ) -> dict[str, Any]:<br># if name == 'ping':<br># return {'ok': True, 'result': {'pong': True}}<br># return {'ok': False, 'error': f'unknown command: {name}'}<br> |
Python source code. |
lastError |
ro |
true |
false |
string / default= |
Last script compile/runtime error. |
tickEnabled |
rw |
true |
false |
boolean / default=False |
Enable onTick scheduler. |
tickMs |
rw |
true |
false |
integer / default=100 |
onTick interval in milliseconds. |
active |
rw |
true |
false |
boolean / default=True |
Service lifecycle state (activate/deactivate). |
svcId |
ro |
true |
false |
string |
Readonly: current service instance id (svcId). |
Key Fields That Matter¶
code(Code,rw): Python source code. Schema: `string / default=# Hooks template (uncomment what you need):
- onStart(ctx)¶
- onStop(ctx)¶
- onPause(ctx, meta=None)¶
- onResume(ctx, meta=None)¶
- onState(ctx, field, value, ts_ms=None)¶
- onData(ctx, port, value, ts_ms=None)¶
- onTick(ctx, tick)¶
- onCommand(ctx, name, args, meta=None)¶
¶
Useful context helpers:¶
- ctx.states. reads cached rw/ro/wo state snapshot¶
- example: ctx.states.tickEnabled / ctx.states.lastError¶
- await ctx.read_state(field) # fresh runtime read¶
- ctx.states.get(field) # cached snapshot¶
- ctx.set_state(field, value)¶
- await ctx.set_state_async(field, value)¶
- ctx.emit(port, value)¶
- ctx.permission.local_exec_granted / ctx.permission.expires_ts_ms¶
- TypeGuard helpers are available from f8_dynamic_inputs¶
- example: from f8_dynamic_inputs import is_port_in¶
- optional: from f8_dynamic_inputs import *¶
- then: if is_port_in(value, port): ...¶
- State TypeGuard helpers are available from f8_dynamic_states¶
- example: from f8_dynamic_states import is_state_tickEnabled¶
- then: if is_state_tickEnabled(value, field): ...¶
¶
from typing import TYPE_CHECKING, Any if TYPE_CHECKING: from f8_script_api import F8PyScriptContext, F8States, F8Tick
¶
def onStart(ctx: 'F8PyScriptContext') -> None: ctx.log('pyscript started')
def onStop(ctx: 'F8PyScriptContext') -> None:¶
ctx.log('pyscript stopped')¶
¶
def onPause(ctx: 'F8PyScriptContext', meta: dict[str, Any] | None = None) -> None:¶
ctx.log(f'paused: {meta}')¶
¶
def onResume(ctx: 'F8PyScriptContext', meta: dict[str, Any] | None = None) -> None:¶
ctx.log(f'resumed: {meta}')¶
¶
def onState(¶
ctx: 'F8PyScriptContext',¶
field: str,¶
value: Any,¶
ts_ms: int | None = None,¶
) -> None:¶
ctx.log(f'state {field}={value} ts_ms={ts_ms}')¶
¶
def onData(¶
ctx: 'F8PyScriptContext',¶
port: str,¶
value: Any,¶
ts_ms: int | None = None,¶
) -> None:¶
ctx.log(f'data port={port} value={value} ts_ms={ts_ms}')¶
¶
def onTick(ctx: 'F8PyScriptContext', tick: 'F8Tick') -> None:¶
ctx.log(f'tick seq={tick.seq} tsMs={tick.tsMs} deltaMs={tick.deltaMs}')¶
¶
def onCommand(¶
ctx: 'F8PyScriptContext',¶
name: str,¶
args: dict[str, Any],¶
meta: dict[str, Any] | None = None,¶
) -> dict[str, Any]:¶
if name == 'ping':¶
return {'ok': True, 'result': {'pong': True}}¶
return {'ok': False, 'error': f'unknown command: {name}'}¶
.
-lastError(Last Error,ro): Last script compile/runtime error. Schema:string / default=.
-tickEnabled(Tick Enabled,rw): Enable onTick scheduler. Schema:boolean / default=False.
-tickMs(Tick Interval (ms),rw): onTick interval in milliseconds. Schema:integer / default=100.
-active(Active,rw): Service lifecycle state (activate/deactivate). Schema:boolean / default=True.
-svcId(Service Id,ro): Readonly: current service instance id (svcId). Schema:string`.
Service Commands¶
grant_local_exec¶
Grant local execution for this script session.
- Show on node:
true
| Param | Required | Schema | Description |
|---|---|---|---|
ttlMs |
false |
integer / default=60000 |
Optional grant TTL in milliseconds. |
revoke_local_exec¶
Revoke local execution grant.
- Show on node:
true - Params: none
Service Data Input Ports¶
| Name | Required | On Node | Schema | Description |
|---|---|---|---|---|
in |
false |
true |
any |
Default data input |
Service Data Output Ports¶
| Name | Required | On Node | Schema | Description |
|---|---|---|---|---|
out |
false |
true |
any |
Default data output |
monitor |
true |
false |
object{active, alive, cpu, error, ...} |
Unified runtime monitor snapshots (health/resource/perf/error). |
Operators¶
None
Related Scenarios¶
- No bundled scenario references this node yet.