Generator (Spec Generator)¶
The Generator utility can create apywire specs by introspecting class constructors.
It is useful for scaffolding a spec quickly for use with Wiring in tests or when
bootstrapping an application.
Overview¶
Generator inspects the constructor signature (or a class factory method)
and generates a spec entry for the given type and instance name. For each
constructor parameter it creates:
- A placeholder for the parameter, e.g.
{instance_param} - A constant default value in the spec based on the parameter's type (when available)
- Or a recursive dependency entry if the parameter is another class type
The main entry point is Generator.generate(*entries).
Usage¶
Basic usage:
from apywire import Generator, Wiring
spec = Generator.generate("datetime.datetime now")
wired = Wiring(spec)
now = wired.now()
You can generate multiple entries in a single call:
spec = Generator.generate(
"myapp.models.DateClass dt",
"myapp.models.DeltaClass delta",
)
# Example output (spec dictionary):
expected_spec = {
"myapp.models.DateClass dt": {
"month": "{dt_month}",
"year": "{dt_year}",
},
"myapp.models.DeltaClass delta": {
"days": "{delta_days}",
},
"dt_month": 0,
"dt_year": 0,
"delta_days": 0,
}
Factory methods are supported by adding the factory method to the entry string:
# Class with @classmethod create(cls, x) -> Instance
spec = Generator.generate("myapp.factories.WithFactory obj.create")
# Example output (spec dictionary):
expected_spec = {
"myapp.factories.WithFactory obj.create": {
"x": "{obj_x}",
},
"obj_x": 0,
}
Behavior and Examples¶
- Parameter names are converted into placeholders using the instance name:
a parameter
yearon instancenowbecomes placeholder{now_year}. - Primitive and common types receive a sensible constant default:
int: 0,float: 0.0,str: "",bool: False,bytes: b"",complex: 0j,None: NoneOptional[T](i.e.,Union[T, None]) is resolved to the default forT.- Complex unions default to
None. list,dict,tupledefaults are[],{}, and()respectively.- Unknown or missing annotations default to
None. - Non-constant parameter defaults (e.g. object instances) fall back to type defaults.
- Dependencies (constructor parameters typed as another class) are generated recursively as separate spec entries, unless importing the dependency fails.
- Circular dependencies may raise
apywire.CircularWiringErroror generate partial specs (tests consider both outcomes acceptable). - Built-in types or types whose signatures cannot be inspected may result in an
empty
{}entry for that wiring key.
Example: Simple class
class Simple:
def __init__(self, year: int, month: int, day: int) -> None:
...
spec = Generator.generate("myapp.models.Simple now")
# Example output (spec dictionary):
expected_spec = {
"myapp.models.Simple now": {
"year": "{now_year}",
"month": "{now_month}",
"day": "{now_day}",
},
"now_year": 0,
"now_month": 0,
"now_day": 0,
}
Example: Dependency resolution
class Inner:
def __init__(self, value: int) -> None: ...
class Outer:
def __init__(self, inner: Inner) -> None: ...
spec = Generator.generate("myapp.models.Outer wrapper")
# Example output (spec dictionary):
expected_spec = {
"myapp.models.Inner wrapper_inner": {
"value": "{wrapper_inner_value}",
},
"myapp.models.Outer wrapper": {
"inner": "{wrapper_inner}",
},
"wrapper_inner_value": 0,
}
Example: Typed parameters
class TypedClass:
def __init__(
self,
int_param: int,
str_param: str,
float_param: float,
bool_param: bool,
opt_param: "Optional[str]" = None,
) -> None:
pass
spec = Generator.generate("myapp.types.TypedClass obj")
# Example output (spec dictionary):
expected_spec = {
"myapp.types.TypedClass obj": {
"int_param": "{obj_int_param}",
"str_param": "{obj_str_param}",
"float_param": "{obj_float_param}",
"bool_param": "{obj_bool_param}",
"opt_param": "{obj_opt_param}",
},
"obj_int_param": 0,
"obj_str_param": "",
"obj_float_param": 0.0,
"obj_bool_param": False,
"obj_opt_param": None,
}
Example: Constant defaults preserved
class WithDefaults:
def __init__(self, name: str = "default_name", count: int = 100) -> None:
pass
spec = Generator.generate("myapp.defaults.WithDefaults obj")
# Example output (spec dictionary):
expected_spec = {
"myapp.defaults.WithDefaults obj": {
"count": "{obj_count}",
"name": "{obj_name}",
},
"obj_count": 100,
"obj_name": "default_name",
}
Example: List and dict types
class Container:
def __init__(self, items: "List[int]", mapping: "Dict[str, int]") -> None:
pass
spec = Generator.generate("myapp.containers.Container c")
# Example output (spec dictionary):
expected_spec = {
"myapp.containers.Container c": {
"items": "{c_items}",
"mapping": "{c_mapping}",
},
"c_items": [],
"c_mapping": {},
}
API¶
Generator.generate(*entries: str) -> apywire.Spec¶
Arguments:
- entries: Strings in the format module.Class name or
module.Class name.factoryMethod.
Returns:
- A Spec dictionary ready for Wiring.
Raises:
- ValueError for invalid entry strings (e.g., missing delimiter or module)
- apywire.exceptions.CircularWiringError when a cycle is detected
Notes and Limitations¶
- The generator bases decisions on constructor type annotations; without them
the generator cannot reliably infer dependencies and will use
None. - For classes that use complex, programmatic defaults or runtime logic, the generator provides initial defaults but you should always review and adjust generated specs to fit runtime requirements.
- The generated spec is a suggestion and can be altered before passing to
Wiring. It's useful for tests and bootstrapping but not intended to fully replace manual configuration in production.
See Also¶
Wiring(docs/user-guide/basic-usage.md)apywire.Compilerand the compiled wiring docs (user-guide/compilation.md)