Repository: sphinx-doc/sphinx

Issue:
inheritance_diagram AttributeError: 'NoneType' object has no attribute 'groups'
Subject: Exception occurs on make latexpdf in try_import of sphinx/ext/inheritance_diagram.py
### Problem

Most Propably a user Error
```
Exception occurred:
  File "/home/johannes/anaconda3/lib/python3.6/site-packages/sphinx/ext/inheritance_diagram.py", line 83, in try_import
    modname, attrname = module_sig_re.match(objname).groups()  # type: ignore
AttributeError: 'NoneType' object has no attribute 'groups'
The full traceback has been saved in /tmp/sphinx-err-0kr1ytc1.log, if you want to report the issue to the developers.
Please also report this if it was a user error, so that a better error message can be provided next time.
A bug report can be filed in the tracker at <https://github.com/sphinx-doc/sphinx/issues>. Thanks!
Makefile:27: die Regel für Ziel „latexpdf“ scheiterte
make: *** [latexpdf] Fehler 1
```

#### Procedure to reproduce the problem
```
make latexpdf
```

#### Error logs / results
```
> # Sphinx version: 1.6.3
> # Python version: 3.6.0 (CPython)
> # Docutils version: 0.14 
> # Jinja2 version: 2.9.6
> # Last messages:
> #   updating environment:
> #   
> #   [extensions changed] 8 added, 0 changed, 0 removed
> #   
> #   reading sources... [ 12%] aus
> #   
> #   base-path: /home/user/some
> #   
> #   reading sources... [ 25%] fileformat
> #   
> # Loaded extensions:
> #   alabaster (0.7.10) from /home/johannes/anaconda3/lib/python3.6/site-packages/alabaster/__init__.py
> #   sphinx.ext.autodoc (1.6.3) from /home/johannes/anaconda3/lib/python3.6/site-packages/sphinx/ext/autodoc.py
> #   sphinx.ext.graphviz (1.6.3) from /home/johannes/anaconda3/lib/python3.6/site-packages/sphinx/ext/graphviz.py
> #   sphinx.ext.inheritance_diagram (1.6.3) from /home/johannes/anaconda3/lib/python3.6/site-packages/sphinx/ext/inheritance_diagram.py
> Traceback (most recent call last):
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/sphinx/ext/inheritance_diagram.py", line 80, in try_import
>     __import__(objname)
> ModuleNotFoundError: No module named '.'
> 
> During handling of the above exception, another exception occurred:
> 
> Traceback (most recent call last):
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/sphinx/cmdline.py", line 306, in main
>     app.build(opts.force_all, filenames)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/sphinx/application.py", line 339, in build
>     self.builder.build_update()
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/sphinx/builders/__init__.py", line 326, in build_update
>     self.build(['__all__'], to_build)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/sphinx/builders/__init__.py", line 344, in build
>     updated_docnames = set(self.env.update(self.config, self.srcdir, self.doctreedir))
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/sphinx/environment/__init__.py", line 583, in update
>     self._read_serial(docnames, self.app)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/sphinx/environment/__init__.py", line 602, in _read_serial
>     self.read_doc(docname, app)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/sphinx/environment/__init__.py", line 723, in read_doc
>     pub.publish()
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/docutils/core.py", line 217, in publish
>     self.settings)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/sphinx/io.py", line 74, in read
>     self.parse()
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/docutils/readers/__init__.py", line 78, in parse
>     self.parser.parse(self.input, document)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/docutils/parsers/rst/__init__.py", line 191, in parse
>     self.statemachine.run(inputlines, document, inliner=self.inliner)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/docutils/parsers/rst/states.py", line 171, in run
>     input_source=document['source'])
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/docutils/statemachine.py", line 239, in run
>     context, state, transitions)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/docutils/statemachine.py", line 460, in check_line
>     return method(match, context, next_state)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/docutils/parsers/rst/states.py", line 2753, in underline
>     self.section(title, source, style, lineno - 1, messages)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/docutils/parsers/rst/states.py", line 327, in section
>     self.new_subsection(title, lineno, messages)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/docutils/parsers/rst/states.py", line 395, in new_subsection
>     node=section_node, match_titles=True)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/docutils/parsers/rst/states.py", line 282, in nested_parse
>     node=node, match_titles=match_titles)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/docutils/parsers/rst/states.py", line 196, in run
>     results = StateMachineWS.run(self, input_lines, input_offset)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/docutils/statemachine.py", line 239, in run
>     context, state, transitions)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/docutils/statemachine.py", line 460, in check_line
>     return method(match, context, next_state)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/docutils/parsers/rst/states.py", line 2328, in explicit_markup
>     self.explicit_list(blank_finish)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/docutils/parsers/rst/states.py", line 2358, in explicit_list
>     match_titles=self.state_machine.match_titles)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/docutils/parsers/rst/states.py", line 319, in nested_list_parse
>     node=node, match_titles=match_titles)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/docutils/parsers/rst/states.py", line 196, in run
>     results = StateMachineWS.run(self, input_lines, input_offset)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/docutils/statemachine.py", line 239, in run
>     context, state, transitions)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/docutils/statemachine.py", line 460, in check_line
>     return method(match, context, next_state)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/docutils/parsers/rst/states.py", line 2631, in explicit_markup
>     nodelist, blank_finish = self.explicit_construct(match)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/docutils/parsers/rst/states.py", line 2338, in explicit_construct
>     return method(self, expmatch)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/docutils/parsers/rst/states.py", line 2081, in directive
>     directive_class, match, type_name, option_presets)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/docutils/parsers/rst/states.py", line 2130, in run_directive
>     result = directive_instance.run()
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/sphinx/ext/inheritance_diagram.py", line 342, in run
>     private_bases='private-bases' in self.options)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/sphinx/ext/inheritance_diagram.py", line 144, in __init__
>     classes = self._import_classes(class_names, currmodule)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/sphinx/ext/inheritance_diagram.py", line 156, in _import_classes
>     classes.extend(import_classes(name, currmodule))
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/sphinx/ext/inheritance_diagram.py", line 100, in import_classes
>     target = try_import(currmodule + '.' + name)
>   File "/home/johannes/anaconda3/lib/python3.6/site-packages/sphinx/ext/inheritance_diagram.py", line 83, in try_import
>     modname, attrname = module_sig_re.match(objname).groups()  # type: ignore
> AttributeError: 'NoneType' object has no attribute 'groups'
```
#### Expected results
No Exception even if object is None

### Reproducible project / your project
not necessary

### Environment info
- OS: <Unix/Linux/Mac/Win/other with version>
- Python version: Python 3.6.0 |Anaconda custom (64-bit)
- Sphinx version: Sphinx-1.6.3-py2.py3-none-any.whl
- sphinx.ext.inheritance_diagram

Discussion:

Patch:
diff --git a/AUTHORS b/AUTHORS
index 69c224df0..226900310 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -73,6 +73,7 @@ Other contributors, listed alphabetically, are:
 * Joel Wurtz -- cellspanning support in LaTeX
 * Hong Xu -- svg support in imgmath extension and various bug fixes
 * Stephen Finucane -- setup command improvements and documentation
+* Daniel Pizetta -- inheritance diagram improvements
 
 Many thanks for all contributions!
 
diff --git a/CHANGES b/CHANGES
index 32ce56d6b..c31bba21b 100644
--- a/CHANGES
+++ b/CHANGES
@@ -15,6 +15,7 @@ Features added
 
 Bugs fixed
 ----------
+* #4019: inheritance_diagram AttributeError stoping make process
 
 Testing
 --------
diff --git a/sphinx/ext/inheritance_diagram.py b/sphinx/ext/inheritance_diagram.py
index 7a1366b26..6f8256662 100644
--- a/sphinx/ext/inheritance_diagram.py
+++ b/sphinx/ext/inheritance_diagram.py
@@ -75,14 +75,20 @@ def try_import(objname):
     try:
         __import__(objname)
         return sys.modules.get(objname)  # type: ignore
-    except ImportError:
-        modname, attrname = module_sig_re.match(objname).groups()  # type: ignore
+    except (ImportError, ValueError):  # ValueError,py27 -> ImportError,py3
+        matched = module_sig_re.match(objname)  # type: ignore
+
+        if not matched:
+            return None
+
+        modname, attrname = matched.groups()
+
         if modname is None:
             return None
         try:
             __import__(modname)
             return getattr(sys.modules.get(modname), attrname, None)
-        except ImportError:
+        except (ImportError, ValueError):  # ValueError,py27 -> ImportError,py3
             return None
 
 


Test patch:
diff --git a/tests/test_ext_inheritance_diagram.py b/tests/test_ext_inheritance_diagram.py
index deb04ce15..ed79729e6 100644
--- a/tests/test_ext_inheritance_diagram.py
+++ b/tests/test_ext_inheritance_diagram.py
@@ -83,6 +83,15 @@ def test_import_classes(rootdir):
         with pytest.raises(InheritanceException):
             import_classes('unknown.Unknown', None)
 
+        # got exception InheritanceException for wrong class or module
+        # not AttributeError (refs: #4019)
+        with pytest.raises(InheritanceException):
+            import_classes('unknown', '.')
+        with pytest.raises(InheritanceException):
+            import_classes('unknown.Unknown', '.')
+        with pytest.raises(InheritanceException):
+            import_classes('.', None)
+
         # a module having no classes
         classes = import_classes('sphinx', None)
         assert classes == []