Hide navigation sidebar
Hide table of contents sidebar
Skip to content
Back to top
View this page
Toggle table of contents sidebar

# 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 the `DocTestDirectiveParser` as described in the doctest section. Some of the options aren’t supported, but their behaviour can be replaced by preceding the `doctest` with a skip directive.

- `testcode` and `testoutput` 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.