Format the code using Black

This is in order to keep the formatting consistent in the future.
This commit is contained in:
Jakub Stasiak 2019-04-05 15:49:50 +02:00
parent 6358f5d304
commit 0d1b4a4798
6 changed files with 280 additions and 239 deletions

View File

@ -16,12 +16,12 @@ import injector
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
# sys.path.insert(0, os.path.abspath('.'))
# -- General configuration -----------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be extensions
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
@ -34,7 +34,7 @@ templates_path = ['_templates']
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
@ -54,40 +54,40 @@ release = version
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents.
#default_role = None
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False
# keep_warnings = False
# -- Options for HTML output ---------------------------------------------------
@ -99,26 +99,26 @@ html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
@ -127,48 +127,47 @@ html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
html_sidebars = {
'index': ('sidebar.html', 'sourcelink.html', 'searchbox.html'),
'**' : ('localtoc.html', 'relations.html',
'sourcelink.html', 'searchbox.html'),
'index': ('sidebar.html', 'sourcelink.html', 'searchbox.html'),
'**': ('localtoc.html', 'relations.html', 'sourcelink.html', 'searchbox.html'),
}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'Injectordoc'
@ -177,55 +176,47 @@ htmlhelp_basename = 'Injectordoc'
# -- Options for LaTeX output --------------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual]).
latex_documents = [
('index', 'Injector.tex', u'Injector Documentation',
u'Alec Thomas', 'manual'),
]
latex_documents = [('index', 'Injector.tex', u'Injector Documentation', u'Alec Thomas', 'manual')]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# latex_domain_indices = True
# -- Options for manual page output --------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
('index', 'injector', u'Injector Documentation',
[u'Alec Thomas'], 1)
]
man_pages = [('index', 'injector', u'Injector Documentation', [u'Alec Thomas'], 1)]
# If true, show URL addresses after external links.
#man_show_urls = False
# man_show_urls = False
# -- Options for Texinfo output ------------------------------------------------
@ -234,22 +225,28 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
('index', 'Injector', u'Injector Documentation',
u'Alec Thomas', 'Injector', 'One line description of project.',
'Miscellaneous'),
(
'index',
'Injector',
u'Injector Documentation',
u'Alec Thomas',
'Injector',
'One line description of project.',
'Miscellaneous',
)
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False
# texinfo_no_detailmenu = False
# Example configuration for intersphinx: refer to the Python standard library.
@ -257,13 +254,13 @@ intersphinx_mapping = {'http://docs.python.org/': None}
def setup(app):
app.connect('autodoc-skip-member', skip_member)
app.connect('autodoc-skip-member', skip_member)
def skip_member(app, what, name, obj, skip, options):
return (
skip or
getattr(obj, '__doc__', None) is None or
getattr(obj, '__private__', False) is True or
getattr(getattr(obj, '__func__', None), '__private__', False) is True
)
return (
skip
or getattr(obj, '__doc__', None) is None
or getattr(obj, '__private__', False) is True
or getattr(getattr(obj, '__func__', None), '__private__', False) is True
)

View File

@ -53,9 +53,12 @@ def synchronized(lock):
def wrapper(*args, **kwargs):
with lock:
return function(*args, **kwargs)
return wrapper
return outside_wrapper
lock = threading.RLock()
@ -76,8 +79,7 @@ class UnsatisfiedRequirement(Error):
def __str__(self):
on = '%s has an ' % _describe(self.args[0]) if self.args[0] else ''
return '%sunsatisfied requirement on %s' % (
on, _describe(self.args[1].interface),)
return '%sunsatisfied requirement on %s' % (on, _describe(self.args[1].interface))
class CallError(Error):
@ -98,12 +100,17 @@ class CallError(Error):
full_method = '.'.join((cls, method_name)).strip('.')
parameters = ', '.join(itertools.chain(
(repr(arg) for arg in args),
('%s=%r' % (key, value) for (key, value) in kwargs.items())
))
parameters = ', '.join(
itertools.chain(
(repr(arg) for arg in args), ('%s=%r' % (key, value) for (key, value) in kwargs.items())
)
)
return 'Call to %s(%s) failed: %s (injection stack: %r)' % (
full_method, parameters, original_error, [level[0] for level in stack])
full_method,
parameters,
original_error,
[level[0] for level in stack],
)
class CircularDependency(Error):
@ -197,7 +204,6 @@ class InstanceProvider(Provider):
return '%s(%r)' % (type(self).__name__, self._instance)
@private
class ListOfProviders(Provider):
"""Provide a list of instances via other Providers."""
@ -241,13 +247,11 @@ class BindingKey(tuple):
def create(cls, what: Any) -> 'BindingKey':
if isinstance(what, list):
if len(what) != 1:
raise Error('list bindings must have a single interface '
'element')
raise Error('list bindings must have a single interface ' 'element')
what = (list, BindingKey.create(what[0]))
elif isinstance(what, dict):
if len(what) != 1:
raise Error('dictionary bindings must have a single interface '
'key and value')
raise Error('dictionary bindings must have a single interface ' 'key and value')
what = (dict, BindingKey.create(list(what.items())[0]))
return tuple.__new__(cls, (what,))
@ -295,8 +299,7 @@ class Binder:
if type(interface) is type and issubclass(interface, (BaseMappingKey, BaseSequenceKey)):
return self.multibind(interface, to, scope=scope)
key = BindingKey.create(interface)
self._bindings[key] = \
self.create_binding(interface, to, scope)
self._bindings[key] = self.create_binding(interface, to, scope)
def bind_scope(self, scope):
"""Bind a Scope.
@ -326,9 +329,7 @@ class Binder:
"""
key = BindingKey.create(interface)
if key not in self._bindings:
if (
isinstance(interface, dict) or
isinstance(interface, type) and issubclass(interface, dict)):
if isinstance(interface, dict) or isinstance(interface, type) and issubclass(interface, dict):
provider = MapBindProvider()
else:
provider = MultiBindProvider()
@ -372,17 +373,18 @@ class Binder:
binder.install(MyModule)
"""
if (
hasattr(module, '__bindings__') or
hasattr(getattr(module, 'configure', None), '__bindings__') or
hasattr(getattr(module, '__init__', None), '__bindings__')
hasattr(module, '__bindings__')
or hasattr(getattr(module, 'configure', None), '__bindings__')
or hasattr(getattr(module, '__init__', None), '__bindings__')
):
warnings.warn(
'Injector Modules (ie. %s) constructors and their configure methods should '
'not have injectable parameters. This can result in non-deterministic '
'initialization. If you need injectable objects in order to provide something '
' you can use @provider-decorated methods. \n'
' This feature will be removed in next Injector release.' %
module.__name__ if hasattr(module, '__name__') else module.__class__.__name__,
' This feature will be removed in next Injector release.' % module.__name__
if hasattr(module, '__name__')
else module.__class__.__name__,
RuntimeWarning,
stacklevel=3,
)
@ -390,11 +392,7 @@ class Binder:
instance = self.injector.create_object(module)
instance(self)
else:
self.injector.call_with_injection(
callable=module,
self_=None,
args=(self,),
)
self.injector.call_with_injection(callable=module, self_=None, args=(self,))
def create_binding(self, interface, to=None, scope=None):
provider = self.provider_for(interface, to)
@ -420,13 +418,21 @@ class Binder:
return to
elif isinstance(interface, Provider):
return interface
elif isinstance(to, (types.FunctionType, types.LambdaType,
types.MethodType, types.BuiltinFunctionType,
types.BuiltinMethodType)):
elif isinstance(
to,
(
types.FunctionType,
types.LambdaType,
types.MethodType,
types.BuiltinFunctionType,
types.BuiltinMethodType,
),
):
return CallableProvider(to)
elif issubclass(type(to), type):
return ClassProvider(to)
elif isinstance(interface, BoundKey):
def proxy(**kwargs):
return interface.interface(**kwargs)
@ -436,11 +442,7 @@ class Binder:
(target,) = interface.__args__
builder = interface(self.injector, target)
return InstanceProvider(builder)
elif (
isinstance(base_type, (tuple, type)) and
interface is not Any and
isinstance(to, base_type)
):
elif isinstance(base_type, (tuple, type)) and interface is not Any and isinstance(to, base_type):
return InstanceProvider(to)
elif issubclass(type(interface), type) or isinstance(interface, (tuple, list)):
if to is not None:
@ -450,8 +452,7 @@ class Binder:
elif hasattr(interface, '__call__'):
raise TypeError('Injecting partially applied functions is no longer supported.')
else:
raise UnknownProvider('couldn\'t determine provider for %r to %r' %
(interface, to))
raise UnknownProvider('couldn\'t determine provider for %r to %r' % (interface, to))
def _get_binding(self, key, *, only_this_binder: bool = False):
binding = self._bindings.get(key)
@ -484,13 +485,11 @@ class Binder:
# "Special" interfaces are ones that you cannot bind yourself but
# you can request them (for example you cannot bind ProviderOf(SomeClass)
# to anything but you can inject ProviderOf(SomeClass) just fine
return any(
_is_specialization(interface, cls)
for cls in [AssistedBuilder, ProviderOf]
)
return any(_is_specialization(interface, cls) for cls in [AssistedBuilder, ProviderOf])
if TYPING353:
def _is_specialization(cls, generic_class):
# Starting with typing 3.5.3/Python 3.6 it is no longer necessarily true that
# issubclass(SomeGeneric[X], SomeGeneric) so we need some other way to
@ -508,6 +507,7 @@ if TYPING353:
# by design).
return origin is generic_class or issubclass(origin, generic_class)
else:
# To maintain compatibility we fall back to an issubclass check.
def _is_specialization(cls, generic_class):
@ -549,9 +549,7 @@ class ScopeDecorator:
cls.__scope__ = self.scope
binding = getattr(cls, '__binding__', None)
if binding:
new_binding = Binding(interface=binding.interface,
provider=binding.provider,
scope=self.scope)
new_binding = Binding(interface=binding.interface, provider=binding.provider, scope=self.scope)
setattr(cls, '__binding__', new_binding)
return cls
@ -561,6 +559,7 @@ class ScopeDecorator:
class NoScope(Scope):
"""An unscoped provider."""
def __init__(self, injector=None):
super(NoScope, self).__init__(injector)
@ -585,6 +584,7 @@ class SingletonScope(Scope):
>>> a is b
True
"""
def configure(self):
self._context = {}
@ -603,6 +603,7 @@ singleton = ScopeDecorator(SingletonScope)
class ThreadLocalScope(Scope):
"""A :class:`Scope` that returns a per-thread instance for a key."""
def configure(self):
self._locals = threading.local()
@ -629,9 +630,7 @@ class Module:
if hasattr(function, '__binding__'):
binding = function.__binding__
binder.bind(
binding.interface,
to=types.MethodType(binding.provider, self),
scope=binding.scope,
binding.interface, to=types.MethodType(binding.provider, self), scope=binding.scope
)
self.configure(binder)
@ -725,8 +724,9 @@ class Injector:
scope_binding, _ = binder.get_binding(None, scope_key)
scope_instance = scope_binding.provider.get(self)
log.debug('%sInjector.get(%r, scope=%r) using %r',
self._log_prefix, interface, scope, binding.provider)
log.debug(
'%sInjector.get(%r, scope=%r) using %r', self._log_prefix, interface, scope, binding.provider
)
result = scope_instance.get(key, binding.provider).get(self)
log.debug('%s -> %r', self._log_prefix, result)
return result
@ -742,19 +742,21 @@ class Injector:
try:
instance = cls.__new__(cls)
except TypeError as e:
reraise(e, CallError(
cls,
getattr(cls.__new__, '__func__', cls.__new__),
(), {}, e, self._stack,),
maximum_frames=2)
reraise(
e,
CallError(cls, getattr(cls.__new__, '__func__', cls.__new__), (), {}, e, self._stack),
maximum_frames=2,
)
try:
self.install_into(instance, _internal = True)
self.install_into(instance, _internal=True)
installed = True
except AttributeError:
installed = False
if hasattr(instance, '__slots__'):
raise Error('Can\'t create an instance of type %r due to presence of __slots__, '
'remove __slots__ to fix that' % (cls,))
raise Error(
'Can\'t create an instance of type %r due to presence of __slots__, '
'remove __slots__ to fix that' % (cls,)
)
# Else do nothing - some builtin types can not be modified.
try:
@ -764,17 +766,23 @@ class Injector:
except TypeError as e:
# The reason why getattr() fallback is used here is that
# __init__.__func__ apparently doesn't exist for Key-type objects
reraise(e, CallError(
instance,
getattr(instance.__init__, '__func__', instance.__init__),
(), additional_kwargs, e, self._stack,)
reraise(
e,
CallError(
instance,
getattr(instance.__init__, '__func__', instance.__init__),
(),
additional_kwargs,
e,
self._stack,
),
)
return instance
finally:
if installed:
self._uninstall_from(instance)
def install_into(self, instance, *, _internal = False):
def install_into(self, instance, *, _internal=False):
"""Put injector reference in given object.
This method has, in general, two applications:
@ -836,6 +844,7 @@ class Injector:
:type kwargs: dict of string -> object
:return: Value returned by callable.
"""
def _get_callable_bindings(callable):
if not hasattr(callable, '__bindings__'):
return {}
@ -847,23 +856,18 @@ class Injector:
bindings = _get_callable_bindings(callable)
noninjectables = getattr(callable, '__noninjectables__', set())
needed = dict(
(k, v) for (k, v) in bindings.items()
if k not in kwargs and k not in noninjectables
)
needed = dict((k, v) for (k, v) in bindings.items() if k not in kwargs and k not in noninjectables)
dependencies = self.args_to_inject(
function=callable,
bindings=needed,
owner_key=self_.__class__ if self_ is not None else callable.__module__,
)
)
dependencies.update(kwargs)
try:
return callable(
*((self_,) if self_ is not None else ()) + tuple(args),
**dependencies)
return callable(*((self_,) if self_ is not None else ()) + tuple(args), **dependencies)
except TypeError as e:
reraise(e, CallError(self_, callable, args, dependencies, e, self._stack))
@ -890,8 +894,8 @@ class Injector:
if key in self._stack:
raise CircularDependency(
'circular dependency detected: %s -> %s' %
(' -> '.join(map(repr_key, self._stack)), repr_key(key))
'circular dependency detected: %s -> %s'
% (' -> '.join(map(repr_key, self._stack)), repr_key(key))
)
self._stack += (key,)
@ -964,7 +968,7 @@ def with_injector(*injector_args, **injector_kwargs):
@functools.wraps(f)
def setup(self_, *args, **kwargs):
injector = Injector(*injector_args, **injector_kwargs)
injector.install_into(self_, _internal = True)
injector.install_into(self_, _internal=True)
return f(self_, *args, **kwargs)
return setup
@ -1039,13 +1043,12 @@ def inject(function=None, **bindings):
raise AssertionError(
'Passing keyword arguments to inject is no longer supported. '
'Use inject in combination with parameter annotations to declare dependencies. '
'See documentation for details',
'See documentation for details'
)
if not function:
raise AssertionError(
'No function being decorated, make sure you decorate your function with '
'@inject, not @inject()',
'No function being decorated, make sure you decorate your function with ' '@inject, not @inject()'
)
try:
@ -1078,23 +1081,25 @@ def noninjectable(*args):
:func:`inject` and :func:`noninjectable`
doesn't matter.
"""
def decorator(function):
argspec = inspect.getfullargspec(inspect.unwrap(function))
for arg in args:
if arg not in argspec.args and arg not in argspec.kwonlyargs:
raise UnknownArgument('Unable to mark unknown argument %s '
'as non-injectable.' % arg)
raise UnknownArgument('Unable to mark unknown argument %s ' 'as non-injectable.' % arg)
existing = getattr(function, '__noninjectables__', set())
merged = existing | set(args)
function.__noninjectables__ = merged
return function
return decorator
def method_wrapper(f, bindings):
argspec = inspect.getfullargspec(f)
if argspec.args and argspec.args[0] == 'self':
@functools.wraps(f)
def inject(self_, *args, **kwargs):
try:
@ -1102,12 +1107,7 @@ def method_wrapper(f, bindings):
except RuntimeError:
injector = None
if injector:
return injector.call_with_injection(
callable=f,
self_=self_,
args=args,
kwargs=kwargs
)
return injector.call_with_injection(callable=f, self_=self_, args=args, kwargs=kwargs)
else:
return f(self_, *args, **kwargs)
@ -1141,8 +1141,10 @@ class BaseKey:
"""Base type for binding keys."""
def __init__(self):
raise Exception('Instantiation of %s prohibited - it is derived from BaseKey '
'so most likely you should bind it to something.' % (self.__class__,))
raise Exception(
'Instantiation of %s prohibited - it is derived from BaseKey '
'so most likely you should bind it to something.' % (self.__class__,)
)
def Key(name):
@ -1160,9 +1162,12 @@ def Key(name):
@private
class BaseMappingKey(dict):
"""Base type for mapping binding keys."""
def __init__(self):
raise Exception('Instantiation of %s prohibited - it is derived from BaseMappingKey '
'so most likely you should bind it to something.' % (self.__class__,))
raise Exception(
'Instantiation of %s prohibited - it is derived from BaseMappingKey '
'so most likely you should bind it to something.' % (self.__class__,)
)
def MappingKey(name):
@ -1173,9 +1178,12 @@ def MappingKey(name):
@private
class BaseSequenceKey(list):
"""Base type for mapping sequence keys."""
def __init__(self):
raise Exception('Instantiation of %s prohibited - it is derived from BaseSequenceKey '
'so most likely you should bind it to something.' % (self.__class__,))
raise Exception(
'Instantiation of %s prohibited - it is derived from BaseSequenceKey '
'so most likely you should bind it to something.' % (self.__class__,)
)
def SequenceKey(name):
@ -1211,7 +1219,6 @@ class BoundKey(tuple):
class AssistedBuilder(Generic[T]):
def __init__(self, injector, target):
self._injector = injector
self._target = target
@ -1224,7 +1231,8 @@ class AssistedBuilder(Generic[T]):
if not isinstance(provider, ClassProvider):
raise Error(
'Assisted interface building works only with ClassProviders, '
'got %r for %r' % (provider, binding.interface))
'got %r for %r' % (provider, binding.interface)
)
return self._build_class(provider._cls, **kwargs)
@ -1268,8 +1276,7 @@ class ProviderOf(Generic[T]):
self._interface = interface
def __repr__(self):
return '%s(%r, %r)' % (
type(self).__name__, self._injector, self._interface)
return '%s(%r, %r)' % (type(self).__name__, self._injector, self._interface)
def get(self) -> T:
"""Get an implementation for the specified interface."""

View File

@ -20,13 +20,33 @@ import warnings
import pytest
from injector import (
Binder, CallError, Injector, Scope, InstanceProvider, ClassProvider,
inject, noninjectable, singleton, threadlocal, UnsatisfiedRequirement,
CircularDependency, Module, Key, SingletonScope,
ScopeDecorator, with_injector, AssistedBuilder, BindingKey,
SequenceKey, MappingKey, provider, ProviderOf, ClassAssistedBuilder,
Error, UnknownArgument,
)
Binder,
CallError,
Injector,
Scope,
InstanceProvider,
ClassProvider,
inject,
noninjectable,
singleton,
threadlocal,
UnsatisfiedRequirement,
CircularDependency,
Module,
Key,
SingletonScope,
ScopeDecorator,
with_injector,
AssistedBuilder,
BindingKey,
SequenceKey,
MappingKey,
provider,
ProviderOf,
ClassAssistedBuilder,
Error,
UnknownArgument,
)
def prepare_basic_injection():
@ -55,21 +75,23 @@ def check_exception_contains_stuff(exception, stuff):
stringified = str(exception)
for thing in stuff:
assert thing in stringified, (
'%r should be present in the exception representation: %s' % (
thing, stringified))
assert thing in stringified, '%r should be present in the exception representation: %s' % (
thing,
stringified,
)
def test_child_injector_inherits_parent_bindings():
parent, child = prepare_nested_injectors()
assert (child.get(str) == parent.get(str))
assert child.get(str) == parent.get(str)
def test_child_injector_overrides_parent_bindings():
parent, child = prepare_nested_injectors()
child.binder.bind(str, to='qwe')
assert ((parent.get(str), child.get(str)) == ('asd', 'qwe'))
assert (parent.get(str), child.get(str)) == ('asd', 'qwe')
def test_child_injector_rebinds_arguments_for_parent_scope():
I = Key("interface")
@ -88,9 +110,10 @@ def test_child_injector_rebinds_arguments_for_parent_scope():
binder.bind(I, to="Child")
parent = Injector(configure_parent)
assert (parent.get(Cls).val == "Parent")
assert parent.get(Cls).val == "Parent"
child = parent.create_child_injector(configure_child)
assert (child.get(Cls).val == "Child")
assert child.get(Cls).val == "Child"
def test_scopes_are_only_bound_to_root_injector():
parent, child = prepare_nested_injectors()
@ -99,7 +122,7 @@ def test_scopes_are_only_bound_to_root_injector():
pass
parent.binder.bind(A, to=A, scope=singleton)
assert (parent.get(A) is child.get(A))
assert parent.get(A) is child.get(A)
def test_key_cannot_be_instantiated():
@ -120,20 +143,20 @@ def test_get_default_injected_instances():
binder.bind(B)
injector = Injector(configure)
assert (injector.get(Injector) is injector)
assert (injector.get(Binder) is injector.binder)
assert injector.get(Injector) is injector
assert injector.get(Binder) is injector.binder
def test_instantiate_injected_method():
A, _ = prepare_basic_injection()
a = A('Bob')
assert (a.b == 'Bob')
assert a.b == 'Bob'
def test_method_decorator_is_wrapped():
A, _ = prepare_basic_injection()
assert (A.__init__.__doc__ == 'Construct a new A.')
assert (A.__init__.__name__ == '__init__')
assert A.__init__.__doc__ == 'Construct a new A.'
assert A.__init__.__name__ == '__init__'
def test_decorator_works_for_function_with_no_args():
@ -169,8 +192,8 @@ def test_inject_direct():
injector = Injector(configure)
a = injector.get(A)
assert (isinstance(a, A))
assert (isinstance(a.b, B))
assert isinstance(a, A)
assert isinstance(a.b, B)
def test_configure_multiple_modules():
@ -184,8 +207,8 @@ def test_configure_multiple_modules():
injector = Injector([configure_a, configure_b])
a = injector.get(A)
assert (isinstance(a, A))
assert (isinstance(a.b, B))
assert isinstance(a, A)
assert isinstance(a.b, B)
def test_inject_with_missing_dependency():
@ -214,8 +237,8 @@ def test_inject_named_interface():
injector = Injector(configure)
a = injector.get(A)
assert (isinstance(a, A))
assert (isinstance(a.b, B))
assert isinstance(a, A)
assert isinstance(a.b, B)
def prepare_transitive_injection():
@ -245,9 +268,9 @@ def test_transitive_injection():
injector = Injector(configure)
a = injector.get(A)
assert (isinstance(a, A))
assert (isinstance(a.b, B))
assert (isinstance(a.b.c, C))
assert isinstance(a, A)
assert isinstance(a.b, B)
assert isinstance(a.b.c, C)
def test_transitive_injection_with_missing_dependency():
@ -280,7 +303,7 @@ def test_inject_singleton():
injector1 = Injector(configure)
a1 = injector1.get(A)
a2 = injector1.get(A)
assert (a1.b is a2.b)
assert a1.b is a2.b
def test_inject_decorated_singleton_class():
@ -300,7 +323,7 @@ def test_inject_decorated_singleton_class():
injector1 = Injector(configure)
a1 = injector1.get(A)
a2 = injector1.get(A)
assert (a1.b is a2.b)
assert a1.b is a2.b
def test_threadlocal():
@ -316,7 +339,7 @@ def test_threadlocal():
a1 = injector.get(A)
a2 = injector.get(A)
assert (a1 is a2)
assert a1 is a2
a3 = [None]
ready = threading.Event()
@ -328,7 +351,7 @@ def test_threadlocal():
threading.Thread(target=inject_a3).start()
ready.wait(1.0)
assert (a2 is not a3[0] and a3[0] is not None)
assert a2 is not a3[0] and a3[0] is not None
def test_injecting_interface_implementation():
@ -349,7 +372,7 @@ def test_injecting_interface_implementation():
injector = Injector(configure)
a = injector.get(A)
assert (isinstance(a.i, Implementation))
assert isinstance(a.i, Implementation)
def test_cyclic_dependencies():
@ -421,7 +444,7 @@ def test_that_injection_is_lazy():
injector = Injector(configure)
assert not (Interface.constructed)
injector.get(A)
assert (Interface.constructed)
assert Interface.constructed
def test_module_provider():
@ -443,7 +466,7 @@ def test_module_class_gets_instantiated():
binder.bind(str, to=name)
injector = Injector(MyModule)
assert (injector.get(str) == name)
assert injector.get(str) == name
def test_with_injector_works():
@ -459,7 +482,7 @@ def test_with_injector_works():
self.username = username
aaa = Aaa()
assert (aaa.username == name)
assert aaa.username == name
def test_bind_using_key():
@ -475,8 +498,8 @@ def test_bind_using_key():
binder.bind(Age, to=25)
injector = Injector(MyModule())
assert (injector.get(Age) == 25)
assert (injector.get(Name) == 'Bob')
assert injector.get(Age) == 25
assert injector.get(Name) == 'Bob'
def test_inject_using_key():
@ -493,7 +516,7 @@ def test_inject_using_key():
def provide_description(self, name: Name) -> Description:
return '%s is cool!' % name
assert (Injector(MyModule()).get(Description) == 'Bob is cool!')
assert Injector(MyModule()).get(Description) == 'Bob is cool!'
def test_inject_and_provide_coexist_happily():
@ -512,7 +535,7 @@ def test_inject_and_provide_coexist_happily():
def provide_description(self, age: int, weight: float) -> str:
return 'Bob is %d and weighs %0.1fkg' % (age, weight)
assert (Injector(MyModule()).get(str) == 'Bob is 25 and weighs 50.0kg')
assert Injector(MyModule()).get(str) == 'Bob is 25 and weighs 50.0kg'
def test_multibind():
@ -524,7 +547,7 @@ def test_multibind():
def configure_two(binder):
binder.multibind(Names, to=['Tom'])
assert (Injector([configure_one, configure_two]).get(Names) == ['Bob', 'Tom'])
assert Injector([configure_one, configure_two]).get(Names) == ['Bob', 'Tom']
def test_provider_sequence_decorator():
@ -539,16 +562,15 @@ def test_provider_sequence_decorator():
def tom(self) -> Names:
return ['Tom']
assert (Injector(MyModule()).get(Names) == ['Bob', 'Tom'])
assert Injector(MyModule()).get(Names) == ['Bob', 'Tom']
def test_auto_bind():
class A:
pass
injector = Injector()
assert (isinstance(injector.get(A), A))
assert isinstance(injector.get(A), A)
def test_custom_scope():
@ -604,20 +626,19 @@ def test_custom_scope():
with scope(request):
handler = injector.get(Handler)
assert (handler.request is request)
assert handler.request is request
with pytest.raises(UnsatisfiedRequirement):
injector.get(Handler)
def test_bind_interface_of_list_of_types():
def configure(binder):
binder.multibind([int], to=[1, 2, 3])
binder.multibind([int], to=[4, 5, 6])
injector = Injector(configure)
assert (injector.get([int]) == [1, 2, 3, 4, 5, 6])
assert injector.get([int]) == [1, 2, 3, 4, 5, 6]
def test_provider_mapping():
@ -638,7 +659,7 @@ def test_provider_mapping():
return {'four': 4}
injector = Injector([configure, MyModule()])
assert (injector.get(StrInt) == {'one': 1, 'two': 2, 'three': 3, 'four': 4})
assert injector.get(StrInt) == {'one': 1, 'two': 2, 'three': 3, 'four': 4}
def test_binder_install():
@ -651,31 +672,31 @@ def test_binder_install():
binder.install(ModuleA())
injector = Injector([ModuleB()])
assert (injector.get(str) == 'hello world')
assert injector.get(str) == 'hello world'
def test_binder_provider_for_method_with_explicit_provider():
injector = Injector()
binder = injector.binder
provider = binder.provider_for(int, to=InstanceProvider(1))
assert (type(provider) is InstanceProvider)
assert (provider.get(injector) == 1)
assert type(provider) is InstanceProvider
assert provider.get(injector) == 1
def test_binder_provider_for_method_with_instance():
injector = Injector()
binder = injector.binder
provider = binder.provider_for(int, to=1)
assert (type(provider) is InstanceProvider)
assert (provider.get(injector) == 1)
assert type(provider) is InstanceProvider
assert provider.get(injector) == 1
def test_binder_provider_for_method_with_class():
injector = Injector()
binder = injector.binder
provider = binder.provider_for(int)
assert (type(provider) is ClassProvider)
assert (provider.get(injector) == 0)
assert type(provider) is ClassProvider
assert provider.get(injector) == 0
def test_binder_provider_for_method_with_class_to_specific_subclass():
@ -688,8 +709,8 @@ def test_binder_provider_for_method_with_class_to_specific_subclass():
injector = Injector()
binder = injector.binder
provider = binder.provider_for(A, B)
assert (type(provider) is ClassProvider)
assert (isinstance(provider.get(injector), B))
assert type(provider) is ClassProvider
assert isinstance(provider.get(injector), B)
def test_binder_provider_for_type_with_metaclass():
@ -697,11 +718,11 @@ def test_binder_provider_for_type_with_metaclass():
# otherwise should be:
# class A(object, metaclass=abc.ABCMeta):
# passa
A = abc.ABCMeta('A', (object, ), {})
A = abc.ABCMeta('A', (object,), {})
injector = Injector()
binder = injector.binder
assert (isinstance(binder.provider_for(A, None).get(injector), A))
assert isinstance(binder.provider_for(A, None).get(injector), A)
def test_injecting_undecorated_class_with_missing_dependencies_raises_the_right_error():
@ -747,7 +768,7 @@ def test_assisted_builder_works_when_got_directly_from_injector():
injector = Injector()
builder = injector.get(AssistedBuilder[NeedsAssistance])
obj = builder.build(b=123)
assert ((obj.a, obj.b) == (str(), 123))
assert (obj.a, obj.b) == (str(), 123)
def test_assisted_builder_works_when_injected():
@ -758,7 +779,7 @@ def test_assisted_builder_works_when_injected():
injector = Injector()
x = injector.get(X)
assert ((x.obj.a, x.obj.b) == (str(), 234))
assert (x.obj.a, x.obj.b) == (str(), 234)
def test_assisted_builder_uses_bindings():
@ -770,7 +791,7 @@ def test_assisted_builder_uses_bindings():
injector = Injector(configure)
builder = injector.get(AssistedBuilder[Interface])
x = builder.build(b=333)
assert ((type(x), x.b) == (NeedsAssistance, 333))
assert (type(x), x.b) == (NeedsAssistance, 333)
def test_assisted_builder_uses_concrete_class_when_specified():
@ -795,7 +816,7 @@ def test_assisted_builder_injection_is_safe_to_use_with_multiple_injectors():
i1, i2 = Injector(), Injector()
b1 = i1.get(X).builder
b2 = i2.get(X).builder
assert ((b1._injector, b2._injector) == (i1, i2))
assert (b1._injector, b2._injector) == (i1, i2)
def test_assisted_builder_injection_uses_the_same_binding_key_every_time():
@ -842,12 +863,12 @@ class TestThreadSafety:
def test_injection_is_thread_safe(self):
objects = self.gather_results(2)
assert (len(objects) == 2)
assert len(objects) == 2
def test_singleton_scope_is_thread_safe(self):
self.injector.binder.bind(self.cls, scope=singleton)
a, b = self.gather_results(2)
assert (a is b)
assert a is b
def test_provider_and_scope_decorator_collaboration():
@ -879,6 +900,7 @@ def test_injecting_into_method_of_object_that_is_falseish_works():
def test_injection_fails_when_injector_cant_install_itself_into_an_object_with_slots():
try:
class ClassName:
__slots__ = ()
@ -1008,6 +1030,7 @@ def test_special_interfaces_work_with_auto_bind_disabled():
def test_binding_an_instance_regression():
text = b'hello'.decode()
def configure(binder):
# Yes, this binding doesn't make sense strictly speaking but
# it's just a sample case.
@ -1046,12 +1069,12 @@ def test_implicit_injection_for_python3():
class B:
@inject
def __init__(self, a:A):
def __init__(self, a: A):
self.a = a
class C:
@inject
def __init__(self, b:B):
def __init__(self, b: B):
self.b = b
injector = Injector()
@ -1323,7 +1346,6 @@ def test_custom_scopes_work_as_expected_with_child_injectors():
# Test for https://github.com/alecthomas/injector/issues/75
def test_inject_decorator_does_not_break_manual_construction_of_pyqt_objects():
class PyQtFake:
@inject
def __init__(self):
pass
@ -1333,7 +1355,8 @@ def test_inject_decorator_does_not_break_manual_construction_of_pyqt_objects():
raise RuntimeError(
'A PyQt class would raise this exception if getting '
'self.__injector__ before __init__ is called and '
'self.__injector__ has not been set by Injector.')
'self.__injector__ has not been set by Injector.'
)
return object.__getattribute__(self, item)
instance = PyQtFake() # This used to raise the exception

4
pyproject.toml Normal file
View File

@ -0,0 +1,4 @@
[tool.black]
line-length = 110
target_version = ['py36', 'py37']
skip_string_normalization = true

View File

@ -2413,6 +2413,7 @@ import base64
import zlib
import imp
class DictImporter:
def __init__(self, sources):
self.sources = sources
@ -2427,6 +2428,7 @@ class DictImporter:
def load_module(self, fullname):
# print "load_module:", fullname
from types import ModuleType
try:
s = self.sources[fullname]
is_pkg = False
@ -2451,14 +2453,17 @@ class DictImporter:
res = self.sources.get(name + '.__init__')
return res
if __name__ == "__main__":
if sys.version_info >= (3, 0):
exec("def do_exec(co, loc): exec(co, loc)\n")
import pickle
sources = sources.encode("ascii") # ensure bytes
sources = sources.encode("ascii") # ensure bytes
sources = pickle.loads(zlib.decompress(base64.decodebytes(sources)))
else:
import pickle as pickle
exec("def do_exec(co, loc): exec co in loc\n")
sources = pickle.loads(zlib.decompress(base64.decodestring(sources)))

View File

@ -17,6 +17,7 @@ class PyTest(Command):
def run(self):
import subprocess
errno = subprocess.call([sys.executable, 'runtest.py'])
raise SystemExit(errno)
@ -35,10 +36,10 @@ version_tag = read_injector_variable('__version_tag__')
try:
import pypandoc
long_description = pypandoc.convert('README.md', 'rst')
except ImportError:
warnings.warn('Could not locate pandoc, using Markdown long_description.',
ImportWarning)
warnings.warn('Could not locate pandoc, using Markdown long_description.', ImportWarning)
with open('README.md') as f:
long_description = f.read()
@ -56,12 +57,16 @@ setup(
license='BSD',
platforms=['any'],
packages=['injector'],
package_data = {'injector': ['py.typed']},
package_data={'injector': ['py.typed']},
author='Alec Thomas',
author_email='alec@swapoff.org',
cmdclass={'test': PyTest},
keywords=[
'Dependency Injection', 'DI', 'Dependency Injection framework',
'Inversion of Control', 'IoC', 'Inversion of Control container',
'Dependency Injection',
'DI',
'Dependency Injection framework',
'Inversion of Control',
'IoC',
'Inversion of Control container',
],
)