1919 SYNTHETIC_CONST ,
2020)
2121from apywire .wiring import (
22+ Spec ,
2223 WiringBase ,
2324 _ConstantValue ,
2425 _ResolvedSpecMapping ,
4041class WiringCompiler (WiringBase ):
4142 """Wiring container with compilation support."""
4243
44+ # Explicit attribute added for type checkers
45+ allow_partial : bool
46+ """Wiring container with compilation support."""
47+
48+ def __init__ (
49+ self ,
50+ spec : Spec ,
51+ * ,
52+ thread_safe : bool = False ,
53+ allow_partial : bool = False ,
54+ ) -> None :
55+ """Initialize compiler with optional partial-construction support.
56+
57+ Args:
58+ spec: wiring spec
59+ thread_safe: pack thread-safe behavior into generated code
60+ allow_partial: If True, the generated code will use a small helper
61+ module to support partial-construction placeholders for
62+ circular dependencies.
63+ """
64+ super ().__init__ (
65+ spec , thread_safe = thread_safe , allow_partial = allow_partial
66+ )
67+ self .allow_partial = allow_partial
68+
4369 def _astify (self , obj : _ResolvedValue , aio : bool = False ) -> ast .expr :
4470 """Convert a Python object (possibly a `_WiredRef`) to AST.
4571
@@ -358,6 +384,32 @@ def _compile_property(
358384
359385 call = ast .Call (func = module_attr , args = args , keywords = kwargs )
360386
387+ # If enabled, wrap the call in a helper that preserves partial
388+ # construction semantics for compiled code.
389+ if self .allow_partial :
390+ helper_call = ast .Call (
391+ func = ast .Attribute (
392+ value = ast .Name (id = "apywire.partial" , ctx = ast .Load ()),
393+ attr = "_compiled_instantiate_with_partial" ,
394+ ctx = ast .Load (),
395+ ),
396+ args = [
397+ ast .Name (id = "self" , ctx = ast .Load ()),
398+ ast .Constant (value = name ),
399+ self ._create_lambda_function (call ),
400+ ast .Attribute (
401+ value = ast .Name (id = module_name , ctx = ast .Load ()),
402+ attr = class_name ,
403+ ctx = ast .Load (),
404+ ),
405+ ast .Constant (value = bool (factory_method )),
406+ ],
407+ keywords = [],
408+ )
409+ inst_expr = helper_call
410+ else :
411+ inst_expr = call
412+
361413 cache_attr = f"{ CACHE_ATTR_PREFIX } { name } "
362414
363415 # Create reusable components
@@ -366,10 +418,10 @@ def _compile_property(
366418 # Build the assignment that sets the cache value; different
367419 # behavior is required for async vs sync callers.
368420 if not aio :
369- assign_cache = self ._create_cache_assignment (cache_attr , call )
421+ assign_cache = self ._create_cache_assignment (cache_attr , inst_expr )
370422 else :
371423 # Async path: compute local values and call in executor.
372- lambda_expr = self ._create_lambda_function (call )
424+ lambda_expr = self ._create_lambda_function (inst_expr )
373425 pre_statements .append (self ._create_loop_assignment ())
374426 run_call = self ._create_executor_call (lambda_expr )
375427 assign_cache = self ._create_cache_assignment (cache_attr , run_call )
@@ -384,7 +436,7 @@ def _compile_property(
384436 )
385437 elif not aio and thread_safe :
386438 # Build sync thread-safe version using helper mixin
387- maker = self ._create_lambda_function (call )
439+ maker = self ._create_lambda_function (inst_expr )
388440 call_inst = ast .Call (
389441 func = ast .Attribute (
390442 value = ast .Name (id = "self" , ctx = ast .Load ()),
@@ -610,6 +662,11 @@ def compile(self, *, aio: bool = False, thread_safe: bool = False) -> str:
610662 # When compiling thread_safe, import thread-safety primitives
611663 modules .add ("apywire.threads" )
612664 modules .add ("apywire.exceptions" )
665+ # If compiler is configured to support partial construction, ensure
666+ # runtime helper is available in generated module.
667+ if self .allow_partial :
668+ modules .add ("apywire.partial" )
669+ modules .add ("apywire.exceptions" )
613670 for module in sorted (modules ):
614671 if module == "apywire.threads" :
615672 # Import ThreadSafeMixin from threads
@@ -633,15 +690,21 @@ def compile(self, *, aio: bool = False, thread_safe: bool = False) -> str:
633690 level = 0 ,
634691 )
635692 )
693+ elif module == "apywire.partial" :
694+ # Import partial helper module used by compiled code
695+ imp = ast .Import (names = [ast .alias (name = "apywire.partial" )])
696+ body .append (imp )
636697 else :
637698 body .append (ast .Import (names = [ast .alias (name = module )]))
638699
639700 # Build class body
640701 class_body : list [ast .stmt ] = []
641- # When thread safe, add __init__ that calls helper mixin init
702+ # Add __init__ if needed to support thread-safety init and/or
703+ # partial-construction flag propagation.
704+ init_body : list [ast .stmt ] = []
705+ needs_init = False
642706 if thread_safe :
643- # class __init__
644- init_body : list [ast .stmt ] = []
707+ needs_init = True
645708 # compiled class will inherit from the mixin and just call
646709 # `_init_thread_safety` from its constructor
647710 init_body .append (
@@ -657,6 +720,22 @@ def compile(self, *, aio: bool = False, thread_safe: bool = False) -> str:
657720 ),
658721 )
659722 )
723+ if self .allow_partial :
724+ needs_init = True
725+ # Set instance flag so compiled container behaves like runtime
726+ init_body .append (
727+ ast .Assign (
728+ targets = [
729+ ast .Attribute (
730+ value = ast .Name (id = "self" , ctx = ast .Load ()),
731+ attr = "allow_partial" ,
732+ ctx = ast .Store (),
733+ )
734+ ],
735+ value = ast .Constant (value = True ),
736+ )
737+ )
738+ if needs_init :
660739 init_def = ast .FunctionDef (
661740 name = "__init__" ,
662741 args = _PROPERTY_ARGS ,
0 commit comments