code0-hercules 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- code0_hercules-1.3.0.dist-info/METADATA +79 -0
- code0_hercules-1.3.0.dist-info/RECORD +258 -0
- code0_hercules-1.3.0.dist-info/WHEEL +5 -0
- code0_hercules-1.3.0.dist-info/entry_points.txt +2 -0
- code0_hercules-1.3.0.dist-info/top_level.txt +1 -0
- hercules/__init__.py +81 -0
- hercules/_metadata.py +38 -0
- hercules/_tsgen/convert.mjs +96 -0
- hercules/_tsgen/package-lock.json +366 -0
- hercules/_tsgen/package.json +11 -0
- hercules/_tucana/__init__.py +29 -0
- hercules/_tucana/helpers.py +73 -0
- hercules/action.py +345 -0
- hercules/actions/__init__.py +21 -0
- hercules/actions/execution.py +121 -0
- hercules/actions/flow_execution.py +11 -0
- hercules/actions/flow_update.py +18 -0
- hercules/actions/module_configurations.py +14 -0
- hercules/actions/sub_flow_execution.py +14 -0
- hercules/cli.py +137 -0
- hercules/decorators/__init__.py +0 -0
- hercules/decorators/datatype.py +39 -0
- hercules/decorators/event.py +35 -0
- hercules/decorators/function.py +59 -0
- hercules/decorators/meta.py +82 -0
- hercules/decorators/runtime_event.py +15 -0
- hercules/decorators/runtime_function.py +18 -0
- hercules/definitions/__init__.py +162 -0
- hercules/definitions/cron_action/__init__.py +0 -0
- hercules/definitions/cron_action/data_types/__init__.py +0 -0
- hercules/definitions/cron_action/data_types/cron_day_of_month.py +11 -0
- hercules/definitions/cron_action/data_types/cron_day_of_week.py +11 -0
- hercules/definitions/cron_action/data_types/cron_hour.py +11 -0
- hercules/definitions/cron_action/data_types/cron_minute.py +11 -0
- hercules/definitions/cron_action/data_types/cron_month.py +11 -0
- hercules/definitions/cron_action/runtime_flow_types/__init__.py +0 -0
- hercules/definitions/cron_action/runtime_flow_types/cron.py +19 -0
- hercules/definitions/rest_action/__init__.py +0 -0
- hercules/definitions/rest_action/data_types/__init__.py +0 -0
- hercules/definitions/rest_action/data_types/rest_adapter_input.py +19 -0
- hercules/definitions/rest_action/data_types/rest_auth_type.py +19 -0
- hercules/definitions/rest_action/data_types/rest_auth_value.py +11 -0
- hercules/definitions/rest_action/runtime_flow_types/__init__.py +0 -0
- hercules/definitions/rest_action/runtime_flow_types/rest.py +20 -0
- hercules/definitions/rest_action/runtime_functions/__init__.py +0 -0
- hercules/definitions/rest_action/runtime_functions/rest_control_respond.py +21 -0
- hercules/definitions/taurus_boolean/__init__.py +0 -0
- hercules/definitions/taurus_boolean/data_types/__init__.py +0 -0
- hercules/definitions/taurus_boolean/data_types/boolean.py +11 -0
- hercules/definitions/taurus_boolean/runtime_functions/__init__.py +0 -0
- hercules/definitions/taurus_boolean/runtime_functions/std_boolean_as_number.py +18 -0
- hercules/definitions/taurus_boolean/runtime_functions/std_boolean_as_text.py +18 -0
- hercules/definitions/taurus_boolean/runtime_functions/std_boolean_from_number.py +18 -0
- hercules/definitions/taurus_boolean/runtime_functions/std_boolean_from_text.py +18 -0
- hercules/definitions/taurus_boolean/runtime_functions/std_boolean_is_equal.py +19 -0
- hercules/definitions/taurus_boolean/runtime_functions/std_boolean_negate.py +18 -0
- hercules/definitions/taurus_color/__init__.py +0 -0
- hercules/definitions/taurus_color/data_types/__init__.py +0 -0
- hercules/definitions/taurus_color/data_types/color.py +17 -0
- hercules/definitions/taurus_color/runtime_functions/__init__.py +0 -0
- hercules/definitions/taurus_color/runtime_functions/std_color_as_hex.py +18 -0
- hercules/definitions/taurus_color/runtime_functions/std_color_as_hsl.py +18 -0
- hercules/definitions/taurus_color/runtime_functions/std_color_as_rgb.py +18 -0
- hercules/definitions/taurus_color/runtime_functions/std_color_from_hex.py +18 -0
- hercules/definitions/taurus_color/runtime_functions/std_color_from_hsl.py +21 -0
- hercules/definitions/taurus_color/runtime_functions/std_color_from_rgb.py +21 -0
- hercules/definitions/taurus_control/__init__.py +0 -0
- hercules/definitions/taurus_control/data_types/__init__.py +0 -0
- hercules/definitions/taurus_control/data_types/comparator.py +21 -0
- hercules/definitions/taurus_control/data_types/consumer.py +20 -0
- hercules/definitions/taurus_control/data_types/predicate.py +20 -0
- hercules/definitions/taurus_control/data_types/runnable.py +13 -0
- hercules/definitions/taurus_control/data_types/transform.py +20 -0
- hercules/definitions/taurus_control/runtime_functions/__init__.py +0 -0
- hercules/definitions/taurus_control/runtime_functions/std_control_if.py +19 -0
- hercules/definitions/taurus_control/runtime_functions/std_control_if_else.py +21 -0
- hercules/definitions/taurus_control/runtime_functions/std_control_return.py +18 -0
- hercules/definitions/taurus_control/runtime_functions/std_control_stop.py +16 -0
- hercules/definitions/taurus_control/runtime_functions/std_control_value.py +18 -0
- hercules/definitions/taurus_date/__init__.py +0 -0
- hercules/definitions/taurus_date/data_types/__init__.py +0 -0
- hercules/definitions/taurus_date/data_types/date.py +11 -0
- hercules/definitions/taurus_date/data_types/hour.py +11 -0
- hercules/definitions/taurus_date/data_types/minute.py +11 -0
- hercules/definitions/taurus_date/data_types/month.py +22 -0
- hercules/definitions/taurus_date/runtime_functions/__init__.py +0 -0
- hercules/definitions/taurus_date/runtime_functions/std_date_format.py +20 -0
- hercules/definitions/taurus_date/runtime_functions/std_date_from.py +24 -0
- hercules/definitions/taurus_date/runtime_functions/std_date_from_text.py +19 -0
- hercules/definitions/taurus_date/runtime_functions/std_date_from_unix.py +18 -0
- hercules/definitions/taurus_date/runtime_functions/std_date_now.py +16 -0
- hercules/definitions/taurus_file/__init__.py +0 -0
- hercules/definitions/taurus_file/data_types/__init__.py +0 -0
- hercules/definitions/taurus_file/data_types/file.py +19 -0
- hercules/definitions/taurus_file/runtime_functions/__init__.py +0 -0
- hercules/definitions/taurus_file/runtime_functions/std_file_size.py +18 -0
- hercules/definitions/taurus_http/__init__.py +0 -0
- hercules/definitions/taurus_http/data_types/__init__.py +0 -0
- hercules/definitions/taurus_http/data_types/http_auth_place.py +12 -0
- hercules/definitions/taurus_http/data_types/http_auth_type.py +17 -0
- hercules/definitions/taurus_http/data_types/http_auth_value.py +11 -0
- hercules/definitions/taurus_http/data_types/http_method.py +16 -0
- hercules/definitions/taurus_http/data_types/http_payload.py +11 -0
- hercules/definitions/taurus_http/data_types/http_request.py +19 -0
- hercules/definitions/taurus_http/data_types/http_response.py +18 -0
- hercules/definitions/taurus_http/data_types/http_schema.py +31 -0
- hercules/definitions/taurus_http/data_types/http_status_code.py +13 -0
- hercules/definitions/taurus_http/data_types/http_url.py +13 -0
- hercules/definitions/taurus_http/runtime_functions/__init__.py +0 -0
- hercules/definitions/taurus_http/runtime_functions/http_request_send.py +26 -0
- hercules/definitions/taurus_list/__init__.py +0 -0
- hercules/definitions/taurus_list/data_types/__init__.py +0 -0
- hercules/definitions/taurus_list/data_types/list.py +13 -0
- hercules/definitions/taurus_list/runtime_functions/__init__.py +0 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_at.py +20 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_concat.py +20 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_filter.py +19 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_find.py +20 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_find_index.py +19 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_find_last.py +20 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_first.py +18 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_flat.py +18 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_for_each.py +19 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_index_of.py +19 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_is_empty.py +18 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_join.py +19 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_last.py +18 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_map.py +19 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_max.py +18 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_min.py +18 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_pop.py +18 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_push.py +19 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_remove.py +19 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_reverse.py +18 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_size.py +18 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_sort.py +19 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_sort_reverse.py +19 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_sum.py +18 -0
- hercules/definitions/taurus_list/runtime_functions/std_list_to_unique.py +18 -0
- hercules/definitions/taurus_number/__init__.py +0 -0
- hercules/definitions/taurus_number/data_types/__init__.py +0 -0
- hercules/definitions/taurus_number/data_types/number.py +11 -0
- hercules/definitions/taurus_number/runtime_functions/__init__.py +0 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_abs.py +18 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_add.py +19 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_arccos.py +18 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_arcsin.py +18 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_arctan.py +18 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_as_text.py +18 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_clamp.py +20 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_cos.py +18 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_cosh.py +18 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_divide.py +20 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_euler.py +16 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_exponential.py +19 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_from_text.py +18 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_has_digits.py +18 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_is_equal.py +19 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_is_greater.py +19 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_is_less.py +19 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_is_positive.py +18 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_is_zero.py +18 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_ln.py +18 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_log.py +19 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_max.py +19 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_min.py +19 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_modulo.py +19 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_multiply.py +19 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_negate.py +18 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_pi.py +16 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_random_number.py +19 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_remove_digits.py +18 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_root.py +19 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_round.py +19 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_round_down.py +19 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_round_up.py +19 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_sin.py +18 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_sinh.py +18 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_square.py +18 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_square_root.py +18 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_subtract.py +19 -0
- hercules/definitions/taurus_number/runtime_functions/std_number_tan.py +18 -0
- hercules/definitions/taurus_object/__init__.py +0 -0
- hercules/definitions/taurus_object/data_types/__init__.py +0 -0
- hercules/definitions/taurus_object/data_types/object.py +13 -0
- hercules/definitions/taurus_object/data_types/type.py +13 -0
- hercules/definitions/taurus_object/runtime_functions/__init__.py +0 -0
- hercules/definitions/taurus_object/runtime_functions/std_object_contains_key.py +19 -0
- hercules/definitions/taurus_object/runtime_functions/std_object_get.py +19 -0
- hercules/definitions/taurus_object/runtime_functions/std_object_keys.py +18 -0
- hercules/definitions/taurus_object/runtime_functions/std_object_set.py +20 -0
- hercules/definitions/taurus_object/runtime_functions/std_object_size.py +18 -0
- hercules/definitions/taurus_text/__init__.py +0 -0
- hercules/definitions/taurus_text/data_types/__init__.py +0 -0
- hercules/definitions/taurus_text/data_types/text.py +11 -0
- hercules/definitions/taurus_text/data_types/text_encoding.py +13 -0
- hercules/definitions/taurus_text/runtime_functions/__init__.py +0 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_append.py +19 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_as_bytes.py +18 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_at.py +19 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_byte_size.py +18 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_capitalize.py +18 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_chars.py +18 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_contains.py +19 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_decode.py +19 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_encode.py +19 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_ends_with.py +19 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_from_ascii.py +18 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_hex.py +18 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_index_of.py +19 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_insert.py +20 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_is_equal.py +19 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_length.py +18 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_lowercase.py +18 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_octal.py +18 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_prepend.py +19 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_remove.py +20 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_replace.py +20 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_replace_first.py +21 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_replace_last.py +20 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_reverse.py +18 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_split.py +19 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_starts_with.py +19 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_swapcase.py +18 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_to_ascii.py +18 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_trim.py +18 -0
- hercules/definitions/taurus_text/runtime_functions/std_text_uppercase.py +18 -0
- hercules/events.py +23 -0
- hercules/internal/__init__.py +0 -0
- hercules/internal/connection.py +37 -0
- hercules/internal/literal.py +113 -0
- hercules/internal/module_builder.py +261 -0
- hercules/internal/pydantic_codegen.py +82 -0
- hercules/internal/schema.py +158 -0
- hercules/internal/tsgen.py +93 -0
- hercules/manager/__init__.py +0 -0
- hercules/manager/base.py +73 -0
- hercules/manager/config.py +45 -0
- hercules/manager/datatype.py +8 -0
- hercules/manager/event.py +8 -0
- hercules/manager/flow.py +12 -0
- hercules/manager/function.py +8 -0
- hercules/manager/runtime_event.py +8 -0
- hercules/manager/runtime_function.py +8 -0
- hercules/map/__init__.py +0 -0
- hercules/map/datatype.py +41 -0
- hercules/map/event.py +74 -0
- hercules/map/function.py +76 -0
- hercules/map/runtime_event.py +43 -0
- hercules/map/runtime_function.py +64 -0
- hercules/models/__init__.py +0 -0
- hercules/models/datatype.py +49 -0
- hercules/models/event.py +35 -0
- hercules/models/function.py +37 -0
- hercules/models/runtime_event.py +29 -0
- hercules/models/runtime_function.py +33 -0
- hercules/schema.py +38 -0
- hercules/types.py +88 -0
hercules/action.py
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
"""The :class:`Action` — the SDK entry point (port of ``src/action.ts``)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import inspect
|
|
6
|
+
import os
|
|
7
|
+
import uuid
|
|
8
|
+
from dataclasses import replace
|
|
9
|
+
from typing import Callable, Dict, List, Optional
|
|
10
|
+
|
|
11
|
+
from hercules._metadata import get_metadata
|
|
12
|
+
from tucana.generated.aquila import action_pb2
|
|
13
|
+
from hercules._tucana.helpers import PlainValue, construct_value, to_allowed_value
|
|
14
|
+
from hercules.actions import actions as _packet_handlers
|
|
15
|
+
from hercules.events import CodeZeroEvent
|
|
16
|
+
from hercules.internal.connection import create_connection
|
|
17
|
+
from hercules.internal.module_builder import ModuleBuildData, build_module
|
|
18
|
+
from hercules.manager.config import ConfigManager
|
|
19
|
+
from hercules.manager.datatype import DataTypeManager
|
|
20
|
+
from hercules.manager.event import EventManager
|
|
21
|
+
from hercules.manager.flow import FlowManager
|
|
22
|
+
from hercules.manager.function import FunctionManager
|
|
23
|
+
from hercules.manager.runtime_event import RuntimeEventManager
|
|
24
|
+
from hercules.manager.runtime_function import RuntimeFunctionManager
|
|
25
|
+
from hercules.map.datatype import data_type_map
|
|
26
|
+
from hercules.map.event import event_map
|
|
27
|
+
from hercules.map.function import function_map
|
|
28
|
+
from hercules.map.runtime_event import runtime_event_map
|
|
29
|
+
from hercules.map.runtime_function import runtime_function_map
|
|
30
|
+
from hercules.models.event import EventModel
|
|
31
|
+
from hercules.models.function import FunctionProps
|
|
32
|
+
from hercules.types import ConfigurationDefinition, RuntimeError, Translation
|
|
33
|
+
|
|
34
|
+
# Registry of actions constructed while exporting, so the CLI can find them.
|
|
35
|
+
_REGISTERED_ACTIONS: List["Action"] = []
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def is_export_mode() -> bool:
|
|
39
|
+
return os.environ.get("HERCULES_EXPORT") == "1"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def registered_actions() -> List["Action"]:
|
|
43
|
+
return list(_REGISTERED_ACTIONS)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class EventEmitter:
|
|
47
|
+
"""A minimal Node-style event emitter."""
|
|
48
|
+
|
|
49
|
+
def __init__(self) -> None:
|
|
50
|
+
self._listeners: Dict[str, List[Callable]] = {}
|
|
51
|
+
|
|
52
|
+
def on(self, event, listener: Callable) -> "EventEmitter":
|
|
53
|
+
self._listeners.setdefault(str(event), []).append(listener)
|
|
54
|
+
return self
|
|
55
|
+
|
|
56
|
+
def once(self, event, listener: Callable) -> "EventEmitter":
|
|
57
|
+
def wrapper(*args):
|
|
58
|
+
self.off(event, wrapper)
|
|
59
|
+
return listener(*args)
|
|
60
|
+
|
|
61
|
+
return self.on(event, wrapper)
|
|
62
|
+
|
|
63
|
+
def off(self, event, listener: Callable) -> "EventEmitter":
|
|
64
|
+
listeners = self._listeners.get(str(event))
|
|
65
|
+
if listeners and listener in listeners:
|
|
66
|
+
listeners.remove(listener)
|
|
67
|
+
return self
|
|
68
|
+
|
|
69
|
+
def emit(self, event, *args) -> bool:
|
|
70
|
+
listeners = list(self._listeners.get(str(event), []))
|
|
71
|
+
for listener in listeners:
|
|
72
|
+
result = listener(*args)
|
|
73
|
+
if inspect.isawaitable(result):
|
|
74
|
+
asyncio.ensure_future(result)
|
|
75
|
+
return bool(listeners)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class Action(EventEmitter):
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
identifier: str,
|
|
82
|
+
version: str,
|
|
83
|
+
aquila_url: Optional[str],
|
|
84
|
+
author: str,
|
|
85
|
+
icon: str,
|
|
86
|
+
documentation: str,
|
|
87
|
+
name: List[Translation],
|
|
88
|
+
configuration_definitions: Optional[List[ConfigurationDefinition]] = None,
|
|
89
|
+
) -> None:
|
|
90
|
+
super().__init__()
|
|
91
|
+
self._identifier = identifier
|
|
92
|
+
self._version = version
|
|
93
|
+
self._aquila_url = aquila_url
|
|
94
|
+
self._author = author
|
|
95
|
+
self._icon = icon
|
|
96
|
+
self._documentation = documentation
|
|
97
|
+
self._name = name
|
|
98
|
+
self._configuration_definitions = [
|
|
99
|
+
c if isinstance(c, ConfigurationDefinition) else ConfigurationDefinition(**c)
|
|
100
|
+
for c in (configuration_definitions or [])
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
self._channel = None
|
|
104
|
+
self._stream = None
|
|
105
|
+
self._actions = {a.packet_type: a.handle for a in _packet_handlers}
|
|
106
|
+
# Pending sub flow / flow execution requests awaiting a response, keyed by
|
|
107
|
+
# a per-invocation identifier: sub flow executions use the correlation
|
|
108
|
+
# identifier generated for each invocation (echoed back on the response),
|
|
109
|
+
# flow executions use their execution identifier. Both are unique per call,
|
|
110
|
+
# so each key maps to a single pending request.
|
|
111
|
+
self._pending_executions: Dict[str, asyncio.Future] = {}
|
|
112
|
+
|
|
113
|
+
self.configs = ConfigManager()
|
|
114
|
+
self.flows = FlowManager()
|
|
115
|
+
self.functions = FunctionManager()
|
|
116
|
+
self.runtime_functions = RuntimeFunctionManager()
|
|
117
|
+
self.data_types = DataTypeManager()
|
|
118
|
+
self.events = EventManager()
|
|
119
|
+
self.runtime_events = RuntimeEventManager()
|
|
120
|
+
|
|
121
|
+
if is_export_mode():
|
|
122
|
+
_REGISTERED_ACTIONS.append(self)
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def identifier(self) -> str:
|
|
126
|
+
return self._identifier
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def version(self) -> str:
|
|
130
|
+
return self._version
|
|
131
|
+
|
|
132
|
+
@property
|
|
133
|
+
def stream(self):
|
|
134
|
+
return self._stream
|
|
135
|
+
|
|
136
|
+
# --- registration ---------------------------------------------------------
|
|
137
|
+
def register_function(self, klass: type) -> None:
|
|
138
|
+
definition = function_map(klass)
|
|
139
|
+
self.functions.set(definition.runtime_name, definition)
|
|
140
|
+
|
|
141
|
+
def register_runtime_function(self, klass: type) -> None:
|
|
142
|
+
omit_definition = get_metadata("hercules:omit_function_definition", klass) or False
|
|
143
|
+
definition = runtime_function_map(klass)
|
|
144
|
+
self.runtime_functions.set(definition.runtime_name, definition)
|
|
145
|
+
if not omit_definition:
|
|
146
|
+
self.functions.set(
|
|
147
|
+
definition.runtime_name,
|
|
148
|
+
FunctionProps(
|
|
149
|
+
runtime_definition_name=definition.runtime_name,
|
|
150
|
+
runtime_name=definition.runtime_name,
|
|
151
|
+
signature=definition.signature,
|
|
152
|
+
throws_error=definition.throws_error,
|
|
153
|
+
name=definition.name,
|
|
154
|
+
description=definition.description,
|
|
155
|
+
documentation=definition.documentation,
|
|
156
|
+
deprecation_message=definition.deprecation_message,
|
|
157
|
+
display_message=definition.display_message,
|
|
158
|
+
alias=definition.alias,
|
|
159
|
+
display_icon=definition.display_icon,
|
|
160
|
+
design=definition.design,
|
|
161
|
+
parameters=[
|
|
162
|
+
replace(p, runtime_definition_name=p.runtime_name)
|
|
163
|
+
for p in (definition.parameters or [])
|
|
164
|
+
],
|
|
165
|
+
),
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
def register_data_type_class(self, klass: type) -> None:
|
|
169
|
+
definition = data_type_map(klass)
|
|
170
|
+
self.data_types.set(definition.identifier, definition)
|
|
171
|
+
|
|
172
|
+
def register_event_class(self, klass: type) -> None:
|
|
173
|
+
definition = event_map(klass)
|
|
174
|
+
self.events.set(definition.identifier, definition)
|
|
175
|
+
|
|
176
|
+
def register_runtime_event_class(self, klass: type) -> None:
|
|
177
|
+
omit_definition = get_metadata("hercules:omit_event_definition", klass) or False
|
|
178
|
+
definition = runtime_event_map(klass)
|
|
179
|
+
self.runtime_events.set(definition.identifier, definition)
|
|
180
|
+
if not omit_definition:
|
|
181
|
+
self.events.set(
|
|
182
|
+
definition.identifier,
|
|
183
|
+
EventModel(
|
|
184
|
+
identifier=definition.identifier,
|
|
185
|
+
signature=definition.signature,
|
|
186
|
+
settings=definition.settings,
|
|
187
|
+
editable=definition.editable,
|
|
188
|
+
name=definition.name,
|
|
189
|
+
description=definition.description,
|
|
190
|
+
documentation=definition.documentation,
|
|
191
|
+
display_message=definition.display_message,
|
|
192
|
+
alias=definition.alias,
|
|
193
|
+
display_icon=definition.display_icon,
|
|
194
|
+
runtime_identifier=definition.identifier,
|
|
195
|
+
),
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# --- runtime dispatch -----------------------------------------------------
|
|
199
|
+
async def send(self, request) -> None:
|
|
200
|
+
if self._stream is None:
|
|
201
|
+
raise RuntimeError("STREAM_NOT_CONNECTED", "Not connected. Call connect() first.")
|
|
202
|
+
await self._stream.write(request)
|
|
203
|
+
|
|
204
|
+
async def fire(self, target, payload: PlainValue = None):
|
|
205
|
+
"""Fire the flow(s) bound to an event, or a single flow by id.
|
|
206
|
+
|
|
207
|
+
``fire(event_class, payload)`` executes every registered flow whose
|
|
208
|
+
``type`` matches the event class' identifier, resolving the results
|
|
209
|
+
together as a list. ``fire(flow_id, payload)`` executes just that flow and
|
|
210
|
+
returns its result. Flows are tracked from the ActionFlowUpdate messages
|
|
211
|
+
Aquila streams (see :attr:`flows`); each is executed via
|
|
212
|
+
:meth:`execute_flow`.
|
|
213
|
+
"""
|
|
214
|
+
if self._stream is None:
|
|
215
|
+
raise Exception("Not connected. Call connect() first.")
|
|
216
|
+
|
|
217
|
+
# Single flow by id.
|
|
218
|
+
if isinstance(target, int) and not isinstance(target, bool):
|
|
219
|
+
return await self.execute_flow(target, payload)
|
|
220
|
+
|
|
221
|
+
# All flows bound to the event's flow type.
|
|
222
|
+
flow_type = get_metadata("hercules:identifier", target)
|
|
223
|
+
if not flow_type:
|
|
224
|
+
raise Exception(f"{target.__name__} is missing an @Identifier decorator.")
|
|
225
|
+
flows = self.flows.filter(lambda flow, _key: flow.type == flow_type)
|
|
226
|
+
return await asyncio.gather(*(self.execute_flow(flow.flow_id, payload) for flow in flows))
|
|
227
|
+
|
|
228
|
+
async def execute_sub_flow(self, sub_flow, *params: PlainValue) -> PlainValue:
|
|
229
|
+
if self._stream is None:
|
|
230
|
+
raise Exception("Not connected. Call connect() first.")
|
|
231
|
+
execution_identifier = sub_flow.execution_identifier
|
|
232
|
+
# Scope this individual invocation with a fresh correlation identifier so
|
|
233
|
+
# its response can be matched even when the same sub flow is executed
|
|
234
|
+
# repeatedly and responses arrive out of order.
|
|
235
|
+
correlation_identifier = str(uuid.uuid4())
|
|
236
|
+
result = self._await_execution_response(correlation_identifier)
|
|
237
|
+
request = action_pb2.ActionTransferRequest(
|
|
238
|
+
sub_flow_execution=action_pb2.ActionSubFlowExecutionRequest(
|
|
239
|
+
execution_identifier=execution_identifier,
|
|
240
|
+
parameters=[
|
|
241
|
+
construct_value(p if p is not None else None) for p in params
|
|
242
|
+
],
|
|
243
|
+
correlation_identifier=correlation_identifier,
|
|
244
|
+
)
|
|
245
|
+
)
|
|
246
|
+
await self.send(request)
|
|
247
|
+
self.emit(CodeZeroEvent.stream_message_sent, request)
|
|
248
|
+
return await result
|
|
249
|
+
|
|
250
|
+
async def execute_flow(self, flow_id, payload: Optional[PlainValue] = None) -> PlainValue:
|
|
251
|
+
if self._stream is None:
|
|
252
|
+
raise Exception("Not connected. Call connect() first.")
|
|
253
|
+
execution_identifier = str(uuid.uuid4())
|
|
254
|
+
result = self._await_execution_response(execution_identifier)
|
|
255
|
+
request = action_pb2.ActionTransferRequest(
|
|
256
|
+
flow_execution=action_pb2.ActionFlowExecutionRequest(
|
|
257
|
+
execution_identifier=execution_identifier,
|
|
258
|
+
flow_id=str(flow_id),
|
|
259
|
+
payload=construct_value(payload if payload is not None else None),
|
|
260
|
+
)
|
|
261
|
+
)
|
|
262
|
+
await self.send(request)
|
|
263
|
+
self.emit(CodeZeroEvent.stream_message_sent, request)
|
|
264
|
+
return await result
|
|
265
|
+
|
|
266
|
+
def _await_execution_response(self, identifier: str) -> asyncio.Future:
|
|
267
|
+
future: asyncio.Future = asyncio.get_event_loop().create_future()
|
|
268
|
+
self._pending_executions[identifier] = future
|
|
269
|
+
return future
|
|
270
|
+
|
|
271
|
+
def resolve_execution_response(self, identifier: str, response) -> None:
|
|
272
|
+
pending = self._pending_executions.pop(identifier, None)
|
|
273
|
+
if pending is None:
|
|
274
|
+
self.emit(
|
|
275
|
+
CodeZeroEvent.error,
|
|
276
|
+
Exception(
|
|
277
|
+
f"Received execution response for unknown execution identifier: "
|
|
278
|
+
f"{identifier}"
|
|
279
|
+
),
|
|
280
|
+
)
|
|
281
|
+
return
|
|
282
|
+
which = response.WhichOneof("result")
|
|
283
|
+
if which == "success":
|
|
284
|
+
pending.set_result(to_allowed_value(response.success))
|
|
285
|
+
elif which == "failure":
|
|
286
|
+
pending.set_exception(RuntimeError(response.failure.code, response.failure.message))
|
|
287
|
+
else:
|
|
288
|
+
pending.set_exception(Exception("Received execution response with no result"))
|
|
289
|
+
|
|
290
|
+
async def connect(
|
|
291
|
+
self,
|
|
292
|
+
auth_token: str,
|
|
293
|
+
aquila_url: Optional[str] = None,
|
|
294
|
+
grpc_options: Optional[list] = None,
|
|
295
|
+
) -> None:
|
|
296
|
+
if is_export_mode():
|
|
297
|
+
return
|
|
298
|
+
url = aquila_url or self._aquila_url
|
|
299
|
+
if not url:
|
|
300
|
+
raise Exception("aquila_url must be provided in the constructor or connect()")
|
|
301
|
+
|
|
302
|
+
try:
|
|
303
|
+
channel, stream = await create_connection(
|
|
304
|
+
self.build_module(), auth_token, url, grpc_options
|
|
305
|
+
)
|
|
306
|
+
self._channel = channel
|
|
307
|
+
self._stream = stream
|
|
308
|
+
except Exception as err: # noqa: BLE001
|
|
309
|
+
self.emit(CodeZeroEvent.error, err)
|
|
310
|
+
raise
|
|
311
|
+
|
|
312
|
+
self.emit(CodeZeroEvent.connected, self)
|
|
313
|
+
await self._process_stream()
|
|
314
|
+
|
|
315
|
+
async def _process_stream(self) -> None:
|
|
316
|
+
async for message in self._stream:
|
|
317
|
+
self.emit(CodeZeroEvent.stream_message_received, message)
|
|
318
|
+
which = message.WhichOneof("data")
|
|
319
|
+
if which is None:
|
|
320
|
+
self.emit(
|
|
321
|
+
CodeZeroEvent.error,
|
|
322
|
+
Exception("Received unknown message type from stream"),
|
|
323
|
+
)
|
|
324
|
+
continue
|
|
325
|
+
handler = self._actions.get(which)
|
|
326
|
+
if handler is not None:
|
|
327
|
+
handler(self, getattr(message, which))
|
|
328
|
+
|
|
329
|
+
def build_module(self):
|
|
330
|
+
return build_module(
|
|
331
|
+
ModuleBuildData(
|
|
332
|
+
identifier=self._identifier,
|
|
333
|
+
version=self._version,
|
|
334
|
+
author=self._author,
|
|
335
|
+
icon=self._icon,
|
|
336
|
+
documentation=self._documentation,
|
|
337
|
+
name=self._name,
|
|
338
|
+
configuration_definitions=self._configuration_definitions,
|
|
339
|
+
data_types=self.data_types.values(),
|
|
340
|
+
events=self.events.values(),
|
|
341
|
+
runtime_events=self.runtime_events.values(),
|
|
342
|
+
functions=self.functions.values(),
|
|
343
|
+
runtime_functions=self.runtime_functions.values(),
|
|
344
|
+
)
|
|
345
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""Action packet handlers (port of ``src/actions/index.ts``)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from hercules.actions import (
|
|
5
|
+
execution,
|
|
6
|
+
flow_execution,
|
|
7
|
+
flow_update,
|
|
8
|
+
module_configurations,
|
|
9
|
+
sub_flow_execution,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
# Ordered list of handlers, keyed by their protobuf ``data`` oneof field name.
|
|
13
|
+
actions = [
|
|
14
|
+
module_configurations,
|
|
15
|
+
execution,
|
|
16
|
+
sub_flow_execution,
|
|
17
|
+
flow_execution,
|
|
18
|
+
flow_update,
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
__all__ = ["actions"]
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Execution request handler (port of ``src/actions/Execution.ts``)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import asyncio
|
|
5
|
+
import inspect
|
|
6
|
+
import time
|
|
7
|
+
|
|
8
|
+
from hercules._tucana.helpers import construct_value
|
|
9
|
+
from tucana.generated.aquila import action_pb2
|
|
10
|
+
from tucana.generated.shared import errors_pb2, execution_result_pb2
|
|
11
|
+
from hercules.events import CodeZeroEvent
|
|
12
|
+
from hercules.internal.literal import resolve_node_value
|
|
13
|
+
from hercules.types import ProjectConfiguration, RuntimeError
|
|
14
|
+
|
|
15
|
+
packet_type = "execution"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _now_micros() -> int:
|
|
19
|
+
return time.time_ns() // 1000
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _build_params(action, execution, func):
|
|
23
|
+
params = []
|
|
24
|
+
parameters = func.parameters or []
|
|
25
|
+
for index, _param in enumerate(parameters):
|
|
26
|
+
field = execution.parameters[index] if index < len(execution.parameters) else None
|
|
27
|
+
params.append(resolve_node_value(action, field))
|
|
28
|
+
return params
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _wants_context(handler, param_count: int) -> bool:
|
|
32
|
+
try:
|
|
33
|
+
sig = inspect.signature(handler)
|
|
34
|
+
except (TypeError, ValueError):
|
|
35
|
+
return False
|
|
36
|
+
positional = [
|
|
37
|
+
p
|
|
38
|
+
for p in sig.parameters.values()
|
|
39
|
+
if p.kind in (p.POSITIONAL_ONLY, p.POSITIONAL_OR_KEYWORD)
|
|
40
|
+
]
|
|
41
|
+
# A `*args` handler has no fixed positionals and never gets a context.
|
|
42
|
+
return len(positional) == param_count + 1
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def handle(action, execution) -> None:
|
|
46
|
+
action.emit(CodeZeroEvent.execution_request_received, execution)
|
|
47
|
+
|
|
48
|
+
func = action.runtime_functions.get(execution.function_identifier)
|
|
49
|
+
if func is None:
|
|
50
|
+
print("Received execution request for unknown function:", execution.function_identifier)
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
params = _build_params(action, execution, func)
|
|
54
|
+
|
|
55
|
+
conf = action.configs.get(execution.project_id) or ProjectConfiguration(
|
|
56
|
+
project_id=0,
|
|
57
|
+
config_values=[],
|
|
58
|
+
find_config=lambda identifier: None,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
from hercules.types import FunctionContext
|
|
62
|
+
|
|
63
|
+
context = FunctionContext(
|
|
64
|
+
project_id=execution.project_id,
|
|
65
|
+
execution_id=execution.execution_identifier,
|
|
66
|
+
matched_config=conf,
|
|
67
|
+
execute_flow=lambda flow_id, payload=None: action.execute_flow(flow_id, payload),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
started_at = _now_micros()
|
|
71
|
+
handler = func.handler
|
|
72
|
+
all_params = (
|
|
73
|
+
[context, *params] if _wants_context(handler, len(params)) else params
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
asyncio.ensure_future(_run(action, execution, handler, all_params, started_at))
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
async def _run(action, execution, handler, all_params, started_at) -> None:
|
|
80
|
+
try:
|
|
81
|
+
value = handler(*all_params)
|
|
82
|
+
if inspect.isawaitable(value):
|
|
83
|
+
value = await value
|
|
84
|
+
finished_at = _now_micros()
|
|
85
|
+
request = action_pb2.ActionTransferRequest(
|
|
86
|
+
result=action_pb2.ActionExecutionResponse(
|
|
87
|
+
execution_identifier=execution.execution_identifier,
|
|
88
|
+
node_result=execution_result_pb2.NodeExecutionResult(
|
|
89
|
+
started_at=started_at,
|
|
90
|
+
finished_at=finished_at,
|
|
91
|
+
parameter_results=[],
|
|
92
|
+
success=construct_value(value if value is not None else None),
|
|
93
|
+
),
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
await action.send(request)
|
|
97
|
+
except Exception as error: # noqa: BLE001
|
|
98
|
+
finished_at = _now_micros()
|
|
99
|
+
is_runtime_error = isinstance(error, RuntimeError)
|
|
100
|
+
proto_error = errors_pb2.Error(
|
|
101
|
+
code=error.code if is_runtime_error else "UNKNOWN_ERROR",
|
|
102
|
+
category="RUNTIME",
|
|
103
|
+
message=(error.description or str(error)) if is_runtime_error else str(error),
|
|
104
|
+
timestamp=finished_at,
|
|
105
|
+
version=action.version,
|
|
106
|
+
)
|
|
107
|
+
request = action_pb2.ActionTransferRequest(
|
|
108
|
+
result=action_pb2.ActionExecutionResponse(
|
|
109
|
+
execution_identifier=execution.execution_identifier,
|
|
110
|
+
node_result=execution_result_pb2.NodeExecutionResult(
|
|
111
|
+
started_at=started_at,
|
|
112
|
+
finished_at=finished_at,
|
|
113
|
+
parameter_results=[],
|
|
114
|
+
error=proto_error,
|
|
115
|
+
),
|
|
116
|
+
)
|
|
117
|
+
)
|
|
118
|
+
try:
|
|
119
|
+
await action.send(request)
|
|
120
|
+
except Exception as err: # noqa: BLE001
|
|
121
|
+
print("Failed to send execution error:", err)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Flow execution response handler (port of ``src/actions/FlowExecution.ts``)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from hercules.events import CodeZeroEvent
|
|
5
|
+
|
|
6
|
+
packet_type = "flow_execution_response"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def handle(action, response) -> None:
|
|
10
|
+
action.emit(CodeZeroEvent.flow_execution_response_received, response)
|
|
11
|
+
action.resolve_execution_response(response.execution_identifier, response)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Flow update handler (port of ``src/actions/FlowUpdate.ts``)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from hercules.events import CodeZeroEvent
|
|
5
|
+
|
|
6
|
+
packet_type = "flow_update"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def handle(action, update) -> None:
|
|
10
|
+
which = update.WhichOneof("data")
|
|
11
|
+
if which == "updated_flow":
|
|
12
|
+
flow = update.updated_flow
|
|
13
|
+
action.flows.set(flow.flow_id, flow)
|
|
14
|
+
action.emit(CodeZeroEvent.flow_updated, flow)
|
|
15
|
+
elif which == "deleted_flow":
|
|
16
|
+
flow_id = update.deleted_flow
|
|
17
|
+
action.flows.delete(flow_id)
|
|
18
|
+
action.emit(CodeZeroEvent.flow_deleted, flow_id)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Module configurations handler.
|
|
2
|
+
|
|
3
|
+
Port of ``src/actions/ModuleConfigurations.ts``.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from hercules.events import CodeZeroEvent
|
|
8
|
+
|
|
9
|
+
packet_type = "module_configurations"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def handle(action, data) -> None:
|
|
13
|
+
action.configs.update(data.module_configurations)
|
|
14
|
+
action.emit(CodeZeroEvent.module_updated, data)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Sub flow execution response handler.
|
|
2
|
+
|
|
3
|
+
Port of ``src/actions/SubFlowExecution.ts``.
|
|
4
|
+
"""
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from hercules.events import CodeZeroEvent
|
|
8
|
+
|
|
9
|
+
packet_type = "sub_flow_execution_response"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def handle(action, response) -> None:
|
|
13
|
+
action.emit(CodeZeroEvent.sub_flow_execution_response_received, response)
|
|
14
|
+
action.resolve_execution_response(response.correlation_identifier, response)
|
hercules/cli.py
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""``hercules`` command line interface (port of ``src/cli.ts``).
|
|
2
|
+
|
|
3
|
+
Loads an entry file (without connecting to aquila) and prints the Action as a
|
|
4
|
+
JSON document in the shape of the tucana protobuf ``Module`` type.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import importlib.util
|
|
9
|
+
import os
|
|
10
|
+
import sys
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
from google.protobuf import json_format
|
|
14
|
+
|
|
15
|
+
from hercules.action import Action, registered_actions
|
|
16
|
+
|
|
17
|
+
USAGE = """Usage: hercules export <entry-file> [options]
|
|
18
|
+
|
|
19
|
+
Loads the given entry file (without connecting to aquila) and prints the
|
|
20
|
+
Action as a JSON document in the shape of the tucana protobuf Module type.
|
|
21
|
+
|
|
22
|
+
Options:
|
|
23
|
+
-o, --out <file> Write the JSON to a file instead of stdout
|
|
24
|
+
--compact Emit compact JSON instead of pretty-printed
|
|
25
|
+
-h, --help Show this help message
|
|
26
|
+
|
|
27
|
+
Examples:
|
|
28
|
+
hercules export src/index.py
|
|
29
|
+
hercules export app.py -o module.json
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _fail(message: str):
|
|
34
|
+
sys.stderr.write(f"{message}\n")
|
|
35
|
+
raise SystemExit(1)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _parse_export_args(argv):
|
|
39
|
+
entry = None
|
|
40
|
+
out = None
|
|
41
|
+
compact = False
|
|
42
|
+
i = 0
|
|
43
|
+
while i < len(argv):
|
|
44
|
+
arg = argv[i]
|
|
45
|
+
if arg in ("-h", "--help"):
|
|
46
|
+
sys.stdout.write(USAGE)
|
|
47
|
+
raise SystemExit(0)
|
|
48
|
+
elif arg in ("-o", "--out"):
|
|
49
|
+
i += 1
|
|
50
|
+
if i >= len(argv):
|
|
51
|
+
_fail(f"Missing value for {arg}\n\n{USAGE}")
|
|
52
|
+
out = argv[i]
|
|
53
|
+
elif arg == "--compact":
|
|
54
|
+
compact = True
|
|
55
|
+
elif arg.startswith("-"):
|
|
56
|
+
_fail(f"Unknown option: {arg}\n\n{USAGE}")
|
|
57
|
+
elif entry is None:
|
|
58
|
+
entry = arg
|
|
59
|
+
else:
|
|
60
|
+
_fail(f"Unexpected argument: {arg}\n\n{USAGE}")
|
|
61
|
+
i += 1
|
|
62
|
+
if entry is None:
|
|
63
|
+
_fail(f"Missing <entry-file> argument\n\n{USAGE}")
|
|
64
|
+
return entry, out, compact
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _is_action(value) -> bool:
|
|
68
|
+
if isinstance(value, Action):
|
|
69
|
+
return True
|
|
70
|
+
return (
|
|
71
|
+
callable(getattr(value, "build_module", None))
|
|
72
|
+
and isinstance(getattr(value, "identifier", None), str)
|
|
73
|
+
and callable(getattr(value, "register_function", None))
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _find_action(module) -> Action:
|
|
78
|
+
for value in vars(module).values():
|
|
79
|
+
if _is_action(value):
|
|
80
|
+
return value
|
|
81
|
+
registered = registered_actions()
|
|
82
|
+
if registered:
|
|
83
|
+
return registered[-1]
|
|
84
|
+
raise Exception(
|
|
85
|
+
"No Action found. Define your Action instance in the entry file or "
|
|
86
|
+
"construct it during module load."
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _load_entry(entry: str):
|
|
91
|
+
entry_path = Path(os.getcwd()) / entry
|
|
92
|
+
if not entry_path.exists():
|
|
93
|
+
_fail(f"Entry file not found: {entry_path}")
|
|
94
|
+
spec = importlib.util.spec_from_file_location("__hercules_entry__", str(entry_path))
|
|
95
|
+
module = importlib.util.module_from_spec(spec)
|
|
96
|
+
# Make the entry's own directory importable so relative sibling imports work.
|
|
97
|
+
sys.path.insert(0, str(entry_path.parent))
|
|
98
|
+
spec.loader.exec_module(module)
|
|
99
|
+
return module
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _run_export(argv):
|
|
103
|
+
entry, out, compact = _parse_export_args(argv)
|
|
104
|
+
# Must be set before the entry file is loaded: it makes connect() a no-op
|
|
105
|
+
# and registers constructed Action instances for _find_action.
|
|
106
|
+
os.environ["HERCULES_EXPORT"] = "1"
|
|
107
|
+
module = _load_entry(entry)
|
|
108
|
+
action = _find_action(module)
|
|
109
|
+
module_message = action.build_module()
|
|
110
|
+
json_text = json_format.MessageToJson(
|
|
111
|
+
module_message,
|
|
112
|
+
indent=None if compact else 2,
|
|
113
|
+
preserving_proto_field_name=False,
|
|
114
|
+
)
|
|
115
|
+
json_text = (json_text if compact else json_text) + "\n"
|
|
116
|
+
if out:
|
|
117
|
+
out_path = Path(os.getcwd()) / out
|
|
118
|
+
out_path.write_text(json_text)
|
|
119
|
+
sys.stderr.write(f"Module written to {out_path}\n")
|
|
120
|
+
else:
|
|
121
|
+
sys.stdout.write(json_text)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def main(argv=None):
|
|
125
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
126
|
+
if not argv or argv[0] in ("-h", "--help"):
|
|
127
|
+
sys.stdout.write(USAGE)
|
|
128
|
+
raise SystemExit(1 if not argv else 0)
|
|
129
|
+
command, *rest = argv
|
|
130
|
+
if command != "export":
|
|
131
|
+
_fail(f"Unknown command: {command}\n\n{USAGE}")
|
|
132
|
+
_run_export(rest)
|
|
133
|
+
raise SystemExit(0)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
if __name__ == "__main__":
|
|
137
|
+
main()
|
|
File without changes
|