Source code for flexicon.code.Shared.wrapper_base
#
# wrapper_base.py
#
# Class: LCMObjectWrapper
# Base class for wrapping LCM objects with unified interface access.
# Transparently handles casting from base interfaces to concrete types.
#
# Platform: Python.NET
# FieldWorks Version 9+
#
# Copyright 2025
#
"""
Base wrapper class for LCM objects with intelligent property routing.
This module provides LCMObjectWrapper, a base class for creating wrapper objects
that transparently handle the two-layer LCM type system:
- Base Interface: Generic interface typed by pythonnet (e.g., IPhSegmentRule)
- Concrete Type: Actual runtime type identified by ClassName attribute
- Concrete Interface: Type-specific interface with additional properties
The Problem:
In pythonnet, when you access objects from a collection, they're typed as
the base interface. Accessing concrete type-specific properties requires
explicit casting based on the ClassName attribute.
The Solution:
LCMObjectWrapper stores both the base interface and concrete type, then
uses __getattr__() to route property access intelligently:
- Try concrete type first (more specific properties)
- Fall back to base interface if property doesn't exist
- Return None for missing properties instead of raising AttributeError
Example::
from flexlibs2.code.Shared.wrapper_base import LCMObjectWrapper
from flexlibs2.code.lcm_casting import cast_to_concrete
# Wrap an LCM object
rule = phonRuleOps.GetAll()[0] # Typed as IPhSegmentRule
wrapped = LCMObjectWrapper(rule)
# Access type-specific properties transparently
if wrapped.ClassName == 'PhRegularRule':
rhs_count = wrapped.RightHandSidesOS.Count # Works without casting!
# Check what properties are available
common_props = wrapped.get_property('StrucDescOS')
if common_props:
print(f"Rule has {len(common_props)} input contexts")
Usage Notes:
- Wrapper classes inherit from LCMObjectWrapper
- Never access _obj or _concrete directly in subclasses
- Use get_property() for safe access with defaults
- Use class_type property to check the concrete type
"""
from ..lcm_casting import cast_to_concrete
[docs]
class LCMObjectWrapper:
"""
Base wrapper for LCM objects providing unified interface access.
Stores both the base interface and concrete type, routing property access
intelligently to support the two-layer LCM type system transparently.
Attributes:
_obj: The base interface object (e.g., IPhSegmentRule)
_concrete: The concrete type object (e.g., IPhRegularRule)
"""
def __init__(self, lcm_obj):
"""
Initialize wrapper with an LCM object.
Automatically casts the object to its concrete type using the
lcm_casting module. Both base and concrete are stored for
flexible property access.
Args:
lcm_obj: An LCM object with a ClassName attribute.
Typically a base interface type (e.g., IPhSegmentRule,
IMoMorphSynAnalysis, IMoForm).
Example::
rule = phonRuleOps.GetAll()[0]
wrapped = LCMObjectWrapper(rule)
print(wrapped.class_type) # "PhRegularRule" or similar
"""
self._obj = lcm_obj
self._concrete = cast_to_concrete(lcm_obj)
def __getattr__(self, name):
"""
Route property access intelligently across base and concrete types.
When a property is accessed on the wrapper, this method:
1. First tries the concrete type (more specific)
2. Falls back to the base interface if property not found
3. Raises AttributeError only if property doesn't exist on either
This allows seamless access to both common properties (on base interface)
and type-specific properties (on concrete interface) without manual casting.
Args:
name: Property or method name being accessed.
Returns:
The property value or method from whichever type has it.
Raises:
AttributeError: If the property doesn't exist on either type.
Example::
# Access common property on base interface
name = wrapped.Name
# Access type-specific property on concrete interface
if wrapped.ClassName == 'PhRegularRule':
rhs = wrapped.RightHandSidesOS # Concrete type only
# Calling methods works transparently
wrapped.SomeMethod()
"""
# Prevent infinite recursion when accessing _obj or _concrete
if name in ("_obj", "_concrete"):
raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'")
# Try concrete type first (more specific)
try:
return getattr(self._concrete, name)
except AttributeError:
pass
# Fall back to base interface
try:
return getattr(self._obj, name)
except AttributeError:
pass
# Property not found on either type
raise AttributeError(f"'{type(self).__name__}' object and its wrapped LCM object have no attribute '{name}'")
@property
def class_type(self):
"""
Get the concrete class type as a string.
Returns the ClassName attribute, which uniquely identifies the concrete
type of the wrapped object. This is the primary way to check which
concrete interface the object implements.
Returns:
str: The ClassName (e.g., "PhRegularRule", "PhMetathesisRule",
"MoStemMsa", "MoInflAffMsa", etc.)
Example::
wrapped = LCMObjectWrapper(rule)
if wrapped.class_type == 'PhRegularRule':
# Object is a regular phonological rule
pass
elif wrapped.class_type == 'PhMetathesisRule':
# Object is a metathesis rule
pass
"""
return self._obj.ClassName
[docs]
def get_property(self, prop_name, default=None):
"""
Safely get a property value with a fallback default.
Attempts to access a property on the wrapped object, returning
a default value if the property doesn't exist. This is safer than
direct property access when you're unsure whether a property exists
on the wrapped object's type.
Args:
prop_name: Name of the property to access.
default: Value to return if property doesn't exist. Default: None.
Returns:
The property value if it exists, otherwise the default value.
Example::
# Safe access to type-specific properties
rhs = wrapped.get_property('RightHandSidesOS')
if rhs:
print(f"Rule has {rhs.Count} RHS")
# Check for optional properties
frequency = wrapped.get_property('Frequency', 0)
print(f"Rule frequency: {frequency}")
# Provide meaningful defaults
context_count = wrapped.get_property('StrucDescOS', [])
print(f"Input contexts: {len(context_count)}")
"""
try:
return getattr(self, prop_name)
except AttributeError:
return default
def __repr__(self):
"""
String representation showing the wrapped object's class type.
Returns:
str: Representation like "LCMObjectWrapper(PhRegularRule)"
"""
return f"{type(self).__name__}({self.class_type})"
def __str__(self):
"""
Human-readable string representation.
Returns:
str: Description like "Wrapped LCM object of type PhRegularRule"
"""
return f"Wrapped LCM object of type {self.class_type}"