Source code for flexicon.code.Grammar.affix_template
#
# affix_template.py
#
# Class: AffixTemplate
# Wrapper for affix template objects providing unified interface
# access to template properties and slots.
#
# Platform: Python.NET
# FieldWorks Version 9+
#
# Copyright 2025
#
"""
Wrapper class for affix template objects with unified interface.
This module provides AffixTemplate, a wrapper class that transparently
handles affix template objects (MoInflAffixTemplate) providing convenient
access to template properties and slots.
Problem:
Affix templates contain multiple slot collections and properties that
require navigation through the LCM object hierarchy. Users working with
templates need convenient access to:
- Basic properties (name, description, stratum, disabled)
- Slot collections (prefix, suffix, proclitic, enclitic)
- Convenience properties (slot counts, capabilities)
Solution:
AffixTemplate wrapper provides:
- Simple properties for common features (name, description, stratum)
- Direct access to slot collections
- Convenience properties for slot counts and checks
- Capability check properties (has_prefix_slots, etc.)
- Property access that works transparently
Example::
from flexlibs2.code.Grammar.affix_template import AffixTemplate
# Wrap a template from GetAll()
template = morphRuleOps.GetAllAffixTemplates()[0]
wrapped = AffixTemplate(template)
# Access common properties
print(wrapped.name) # Template name
print(wrapped.description) # Template description
print(wrapped.stratum) # Stratum reference
# Check capabilities
if wrapped.has_prefix_slots:
print(f"Template has {wrapped.prefix_slot_count} prefix slots")
# Access slots directly
for slot in wrapped.prefix_slots:
print(f"Slot: {slot.Name}")
"""
from ..Shared.wrapper_base import LCMObjectWrapper
from ..lcm_casting import cast_to_concrete
try:
from SIL.LCModel import IPartOfSpeech, PartOfSpeechTags
except ImportError:
# Mock for testing without FieldWorks installed
IPartOfSpeech = None
class PartOfSpeechTags:
kClassId = 0
[docs]
class AffixTemplate(LCMObjectWrapper):
"""
Wrapper for affix template objects providing unified interface access.
Handles MoInflAffixTemplate objects transparently, providing common
properties and convenience methods without exposing ClassName or casting.
Attributes:
_obj: The base interface object (IMoInflAffixTemplate)
_concrete: The concrete type object (IMoInflAffixTemplate)
Example::
template = morphRuleOps.GetAllAffixTemplates()[0]
wrapped = AffixTemplate(template)
print(wrapped.name)
print(wrapped.prefix_slot_count)
if wrapped.has_suffix_slots:
print(f"Has {wrapped.suffix_slot_count} suffix slots")
"""
def __init__(self, lcm_template):
"""
Initialize AffixTemplate wrapper with a template object.
Args:
lcm_template: An IMoInflAffixTemplate object.
Typically from MorphRuleOperations.GetAllAffixTemplates().
Example::
template = morphRuleOps.GetAllAffixTemplates()[0]
wrapped = AffixTemplate(template)
"""
super().__init__(lcm_template)
# ========== Common Properties (basic template information) ==========
@property
def name(self) -> str:
"""
Get the template's name.
Returns:
str: The template name, or empty string if not set.
Example::
print(f"Template: {wrapped.name}")
"""
from SIL.LCModel.Core.KernelInterfaces import ITsString
name_multistring = self._obj.Name
if not name_multistring:
return ""
default_ws = self._obj.Cache.DefaultAnalWs
name_text = ITsString(name_multistring.get_String(default_ws)).Text
return name_text or ""
@property
def description(self):
"""
Get the template's description.
Returns:
str: The template description, or empty string if not set.
Example::
print(f"Description: {wrapped.description}")
"""
from SIL.LCModel.Core.KernelInterfaces import ITsString
desc_multistring = self._obj.Description
if not desc_multistring:
return ""
default_ws = self._obj.Cache.DefaultAnalWs
desc_text = ITsString(desc_multistring.get_String(default_ws)).Text
return desc_text or ""
@property
def stratum(self):
"""
Get the template's stratum reference.
Returns:
IMoStratum or None: The phonological stratum that owns this template, or None if not set.
Example::
if wrapped.stratum:
print(f"Stratum: {wrapped.stratum.Name}")
"""
try:
if hasattr(self._concrete, "StratumRA"):
return self._concrete.StratumRA
return None
except Exception:
return None
@property
def disabled(self):
"""
Check if the template is disabled.
Returns:
bool: True if the template is disabled, False otherwise.
Example::
if wrapped.disabled:
print("Template is disabled")
"""
try:
if hasattr(self._concrete, "Disabled"):
return self._concrete.Disabled
return False
except Exception:
return False
# ========== Slot Properties (access to slot collections) ==========
@property
def prefix_slots(self) -> "list[object]":
"""
Get the prefix slots collection.
Returns:
List or collection of prefix slot objects, or empty list if none.
Example::
for slot in wrapped.prefix_slots:
print(f"Prefix slot: {slot.Name}")
Notes:
- Access via PrefixSlotsRS from the concrete interface
- Returns empty list if slots not available
"""
try:
if hasattr(self._concrete, "PrefixSlotsRS"):
slots = self._concrete.PrefixSlotsRS
return list(slots) if slots else []
return []
except Exception:
return []
@property
def suffix_slots(self):
"""
Get the suffix slots collection.
Returns:
List or collection of suffix slot objects, or empty list if none.
Example::
for slot in wrapped.suffix_slots:
print(f"Suffix slot: {slot.Name}")
Notes:
- Access via SuffixSlotsRS from the concrete interface
- Returns empty list if slots not available
"""
try:
if hasattr(self._concrete, "SuffixSlotsRS"):
slots = self._concrete.SuffixSlotsRS
return list(slots) if slots else []
return []
except Exception:
return []
@property
def proclitic_slots(self):
"""
Get the proclitic slots collection.
Returns:
List or collection of proclitic slot objects, or empty list if none.
Example::
for slot in wrapped.proclitic_slots:
print(f"Proclitic slot: {slot.Name}")
Notes:
- Access via ProcliticSlotsRS from the concrete interface
- Returns empty list if slots not available
"""
try:
if hasattr(self._concrete, "ProcliticSlotsRS"):
slots = self._concrete.ProcliticSlotsRS
return list(slots) if slots else []
return []
except Exception:
return []
@property
def enclitic_slots(self):
"""
Get the enclitic slots collection.
Returns:
List or collection of enclitic slot objects, or empty list if none.
Example::
for slot in wrapped.enclitic_slots:
print(f"Enclitic slot: {slot.Name}")
Notes:
- Access via EncliticSlotsRS from the concrete interface
- Returns empty list if slots not available
"""
try:
if hasattr(self._concrete, "EncliticSlotsRS"):
slots = self._concrete.EncliticSlotsRS
return list(slots) if slots else []
return []
except Exception:
return []
# ========== Convenience Properties (counts and totals) ==========
@property
def prefix_slot_count(self):
"""
Get the number of prefix slots.
Returns:
int: Number of prefix slots (0 if none).
Example::
print(f"Prefix slots: {wrapped.prefix_slot_count}")
"""
try:
return len(self.prefix_slots)
except Exception:
return 0
@property
def suffix_slot_count(self):
"""
Get the number of suffix slots.
Returns:
int: Number of suffix slots (0 if none).
Example::
print(f"Suffix slots: {wrapped.suffix_slot_count}")
"""
try:
return len(self.suffix_slots)
except Exception:
return 0
@property
def proclitic_slot_count(self):
"""
Get the number of proclitic slots.
Returns:
int: Number of proclitic slots (0 if none).
Example::
print(f"Proclitic slots: {wrapped.proclitic_slot_count}")
"""
try:
return len(self.proclitic_slots)
except Exception:
return 0
@property
def enclitic_slot_count(self):
"""
Get the number of enclitic slots.
Returns:
int: Number of enclitic slots (0 if none).
Example::
print(f"Enclitic slots: {wrapped.enclitic_slot_count}")
"""
try:
return len(self.enclitic_slots)
except Exception:
return 0
@property
def total_slots(self):
"""
Get the total number of slots across all types.
Returns the sum of prefix, suffix, proclitic, and enclitic slots.
Returns:
int: Total slot count (0 if no slots).
Example::
print(f"Total slots: {wrapped.total_slots}")
"""
try:
return (
self.prefix_slot_count + self.suffix_slot_count + self.proclitic_slot_count + self.enclitic_slot_count
)
except Exception:
return 0
@property
def owner_pos(self):
"""
Get the owner Part of Speech for this template.
Convenience property to access the POS that owns this template.
Returns:
IPartOfSpeech or None: The owner POS, or None if not available.
Example::
if wrapped.owner_pos:
print(f"Template for POS: {wrapped.owner_pos.Name}")
"""
# OwnerOfClass is an LCM method requiring an Int32 class ID argument.
# The previous code read it as a bare attribute (returning the bound
# method object) and the hasattr guard was always True (every ICmObject
# has OwnerOfClass), so the caller always received a method object
# instead of an IPartOfSpeech. (Pattern G, issue #152)
if not hasattr(self._concrete, "OwnerOfClass"):
return None
pos_lcm = self._concrete.OwnerOfClass(PartOfSpeechTags.kClassId)
if pos_lcm is None:
return None
return IPartOfSpeech(pos_lcm) if IPartOfSpeech is not None else pos_lcm
# ========== Capability Checks (for slot-specific operations) ==========
@property
def has_prefix_slots(self):
"""
Check if this template has prefix slots.
Returns:
bool: True if template has at least one prefix slot.
Example::
if wrapped.has_prefix_slots:
print(f"Template has {wrapped.prefix_slot_count} prefix slots")
"""
return self.prefix_slot_count > 0
@property
def has_suffix_slots(self):
"""
Check if this template has suffix slots.
Returns:
bool: True if template has at least one suffix slot.
Example::
if wrapped.has_suffix_slots:
print(f"Template has {wrapped.suffix_slot_count} suffix slots")
"""
return self.suffix_slot_count > 0
@property
def has_proclitic_slots(self):
"""
Check if this template has proclitic slots.
Returns:
bool: True if template has at least one proclitic slot.
Example::
if wrapped.has_proclitic_slots:
print(f"Template has {wrapped.proclitic_slot_count} proclitic slots")
"""
return self.proclitic_slot_count > 0
@property
def has_enclitic_slots(self):
"""
Check if this template has enclitic slots.
Returns:
bool: True if template has at least one enclitic slot.
Example::
if wrapped.has_enclitic_slots:
print(f"Template has {wrapped.enclitic_slot_count} enclitic slots")
"""
return self.enclitic_slot_count > 0
@property
def has_any_slots(self):
"""
Check if this template has any slots at all.
Returns:
bool: True if template has at least one slot of any type.
Example::
if wrapped.has_any_slots:
print(f"Template has {wrapped.total_slots} total slots")
"""
return self.total_slots > 0
# ========== Advanced: Direct C# class access (optional for power users) ==========
@property
def concrete(self):
"""
Get the raw concrete interface object.
For advanced users who need to access the underlying C# interface
directly without going through wrapper properties.
Returns:
The concrete interface object (IMoInflAffixTemplate).
Example::
# Direct access to concrete interface
concrete = template_obj.concrete
prefix_slots = concrete.PrefixSlotsRS
Notes:
- For power users only
- Bypasses the wrapper's abstraction
- Normal users should prefer wrapper properties like
has_prefix_slots, prefix_slot_count, etc.
"""
return self._concrete
def __repr__(self):
"""String representation showing template name."""
return f"AffixTemplate({self.name or 'Unnamed'})"
def __str__(self):
"""Human-readable description."""
if self.name:
return f"Template '{self.name}' ({self.total_slots} slots)"
return f"Unnamed template ({self.total_slots} slots)"