From d1d87221d59263a97ca4ae1afc11d1e9f0be051e Mon Sep 17 00:00:00 2001 From: Wei Yen Date: Wed, 22 Feb 2017 06:55:47 +1100 Subject: [PATCH 1/3] Regression test to ensure docstring is trimmed Related to issue #418 --- graphene/types/tests/test_objecttype.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/graphene/types/tests/test_objecttype.py b/graphene/types/tests/test_objecttype.py index eb297917..8ccc0fac 100644 --- a/graphene/types/tests/test_objecttype.py +++ b/graphene/types/tests/test_objecttype.py @@ -173,3 +173,14 @@ def test_objecttype_container_benchmark(benchmark): @benchmark def create_objecttype(): Container(field1='field1', field2='field2') + + +def test_generate_objecttype_description(): + class MyObjectType(ObjectType): + ''' + Documentation + + Documentation line 2 + ''' + + assert MyObjectType._meta.description == "Documentation\n\nDocumentation line 2" From 0dc8e57c50b2245687e13a8be8de7b93bdff9bde Mon Sep 17 00:00:00 2001 From: Wei Yen Date: Wed, 22 Feb 2017 06:56:15 +1100 Subject: [PATCH 2/3] Implement `trim_docstring` Thin wrapper around `inspect.clean_doc` --- graphene/utils/tests/test_trim_docstring.py | 21 +++++++++++++++++++++ graphene/utils/trim_docstring.py | 9 +++++++++ 2 files changed, 30 insertions(+) create mode 100644 graphene/utils/tests/test_trim_docstring.py create mode 100644 graphene/utils/trim_docstring.py diff --git a/graphene/utils/tests/test_trim_docstring.py b/graphene/utils/tests/test_trim_docstring.py new file mode 100644 index 00000000..3aab5f11 --- /dev/null +++ b/graphene/utils/tests/test_trim_docstring.py @@ -0,0 +1,21 @@ +from ..trim_docstring import trim_docstring + + +def test_trim_docstring(): + class WellDocumentedObject(object): + """ + This object is very well-documented. It has multiple lines in its + description. + + Multiple paragraphs too + """ + pass + + assert (trim_docstring(WellDocumentedObject.__doc__) == + "This object is very well-documented. It has multiple lines in its\n" + "description.\n\nMultiple paragraphs too") + + class UndocumentedObject(object): + pass + + assert trim_docstring(UndocumentedObject.__doc__) is None diff --git a/graphene/utils/trim_docstring.py b/graphene/utils/trim_docstring.py new file mode 100644 index 00000000..a23c7e7d --- /dev/null +++ b/graphene/utils/trim_docstring.py @@ -0,0 +1,9 @@ +import inspect + + +def trim_docstring(docstring): + # Cleans up whitespaces from an indented docstring + # + # See https://www.python.org/dev/peps/pep-0257/ + # and https://docs.python.org/2/library/inspect.html#inspect.cleandoc + return inspect.cleandoc(docstring) if docstring else None From c592e94f73923ee2d2fbac26311e5976fcddee87 Mon Sep 17 00:00:00 2001 From: Wei Yen Date: Wed, 22 Feb 2017 06:58:14 +1100 Subject: [PATCH 3/3] Use trim_docstring to ensure description has no leading whitespace --- graphene/types/enum.py | 3 ++- graphene/types/inputobjecttype.py | 3 ++- graphene/types/interface.py | 3 ++- graphene/types/objecttype.py | 3 ++- graphene/types/scalars.py | 4 ++-- graphene/types/union.py | 3 ++- 6 files changed, 12 insertions(+), 7 deletions(-) diff --git a/graphene/types/enum.py b/graphene/types/enum.py index 3bff137c..a6575cea 100644 --- a/graphene/types/enum.py +++ b/graphene/types/enum.py @@ -3,6 +3,7 @@ from collections import OrderedDict import six from ..utils.is_base_type import is_base_type +from ..utils.trim_docstring import trim_docstring from .options import Options from .unmountedtype import UnmountedType @@ -23,7 +24,7 @@ class EnumTypeMeta(type): options = Options( attrs.pop('Meta', None), name=name, - description=attrs.get('__doc__'), + description=trim_docstring(attrs.get('__doc__')), enum=None, ) if not options.enum: diff --git a/graphene/types/inputobjecttype.py b/graphene/types/inputobjecttype.py index cbc13f95..1796988a 100644 --- a/graphene/types/inputobjecttype.py +++ b/graphene/types/inputobjecttype.py @@ -1,6 +1,7 @@ import six from ..utils.is_base_type import is_base_type +from ..utils.trim_docstring import trim_docstring from .abstracttype import AbstractTypeMeta from .inputfield import InputField from .options import Options @@ -19,7 +20,7 @@ class InputObjectTypeMeta(AbstractTypeMeta): options = Options( attrs.pop('Meta', None), name=name, - description=attrs.get('__doc__'), + description=trim_docstring(attrs.get('__doc__')), local_fields=None, ) diff --git a/graphene/types/interface.py b/graphene/types/interface.py index cc8361e6..f0980b6c 100644 --- a/graphene/types/interface.py +++ b/graphene/types/interface.py @@ -1,6 +1,7 @@ import six from ..utils.is_base_type import is_base_type +from ..utils.trim_docstring import trim_docstring from .abstracttype import AbstractTypeMeta from .field import Field from .options import Options @@ -18,7 +19,7 @@ class InterfaceMeta(AbstractTypeMeta): options = Options( attrs.pop('Meta', None), name=name, - description=attrs.get('__doc__'), + description=trim_docstring(attrs.get('__doc__')), local_fields=None, ) diff --git a/graphene/types/objecttype.py b/graphene/types/objecttype.py index f06dbf5e..9b9e1005 100644 --- a/graphene/types/objecttype.py +++ b/graphene/types/objecttype.py @@ -3,6 +3,7 @@ from collections import OrderedDict import six from ..utils.is_base_type import is_base_type +from ..utils.trim_docstring import trim_docstring from .abstracttype import AbstractTypeMeta from .field import Field from .interface import Interface @@ -22,7 +23,7 @@ class ObjectTypeMeta(AbstractTypeMeta): options = _meta or Options( attrs.pop('Meta', None), name=name, - description=attrs.get('__doc__'), + description=trim_docstring(attrs.get('__doc__')), interfaces=(), local_fields=OrderedDict(), ) diff --git a/graphene/types/scalars.py b/graphene/types/scalars.py index 6f07c91c..e1ff80d3 100644 --- a/graphene/types/scalars.py +++ b/graphene/types/scalars.py @@ -1,9 +1,9 @@ import six - from graphql.language.ast import (BooleanValue, FloatValue, IntValue, StringValue) from ..utils.is_base_type import is_base_type +from ..utils.trim_docstring import trim_docstring from .options import Options from .unmountedtype import UnmountedType @@ -19,7 +19,7 @@ class ScalarTypeMeta(type): options = Options( attrs.pop('Meta', None), name=name, - description=attrs.get('__doc__'), + description=trim_docstring(attrs.get('__doc__')), ) return type.__new__(cls, name, bases, dict(attrs, _meta=options)) diff --git a/graphene/types/union.py b/graphene/types/union.py index e36086d0..d4af88ed 100644 --- a/graphene/types/union.py +++ b/graphene/types/union.py @@ -1,6 +1,7 @@ import six from ..utils.is_base_type import is_base_type +from ..utils.trim_docstring import trim_docstring from .options import Options from .unmountedtype import UnmountedType @@ -16,7 +17,7 @@ class UnionMeta(type): options = Options( attrs.pop('Meta', None), name=name, - description=attrs.get('__doc__'), + description=trim_docstring(attrs.get('__doc__')), types=(), )