Coverage for tests/container_test.py: 98%

1 from __future__ import annotations

2

3 from typing import Any

4

5 import pytest

6

7 from diy import Container, RuntimeContainer, Specification

8 from diy.errors import UninstanciableTypeError

9

10

11 class ConstructurWithoutSelf:

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

13 pass

14

15

16 def test_container_reports_uninstantiable_types() -> None:

17 container = RuntimeContainer()

18

19 with pytest.raises(UninstanciableTypeError):

20 container.resolve(ConstructurWithoutSelf)

21

22

23 class NoConstructor:

24 pass

25

26

27 class 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)

40 def 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

48 def 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

61 def 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

73 class ApiClient:

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

75 super().__init__()

76 self.token = token

77

78

79 def 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

88 class ImplicitlyResolvesApiClient:

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

90 super().__init__()

91 self.api = api

92

93

94 def 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

105 def 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