Packaging (using pyproject.toml)¶
The pyproject.toml allows for easy packaging and installation:
$ pip install pyyc
or through the complete cloning of the gitlab repository:
$ git clone https://gitlab.in2p3.fr/ycopin/pyyc # cloning repo
$ cd pyyc
$ pip install . # local installation
Package initialization (__init__.py)¶
The __init__.py file in each package directory describes the
initialization of the package, i.e. the actions (usually imports, but
also definitions, etc.) to be performed at package import.
>>> import pyyc # Will run pyyc/__init__.py and all subsequent initializations
Initialization top-level module
Initialization sub-package A module A1
Initialization sub-package A module A2
Initialization sub-package B module + sub-package A module A1
>>> pyyc.subpkgA.modA1.version
'sub-package A module A1'
>>> pyyc.subpkgB.version
'sub-package B module + sub-package A module A1'
Note
The print-outs in the __init__.py are just for educational purpose!
Main entries (__main__.py)¶
The main program can be called in different ways:
as the __main__.py entry of a module, e.g.:
$ python -m pyyc arg1 arg2 # Execute top-level pyyc/__main__.py Initialization top-level module Initialization sub-package A module A1 Initialization sub-package A module A2 Initialization sub-package B module + sub-package A module A1 ---------------------- MAIN ---------------------- Command line arguments: ['arg1', 'arg2']
There can be only one package main, corresponding to the
if __name == "__main__"part of the__main__.pyfile.as console scripts defined as entry points in pyproject.toml:
[project.scripts] # Entry points and automatic script creation # script 'pyyc' corresponds to function 'main()' in file '__main__.py' pyyc = "pyyc.__main__:main" # 'pyyc_addition' corresponds to function 'main_addition()' in '__main__.py' pyyc_addition = "pyyc.__main__:main_addition"
These entry points are converted to plain scripts at installation:
$ pyyc arg1 arg2 # Execute pyyc/__main__.py:main() Initialization top-level module Initialization sub-package A module A1 Initialization sub-package A module A2 Initialization sub-package B module + sub-package A module A1 ---------------------- MAIN ---------------------- Command line arguments: ['arg1', 'arg2'] $ pyyc_addition 1 2 # Execute pyyc/__main__.py:main_addition() Initialization top-level module Initialization sub-package A module A1 Initialization sub-package A module A2 Initialization sub-package B module + sub-package A module A1 1 + 2 = 3
Reference: https://setuptools.pypa.io/en/latest/userguide/entry_point.html
How-to generate documentation¶
The sample code is documented using the documentation generator
sphinx within the dedicated directory docs/, typically for a
first use:
[docs/]$ sphinx-quickstart # initiate the documentation tool
[docs/]$ # edit 'conf.py' to your needs (see below)
[docs/]$ sphinx-apidoc -o . ../pyyc # automatic generation of documentation files
Creating file ./pyyc.rst.
Creating file ./pyyc.subpkgA.rst.
Creating file ./pyyc.subpkgB.rst.
Creating file ./modules.rst.
[docs/]$ # include these documentation files in 'index.rst' (see below)
[docs/]$ make html # build the documentation as a website
[docs/]$ firefox _build/html/index.html
As an illustration, the online version of this documentation, using the
sphinx configuration file conf.py, is available at
https://ycopin.pages.in2p3.fr/pyyc/. The automatically generated code
documentation part is under Code documentation.
Documentation configuration (conf.py)¶
In particular, the use of the auto-documentation extension
sphinx.ext.autodoc requires few non-trivial lines in conf.py:
set-up the path to code sources for extraction of docstrings:
import os, sys, re sys.path.insert(0, os.path.abspath(".."))
add
sphinx.ext.autodocin the list of sphinx extensionsextensions = [...](or initially add option--ext-autodoctosphinx-quickstart)configure the
autodocextension:# Autodoc configuration autodoc_default_options = { "members": True, # Document all members "undoc-members": True, # ... including undocumented ones "ignore-module-all": True, # do not stick to __all__ } autoclass_content = "both" # Insert class and __init__ docstrings autodoc_member_order = "bysource" # Keep source order
Documentation content (index.rst)¶
The index.rst file is the top-level documentation file, which will
include links to all the other documentation .rst files under the
specific .. toctree:: command, e.g.:
.. toctree::
:caption: Code documentation
:titlesonly:
pyyc
pyyc.subpkgA
pyyc.subpkgB
modules
How-to include notebooks¶
If any, you can add notebooks directly to your documentation with the nbsphinx extension (which needs to be installed).
add
nbsphinxin the list of sphinx extensionsextensions = [...]add notebooks stored in
docs/notebooks/directory in a dedicated documentation section, e.g.:Notebooks ========= .. toctree:: :titlesonly: notebooks/pyyc.ipynb
How-to run code tests¶
Note
see Test definition for definition of tests.
Doctests¶
Doctests (i.e. small examples and tests directly included in the docstrings)
can be performed on documented source code with either standard package
doctest or with external package pytest:
[pyyc/]$ python -m doctest -v mod.py
[pyyc/]$ pytest --doctest-modules -v mod.py
Dedicated tests¶
Tests gathered in the tests/ directory shall be performed using
pytest, e.g.:
[tests/]$ pytest -v test_mod.py
pytest will actually auto-discover all the tests from top-level
directory, and read configuration (if any) from
pyproject.toml:
$ pytest
Test coverage¶
coverage will run the test suite and look for parts of the code which have been (and more importantly not been) tested.
$ coverage run -m pytest
$ coverage report
Name Stmts Miss Cover
----------------------------------------------
pyyc/__init__.py 5 0 100%
pyyc/mod.py 44 2 95%
pyyc/subpkgA/__init__.py 5 0 100%
pyyc/subpkgA/modA1.py 2 0 100%
pyyc/subpkgA/modA2.py 2 0 100%
pyyc/subpkgB/__init__.py 3 0 100%
pyyc/subpkgB/modB.py 3 0 100%
tests/__init__.py 0 0 100%
tests/test_mod.py 46 0 100%
----------------------------------------------
TOTAL 110 2 98%
To visualize which parts of the code is documented or not:
$ coverage html
Wrote HTML report to htmlcov/index.html
$ firefox htmlcov/index.html
Coverage can also be estimated directly from pytest using plugin
pytest-cov:
$ pytest --cov # summary report
$ pytest --cov --cov-report=html # generate an html interactive report
$ firefox htmlcov/index.html
How-to include data files (e.g. config/)¶
pyyc.mod.read_config() is an example of a function accessing data file at
run-time, using importlib.resources.files (python 3.10+):
>>> from pyyc.mod import read_config
>>> cfg = read_config() # will look for config file distributed with pyyc package
Reading configuration from .../pyYC/pyyc/config/default.cfg...
>>> cfg['DEFAULT']['version']
'cfg-1.0'
This is controlled by section [tool.setuptools.package-data] in
pyproject.toml:
[tool.setuptools.package-data] # Package data
pyyc = ["*.cfg"]
Reference: https://setuptools.pypa.io/en/latest/userguide/datafiles.html#accessing-data-files-at-runtime
How-to set-up Gitlab continuous integration¶
If the code is hosted in a GitLab repository, one can use Continuous methods with a dedicated configuration
file .gitlab-ci.yml in the top-level directory. The
provided configuration will build and deploy the documentation as a static
website hosted on GitLab pages, namely
https://ycopin.pages.in2p3.fr/pyyc/ for pyyc.
Attention
the documentation pages might not be visible to everyone after
deployment; check page visibility setting in Settings / General
/ Visibility, project features, permissions.
More elements¶
For French-speaking users, you can have a look at the online course Analyse scientifique avec Python, and in particular to the packaging section.
To do¶
Display directory structure and content based on this recipe
Use
src/as package directory nameUse https://github.com/pypa/setuptools_scm for git integration