Skip to content

Commit

Permalink
Doctests (#385)
Browse files Browse the repository at this point in the history
First off, thank you for the library, it is a joy to use.

I was looking at the docstr for `pylatex.Document` in IPython and I
found myself wishing that it had an example of a minimal use-case. I was
able to look at the read the docs to get what I needed, but it would
have been faster if it was builtin to the docstring itself. So I added a
doctest that give a "hello world" example.

You'll note there I also added a "demo" section that gives a command
line that will invoke the doctest, as well as a section at the end which
uses my xdev package to actually pop the open the generated document for
the user in whatever the default pdf program is. I think these are nice,
but also not strictly necessary and it does require an extra dependency
if you actually want to execute the doctest, so it could be paired down
if desired.

However, I noticed that there were other doctests in the package, but it
seems like they are never being run in CI and many of them are out of
data and display incorrect results. I went through and fixed these. To
ensure these don't break again I added the xdoctest package as a
test-time dependency and included it in the `testall.sh` script, so now
doctests will be run with the unit tests when invoking pytest. I also
added a section to the pyproject.toml that does "the right thing" when
the users just runs pytest in the repo root. There was also a minor
issue with pytest attempting to pickup a `__pycache__` directory that I
fixed.

Lastly, there were a few deprecation warnings I saw in the test result.
np.matrix is deprecated, so I replaced it with np.array. Also there were
some backslashes that were not escaped correctly in two test files that
I fixed.
  • Loading branch information
Erotemic authored Jul 23, 2024
1 parent b0218ae commit 3b19b9e
Show file tree
Hide file tree
Showing 9 changed files with 82 additions and 31 deletions.
16 changes: 8 additions & 8 deletions pylatex/base_classes/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,13 @@ def __init__(
>>> options=Options('12pt', 'a4paper', 'twoside'),
>>> arguments='article').dumps()
'\\documentclass[12pt,a4paper,twoside]{article}'
>>> Command('com')
>>> Command('com').dumps()
'\\com'
>>> Command('com', 'first')
>>> Command('com', 'first').dumps()
'\\com{first}'
>>> Command('com', 'first', 'option')
>>> Command('com', 'first', 'option').dumps()
'\\com[option]{first}'
>>> Command('com', 'first', 'option', 'second')
>>> Command('com', 'first', 'option', extra_arguments='second').dumps()
'\\com{first}[option]{second}'
"""
Expand Down Expand Up @@ -326,10 +326,10 @@ class Options(Parameters):
Examples
--------
>>> args = Options('a', 'b', 'c').dumps()
>>> Options('a', 'b', 'c').dumps()
'[a,b,c]'
>>> Options('clip', width=50, height='25em', trim='1 2 3 4').dumps()
'[clip,trim=1 2 3 4,width=50,height=25em]'
'[clip,width=50,height=25em,trim=1 2 3 4]'
"""

Expand Down Expand Up @@ -367,9 +367,9 @@ class Arguments(Parameters):
Examples
--------
>>> args = Arguments('a', 'b', 'c').dumps()
>>> Arguments('a', 'b', 'c').dumps()
'{a}{b}{c}'
>>> args = Arguments('clip', width=50, height='25em').dumps()
>>> args = Arguments('clip', width=50, height='25em')
>>> args.dumps()
'{clip}{width=50}{height=25em}'
Expand Down
41 changes: 41 additions & 0 deletions pylatex/document.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,47 @@ class Document(Environment):
For instance, if you need to use ``\maketitle`` you can add the title,
author and date commands to the preamble to make it work.
Example
-------
>>> import pylatex
>>> import pathlib
>>> import tempfile
>>> # Create a place where we can write our PDF to disk
>>> temp_output_path = pathlib.Path(tempfile.mkdtemp())
>>> temp_output_path.mkdir(exist_ok=True)
>>> document_fpath = temp_output_path / 'my_document.pdf'
>>> # The Document class is the main point of interaction.
>>> doc = pylatex.Document(
>>> document_fpath.with_suffix(''), # give the output file path without the .pdf
>>> inputenc=None,
>>> page_numbers=False,
>>> indent=False,
>>> fontenc=None,
>>> lmodern=True,
>>> textcomp=False,
>>> documentclass='article',
>>> geometry_options='paperheight=0.4in,paperwidth=1in,margin=0.1in',
>>> )
>>> # Append content to the document, which can be plain text, or
>>> # object from pylatex. For now lets just say hello!
>>> doc.append('Hello World')
>>> # Inspect the generated latex
>>> print(doc.dumps())
\documentclass{article}%
\usepackage{lmodern}%
\usepackage{parskip}%
\usepackage{geometry}%
\geometry{paperheight=0.4in,paperwidth=1in,margin=0.1in}%
%
%
%
\begin{document}%
\pagestyle{empty}%
\normalsize%
Hello World%
\end{document}
>>> # Generate and the PDF in document_fpath
>>> doc.generate_pdf()
"""

def __init__(
Expand Down
6 changes: 3 additions & 3 deletions pylatex/quantities.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,20 +91,20 @@ def __init__(self, quantity, *, options=None, format_cb=None):
>>> speed = 3.14159265 * pq.meter / pq.second
>>> Quantity(speed, options={'round-precision': 3,
... 'round-mode': 'figures'}).dumps()
'\\SI[round-mode=figures,round-precision=3]{3.14159265}{\meter\per\second}'
'\\SI[round-precision=3,round-mode=figures]{3.14159265}{\\meter\\per\\second}'
Uncertainties are also handled:
>>> length = pq.UncertainQuantity(16.0, pq.meter, 0.3)
>>> width = pq.UncertainQuantity(16.0, pq.meter, 0.4)
>>> Quantity(length*width).dumps()
'\\SI{256.0 +- 0.5}{\meter\tothe{2}}
'\\SI{256.0 +- 8.0}{\\meter\\tothe{2}}'
Ordinary numbers are also supported:
>>> Avogadro_constant = 6.022140857e23
>>> Quantity(Avogadro_constant, options={'round-precision': 3}).dumps()
'\\num[round-precision=3]{6.022e23}'
'\\num[round-precision=3]{6.022140857e+23}'
"""
import numpy as np
Expand Down
33 changes: 18 additions & 15 deletions pylatex/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,11 @@ def escape_latex(s):
Examples
--------
>>> escape_latex("Total cost: $30,000")
'Total cost: \$30,000'
NoEscape(Total cost: \$30,000)
>>> escape_latex("Issue #5 occurs in 30% of all cases")
'Issue \#5 occurs in 30\% of all cases'
NoEscape(Issue \#5 occurs in 30\% of all cases)
>>> print(escape_latex("Total cost: $30,000"))
Total cost: \$30,000
References
----------
Expand Down Expand Up @@ -126,7 +127,8 @@ def fix_filename(path):
>>> fix_filename("/etc/local/foo.bar.baz/document.pdf")
'/etc/local/foo.bar.baz/document.pdf'
>>> fix_filename("/etc/local/foo.bar.baz/foo~1/document.pdf")
'\detokenize{/etc/local/foo.bar.baz/foo~1/document.pdf}'
'\\detokenize{/etc/local/foo.bar.baz/foo~1/document.pdf}'
"""

path_parts = path.split("/" if os.name == "posix" else "\\")
Expand Down Expand Up @@ -174,16 +176,17 @@ def dumps_list(l, *, escape=True, token="%\n", mapper=None, as_content=True):
Examples
--------
>>> dumps_list([r"\textbf{Test}", r"\nth{4}"])
'\\textbf{Test}%\n\\nth{4}'
NoEscape(\textbackslash{}textbf\{Test\}%
\textbackslash{}nth\{4\})
>>> print(dumps_list([r"\textbf{Test}", r"\nth{4}"]))
\textbf{Test}
\nth{4}
\textbackslash{}textbf\{Test\}%
\textbackslash{}nth\{4\}
>>> print(pylatex.utils.dumps_list(["There are", 4, "lights!"]))
There are
4
There are%
4%
lights!
>>> print(dumps_list(["$100%", "True"], escape=True))
\$100\%
\$100\%%
True
"""
strings = (
Expand Down Expand Up @@ -254,7 +257,7 @@ def bold(s, *, escape=True):
Examples
--------
>>> bold("hello")
'\\textbf{hello}'
NoEscape(\textbf{hello})
>>> print(bold("hello"))
\textbf{hello}
"""
Expand Down Expand Up @@ -285,7 +288,7 @@ def italic(s, *, escape=True):
Examples
--------
>>> italic("hello")
'\\textit{hello}'
NoEscape(\textit{hello})
>>> print(italic("hello"))
\textit{hello}
"""
Expand Down Expand Up @@ -315,10 +318,10 @@ def verbatim(s, *, delimiter="|"):
Examples
--------
>>> verbatim(r"\renewcommand{}")
'\\verb|\\renewcommand{}|'
NoEscape(\verb|\renewcommand{}|)
>>> print(verbatim(r"\renewcommand{}"))
\verb|\renewcommand{}|
>>> print(verbatim('pi|pe', '!'))
>>> print(verbatim('pi|pe', delimiter='!'))
\verb!pi|pe!
"""

Expand All @@ -335,8 +338,8 @@ def make_temp_dir():
Examples
--------
>>> make_temp_dir()
'/var/folders/g9/ct5f3_r52c37rbls5_9nc_qc0000gn/T/pylatex'
>>> make_temp_dir() # xdoctest: +IGNORE_WANT
'/tmp/pylatex-tmp.y_b7xp21'
"""

global _tmp_path
Expand Down
7 changes: 7 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,10 @@ skip = ['.bzr', '.direnv', '.eggs', '.git', '.hg', '.mypy_cache', '.nox', '.pant

[tool.black]
extend-exclude = 'versioneer\.py|src'

[tool.pytest.ini_options]
addopts = "--xdoctest --ignore-glob=setup.py --ignore-glob=docs"
norecursedirs = ".git __pycache__ docs"
filterwarnings = [
"default",
]
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"matrices": ["numpy"],
"matplotlib": ["matplotlib"],
"quantities": ["quantities", "numpy"],
"testing": ["pytest>=4.6", "coverage", "pytest-cov", "black", "isort"],
"testing": ["pytest>=4.6", "coverage", "pytest-cov", "black", "isort", "xdoctest"],
"packaging": ["twine"],
}

Expand Down
2 changes: 1 addition & 1 deletion testall.sh
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ else
fi

echo -e '\e[32mTesting tests directory\e[0m'
if ! $python "$(command -v pytest)" --cov=pylatex tests/*; then
if ! $python "$(command -v pytest)" --xdoctest --cov=pylatex pylatex tests/*.py; then
exit 1
fi
mv .coverage{,.tests}
Expand Down
4 changes: 2 additions & 2 deletions tests/test_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def test_math():
repr(vec)

# Numpy
m = np.matrix([[2, 3, 4], [0, 0, 1], [0, 0, 2]])
m = np.array([[2, 3, 4], [0, 0, 1], [0, 0, 2]])

matrix = Matrix(matrix=m, mtype="p", alignment=None)
repr(matrix)
Expand Down Expand Up @@ -210,7 +210,7 @@ def test_graphics():
# Subfigure
s = SubFigure(data=None, position=None, width=r"0.45\linewidth")

s.add_image(filename="", width="r\linewidth", placement=None)
s.add_image(filename="", width=r"r\linewidth", placement=None)

s.add_caption(caption="")
repr(s)
Expand Down
2 changes: 1 addition & 1 deletion tests/test_utils_fix_filename.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,5 +55,5 @@ def test_dots_in_path_and_multiple_in_filename():
def test_tilde_in_filename():
fname = "/etc/local/foo.bar.baz/foo~1/document.pdf"
assert (
fix_filename(fname) == "\detokenize{/etc/local/foo.bar.baz/foo~1/document.pdf}"
fix_filename(fname) == r"\detokenize{/etc/local/foo.bar.baz/foo~1/document.pdf}"
)

0 comments on commit 3b19b9e

Please sign in to comment.