Skip to content

Factory System Quick Reference

Glossary

Term Definition Example
Factory Registry that creates objects from configuration Factory()
Loader Automatically finds classes/functions in modules Loader(template="pkg.{task}")
Parser Transforms config values into Python objects OPTIMIZERS = Factory()
Context Orchestrates factories, loaders, and parsers Context[DataType](...)
BuilderKey Tuple of (task, name) identifying a constructor ("classification", "resnet")
Decorator Function that modifies objects after creation add_normalization(dataset, params)
Template Module path pattern with {task} placeholder "myapp.models.{task}"
Singleton Global instance of a factory/loader DATASETS = Factory()
Constructor Function or class that creates objects ResNetModel or create_resnet()
Wildcard Using None to match any value (None, "adam") matches any task

Common Patterns

Creating a Factory

from ainxt.factory import Factory

# Basic factory
factory = Factory()

# Factory with decorator support
factory = Factory(decorator=my_decorator_factory)

# Combining factories
combined = factory1 + factory2

Registering Constructors

# Register class
factory.register("classification", "resnet", ResNetModel)

# Register function
factory.register(None, "create", lambda x: x * 2)

# Register with wildcards
factory.register(None, "default", DefaultModel)  # Matches any task

Creating a Loader

from ainxt.factory import Loader

loader = Loader(
    template="myapp.{task}.models",     # Module template
    tasks=["nlp", "vision"],            # Tasks to scan
    kind=Model,                         # Type to find
    pattern=r"^[A-Z].*Model$",          # Name pattern
    strict=True                         # Only from this module
)

Setting Up Parsers

# Create parser factory
OPTIMIZERS = Factory()
OPTIMIZERS.register(None, "adam", AdamOptimizer)
OPTIMIZERS.register(None, "sgd", SGDOptimizer)

# Register as parser
PARSERS = {"optimizer": OPTIMIZERS}

# Use in config
config = {
    "optimizer": {
        "name": "adam",
        "learning_rate": 0.001
    }
}

Using Context

from ainxt.scripts.context import Context

# Create context
context = Context[DataType](
    encoder=MyEncoder(),
    decoder=MyDecoder(),
    dataset_builder=DATASETS,
    model_builder=MODELS,
    parsers=PARSERS
)

# Load objects
dataset = context.load_dataset(config)
model = context.load_model(config)

File Organization

project/
├── context.py                 # Main Context definition
├── myapp/
│   ├── data/
│   │   └── datasets/
│   │       ├── __init__.py
│   │       ├── classification.py  # Classification datasets
│   │       └── detection.py       # Detection datasets
│   ├── models/
│   │   ├── __init__.py
│   │   ├── classification.py      # Classification models
│   │   └── detection.py           # Detection models
│   ├── parsers/
│   │   ├── __init__.py
│   │   ├── optimizer.py           # Optimizer parsers
│   │   └── augmentation.py        # Augmentation parsers
│   └── serving/
│       ├── __init__.py
│       └── singletons.py          # Global factories
└── config/
    └── experiments/
        └── example.yaml            # Configuration files

Configuration Structure

Basic Configuration

dataset:
  task: classification
  name: imagenet
  path: /data/imagenet

model:
  task: classification
  name: resnet50
  num_classes: 1000

With Parsers

dataset:
  name: custom_dataset
  # This triggers the augmentation parser
  augmentation:
    name: flip
    probability: 0.5

model:
  name: classifier
  # This triggers the optimizer parser
  optimizer:
    name: adam
    learning_rate: 0.001

With Decorators

dataset:
  name: basic_dataset
  path: /data
  # These trigger decorators
  normalize:
    mean: [0.5, 0.5, 0.5]
  cache: 100

Python Concepts for Beginners

The ** Operator

# Unpacks dictionary as keyword arguments
config = {"a": 1, "b": 2}
func(**config)  # Same as func(a=1, b=2)

The * Operator

# Unpacks list/tuple as positional arguments
args = [1, 2, 3]
func(*args)  # Same as func(1, 2, 3)

Optional Type Hints

from typing import Optional

def func(x: Optional[str] = None):
    # x can be a string or None
    pass

BuilderKey Tuples

# Tuples are immutable pairs
key = ("classification", "resnet")  # Can't be changed
task, name = key  # Unpack tuple

Common Commands

Creating Singletons

from ainxt.serving import (
    create_dataset_factory,
    create_model_factory,
    create_parsers
)

# Create global factories
DATASETS = create_dataset_factory("myapp", tasks)
MODELS = create_model_factory("myapp", tasks)
PARSERS = create_parsers("myapp", register_singular=True)

Combining with Core aiNXT

from ainxt.serving import DATASETS as CORE_DATASETS

# Extend core with your components
DATASETS = CORE_DATASETS + create_dataset_factory("myapp", tasks)

Debugging Factories

# List all registered constructors
for key in factory:
    print(key)

# Check if constructor exists
if ("classification", "resnet") in factory:
    print("ResNet is registered")

# Get constructor directly
constructor = factory[("classification", "resnet")]

Troubleshooting

Problem: "No constructor found"

# Check registration
print(list(factory.keys()))  # See all keys

# Check task/name spelling
factory.build("classificaiton", "resnet")  # Typo!

Problem: "Module not found"

# Verify module exists
import myapp.models.classification  # Test import

# Check template
loader = Loader(template="myapp.models.{task}")  # Correct path?

Problem: Parser not working

# Ensure parser key matches config key
PARSERS = {"optimizer": OPTIMIZERS}  # Key is "optimizer"

config = {
    "optimizer": {...}  # Must match parser key
}

Problem: Wildcard not matching

# Remember None is wildcard
factory.register(None, "default", DefaultClass)  # Matches any task
factory.build("any_task", "default")  # This works!

Advanced Tips

1. Lazy Loading

# Use cached_property for expensive operations
from functools import cached_property

class Loader:
    @cached_property
    def _constructors(self):
        # Only computed once
        return self._scan_modules()

2. Type Safety

from typing import TypeVar

T = TypeVar("T")

class Factory(Generic[T]):
    def build(self, ...) -> T:
        # Return type matches Factory type
        pass

3. Custom Conditions

# Filter constructors with custom logic
def has_gpu_support(constructor):
    return hasattr(constructor, 'cuda_enabled')

loader = Loader(
    template="models.{task}",
    condition=has_gpu_support
)

4. Dynamic Registration

# Register based on available packages
try:
    import torch
    factory.register(None, "torch_model", TorchModel)
except ImportError:
    factory.register(None, "numpy_model", NumpyModel)

Quick Examples

Minimal Factory Setup

from ainxt.factory import Factory

f = Factory()
f.register(None, "test", lambda x: x * 2)
result = f.build(None, "test", x=5)  # Returns 10

Minimal Loader Setup

from ainxt.factory import Loader

loader = Loader(
    template="myapp.components",
    kind=MyBaseClass
)
factory = Factory(loader)

Minimal Parser Setup

PARSERS = {"transform": Factory()}
PARSERS["transform"].register(None, "double", lambda x: x * 2)

config = {"value": 5, "transform": {"name": "double", "x": 5}}
parsed = parse_config(config, PARSERS)  # transform becomes 10

Minimal Context Setup

context = Context(
    dataset_builder=Factory(),
    model_builder=Factory(),
    parsers={}
)

Key Takeaways

  1. Factories are dictionaries of constructors
  2. Loaders find constructors automatically
  3. Parsers transform config to objects
  4. Context brings everything together
  5. None acts as a wildcard in keys
  6. Templates use {task} for dynamic paths
  7. Decorators modify objects after creation
  8. BuilderKeys are (task, name) tuples

Remember: The system is designed to separate configuration from implementation, making experiments easy to define and share!