pyiv 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pyiv/__init__.py ADDED
@@ -0,0 +1,137 @@
1
+ """Guice-style dependency injection for Python.
2
+
3
+ pyiv provides type-based constructor injection, scopes, qualified keys, and
4
+ built-in test doubles. Runtime has zero third-party dependencies. Python 3.8+.
5
+
6
+ Key Features:
7
+
8
+ - Type-based constructor injection from annotations
9
+ - Scopes (per-injector and process-wide singletons, plus custom Scope)
10
+ - Qualified keys and a fluent Binder API
11
+ - Reflection to discover implementations in a package
12
+ - Test doubles for Clock, Filesystem, Console, and DateTimeService
13
+ - Zero runtime dependencies
14
+
15
+ Quick Start:
16
+
17
+ >>> from pyiv import Config, get_injector
18
+ >>> class Database:
19
+ ... pass
20
+ >>> class PostgreSQL(Database):
21
+ ... pass
22
+ >>> class MyConfig(Config):
23
+ ... def configure(self):
24
+ ... self.register(Database, PostgreSQL)
25
+ >>> injector = get_injector(MyConfig)
26
+ >>> isinstance(injector.inject(Database), PostgreSQL)
27
+ True
28
+ """
29
+
30
+ from pyiv.binder import Binder, BindingBuilder
31
+ from pyiv.chain import ChainHandler, ChainType
32
+ from pyiv.clock import Clock, RealClock, SyntheticClock, Timer
33
+ from pyiv.config import Config
34
+ from pyiv.console import (
35
+ BaseConsole,
36
+ Console,
37
+ FileConsole,
38
+ MemoryConsole,
39
+ MockConsole,
40
+ PTYConsole,
41
+ RealConsole,
42
+ )
43
+ from pyiv.datetime_service import DateTimeService, MockDateTimeService, PythonDateTimeService
44
+ from pyiv.factory import BaseFactory, Factory, SimpleFactory
45
+ from pyiv.filesystem import Filesystem, MemoryFilesystem, RealFilesystem
46
+ from pyiv.injector import Injector, get_injector
47
+ from pyiv.key import Key, Named, Qualifier
48
+ from pyiv.members import InjectorMembersInjector, MembersInjector
49
+ from pyiv.multibinder import ListMultibinder, Multibinder, SetMultibinder
50
+ from pyiv.network import HTTPClient, HTTPSClient, NetworkClient
51
+ from pyiv.optional import get_optional_type, is_optional_type
52
+ from pyiv.provider import (
53
+ BaseProvider,
54
+ FactoryProvider,
55
+ InjectorProvider,
56
+ InstanceProvider,
57
+ Provider,
58
+ )
59
+ from pyiv.reflection import ReflectionConfig
60
+ from pyiv.scope import GlobalSingletonScope, NoScope, Scope, SingletonScope
61
+ from pyiv.serde import Base64SerDe, JSONSerDe, NoOpSerDe, PickleSerDe, SerDe, XMLSerDe, YAMLSerDe
62
+ from pyiv.singleton import GlobalSingletonRegistry, SingletonType
63
+
64
+ # Command interface (optional import)
65
+ try:
66
+ from pyiv.command import CLICommand, Command, CommandRunner, ServiceCommand
67
+
68
+ _has_commands = True
69
+ except ImportError:
70
+ _has_commands = False
71
+
72
+ __version__ = "0.3.0"
73
+ __all__ = [
74
+ "Config",
75
+ "ReflectionConfig",
76
+ "Injector",
77
+ "get_injector",
78
+ "ChainType",
79
+ "ChainHandler",
80
+ "Filesystem",
81
+ "RealFilesystem",
82
+ "MemoryFilesystem",
83
+ "Console",
84
+ "BaseConsole",
85
+ "RealConsole",
86
+ "MemoryConsole",
87
+ "FileConsole",
88
+ "PTYConsole",
89
+ "MockConsole",
90
+ "Clock",
91
+ "RealClock",
92
+ "SyntheticClock",
93
+ "Timer",
94
+ "DateTimeService",
95
+ "PythonDateTimeService",
96
+ "MockDateTimeService",
97
+ "Factory",
98
+ "BaseFactory",
99
+ "SimpleFactory",
100
+ "SerDe",
101
+ "JSONSerDe",
102
+ "Base64SerDe",
103
+ "XMLSerDe",
104
+ "YAMLSerDe",
105
+ "PickleSerDe",
106
+ "NoOpSerDe",
107
+ "NetworkClient",
108
+ "HTTPClient",
109
+ "HTTPSClient",
110
+ "SingletonType",
111
+ "GlobalSingletonRegistry",
112
+ # New interfaces
113
+ "Provider",
114
+ "BaseProvider",
115
+ "InjectorProvider",
116
+ "InstanceProvider",
117
+ "FactoryProvider",
118
+ "Scope",
119
+ "NoScope",
120
+ "SingletonScope",
121
+ "GlobalSingletonScope",
122
+ "Key",
123
+ "Named",
124
+ "Qualifier",
125
+ "Binder",
126
+ "BindingBuilder",
127
+ "MembersInjector",
128
+ "InjectorMembersInjector",
129
+ "Multibinder",
130
+ "SetMultibinder",
131
+ "ListMultibinder",
132
+ "is_optional_type",
133
+ "get_optional_type",
134
+ ]
135
+
136
+ if _has_commands:
137
+ __all__.extend(["Command", "ServiceCommand", "CLICommand", "CommandRunner"])
pyiv/binder.py ADDED
@@ -0,0 +1,203 @@
1
+ """Binder interface for fluent configuration API.
2
+
3
+ This module provides the Binder interface for configuring dependency bindings
4
+ in a fluent, decoupled way. The Binder separates the configuration API from
5
+ the implementation, making Config more testable and enabling programmatic
6
+ configuration.
7
+
8
+ **What Problem Does This Solve?**
9
+
10
+ Binders solve the configuration API design problem:
11
+
12
+ - **Fluent API**: Method chaining for readable, expressive configuration
13
+ - **Separation of Concerns**: Configuration API separated from implementation
14
+ - **Testability**: Mock binders for testing configuration logic
15
+ - **Programmatic Configuration**: Build configurations dynamically
16
+ - **Better Readability**: ``binder.bind(X).to(Y).in_scope(Z)`` is clearer than nested calls
17
+
18
+ **Real-World Use Cases:**
19
+
20
+ - **Dynamic Configuration**: Build configurations based on environment variables
21
+ - **Configuration Testing**: Mock binders to test configuration logic
22
+ - **Modular Configuration**: Compose configurations from multiple sources
23
+ - **Framework Integration**: Provide fluent APIs for framework-specific configuration
24
+
25
+ Architecture:
26
+ - Binder: Protocol defining the binder interface
27
+ - BindingBuilder: Fluent builder for configuring bindings
28
+ - ConfigBinder: Concrete binder implementation used by Config
29
+
30
+ Usage Examples:
31
+
32
+ Basic Fluent Configuration:
33
+ >>> from pyiv.binder import Binder
34
+ >>> from pyiv.scope import SingletonScope
35
+ >>> from pyiv import Config, get_injector
36
+ >>>
37
+ >>> class Database:
38
+ ... pass
39
+ >>>
40
+ >>> class PostgreSQL(Database):
41
+ ... pass
42
+ >>>
43
+ >>> class Logger:
44
+ ... pass
45
+ >>>
46
+ >>> class FileLogger(Logger):
47
+ ... pass
48
+ >>>
49
+ >>> class MyConfig(Config):
50
+ ... def configure(self):
51
+ ... binder = self.get_binder()
52
+ ... # Fluent API - easy to read and chain
53
+ ... binder.bind(Database).to(PostgreSQL)
54
+ ... binder.bind(Logger).to(FileLogger).in_scope(SingletonScope())
55
+ >>>
56
+ >>> injector = get_injector(MyConfig)
57
+ >>> db = injector.inject(Database)
58
+ >>> isinstance(db, PostgreSQL)
59
+ True
60
+
61
+ Binding to Instances:
62
+ >>> from pyiv import Config, get_injector
63
+ >>>
64
+ >>> class Cache:
65
+ ... def __init__(self):
66
+ ... self.data = {}
67
+ >>>
68
+ >>> cache = Cache()
69
+ >>> class InstanceConfig(Config):
70
+ ... def configure(self):
71
+ ... self.get_binder().bind_instance(Cache, cache)
72
+ >>>
73
+ >>> injector = get_injector(InstanceConfig)
74
+ >>> injector.inject(Cache) is cache
75
+ True
76
+ """
77
+
78
+ from typing import Any, Generic, Protocol, Type, TypeVar
79
+
80
+ from pyiv.key import Key
81
+ from pyiv.provider import Provider
82
+ from pyiv.scope import Scope
83
+
84
+ T = TypeVar("T")
85
+
86
+
87
+ class BindingBuilder(Protocol, Generic[T]):
88
+ """Fluent builder for configuring bindings.
89
+
90
+ This builder provides a fluent API for configuring how a type should
91
+ be bound. It supports chaining methods to configure the binding.
92
+
93
+ Example:
94
+ >>> from pyiv import Config, get_injector
95
+ >>> from pyiv.scope import SingletonScope
96
+ >>> class Database:
97
+ ... pass
98
+ >>> class PostgreSQL(Database):
99
+ ... pass
100
+ >>> class MyConfig(Config):
101
+ ... def configure(self):
102
+ ... self.get_binder().bind(Database).to(PostgreSQL).in_scope(SingletonScope())
103
+ >>> isinstance(get_injector(MyConfig).inject(Database), PostgreSQL)
104
+ True
105
+ """
106
+
107
+ def to(self, implementation: Type[T]) -> "BindingBuilder[T]":
108
+ """Bind to a concrete implementation.
109
+
110
+ Args:
111
+ implementation: The concrete class to bind to
112
+
113
+ Returns:
114
+ Self for method chaining
115
+ """
116
+ ...
117
+
118
+ def to_instance(self, instance: T) -> "BindingBuilder[T]":
119
+ """Bind to a pre-created instance.
120
+
121
+ Args:
122
+ instance: The instance to bind to
123
+
124
+ Returns:
125
+ Self for method chaining
126
+ """
127
+ ...
128
+
129
+ def to_provider(self, provider: Provider[T]) -> "BindingBuilder[T]":
130
+ """Bind to a provider.
131
+
132
+ Args:
133
+ provider: The provider to use for instance creation
134
+
135
+ Returns:
136
+ Self for method chaining
137
+ """
138
+ ...
139
+
140
+ def in_scope(self, scope: Scope) -> "BindingBuilder[T]":
141
+ """Set the scope for this binding.
142
+
143
+ Args:
144
+ scope: The scope to use
145
+
146
+ Returns:
147
+ Self for method chaining
148
+ """
149
+ ...
150
+
151
+
152
+ class Binder(Protocol):
153
+ """Protocol for binder implementations.
154
+
155
+ Binders provide a fluent API for configuring dependency bindings.
156
+ They separate the configuration API from the implementation, making
157
+ Config more testable and enabling programmatic configuration.
158
+
159
+ Example::
160
+
161
+ class MyBinder(Binder):
162
+ def bind(self, abstract):
163
+ return BindingBuilder(...)
164
+ """
165
+
166
+ def bind(self, abstract: Type[T]) -> BindingBuilder[T]:
167
+ """Start a binding configuration.
168
+
169
+ Args:
170
+ abstract: The abstract type to bind
171
+
172
+ Returns:
173
+ A binding builder for fluent configuration
174
+ """
175
+ ...
176
+
177
+ def bind_key(self, key: Key[T]) -> BindingBuilder[T]:
178
+ """Start a binding configuration with a qualified key.
179
+
180
+ Args:
181
+ key: The qualified key to bind
182
+
183
+ Returns:
184
+ A binding builder for fluent configuration
185
+ """
186
+ ...
187
+
188
+ def bind_instance(self, abstract: Type[T], instance: T) -> None:
189
+ """Bind to a pre-created instance.
190
+
191
+ Args:
192
+ abstract: The abstract type
193
+ instance: The pre-created instance
194
+ """
195
+ ...
196
+
197
+ def install(self, config: Any) -> None:
198
+ """Install another configuration module.
199
+
200
+ Args:
201
+ config: Another Config instance to install
202
+ """
203
+ ...
pyiv/binder_impl.py ADDED
@@ -0,0 +1,178 @@
1
+ """Concrete Binder implementation for Config.
2
+
3
+ This module provides the ConfigBinder implementation that works with Config
4
+ to provide a fluent configuration API.
5
+ """
6
+
7
+ from typing import Any, Generic, List, Optional, Type, TypeVar
8
+
9
+ from pyiv.binder import Binder, BindingBuilder
10
+ from pyiv.config import Config
11
+ from pyiv.key import Key
12
+ from pyiv.provider import InjectorProvider, InstanceProvider, Provider
13
+ from pyiv.scope import NoScope, Scope
14
+
15
+ T = TypeVar("T")
16
+
17
+
18
+ class ConfigBindingBuilder(BindingBuilder[T]):
19
+ """Binding builder implementation for Config."""
20
+
21
+ def __init__(self, config: Config, abstract: Type[T]):
22
+ """Initialize binding builder.
23
+
24
+ Args:
25
+ config: The config to register bindings with
26
+ abstract: The abstract type being bound
27
+ """
28
+ self._config = config
29
+ self._abstract = abstract
30
+ self._implementation: Optional[Type[T]] = None
31
+ self._instance: Optional[T] = None
32
+ self._provider: Optional[Provider[T]] = None
33
+ self._scope: Optional[Scope] = None
34
+
35
+ def to(self, implementation: Type[T]) -> "ConfigBindingBuilder[T]":
36
+ """Bind to a concrete implementation.
37
+
38
+ Args:
39
+ implementation: The concrete class to bind to
40
+
41
+ Returns:
42
+ Self for method chaining
43
+ """
44
+ self._implementation = implementation
45
+ self._finalize()
46
+ return self
47
+
48
+ def to_instance(self, instance: T) -> "ConfigBindingBuilder[T]":
49
+ """Bind to a pre-created instance.
50
+
51
+ Args:
52
+ instance: The instance to bind to
53
+
54
+ Returns:
55
+ Self for method chaining
56
+ """
57
+ self._instance = instance
58
+ self._finalize()
59
+ return self
60
+
61
+ def to_provider(self, provider: Provider[T]) -> "ConfigBindingBuilder[T]":
62
+ """Bind to a provider.
63
+
64
+ Args:
65
+ provider: The provider to use for instance creation
66
+
67
+ Returns:
68
+ Self for method chaining
69
+ """
70
+ self._provider = provider
71
+ self._finalize()
72
+ return self
73
+
74
+ def in_scope(self, scope: Scope) -> "ConfigBindingBuilder[T]":
75
+ """Set the scope for this binding.
76
+
77
+ Args:
78
+ scope: The scope to use
79
+
80
+ Returns:
81
+ Self for method chaining
82
+ """
83
+ self._scope = scope
84
+ # Update scope if already finalized
85
+ if hasattr(self, "_finalized") and getattr(self, "_finalized", False):
86
+ self._config._scopes[self._abstract] = scope
87
+ return self
88
+
89
+ def _finalize(self) -> None:
90
+ """Finalize the binding registration."""
91
+ if hasattr(self, "_finalized") and getattr(self, "_finalized", False):
92
+ return
93
+
94
+ if self._instance is not None:
95
+ self._config.register_instance(self._abstract, self._instance)
96
+ if self._scope is not None:
97
+ self._config._scopes[self._abstract] = self._scope
98
+ elif self._provider is not None:
99
+ self._config.register_provider(self._abstract, self._provider)
100
+ if self._scope is not None:
101
+ self._config._scopes[self._abstract] = self._scope
102
+ elif self._implementation is not None:
103
+ self._config.register(
104
+ self._abstract,
105
+ self._implementation,
106
+ scope=self._scope if self._scope is not None else NoScope(),
107
+ )
108
+ else:
109
+ # Don't raise error - allow chaining
110
+ return
111
+
112
+ self._finalized = True
113
+
114
+
115
+ class ConfigBinder(Binder):
116
+ """Concrete Binder implementation for Config."""
117
+
118
+ def __init__(self, config: Config):
119
+ """Initialize binder.
120
+
121
+ Args:
122
+ config: The config to register bindings with
123
+ """
124
+ self._config = config
125
+ self._builders: List[ConfigBindingBuilder[Any]] = []
126
+
127
+ def bind(self, abstract: Type[T]) -> BindingBuilder[T]:
128
+ """Start a binding configuration.
129
+
130
+ Args:
131
+ abstract: The abstract type to bind
132
+
133
+ Returns:
134
+ A binding builder for fluent configuration
135
+ """
136
+ builder = ConfigBindingBuilder(self._config, abstract)
137
+ self._builders.append(builder)
138
+ return builder
139
+
140
+ def bind_key(self, key: Key[T]) -> BindingBuilder[T]:
141
+ """Start a binding configuration with a qualified key.
142
+
143
+ Args:
144
+ key: The qualified key to bind
145
+
146
+ Returns:
147
+ A binding builder for fluent configuration
148
+ """
149
+ # For qualified keys, we need a special builder
150
+ builder: ConfigBindingBuilder[Any] = ConfigBindingBuilder(self._config, key.type) # type: ignore[arg-type]
151
+ builder._key = key # type: ignore[attr-defined] # Store the key
152
+ self._builders.append(builder)
153
+ return builder
154
+
155
+ def bind_instance(self, abstract: Type[T], instance: T) -> None:
156
+ """Bind to a pre-created instance.
157
+
158
+ Args:
159
+ abstract: The abstract type
160
+ instance: The pre-created instance
161
+ """
162
+ self._config.register_instance(abstract, instance)
163
+
164
+ def install(self, config: Any) -> None:
165
+ """Install another configuration module.
166
+
167
+ Args:
168
+ config: Another Config instance to install
169
+ """
170
+ # This would require merging configs, which is complex
171
+ # For now, we'll raise NotImplementedError
172
+ raise NotImplementedError("Config installation not yet implemented")
173
+
174
+ def finalize(self) -> None:
175
+ """Finalize all pending bindings."""
176
+ for builder in self._builders:
177
+ builder._finalize()
178
+ self._builders.clear()
pyiv/chain.py ADDED
@@ -0,0 +1,79 @@
1
+ """Chain of Responsibility pattern for pyiv.
2
+
3
+ This module provides a general chain of responsibility system that can be used
4
+ for various purposes: encoding, hashing, sorting, etc. Each chain type has its
5
+ own interface and implementations.
6
+ """
7
+
8
+ from abc import ABC, abstractmethod
9
+ from enum import Enum
10
+ from typing import Any, TypeVar
11
+
12
+ T = TypeVar("T")
13
+
14
+
15
+ class ChainType(Enum):
16
+ """Types of chain of responsibility handlers.
17
+
18
+ Each chain type represents a different category of handlers:
19
+ - ENCODING: Serialization/deserialization (SerDe)
20
+ - HASHING: Hash function implementations
21
+ - SORTING: Sorting algorithm implementations
22
+ - NETWORK_CLIENT: Network protocol clients (HTTP, HTTPS, etc.)
23
+ - etc.
24
+ """
25
+
26
+ ENCODING = "encoding"
27
+ HASHING = "hashing"
28
+ SORTING = "sorting"
29
+ NETWORK_CLIENT = "network_client"
30
+
31
+
32
+ class ChainHandler(ABC):
33
+ """Abstract base class for chain of responsibility handlers.
34
+
35
+ Chain handlers process requests in a chain. Each handler can either:
36
+ - Handle the request and return a result
37
+ - Pass the request to the next handler in the chain
38
+ - Reject the request
39
+
40
+ Subclasses must implement:
41
+ - chain_type: The type of chain this handler belongs to
42
+ - handler_type: The specific handler type identifier (e.g., "json", "md5", "quicksort")
43
+ - handle(): Process the request
44
+ """
45
+
46
+ @property
47
+ @abstractmethod
48
+ def chain_type(self) -> ChainType:
49
+ """Return the chain type this handler belongs to.
50
+
51
+ Returns:
52
+ The ChainType enum value
53
+ """
54
+ pass
55
+
56
+ @property
57
+ @abstractmethod
58
+ def handler_type(self) -> str:
59
+ """Return the handler type identifier.
60
+
61
+ This identifies the specific implementation (e.g., "json", "md5", "quicksort").
62
+
63
+ Returns:
64
+ A string identifying the handler type
65
+ """
66
+ pass
67
+
68
+ @abstractmethod
69
+ def handle(self, request: Any, **kwargs) -> Any:
70
+ """Handle a request.
71
+
72
+ Args:
73
+ request: The request to handle
74
+ **kwargs: Additional keyword arguments
75
+
76
+ Returns:
77
+ The result of handling the request
78
+ """
79
+ pass