diff --git a/autoapi/_mapper.py b/autoapi/_mapper.py index 872c8b41..0463fb68 100644 --- a/autoapi/_mapper.py +++ b/autoapi/_mapper.py @@ -312,9 +312,10 @@ def _wrapped_prepare(value): self._use_implicit_namespace = ( self.app.config.autoapi_python_use_implicit_namespaces ) + self._follow_symlinks = self.app.config.autoapi_follow_symlinks @staticmethod - def find_files(patterns, dirs, ignore): + def find_files(patterns, dirs, ignore, follow_symlinks: bool): if not ignore: ignore = [] @@ -324,7 +325,9 @@ def find_files(patterns, dirs, ignore): pattern_regexes.append((pattern, regex)) for _dir in dirs: # iterate autoapi_dirs - for root, subdirectories, filenames in os.walk(_dir): + for root, subdirectories, filenames in os.walk( + _dir, followlinks=follow_symlinks + ): # skip directories if needed for sub_dir in subdirectories.copy(): # iterate copy as we adapt subdirectories during loop @@ -342,6 +345,10 @@ def find_files(patterns, dirs, ignore): for pattern, pattern_re in pattern_regexes: for filename in fnmatch.filter(filenames, pattern): match = re.match(pattern_re, filename) + if match is None: + raise ValueError( + f'Could not match pattern "{pattern_re}" to filename "{filename}".' + ) norm_name = match.groups() if norm_name in seen: continue @@ -429,7 +436,12 @@ def _find_files(self, patterns, dirs, ignore): ): dir_root = os.path.abspath(os.path.join(dir_, os.pardir)) - for path in self.find_files(patterns=patterns, dirs=[dir_], ignore=ignore): + for path in self.find_files( + patterns=patterns, + dirs=[dir_], + ignore=ignore, + follow_symlinks=self._follow_symlinks, + ): yield dir_root, path def load(self, patterns, dirs, ignore=None): diff --git a/autoapi/extension.py b/autoapi/extension.py index 218f28e8..e4f1a748 100644 --- a/autoapi/extension.py +++ b/autoapi/extension.py @@ -256,6 +256,7 @@ def setup(app): app.add_config_value("autoapi_add_toctree_entry", True, "html") app.add_config_value("autoapi_template_dir", None, "html") app.add_config_value("autoapi_include_summaries", None, "html") + app.add_config_value("autoapi_follow_symlinks", False, "html") app.add_config_value("autoapi_python_use_implicit_namespaces", False, "html") app.add_config_value("autoapi_python_class_content", "class", "html") app.add_config_value("autoapi_generate_api_docs", True, "html") diff --git a/docs/changes/+7eaa921c.misc.rst b/docs/changes/+7eaa921c.misc.rst new file mode 100644 index 00000000..85f26cc6 --- /dev/null +++ b/docs/changes/+7eaa921c.misc.rst @@ -0,0 +1 @@ +Handling case where match returns None to fix mypy unit test. diff --git a/docs/changes/+d379912c.feature.rst b/docs/changes/+d379912c.feature.rst new file mode 100644 index 00000000..267459da --- /dev/null +++ b/docs/changes/+d379912c.feature.rst @@ -0,0 +1 @@ +Adding `autoapi_follow_symlinks`, which allows api to traverse into symlinked directories when generating the API documentation. diff --git a/docs/reference/config.rst b/docs/reference/config.rst index 2f527c5e..b4186af5 100644 --- a/docs/reference/config.rst +++ b/docs/reference/config.rst @@ -92,6 +92,14 @@ Customisation Options A list of patterns to ignore when finding directories and files to document. +.. confval:: autoapi_follow_symlinks + + Default: ``False`` + + If `True`, then follow symlinks when walking ``autoapi_dirs`` to generate the API + documentation. If `False`, then do not follow symlinks when when walking + ``autoapi_dirs`` to generate the API documentation. + .. confval:: autoapi_root Default: ``autoapi`` diff --git a/tests/test_integration.py b/tests/test_integration.py index 413cb279..902d3dbb 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -27,11 +27,21 @@ def sphinx_build(test_dir, confoverrides=None): class LanguageIntegrationTests: - def _run_test(self, test_dir, test_file, test_string): - with sphinx_build(test_dir): + def _run_test( + self, + test_dir, + test_file, + test_string, + confoverrides={}, + test_missing: bool = False, + ): + with sphinx_build(test_dir, confoverrides=confoverrides): with open(test_file, encoding="utf8") as fin: text = fin.read().strip() - assert test_string in text + if test_missing: + assert test_string not in text + else: + assert test_string in text class TestIntegration(LanguageIntegrationTests): @@ -54,3 +64,24 @@ def test_toctree_domain_insertion(self): self._run_test( "toctreeexample", "_build/text/index.txt", '* "example_function()"' ) + + def test_symlink(self): + """ + Test that the example_function_2 gets added to the TOC Tree when running with symlinks + and that it does not get added when running without them. + """ + # Without symlinks, should not contain example_function_2 + self._run_test( + "toctreeexample", + "_build/text/index.txt", + '* "example_function_2()"', + test_missing=True, + ) + + # With symlinks, should contain example_function_2 + self._run_test( + "toctreeexample", + "_build/text/index.txt", + '* "example_function_2()"', + confoverrides={"autoapi_follow_symlinks": True}, + ) diff --git a/tests/toctreeexample/example/example_2 b/tests/toctreeexample/example/example_2 new file mode 120000 index 00000000..f2aaf432 --- /dev/null +++ b/tests/toctreeexample/example/example_2 @@ -0,0 +1 @@ +../example_2 \ No newline at end of file diff --git a/tests/toctreeexample/example_2/example_2.py b/tests/toctreeexample/example_2/example_2.py new file mode 100644 index 00000000..08a3d193 --- /dev/null +++ b/tests/toctreeexample/example_2/example_2.py @@ -0,0 +1,8 @@ +__author__ = "leake" + +import math + + +def example_function_2(x): + """Compute the square of x and return it.""" + return x**2