Skip to main content

Compile serialized math.js expression trees into fast, reusable Python callables.

Project description

mathjs-to-func

PyPI Ruff Black ty CI

A tiny Python library that compiles serialized math.js expression trees into fast, reusable Python callables. The generated function respects dependency ordering, validates inputs, and mirrors a subset of math.js operators (+, -, *, /, ^, %, unary plus/minus) and functions (min, max, sum, ifnull).

Key Features

  • Execute without reparsing or repeatedly walking the JSON graph.
  • Detect dependency cycles and missing identifiers early.
  • Keep execution sandboxed by compiling a controlled Python AST.
  • Work well with scalars or NumPy arrays for vectorised workloads.

Installation

The project uses uv for dependency and virtualenv management. From the repository root:

uv sync  # create the virtual environment declared in uv.lock

All examples below assume commands are wrapped with uv run ... to execute inside the managed environment.

Public API

from mathjs_to_func import build_evaluator

def main():
    mathjs_payload = {
        "expressions": {
            # z = (x + y) / 2
            "sum_xy": {
                "type": "OperatorNode",
                "fn": "add",
                "args": [
                    {"type": "SymbolNode", "name": "x"},
                    {"type": "SymbolNode", "name": "y"},
                ],
            },
            "mean": {
                "type": "OperatorNode",
                "fn": "divide",
                "args": [
                    {"type": "SymbolNode", "name": "sum_xy"},
                    {"type": "ConstantNode", "value": "2", "valueType": "number"},
                ],
            },
        },
        "inputs": ["x", "y"],
        "target": "mean",
    }

    evaluator = build_evaluator(**mathjs_payload, include_source=True)

    result = evaluator({"x": 10, "y": 6})
    print(result)  # -> 8.0

    # Introspection helpers
    print(evaluator.__mathjs_required_inputs__)     # ('x', 'y')
    print(evaluator.__mathjs_evaluation_order__)    # ('sum_xy', 'mean')
    print(evaluator.__mathjs_source__)              # Generated Python source

Parameters

build_evaluator accepts keyword parameters (or a single payload mapping containing the same keys):

Argument Type Description
expressions Mapping[str, Mapping[str, Any]] math.js AST JSON keyed by expression id. Each id becomes a local variable in the compiled function.
inputs Iterable[str] Whitelisted identifiers that may be supplied when the function is invoked.
target str Name of the expression whose computed value should be returned.
include_source bool (optional) Attach the generated Python source code as __mathjs_source__ on the returned callable.

The returned callable always expects a single mapping argument with the provided inputs. It returns the evaluated target value and may be reused across invocations.

Supported math.js nodes

Node Notes
ConstantNode numeric (number), boolean, or null literals
SymbolNode validated identifiers; must be alphanumeric/underscore, starting with a letter/underscore
OperatorNode add, subtract, multiply, divide, pow, mod, unary unaryPlus, unaryMinus
FunctionNode min, max, sum, ifnull
ParenthesisNode forwards to the wrapped expression
ArrayNode materialised to Python lists/NumPy arrays

Unknown node types, invalid identifiers, or disallowed functions raise InvalidNodeError during compilation.

Error handling

  • ExpressionError: base class for configuration mistakes.
  • MissingTargetError: requested target id does not exist.
  • UnknownIdentifierError: an expression references a symbol that is neither an input nor another expression.
  • CircularDependencyError: dependency graph contains a cycle.
  • InvalidNodeError: AST contains unsupported structures or invalid literals.
  • InputValidationError: the compiled function received inputs that are missing, unexpected, or not a mapping.

All exceptions provide enough context (expression name, offending identifier, cycle list, etc.) to surface descriptive UI errors.

Implementation Notes

  1. AST translationMathJsAstBuilder walks the math.js JSON and emits Python ast.AST nodes. Identifiers are validated via a strict regex to prevent sneaky names like __import__.
  2. Dependency graph – A topological sorter (graphlib.TopologicalSorter) runs over expression references to produce a safe evaluation order while catching cycles and missing references upfront.
  3. Code generation – The generated function validates the provided scope, binds required inputs to local variables, evaluates expressions in order, and returns the target. Intermediate values are stored as local variables named after their expression id.
  4. Execution sandbox – The compiled module is executed with a tightly scoped globals dictionary: helper math functions, NumPy, and a few safe built-ins only. There is no ambient __builtins__ exposure.
  5. Helper functions – math.js functions map onto small Python helpers (_mj_min, _mj_max, _mj_sum, _mj_ifnull) that understand scalars and NumPy arrays.

Testing

Run the full suite (178 tests) with:

uv run pytest

The tests cover operator translation, helper semantics, dependency validation, error conditions, numpy-friendly behaviour, and public API ergonomics.

Project Structure

src/mathjs_to_func/
├── __init__.py          # build_evaluator public API and export list
├── ast_builder.py       # math.js JSON → Python AST translation
├── compiler.py          # dependency graph, code generation, compilation
├── errors.py            # structured exception hierarchy
├── helpers.py           # runtime helpers for min/max/sum/ifnull
└── py.typed             # PEP 561 marker for type-aware consumers

Additional documentation lives in docs/api_design.md, outlining the initial design considerations.

Limitations & Future Work

  • Only a subset of math.js functions/operators are implemented today.
  • Units, user-defined functions, and incremental recomputation are intentionally out of scope for this milestone.
  • Arrays are handled via NumPy; if you need bigints, complex numbers, or matrices, the helper layer will require extension.

Contributions and bug reports are welcome!

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

mathjs_to_func-0.1.0.tar.gz (15.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

mathjs_to_func-0.1.0-py3-none-any.whl (16.6 kB view details)

Uploaded Python 3

File details

Details for the file mathjs_to_func-0.1.0.tar.gz.

File metadata

  • Download URL: mathjs_to_func-0.1.0.tar.gz
  • Upload date:
  • Size: 15.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.9.7

File hashes

Hashes for mathjs_to_func-0.1.0.tar.gz
Algorithm Hash digest
SHA256 8e2f6c0b353bcd79b9cc9e7892f6600eee3f0c735fcbea7f97448aac888ea808
MD5 57f28686c0b090353cd8671075b10291
BLAKE2b-256 05404a7f72293c685065e323330d1bf25058a38a45a0206aeca836545db0991e

See more details on using hashes here.

File details

Details for the file mathjs_to_func-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for mathjs_to_func-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 e89782aeaf4d0b5eb43b7ae1a2bc312e9c84d9252441c1dbc4dab788849ae724
MD5 0e918f1860f82d8c2e9d570e6ebd2983
BLAKE2b-256 54a6cc6b019db560905bd05e945c5787d22e8a5dd0ac85ebac87ac0ceb4f08c3

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page