Skip to content

About

The keyword-driven action executor shared by APITestka, LoadDensity, MailThunder and FileAutomation

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

ActionCore

English | 繁體中文 | 简体中文

je_action_core is the keyword-driven action executor shared by APITestka, LoadDensity, MailThunder and FileAutomation. Each of them carried its own copy of the same executor, package manager, callback executor, action-file reader and socket server. This package replaces those copies: a project configures it with its own command prefix, document key, exceptions and messages.

It has no dependencies and supports Python 3.10 to 3.14.

Contents

Installation

pip install je_action_core

A framework that is built on it installs it as a dependency.

Actions

An action list is a JSON list of actions, or a document that keeps the list under one key ({"api_testka": [...]}). Each action takes one of three forms:

Action Call
["name"] command()
["name", {"a": 1}] command(a=1)
["name", [1, 2]] command(1, 2)

Running a list returns one record per action: the command's return value, or repr(error) when it raised. One failing action does not stop the rest.

Quick start

from je_action_core import (
    ActionExecutor, ActionListRules, CommandPolicy, CommandRegistry, ExecutorSettings, LoggingReporter,
)
import logging

registry = CommandRegistry({"MY_add": lambda a, b: a + b}, policy=CommandPolicy.FUNCTIONS_ONLY)
executor = ActionExecutor(
    ExecutorSettings(rules=ActionListRules("my_tool"), reporter=LoggingReporter(logging.getLogger("my_tool"))),
    registry,
)

executor.execute_action({"my_tool": [["MY_add", [1, 2]], ["MY_add", {"a": 3, "b": 4}]]})
# {"execute: ['MY_add', [1, 2]]": 3, "execute: ['MY_add', {'a': 3, 'b': 4}]": 7}

The pieces

Module What it gives you Settings
registry CommandRegistry: commands by name, event_dict as the live mapping CommandPolicy.FUNCTIONS_ONLY or ANY_CALLABLE for caller-added commands; the exception for a refused one
action_list ActionListRules (where the list is), LegacyActionParser and StrictActionParser document key and legacy keys (with a DeprecationWarning); empty list raises or returns {}; error messages
executor ActionExecutor: execute_action, collect_action_results (records and the failed keys, unreported), execute_files, add_command_to_executor; override attempt to wrap every action (retries, a span) ExecutorSettings: rules, parser, reporter, file reader, record key (execute: … or execute[i]: …), repeated keys replaced or numbered (#2), what a failure records (repr(error) by default), action rewrite
reporting LoggingReporter, PrintReporter, or your own ExecutionReporter where events, failures and records go
package_manager PackageManager: load an installed package's members as commands, behind the package gate member naming (<package>_<member> or bare), predicates, name check, errors to log
callback CallbackFunctionExecutor: run a trigger, then a callback legacy or strict checking; raise or log and return None
json_io ActionJsonFile, read_action_json, write_action_json: UTF-8, locked, non-ASCII kept exception class and message templates
file_listing get_dir_files_as_list: action files under a directory —
socket_server, socket_auth start_action_socket_server and the request handlers (plain, secret header, JSON-envelope token) executor, payload check, errors answered, framing, TLS, reply templates, secret
builtins_policy SAFE_BUILTINS: the builtins an action list may call —

__all__ in je_action_core/__init__.py is the supported import surface.

Package gate

PackageManager.add_package_to_executor imports a package and registers its members as commands. An action list that can name os or subprocess could therefore run anything. The host program decides what may load:

package_manager.allow_packages("my_helpers")          # these, and their submodules
package_manager.set_allow_arbitrary_packages(False)   # refuse everything else before importing it

Neither switch should ever be exposed as an action command, so an action list cannot open its own gate. A refused package raises the project's refused exception, which the executor records as that action's result. Until the host calls either switch, any package still loads but raises a DeprecationWarning. A project can turn the gate off (PackageGate.OFF) while it moves to it.

Socket server protocol

start_action_socket_server(host, port, settings, handler_class=ActionRequestHandler) serves on a daemon thread. A client sends one JSON action document per connection. The server replies with one line per record, then Return_Data_Over_JE. A failure replies with the error text and the same marker. quit_server stops the server and sets close_flag and close_event. What can be configured:

  • Framing: Framing.RAW reads one 8 KiB recv, and OversizePolicy says what happens to a full buffer. Framing.LENGTH_PREFIX reads a 4-byte big-endian length, then the body (at most 1 MiB), and sends every reply line as its own frame.
  • TLS: tls_context=server_tls_context(certfile, keyfile) wraps each connection (TLS 1.2 or later).
  • Replies: ReplyMessages holds the templates for a record line, each failure stage (JSON, refused by validate, execution), an undecodable request, the quit reply, the authentication refusals and the log line.
  • Authentication: the base handler has none. SecretHeaderRequestHandler requires a first line <auth_prefix><secret>. EnvelopeTokenRequestHandler accepts {"token": ..., "command": ...} and {"token": ..., "op": "quit"}. Both compare secrets in constant time.

Without authentication, bind the server only to a trusted interface.

Who uses it

APITestka (AT_, port 9939), LoadDensity (LD_), MailThunder (MT_, port 9942) and FileAutomation (FA_) are the projects this package is for. architecture.md §6 lists which of them have moved to it and which pieces and settings each one uses. All four run their TCP servers on socket_server: LoadDensity with the JSON-envelope token, framing and TLS, FileAutomation with the AUTH header and its ACL.

WebRunner (WR_) runs its action executor on it as well. Its own action format ([name, [args], {kwargs}]) is a parser, and its retries and action span wrap attempt. Failure screenshots go into failure_record, and repeated actions are numbered (DuplicateKeys.NUMBER).

n and an ACL.

Development

pip install -e .
pip install pytest
python -m pytest test/

architecture.md describes the layers and the contracts with the four projects. Outstanding work is in progress.md, and finished work is recorded in docs/updates/.

License

MIT, see LICENSE.

About

The keyword-driven action executor shared by APITestka, LoadDensity, MailThunder and FileAutomation

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages