jmri_core.bean
NamedBean — the universal interface every JMRI domain object satisfies.
A bean has:
- an immutable
system_name— globally unique within a manager, formed of a connection prefix (e.g."L"for LocoNet) plus a type letter (e.g."T"for turnout) plus the system-specific address ("12"); - an optional
user_name— a human-friendly label; - a
comment— free-form; - a
state_changedSignalthat fires on any state mutation; - a generic
propertiesdict for ad-hoc extensions.
The NamedBean Protocol is structural; any object with the right shape
satisfies it. AbstractNamedBean is the canonical implementation
domain beans inherit.
1"""``NamedBean`` — the universal interface every JMRI domain object satisfies. 2 3A bean has: 4 * an immutable ``system_name`` — globally unique within a manager, formed of 5 a connection prefix (e.g. ``"L"`` for LocoNet) plus a type letter (e.g. 6 ``"T"`` for turnout) plus the system-specific address (``"12"``); 7 * an optional ``user_name`` — a human-friendly label; 8 * a ``comment`` — free-form; 9 * a ``state_changed`` :class:`Signal` that fires on any state mutation; 10 * a generic ``properties`` dict for ad-hoc extensions. 11 12The :class:`NamedBean` Protocol is structural; any object with the right shape 13satisfies it. :class:`AbstractNamedBean` is the canonical implementation 14domain beans inherit. 15""" 16from __future__ import annotations 17 18from typing import Any, Protocol, runtime_checkable 19 20from jmri_core.events import Signal 21 22 23@runtime_checkable 24class NamedBean(Protocol): 25 """Structural protocol for any JMRI domain object.""" 26 27 system_name: str 28 user_name: str | None 29 comment: str | None 30 state_changed: Signal[Any] 31 32 33class AbstractNamedBean: 34 """Concrete mixin providing the standard ``NamedBean`` machinery. 35 36 Subclasses extend this and add their state attributes + transition logic. 37 """ 38 39 __slots__ = ("comment", "properties", "state_changed", "system_name", "user_name") 40 41 def __init__(self, system_name: str, user_name: str | None = None) -> None: 42 if not system_name: 43 raise ValueError("system_name must be a non-empty string") 44 self.system_name = system_name 45 self.user_name = user_name 46 self.comment: str | None = None 47 self.state_changed: Signal[Any] = Signal() 48 self.properties: dict[str, Any] = {} 49 50 @property 51 def display_name(self) -> str: 52 """``user_name`` if set, else ``system_name``. Mirrors JMRI's getDisplayName().""" 53 return self.user_name if self.user_name else self.system_name 54 55 def __repr__(self) -> str: 56 return f"{type(self).__name__}({self.system_name!r})" 57 58 def __eq__(self, other: object) -> bool: 59 # Equality by (type, system_name) — JMRI manager invariant. 60 if type(self) is not type(other): 61 return False 62 return self.system_name == other.system_name 63 64 def __hash__(self) -> int: 65 return hash((type(self).__name__, self.system_name))
@runtime_checkable
class
NamedBean24@runtime_checkable 25class NamedBean(Protocol): 26 """Structural protocol for any JMRI domain object.""" 27 28 system_name: str 29 user_name: str | None 30 comment: str | None 31 state_changed: Signal[Any]
Structural protocol for any JMRI domain object.
NamedBean(*args, **kwargs)
1945def _no_init_or_replace_init(self, *args, **kwargs): 1946 cls = type(self) 1947 1948 if cls._is_protocol: 1949 raise TypeError('Protocols cannot be instantiated') 1950 1951 # Already using a custom `__init__`. No need to calculate correct 1952 # `__init__` to call. This can lead to RecursionError. See bpo-45121. 1953 if cls.__init__ is not _no_init_or_replace_init: 1954 return 1955 1956 # Initially, `__init__` of a protocol subclass is set to `_no_init_or_replace_init`. 1957 # The first instantiation of the subclass will call `_no_init_or_replace_init` which 1958 # searches for a proper new `__init__` in the MRO. The new `__init__` 1959 # replaces the subclass' old `__init__` (ie `_no_init_or_replace_init`). Subsequent 1960 # instantiation of the protocol subclass will thus use the new 1961 # `__init__` and no longer call `_no_init_or_replace_init`. 1962 for base in cls.__mro__: 1963 init = base.__dict__.get('__init__', _no_init_or_replace_init) 1964 if init is not _no_init_or_replace_init: 1965 cls.__init__ = init 1966 break 1967 else: 1968 # should not happen 1969 cls.__init__ = object.__init__ 1970 1971 cls.__init__(self, *args, **kwargs)
state_changed: jmri_core.Signal[typing.Any]
class
AbstractNamedBean:
34class AbstractNamedBean: 35 """Concrete mixin providing the standard ``NamedBean`` machinery. 36 37 Subclasses extend this and add their state attributes + transition logic. 38 """ 39 40 __slots__ = ("comment", "properties", "state_changed", "system_name", "user_name") 41 42 def __init__(self, system_name: str, user_name: str | None = None) -> None: 43 if not system_name: 44 raise ValueError("system_name must be a non-empty string") 45 self.system_name = system_name 46 self.user_name = user_name 47 self.comment: str | None = None 48 self.state_changed: Signal[Any] = Signal() 49 self.properties: dict[str, Any] = {} 50 51 @property 52 def display_name(self) -> str: 53 """``user_name`` if set, else ``system_name``. Mirrors JMRI's getDisplayName().""" 54 return self.user_name if self.user_name else self.system_name 55 56 def __repr__(self) -> str: 57 return f"{type(self).__name__}({self.system_name!r})" 58 59 def __eq__(self, other: object) -> bool: 60 # Equality by (type, system_name) — JMRI manager invariant. 61 if type(self) is not type(other): 62 return False 63 return self.system_name == other.system_name 64 65 def __hash__(self) -> int: 66 return hash((type(self).__name__, self.system_name))
Concrete mixin providing the standard NamedBean machinery.
Subclasses extend this and add their state attributes + transition logic.
AbstractNamedBean(system_name: str, user_name: str | None = None)
42 def __init__(self, system_name: str, user_name: str | None = None) -> None: 43 if not system_name: 44 raise ValueError("system_name must be a non-empty string") 45 self.system_name = system_name 46 self.user_name = user_name 47 self.comment: str | None = None 48 self.state_changed: Signal[Any] = Signal() 49 self.properties: dict[str, Any] = {}
state_changed: jmri_core.Signal[typing.Any]