Overview

The module 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.

Helper Functions

The following helper functions are used by DescribePythonPackage(), but these can also be called individually to reuse internal features offered by that package description function.

loadReadmeFile

The function loadReadmeFile() reads a README file and guesses the contents MIME type on the file’s extension. It returns an instance of Readme.

This read text can then be used for the package’s long description.

Usage in a setup.py

from pathlib import Path
from pyTooling.Packaging import loadReadmeFile

readmeFile = Path("README.md")
readme = loadReadmeFile(readmeFile)
# print(readme.Content)
# print(readme.MimeType)

README.md

# 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.

loadRequirementsFile

The function loadRequirementsFile() recursively reads a requirements.txt file and extracts all specified dependencies. As a result, a list of requirement strings is returned.

Warning

The returned list might contain duplicates, which should be removed before further processing.

This can be achieve by converting the result to a set and back to a list.

requirements = list(set(loadRequirementsFile(requirementsFile)))

Usage in a setup.py

from pathlib import Path
from pyTooling.Packaging import loadRequirementsFile

requirementsFile = Path("doc/requirements.txt")
requirements = loadRequirementsFile(requirementsFile)
# for req in requirements:
#   print(req)

requirements.txt

-r ../requirements.txt

Sphinx ~= 8.1
docutils <= 0.21

sphinx_rtd_theme ~= 3.0

extractVersionInformation

The function extractVersionInformation() extracts version information from a Python source file (module). Usually these module variables are defined in a __init__.py file.

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 VersionInformation, which offers the gathered information as properties.

Usage in setup.py

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,
  # ...
)

__init__.py

__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"]

PackageDescriptions

DescribePythonPackage

DescribePythonPackage() is a helper function to describe a Python package. The result is a dictionary that can be handed over to 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 setuptools.find_namespace_packages() is used to discover package files.
Otherwise, the package is considered a normal package and 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__: str

  • __copyright__: str

  • __email__: str

  • __keywords__: typing.Iterable`[:class:`str]

  • __license__: str

  • __version__: 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.
See also pyTooling.Licensing.License.PythonClassifier()

Python versions

Always add Programming Language :: Python :: 3 :: Only.
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.

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.

DescribePythonPackageHostedOnGitHub

DescribePythonPackageHostedOnGitHub() is a helper function to describe a Python package when the source code is hosted on GitHub.

This is a wrapper for 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

Usage in setup.py

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")
  )
)