mixin.registry

Mixin Function Registry for AxisFuzzy core class extensions.

This module provides the infrastructure for registering and dynamically injecting mtype-agnostic structural operations into axisfuzzy.core.fuzznums.Fuzznum and axisfuzzy.core.fuzzarray.Fuzzarray classes, as well as the top-level axisfuzzy namespace.

The registry system enables a clean separation between core class definitions and extended functionality, allowing for modular development while maintaining a cohesive user interface.

Architecture Overview

The mixin system follows a three-phase lifecycle:

  1. Registration Phase: Functions are registered with metadata specifying their injection targets and exposure types using the register_mixin() decorator.

  2. Storage Phase: The MixinFunctionRegistry stores registered functions and their associated metadata in internal dictionaries.

  3. Injection Phase: During library initialization, MixinFunctionRegistry.build_and_inject() dynamically attaches registered functions to target classes and the module namespace.

Key Differences from Extension System

The mixin system differs from axisfuzzy.extension in several fundamental ways:

  • Scope: Mixin functions are mtype-agnostic and focus on structural operations (reshape, transpose, concatenate) that work uniformly across all fuzzy number types.

  • Dispatch: No runtime mtype-based dispatch is needed; all functions work the same way regardless of the underlying fuzzy number type.

  • Use Cases: Primarily for NumPy-like array manipulation and container operations.

Injection Types

Functions can be exposed in three different ways:

  • 'instance_function': Injected as bound methods on target classes (e.g., my_fuzzarray.reshape(2, 3)).

  • 'top_level_function': Injected into the module namespace (e.g., axisfuzzy.reshape(my_fuzzarray, 2, 3)).

  • 'both': Exposed as both instance methods and top-level functions.

Thread Safety

The registry is not inherently thread-safe during the registration and injection phases. However, since these operations typically occur during module initialization (import time), this is generally not a concern in practice.

See also

axisfuzzy.mixin.factory

Implementation layer for mixin operations.

axisfuzzy.mixin.register

Registration declarations for standard operations.

axisfuzzy.extension

mtype-sensitive extension system for specialized operations.

Examples

Registering a simple mixin function:

from axisfuzzy.mixin.registry import register_mixin

@register_mixin(name='is_empty', target_classes=['Fuzzarray'],
                injection_type='instance_function')
def _is_empty_impl(self):
    return self.size == 0

# After library initialization:
# arr = fuzzarray([...])
# arr.is_empty()  # True/False

Registering a function as both instance method and top-level function:

@register_mixin(name='normalize_shape', target_classes=['Fuzzarray'],
                injection_type='both')
def _normalize_shape_impl(obj):
    return obj.reshape(-1)

# After library initialization:
# arr.normalize_shape()  # instance method
# axisfuzzy.normalize_shape(arr)  # top-level function

Manual injection during initialization:

from axisfuzzy.mixin.registry import get_registry_mixin
from axisfuzzy.core import Fuzznum, Fuzzarray

registry = get_registry_mixin()
class_map = {'Fuzznum': Fuzznum, 'Fuzzarray': Fuzzarray}
module_globals = globals()

registry.build_and_inject(class_map, module_globals)
class axisfuzzy.mixin.registry.MixinFunctionRegistry[source]

Bases: object

Central registry for mtype-agnostic functions extending AxisFuzzy core classes.

This registry manages the registration, storage, and injection of functions that provide NumPy-like structural operations for Fuzznum and Fuzzarray objects. Unlike the extension system, mixin functions work uniformly across all fuzzy number types without requiring dispatch logic.

The registry supports three injection modes: instance methods, top-level functions, or both. It ensures that the extended functionality integrates seamlessly with the existing class interfaces.

_functions

Maps function names to their implementation callables.

Type:

dict of str to callable

_metadata

Maps function names to their registration metadata, including target classes and injection preferences.

Type:

dict of str to dict

Notes

  • The registry is designed as a singleton accessed via get_registry_mixin().

  • Registration typically occurs at module import time via decorators.

  • Injection happens once during library initialization.

See also

register_mixin

Convenience decorator for function registration.

get_registry_mixin

Access to the global registry singleton.

build_and_inject(class_map, module_namespace)[source]

Inject all registered functions into target classes and module namespace.

This method performs the final injection phase, iterating through all registered functions and attaching them to their specified targets based on the injection metadata. It handles both instance method injection (via setattr() on classes) and top-level function injection (via dictionary assignment on the module namespace).

Parameters:
  • class_map (dict of str to type) – Maps class names to actual class objects for instance method injection. Typically constructed as {'Fuzznum': Fuzznum, 'Fuzzarray': Fuzzarray}.

  • module_namespace (dict of str to any) – Target module’s namespace (usually globals() of the target module) where top-level functions should be injected. Functions are added as key-value pairs to this dictionary.

Notes

  • Instance methods are injected using setattr(class, name, function)

  • Top-level functions use different strategies for ‘both’ vs ‘top_level_function’ only:

    • ‘both’: Creates a wrapper that delegates to the instance method

    • ‘top_level_function’: Injects the original function directly

  • Injection is idempotent but not thread-safe

  • Missing classes in class_map are silently ignored

Examples

Typical usage during library initialization:

from axisfuzzy.core import Fuzznum, Fuzzarray
from axisfuzzy.mixin.registry import get_registry_mixin

# Prepare injection targets
class_map = {
    'Fuzznum': Fuzznum,
    'Fuzzarray': Fuzzarray
}
module_globals = globals()

# Perform injection
registry = get_registry_mixin()
registry.build_and_inject(class_map, module_globals)

# Now functions are available:
# arr = Fuzzarray(...)
# arr.reshape(2, 2)  # instance method
# reshape(arr, 2, 2)  # top-level function

Custom class mapping:

# Only inject into Fuzzarray
class_map = {'Fuzzarray': Fuzzarray}
registry.build_and_inject(class_map, {})
get_top_level_function_names()[source]

Get names of all functions registered for top-level injection.

This method scans the registry metadata and returns a list of function names that should be exposed as top-level functions. It’s useful for populating __all__ lists and documentation generation.

Returns:

Sorted list of function names that have injection_type of ‘top_level_function’ or ‘both’.

Return type:

list of str

Examples

registry = get_registry_mixin()

# Register some functions
@registry.register('func1', injection_type='instance_function',
                  target_classes=['Fuzznum'])
def f1(self): pass

@registry.register('func2', injection_type='top_level_function')
def f2(): pass

@registry.register('func3', injection_type='both',
                  target_classes=['Fuzzarray'])
def f3(self): pass

names = registry.get_top_level_function_names()
print(names)  # ['func2', 'func3']

Use in module __all__ definition:

from axisfuzzy.mixin.registry import get_registry_mixin

# Get all mixin top-level functions
_mixin_functions = get_registry_mixin().get_top_level_function_names()

# Combine with other exports
__all__ = ['Fuzznum', 'Fuzzarray'] + _mixin_functions
register(name, target_classes=None, injection_type='both')[source]

Decorator factory to register a function for dynamic injection.

This method provides the core registration mechanism for mixin functions. It validates registration parameters, stores the function and its metadata, and returns a decorator that can be applied to the implementation function.

Parameters:
  • name (str) – Unique identifier for the function within the registry. This name will be used as the method/function name after injection.

  • target_classes (list of str, optional) – Class names where the function should be injected as an instance method. Required when injection_type is ‘instance_function’ or ‘both’. Common values are [‘Fuzznum’], [‘Fuzzarray’], or [‘Fuzznum’, ‘Fuzzarray’].

  • injection_type ({'instance_function', 'top_level_function', 'both'}, default 'both') –

    Specifies how the function should be exposed:

    • ’instance_function’: Only as a bound method on target classes

    • ’top_level_function’: Only in the module namespace

    • ’both’: As both instance methods and top-level functions

Returns:

A decorator function that accepts the implementation and registers it.

Return type:

callable

Raises:

ValueError

  • If injection_type is not one of the allowed values - If target_classes is None when instance injection is requested - If a function with the same name is already registered

Examples

Register an instance-only method:

@registry.register('is_normalized',
                  target_classes=['Fuzznum', 'Fuzzarray'],
                  injection_type='instance_function')
def _is_normalized_impl(self):
    # Implementation logic
    return True

Register a top-level-only function:

@registry.register('create_identity', injection_type='top_level_function')
def _create_identity_impl(mtype='qrofn'):
    # Implementation logic
    return Fuzznum(mtype).create(md=1.0, nmd=0.0)

Register both instance method and top-level function:

@registry.register('to_list', target_classes=['Fuzzarray'], injection_type='both')
def _to_list_impl(self):
    # Works as both arr.to_list() and axisfuzzy.to_list(arr)
    return list(self)
axisfuzzy.mixin.registry.get_registry_mixin()[source]

Access the global singleton MixinFunctionRegistry instance.

This function provides the standard entry point to the mixin registry system. It returns the same registry instance across all calls, ensuring consistent registration and injection behavior throughout the library.

Returns:

The global singleton registry instance.

Return type:

MixinFunctionRegistry

Notes

The registry is created once when this module is first imported and reused for all subsequent calls. This singleton pattern ensures that all registered functions are stored in the same location and available for injection.

Examples

Basic registry access:

from axisfuzzy.mixin.registry import get_registry_mixin

registry = get_registry_mixin()
# Use registry.register(...) to register functions

Use in registration modules:

# In axisfuzzy/mixin/register.py or similar
from axisfuzzy.mixin.registry import get_registry_mixin

registry = get_registry_mixin()

@registry.register('my_function', target_classes=['Fuzzarray'])
def _my_function_impl(self):
    return self.copy()

Use in library initialization:

# In axisfuzzy/__init__.py
from axisfuzzy.mixin.registry import get_registry_mixin
from axisfuzzy.core import Fuzznum, Fuzzarray

# Inject all registered mixin functions
registry = get_registry_mixin()
class_map = {'Fuzznum': Fuzznum, 'Fuzzarray': Fuzzarray}
registry.build_and_inject(class_map, globals())
axisfuzzy.mixin.registry.register_mixin(name, target_classes=None, injection_type='both')[source]

Convenience decorator for registering mixin functions.

This function provides a streamlined interface to the global mixin registry, eliminating the need to explicitly access the registry instance. It’s the recommended way to register mixin functions in most scenarios.

Parameters:
  • name (str) – Unique function name for registry and injection.

  • target_classes (list of str, optional) – Class names for instance method injection. Required for ‘instance_function’ and ‘both’ injection types.

  • injection_type ({'instance_function', 'top_level_function', 'both'}, default 'both') – How the function should be exposed after injection.

Returns:

Decorator function that registers the implementation.

Return type:

callable

Raises:

ValueError – If parameters are invalid or if the function name is already registered.

Examples

Register an instance method:

from axisfuzzy.mixin.registry import register_mixin

@register_mixin('is_square', target_classes=['Fuzzarray'],
                injection_type='instance_function')
def _is_square_impl(self):
    return len(set(self.shape)) <= 1

Register a top-level function:

@register_mixin('zeros_like', injection_type='top_level_function')
def _zeros_like_impl(template):
    # Create zero array with same shape and mtype as template
    return Fuzzarray(shape=template.shape, mtype=template.mtype)

Register both instance method and top-level function:

@register_mixin('flatten', target_classes=['Fuzzarray'], injection_type='both')
def _flatten_impl(self):
    # Available as both arr.flatten() and axisfuzzy.flatten(arr)
    return self.reshape(-1)

See also

MixinFunctionRegistry.register

The underlying registration method.

get_registry_mixin

Access to the global registry instance.