GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Passed
Push — master ( 7603ec...aaf73b )
by Raphaël
01:48
created

DependencyInjector   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 252
Duplicated Lines 0 %

Test Coverage

Coverage 79.57%

Importance

Changes 6
Bugs 0 Features 0
Metric Value
dl 0
loc 252
ccs 74
cts 93
cp 0.7957
rs 8.6
c 6
b 0
f 0
wmc 37

12 Methods

Rating   Name   Duplication   Size   Complexity  
A _get_singleton() 0 16 3
A _set_singleton() 0 14 2
A __init__() 0 4 1
B register() 0 28 2
A get_uninstantiated() 0 10 3
B _generate_arguments_dict() 0 31 4
A _is_object_init() 0 14 3
B get() 0 29 4
A has_service() 0 16 3
D _get_argument() 0 36 8
A _get_instance() 0 12 2
B register_singleton() 0 29 2
1 1
import logging
2
3 1
from inspect import signature
4 1
from inspect import Parameter
5 1
from collections import OrderedDict
6
7 1
from pyjection.service import Service
8 1
from pyjection.reference import Reference
9 1
from pyjection.helper import get_service_subject_identifier
10
11
12 1
class DependencyInjector(object):
13
14 1
    def __init__(self):
15 1
        self._logger = logging.getLogger(__name__)
16 1
        self._services = dict()
17 1
        self._singletons = dict()
18
19 1
    def register(self, service_subject, identifier=None):
20
        """
21
        Register a new service in the dependency injector
22
23
        This service can be :
24
            * A class that will be instantiated when called
25
            * An already instantiated instance that will be returned
26
27
        If no identifier is passed, it will be the class name in snake_case
28
29
        :param service_subject: The class or instance
30
        :type service_subject: mixed
31
        :param identifier: The identifier used to later retrieve a service instance
32
        :type identifier: string
33
34
        :return: Return the newly created service entry
35
        :rtype: Service
36
        """
37 1
        if identifier is None:
38 1
            identifier = get_service_subject_identifier(service_subject)
39 1
        service = Service(service_subject)
40 1
        self._services[identifier] = service
41 1
        self._logger.debug(
42
            "Class %s registered with identifier %s",
43
            str(service_subject),
44
            identifier
45
        )
46 1
        return service
47
48 1
    def register_singleton(self, service_subject, identifier=None):
49
        """
50
        Register a new singleton service in in the dependency injector
51
52
        This service can be :
53
            * A class that will be instantiated when called
54
            * An already instantiated instance that will be returned
55
56
        If no identifier is passed, it will be the class name in snake_case
57
58
        :param service_subject: The class or instance
59
        :type service_subject: mixed
60
        :param identifier: The identifier used to later retrieve a service singleton
61
        :type identifier: string
62
63
        :return: Return the newly created dependency entry
64
        :rtype: Service
65
        """
66 1
        if identifier is None:
67 1
            identifier = get_service_subject_identifier(service_subject)
68 1
        service = Service(service_subject)
69 1
        service.is_singleton = True
70 1
        self._services[identifier] = service
71 1
        self._logger.debug(
72
            "Class %s registered as singleton with identifier %s",
73
            str(service_subject),
74
            identifier
75
        )
76 1
        return service
77
78 1
    def get(self, identifier):
79
        """
80
        Instantiate and retrieve the service matching this identifier
81
82
        If the service has been self has a singleton the same service object
83
        will be return each time this service is asked
84
85
        :param identifier: The identifier or the class to retrieve
86
        :type identifier: mixed
87
        :return: The instantiated object
88
        :rtype: mixed
89
        """
90 1
        if isinstance(identifier, str) is False:
91 1
            identifier = get_service_subject_identifier(identifier)
92
93 1
        if self.has_service(identifier) is False:
94 1
            self._logger.error("No service has been declared with ID %s", identifier)
95 1
            raise Exception("No service has been declared with this ID")
96
97 1
        service = self._services[identifier]
98 1
        instance = self._get_singleton(identifier, service)
99 1
        if instance is not None:
100 1
            self._logger.debug("Return singleton with ID %s", identifier)
101 1
            return instance
102
103 1
        instance = self._get_instance(service)
104 1
        self._set_singleton(identifier, instance, service)
105 1
        self._logger.debug("Return instance with ID %s", identifier)
106 1
        return instance
107
108 1
    def get_uninstantiated(self, identifier):
109
        if isinstance(identifier, str) is False:
110
            identifier = get_service_subject_identifier(identifier)
111
112
        if self.has_service(identifier) is False:
113
            self._logger.error("No service has been declared with ID %s", identifier)
114
            raise Exception("No service has been declared with this ID")
115
116
        service = self._services[identifier]
117
        return service.subject
118
119 1
    def has_service(self, identifier):
120
        """
121
        Check if the service matching the given identifier
122
        has already been declared
123
124
        :param identifier: Name of the service or the class
125
        :type identifier: mixed
126
        :return: Whether or not the service exists
127
        :rtype: boolean
128
        """
129 1
        if isinstance(identifier, str) is False:
130 1
            identifier = get_service_subject_identifier(identifier)
131
132 1
        if identifier in self._services:
133 1
            return True
134 1
        return False
135
136 1
    def _get_singleton(self, identifier, service):
137
        """
138
        Return the singleton if it has been setted and
139
        the service represents a singleton
140
141
        :param identifier: the singleton identifier
142
        :param service: The service we need the singleton for
143
        :type identifier: string
144
        :type service: Service
145
146
        :return: The singleton instance or None
147
        :rtype: mixed
148
        """
149 1
        if service.is_singleton is True and identifier in self._singletons:
150 1
            return self._singletons[identifier]
151 1
        return None
152
153 1
    def _set_singleton(self, identifier, instance, service):
154
        """
155
        Set the instance as a singleton in the dict
156
        if the service represents a singleton
157
158
        :param identifier: the singleton identifier
159
        :param service: The service we want to set a singleton for
160
        :param instance: The singleton instance
161
        :type identifier: string
162
        :type service: Service
163
        :type instance: mixed
164
        """
165 1
        if service.is_singleton is True:
166 1
            self._singletons[identifier] = instance
167
168 1
    def _get_instance(self, service):
169
        """
170
        Return the instantiated object for the given service
171
172
        :param service: The service we need an instance for
173
        :type service: Service
174
        :return: The instantiated object
175
        """
176 1
        if service.type == 'instance':
177 1
            return service.subject
178 1
        arguments = self._generate_arguments_dict(service)
179 1
        return service.subject(**arguments)
180
181 1
    def _generate_arguments_dict(self, service):
182
        """
183
        Generate a dict containing all the parameters values
184
        required to Instantiate the service.
185
186
        An exception is raised if a mandatory argument cannot be
187
        retrieved.
188
189
        :param service: The service that needs to be instantiated
190
        :type service: Service
191
        :return: The parameters values to use to instantiate the service
192
        :rtype: dict
193
        """
194 1
        arguments = dict()
195
196
        # We can't use signature on object __init__
197 1
        if self._is_object_init(service.subject) is True:
198
            return arguments
199
200 1
        sig = signature(service.subject.__init__)
201 1
        method_parameters = OrderedDict(sig.parameters)
202
203
        # Pop the first param since it's the self class instance
204 1
        method_parameters.popitem(False)
205
206 1
        for method_parameter in method_parameters.values():
207 1
            argument = self._get_argument(service, method_parameter)
208 1
            if argument is not None:
209
                arguments[method_parameter.name] = argument
210
211 1
        return arguments
212
213 1
    def _is_object_init(self, subject):
214
        """
215
        Check if the __init__ method for the object comes from
216
        the default object or has been overridden
217
218
        :param subject: The subject we want to check the __init__ for
219
        :type subject: mixed
220
221
        :return: Whether the __init__ method is the default on or not
222
        :rtype: boolean
223
        """
224 1
        if '__objclass__' in dir(subject.__init__) and subject.__init__.__objclass__ == object:
225
            return True
226 1
        return False
227
228 1
    def _get_argument(self, service, method_parameter):
229
        """
230
        Retrieve the argument value for the given service
231
232
        :param service: The service we need an argument for
233
        :param method_parameter: The parameter we need the value for
234
        :type service: Service
235
        :type method_parameter: Parameter
236
        :return: The argument value
237
        :rtype: mixed
238
        """
239
        # First check if we specified this argument for the service
240 1
        if method_parameter.name in service.arguments:
241
            value = service.arguments[method_parameter.name]
242
            # The value references an other dependency service
243
            if isinstance(value, Reference):
244
                if value.return_class == True:
245
                    return self.get_uninstantiated(value.name)
246
                else:
247
                    return self.get(value.name)
248
            return value
249
250
        # Then check if another service has this name
251 1
        if self.has_service(method_parameter.name):
252
            return self.get(method_parameter.name)
253
254
        # If the parameter is *args or **kwargs then we don't raise any exception
255 1
        if method_parameter.kind == Parameter.VAR_POSITIONAL or \
256
                method_parameter.kind == Parameter.VAR_KEYWORD:
257 1
            return None
258
        # If the parameter has a default value then we don't raise any exception
259 1
        if method_parameter.default is not Parameter.empty:
260 1
            return None
261
262
        self._logger.error("A required argument is not set: %s", method_parameter.name)
263
        raise Exception("A required argument is not set (%s)" % method_parameter.name)
264