Skip to main content

wex_core

Version: 30.2.0

wexample-wex-core is the kernel and command-dispatch layer of the wex CLI: it resolves the four command namespaces (addon::group/name, .group/name, @addon::group/name, ~group/name), runs commands as Python functions or multi-step YAML scripts (bash, python, docker runners), and provides the AbstractAddonManager base that every higher-level package uses to register its own commands, resolvers, middlewares, and step guards. It is the shared foundation consumed by all other wex packages, not an end-user tool on its own.

Table of Contents

Installation

pip install wexample-wex-core

Requires Python >=3.10.

Quickstart

Install the package:

pip install wexample-wex-core

The snippet below builds a kernel, registers two addons, and executes the built-in demo::ping/pong command. It needs no pre-existing project on disk.

import tempfile
from pathlib import Path

from wexample_app.const.output import OUTPUT_TARGET_NONE
from wexample_wex_core.addons.core.core_addon_manager import CoreAddonManager
from wexample_wex_core.addons.demo.demo_addon_manager import DemoAddonManager
from wexample_wex_core.common.command_request import CommandRequest
from wexample_wex_core.common.kernel import Kernel

with tempfile.TemporaryDirectory() as tmp:
    root = Path(tmp)
    wex_dir = root / "wex"
    wex_dir.mkdir()
    (root / ".env.yml").write_text("APP_ENV: test\n")

    kernel = Kernel(entrypoint_path=wex_dir)
    kernel.setup(addons=[CoreAddonManager, DemoAddonManager])

    request = CommandRequest(
        kernel=kernel,
        name="demo::ping/pong",
        output_target=[OUTPUT_TARGET_NONE],
        arguments={"type": "dict"},
    )

    response = kernel.execute_kernel_command(request)
    print(response.content)  # {'status': 'pong'}

Kernel(entrypoint_path=...) takes the .wex/ directory itself — a wex/ subdirectory of the project root. The .env.yml file lives one level up (at the project root) and must declare at least APP_ENV.

CoreAddonManager wires the built-in middlewares. DemoAddonManager registers the demo namespace, including ping/pong. A real application passes its own addon managers to setup() in place of or alongside these.

The CommandRequest fields that matter at call time:

field purpose
name command address — addon::group/name, .group/name, @addon::group/name, or ~group/name
output_target where output goes: OUTPUT_TARGET_NONE, OUTPUT_TARGET_STDOUT, or OUTPUT_TARGET_FILE
arguments option values keyed by option name

Tests

This project uses pytest for testing and pytest-cov for code coverage analysis.

Installation

First, install the required testing dependencies:

.venv/bin/python -m pip install pytest pytest-cov

Basic Usage

Run all tests with coverage:

.venv/bin/python -m pytest --cov --cov-report=html

Common Commands

# Run tests with coverage for a specific module
.venv/bin/python -m pytest --cov=your_module

# Show which lines are not covered
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing

# Generate an HTML coverage report
.venv/bin/python -m pytest --cov=your_module --cov-report=html

# Combine terminal and HTML reports
.venv/bin/python -m pytest --cov=your_module --cov-report=term-missing --cov-report=html

# Run specific test file with coverage
.venv/bin/python -m pytest tests/test_file.py --cov=your_module --cov-report=term-missing

Viewing HTML Reports

After generating an HTML report, open htmlcov/index.html in your browser to view detailed line-by-line coverage information.

Coverage Threshold

To enforce a minimum coverage percentage:

.venv/bin/python -m pytest --cov=your_module --cov-fail-under=80

This will cause the test suite to fail if coverage drops below 80%.

Architecture

wexample-wex-core is the kernel and command-dispatch layer consumed by every other wex package. It owns four things: the Kernel that orchestrates startup and execution, the resolver chain that maps a command string to a file, the runner pair that executes that file, and the addon system that lets higher-level packages extend all of the above.

Source layout

src/wexample_wex_core/
  common/           # Kernel, AbstractAddonManager, CommandRequest, CommandAddress, RegistryBuilder
  registry/         # KernelRegistry — the in-memory command map
  path/             # KernelRegistryFile — JSON persistence of the registry
  resolver/         # AbstractCommandResolver, AddonCommandResolver, UserCommandResolver
  runner/           # CorePythonCommandRunner, CoreYamlCommandRunner
  yaml/             # YamlCommandDefinition, AbstractScriptRunner and its four runners
  middleware/       # AbstractEachPathMiddleware, EachPathMiddleware, EachFileMiddleware, EachDirectoryMiddleware
  decorator/        # @command, @option, @alias, @attach, @as_sudo — applied to command functions
  context/          # ExecutionContext — passed to every command function
  workdir/          # KernelWorkdir, AddonWorkdir — file-state wrappers for .wex directories
  webhook/          # HTTP server, routing, token store, type resolvers
  addons/           # CoreAddonManager, DemoAddonManager, DockerAddonManager, GitAddonManager, SystemAddonManager
  const/            # globals.py — command patterns, separators, type names
  exception/        # Typed exceptions for option and command errors

Kernel

src/wexample_wex_core/common/kernel.py is the root object. Kernel.setup() runs a fixed sequence of _init_* methods:

  1. _init_local_env — loads .wex/local/env.yml into os.environ.
  2. _init_command_line_kernel — parses core CLI flags (--output-target, --output-format, --indentation-level, …).
  3. _init_logging — attaches a WARNING-level stderr logger named wex.
  4. _init_addons — instantiates every AbstractAddonManager class passed to setup().
  5. _auto_detect_env — asks each addon for auto-detectable env vars (e.g. PATH entries) and persists found values back to .wex/local/env.yml.
  6. _init_resolvers — builds AddonCommandResolver, UserCommandResolver, and any resolver classes contributed by addons.
  7. _init_runners — builds CorePythonCommandRunner and CoreYamlCommandRunner.
  8. _init_middlewares — collects middleware classes from every addon.
  9. _init_registry — reads or builds src/wexample_wex_core/path/kernel_registry_file.py at .wex/tmp/registry.json. Live resolvers (user) are always re-scanned; addon resolver data is read from the cached file when present.
  10. _init_script_runner_registry — registers bash, docker, exec, and python YAML step runners.
  11. _init_step_guard_registry — collects AbstractStepGuard subclasses from addons.

execute_kernel_command(request) is the single dispatch entry point. It increments an execution-depth counter (used to gate sudo re-exec to depth 0), fires before attachment commands, delegates to the parent runner chain, and fires after / always_after attachments.

Command namespaces

Four patterns, defined in src/wexample_wex_core/const/globals.py:

Pattern Example Resolver
addon::group/name core::ping/hi AddonCommandResolver
.group/name .install/local app resolver (contributed by host)
@addon::group/name @nginx::status service resolver (contributed by host)
~group/name ~my-group/my-cmd UserCommandResolver

Unqualified group/name (no prefix) is resolved by scanning all loaded addon workdirs for a unique match; if ambiguous, it raises. Aliases declared with @alias(…) are resolved first by searching the registry before the pattern match.

A CommandAddress (src/wexample_wex_core/common/command_address.py) is the canonical three-tuple (addon, group, name). It converts between every representation used internally: command string (core::ping/hi), Python function name (core__ping__hi), and file path (commands/ping/hi.py).

Resolvers

src/wexample_wex_core/resolver/abstract_command_resolver.py defines build_registry_data(), build_command_path(), build_command_function_name(), and is_live(). is_live() returns False for addon commands (cached in the registry file) and True for user commands (always scanned fresh).

src/wexample_wex_core/resolver/addon_command_resolver.py walks each addon's commands/ directory: for .py files it imports the module with importlib and reads the CommandMethodWrapper; for .yml files it reads the decorators: block. Both paths populate the same RegistryCommandData structure.

src/wexample_wex_core/resolver/user_command_resolver.py does the same scan against ~/.wex/commands/ and adds that directory to sys.path on first use.

Registry

src/wexample_wex_core/registry/kernel_registry.py holds _resolvers, a nested dict {resolver_key: {addon_name: {command_key: RegistryCommandData}}}. get_all_commands() flattens it into a single keyed dict and caches the result. serialize() serialises non-live resolvers to JSON; hydrate() restores from the same JSON. The file is managed by src/wexample_wex_core/path/kernel_registry_file.py, which extends JsonFile from wexample-filestate.

Running wex core::registry/build (src/wexample_wex_core/addons/core/commands/registry/build.py) forces a full re-scan, overwrites the file, and writes .wex/tmp/autocomplete.json for shell completion.

Runners

Two runners handle the two command file types:

CorePythonCommandRunner (src/wexample_wex_core/runner/core_python_command_runner.py) — calls the parent PythonCommandRunner._build_command_function() to import the module and fetch the decorated function, wraps it in an ExtendedCommand, and returns it. The function must be named {addon}__{group}__{name} and decorated with @command(type=COMMAND_TYPE_ADDON).

CoreYamlCommandRunner (src/wexample_wex_core/runner/core_yaml_command_runner.py) — reads and caches YamlCommandDefinition.from_path(), synthesises a CommandMethodWrapper around a closure that iterates over scripts:, and returns an ExtendedCommand. Before each step runs, ${VAR_NAME} placeholders are substituted from a merged dict of .wex/local/env.yml, os.environ, built-ins (PATH_CURRENT), and option values (uppercased). After substitution, StepGuardRegistry.should_skip_step() is checked; if the step has a command: key it dispatches a sub-request; otherwise it looks up the named script runner.

YAML step runners

All four implement src/wexample_wex_core/yaml/abstract_script_runner.py:

runner: key Class What it executes
bash BashScriptRunner bash -c <script> or bash <file> on the host
python PythonScriptRunner inline Python via PythonScriptResponse (executed in-process)
docker DockerScriptRunner docker exec --env CI=true <container> bash -c <script>
exec ExecScriptRunner arbitrary interpreter list, e.g. [node, -e] + script

DockerScriptRunner._resolve_service_name() iterates addons calling get_service_docker_container_name(service); the first non-None answer wins.

Addons

Every addon is a subclass of src/wexample_wex_core/common/abstract_addon_manager.py. At construction its workdir is set to the directory containing the addon manager file, so commands/ is always found relative to the package. The methods an addon can override:

  • get_command_resolver_classes() — add resolver types (e.g. an app or service resolver).
  • get_middlewares_classes() — add middleware types.
  • get_step_guard_classes() — add AbstractStepGuard subclasses that can skip YAML steps.
  • get_local_configurable_keys() — declare env vars the kernel should auto-detect.
  • get_service_docker_container_name(service) — resolve a short Docker service name.
  • get_webhook_resolvers() — register webhook type resolvers.

The five addons shipped in this package are CoreAddonManager, DemoAddonManager, DockerAddonManager, GitAddonManager, and SystemAddonManager. CoreAddonManager is the only one that contributes middlewares: EachPathMiddleware, EachFileMiddleware, and EachDirectoryMiddleware.

Middlewares

src/wexample_wex_core/middleware/abstract_each_path_middleware.py is a middleware base that expands a path option into multiple ExecutionContext objects — one per matching file or directory — before the command function is called. Subclasses override _should_process_item() and _should_explore_directory() to filter by type. Options recursive, expand_glob, recursion_limit, and should_exist are declared as public attrs.

Call path (Python command)

CLI argv
  → Kernel._build_command_requests_from_arguments()      # parse name + options
  → Kernel.execute_kernel_command(CommandRequest)
      → _enforce_sudo_if_needed()                        # re-exec under sudo if @as_sudo
      → _execute_attached(request, "before")             # before-hooks
      → AbstractKernel.execute_kernel_command()
          → AddonCommandResolver.supports()              # regex match; alias/unqualified fallback
          → CorePythonCommandRunner.will_run()           # check .py file exists
          → CorePythonCommandRunner.build_runnable_command()
              → import module, fetch CommandMethodWrapper
              → ExtendedCommand(kernel, wrapper)
          → ExtendedCommand.execute(context)
              → middleware chain → command function(context, **options)
      → _execute_attached(request, "after")

Call path (YAML command)

  → CoreYamlCommandRunner.will_run()                     # check .yml file exists
  → CoreYamlCommandRunner.build_runnable_command()
      → YamlCommandDefinition.from_path()                # parse yaml once, cache
      → synthesise CommandMethodWrapper(_make_executor)
  → ExtendedCommand.execute()
      → _make_executor(**kwargs)
          for step in scripts:
              yaml_substitute_step(step, variables)      # ${VAR} replacement
              StepGuardRegistry.should_skip_step()
              if "command": sub-request → execute_kernel_command()
              else: ScriptRunner.run(step, variables)    # bash / python / docker / exec

Webhook server

core::webhook/listen starts a ThreadingHTTPServer (src/wexample_wex_core/webhook/handler.py) on a configurable port. Incoming GET requests are validated by routing_is_allowed_route(), then matched to a route (/webhook/{type}/{path}, /health, /metrics). Token validation uses HMAC comparison. Matched requests spawn a wex core::webhook/exec --command-str … subprocess; synchronous calls wait for its output, async calls return immediately with the PID. Prometheus-format metrics are accumulated in-process across requests.

Key types

  • CommandRequest — (kernel, name, arguments, output_target, request_id). The name field is rewritten in-place by resolvers when an alias or unqualified form is found.
  • CommandAddress — frozen dataclass (addon, group, name) with conversion helpers.
  • KernelRegistry — in-memory command map; serialisable to JSON.
  • RegistryCommandData — typed dict: command, path, test, description, alias, attachments, options, tags, sudo, webhook.
  • ExecutionContext — carries kernel, request, command_wrapper, function_kwargs, and middleware into every command function.

Integration in the Suite

This package is part of the Wexample Suite — a collection of high-quality, modular tools designed to work seamlessly together across multiple languages and environments.

The suite includes packages for configuration management, file handling, prompts, and more. Each package can be used independently or as part of the integrated suite.

Visit the Wexample Suite documentation for the complete package ecosystem.

Dependencies

  • attrs: >=23.1.0
  • cattrs: >=23.1.0
  • click:
  • psutil: >=5.9
  • wexample-app: >=19.2.0
  • wexample-cli: >=2.1.0
  • wexample-filestate-git: >=8.1.0
  • wexample-filestate: >=17.2.0
  • wexample-helpers-git: >=7.1.0

Versioning & Compatibility Policy

Wexample packages follow Semantic Versioning (SemVer):

  • MAJOR: Breaking changes
  • MINOR: New features, backward compatible
  • PATCH: Bug fixes, backward compatible

We maintain backward compatibility within major versions and provide clear migration guides for breaking changes.

License

This project is licensed under the MIT License - see the LICENSE file for details.

Free to use in both personal and commercial projects.

About us

Wexample stands as a cornerstone of the digital ecosystem — a collective of seasoned engineers, researchers, and creators driven by a relentless pursuit of technological excellence. More than a media platform, it has grown into a vibrant community where innovation meets craftsmanship, and where every line of code reflects a commitment to clarity, durability, and shared intelligence.

This packages suite embodies this spirit. Trusted by professionals and enthusiasts alike, it delivers a consistent, high-quality foundation for modern development — open, elegant, and battle-tested. Its reputation is built on years of collaboration, refinement, and rigorous attention to detail, making it a natural choice for those who demand both robustness and beauty in their tools.

Wexample cultivates a culture of mastery. Each package, each contribution carries the mark of a community that values precision, ethics, and innovation — a community proud to shape the future of digital craftsmanship.

Known Limitations & Roadmap

Current limitations and planned features are tracked in the GitHub issues.

See the project roadmap for upcoming features and improvements.

Status & Compatibility

Maturity: Production-ready

Python Support: >=3.10

OS Support: Linux, macOS, Windows

Status: Actively maintained

Migration Notes

When upgrading between major versions, refer to the migration guides in the documentation.

Breaking changes are clearly documented with upgrade paths and examples.

Metadata

Release files for wexample-wex-core 30.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for wexample-wex-core 30.2.0
File Size Uploaded
wexample_wex_core-30.2.0.tar.gz 82.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for wexample-wex-core 30.2.0
File Interpreter ABI Platform
wexample_wex_core-30.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 211.2 kB

Release files / wexample_wex_core-30.2.0.tar.gz

Download URL wexample_wex_core-30.2.0.tar.gz
Size 82.7 kB
Tags Source
SHA-256 checksum
How to use checksums
3adb968f709dfac9e96fb612f1feaf2780caf36b456be2fab328534440364e76
BLAKE2b-256 checksum
How to use checksums
ae5b91f02a766bcc5e05e0e9102dc92a4f821356a0738c334b30bd7b0439a698
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via pdm/2.25.9 CPython/3.12.3 Linux/6.8.0-139-generic

Release files / wexample_wex_core-30.2.0-py3-none-any.whl

Download URL wexample_wex_core-30.2.0-py3-none-any.whl
Size 128.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
496351ffaac6107f942238e1581b0ddb8066bc1fee594c160bc748514951bad7
BLAKE2b-256 checksum
How to use checksums
f372287af2f11544707ab1a6f62290cf02f658d36d9656e91257d17834bcc75c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via pdm/2.25.9 CPython/3.12.3 Linux/6.8.0-139-generic

Release history Release notifications | RSS feed

31.2.0

2 release files

31.0.0

2 release files

This release

30.2.0 This release

2 release files

30.0.0

2 release files

29.0.2

2 release files

28.1.0

2 release files

28.0.0

2 release files

27.0.2

2 release files

27.0.1

2 release files

27.0.0

2 release files

26.1.4

2 release files

26.1.3

2 release files

26.1.2

2 release files

26.1.1

2 release files

26.1.0

2 release files

26.0.1

2 release files

26.0.0

2 release files

24.3.0

2 release files

24.2.0

2 release files

24.1.4

2 release files

24.1.3

2 release files

24.1.2

2 release files

24.1.1

2 release files

24.1.0

2 release files

24.0.0

2 release files

23.0.2

2 release files

23.0.1

2 release files

23.0.0

2 release files

22.2.1

2 release files

22.2.0

2 release files

22.1.0

2 release files

22.0.0

2 release files

20.3.3

2 release files

20.3.2

2 release files

20.3.1

2 release files

20.3.0

2 release files

20.2.1

2 release files

20.2.0

2 release files

17.0.0

2 release files

16.0.1

2 release files

16.0.0

2 release files

15.4.0

2 release files

15.3.1

2 release files

15.3.0

2 release files

15.2.0

2 release files

15.1.0

2 release files

15.0.0

2 release files

14.1.0

2 release files

14.0.0

2 release files

7.3.0

2 release files

7.2.0

2 release files

7.1.0

2 release files

7.0.0

2 release files

6.0.68

2 release files

6.0.65

2 release files

6.0.64

2 release files

6.0.49

2 release files

6.0.48

2 release files

6.0.47

2 release files

6.0.43

2 release files

6.0.41

2 release files

6.0.40

1 release file

6.0.39

2 release files

6.0.38

2 release files

6.0.37

2 release files

6.0.36

2 release files

6.0.35

2 release files

6.0.34

2 release files

6.0.29

2 release files

6.0.28

2 release files

6.0.27

2 release files

6.0.26

1 release file

6.0.25

2 release files

6.0.17

2 release files

6.0.16

2 release files

6.0.15

2 release files

6.0.14

2 release files

6.0.13

2 release files

6.0.9

2 release files

6.0.8

2 release files

6.0.7

2 release files

6.0.6

2 release files

6.0.5

2 release files

6.0.4

2 release files

6.0.3

2 release files

6.0.2

2 release files

6.0.1

2 release files

0.0.40

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page