diff --git a/graphene/utils/module_loading.py b/graphene/utils/module_loading.py index cbbbb39d..f544cf24 100644 --- a/graphene/utils/module_loading.py +++ b/graphene/utils/module_loading.py @@ -2,10 +2,13 @@ from functools import partial from importlib import import_module -def import_string(dotted_path): +def import_string(dotted_path, dotted_attributes=None): """ Import a dotted module path and return the attribute/class designated by the - last name in the path. Raise ImportError if the import failed. + last name in the path. When a dotted attribute path is also provided, the + dotted attribute path would be applied to the attribute/class retrieved from + the first step, and return the corresponding value designated by the + attribute path. Raise ImportError if the import failed. """ try: module_path, class_name = dotted_path.rsplit('.', 1) @@ -15,12 +18,27 @@ def import_string(dotted_path): module = import_module(module_path) try: - return getattr(module, class_name) + result = getattr(module, class_name) except AttributeError: raise ImportError('Module "%s" does not define a "%s" attribute/class' % ( module_path, class_name) ) + if not dotted_attributes: + return result + else: + attributes = dotted_attributes.split('.') + traveled_attributes = [] + try: + for attribute in attributes: + traveled_attributes.append(attribute) + result = getattr(result, attribute) + return result + except AttributeError: + raise ImportError('Module "%s" does not define a "%s" attribute inside attribute/class "%s"' % ( + module_path, '.'.join(traveled_attributes), class_name + )) -def lazy_import(dotted_path): - return partial(import_string, dotted_path) + +def lazy_import(dotted_path, dotted_attributes=None): + return partial(import_string, dotted_path, dotted_attributes) diff --git a/graphene/utils/tests/test_module_loading.py b/graphene/utils/tests/test_module_loading.py index a0975f74..769fde8b 100644 --- a/graphene/utils/tests/test_module_loading.py +++ b/graphene/utils/tests/test_module_loading.py @@ -1,6 +1,7 @@ from pytest import raises from graphene import String +from graphene.types.objecttype import ObjectTypeMeta from ..module_loading import lazy_import, import_string @@ -8,6 +9,9 @@ def test_import_string(): MyString = import_string('graphene.String') assert MyString == String + MyObjectTypeMeta = import_string('graphene.ObjectType', '__class__') + assert MyObjectTypeMeta == ObjectTypeMeta + def test_import_string_module(): with raises(Exception) as exc_info: @@ -23,7 +27,31 @@ def test_import_string_class(): assert str(exc_info.value) == 'Module "graphene" does not define a "Stringa" attribute/class' +def test_import_string_attributes(): + with raises(Exception) as exc_info: + import_string('graphene.String', 'length') + + assert str(exc_info.value) == 'Module "graphene" does not define a "length" attribute inside attribute/class ' \ + '"String"' + + with raises(Exception) as exc_info: + import_string('graphene.ObjectType', '__class__.length') + + assert str(exc_info.value) == 'Module "graphene" does not define a "__class__.length" attribute inside ' \ + 'attribute/class "ObjectType"' + + with raises(Exception) as exc_info: + import_string('graphene.ObjectType', '__classa__.__base__') + + assert str(exc_info.value) == 'Module "graphene" does not define a "__classa__" attribute inside attribute/class ' \ + '"ObjectType"' + + def test_lazy_import(): f = lazy_import('graphene.String') MyString = f() assert MyString == String + + f = lazy_import('graphene.ObjectType', '__class__') + MyObjectTypeMeta = f() + assert MyObjectTypeMeta == ObjectTypeMeta