"""
Parametric Axes Prototype - Arbitrary semantic axes for type construction.
This module prototypes extending type() to support arbitrary axes beyond (B, S).
Proof-of-concept for PEP proposal: extending type() with axes= parameter.
In the real CPython implementation, this logic would be in type.__new__ itself,
and every class would have __axes__ by default (empty MappingProxyType).
This prototype uses AxesMeta as a stand-in for what type() would do natively.
Once any class uses metaclass=AxesMeta, all subclasses inherit it automatically.
Usage:
from objectstate.parametric_axes import AxesMeta
# One class in hierarchy uses metaclass=AxesMeta
class Step(metaclass=AxesMeta):
pass
# All subclasses automatically get axes support (metaclass inherited)
class MyStep(Step, axes={"scope": "/pipeline/step_0"}):
pass
MyStep.__axes__["scope"] # "/pipeline/step_0"
# Per-key MRO inheritance works automatically
class ChildStep(MyStep, axes={"priority": 1}):
pass
ChildStep.__axes__["scope"] # "/pipeline/step_0" (inherited)
ChildStep.__axes__["priority"] # 1 (defined here)
"""
from typing import Dict, Any, Tuple, Optional, Callable
from types import MappingProxyType
import weakref
# =============================================================================
# TYPE CACHE - Same pattern as reified_generics
# =============================================================================
_axes_cache: Dict[Tuple[type, tuple, tuple], type] = {}
def _cache_key(origin: type, bases: tuple, axes: dict) -> tuple:
"""Create hashable cache key from axes dict."""
# Sort axes for consistent hashing
axes_tuple = tuple(sorted(axes.items()))
return (origin, bases, axes_tuple)
# =============================================================================
# AXES METACLASS
# =============================================================================
# =============================================================================
# DECORATOR + BASE CLASS (used by tests / ergonomic API)
# =============================================================================
[docs]
def with_axes(**axes: Any) -> Callable[[type], type]:
"""Class decorator that attaches axes metadata.
The decorated class is recreated using :class:`AxesMeta` so it gains:
- ``__axes__`` (MappingProxyType)
- convenience attributes (``__scope__``, ``__priority__``, etc.)
"""
def _decorator(cls: type) -> type:
# Recreate class using AxesMeta. Filter out internal descriptors.
namespace = {
k: v
for k, v in cls.__dict__.items()
if k not in {"__dict__", "__weakref__"}
}
return AxesMeta(cls.__name__, cls.__bases__, namespace, axes=axes)
return _decorator
[docs]
class AxesBase(metaclass=AxesMeta):
"""Opt-in base class enabling ``class Foo(AxesBase, axes={...})`` syntax."""
pass
# =============================================================================
# FACTORY FUNCTION - Mimics extended type() signature
# =============================================================================
[docs]
def axes_type(name: str, bases: tuple, namespace: dict, **axes) -> type:
"""
Create a type with arbitrary axes - prototype for extended type().
This mimics the proposed signature:
type(name, bases, namespace, **axes)
Args:
name: Class name
bases: Base classes tuple
namespace: Class namespace dict
**axes: Arbitrary axes (scope=, registry=, version=, etc.)
Returns:
New type with __axes__ containing all axis values
Example:
MyStep = axes_type("MyStep", (Step,), {"process": fn},
scope="/pipeline/step_0",
registry=step_registry)
MyStep.__axes__["scope"] # "/pipeline/step_0"
MyStep.__scope__ # "/pipeline/step_0" (convenience)
"""
return AxesMeta(name, bases, namespace, axes=axes)
# =============================================================================
# UTILITY FUNCTIONS
# =============================================================================
[docs]
def get_axes(cls: type) -> Dict[str, Any]:
"""Get all axes from a type."""
return getattr(cls, '__axes__', {})
[docs]
def get_axis(cls: type, name: str, default: Any = None) -> Any:
"""Get a specific axis value."""
return get_axes(cls).get(name, default)
[docs]
def has_axis(cls: type, name: str) -> bool:
"""Check if type has a specific axis."""
return name in get_axes(cls)