Skip to content

Commit 6e59b4a

Browse files
committed
Merge branch 'release/4.23.2' into master
2 parents b2ea773 + 6402c5b commit 6e59b4a

File tree

7 files changed

+3255
-2498
lines changed

7 files changed

+3255
-2498
lines changed

docs/main/changelog.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ that were made in every particular version.
77
From version 0.7.6 *Dependency Injector* framework strictly
88
follows `Semantic versioning`_
99

10+
4.23.2
11+
------
12+
- Improve async mode exceptions handling.
13+
- Fix double printing of exception when async resource initialization causes an error.
14+
1015
4.23.1
1116
------
1217
- Hotfix a bug with importing FastAPI ``Request``.

src/dependency_injector/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
"""Top-level package."""
22

3-
__version__ = '4.23.1'
3+
__version__ = '4.23.2'
44
"""Version number.
55
66
:type: str

src/dependency_injector/providers.c

Lines changed: 3074 additions & 2475 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/dependency_injector/providers.pxd

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -455,10 +455,15 @@ cdef inline void __async_prepare_args_kwargs_callback(
455455
object awaitables,
456456
object future,
457457
):
458-
awaited = future.result()
459-
for value, (key, _) in zip(awaited, awaitables):
460-
args[key] = value
461-
future_result.set_result(args)
458+
try:
459+
awaited = future.result()
460+
461+
for value, (key, _) in zip(awaited, awaitables):
462+
args[key] = value
463+
except Exception as exception:
464+
future_result.set_exception(exception)
465+
else:
466+
future_result.set_result(args)
462467

463468

464469
@cython.boundscheck(False)
@@ -498,9 +503,15 @@ cdef inline object __async_inject_attributes(future_instance, future_attributes)
498503

499504

500505
cdef inline void __async_inject_attributes_callback(object future_result, object future):
501-
instance, attributes = future.result()
502-
__inject_attributes(instance, attributes)
503-
future_result.set_result(instance)
506+
try:
507+
instance, attributes = future.result()
508+
509+
for name, value in attributes.items():
510+
setattr(instance, name, value)
511+
except Exception as exception:
512+
future_result.set_exception(exception)
513+
else:
514+
future_result.set_result(instance)
504515

505516

506517
cdef inline void __inject_attributes(object instance, dict attributes):
@@ -560,19 +571,26 @@ cdef inline object __call(
560571

561572

562573
cdef inline void __async_call_callback(object future_result, object call, object future):
563-
args, kwargs = future.result()
564-
result = call(*args, **kwargs)
565-
566-
if __isawaitable(result):
567-
result = asyncio.ensure_future(result)
568-
result.add_done_callback(functools.partial(__async_result_callback, future_result))
569-
return
570-
571-
future_result.set_result(result)
574+
try:
575+
args, kwargs = future.result()
576+
result = call(*args, **kwargs)
577+
except Exception as exception:
578+
future_result.set_exception(exception)
579+
else:
580+
if __isawaitable(result):
581+
result = asyncio.ensure_future(result)
582+
result.add_done_callback(functools.partial(__async_result_callback, future_result))
583+
return
584+
future_result.set_result(result)
572585

573586

574587
cdef inline object __async_result_callback(object future_result, object future):
575-
future_result.set_result(future.result())
588+
try:
589+
result = future.result()
590+
except Exception as exception:
591+
future_result.set_exception(exception)
592+
else:
593+
future_result.set_result(result)
576594

577595

578596
cdef inline object __callable_call(Callable self, tuple args, dict kwargs):

src/dependency_injector/providers.pyx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3486,7 +3486,6 @@ cdef class Resource(Provider):
34863486
resource = initializer.result()
34873487
except Exception:
34883488
self.__initialized = False
3489-
raise
34903489
else:
34913490
self.__resource = resource
34923491
self.__shutdowner = shutdowner

tests/unit/providers/test_async_py36.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,64 @@ class ContainerWithArgsAndKwArgs(containers.DeclarativeContainer):
190190

191191
self.assertIsNot(service1.client, service2.client)
192192

193+
def test_injection_error(self):
194+
async def init_resource():
195+
raise Exception('Something went wrong')
196+
197+
class Container(containers.DeclarativeContainer):
198+
resource_with_error = providers.Resource(init_resource)
199+
200+
client = providers.Factory(
201+
Client,
202+
resource1=resource_with_error,
203+
resource2=None,
204+
)
205+
206+
container = Container()
207+
208+
with self.assertRaises(Exception) as context:
209+
self._run(container.client())
210+
self.assertEqual(str(context.exception), 'Something went wrong')
211+
212+
def test_injection_runtime_error_async_provides(self):
213+
async def create_client(*args, **kwargs):
214+
raise Exception('Something went wrong')
215+
216+
class Container(containers.DeclarativeContainer):
217+
resource = providers.Resource(init_resource, providers.Object(RESOURCE1))
218+
219+
client = providers.Factory(
220+
create_client,
221+
resource1=resource,
222+
resource2=None,
223+
)
224+
225+
container = Container()
226+
227+
with self.assertRaises(Exception) as context:
228+
self._run(container.client())
229+
self.assertEqual(str(context.exception), 'Something went wrong')
230+
231+
def test_injection_call_error_async_provides(self):
232+
async def create_client(): # <-- no args defined
233+
...
234+
235+
class Container(containers.DeclarativeContainer):
236+
resource = providers.Resource(init_resource, providers.Object(RESOURCE1))
237+
238+
client = providers.Factory(
239+
create_client,
240+
resource1=resource,
241+
resource2=None,
242+
)
243+
244+
container = Container()
245+
246+
with self.assertRaises(TypeError) as context:
247+
self._run(container.client())
248+
self.assertIn("create_client() got", str(context.exception))
249+
self.assertIn("unexpected keyword argument", str(context.exception))
250+
193251
def test_attributes_injection(self):
194252
class ContainerWithAttributes(containers.DeclarativeContainer):
195253
resource1 = providers.Resource(init_resource, providers.Object(RESOURCE1))
@@ -236,6 +294,53 @@ class ContainerWithAttributes(containers.DeclarativeContainer):
236294

237295
self.assertIsNot(service1.client, service2.client)
238296

297+
def test_attributes_injection_attribute_error(self):
298+
class ClientWithException(Client):
299+
@property
300+
def attribute_set_error(self):
301+
return None
302+
303+
@attribute_set_error.setter
304+
def attribute_set_error(self, value):
305+
raise Exception('Something went wrong')
306+
307+
class Container(containers.DeclarativeContainer):
308+
resource = providers.Resource(init_resource, providers.Object(RESOURCE1))
309+
310+
client = providers.Factory(
311+
ClientWithException,
312+
resource1=resource,
313+
resource2=resource,
314+
)
315+
client.add_attributes(attribute_set_error=123)
316+
317+
container = Container()
318+
319+
with self.assertRaises(Exception) as context:
320+
self._run(container.client())
321+
self.assertEqual(str(context.exception), 'Something went wrong')
322+
323+
def test_attributes_injection_runtime_error(self):
324+
async def init_resource():
325+
raise Exception('Something went wrong')
326+
327+
class Container(containers.DeclarativeContainer):
328+
resource = providers.Resource(init_resource)
329+
330+
client = providers.Factory(
331+
Client,
332+
resource1=None,
333+
resource2=None,
334+
)
335+
client.add_attributes(resource1=resource)
336+
client.add_attributes(resource2=resource)
337+
338+
container = Container()
339+
340+
with self.assertRaises(Exception) as context:
341+
self._run(container.client())
342+
self.assertEqual(str(context.exception), 'Something went wrong')
343+
239344
def test_async_instance_and_sync_attributes_injection(self):
240345
class ContainerWithAttributes(containers.DeclarativeContainer):
241346
resource1 = providers.Resource(init_resource, providers.Object(RESOURCE1))

tests/unit/providers/test_resource_py35.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -449,14 +449,45 @@ async def _init():
449449
self.assertTrue(provider.initialized)
450450
self.assertTrue(provider.is_async_mode_enabled())
451451

452-
# Disable default exception handling to prevent output
453-
asyncio.get_event_loop().set_exception_handler(lambda loop, context: ...)
452+
with self.assertRaises(RuntimeError):
453+
self._run(future)
454+
455+
self.assertFalse(provider.initialized)
456+
self.assertTrue(provider.is_async_mode_enabled())
457+
458+
def test_init_async_gen_with_error(self):
459+
async def _init():
460+
raise RuntimeError()
461+
yield
462+
463+
provider = providers.Resource(_init)
464+
465+
future = provider()
466+
self.assertTrue(provider.initialized)
467+
self.assertTrue(provider.is_async_mode_enabled())
454468

455469
with self.assertRaises(RuntimeError):
456470
self._run(future)
457471

458-
# Restore default exception handling
459-
asyncio.get_event_loop().set_exception_handler(None)
472+
self.assertFalse(provider.initialized)
473+
self.assertTrue(provider.is_async_mode_enabled())
474+
475+
def test_init_async_subclass_with_error(self):
476+
class _Resource(resources.AsyncResource):
477+
async def init(self):
478+
raise RuntimeError()
479+
480+
async def shutdown(self, resource):
481+
pass
482+
483+
provider = providers.Resource(_Resource)
484+
485+
future = provider()
486+
self.assertTrue(provider.initialized)
487+
self.assertTrue(provider.is_async_mode_enabled())
488+
489+
with self.assertRaises(RuntimeError):
490+
self._run(future)
460491

461492
self.assertFalse(provider.initialized)
462493
self.assertTrue(provider.is_async_mode_enabled())

0 commit comments

Comments
 (0)