Skip to content

Commit d99cad9

Browse files
greschdjorgepiloto
andauthored
Update packaging guide (#44)
* Update packaging guide to refer to 'flit' * Switch from 'flit' to 'poetry' * Update to pyansys-templates Co-authored-by: Jorge Martinez <jorge.martinezgarrido@ansys.com>
1 parent ee826ff commit d99cad9

File tree

1 file changed

+214
-38
lines changed

1 file changed

+214
-38
lines changed

Diff for: doc/source/library_description/packaging.rst

+214-38
Original file line numberDiff line numberDiff line change
@@ -2,73 +2,229 @@
22

33
Packaging
44
#########
5-
A Python package organizes and structures a Python library, which contains several
6-
modules and assets such as examples or binary extensions. A Python package
7-
offers an easy, reliable, and comprehensive way to distribute and install
8-
a Python library on a variety of platforms and environments.
5+
6+
A Python package organizes and structures a Python library, which contains
7+
several modules and assets such as examples or binary extensions. A Python
8+
package offers an easy, reliable, and comprehensive way to distribute and
9+
install a Python library on a variety of platforms and environments.
10+
11+
.. note::
12+
13+
If you want to create a new PyAnsys project according to the guidelines
14+
presented in the following lines, consider using the `ansys-templates tool`_.
15+
16+
17+
Python Scripts, Modules, Sub-packages, and Packages
18+
---------------------------------------------------
19+
20+
It is important to understand the difference between Python scripts, modules,
21+
sub-packages, and packages:
22+
23+
* ``Script``: Any Python file with logic source code.
24+
* ``Module``: Any Python script hosted next to an ``__init__.py`` file.
25+
* ``Sub-package``: Any directory containing various Python modules.
26+
* ``Package``: Any directory containing Python modules and sub-packages.
27+
28+
The following structure is shown to better explain previous concepts:
29+
30+
.. code:: bash
31+
32+
.
33+
├── src
34+
│   └── package
35+
│   ├── subpackage_a
36+
│   │   ├── __init__.py
37+
│   │   └── module_c.py
38+
│   ├── __init__.py
39+
│   ├── module_a.py
40+
│   └── module_b.py
41+
├── LICENSE
42+
├── README.rst
43+
└── pyproject.toml
44+
945
1046
Namespace Packaging
1147
-------------------
12-
A PyAnsys library uses `namespace packaging`_.
13-
Namespace packages allow a user to easily split subpackages from a package into
14-
a single, independent distribution.
48+
A PyAnsys library uses `namespace packaging`_. Namespace packages allow you
49+
to easily split sub-packages from a package into single, independent
50+
distributions.
51+
52+
There are different approaches available for creating a namespace package. For
53+
the ``ansys`` namespace, we use the `PEP 420`_ `native namespace packages`_
54+
approach.
55+
56+
Therefore, the source directory of any `PyAnsys library` should look like this:
57+
58+
.. code:: bash
1559
16-
Three different approaches are currently available for creating a namespace package:
60+
.
61+
└── src
62+
└── ansys
63+
└── product
64+
└── library
65+
└── __init__.py
1766
18-
* `native namespace packages`_
19-
* pkgutil-style namespace packages
20-
* pkg_resources-style namespace packages
2167
2268
Required Files
2369
--------------
2470

25-
* README.rst file: Describes the purpose of the package.
71+
* ``README.rst`` file: Describes the purpose of the package.
2672
*The format of this file must be reStructuredText.*
2773

28-
* LICENSE file: Specifies copyrights and required authorization.
74+
* ``LICENSE`` file: Specifies copyrights and required authorization.
2975

30-
* setup.py file: Provides package information.
31-
The presence of this file indicate that the package was likely created using ``disutils``,
32-
which is the Python standard for building and distributing a Python package.
76+
* ``pyproject.toml`` file: Provides package metadata and defines how the package
77+
is built. There are different build backends available, such as `setuptools`_,
78+
`poetry`_, and `flit`_.
3379

80+
* ``src/ansys/product/library/__init__.py`` file: Usually contains the
81+
version of the package in a variable named ``__version__``. The value of this
82+
variable can be parsed from the ``pyproject.toml`` file so that the version
83+
is only specified in one location.
3484

35-
Setup File
36-
----------
37-
The `setup.py`_ file is the build script for ``setuptools``. It exposes dynamic metadata and contains
38-
package information, such as a description, author, and version.
39-
In this file, the ``setuptools`` module is used to configure the metadata (as opposed to ``distutils``).
4085

41-
.. code:: python
86+
Additional Directories
87+
----------------------
88+
89+
The following directories may be specified at the same level as the ``src/`` one:
90+
91+
* ``tests/``: Contains all unit tests for the package. It is
92+
likely that these tests take advantage of the `pytest`_ framework.
93+
94+
* ``doc/``: Contain all documentation files and examples on
95+
how to use the package.
96+
97+
98+
Project File and Build System
99+
------------------------------
100+
101+
The ``pyproject.toml`` file is the standardized build configuration file for Python
102+
projects. It must contain at least a ``[build-system]`` section, which determines
103+
how the project is built. Some commonly used packaging tools are `setuptools`_,
104+
`poetry`_, and `flit`_. All three of these packaging tools are currently supported by
105+
the ``pyansys-advanced`` template, which is included in the `ansys-templates tool`_.
106+
107+
108+
Flit
109+
^^^^
110+
111+
Flit is a modern and lightweight build system that requires developers
112+
to manage virtual environments on their own. Developers must:
113+
114+
* Create a virtual environment and activate it.
115+
* Install the package in editable mode.
116+
117+
Flit is the default tool for creating a new ``pyansys`` project when using the
118+
`ansys-templates tool`_.
119+
120+
The ``[project]`` section specifies the project's metadata and required
121+
dependencies. For more information, see `flit pyproject.toml
122+
guidelines`_.
123+
42124

43-
import setuptools
44-
setuptools.setup(...)
125+
Poetry
126+
^^^^^^
127+
128+
Because of its ``poetry.lock`` file, Poetry provides strong dependency pinning. When
129+
installing a package, poetry creates a virtual environment, thus ensuring an isolated
130+
package development environment.
131+
132+
Nevertheless, it is possible to make Poetry ignore the `poetry.lock` file by running:
133+
134+
.. code:: bash
135+
136+
poetry config virtualenvs.create false --local
137+
138+
Using `poetry`_ is popular because it:
139+
140+
* Supports pinning dependency versions via a ``poetry.lock`` file that can be
141+
used for testing and CI
142+
* Allows downstream packages to still consume a loose dependency specification
143+
* Integrates with `dependabot`_ to update the pinned version
144+
145+
The ``[tool.poetry]`` section contains metadata and defines the project's
146+
dependencies. For more information, see `poetry pyproject.toml documentation`_.
147+
148+
149+
Setuptools
150+
^^^^^^^^^^
151+
152+
Setuptools is a very well known build system in the Python ecosystem. It is used
153+
in projects requiring a ``setup.py`` file and can be used in projects with a
154+
``pyproject.toml`` file, although not all metadata in this second file
155+
is fully supported yet.
156+
157+
The main advantage of this build system is the ability to create custom build
158+
steps in the form of Python code.
159+
160+
161+
Specifying Package Version
162+
--------------------------
163+
164+
It is very common for packages to specify their current version in the
165+
``__version__`` variable. This variable is usually declared in the
166+
``__init__.py`` file included in the ``library`` directory.
167+
168+
However, it is also required to specify the version in the ``pyproject.toml`` or
169+
``setup.py`` file. This leads to a duplicate declaration of the project's version,
170+
which could lead to a potential mismatch between both.
171+
172+
Therefore, a good practice is to take advantage of the `importlib.metadata package`_
173+
for parsing the version from package metadata. This guarantees that there is no mismatch
174+
between both version declarations.
45175

46-
This file gathers all namespace packages and files that must be included in the distributed
47-
package.
48176

49177
.. code:: python
50178
51-
packages = []
52-
for package in setuptools.find_namespace_packages(include='ansys*'):
53-
if package.startswith('ansys.tools.example_coverage'):
54-
packages.append(package)
179+
try:
180+
import importlib.metadata as importlib_metadata
181+
except ModuleNotFoundError:
182+
import importlib_metadata
183+
184+
__version__ = importlib_metadata.version(__name__.replace(".", "-"))
185+
186+
187+
Extra Tools Configuration
188+
-------------------------
189+
190+
There are plenty of tools in the Python ecosystem that enable developers to
191+
write clean code according to different coding style guidelines. Some of these
192+
tools are `black`_, `isort`_, `flake8`_, and `mypy`_.
193+
194+
Some of these tools can be configured. This configuration might be specified in
195+
custom files required by the tool or in the ``pyproject.toml`` file, thus reducing the
196+
number of files in the project directory.
55197

198+
.. note::
56199

57-
It also extracts the version number from the ``_version.py`` file located in the
58-
``ansys/<product>/library`` directory of the source code.
200+
When using `setuptools`_ as a build backend, providing the metadata in
201+
the ``pyproject.toml`` file is not yet fully supported. Instead, it also
202+
requires a ``setup.cfg`` file, ``setup.py`` file, or both files.
203+
204+
In the `pyansys template`, all these configurations are included by default in
205+
the ``.pre-commit-config.yaml`` file because ``pre-commit`` is not able to parse the
206+
``pyproject.toml`` file nor the ``setup.py`` file.
59207

60208

61209
Generate the Package and Upload It on PyPI
62210
------------------------------------------
63211

64-
The first time that you want to upload a package on PyPI under the `ansys <https://pypi.org/user/ansys/>`_
212+
The first time that you want to upload a package on PyPI under `ansys <https://pypi.org/user/ansys/>`_
65213
account, you must perform the following process manually.
66214

67215
Create the python package.
68216

69217
.. code::
70218
71-
python setup.py sdist
219+
pip install build
220+
python -m build
221+
222+
If using flit or poetry, you can also run:
223+
224+
.. code::
225+
226+
flit build
227+
poetry build
72228
73229
Verify the distribution's long description rendering with ``twine``.
74230

@@ -77,8 +233,14 @@ Verify the distribution's long description rendering with ``twine``.
77233
pip install twine
78234
twine check dist/*
79235
80-
Upload the package to PyPI using ``twine`` and the upload token generated for the ``ansys`` PyPI account.
81-
Contact alexander.kaszynski@ansys.com for the token.
236+
237+
Upload the package to PyPI using ``twine`` and the upload token generated for
238+
the ``ansys`` PyPI account. As soon as the package has been released for the
239+
first time, it is possible to create an independent token dedicated to this
240+
package. This way the token stored in the GitHub secrets and used in the
241+
release's workflow is only related to that specific package. This limits the
242+
exposure to any potential token security flaws. Contact
243+
alexander.kaszynski@ansys.com for the token.
82244

83245
.. code::
84246
@@ -127,7 +289,7 @@ Install a package with:
127289

128290
.. code::
129291
130-
pip install ansys.<product>.<library>
292+
pip install ansys-<product>-<library>
131293
132294
To create a package complying with the above standards, here is the minimal content of your PyAnsys library:
133295

@@ -136,12 +298,26 @@ To create a package complying with the above standards, here is the minimal cont
136298
ansys/<product>/<library>/__init__.py
137299
LICENSE
138300
README.rst
139-
setup.py
301+
pyproject.toml
140302
tests/
141303
142304
143305
.. _namespace packaging: https://packaging.python.org/guides/packaging-namespace-packages/
144306
.. _native namespace packages: https://packaging.python.org/guides/packaging-namespace-packages/#native-namespace-packages
307+
.. _PEP 420: https://www.python.org/dev/peps/pep-0420/
308+
.. _setuptools: https://setuptools.pypa.io
309+
.. _poetry: https://python-poetry.org/docs/
310+
.. _flit pyproject.toml guidelines: https://flit.readthedocs.io/en/latest/pyproject_toml.html
311+
.. _flit: https://flit.readthedocs.io
312+
.. _dependabot: https://docs.github.com/en/code-security/supply-chain-security/keeping-your-dependencies-updated-automatically/about-dependabot-version-updates
313+
.. _ansys-templates tool: https://github.com/pyansys/pyansys-templates
314+
.. _poetry pyproject.toml documentation: https://python-poetry.org/docs/pyproject/
315+
.. _black: https://black.readthedocs.io/en/stable/usage_and_configuration/the_basics.html#configuration-via-a-file
316+
.. _mypy: https://mypy.readthedocs.io/en/stable/config_file.html#the-mypy-configuration-file
145317
.. _trunk-based development: https://trunkbaseddevelopment.com/
146318
.. _secret: https://docs.github.com/en/actions/reference/encrypted-secrets
147319
.. _setup.py: https://packaging.python.org/tutorials/packaging-projects/#configuring-metadata
320+
.. _importlib.metadata package: https://docs.python.org/3/library/importlib.metadata.html
321+
.. _isort: https://github.com/PyCQA/isort
322+
.. _flake8: https://flake8.pycqa.org/en/latest/
323+
.. _pytest: https://docs.pytest.org/en/latest/

0 commit comments

Comments
 (0)