Skip to content

Commit 6ca8f41

Browse files
committed
Refactoring: types, mixin improvements, clarity
1 parent 5d8eb0a commit 6ca8f41

7 files changed

Lines changed: 83 additions & 88 deletions

File tree

apywire/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@
1212
WiringError,
1313
)
1414
from .runtime import Accessor, AioAccessor, Spec, SpecEntry, WiringRuntime
15+
from .threads import ThreadSafeMixin
1516
from .wiring import WiringBase
1617

1718
Wiring = WiringRuntime
1819

1920
__all__ = [
2021
"Spec",
2122
"SpecEntry",
23+
"ThreadSafeMixin",
2224
"Wiring",
2325
"WiringRuntime",
2426
"WiringCompiler",

apywire/compiler.py

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -604,12 +604,12 @@ def compile(self, *, aio: bool = False, thread_safe: bool = False) -> str:
604604
modules.add("apywire.exceptions")
605605
for module in sorted(modules):
606606
if module == "apywire.threads":
607-
# Import CompiledThreadSafeMixin from threads
607+
# Import ThreadSafeMixin from threads
608608
body.append(
609609
ast.ImportFrom(
610610
module="apywire.threads",
611611
names=[
612-
ast.alias(name="CompiledThreadSafeMixin"),
612+
ast.alias(name="ThreadSafeMixin"),
613613
],
614614
level=0,
615615
)
@@ -658,25 +658,23 @@ def compile(self, *, aio: bool = False, thread_safe: bool = False) -> str:
658658
type_params=[],
659659
)
660660
class_body.insert(0, init_def)
661-
for name, (
662-
module_name,
663-
class_name,
664-
factory_method,
665-
data,
666-
) in self._parsed.items():
661+
for name, entry in self._parsed.items():
667662
# Skip synthetic auto-promoted constants
668663
# These require runtime interpolation and can't be pre-computed
669-
if module_name == SYNTHETIC_CONST and class_name == "str":
664+
if (
665+
entry.module_name == SYNTHETIC_CONST
666+
and entry.class_name == "str"
667+
):
670668
continue
671669

672670
# Regular wired object
673671
class_body.append(
674672
self._compile_property(
675673
name,
676-
module_name,
677-
class_name,
678-
factory_method,
679-
cast(_ResolvedSpecMapping, data),
674+
entry.module_name,
675+
entry.class_name,
676+
entry.factory_method,
677+
cast(_ResolvedSpecMapping, entry.data),
680678
aio=aio,
681679
thread_safe=thread_safe,
682680
)
@@ -696,14 +694,12 @@ def compile(self, *, aio: bool = False, thread_safe: bool = False) -> str:
696694

697695
class_body = [ast.Pass()] if not class_body else class_body
698696
# When using thread_safe compiled output we will rely on the
699-
# CompiledThreadSafeMixin and _LockUnavailableError imported from
697+
# ThreadSafeMixin and _LockUnavailableError imported from
700698
# apywire.threads rather than embedding helper code.
701699
# Build class definition
702700
class_bases: list[ast.expr] = []
703701
if thread_safe:
704-
class_bases.append(
705-
ast.Name(id="CompiledThreadSafeMixin", ctx=ast.Load())
706-
)
702+
class_bases.append(ast.Name(id="ThreadSafeMixin", ctx=ast.Load()))
707703
class_def = ast.ClassDef(
708704
name="Compiled",
709705
bases=class_bases,

apywire/runtime.py

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
UnknownPlaceholderError,
1919
WiringError,
2020
)
21-
from apywire.threads import CompiledThreadSafeMixin
21+
from apywire.threads import ThreadSafeMixin
2222
from apywire.wiring import (
2323
Spec,
2424
SpecEntry,
@@ -48,7 +48,7 @@ class _Constructor(Protocol):
4848
def __call__(self, *args: object, **kwargs: object) -> object: ...
4949

5050

51-
class WiringRuntime(WiringBase, CompiledThreadSafeMixin):
51+
class WiringRuntime(WiringBase, ThreadSafeMixin):
5252
"""Runtime container for wired objects.
5353
5454
This class handles the runtime resolution and instantiation of wired
@@ -79,21 +79,23 @@ def __init__(
7979
max_lock_attempts=max_lock_attempts,
8080
lock_retry_sleep=lock_retry_sleep,
8181
)
82+
if self._thread_safe:
83+
self._init_thread_safety(max_lock_attempts, lock_retry_sleep)
8284

8385
def _init_thread_safety(
8486
self,
8587
max_lock_attempts: int = 10,
8688
lock_retry_sleep: float = 0.01,
8789
) -> None:
8890
"""Initialize thread safety mixin."""
89-
CompiledThreadSafeMixin._init_thread_safety(
91+
ThreadSafeMixin._init_thread_safety(
9092
self, max_lock_attempts, lock_retry_sleep
9193
)
9294

9395
def _get_resolving_stack(self) -> list[str]:
9496
"""Return the resolving stack for the current context."""
9597
if self._thread_safe:
96-
return CompiledThreadSafeMixin._get_resolving_stack(self)
98+
return ThreadSafeMixin._get_resolving_stack(self)
9799
return self._resolving_stack
98100

99101
def __getattr__(self, name: str) -> Accessor:
@@ -124,13 +126,13 @@ def _instantiate_attr(
124126
) -> object:
125127
"""Instantiate an attribute using the configured strategy.
126128
127-
If thread_safe is True, uses the CompiledThreadSafeMixin implementation
129+
If thread_safe is True, uses the ThreadSafeMixin implementation
128130
which handles optimistic locking and global fallback.
129131
If thread_safe is False, uses a simple direct instantiation.
130132
"""
131133
if self._thread_safe:
132134
# Use the mixin's implementation which handles locking
133-
return CompiledThreadSafeMixin._instantiate_attr(self, name, maker)
135+
return ThreadSafeMixin._instantiate_attr(self, name, maker)
134136

135137
# Non-thread-safe path: simple check and set
136138
if name in self._values:
@@ -206,26 +208,31 @@ def _instantiate_impl(self, name: str) -> _RuntimeValue:
206208
f"Unknown placeholder '{name}' referenced."
207209
)
208210

209-
module_name, class_name, factory_method, data = self._parsed[name]
211+
entry = self._parsed[name]
210212

211213
# Check for synthetic auto-promoted constant
212-
if module_name == SYNTHETIC_CONST and class_name == "str":
214+
if (
215+
entry.module_name == SYNTHETIC_CONST
216+
and entry.class_name == "str"
217+
):
213218
# This is an auto-promoted constant with string interpolation
214-
value = self._format_string_constant(data, context=name)
219+
value = self._format_string_constant(entry.data, context=name)
215220
self._values[name] = value
216221
return value
217222

218-
module = importlib.import_module(module_name)
219-
cls = cast(_Constructor, getattr(module, class_name))
223+
module = importlib.import_module(entry.module_name)
224+
cls = cast(_Constructor, getattr(module, entry.class_name))
220225

221226
# If a factory method is specified, get it from the class
222-
if factory_method:
223-
constructor = cast(_Constructor, getattr(cls, factory_method))
227+
if entry.factory_method:
228+
constructor = cast(
229+
_Constructor, getattr(cls, entry.factory_method)
230+
)
224231
else:
225232
constructor = cls
226233

227234
# Resolve arguments
228-
kwargs = self._resolve_runtime(data, context=name)
235+
kwargs = self._resolve_runtime(entry.data, context=name)
229236

230237
try:
231238
if isinstance(kwargs, dict):

apywire/threads.py

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from apywire.exceptions import LockUnavailableError
2020

2121

22-
class ThreadLocalState(threading.local):
22+
class _ThreadLocalState(threading.local):
2323
"""Typed thread-local storage for wiring resolution state.
2424
2525
This class provides properly typed attributes to avoid mypy Any propagation
@@ -32,12 +32,14 @@ class ThreadLocalState(threading.local):
3232
held_locks: List of locks currently held by this thread.
3333
"""
3434

35-
resolving_stack: list[str]
36-
mode: Literal["optimistic", "global"] | None
37-
held_locks: list[threading.RLock]
35+
def __init__(self) -> None:
36+
super().__init__()
37+
self.resolving_stack: list[str] = []
38+
self.mode: Literal["optimistic", "global"] | None = None
39+
self.held_locks: list[threading.RLock] = []
3840

3941

40-
class CompiledThreadSafeMixin:
42+
class ThreadSafeMixin:
4143
"""Mixin that provides thread-safety helpers for wiring containers.
4244
4345
This mixin is used by both runtime `Wiring` and compiled `Compiled`
@@ -63,7 +65,7 @@ def _init_thread_safety(
6365
self._inst_lock = threading.RLock()
6466
self._attr_locks: dict[str, threading.RLock] = {}
6567
self._attr_locks_lock = threading.Lock()
66-
self._local: ThreadLocalState = ThreadLocalState()
68+
self._local: _ThreadLocalState = _ThreadLocalState()
6769
self._max_lock_attempts = max_lock_attempts
6870
self._lock_retry_sleep = lock_retry_sleep
6971

@@ -74,18 +76,12 @@ def _get_attribute_lock(self, name: str) -> threading.RLock:
7476
return self._attr_locks[name]
7577

7678
def _get_resolving_stack(self) -> list[str]:
77-
if not hasattr(self._local, "resolving_stack"):
78-
self._local.resolving_stack = []
7979
return self._local.resolving_stack
8080

8181
def _get_held_locks(self) -> list[threading.RLock]:
82-
if not hasattr(self._local, "held_locks"):
83-
self._local.held_locks = []
8482
return self._local.held_locks
8583

8684
def _release_held_locks(self) -> None:
87-
if not hasattr(self._local, "held_locks"):
88-
return
8985
for lock in reversed(self._local.held_locks):
9086
lock.release()
9187
self._local.held_locks.clear()
@@ -156,7 +152,7 @@ def _instantiate_attr(
156152
return value
157153

158154
lock = self._get_attribute_lock(name)
159-
mode: str | None = getattr(self._local, "mode", None)
155+
mode = self._local.mode
160156
if mode is None:
161157
# Optimistic locking: Try to acquire per-attribute lock without
162158
# blocking. If successful, instantiate in optimistic mode.
@@ -192,13 +188,7 @@ def _instantiate_attr(
192188
return inst
193189
finally:
194190
# Clean up optimistic mode when exiting top-level call
195-
if (
196-
cast(
197-
Literal["optimistic", "global"] | None,
198-
getattr(self._local, "mode", None),
199-
)
200-
== "optimistic"
201-
):
191+
if self._local.mode == "optimistic":
202192
self._local.mode = None
203193

204194
# fallback to global

apywire/wiring.py

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import re
2020
from collections import deque
2121
from types import EllipsisType
22-
from typing import TypeAlias, cast
22+
from typing import NamedTuple, TypeAlias, cast
2323

2424
from apywire.constants import (
2525
PLACEHOLDER_END,
@@ -29,7 +29,6 @@
2929
SYNTHETIC_CONST,
3030
)
3131
from apywire.exceptions import CircularWiringError, UnknownPlaceholderError
32-
from apywire.threads import ThreadLocalState
3332

3433
_ConstantValue: TypeAlias = (
3534
str | bytes | bool | int | float | complex | EllipsisType | None
@@ -91,20 +90,24 @@ def __init__(self, name: str) -> None:
9190
)
9291
Spec: TypeAlias = dict[str, _SpecMapping | _ConstantValue]
9392

94-
# Type aliases for parsed spec entries
95-
_ParsedEntry: TypeAlias = tuple[
96-
str, str, str | None, _ResolvedSpecMapping | str | _WiredRef
97-
] # (module, class, factory_method, data)
98-
_UnresolvedParsedEntry: TypeAlias = tuple[
99-
str, str, str, str | None, _SpecMapping
100-
] # (module, class, name, factory_method, data)
10193

94+
class _ParsedEntry(NamedTuple):
95+
"""A parsed wiring entry ready for instantiation."""
10296

103-
class _ThreadLocalState(ThreadLocalState):
104-
"""Thread-local state for wiring resolution.
97+
module_name: str
98+
class_name: str
99+
factory_method: str | None
100+
data: _ResolvedSpecMapping | str | _WiredRef
105101

106-
Inherits from ThreadLocalState to get properly typed attributes.
107-
"""
102+
103+
class _UnresolvedParsedEntry(NamedTuple):
104+
"""An intermediate parsed entry before placeholder resolution."""
105+
106+
module_name: str
107+
class_name: str
108+
name: str
109+
factory_method: str | None
110+
data: _SpecMapping
108111

109112

110113
class WiringBase:
@@ -150,13 +153,13 @@ def __init__(
150153

151154
# Parse wired objects first
152155
self._parsed: dict[str, _ParsedEntry] = {
153-
name: (
154-
module_name,
155-
class_name,
156-
factory_method,
157-
cast(_ResolvedSpecMapping, self._resolve(value)),
156+
entry.name: _ParsedEntry(
157+
entry.module_name,
158+
entry.class_name,
159+
entry.factory_method,
160+
cast(_ResolvedSpecMapping, self._resolve(entry.data)),
158161
)
159-
for module_name, class_name, name, factory_method, value in parsed
162+
for entry in parsed
160163
}
161164

162165
# Classify constants by placeholder type
@@ -227,19 +230,13 @@ def __init__(
227230
# Create synthetic parsed entries for auto-promoted constants
228231
for key, value in const_with_wired_refs.items():
229232
# Create a synthetic entry that will format the string at runtime
230-
self._parsed[key] = (
233+
self._parsed[key] = _ParsedEntry(
231234
SYNTHETIC_CONST, # Synthetic module marker
232235
"str", # Will use string formatting
233236
None, # No factory method
234237
cast(str | _WiredRef, self._resolve(value)),
235238
)
236-
if self._thread_safe:
237-
# Thread-safe mode: use mixin helpers for lock state.
238-
# Note: Derived classes must implement _init_thread_safety
239-
# if they support thread safety (like WiringRuntime).
240-
if hasattr(self, "_init_thread_safety"):
241-
self._init_thread_safety(max_lock_attempts, lock_retry_sleep)
242-
else:
239+
if not self._thread_safe:
243240
# Non-thread-safe mode: use simple list for resolving stack
244241
self._resolving_stack: list[str] = []
245242

@@ -276,7 +273,7 @@ def _parse_spec_entry(
276273
f"invalid spec key '{key}': missing module qualification"
277274
)
278275

279-
return (
276+
return _UnresolvedParsedEntry(
280277
module_name,
281278
class_name,
282279
name,

0 commit comments

Comments
 (0)