jmri_scripting.api
Public script-authoring API — the shim as an importable module.
The runtime shim's JmriShim.globals() injects sensors,
turnouts, powermanager, etc. into every script's namespace, so
scripts can use them without any import. That's the JMRI-Jython
convention and it's covered.
This module is the authored alternative: scripts (or small helper
modules a user writes) can from jmri_scripting.api import turnouts,
powermanager and reference the same shim managers explicitly. The
extra clarity helps when:
- one script factors reusable code into a helper module and the helper can't rely on the injected globals (module top-level code runs at import time, not exec time);
- an IDE / type-checker resolves imports and needs a real symbol;
- a user prefers explicit-over-implicit imports.
Semantics: each name is a module-level attribute proxy that
resolves via jython_shim._active_shim on every access. So the
same import works across scripts on the same worker thread — each
script binds its shim before exec() and unbinds on teardown.
1"""Public script-authoring API — the shim as an importable module. 2 3The runtime shim's :meth:`JmriShim.globals` injects ``sensors``, 4``turnouts``, ``powermanager``, etc. into every script's namespace, so 5scripts can use them without any import. That's the JMRI-Jython 6convention and it's covered. 7 8This module is the *authored* alternative: scripts (or small helper 9modules a user writes) can ``from jmri_scripting.api import turnouts, 10powermanager`` and reference the same shim managers explicitly. The 11extra clarity helps when: 12 13* one script factors reusable code into a helper module and the helper 14 can't rely on the injected globals (module top-level code runs at 15 import time, not exec time); 16* an IDE / type-checker resolves imports and needs a real symbol; 17* a user prefers explicit-over-implicit imports. 18 19Semantics: each name is a **module-level attribute proxy** that 20resolves via :data:`jython_shim._active_shim` on every access. So the 21same import works across scripts on the same worker thread — each 22script binds its shim before ``exec()`` and unbinds on teardown. 23""" 24from __future__ import annotations 25 26from typing import Any 27 28from jmri_scripting import jython_shim 29 30 31class _ShimProxy: 32 """Attribute proxy that reads *attr* off the active :class:`JmriShim`. 33 34 A single proxy instance is installed at module import time as one 35 of ``sensors``, ``turnouts``, etc. Every attribute access on the 36 proxy is forwarded to the active shim's matching attribute — so a 37 helper module can capture ``turnouts`` at import time and every 38 call still runs against the currently-executing script's shim. 39 40 Trying to use a proxy outside a running script raises the same 41 clear error as :meth:`jython_shim._get_active_shim`. 42 """ 43 44 __slots__ = ("_attr",) 45 46 def __init__(self, attr: str) -> None: 47 self._attr = attr 48 49 def _target(self) -> Any: 50 return getattr(jython_shim._get_active_shim(), self._attr) 51 52 def __getattr__(self, name: str) -> Any: 53 return getattr(self._target(), name) 54 55 def __iter__(self) -> Any: 56 return iter(self._target()) 57 58 def __len__(self) -> int: 59 return len(self._target()) 60 61 def __repr__(self) -> str: 62 try: 63 return f"<api.{self._attr} → {self._target()!r}>" 64 except RuntimeError: 65 return f"<api.{self._attr} (no active script)>" 66 67 68sensors = _ShimProxy("sensors") 69turnouts = _ShimProxy("turnouts") 70lights = _ShimProxy("lights") 71memories = _ShimProxy("memories") 72reporters = _ShimProxy("reporters") 73blocks = _ShimProxy("blocks") 74routes = _ShimProxy("routes") 75signals = _ShimProxy("signals") 76masts = _ShimProxy("masts") 77powermanager = _ShimProxy("powermanager") 78consists = _ShimProxy("consists") 79programmers = _ShimProxy("programmers") 80 81 82# The state-constant enums are just re-exports — they aren't 83# per-script. 84from jmri_core.state import ( # noqa: E402 85 LightState, 86 PowerState, 87 SensorState, 88 ThrottleDirection, 89 TurnoutState, 90) 91 92 93# Bare state constants, matching the injected globals namespace. 94# JMRI Jython scripts commonly do ``t.setCommandedState(THROWN)`` 95# with no explicit import; exposing the same names here means a 96# script that opts into ``from jmri_scripting.api import ...`` can 97# still write the same code. 98ACTIVE = SensorState.ACTIVE 99INACTIVE = SensorState.INACTIVE 100CLOSED = TurnoutState.CLOSED 101THROWN = TurnoutState.THROWN 102ON = LightState.ON 103OFF = LightState.OFF 104FORWARD = ThrottleDirection.FORWARD 105REVERSE = ThrottleDirection.REVERSE 106 107 108__all__ = [ 109 "ACTIVE", 110 "CLOSED", 111 "FORWARD", 112 "INACTIVE", 113 "LightState", 114 "OFF", 115 "ON", 116 "PowerState", 117 "REVERSE", 118 "SensorState", 119 "THROWN", 120 "ThrottleDirection", 121 "TurnoutState", 122 "blocks", 123 "consists", 124 "lights", 125 "masts", 126 "memories", 127 "powermanager", 128 "programmers", 129 "reporters", 130 "routes", 131 "sensors", 132 "signals", 133 "turnouts", 134]
75class LightState(IntEnum): 76 """Matches ``jmri.Light.OFF/ON/INTERMEDIATE``.""" 77 78 UNKNOWN = 1 79 OFF = 2 80 ON = 4 81 INTERMEDIATE = 8
Matches jmri.Light.OFF/ON/INTERMEDIATE.
84class PowerState(IntEnum): 85 """Matches ``jmri.PowerManager.UNKNOWN/ON/OFF/IDLE``.""" 86 87 UNKNOWN = 0 88 ON = 2 89 OFF = 4 90 IDLE = 8
Matches jmri.PowerManager.UNKNOWN/ON/OFF/IDLE.
39class SensorState(IntEnum): 40 """Matches ``jmri.Sensor.UNKNOWN/ACTIVE/INACTIVE/INCONSISTENT``.""" 41 42 UNKNOWN = 1 43 ACTIVE = 2 44 INACTIVE = 4 45 INCONSISTENT = 8
Matches jmri.Sensor.UNKNOWN/ACTIVE/INACTIVE/INCONSISTENT.
102class ThrottleDirection(IntEnum): 103 """Locomotive direction. Matches NMRA convention.""" 104 105 REVERSE = 0 106 FORWARD = 1
Locomotive direction. Matches NMRA convention.
16class TurnoutState(IntEnum): 17 """Matches ``jmri.Turnout.UNKNOWN/CLOSED/THROWN/INCONSISTENT`` Java constants.""" 18 19 UNKNOWN = 1 20 CLOSED = 2 21 THROWN = 4 22 INCONSISTENT = 8
Matches jmri.Turnout.UNKNOWN/CLOSED/THROWN/INCONSISTENT Java constants.