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__ (spec , thread_safe = thread_safe )
65+ self .allow_partial = allow_partial
66+
4367 def _astify (self , obj : _ResolvedValue , aio : bool = False ) -> ast .expr :
4468 """Convert a Python object (possibly a `_WiredRef`) to AST.
4569
@@ -358,6 +382,32 @@ def _compile_property(
358382
359383 call = ast .Call (func = module_attr , args = args , keywords = kwargs )
360384
385+ # If enabled, wrap the call in a helper that preserves partial
386+ # construction semantics for compiled code.
387+ if self .allow_partial :
388+ helper_call = ast .Call (
389+ func = ast .Attribute (
390+ value = ast .Name (id = "apywire.partial" , ctx = ast .Load ()),
391+ attr = "_compiled_instantiate_with_partial" ,
392+ ctx = ast .Load (),
393+ ),
394+ args = [
395+ ast .Name (id = "self" , ctx = ast .Load ()),
396+ ast .Constant (value = name ),
397+ self ._create_lambda_function (call ),
398+ ast .Attribute (
399+ value = ast .Name (id = module_name , ctx = ast .Load ()),
400+ attr = class_name ,
401+ ctx = ast .Load (),
402+ ),
403+ ast .Constant (value = bool (factory_method )),
404+ ],
405+ keywords = [],
406+ )
407+ inst_expr = helper_call
408+ else :
409+ inst_expr = call
410+
361411 cache_attr = f"{ CACHE_ATTR_PREFIX } { name } "
362412
363413 # Create reusable components
@@ -366,10 +416,10 @@ def _compile_property(
366416 # Build the assignment that sets the cache value; different
367417 # behavior is required for async vs sync callers.
368418 if not aio :
369- assign_cache = self ._create_cache_assignment (cache_attr , call )
419+ assign_cache = self ._create_cache_assignment (cache_attr , inst_expr )
370420 else :
371421 # Async path: compute local values and call in executor.
372- lambda_expr = self ._create_lambda_function (call )
422+ lambda_expr = self ._create_lambda_function (inst_expr )
373423 pre_statements .append (self ._create_loop_assignment ())
374424 run_call = self ._create_executor_call (lambda_expr )
375425 assign_cache = self ._create_cache_assignment (cache_attr , run_call )
@@ -384,7 +434,7 @@ def _compile_property(
384434 )
385435 elif not aio and thread_safe :
386436 # Build sync thread-safe version using helper mixin
387- maker = self ._create_lambda_function (call )
437+ maker = self ._create_lambda_function (inst_expr )
388438 call_inst = ast .Call (
389439 func = ast .Attribute (
390440 value = ast .Name (id = "self" , ctx = ast .Load ()),
@@ -610,6 +660,11 @@ def compile(self, *, aio: bool = False, thread_safe: bool = False) -> str:
610660 # When compiling thread_safe, import thread-safety primitives
611661 modules .add ("apywire.threads" )
612662 modules .add ("apywire.exceptions" )
663+ # If compiler is configured to support partial construction, ensure
664+ # runtime helper is available in generated module.
665+ if self .allow_partial :
666+ modules .add ("apywire.partial" )
667+ modules .add ("apywire.exceptions" )
613668 for module in sorted (modules ):
614669 if module == "apywire.threads" :
615670 # Import ThreadSafeMixin from threads
@@ -633,15 +688,21 @@ def compile(self, *, aio: bool = False, thread_safe: bool = False) -> str:
633688 level = 0 ,
634689 )
635690 )
691+ elif module == "apywire.partial" :
692+ # Import partial helper module used by compiled code
693+ imp = ast .Import (names = [ast .alias (name = "apywire.partial" )])
694+ body .append (imp )
636695 else :
637696 body .append (ast .Import (names = [ast .alias (name = module )]))
638697
639698 # Build class body
640699 class_body : list [ast .stmt ] = []
641- # When thread safe, add __init__ that calls helper mixin init
700+ # Add __init__ if needed to support thread-safety init and/or
701+ # partial-construction flag propagation.
702+ init_body : list [ast .stmt ] = []
703+ needs_init = False
642704 if thread_safe :
643- # class __init__
644- init_body : list [ast .stmt ] = []
705+ needs_init = True
645706 # compiled class will inherit from the mixin and just call
646707 # `_init_thread_safety` from its constructor
647708 init_body .append (
@@ -657,6 +718,22 @@ def compile(self, *, aio: bool = False, thread_safe: bool = False) -> str:
657718 ),
658719 )
659720 )
721+ if self .allow_partial :
722+ needs_init = True
723+ # Set instance flag so compiled container behaves like runtime
724+ init_body .append (
725+ ast .Assign (
726+ targets = [
727+ ast .Attribute (
728+ value = ast .Name (id = "self" , ctx = ast .Load ()),
729+ attr = "allow_partial" ,
730+ ctx = ast .Store (),
731+ )
732+ ],
733+ value = ast .Constant (value = True ),
734+ )
735+ )
736+ if needs_init :
660737 init_def = ast .FunctionDef (
661738 name = "__init__" ,
662739 args = _PROPERTY_ARGS ,
0 commit comments