Coverage for src/diy/errors.py: 66%

1 from __future__ import annotations

2

3 from typing import Any

4

5

6 class DiyError(Exception):

7 pass

8

9

10 class UninstanciableTypeError(DiyError):

11 abstract: type[Any]

12

13 def __init__(self, abstract: type[Any]) -> None:

14 message = f"Can't instantiate type '{abstract.__qualname__}', since it's __init__ method does not accept 'self' as its first argument!"

15 super().__init__(message)

16 self.abstract = abstract

17

18

19 class UnsupportedParameterTypeError(DiyError):

20 pass

21

22

23 class UnresolvableDependencyError(DiyError):

24 """

25 Gets thrown when a :class:`Container` tries to instantiate a type, but not

26 all requirements can be automatically resolved.

27

28 ## Example

29

30 Imagine we want to issue HTTP requests to an external API. As a security

31 measure, that API requires us to send a secret token in a header:

32 ```python

33 class ApiClient:

34 def __init__(self, auth_token: str):

35 self.auth_token = auth_token

36

37 def request_things():

38 url = "http://my-api.com/things"

39 return requests.get(url, headers={token: self.auth_token}).json()

40 ```

41

42 if we don't tell our container what to use.

43 """

44

45 def __init__(self, abstract: type[Any], known: list[type[Any]]) -> None:

46 super().__init__(f"Failed to resolve an instance of '{abstract.__qualname__}'")

47 self.abstract = abstract

48 enumeration = "\n".join([f"- {x.__qualname__}" for x in known])

49 note = f"Known types are:\n{enumeration}"

50 self.add_note(note)

51

52

53 class MissingReturnTypeAnnotationError(DiyError):

54 """

55 Gets thrown when trying to register a builder function for a

56 :class:`Specification`, but the function is missing a return type

57 annotation.

58

59 In this case, we can not identify which type the function tries to build,

60 without leveraging extensive mechanisms such as parsing its body

61 on-the-fly. This would be be bad for performance and also feels a bit too

62 much.

63 """

64

65 def __init__(self) -> None:

66 message = "A builder function requires a return type annotation."

67 super().__init__(message)

68 self.add_note(

69 """If you e.g. define a function

70 @spec.builder

71 def my_builder():

72 return MyType()

73

74 you can add a return type like this:

75

76 @spec.builder

77 def my_builder() -> MyType:

78 return MyType()

79

80 diy can't infer this without extensive measures, so you as a user are required

81 to provide proper annotations.

82"""

83 )

84

85

86 class MissingConstructorKeywordArgumentError(DiyError):

87 def __init__(self, abstract: type[Any], name: str) -> None:

88 message = f"Tried to register partial builder for parameter '{abstract.__qualname__}'::'{name}' of type , but its __init__ function does not have a keyword argument named '{name}'!"

89 super().__init__(message)

90

91

92 class MissingConstructorKeywordTypeAnnotationError(DiyError):

93 def __init__(self, abstract: type[Any], name: str) -> None:

94 message = f"Tried to build an instance of '{abstract.__qualname__}', but the '{name}' parameter is missing a type annotation."

95 super().__init__(message)

96 self.add_note(

97 "Either register an explicit builder function via diy.Specification.builders.add, or provide a type annotation for a type that is already known or can be automatically resolved."

98 )

99

100

101 class InvalidConstructorKeywordArgumentError(DiyError):

102 def __init__(

103 self, abstract: type[Any], name: str, provided: type[Any], required: type[Any]

104 ) -> None:

105 target = f"'{abstract.__qualname__}::{name}"

106 message = f"Tried to register partial builder for '{target}'. The builder returns '{provided.__qualname__}', but '{target}' accepts '{required.__qualname__}'!"

107 super().__init__(message)