Skip to content

Commit 91f0958

Browse files
committed
Add BlankProvider as placeholder
This can be used to store required configuration such as domains and passwords.
1 parent 2bcd883 commit 91f0958

File tree

4 files changed

+144
-11
lines changed

4 files changed

+144
-11
lines changed

README.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ from pif import wiring, providers
3232

3333

3434
@wiring.injected # <- automatically injects providers.Provider default arguments!
35-
def my_function(a: str = providers.Factory[str](lambda: "hello world")):
35+
def my_function(a: str = providers.ExistingSingleton("hello world")):
3636
return a
3737

3838

@@ -48,7 +48,7 @@ With this approach you can wire all methods in the specified modules.
4848
from pif import wiring, providers
4949

5050

51-
def my_function(a: str = providers.Factory[str](lambda: "hello world")):
51+
def my_function(a: str = providers.ExistingSingleton("hello world")):
5252
return a
5353

5454

@@ -65,12 +65,13 @@ services for testing or dynamically patching application behavior based on appli
6565

6666
#### Standard Overriding
6767

68-
If you want to patch a value all you need to do is call `.override()` on the provider in question.
68+
If you want to patch a value all you need to do is call `.override()` on the provider in question. If you are wanting to
69+
override an existing singleton you may call the convenience method `.override_existing()`.
6970

7071
```python
7172
from pif import wiring, providers
7273

73-
StringProvider = providers.Factory[str](lambda: "hello world")
74+
StringProvider = providers.ExistingSingleton("hello world")
7475

7576

7677
@wiring.injected
@@ -81,7 +82,7 @@ def my_function(a: str = StringProvider):
8182
if __name__ == "__main__":
8283
assert "hello world" == my_function()
8384

84-
override = StringProvider.override(providers.Factory[str](lambda: "overridden_1"))
85+
override = StringProvider.override_existing("overridden_1")
8586

8687
assert "overridden_1"
8788
```
@@ -93,7 +94,7 @@ If you want more control around the override lifecycles then you may use the `Ov
9394
```python
9495
from pif import wiring, providers
9596

96-
StringProvider = providers.Factory[str](lambda: "hello world")
97+
StringProvider = providers.ExistingSingleton("hello world")
9798

9899

99100
@wiring.injected
@@ -104,13 +105,13 @@ def my_function(a: str = StringProvider):
104105
if __name__ == "__main__":
105106
assert "hello world" == my_function()
106107

107-
OverrideProvider = providers.Factory[str](lambda: "overridden_1")
108+
OverrideProvider = providers.ExistingSingleton("overridden_1")
108109

109-
with StringProvider.override(OverrideProvider):
110+
with StringProvider.override_existing(OverrideProvider):
110111
assert "overridden_1" == my_function()
111112

112-
with OverrideProvider.override(providers.Factory[str]("overridden_2")):
113-
assert "overridden_2" == my_function() # You can even stack overrides!!
113+
with OverrideProvider.override_existing("overridden_2"):
114+
assert "overridden_2" == my_function() # You can even stack overrides!!
114115

115116
assert "overridden_1" == my_function()
116117

pif/exceptions.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
class PifException(Exception):
2+
"""
3+
Any custom exception thrown by the Python Injection Framework (PIF).
4+
"""
5+
6+
msg: str
7+
8+
def __init__(self, msg: str = None, *args):
9+
super().__init__(*args)
10+
self.msg = msg or self.__doc__
11+
12+
def __str__(self):
13+
return self.msg
14+
15+
def __repr__(self):
16+
return f"<{type(self)}({self.msg!r})>"
17+
18+
19+
class BlankProviderException(PifException):
20+
"""
21+
Attempted to evaluate a BlankProvider. Make sure to override this first!
22+
"""

pif/providers.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
import functools
1515
from typing import Callable, Self
1616

17+
from pif import exceptions
18+
1719

1820
class Provider[T](abc.ABC):
1921
"""
@@ -44,6 +46,12 @@ def override[U: Provider | None](self, provider: U) -> Override[U]:
4446
"""
4547
return Override(self, provider)
4648

49+
def override_existing[U](self, value: U) -> Override[Provider[T]]:
50+
"""
51+
Override the current provider with an existing singleton.
52+
"""
53+
return self.override(ExistingSingleton(value))
54+
4755

4856
class Override[ProviderT: Provider]:
4957
"""
@@ -72,6 +80,17 @@ def __exit__(self, exc_type, exc_val, exc_tb):
7280
self.disable()
7381

7482

83+
class BlankProvider(Provider):
84+
"""
85+
A placeholder for a provider.
86+
"""
87+
88+
__slots__ = tuple()
89+
90+
def _evaluate(self) -> None:
91+
raise exceptions.BlankProviderException()
92+
93+
7594
class ExistingSingleton[T](Provider):
7695
"""
7796
Provide an existing object instance.

tests/test_providers.py

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
from pif import providers
1+
import pytest
2+
3+
from pif import exceptions, providers
24

35

46
def test_override_standard_shallow():
@@ -108,3 +110,92 @@ def test_override_contextmanager_nested():
108110
assert provide_a() == "a"
109111
assert provide_b() == "b"
110112
assert provide_c() == "c"
113+
114+
115+
def test_blank_provider():
116+
"""
117+
Checking the blank provider raises a BlankProviderException when it hasn't been overridden.
118+
"""
119+
provider = providers.BlankProvider()
120+
121+
with pytest.raises(exceptions.BlankProviderException):
122+
provider()
123+
124+
with provider.override_existing("some_value"):
125+
assert provider() == "some_value"
126+
127+
with pytest.raises(exceptions.BlankProviderException):
128+
provider()
129+
130+
131+
def test_existing_singleton():
132+
"""
133+
Checking the existing singleton provider returns the exact same object.
134+
"""
135+
obj_1 = object()
136+
obj_2 = object()
137+
138+
provider = providers.ExistingSingleton(obj_1)
139+
140+
assert obj_1 is not obj_2
141+
assert obj_1 is provider()
142+
assert obj_1 is provider()
143+
144+
with provider.override_existing(obj_2):
145+
assert obj_2 is not obj_1
146+
assert obj_2 is provider()
147+
assert obj_2 is provider()
148+
149+
assert obj_1 is not obj_2
150+
assert obj_1 is provider()
151+
assert obj_1 is provider()
152+
153+
154+
def test_singleton():
155+
"""
156+
Checking the singleton provider creates only once.
157+
"""
158+
provider = providers.Singleton[dict](dict, a=1, b=2)
159+
160+
dict_1 = provider()
161+
dict_2 = provider()
162+
163+
with provider.override(providers.Singleton[dict](dict, a=1, b=2)):
164+
dict_alt = provider()
165+
166+
dict_3 = provider()
167+
168+
assert dict_1 == {"a": 1, "b": 2}
169+
assert dict_1 is not {"a": 1, "b": 2}
170+
assert dict_2 is dict_1
171+
assert dict_3 is dict_1
172+
173+
assert dict_1 == dict_alt
174+
assert dict_1 is not dict_alt
175+
176+
177+
def test_factory():
178+
"""
179+
Checking the factory provider creates every single time.
180+
"""
181+
provider = providers.Factory[dict](dict, a=1, b=2)
182+
183+
dict_1 = provider()
184+
dict_2 = provider()
185+
186+
with provider.override(providers.Factory[dict](dict, a=1, b=2)):
187+
dict_alt = provider()
188+
189+
dict_3 = provider()
190+
191+
assert dict_1 == {"a": 1, "b": 2}
192+
assert dict_1 is not {"a": 1, "b": 2}
193+
194+
assert dict_2 == dict_1
195+
assert dict_2 is not dict_1
196+
197+
assert dict_3 == dict_1
198+
assert dict_3 is not dict_1
199+
200+
assert dict_alt == dict_1
201+
assert dict_alt is not dict_1

0 commit comments

Comments
 (0)