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),
|
"tox": ("https://tox.wiki/en/stable", None),
|
||||||
"virtualenv": ("https://virtualenv.pypa.io/en/stable", None),
|
"virtualenv": ("https://virtualenv.pypa.io/en/stable", None),
|
||||||
"setuptools": ("https://setuptools.pypa.io/en/stable", None),
|
"setuptools": ("https://setuptools.pypa.io/en/stable", None),
|
||||||
|
"packaging": ("https://packaging.python.org/en/latest", None),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -12,36 +12,21 @@ For development, we recommend you use :mod:`venv` for virtual environments and
|
||||||
as well as the ``pytest`` package itself.
|
as well as the ``pytest`` package itself.
|
||||||
This ensures your code and dependencies are isolated from your system Python installation.
|
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
|
.. code-block:: toml
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools>=42", "wheel"]
|
requires = ["hatchling"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "hatchling.build"
|
||||||
|
|
||||||
and a ``setup.cfg`` file containing your package's metadata with the following minimum content:
|
|
||||||
|
|
||||||
.. code-block:: ini
|
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
name = PACKAGENAME
|
name = "PACKAGENAME"
|
||||||
|
|
||||||
[options]
|
|
||||||
packages = find:
|
|
||||||
|
|
||||||
where ``PACKAGENAME`` is the name of your package.
|
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:
|
You can then install your package in "editable" mode by running from the same directory:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
@ -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
|
See :ref:`pytest vs python -m pytest` for more information about the difference between calling ``pytest`` and
|
||||||
``python -m pytest``.
|
``python -m pytest``.
|
||||||
|
|
||||||
Note that this scheme has a drawback if you are using ``prepend`` :ref:`import mode <import-modes>`
|
For the most convenient experience,
|
||||||
(which is the default): your test files must have **unique names**, because
|
choose the ``import-lib`` :ref:`import mode <import-modes>`.
|
||||||
``pytest`` will import them as *top-level* modules since there are no packages
|
To this end, add the following to your ``pyproject.toml``:
|
||||||
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``.
|
|
||||||
|
|
||||||
If you need to have test modules with the same name, you might add ``__init__.py`` files to your
|
.. code-block:: toml
|
||||||
``tests`` folder and subfolders, changing them to packages:
|
|
||||||
|
|
||||||
.. code-block:: text
|
[tool.pytest.ini_options]
|
||||||
|
addopts = [
|
||||||
|
"--import-mode=importlib",
|
||||||
|
]
|
||||||
|
|
||||||
pyproject.toml
|
The default :ref:`import mode <import-modes>` ``prepend`` has several drawbacks [1]_.
|
||||||
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.
|
|
||||||
|
|
||||||
.. _`src-layout`:
|
.. _`src-layout`:
|
||||||
|
|
||||||
In this situation, it is **strongly** suggested to use a ``src`` layout where application root package resides in a
|
Generally, but especially if you use the default import mode,
|
||||||
sub-directory of your root:
|
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
|
.. code-block:: text
|
||||||
|
|
||||||
|
@ -159,26 +125,11 @@ sub-directory of your root:
|
||||||
__init__.py
|
__init__.py
|
||||||
app.py
|
app.py
|
||||||
view.py
|
view.py
|
||||||
tests/
|
tests/...
|
||||||
__init__.py
|
|
||||||
foo/
|
|
||||||
__init__.py
|
|
||||||
test_view.py
|
|
||||||
bar/
|
|
||||||
__init__.py
|
|
||||||
test_view.py
|
|
||||||
|
|
||||||
|
|
||||||
This layout prevents a lot of common pitfalls and has many benefits, which are better explained in this excellent
|
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>`_.
|
`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
|
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
|
setuptools intends to
|
||||||
`remove the test command <https://github.com/pypa/setuptools/issues/931>`_.
|
`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