diff --git a/.ci/setup.sh b/.ci/setup.sh index 1ac3983..27d5ea3 100755 --- a/.ci/setup.sh +++ b/.ci/setup.sh @@ -42,6 +42,7 @@ ${CONDA_DIR}/bin/pip install \ coverage \ codecov \ pycodestyle \ + requests \ sphinx \ sphinx_autodoc_typehints \ sphinx_rtd_theme \ diff --git a/.gitignore b/.gitignore index f229b16..9d44530 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,9 @@ **/*.ipynb # R files +**/*.Renviron **/*.Rhistory +**/*.Rprofile **/*.rda **/*.rds **/*.Rdata diff --git a/.travis.yml b/.travis.yml index 23b6e1c..ab81933 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,7 +31,7 @@ matrix: install: - /Users/travis/miniconda3/bin/conda create -q -n testenv python=3.6 nose pytest - source activate testenv - - pip install argparse + - pip install argparse requests - python setup.py install - os: linux language: python diff --git a/doppel/bin/analyze.py b/doppel/bin/analyze.py index 3a1311a..364c72c 100755 --- a/doppel/bin/analyze.py +++ b/doppel/bin/analyze.py @@ -280,9 +280,9 @@ def _is_builtin(obj): # according to the previous checks, but if they're callable # they should count as exported functions elif _is_builtin(obj) and callable(obj): - out["functions"][obj_name] = { - "args": [] - } + if not obj.__module__.startswith(PKG_NAME): + _log_info("Callable '{}' is a built-in not included in this package's namespace. Skipping it.".format(obj.__name__)) + next else: _log_info("Could not figure out what {} is".format(obj_name)) diff --git a/integration_tests/python_tests/test_analyze.py b/integration_tests/python_tests/test_analyze.py index f17e02e..d74a6af 100644 --- a/integration_tests/python_tests/test_analyze.py +++ b/integration_tests/python_tests/test_analyze.py @@ -30,7 +30,8 @@ def rundescribe(): 'testpkguno', 'testpkgdos', 'testpkgtres', - 'pythonspecific' + 'pythonspecific', + 'pythonspecific2' ] # Added this abomination because something about @@ -399,3 +400,57 @@ def test_builtin_method(self, rundescribe): result_json = rundescribe['pythonspecific'] assert result_json['classes']['MinWrapper']['public_methods']['wrap_min'] == {'args': []} + + +class TestPythonSpecific: + """ + Test the behavior of analyze.py for packages using + 'from import *' in __init__.py. This package also + tests the behavior of analyze.py in the presence of top-level + built-in imports like "from warnings import warn" + """ + + def test_top_level_keys(self, rundescribe): + """ + The JSON file produce by doppel-describe + should have only the expected top-level dictionary keys + """ + result_json = rundescribe['pythonspecific2'] + + for top_level_key in EXPECTED_TOP_LEVEL_KEYS: + assert result_json.get(top_level_key, None) is not None + assert len(result_json.keys()) == NUM_TOP_LEVEL_KEYS + + def test_functions(self, rundescribe): + """ + analyze.py should find all functions defined in the package + whose names do not start with "_", regardless of what is in + ``__all__`` in ``__init__.py``. + """ + result_json = rundescribe['pythonspecific2'] + + assert set(result_json['functions'].keys()) == set(['create_warning', 'create_warm_things']) + # imports from other packages are included if you explicitly + # wrap them in a def() + assert 'create_warm_things' in result_json['functions'] + # import from other packages are excluded even if you map them to + # a new name in your package + assert 'shmeate_schmarning' not in result_json['functions'] + # internal functions + assert '_super_secret' not in result_json['functions'].keys() + # imported standard lib function + assert 'warn' not in result_json['functions'].keys() + # imported non-standard-lib function + assert 'get' not in result_json['functions'].keys() + # imports from other packages are ignored even if you rename them + # or add them to __all__ in __init__.py + assert 'custom_post' not in result_json['functions'].keys() + assert 'post' not in result_json['functions'].keys() + + def test_classes(self, rundescribe): + """ + analyze.py should not have found any classes in this + package but should have created the 'classes' section + """ + result_json = rundescribe['pythonspecific2'] + assert result_json['classes'] == {} diff --git a/integration_tests/test-packages/python/pythonspecific2/pythonspecific2/__init__.py b/integration_tests/test-packages/python/pythonspecific2/pythonspecific2/__init__.py new file mode 100644 index 0000000..22988d1 --- /dev/null +++ b/integration_tests/test-packages/python/pythonspecific2/pythonspecific2/__init__.py @@ -0,0 +1,6 @@ +__all__ = [ + 'create_warning', + 'custom_post' +] + +from .module import * diff --git a/integration_tests/test-packages/python/pythonspecific2/pythonspecific2/module.py b/integration_tests/test-packages/python/pythonspecific2/pythonspecific2/module.py new file mode 100644 index 0000000..9a01476 --- /dev/null +++ b/integration_tests/test-packages/python/pythonspecific2/pythonspecific2/module.py @@ -0,0 +1,19 @@ +from warnings import warn +from requests import get +from requests import post as custom_post + + +def create_warning(): + print('uh oh') + warn('not good') + + +shmeate_schmarning = warn + + +def create_warm_things(**kwargs): + warn(**kwargs) + + +def _super_secret(): + print('shhhh') diff --git a/integration_tests/test-packages/python/pythonspecific2/setup.py b/integration_tests/test-packages/python/pythonspecific2/setup.py new file mode 100644 index 0000000..e7c5da6 --- /dev/null +++ b/integration_tests/test-packages/python/pythonspecific2/setup.py @@ -0,0 +1,9 @@ +from setuptools import find_packages +from setuptools import setup + + +setup( + name='pythonspecific2', + version='0.0.1', + packages=find_packages(), +)