Skip to content

Commit

Permalink
Update docs. Add CHANGELOG entry
Browse files Browse the repository at this point in the history
  • Loading branch information
Erotemic committed Sep 2, 2021
1 parent 26214d2 commit d85d12b
Show file tree
Hide file tree
Showing 8 changed files with 174 additions and 47 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,15 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm

## Version 0.15.7 - Unreleased

### Changed
* Removed the distracting and very long internal traceback that occurred in
pytest when a module errors while it is being imported before the doctest is
run.


### Fixed
* Bug in REQUIRES state did not respect `python_implementation` arguments
* Ported sphinx fixes from ubelt

## Version 0.15.6 - Released 2021-08-08

Expand Down
5 changes: 1 addition & 4 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,9 @@ sphinx
sphinx-autobuild
sphinx_rtd_theme
sphinxcontrib-napoleon

sphinx-autoapi

six
Pygments

ubelt

sphinx-reredirects
myst_parser
102 changes: 100 additions & 2 deletions docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,6 @@ def visit_Assign(self, node):
sphobjinv suggest -t 90 -u https://readthedocs.org/projects/pytest/reference/objects.inv
"signal.convolve2d"
python -m sphinx.ext.intersphinx https://pygments-doc.readthedocs.io/en/latest/objects.inv
"""
Expand All @@ -294,8 +293,107 @@ def visit_Assign(self, node):
# 'pygments': ('https://pygments-doc.readthedocs.io/en/latest/', None),
# 'colorama': ('https://pypi.org/project/colorama/', None),
'python': ('https://docs.python.org/3', None),
# 'ubelt': ('https://readthedocs.org/projects/ubelt/', None),
'ubelt': ('https://ubelt.readthedocs.io/en/latest/', None),
# 'numpy': ('http://docs.scipy.org/doc/numpy/', None),
# 'cv2' : ('http://docs.opencv.org/2.4/', None),
# 'h5py' : ('http://docs.h5py.org/en/latest/', None)
}
__dev_note__ = """
python -m sphinx.ext.intersphinx https://docs.python.org/3/objects.inv
python -m sphinx.ext.intersphinx https://ubelt.readthedocs.io/en/latest/objects.inv
python -m sphinx.ext.intersphinx https://networkx.org/documentation/stable/objects.inv
"""


# -- Extension configuration -------------------------------------------------


from sphinx.domains.python import PythonDomain # NOQA


class PatchedPythonDomain(PythonDomain):
"""
References:
https://github.com/sphinx-doc/sphinx/issues/3866
"""
def resolve_xref(self, env, fromdocname, builder, typ, target, node, contnode):
# TODO: can use this to resolve references nicely
if target.startswith('xdoc.'):
target = 'xdoctest.' + target[3]
return_value = super(PatchedPythonDomain, self).resolve_xref(
env, fromdocname, builder, typ, target, node, contnode)
return return_value


def setup(app):
# app.add_domain(PatchedPythonDomain, override=True)

if 1:
# https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html
from sphinx.application import Sphinx
from typing import Any, List

what = None
# Custom process to transform docstring lines
# Remove "Ignore" blocks
def process(app: Sphinx, what_: str, name: str, obj: Any, options: Any, lines: List[str]
) -> None:
if what and what_ not in what:
return
orig_lines = lines[:]

# text = '\n'.join(lines)
# if 'Example' in text and 'CommandLine' in text:
# import xdev
# xdev.embed()

ignore_tags = tuple(['Ignore'])

mode = None
# buffer = None
new_lines = []
for i, line in enumerate(orig_lines):

# See if the line triggers a mode change
if line.startswith(ignore_tags):
mode = 'ignore'
elif line.startswith('CommandLine'):
mode = 'cmdline'
elif line and not line.startswith(' '):
# if the line startswith anything but a space, we are no
# longer in the previous nested scope
mode = None

if mode is None:
new_lines.append(line)
elif mode == 'ignore':
# print('IGNORE line = {!r}'.format(line))
pass
elif mode == 'cmdline':
if line.startswith('CommandLine'):
new_lines.append('.. rubric:: CommandLine')
new_lines.append('')
new_lines.append('.. code-block:: bash')
new_lines.append('')
# new_lines.append(' # CommandLine')
else:
# new_lines.append(line.strip())
new_lines.append(line)
else:
raise KeyError(mode)

lines[:] = new_lines
# make sure there is a blank line at the end
if lines and lines[-1]:
lines.append('')

app.connect('autodoc-process-docstring', process)
else:
# https://stackoverflow.com/questions/26534184/can-sphinx-ignore-certain-tags-in-python-docstrings
# Register a sphinx.ext.autodoc.between listener to ignore everything
# between lines that contain the word IGNORE
# from sphinx.ext.autodoc import between
# app.connect('autodoc-process-docstring', between('^ *Ignore:$', exclude=True))
pass

return app
2 changes: 1 addition & 1 deletion xdoctest/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ def output_difference(self, runstate=None, colored=True):
for a given example (`example`) and the actual output (`got`).
The `runstate` contains option flags used to compare `want` and `got`.
Notes:
Note:
This does not check if got matches want, it only outputs the raw
differences. Got/Want normalization may make the differences appear
more exagerated than they are.
Expand Down
28 changes: 20 additions & 8 deletions xdoctest/doctest_example.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,14 +272,16 @@ def is_disabled(self, pytest=False):
"""
Checks for comment directives on the first line of the doctest
A doctest is disabled if it starts with any of the following patterns:
# DISABLE_DOCTEST
# SCRIPT
# UNSTABLE
# FAILING
A doctest is disabled if it starts with any of the following patterns
* ``>>> # DISABLE_DOCTEST``
* ``>>> # SCRIPT``
* ``>>> # UNSTABLE``
* ``>>> # FAILING``
And if running in pytest, you can also use
# pytest.skip
* ``>>> import pytest; pytest.skip()``
"""
disable_patterns = [
r'>>>\s*#\s*DISABLE',
Expand All @@ -300,15 +302,23 @@ def is_disabled(self, pytest=False):

@property
def unique_callname(self):
"""
A key that references this doctest within xdoctest given its module
"""
return self.callname + ':' + str(self.num)

@property
def node(self):
""" this pytest node """
"""
A key that references this doctest within pytest
"""
return self.modpath + '::' + self.callname + ':' + str(self.num)

@property
def valid_testnames(self):
"""
A set of callname and unique_callname
"""
return {
self.callname,
self.unique_callname,
Expand All @@ -325,7 +335,9 @@ def wants(self):

def format_parts(self, linenos=True, colored=None, want=True,
offset_linenos=None, prefix=True):
""" used by format_src """
"""
Used by :func:`format_src`
"""
self._parse()
colored = self.config.getvalue('colored', colored)
partnos = self.config.getvalue('partnos')
Expand Down
66 changes: 38 additions & 28 deletions xdoctest/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,28 @@
Terms and definitions:
logical block: a snippet of code that can be executed by itself if given
the correct global / local variable context.
PS1 : The original meaning is "Prompt String 1". In the context of
xdoctest, instead of referring to the prompt prefix, we use PS1 to refer
to a line that starts a "logical block" of code. In the original
doctest module these all had to be prefixed with ">>>". In xdoctest the
prefix is used to simply denote the code is part of a doctest. It does
not necessarilly mean a new "logical block" is starting.
PS2 : The original meaning is "Prompt String 2". In the context of
xdoctest, instead of referring to the prompt prefix, we use PS2 to refer
to a line that continues a "logical block" of code. In the original
doctest module these all had to be prefixed with "...". However,
xdoctest uses parsing to automatically determine this.
want statement: Lines directly after a logical block of code in a doctest
indicating the desired result of executing the previous block.
logical block:
a snippet of code that can be executed by itself if given the correct
global / local variable context.
PS1:
The original meaning is "Prompt String 1". In the context of xdoctest,
instead of referring to the prompt prefix, we use PS1 to refer to a
line that starts a "logical block" of code. In the original doctest
module these all had to be prefixed with ">>>". In xdoctest the prefix
is used to simply denote the code is part of a doctest. It does not
necessarilly mean a new "logical block" is starting.
PS2:
The original meaning is "Prompt String 2". In the context of xdoctest,
instead of referring to the prompt prefix, we use PS2 to refer to a
line that continues a "logical block" of code. In the original doctest
module these all had to be prefixed with "...". However, xdoctest uses
parsing to automatically determine this.
want statement:
Lines directly after a logical block of code in a doctest indicating
the desired result of executing the previous block.
While I do believe this AST-based code is a significant improvement over the
RE-based builtin doctest parser, I acknowledge that I'm not an AST expert and
Expand Down Expand Up @@ -216,7 +220,8 @@ def _package_chunk(self, raw_source_lines, raw_want_lines, lineno=0):
own part. Otherwise, statements are grouped by the closest `want`
statement.
TODO: EXCEPT IN CASES OF EXPLICIT CONTINUATION
TODO:
- [ ] EXCEPT IN CASES OF EXPLICIT CONTINUATION
Example:
>>> from xdoctest.parser import *
Expand Down Expand Up @@ -429,9 +434,10 @@ def _locate_ps1_linenos(self, source_lines):
these will be unindented, prefixed, and without any want.
Returns:
Tuple[List[int], bool]: a list of indices indicating which lines
are considered "PS1" and a flag indicating if the final line
should be considered for a got/want assertion.
Tuple[List[int], bool]:
a list of indices indicating which lines are considered "PS1"
and a flag indicating if the final line should be considered
for a got/want assertion.
Example:
>>> self = DoctestParser()
Expand Down Expand Up @@ -557,14 +563,15 @@ def _workaround_16806(ps1_linenos, exec_source_lines):
exec_source_lines (List[str]): code referenced by ps1_linenos
Returns:
List[int]: new_ps1_lines: Fixed `ps1_linenos` where multiline
strings now point to the line where they begin.
List[int]: new_ps1_lines
Fixed `ps1_linenos` where multiline strings now point to the
line where they begin.
Notes:
Note:
A patch for this issue exists
`https://github.com/python/cpython/pull/1800`. This workaround is a
idempotent (i.e. a no-op) when line numbers are correct, so nothing
should break when this bug is fixed.
`<https://github.com/python/cpython/pull/1800>`_. This workaround
is a idempotent (i.e. a no-op) when line numbers are correct, so
nothing should break when this bug is fixed.
Starting from the end look at consecutive pairs of indices to
inspect the statement it corresponds to. (the first statement goes
Expand Down Expand Up @@ -605,6 +612,9 @@ def _label_docsrc_lines(self, string):
up by lines, each with a label indicating its type for later
use in parsing.
TODO:
- [ ] Sphinx does not parse this doctest properly
Example:
>>> from xdoctest.parser import *
>>> # Having multiline strings in doctests can be nice
Expand Down
9 changes: 6 additions & 3 deletions xdoctest/utils/util_import.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ def import_module_from_path(modpath, index=-1):
References:
https://stackoverflow.com/questions/67631/import-module-given-path
Notes:
Note:
If the module is part of a package, the package will be imported first.
These modules may cause problems when reloading via IPython magic
Expand All @@ -231,6 +231,9 @@ def import_module_from_path(modpath, index=-1):
For example if you try to import '/foo/bar/pkg/mod.py' from the folder
structure:
.. code::
- foo/
+- bar/
+- pkg/
Expand Down Expand Up @@ -408,7 +411,7 @@ def _syspath_modname_to_modpath(modname, sys_path=None, exclude=None):
list of directory paths. if specified prevents these directories
from being searched.
Notes:
Note:
This is much slower than the pkgutil mechanisms.
Example:
Expand Down Expand Up @@ -565,7 +568,7 @@ def normalize_modpath(modpath, hide_init=True, hide_main=False):
Returns:
PathLike: a normalized path to the module
Notes:
Note:
Adds __init__ if reasonable, but only removes __main__ by default
Example:
Expand Down
2 changes: 1 addition & 1 deletion xdoctest/utils/util_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def isatty(self): # nocover
"""
Returns true of the redirect is a terminal.
Notes:
Note:
Needed for IPython.embed to work properly when this class is used
to override stdout / stderr.
"""
Expand Down

0 comments on commit d85d12b

Please sign in to comment.