def check_varargs(name, varargs, kwargs): if varargs and kwargs: message = "{0} have arbitrary argument list and keyword arguments" raise DependencyError(message.format(name)) elif varargs: message = "{0} have arbitrary argument list" raise DependencyError(message.format(name)) elif kwargs: message = "{0} have arbitrary keyword arguments" raise DependencyError(message.format(name))
def _check_argument_default(argument, value, owner): expect_class = argument.endswith("_class") is_class = isclass(value) if expect_class and not is_class: message = "{0!r} default value should be a class" raise DependencyError(message.format(argument)) if not expect_class and is_class: message = default_class_value_template.format( owner=owner, argument=argument, value=value.__name__ ) raise DependencyError(message)
def check_cls_arguments(argnames, defaults, owner_message): for argument, value in zip(reversed(argnames), reversed(defaults)): expect_class = argument.endswith("_class") is_class = inspect.isclass(value) if expect_class and not is_class: message = "{0!r} default value should be a class" raise DependencyError(message.format(argument)) if not expect_class and is_class: message = default_class_value_template.format( owner_message=owner_message, argument=argument, value=value.__name__) raise DependencyError(message)
def _args(func, funcname, owner): args = [] for name, param in signature(func).parameters.items(): have_default = param.default is not param.empty args.append((name, have_default)) if have_default: _check_argument_default(name, param.default, owner) if param.kind is param.VAR_POSITIONAL: raise DependencyError( f"{funcname!r} have variable-length positional arguments" ) if param.kind is param.VAR_KEYWORD: raise DependencyError( f"{funcname!r} have variable-length keyword arguments" ) return args
def _check_expression(expression): if not any( symbol for operator, symbol in expression if operator == "." and symbol != "__parent__" ): raise DependencyError("You can not use 'this' directly in the 'Injector'")
def _check_expression(dependency): if not any( symbol for kind, symbol in dependency.__expression__ if kind == "." and symbol != "__parent__" ): raise DependencyError("You can not use 'this' directly in the 'Injector'")
def _get_attribute(instance, name): try: return getattr(instance, name) except DependencyError: if name == "__parent__": raise DependencyError( "You tried to shift this more times than Injector has levels" ) else: raise
def __getattr__(cls, attrname): __tracebackhide__ = True cache, cached = {"__self__": cls}, {"__self__"} current_attr, attrs_stack = attrname, [attrname] have_default = False while attrname not in cache: spec = cls.__dependencies__.get(current_attr) if spec is None: if have_default: cached.add(current_attr) current_attr = attrs_stack.pop() have_default = False continue if len(attrs_stack) > 1: message = "{!r} can not resolve attribute {!r} while building {!r}".format( # noqa: E501 cls.__name__, current_attr, attrs_stack.pop()) else: message = "{!r} can not resolve attribute {!r}".format( cls.__name__, current_attr) raise DependencyError(message) marker, attribute, args, have_defaults = spec if set(args).issubset(cached): kwargs = {k: cache[k] for k in args if k in cache} try: cache[current_attr] = attribute(**kwargs) except _Replace as replace: _deep_replace_dependency(cls, current_attr, replace) _check_loops(cls.__name__, cls.__dependencies__) _check_circles(cls.__dependencies__) continue cached.add(current_attr) current_attr = attrs_stack.pop() have_default = False continue for n, arg in enumerate(args, 1): if arg not in cached: attrs_stack.append(current_attr) current_attr = arg have_default = False if n < have_defaults else True break return cache[attrname]
def _check_circles_for(dependencies, attrname, origin): try: argspec = dependencies[attrname] except KeyError: return if argspec[0] is injectable: args = argspec[2] if origin in args: message = "{0!r} is a circular dependency in the {1!r} constructor" raise DependencyError(message.format(origin, argspec[1].__name__)) for name in args: _check_circles_for(dependencies, name, origin)
def is_optional(self, spec): if spec is not None: return False if self.state.have_default: self.state.pop() return True if self.state.full(): message = "{!r} can not resolve attribute {!r} while building {!r}".format( self.injector.__name__, self.state.current, self.state.stack.pop()[0]) else: message = "{!r} can not resolve attribute {!r}".format( self.injector.__name__, self.state.current) raise DependencyError(message)
def __call__(self, __self__): result = __self__ for kind, symbol in self.dependency.__expression__: if kind == ".": try: result = getattr(result, symbol) except DependencyError: message = ( "You tried to shift this more times than Injector has levels" ) if symbol == "__parent__": raise DependencyError(message) else: raise elif kind == "[]": result = result[symbol] return result
def check_loops_for(class_name, attribute_name, dependencies, origin, expression): try: attrname = next(expression) except StopIteration: return try: spec = dependencies[attrname] except KeyError: return if spec[0] is nested_injector: check_loops_for( class_name, attribute_name, nested_dependencies(dependencies, spec), origin, expression, ) elif attrname == "__parent__": from weakref import ReferenceType if isinstance(spec[1], ReferenceType): # FIXME: This is an ad-hoc solution for the broken # `Replace` problem. See `dependencies._injector` comment # for more info. resolved_parent = spec[1]().__dependencies__ else: resolved_parent = spec[1] check_loops_for(class_name, attribute_name, resolved_parent, origin, expression) elif spec is origin: message = "{0!r} is a circle link in the {1!r} injector" raise DependencyError(message.format(attribute_name, class_name)) elif spec[0] is this: check_loops_for(class_name, attribute_name, dependencies, origin, filter_expression(spec))
def _check_loops_for(class_name, attribute_name, dependencies, origin, expression): try: attrname = next(expression) except StopIteration: return try: spec = dependencies[attrname] except KeyError: return if spec[0] is nested_injector: _check_loops_for( class_name, attribute_name, _nested_dependencies(dependencies, spec), origin, expression, ) elif attrname == "__parent__": from weakref import ReferenceType if isinstance(spec[1], ReferenceType): resolved_parent = spec[1]().__dependencies__ else: resolved_parent = spec[1] _check_loops_for( class_name, attribute_name, resolved_parent, origin, expression ) elif spec is origin: message = "{0!r} is a circle link in the {1!r} injector" raise DependencyError(message.format(attribute_name, class_name)) elif spec[0] is this: _check_loops_for( class_name, attribute_name, dependencies, origin, _filter_expression(spec) )
def check_method(arguments): if "self" in arguments: raise DependencyError( "'operation' decorator can not be used on methods")
def __method(self, form): raise DependencyError( "Add {!r} to the {!r} injector".format(method, injector.__name__) )
def __getattr__(cls, attrname): __tracebackhide__ = True cache, cached = {"__self__": cls}, {"__self__"} current_attr, attrs_stack = attrname, [attrname] have_default = False while attrname not in cache: spec = cls.__dependencies__.get(current_attr) if spec is None: if have_default: # FIXME: If first dependency have this name as # default and the second one have this name # without default, we will see a very strange # KeyError about `cache` access. cached.add(current_attr) current_attr = attrs_stack.pop() have_default = False continue if len(attrs_stack) > 1: message = "{!r} can not resolve attribute {!r} while building {!r}".format( # noqa: E501 cls.__name__, current_attr, attrs_stack.pop() ) else: message = "{!r} can not resolve attribute {!r}".format( cls.__name__, current_attr ) raise DependencyError(message) marker, attribute, args, have_defaults = spec if set(args).issubset(cached): kwargs = {k: cache[k] for k in args if k in cache} try: cache[current_attr] = attribute(**kwargs) except Replace as replace: deep_replace_dependency(cls, current_attr, replace) # FIXME: # # We'll probably resolve weakref. This happens because nested # injector decide to replace a lazy import. This nested injector # is already resolved by its parent, so it contain a weakref in # it. Ideally we should not have parent in the scope at all. # # Also, `Replace` doesn't change dependencies dict of its parent, # so lazy import will be evaluated again and again. This kills # the whole point of `Replace`. check_loops(cls.__name__, cls.__dependencies__) check_circles(cls.__dependencies__) continue cached.add(current_attr) current_attr = attrs_stack.pop() have_default = False continue for n, arg in enumerate(args, 1): if arg not in cached: attrs_stack.append(current_attr) current_attr = arg have_default = False if n < have_defaults else True break return cache[attrname]
def check_inheritance(bases, injector): for base in bases: if not issubclass(base, injector): message = "Multiple inheritance is allowed for Injector subclasses only" raise DependencyError(message)
def check_attrs_redefinition(name): if name == "let": raise DependencyError("'let' redefinition is not allowed")
def check_dunder_name(name): if name.startswith("__") and name.endswith("__"): raise DependencyError("Magic methods are not allowed")
def __getattr__(cls, attrname): __tracebackhide__ = True cache, cached = {"__self__": cls}, {"__self__"} current_attr, attrs_stack = attrname, [attrname] have_default = False replaced_dependencies = {} while attrname not in cache: spec = cls.__dependencies__.get(current_attr) if spec is None: if have_default: cached.add(current_attr) current_attr = attrs_stack.pop() have_default = False continue if len(attrs_stack) > 1: message = "{!r} can not resolve attribute {!r} while building {!r}".format( # noqa: E501 cls.__name__, current_attr, attrs_stack.pop() ) else: message = "{!r} can not resolve attribute {!r}".format( cls.__name__, current_attr ) raise DependencyError(message) marker, attribute, args, have_defaults = spec if set(args).issubset(cached): kwargs = {k: cache[k] for k in args if k in cache} try: dependency = attribute(**kwargs) if ('nested' not in marker and inspect.isclass(dependency) and not current_attr.endswith("_class")): spec = _make_init_spec(dependency) replaced_dependency = _replace_dependency(cls, current_attr, spec) replaced_dependencies[current_attr] = replaced_dependency continue elif isinstance(dependency, This): spec = _make_this_spec(dependency) replaced_dependency = _replace_dependency(cls, current_attr, spec) replaced_dependencies[current_attr] = replaced_dependency continue else: cache[current_attr] = dependency except _Replace as replace: _deep_replace_dependency(cls, current_attr, replace) _check_loops(cls.__name__, cls.__dependencies__) _check_circles(cls.__dependencies__) continue cached.add(current_attr) current_attr = attrs_stack.pop() have_default = False continue for n, arg in enumerate(args, 1): if arg not in cached: attrs_stack.append(current_attr) current_attr = arg have_default = False if n < have_defaults else True break # Restore @value dependencies that returned a class from the result class # to their defining function for attr, dep in replaced_dependencies.items(): cls.__dependencies__[attr] = dep return cache[attrname]
def _is_descriptor(name, dependency): if ismethoddescriptor(dependency) or isdatadescriptor(dependency): message = descriptor_template.format(name=name) raise DependencyError(message)
def _check_class(function): if isclass(function): raise DependencyError("'value' decorator can not be used on classes")
def _check_method(arguments): if "self" in arguments: raise DependencyError("'value' decorator can not be used on methods")
def _check_extension_scope(bases, namespace): if len(bases) == 1 and not namespace: raise DependencyError("Extension scope can not be empty")
def __init__(self, *args, **kwargs): raise DependencyError("Do not instantiate Injector")
def check_class(function): if inspect.isclass(function): raise DependencyError( "'operation' decorator can not be used on classes")
def __delattr__(cls, attrname): raise DependencyError("'Injector' modification is not allowed")
def _is_enum(name, dependency): if (not name.endswith("_class") and isclass(dependency) and issubclass(dependency, Enum)): message = enum_template.format(name=name) raise DependencyError(message)