From d3810225328e607cd1afe68f7fc1283f8f233b6f Mon Sep 17 00:00:00 2001 From: Joris Van den Bossche Date: Fri, 17 Aug 2018 09:20:11 +0200 Subject: [PATCH] DOC/CI: run doctests on travis (#19952) --- .travis.yml | 3 +- ci/build_docs.sh | 18 ----------- ci/doctests.sh | 60 +++++++++++++++++++++++++++++++++++++ doc/source/contributing.rst | 25 ++++++++++++++++ pandas/core/config.py | 2 +- pandas/core/frame.py | 14 ++++----- pandas/core/generic.py | 12 ++++---- pandas/util/testing.py | 9 +++--- setup.cfg | 2 +- 9 files changed, 105 insertions(+), 40 deletions(-) create mode 100755 ci/doctests.sh diff --git a/.travis.yml b/.travis.yml index 2d2a0bc019c805..32e6d2eae90a75 100644 --- a/.travis.yml +++ b/.travis.yml @@ -56,7 +56,7 @@ matrix: - python-gtk2 - dist: trusty env: - - JOB="3.6, coverage" ENV_FILE="ci/travis-36.yaml" TEST_ARGS="--skip-slow --skip-network" PANDAS_TESTING_MODE="deprecate" COVERAGE=true + - JOB="3.6, coverage" ENV_FILE="ci/travis-36.yaml" TEST_ARGS="--skip-slow --skip-network" PANDAS_TESTING_MODE="deprecate" COVERAGE=true DOCTEST=true # In allow_failures - dist: trusty env: @@ -119,6 +119,7 @@ script: - ci/script_single.sh - ci/script_multi.sh - ci/lint.sh + - ci/doctests.sh - echo "checking imports" - source activate pandas && python ci/check_imports.py - echo "script done" diff --git a/ci/build_docs.sh b/ci/build_docs.sh index 90a666dc34ed76..f445447e3565c4 100755 --- a/ci/build_docs.sh +++ b/ci/build_docs.sh @@ -8,15 +8,6 @@ fi cd "$TRAVIS_BUILD_DIR" echo "inside $0" -git show --pretty="format:" --name-only HEAD~5.. --first-parent | grep -P "rst|txt|doc" - -# if [ "$?" != "0" ]; then -# echo "Skipping doc build, none were modified" -# # nope, skip docs build -# exit 0 -# fi - - if [ "$DOC" ]; then echo "Will build docs" @@ -60,15 +51,6 @@ if [ "$DOC" ]; then git remote -v git push origin gh-pages -f - - echo "Running doctests" - cd "$TRAVIS_BUILD_DIR" - pytest --doctest-modules \ - pandas/core/reshape/concat.py \ - pandas/core/reshape/pivot.py \ - pandas/core/reshape/reshape.py \ - pandas/core/reshape/tile.py - fi exit 0 diff --git a/ci/doctests.sh b/ci/doctests.sh new file mode 100755 index 00000000000000..04b3e14a7120a1 --- /dev/null +++ b/ci/doctests.sh @@ -0,0 +1,60 @@ +#!/bin/bash + +echo "inside $0" + + +source activate pandas +cd "$TRAVIS_BUILD_DIR" + +RET=0 + +if [ "$DOCTEST" ]; then + + echo "Running doctests" + + # running all doctests is not yet working + # pytest --doctest-modules --ignore=pandas/tests -v pandas + + # if [ $? -ne "0" ]; then + # RET=1 + # fi + + # DataFrame / Series docstrings + pytest --doctest-modules -v pandas/core/frame.py \ + -k"-assign -axes -combine -isin -itertuples -join -nlargest -nsmallest -nunique -pivot_table -quantile -query -reindex -reindex_axis -replace -round -set_index -stack -to_dict -to_records -to_stata -transform" + + if [ $? -ne "0" ]; then + RET=1 + fi + + pytest --doctest-modules -v pandas/core/series.py \ + -k"-agg -map -nlargest -nonzero -nsmallest -reindex -searchsorted -to_dict" + + if [ $? -ne "0" ]; then + RET=1 + fi + + pytest --doctest-modules -v pandas/core/generic.py \ + -k"-_set_axis_name -_xs -describe -droplevel -groupby -interpolate -pct_change -pipe -reindex -reindex_axis -resample -sample -to_json -to_xarray -transform -transpose -values -xs" + + if [ $? -ne "0" ]; then + RET=1 + fi + + # top-level reshaping functions + pytest --doctest-modules -v \ + pandas/core/reshape/concat.py \ + pandas/core/reshape/pivot.py \ + pandas/core/reshape/reshape.py \ + pandas/core/reshape/tile.py \ + -k"-crosstab -pivot_table -cut" + + if [ $? -ne "0" ]; then + RET=1 + fi + +else + echo "NOT running doctests" +fi + +exit $RET diff --git a/doc/source/contributing.rst b/doc/source/contributing.rst index 2ab78734f78a53..625df2806add7a 100644 --- a/doc/source/contributing.rst +++ b/doc/source/contributing.rst @@ -365,6 +365,31 @@ This will identify methods documented in ``doc/source/api.rst`` that are not act class methods, and existing methods that are not documented in ``doc/source/api.rst``. +Updating a *pandas* docstring +----------------------------- + +When improving a single function or method's docstring, it is not necessarily +needed to build the full documentation (see next section). +However, there is a script that checks a docstring (for example for the ``DataFrame.mean`` method):: + + python scripts/validate_docstrings.py pandas.DataFrame.mean + +This script will indicate some formatting errors if present, and will also +run and test the examples included in the docstring. +Check the :ref:`pandas docstring guide ` for a detailed guide +on how to format the docstring. + +The examples in the docstring ('doctests') must be valid Python code, +that in a deterministic way returns the presented output, and that can be +copied and run by users. This can be checked with the script above, and is +also tested on Travis. A failing doctest will be a blocker for merging a PR. +Check the :ref:`examples ` section in the docstring guide +for some tips and tricks to get the doctests passing. + +When doing a PR with a docstring update, it is good to post the +output of the validation script in a comment on github. + + How to build the *pandas* documentation --------------------------------------- diff --git a/pandas/core/config.py b/pandas/core/config.py index abcdbfa12e4e96..f178600b74626e 100644 --- a/pandas/core/config.py +++ b/pandas/core/config.py @@ -384,7 +384,7 @@ class option_context(object): -------- >>> with option_context('display.max_rows', 10, 'display.max_columns', 5): - ... + ... ... """ diff --git a/pandas/core/frame.py b/pandas/core/frame.py index 78ad9728800d61..411e3d970ebf58 100644 --- a/pandas/core/frame.py +++ b/pandas/core/frame.py @@ -314,15 +314,13 @@ class DataFrame(NDFrame): Constructing DataFrame from numpy ndarray: - >>> df2 = pd.DataFrame(np.random.randint(low=0, high=10, size=(5, 5)), - ... columns=['a', 'b', 'c', 'd', 'e']) + >>> df2 = pd.DataFrame(np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), + ... columns=['a', 'b', 'c']) >>> df2 - a b c d e - 0 2 8 8 3 4 - 1 4 2 9 0 9 - 2 1 0 7 8 0 - 3 5 1 7 1 3 - 4 6 0 2 4 2 + a b c + 0 1 2 3 + 1 4 5 6 + 2 7 8 9 See also -------- diff --git a/pandas/core/generic.py b/pandas/core/generic.py index 2245abd780edd7..fd372ce2fd78eb 100644 --- a/pandas/core/generic.py +++ b/pandas/core/generic.py @@ -738,14 +738,14 @@ def droplevel(self, level, axis=0): Examples -------- >>> df = pd.DataFrame([ - ...: [1, 2, 3, 4], - ...: [5, 6, 7, 8], - ...: [9, 10, 11, 12] - ...: ]).set_index([0, 1]).rename_axis(['a', 'b']) + ... [1, 2, 3, 4], + ... [5, 6, 7, 8], + ... [9, 10, 11, 12] + ... ]).set_index([0, 1]).rename_axis(['a', 'b']) >>> df.columns = pd.MultiIndex.from_tuples([ - ...: ('c', 'e'), ('d', 'f') - ...:], names=['level_1', 'level_2']) + ... ('c', 'e'), ('d', 'f') + ... ], names=['level_1', 'level_2']) >>> df level_1 c d diff --git a/pandas/util/testing.py b/pandas/util/testing.py index 39ab498d080bfe..1eedb9e2a82749 100644 --- a/pandas/util/testing.py +++ b/pandas/util/testing.py @@ -591,7 +591,7 @@ def set_defaultencoding(encoding): def capture_stdout(f): - """ + r""" Decorator to capture stdout in a buffer so that it can be checked (or suppressed) during testing. @@ -609,7 +609,6 @@ def capture_stdout(f): -------- >>> from pandas.util.testing import capture_stdout - >>> >>> import sys >>> >>> @capture_stdout @@ -639,7 +638,7 @@ def wrapper(*args, **kwargs): def capture_stderr(f): - """ + r""" Decorator to capture stderr in a buffer so that it can be checked (or suppressed) during testing. @@ -657,7 +656,6 @@ def capture_stderr(f): -------- >>> from pandas.util.testing import capture_stderr - >>> >>> import sys >>> >>> @capture_stderr @@ -2370,7 +2368,7 @@ def wrapper(*args, **kwargs): def assert_raises_regex(_exception, _regexp, _callable=None, *args, **kwargs): - r""" + """ Check that the specified Exception is raised and that the error message matches a given regular expression pattern. This may be a regular expression object or a string containing a regular expression suitable @@ -2396,6 +2394,7 @@ def assert_raises_regex(_exception, _regexp, _callable=None, AssertionError: "pear" does not match "'apple'" You can also use this in a with statement. + >>> with assert_raises_regex(TypeError, 'unsupported operand type\(s\)'): ... 1 + {} >>> with assert_raises_regex(TypeError, 'banana'): diff --git a/setup.cfg b/setup.cfg index 96f447e90cd58d..c4e3243d824e58 100644 --- a/setup.cfg +++ b/setup.cfg @@ -38,5 +38,5 @@ markers = slow: mark a test as slow network: mark a test as network high_memory: mark a test as a high-memory only +doctest_optionflags = NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL addopts = --strict-data-files -doctest_optionflags= NORMALIZE_WHITESPACE IGNORE_EXCEPTION_DETAIL \ No newline at end of file