Patterns of Use¶
Several commons patterns of use for Sybil are covered here.
Documentation and source examples in Restructured Text¶
If your project looks like this:
├─docs/
│ ├─conf.py
│ └─index.rst
├─src/
│ └─myproj/
│ └─__init__.py
├─conftest.py
├─pytest.ini
└─setup.py
And if your documentation looks like this:
My Project
==========
.. invisible-code-block: python
import myproj
myproj.SEED = 0.3
This project helps you bar your foo!
>>> from myproj import foo
>>> foo('baz')
'baz bar bar bar'
With your examples in source code looking like this:
"""
This package foo's stuff so it bars.
:data:`SEED` is normally random, but if you like you
can set it as follows:
.. code-block:: python
import myproj
myproj.SEED = 0.2
"""
import random
SEED: float = random.random()
def foo(text: str):
"""
Put some ``bar`` on your ``foo``!
>>> foo('stuff')
'stuff bar bar'
"""
return text+int(SEED*10)*' bar'
Then the following configuration in a conftest.py
will ensure all
your examples are correct:
import pytest
from sybil import Sybil
from sybil.parsers.rest import DocTestParser, PythonCodeBlockParser
@pytest.fixture(scope='session')
def keep_seed():
import myproj
seed = myproj.SEED
yield
myproj.SEED = seed
pytest_collect_file = Sybil(
parsers=[
DocTestParser(),
PythonCodeBlockParser(),
],
patterns=['*.rst', '*.py'],
fixtures=['keep_seed']
).pytest()
Documentation in MyST and source examples in Restructured Text¶
If your project looks like this:
├─docs/
│ ├─conf.py
│ └─index.md
├─src/
│ └─myproj/
│ └─__init__.py
├─conftest.py
├─pytest.ini
└─setup.py
And if your documentation looks like this:
My Project
####
% invisible-code-block: python
%
% import myproj
% myproj.SEED = 0.3
This project helps you bar your foo!
```{doctest}
>>> from myproj import foo
>>> foo('baz')
'baz bar bar bar'
```
With your examples in source code looking like this:
"""
This package foo's stuff so it bars.
:data:`SEED` is normally random, but if you like you
can set it as follows:
.. code-block:: python
import myproj
myproj.SEED = 0.2
"""
import random
SEED: float = random.random()
def foo(text: str):
"""
Put some ``bar`` on your ``foo``!
>>> foo('stuff')
'stuff bar bar'
"""
return text+int(SEED*10)*' bar'
Then the following configuration in a conftest.py
will ensure all
your examples are correct:
import pytest
from sybil import Sybil
from sybil.parsers.myst import (
DocTestDirectiveParser as MarkdownDocTestParser,
PythonCodeBlockParser as MarkdownPythonCodeBlockParser
)
from sybil.parsers.rest import (
DocTestParser as ReSTDocTestParser,
PythonCodeBlockParser as ReSTPythonCodeBlockParser
)
@pytest.fixture(scope='session')
def keep_seed():
import myproj
seed = myproj.SEED
yield
myproj.SEED = seed
markdown_examples = Sybil(
parsers=[
MarkdownDocTestParser(),
MarkdownPythonCodeBlockParser(),
],
patterns=['*.md'],
fixtures=['keep_seed']
)
rest_examples = Sybil(
parsers=[
ReSTDocTestParser(),
ReSTPythonCodeBlockParser(),
],
patterns=['*.py'],
fixtures=['keep_seed']
)
pytest_collect_file = (markdown_examples+rest_examples).pytest()
Linting and checking examples¶
If you wish to perform linting of examples in addition to checking that they are correct, you will need to parse each documentation file once for linting and once for checking.
This can be done by having one Sybil
to do the linting and another Sybil
to do the checking.
Given documentation that looks like this:
My Project
==========
Here is a function:
.. code-block: python
def a_function(text: str) -> str:
return f'a function called with {text}'
Let's see this function in use:
>>> a_function('baz')
'a function called with baz'
Then the following configuration in a conftest.py
could be used to ensure all
your examples are both correct and lint-free:
from typing import Optional
from sybil import Sybil, Example
from sybil.parsers.rest import DocTestParser, PythonCodeBlockParser, CodeBlockParser
def lint_python_source(example: Example) -> Optional[str]:
# here you'd feed example.parsed, which contains the python source of the
# .. code-block:: python, to your linting tool of choice
pass
linting = Sybil(
name='linting',
parsers=[
CodeBlockParser(language='python', evaluator=lint_python_source),
],
patterns=['*.rst'],
)
tests = Sybil(
name='tests',
parsers=[
DocTestParser(),
PythonCodeBlockParser(),
],
patterns=['*.rst'],
)
pytest_collect_file = (linting + tests).pytest()
Migrating from sphinx.ext.doctest¶
Sybil currently has partial support for
sphinx.ext.doctest.
The list below shows how to approach migrating or supporting the various
directives from sphinx.ext.doctest
. Adding further support won’t be hard,
so if anything is missing that’s holding you back, please open an issue on GitHub.
After that, it’s mainly left to stop running make doctest
!
testsetup
can be replaced with invisible-code-block.testcleanup
can be replaced with invisible-code-block.doctest
is supported using theDocTestDirectiveParser
as described in the doctest section. Some of the options aren’t supported, but their behaviour can be replaced by preceding thedoctest
with a skip directive.testcode
andtestoutput
would need parsers and evaluators to be written, however, they could probably just be replaced with a doctest block.groups
aren’t supported, but you can achieve test isolation using clear-namespace.