From c977ca70d3b4e1e46a6503e57be682ec81b9fbb5 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 20 Apr 2019 16:47:55 +0200 Subject: [PATCH] documentation/python: adapt to slightly different internals in 3.6. --- documentation/python.py | 35 +++++++++---- .../inspect_annotations/math36.html | 52 +++++++++++++++++++ documentation/test_python/test_inspect.py | 24 ++++++++- 3 files changed, 101 insertions(+), 10 deletions(-) create mode 100644 documentation/test_python/inspect_annotations/math36.html diff --git a/documentation/python.py b/documentation/python.py index cd38f8ab..59012704 100755 --- a/documentation/python.py +++ b/documentation/python.py @@ -41,6 +41,7 @@ from types import SimpleNamespace as Empty from importlib.machinery import SourceFileLoader from typing import Tuple, Dict, Any, List from urllib.parse import urljoin +from distutils.version import LooseVersion import jinja2 @@ -147,11 +148,14 @@ def extract_annotation(annotation) -> str: # TODO: why this is not None directly? if annotation is inspect.Signature.empty: return None - # To avoid getting for classes (and getting foo.bar + # To avoid getting for types (and getting foo.bar # instead) but getting the actual type for types annotated with e.g. - # List[int], we need to branch on isclass() - if inspect.isclass(annotation): return extract_type(annotation) - return str(annotation) + # List[int], we need to check if the annotation is actually from the + # typing module or it's directly a type. In Python 3.7 this worked with + # inspect.isclass(annotation), but on 3.6 that gives True for annotations + # as well and then we would get just List instead of List[int]. + if annotation.__module__ == 'typing': return str(annotation) + return extract_type(annotation) def render(config, template: str, page, env: jinja2.Environment): template = env.get_template(template) @@ -404,9 +408,7 @@ def render_module(config, path, module, env): # to have in the docs, so filter them out. Uh... kinda ugly. _filtered_builtin_functions = set([ ('__delattr__', "Implement delattr(self, name)."), - ('__dir__', "Default dir() implementation."), ('__eq__', "Return self==value."), - ('__format__', "Default object formatter."), ('__ge__', "Return self>=value."), ('__getattribute__', "Return getattr(self, name)."), ('__gt__', "Return self>value."), @@ -421,11 +423,8 @@ _filtered_builtin_functions = set([ ('__ne__', "Return self!=value."), ('__new__', "Create and return a new object. See help(type) for accurate signature."), - ('__reduce__', "Helper for pickle."), - ('__reduce_ex__', "Helper for pickle."), ('__repr__', "Return repr(self)."), ('__setattr__', "Implement setattr(self, name, value)."), - ('__sizeof__', "Size of object in memory, in bytes."), ('__str__', "Return str(self)."), ('__subclasshook__', "Abstract classes can override this to customize issubclass().\n\n" @@ -435,6 +434,24 @@ _filtered_builtin_functions = set([ "overrides the normal algorithm (and the outcome is cached).\n") ]) +# Python 3.6 has slightly different docstrings than 3.7 +if LooseVersion(sys.version) >= LooseVersion("3.7"): + _filtered_builtin_functions.update({ + ('__dir__', "Default dir() implementation."), + ('__format__', "Default object formatter."), + ('__reduce__', "Helper for pickle."), + ('__reduce_ex__', "Helper for pickle."), + ('__sizeof__', "Size of object in memory, in bytes."), + }) +else: + _filtered_builtin_functions.update({ + ('__dir__', "__dir__() -> list\ndefault dir() implementation"), + ('__format__', "default object formatter"), + ('__reduce__', "helper for pickle"), + ('__reduce_ex__', "helper for pickle"), + ('__sizeof__', "__sizeof__() -> int\nsize of object in memory, in bytes") + }) + _filtered_builtin_properties = set([ ('__weakref__', "list of weak references to the object (if defined)") ]) diff --git a/documentation/test_python/inspect_annotations/math36.html b/documentation/test_python/inspect_annotations/math36.html new file mode 100644 index 00000000..f8cf25d2 --- /dev/null +++ b/documentation/test_python/inspect_annotations/math36.html @@ -0,0 +1,52 @@ + + + + + math | My Python Project + + + + + +
+
+
+
+
+

+ math module +

+

This module is always available. It provides access to the +mathematical functions defined by the C standard.

+
+

Contents

+ +
+
+

Functions

+
+
+ def log(...) +
+
log(x[, base])
+
+
+
+
+
+
+ + diff --git a/documentation/test_python/test_inspect.py b/documentation/test_python/test_inspect.py index 09a34a30..e483e76f 100644 --- a/documentation/test_python/test_inspect.py +++ b/documentation/test_python/test_inspect.py @@ -22,9 +22,12 @@ # DEALINGS IN THE SOFTWARE. # +import math import os import sys -import math +import unittest + +from distutils.version import LooseVersion from . import BaseTestCase @@ -75,6 +78,8 @@ class Annotations(BaseTestCase): self.assertEqual(*self.actual_expected_contents('inspect_annotations.html')) self.assertEqual(*self.actual_expected_contents('inspect_annotations.Foo.html')) + @unittest.skipUnless(LooseVersion(sys.version) >= LooseVersion('3.7'), + "signature with / for pow() is not present in 3.6") def test_math(self): # From math export only pow() so we have the verification easier, and # in addition log() because it doesn't provide any signature metadata @@ -89,3 +94,20 @@ class Annotations(BaseTestCase): assert not hasattr(math, '__all__') self.assertEqual(*self.actual_expected_contents('math.html')) + + @unittest.skipUnless(LooseVersion(sys.version) < LooseVersion('3.7'), + "docstring for log() is different in 3.7") + def test_math36(self): + # From math export only pow() so we have the verification easier, and + # in addition log() because it doesn't provide any signature metadata + assert not hasattr(math, '__all__') + math.__all__ = ['log'] + + self.run_python({ + 'INPUT_MODULES': [math] + }) + + del math.__all__ + assert not hasattr(math, '__all__') + + self.assertEqual(*self.actual_expected_contents('math.html', 'math36.html')) -- 2.30.2