1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
|
.. _PACKAGING:
Overview
########
The module :mod:`pyTooling.Packaging` provides helper functions to achieve a *single-source-of-truth* Python package
description, where (almost) no information is duplicated. The main idea is to read configuration files, READMEs, and
Python source files from ``setup.py``, so it doesn't duplicate information. This allows an easier the maintenance of
Python packages.
.. #contents:: Table of Contents
:depth: 2
.. _PACKAGING/Helper:
Helper Functions
################
The following helper functions are used by :func:`~pyTooling.Packaging.DescribePythonPackage`, but these can also be
called individually to reuse internal features offered by that package description function.
.. _PACKAGING/Helper/loadReadmeFile:
loadReadmeFile
**************
The function :func:`~pyTooling.Packaging.loadReadmeFile` reads a ``README`` file and guesses the contents MIME type on
the file's extension. It returns an instance of :class:`~pyTooling.Packaging.Readme`.
This read text can then be used for the package's *long description*.
.. topic:: Supported file formats
* ``*.txt`` - Plain text
* ``*.md`` - `Markdown <https://daringfireball.net/projects/markdown/>`__ (further reading: :wiki:`Markdown`)
* ``*.rst`` - `ReStructured Text <https://docutils.sourceforge.io/rst.html>`__ (further reading: :wiki:`ReStructuredText`)
.. grid:: 2
.. grid-item::
:columns: 6
.. admonition:: Usage in a ``setup.py``
.. code-block:: Python
from pathlib import Path
from pyTooling.Packaging import loadReadmeFile
readmeFile = Path("README.md")
readme = loadReadmeFile(readmeFile)
# print(readme.Content)
# print(readme.MimeType)
.. grid-item::
:columns: 6
.. admonition:: ``README.md``
.. code-block:: Markdown
# pyTooling
**pyTooling** is a powerful collection of arbitrary useful abstract data models, missing classes,
decorators, a new performance boosting meta-class and enhanced exceptions. It also provides lots of helper
functions e.g. to ease the handling of package descriptions or to unify multiple existing APIs into a single
API.
.. _PACKAGING/Helper/loadRequirementsFile:
loadRequirementsFile
********************
The function :func:`~pyTooling.Packaging.loadRequirementsFile` recursively reads a ``requirements.txt`` file and
extracts all specified dependencies. As a result, a list of requirement strings is returned.
.. topic:: Features
* Comments are skipped.
* Recursive references are followed.
* Special dependency entries like Git repository references are translates to match the syntax expected by setuptools.
.. warning::
The returned list might contain duplicates, which should be removed before further processing.
This can be achieve by converting the result to a :class:`set` and back to a :class:`list`.
.. code-block:: Python
requirements = list(set(loadRequirementsFile(requirementsFile)))
.. grid:: 2
.. grid-item::
:columns: 6
.. admonition:: Usage in a ``setup.py``
.. code-block:: Python
from pathlib import Path
from pyTooling.Packaging import loadRequirementsFile
requirementsFile = Path("doc/requirements.txt")
requirements = loadRequirementsFile(requirementsFile)
# for req in requirements:
# print(req)
.. grid-item::
:columns: 6
.. admonition:: ``requirements.txt``
.. code-block::
-r ../requirements.txt
Sphinx ~= 8.2
docutils <= 0.21
sphinx_rtd_theme ~= 3.0
.. _PACKAGING/Helper/extractVersionInformation:
extractVersionInformation
*************************
The function :func:`~pyTooling.Packaging.extractVersionInformation` extracts version information from a Python source
file (module). Usually these module variables are defined in a ``__init__.py`` file.
.. rubric:: Supported fields
* Author name (``__author__``)
* Author email address (``__email__``)
* Copyright information (``__copyright_``)
* License name (``__license__``)
* Version number (``__version__``)
* Keywords (``__keywords__``)
The function returns an instance of :class:`~pyTooling.Packaging.VersionInformation`, which offers the gathered
information as properties.
.. grid:: 2
.. grid-item::
:columns: 6
.. admonition:: Usage in ``setup.py``
.. code-block:: python
from setuptools import setup
from pyTooling.Packaging import extractVersionInformation
file = Path("./pyTooling/Common/__init__.py")
versionInfo = extractVersionInformation(file)
setup(
# ...
version=versionInformation.Version,
author=versionInformation.Author,
author_email=versionInformation.Email,
keywords=versionInformation.Keywords,
# ...
)
.. grid-item::
:columns: 6
.. admonition:: ``__init__.py``
.. code-block:: python
__author__ = "Patrick Lehmann"
__email__ = "Paebbels@gmail.com"
__copyright__ = "2017-2024, Patrick Lehmann"
__license__ = "Apache License, Version 2.0"
__version__ = "1.10.1"
__keywords__ = ["decorators", "meta classes", "exceptions", "platform", "versioning"]
.. _PACKAGING/Descriptions:
PackageDescriptions
###################
.. _PACKAGING/Descriptions/Python:
DescribePythonPackage
*********************
:func:`~pyTooling.Packaging.DescribePythonPackage` is a helper function to describe a Python package. The result is a
dictionary that can be handed over to :func:`setuptools.setup`. Some information will be gathered implicitly from
well-known files (e.g. ``README.md``, ``requirements.txt``, ``__init__.py``).
Handling of namespace packages
==============================
If parameter ``packageName`` contains a dot, a namespace package is assumed. Then
:func:`setuptools.find_namespace_packages` is used to discover package files. |br|
Otherwise, the package is considered a normal package and :func:`setuptools.find_packages` is used.
In both cases, the following packages (directories) are excluded from search:
* ``build``, ``build.*``
* ``dist``, ``dist.*``
* ``doc``, ``doc.*``
* ``tests``, ``tests.*``
Handling of minimal Python version
==================================
The minimal required Python version is selected from parameter ``pythonVersions``.
Handling of dunder variables
============================
A Python source file specified by parameter ``sourceFileWithVersion`` will be analyzed with Pythons parser and the
resulting AST will be searched for the following dunder variables:
* ``__author__``: :class:`str`
* ``__copyright__``: :class:`str`
* ``__email__``: :class:`str`
* ``__keywords__``: :class:`typing.Iterable`[:class:`str`]
* ``__license__``: :class:`str`
* ``__version__``: :class:`str`
The gathered information be used to add further mappings in the result dictionary.
Handling of package classifiers
===============================
To reduce redundantly provided parameters to this function (e.g. supported ``pythonVersions``), only additional
classifiers should be provided via parameter ``classifiers``. The supported Python versions will be implicitly
converted to package classifiers, so no need to specify them in parameter ``classifiers``.
The following classifiers are implicitly handled:
license
The license specified by parameter ``license`` is translated into a classifier. |br|
See also :meth:`pyTooling.Licensing.License.PythonClassifier`
Python versions
Always add ``Programming Language :: Python :: 3 :: Only``. |br|
For each value in ``pythonVersions``, one ``Programming Language :: Python :: Major.Minor`` is added.
Development status
The development status specified by parameter ``developmentStatus`` is translated to a classifier and added.
.. seealso::
`Python package classifiers <https://pypi.org/classifiers/>`__
Handling of extra requirements
==============================
If additional requirement files are provided, e.g. requirements to build the documentation, then *extra*
requirements are defined. These can be installed via ``pip install packageName[extraName]``. If so, an extra called
``all`` is added, so developers can install all dependencies needed for package development.
``doc``
If parameter ``documentationRequirementsFile`` is present, an extra requirements called ``doc`` will be defined.
``test``
If parameter ``unittestRequirementsFile`` is present, an extra requirements called ``test`` will be defined.
``build``
If parameter ``packagingRequirementsFile`` is present, an extra requirements called ``build`` will be defined.
User-defined
If parameter ``additionalRequirements`` is present, an extra requirements for every mapping entry in the
dictionary will be added.
``all``
If any of the above was added, an additional extra requirement called ``all`` will be added, summarizing all
extra requirements.
Handling of keywords
====================
If parameter ``keywords`` is not specified, the dunder variable ``__keywords__`` from ``sourceFileWithVersion``
will be used. Otherwise, the content of the parameter, if not None or empty.
.. _PACKAGING/Descriptions/GitHub:
DescribePythonPackageHostedOnGitHub
***********************************
:func:`~pyTooling.Packaging.DescribePythonPackageHostedOnGitHub` is a helper function to describe a Python package when
the source code is hosted on GitHub.
This is a wrapper for :func:`~pyTooling.Packaging.DescribePythonPackage`, because some parameters can be simplified by
knowing the GitHub namespace and repository name: issue tracker URL, source code URL, ...
.. todo::
normal packages
``PackageName``
namespace package root package
``NamespacePackage.*``
namespace package sub package
``NamespacePackage.PackageName``
deriving URLs
.. admonition:: Usage in ``setup.py``
.. code-block:: Python
from setuptools import setup
from pathlib import Path
from pyTooling.Packaging import DescribePythonPackageHostedOnGitHub
packageName = "pyTooling.Packaging"
setup(
**DescribePythonPackageHostedOnGitHub(
packageName=packageName,
description="A set of helper functions to describe a Python package for setuptools.",
gitHubNamespace="pyTooling",
keywords="Python3 setuptools package wheel installation",
sourceFileWithVersion=Path(f"{packageName.replace('.', '/')}/__init__.py"),
developmentStatus="beta",
pythonVersions=("3.8", "3.9", "3.10")
)
)
|