Skip to content

Commit

Permalink
[issue google#7] Introduce support to Python 3.x
Browse files Browse the repository at this point in the history
- Use Six package, a simple utilities for wrapping over
  differences between Python 2 and Python 3

- Encapsulate compatibility methods in 'support.py' module

- Introduce unit tests for 'support.py' module
  • Loading branch information
Guilherme M. Trein authored and trein committed Sep 30, 2018
1 parent eb696ec commit 5107f91
Show file tree
Hide file tree
Showing 22 changed files with 620 additions and 179 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ celerybeat-schedule
.env
.venv
env/
env2/
env3/
venv/
ENV/
env.bak/
Expand Down
6 changes: 2 additions & 4 deletions pinject/bindings.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@
"""


import inspect
import re
import inspect
import threading
import types

from . import binding_keys
from . import decorators
Expand Down Expand Up @@ -179,8 +178,7 @@ def get_provider_bindings(
get_arg_names_from_provider_fn_name=(
providing.default_get_arg_names_from_provider_fn_name)):
provider_bindings = []
fns = inspect.getmembers(binding_spec,
lambda x: type(x) == types.MethodType)
fns = inspect.getmembers(binding_spec, lambda x: inspect.ismethod(x))
for _, fn in fns:
default_arg_names = get_arg_names_from_provider_fn_name(fn.__name__)
fn_bindings = get_provider_fn_bindings(fn, default_arg_names)
Expand Down
16 changes: 7 additions & 9 deletions pinject/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,11 @@
limitations under the License.
"""

import collections
import inspect

import decorator

from . import arg_binding_keys
from . import support
from . import errors
from . import locations
from . import scoping
Expand Down Expand Up @@ -84,8 +83,8 @@ def inject(arg_names=None, all_except=None):
if arg_value is not None:
if not arg_value:
raise errors.EmptySequenceArgError(back_frame_loc, arg)
if (not isinstance(arg_value, collections.Sequence) or
isinstance(arg_value, basestring)):
if (not support.is_sequence(arg_value) or
support.is_string(arg_value)):
raise errors.WrongArgTypeError(
arg, 'sequence (of arg names)', type(arg_value).__name__)
if arg_names is None and all_except is None:
Expand Down Expand Up @@ -222,7 +221,7 @@ def _get_pinject_wrapper(
def get_pinject_decorated_fn_with_additions(fn):
pinject_decorated_fn = _get_pinject_decorated_fn(fn)
orig_arg_names, unused_varargs, unused_keywords, unused_defaults = (
inspect.getargspec(getattr(pinject_decorated_fn, _ORIG_FN_ATTR)))
support.get_method_args(getattr(pinject_decorated_fn, _ORIG_FN_ATTR)))
if arg_binding_key is not None:
if not arg_binding_key.can_apply_to_one_of_arg_names(orig_arg_names):
raise errors.NoSuchArgToInjectError(decorator_loc, arg_binding_key, fn)
Expand All @@ -234,14 +233,14 @@ def get_pinject_decorated_fn_with_additions(fn):
arg_binding_key)
if (provider_arg_name is not None or
provider_annotated_with is not None or
provider_in_scope_id is not None):
provider_in_scope_id is not None):
provider_decorations = getattr(
pinject_decorated_fn, _PROVIDER_DECORATIONS_ATTR)
provider_decorations.append(ProviderDecoration(
provider_arg_name, provider_annotated_with,
provider_in_scope_id))
if (inject_arg_names is not None or
inject_all_except_arg_names is not None):
inject_all_except_arg_names is not None):
if hasattr(pinject_decorated_fn, _NON_INJECTABLE_ARG_NAMES_ATTR):
raise errors.DuplicateDecoratorError('inject', decorator_loc)
non_injectable_arg_names = []
Expand Down Expand Up @@ -281,8 +280,7 @@ def get_injectable_arg_binding_keys(fn, direct_pargs, direct_kwargs):
existing_arg_binding_keys = []
orig_fn = fn

arg_names, unused_varargs, unused_keywords, defaults = (
inspect.getargspec(orig_fn))
arg_names, unused_varargs, unused_keywords, defaults = support.get_method_args(orig_fn)
num_args_with_defaults = len(defaults) if defaults is not None else 0
if num_args_with_defaults:
arg_names = arg_names[:-num_args_with_defaults]
Expand Down
2 changes: 1 addition & 1 deletion pinject/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"""


import locations
from . import locations


class Error(Exception):
Expand Down
5 changes: 2 additions & 3 deletions pinject/finding.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,10 @@ def find_classes(modules, classes):

def _get_explicit_or_default_modules(modules):
if modules is ALL_IMPORTED_MODULES:
return sys.modules.values()
return list(sys.modules.values())
elif modules is None:
return []
else:
return modules
return modules


def _find_classes_in_module(module):
Expand Down
6 changes: 3 additions & 3 deletions pinject/initializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@
limitations under the License.
"""

import inspect

import decorator

from . import errors
from . import support


def copy_args_to_internal_fields(fn):
Expand All @@ -41,15 +41,15 @@ def _copy_args_to_fields(fn, decorator_name, field_prefix):
raise errors.DecoratorAppliedToNonInitError(
decorator_name, fn)
arg_names, varargs, unused_keywords, unused_defaults = (
inspect.getargspec(fn))
support.get_method_args(fn))
if varargs is not None:
raise errors.PargsDisallowedWhenCopyingArgsError(
decorator_name, fn, varargs)

def CopyThenCall(fn_to_wrap, self, *pargs, **kwargs):
for index, parg in enumerate(pargs, start=1):
setattr(self, field_prefix + arg_names[index], parg)
for kwarg, kwvalue in kwargs.iteritems():
for kwarg, kwvalue in support.items(kwargs):
setattr(self, field_prefix + kwarg, kwvalue)
fn_to_wrap(self, *pargs, **kwargs)

Expand Down
75 changes: 69 additions & 6 deletions pinject/locations.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

import inspect

LOCALS_TOKEN = '<locals>'


def get_loc(thing):
try:
Expand All @@ -27,12 +29,8 @@ def get_loc(thing):

def get_name_and_loc(thing):
try:
if hasattr(thing, 'im_class'):
class_name = '{0}.{1}'.format(
thing.im_class.__name__, thing.__name__)
else:
class_name = '{0}.{1}'.format(
inspect.getmodule(thing).__name__, thing.__name__)
type_name = _get_type_name(thing)
class_name = '{0}.{1}'.format(type_name, thing.__name__)
except (TypeError, IOError):
class_name = '{0}.{1}'.format(
inspect.getmodule(thing).__name__, thing.__name__)
Expand All @@ -47,3 +45,68 @@ def get_back_frame_loc():
back_frame = inspect.currentframe().f_back.f_back
return '{0}:{1}'.format(back_frame.f_code.co_filename,
back_frame.f_lineno)


def _get_type_name(target_thing):
"""
Functions, bound methods and unbound methods change significantly in Python 3.
For instance:
class SomeObject(object):
def method():
pass
In Python 2:
- Unbound method inspect.ismethod(SomeObject.method) returns True
- Unbound inspect.isfunction(SomeObject.method) returns False
- Unbound hasattr(SomeObject.method, 'im_class') returns True
- Bound method inspect.ismethod(SomeObject().method) returns True
- Bound method inspect.isfunction(SomeObject().method) returns False
- Bound hasattr(SomeObject().method, 'im_class') returns True
In Python 3:
- Unbound method inspect.ismethod(SomeObject.method) returns False
- Unbound inspect.isfunction(SomeObject.method) returns True
- Unbound hasattr(SomeObject.method, 'im_class') returns False
- Bound method inspect.ismethod(SomeObject().method) returns True
- Bound method inspect.isfunction(SomeObject().method) returns False
- Bound hasattr(SomeObject().method, 'im_class') returns False
This method tries to consolidate the approach for retrieving the
enclosing type of a bound/unbound method and functions.
"""
thing = target_thing
if hasattr(thing, 'im_class'):
# only works in Python 2
return thing.im_class.__name__
if inspect.ismethod(thing):
for cls in inspect.getmro(thing.__self__.__class__):
if cls.__dict__.get(thing.__name__) is thing:
return cls.__name__
thing = thing.__func__
if inspect.isfunction(thing) and hasattr(thing, '__qualname__'):
qualifier = thing.__qualname__
if LOCALS_TOKEN in qualifier:
return _get_local_type_name(thing)
return _get_external_type_name(thing)
return inspect.getmodule(target_thing).__name__


def _get_local_type_name(thing):
qualifier = thing.__qualname__
parts = qualifier.split(LOCALS_TOKEN, 1)
type_name = parts[1].split('.')[1]
if thing.__name__ == type_name:
return inspect.getmodule(thing).__name__
return type_name


def _get_external_type_name(thing):
qualifier = thing.__qualname__
name = qualifier.rsplit('.', 1)[0]
if hasattr(inspect.getmodule(thing), name):
cls = getattr(inspect.getmodule(thing), name)
if isinstance(cls, type):
return cls.__name__
return inspect.getmodule(thing).__name__
69 changes: 14 additions & 55 deletions pinject/object_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@
"""


import collections
import functools
import inspect
import types

from . import arg_binding_keys
from . import bindings
from . import decorators
from . import errors
Expand All @@ -30,6 +24,7 @@
from . import providing
from . import required_bindings as required_bindings_lib
from . import scoping
from . import support


def new_object_graph(
Expand Down Expand Up @@ -81,21 +76,21 @@ def new_object_graph(
"""
try:
if modules is not None and modules is not finding.ALL_IMPORTED_MODULES:
_verify_types(modules, types.ModuleType, 'modules')
support.verify_module_types(modules, 'modules')
if classes is not None:
_verify_types(classes, types.TypeType, 'classes')
support.verify_class_types(classes, 'classes')
if binding_specs is not None:
_verify_subclasses(
support.verify_subclasses(
binding_specs, bindings.BindingSpec, 'binding_specs')
if get_arg_names_from_class_name is not None:
_verify_callable(get_arg_names_from_class_name,
'get_arg_names_from_class_name')
support.verify_callable(get_arg_names_from_class_name,
'get_arg_names_from_class_name')
if get_arg_names_from_provider_fn_name is not None:
_verify_callable(get_arg_names_from_provider_fn_name,
'get_arg_names_from_provider_fn_name')
support.verify_callable(get_arg_names_from_provider_fn_name,
'get_arg_names_from_provider_fn_name')
if is_scope_usable_from_scope is not None:
_verify_callable(is_scope_usable_from_scope,
'is_scope_usable_from_scope')
support.verify_callable(is_scope_usable_from_scope,
'is_scope_usable_from_scope')
injection_context_factory = injection_contexts.InjectionContextFactory(
is_scope_usable_from_scope)
id_to_scope = scoping.get_id_to_scope_with_defaults(id_to_scope)
Expand Down Expand Up @@ -169,46 +164,10 @@ def new_object_graph(
use_short_stack_traces)


def _verify_type(elt, required_type, arg_name):
if type(elt) != required_type:
raise errors.WrongArgTypeError(
arg_name, required_type.__name__, type(elt).__name__)


def _verify_types(seq, required_type, arg_name):
if not isinstance(seq, collections.Sequence):
raise errors.WrongArgTypeError(
arg_name, 'sequence (of {0})'.format(required_type.__name__),
type(seq).__name__)
for idx, elt in enumerate(seq):
if type(elt) != required_type:
raise errors.WrongArgElementTypeError(
arg_name, idx, required_type.__name__, type(elt).__name__)


def _verify_subclasses(seq, required_superclass, arg_name):
if not isinstance(seq, collections.Sequence):
raise errors.WrongArgTypeError(
arg_name,
'sequence (of subclasses of {0})'.format(
required_superclass.__name__),
type(seq).__name__)
for idx, elt in enumerate(seq):
if not isinstance(elt, required_superclass):
raise errors.WrongArgElementTypeError(
arg_name, idx,
'subclass of {0}'.format(required_superclass.__name__),
type(elt).__name__)


def _verify_callable(fn, arg_name):
if not callable(fn):
raise errors.WrongArgTypeError(arg_name, 'callable', type(fn).__name__)


def _pare_to_present_args(kwargs, fn):
arg_names, _, _, _ = inspect.getargspec(fn)
return {arg: value for arg, value in kwargs.iteritems() if arg in arg_names}
arg_names, _, _, _ = support.get_method_args(fn)
return {arg: value
for arg, value in support.items(kwargs) if arg in arg_names}


class ObjectGraph(object):
Expand All @@ -231,7 +190,7 @@ def provide(self, cls):
Raises:
Error: an instance of cls is not providable
"""
_verify_type(cls, types.TypeType, 'cls')
support.verify_class_type(cls, 'cls')
if not self._is_injectable_fn(cls):
provide_loc = locations.get_back_frame_loc()
raise errors.NonExplicitlyBoundClassError(provide_loc, cls)
Expand Down
5 changes: 2 additions & 3 deletions pinject/object_providers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@
"""


import types

from . import support
from . import arg_binding_keys
from . import decorators
from . import errors
Expand Down Expand Up @@ -61,7 +60,7 @@ def Provide(*pargs, **kwargs):

def provide_class(self, cls, injection_context,
direct_init_pargs, direct_init_kwargs):
if type(cls.__init__) is types.MethodType:
if support.is_constructor_defined(cls):
init_pargs, init_kwargs = self.get_injection_pargs_kwargs(
cls.__init__, injection_context,
direct_init_pargs, direct_init_kwargs)
Expand Down
Loading

0 comments on commit 5107f91

Please sign in to comment.