First attempt to overhaul goodpractices.rst
This commit is contained in:
parent
b3c1377693
commit
6c60840aae
|
@ -393,6 +393,7 @@ intersphinx_mapping = {
|
|||
"tox": ("https://tox.wiki/en/stable", None),
|
||||
"virtualenv": ("https://virtualenv.pypa.io/en/stable", None),
|
||||
"setuptools": ("https://setuptools.pypa.io/en/stable", None),
|
||||
"packaging": ("https://packaging.python.org/en/latest", None),
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -12,41 +12,26 @@ For development, we recommend you use :mod:`venv` for virtual environments and
|
|||
as well as the ``pytest`` package itself.
|
||||
This ensures your code and dependencies are isolated from your system Python installation.
|
||||
|
||||
Next, place a ``pyproject.toml`` file in the root of your package:
|
||||
Create a ``pyproject.toml`` file in the root of your repository as described in
|
||||
:doc:`packaging:tutorials/packaging-projects`.
|
||||
The first few lines should look like this:
|
||||
|
||||
.. code-block:: toml
|
||||
|
||||
[build-system]
|
||||
requires = ["setuptools>=42", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
and a ``setup.cfg`` file containing your package's metadata with the following minimum content:
|
||||
|
||||
.. code-block:: ini
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[metadata]
|
||||
name = PACKAGENAME
|
||||
|
||||
[options]
|
||||
packages = find:
|
||||
name = "PACKAGENAME"
|
||||
|
||||
where ``PACKAGENAME`` is the name of your package.
|
||||
|
||||
.. note::
|
||||
|
||||
If your pip version is older than ``21.3``, you'll also need a ``setup.py`` file:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from setuptools import setup
|
||||
|
||||
setup()
|
||||
|
||||
You can then install your package in "editable" mode by running from the same directory:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
pip install -e .
|
||||
pip install -e .
|
||||
|
||||
which lets you change your source code (both tests and application) and rerun tests at will.
|
||||
|
||||
|
@ -112,43 +97,24 @@ This has the following benefits:
|
|||
See :ref:`pytest vs python -m pytest` for more information about the difference between calling ``pytest`` and
|
||||
``python -m pytest``.
|
||||
|
||||
Note that this scheme has a drawback if you are using ``prepend`` :ref:`import mode <import-modes>`
|
||||
(which is the default): your test files must have **unique names**, because
|
||||
``pytest`` will import them as *top-level* modules since there are no packages
|
||||
to derive a full package name from. In other words, the test files in the example above will
|
||||
be imported as ``test_app`` and ``test_view`` top-level modules by adding ``tests/`` to
|
||||
``sys.path``.
|
||||
For the most convenient experience,
|
||||
choose the ``import-lib`` :ref:`import mode <import-modes>`.
|
||||
To this end, add the following to your ``pyproject.toml``:
|
||||
|
||||
If you need to have test modules with the same name, you might add ``__init__.py`` files to your
|
||||
``tests`` folder and subfolders, changing them to packages:
|
||||
.. code-block:: toml
|
||||
|
||||
.. code-block:: text
|
||||
[tool.pytest.ini_options]
|
||||
addopts = [
|
||||
"--import-mode=importlib",
|
||||
]
|
||||
|
||||
pyproject.toml
|
||||
setup.cfg
|
||||
mypkg/
|
||||
...
|
||||
tests/
|
||||
__init__.py
|
||||
foo/
|
||||
__init__.py
|
||||
test_view.py
|
||||
bar/
|
||||
__init__.py
|
||||
test_view.py
|
||||
|
||||
Now pytest will load the modules as ``tests.foo.test_view`` and ``tests.bar.test_view``, allowing
|
||||
you to have modules with the same name. But now this introduces a subtle problem: in order to load
|
||||
the test modules from the ``tests`` directory, pytest prepends the root of the repository to
|
||||
``sys.path``, which adds the side-effect that now ``mypkg`` is also importable.
|
||||
|
||||
This is problematic if you are using a tool like `tox`_ to test your package in a virtual environment,
|
||||
because you want to test the *installed* version of your package, not the local code from the repository.
|
||||
The default :ref:`import mode <import-modes>` ``prepend`` has several drawbacks [1]_.
|
||||
|
||||
.. _`src-layout`:
|
||||
|
||||
In this situation, it is **strongly** suggested to use a ``src`` layout where application root package resides in a
|
||||
sub-directory of your root:
|
||||
Generally, but especially if you use the default import mode,
|
||||
it is **strongly** suggested to use a ``src`` layout.
|
||||
Here, your application root package resides in a sub-directory of your root:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
|
@ -159,26 +125,11 @@ sub-directory of your root:
|
|||
__init__.py
|
||||
app.py
|
||||
view.py
|
||||
tests/
|
||||
__init__.py
|
||||
foo/
|
||||
__init__.py
|
||||
test_view.py
|
||||
bar/
|
||||
__init__.py
|
||||
test_view.py
|
||||
|
||||
tests/...
|
||||
|
||||
This layout prevents a lot of common pitfalls and has many benefits, which are better explained in this excellent
|
||||
`blog post by Ionel Cristian Mărieș <https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure>`_.
|
||||
|
||||
.. note::
|
||||
The ``--import-mode=importlib`` option (see :ref:`import-modes`) does not have
|
||||
any of the drawbacks above because ``sys.path`` is not changed when importing
|
||||
test modules, so users that run into this issue are strongly encouraged to try it.
|
||||
|
||||
The ``src`` directory layout is still strongly recommended however.
|
||||
|
||||
|
||||
Tests as part of application code
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
@ -285,3 +236,47 @@ See also `pypa/setuptools#1684 <https://github.com/pypa/setuptools/issues/1684>`
|
|||
|
||||
setuptools intends to
|
||||
`remove the test command <https://github.com/pypa/setuptools/issues/931>`_.
|
||||
|
||||
|
||||
.. [1] The default ``prepend`` :ref:`import mode <import-modes>` works as follows:
|
||||
|
||||
Since there are no packages to derive a full package name from,
|
||||
``pytest`` will import your test files as *top-level* modules.
|
||||
The test files in the first example would be imported as
|
||||
``test_app`` and ``test_view`` top-level modules by adding ``tests/`` to ``sys.path``.
|
||||
|
||||
This results in the following drawback compared to the import mode ``import-lib``:
|
||||
your test files must have **unique names**.
|
||||
|
||||
If you need to have test modules with the same name,
|
||||
as a workaround you might add ``__init__.py`` files to your ``tests`` folder and subfolders,
|
||||
changing them to packages:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
pyproject.toml
|
||||
setup.cfg
|
||||
mypkg/
|
||||
...
|
||||
tests/
|
||||
__init__.py
|
||||
foo/
|
||||
__init__.py
|
||||
test_view.py
|
||||
bar/
|
||||
__init__.py
|
||||
test_view.py
|
||||
|
||||
Now pytest will load the modules as ``tests.foo.test_view`` and ``tests.bar.test_view``,
|
||||
allowing you to have modules with the same name.
|
||||
But now this introduces a subtle problem:
|
||||
in order to load the test modules from the ``tests`` directory,
|
||||
pytest prepends the root of the repository to ``sys.path``,
|
||||
which adds the side-effect that now ``mypkg`` is also importable.
|
||||
|
||||
This is problematic if you are using a tool like `tox`_ to test your package in a virtual environment,
|
||||
because you want to test the *installed* version of your package,
|
||||
not the local code from the repository.
|
||||
|
||||
The ``importlib`` import mode does not have any of the drawbacks above,
|
||||
because ``sys.path`` is not changed when importing test modules.
|
||||
|
|
Loading…
Reference in New Issue