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

38 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 

5 

6class DiyError(Exception): 

7 pass 

8 

9 

10class 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 

19class UnsupportedParameterTypeError(DiyError): 

20 pass 

21 

22 

23class 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 

53class 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 

74you can add a return type like this: 

75 

76 @spec.builder 

77 def my_builder() -> MyType: 

78 return MyType() 

79 

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

81to provide proper annotations. 

82""" 

83 ) 

84 

85 

86class 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 

92class 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 

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