Packaging (setup.cfg
)¶
The setup.cfg 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
$ 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'
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__.py
file.as console scripts defined as entry points in setup.cfg:
[options.entry_points] # Entry points (scripts, GUI, etc.) console_scripts = # 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
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 configuration file conf.py, is available at https://ycopin.pages.in2p3.fr/pyyc/. The automatically generated code documentation part is under Code documentation.
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 sys.path.insert(0, os.path.abspath('..'))
add
sphinx.ext.autodoc
in the list of sphinx extensionsextensions = [...]
(or initially add option--ext-autodoc
tosphinx-quickstart
)configure the
autodoc
extension:# 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
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
setup.cfg
¶
The packaging configuration of the documentation is controlled by dedicated sections in setup.cfg:
[options.extras_require]
# Extra requirements for documentation or tests (but not for code)
docs =
nbsphinx
ipython
tests =
pytest
# BEWARE: coverage requires attrs==21.2.0
# https://stackoverflow.com/questions/70509476
attrs==21.2.0
coverage
[build_sphinx]
# Sphinx documentation: python setup.py build_sphinx
source-dir = docs/
build-dir = docs/_build
all_files = 1
This adds a new command in the setup.py
:
$ python setup.py build_sphinx
Notebooks¶
If any, you can add notebooks directly to your documentation with the nbsphinx extension (which needs to be installed).
add
nbsphinx
in the list of sphinx extensionsextensions = [...]
add notebooks stored in
docc/notebooks/
directory in a dedicated documentation section, e.g.:Notebooks ========= .. toctree:: :titlesonly: notebooks/pyyc.ipynb
Testing¶
Warning
With the current configuration setup.cfg (considered
deprecated for tests), it is not possible to run the tests directly from
setup.py
. It is therefore necessary to run the tests manually.
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:
$ 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 4 0 100%
pyyc/mod.py 41 2 95%
pyyc/subpkgA/__init__.py 3 0 100%
pyyc/subpkgA/modA1.py 2 0 100%
pyyc/subpkgA/modA2.py 2 0 100%
pyyc/subpkgB/__init__.py 1 0 100%
pyyc/subpkgB/modB.py 3 0 100%
tests/__init__.py 0 0 100%
tests/test_mod.py 40 0 100%
----------------------------------------------
TOTAL 96 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
Data files (e.g. config/
)¶
Example to access data file at run-time:
>>> 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 [options.package_data]
in
setup.cfg:
[options.package_data]
# Include config/ directory as data
* = config/*
Reference: https://setuptools.pypa.io/en/latest/userguide/datafiles.html#accessing-data-files-at-runtime
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 current configuration will build and deploy the
documentation as a static website hosted on GitLab pages, namely
https://ycopin.pages.in2p3.fr/pyyc/ for pyyc
.
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 name