Coverage for tests/container_test.py: 98%

64 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-10 22:09 +0200

1from __future__ import annotations 

2 

3from typing import Any 

4 

5import pytest 

6 

7from diy import Container, RuntimeContainer, Specification 

8from diy.errors import UninstanciableTypeError 

9 

10 

11class ConstructurWithoutSelf: 

12 def __init__() -> None: # type: ignore[reportSelfClsParameterName] 

13 pass 

14 

15 

16def test_container_reports_uninstantiable_types() -> None: 

17 container = RuntimeContainer() 

18 

19 with pytest.raises(UninstanciableTypeError): 

20 container.resolve(ConstructurWithoutSelf) 

21 

22 

23class NoConstructor: 

24 pass 

25 

26 

27class ConstructorWithOnlySelf: 

28 def __init__(self) -> None: 

29 super().__init__() 

30 self.something = "foo" 

31 

32 

33@pytest.mark.parametrize( 

34 "abstract", 

35 [ 

36 NoConstructor, 

37 ConstructorWithOnlySelf, 

38 ], 

39) 

40def test_container_can_instantiate_constructors_that_require_no_arguments( 

41 abstract: type[Any], 

42) -> None: 

43 container = RuntimeContainer() 

44 instance = container.resolve(abstract) 

45 assert isinstance(instance, abstract) 

46 

47 

48def test_container_can_instantiate_constructors_that_only_require_default_arguments() -> ( 

49 None 

50): 

51 class ConstructorWithOneDefaultArgument: 

52 def __init__(self, name: str = "default name") -> None: 

53 super().__init__() 

54 self.name = name 

55 

56 container = RuntimeContainer() 

57 instance = container.resolve(ConstructorWithOneDefaultArgument) 

58 assert isinstance(instance, ConstructorWithOneDefaultArgument) 

59 

60 

61def test_container_actually_resolves_the_default_arguments() -> None: 

62 container = RuntimeContainer() 

63 

64 sentinel = object() 

65 

66 def my_function(my_argument: Any = sentinel): 

67 return my_argument 

68 

69 result = container.call(my_function) 

70 assert result == sentinel 

71 

72 

73class ApiClient: 

74 def __init__(self, token: str) -> None: 

75 super().__init__() 

76 self.token = token 

77 

78 

79def test_container_can_instantiate_kwargs_only_constructors() -> None: 

80 spec = Specification() 

81 spec.builders.add(ApiClient, lambda: ApiClient("test")) 

82 

83 container = RuntimeContainer(spec) 

84 instance = container.resolve(ApiClient) 

85 assert isinstance(instance, ApiClient) 

86 

87 

88class ImplicitlyResolvesApiClient: 

89 def __init__(self, api: ApiClient) -> None: 

90 super().__init__() 

91 self.api = api 

92 

93 

94def test_container_can_implicitly_resolve_argument_that_are_contained_in_the_spec() -> ( 

95 None 

96): 

97 spec = Specification() 

98 spec.builders.add(ApiClient, lambda: ApiClient("test")) 

99 

100 container = RuntimeContainer(spec) 

101 instance = container.resolve(ImplicitlyResolvesApiClient) 

102 assert isinstance(instance, ImplicitlyResolvesApiClient) 

103 

104 

105def test_it_can_inject_itself_via_protocols() -> None: 

106 spec = Specification() 

107 container = RuntimeContainer(spec) 

108 

109 # TODO: Not sure if adding to the spec _after_ handing it into a container 

110 # should have an effect on the container. Could be prevented by deep- 

111 # copying the spec in the container constructor, but for this 

112 # specific use-case, it is convenient. Though I guess it feelds more 

113 # proper to support injecting builder arguments from the container 

114 # instead. 

115 spec.builders.add(Container, lambda: container) 

116 

117 instance = container.resolve(Container) 

118 assert instance == container