Source code for limulus

"""Top-level public imports for the limulus workflow surface.

Import from here when you want the stable user-facing entry points such as
``Session``, ``submit(...)``, and ``run(...)``.

Importing ``limulus`` itself is deliberately light: pure models stay eager,
while data-processing exports (Session, execution, adapters, parser
service) are resolved lazily through module ``__getattr__`` so pure syntax
imports never load Arrow, Polars, the native extension, or the Session
runtime.  ``submit``/``run`` remain module-level functions and still
capture the direct caller context inside their own bodies.
"""

from __future__ import annotations

import importlib
import inspect
from typing import Any

# Public API: pure models are stdlib-only and safe to import eagerly.
from .host_functions import HostFunctionContextCapture, capture_host_function_context
from .models import (
    CompatibilityNotice,
    DatasetCatalog,
    DataSetRef,
    Diagnostic,
    ExecuteRequest,
    ExecuteResponse,
    LogEntry,
    OutputConversionResult,
    SubmitResult,
)

# Data-processing exports resolved on first attribute access (PackageExports).
_LAZY_EXPORTS: dict[str, tuple[str, str]] = {
    "Session": ("limulus.session", "Session"),
    "DataStepExecutor": ("limulus.execution", "DataStepExecutor"),
    "FormatSupportResult": ("limulus.execution", "FormatSupportResult"),
    "RuntimeRequirements": ("limulus.execution", "RuntimeRequirements"),
    "DataAdapterError": ("limulus.io_adapters", "DataAdapterError"),
    "DataFrameAdapterPandas": ("limulus.io_adapters", "DataFrameAdapterPandas"),
    "DataInputAdapterArrow": ("limulus.io_adapters", "DataInputAdapterArrow"),
    "DataOutputAdapterArrow": ("limulus.io_adapters", "DataOutputAdapterArrow"),
    "InputSpec": ("limulus.io_adapters", "InputSpec"),
    "OutputSpec": ("limulus.io_adapters", "OutputSpec"),
    "DataStepAst": ("limulus.parser", "DataStepAst"),
    "ParseResult": ("limulus.parser", "ParseResult"),
    "ParsedStatement": ("limulus.parser", "ParsedStatement"),
    "ParserService": ("limulus.parser", "ParserService"),
}


def __getattr__(name: str) -> Any:
    spec = _LAZY_EXPORTS.get(name)
    if spec is None:
        raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
    module_name, attribute_name = spec
    value = getattr(importlib.import_module(module_name), attribute_name)
    globals()[name] = value
    return value


def __dir__() -> list[str]:
    return sorted(set(globals()) | set(_LAZY_EXPORTS))


def _submit_top_level_with_context(
    code: str,
    *,
    backend: str,
    datasets: dict[str, Any],
    host_capture: HostFunctionContextCapture,
) -> SubmitResult:
    """Create the one-shot Session and execute an already-captured request."""

    # Imported here so importing ``limulus`` stays free of Session and the
    # data-processing stack until a top-level call actually uses it.
    from .session import Session

    session = Session(backend=backend)
    for name, data in datasets.items():
        session.load(name, data)
    # Calling Session.submit here would capture this module frame rather than
    # the user's direct caller. The private common entry preserves the one
    # top-level snapshot and avoids a second capture through ``run``.
    return session._submit_with_host_context(
        code,
        backend=None,
        show_result=True,
        host_capture=host_capture,
    )


[docs] def submit( code: str, *, backend: str = "auto", functions: object | None = None, **datasets: Any, ) -> SubmitResult: """One-shot execution (when session management is not needed). Args: code: Data Step DSL text to execute. backend: Runtime backend to use, one of ``"rust"``, ``"python"``, or ``"auto"``. Defaults to ``"auto"``. functions: Optional request-scoped mapping of string names to callables for ``apply``. This keyword is reserved and is not a dataset name. To use a dataset named ``functions``, register it through ``Session.load("functions", table)`` and call ``Session.submit``. Examples: >>> import limulus, pyarrow as pa >>> result = limulus.submit("data out; set inp; run;", inp=pa.table({"x": [1, 2]})) >>> result.success True """ started_frame = inspect.currentframe() caller_frame = None try: caller_frame = started_frame.f_back if started_frame is not None else None caller_locals = dict(caller_frame.f_locals) if caller_frame is not None else {} caller_globals = dict(caller_frame.f_globals) if caller_frame is not None else {} finally: del caller_frame del started_frame host_capture = capture_host_function_context( caller_locals, caller_globals, functions=functions, ) del caller_locals del caller_globals return _submit_top_level_with_context( code, backend=backend, datasets=datasets, host_capture=host_capture, )
[docs] def run( code: str, *, backend: str = "auto", functions: object | None = None, **datasets: Any, ) -> SubmitResult: """Run one-shot code with the same direct-caller and functions contract.""" started_frame = inspect.currentframe() caller_frame = None try: caller_frame = started_frame.f_back if started_frame is not None else None caller_locals = dict(caller_frame.f_locals) if caller_frame is not None else {} caller_globals = dict(caller_frame.f_globals) if caller_frame is not None else {} finally: del caller_frame del started_frame host_capture = capture_host_function_context( caller_locals, caller_globals, functions=functions, ) del caller_locals del caller_globals return _submit_top_level_with_context( code, backend=backend, datasets=datasets, host_capture=host_capture, )
# Public surface __all__ = [ "Session", "submit", "run", "SubmitResult", "LogEntry", "DatasetCatalog", "DataStepExecutor", "FormatSupportResult", "RuntimeRequirements", "DataAdapterError", "DataFrameAdapterPandas", "DataInputAdapterArrow", "DataOutputAdapterArrow", "InputSpec", "OutputSpec", "CompatibilityNotice", "DataSetRef", "Diagnostic", "ExecuteRequest", "ExecuteResponse", "OutputConversionResult", "DataStepAst", "ParseResult", "ParsedStatement", "ParserService", ]