diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 3c24cfc46..c6372ec30 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: check-added-large-files - id: check-case-conflict @@ -12,7 +12,7 @@ repos: - id: trailing-whitespace - repo: https://github.com/codespell-project/codespell - rev: v2.2.6 + rev: v2.3.0 hooks: - id: codespell args: ["-L", "ned,ist,oder", "--skip", "*.po"] @@ -34,7 +34,7 @@ repos: - id: rst-inline-touching-normal - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.2.1 + rev: v0.4.10 hooks: - id: ruff - id: ruff-format diff --git a/source/conf.py b/source/conf.py index 831f9c9ea..c777550ce 100644 --- a/source/conf.py +++ b/source/conf.py @@ -1,6 +1,16 @@ # -- Project information --------------------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information +import os + +# Some options are only enabled for the main packaging.python.org deployment builds +RTD_BUILD = bool(os.getenv("READTHEDOCS")) +RTD_PR_BUILD = RTD_BUILD and os.getenv("READTHEDOCS_VERSION_TYPE") == "external" +RTD_URL = os.getenv("READTHEDOCS_CANONICAL_URL") +RTD_CANONICAL_BUILD = ( + RTD_BUILD and not RTD_PR_BUILD and "packaging.python.org" in RTD_URL +) + project = "Python Packaging User Guide" copyright = "2013–2020, PyPA" @@ -55,6 +65,18 @@ html_favicon = "assets/py.png" html_last_updated_fmt = "" +_metrics_js_files = [ + ( + "https://plausible.io/js/script.js", + {"data-domain": "packaging.python.org", "defer": "defer"}, + ) +] +html_js_files = [] +if RTD_CANONICAL_BUILD: + # Enable collection of the visitor metrics reported at + # https://plausible.io/packaging.python.org + html_js_files.extend(_metrics_js_files) + # -- Options for HTML help output ------------------------------------------------------ # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-help-output @@ -110,6 +132,7 @@ # Ignore while StackOverflow is blocking GitHub CI. Ref: # https://github.com/pypa/packaging.python.org/pull/1474 "https://stackoverflow.com/*", + "https://pyscaffold.org/*", ] linkcheck_retries = 5 # Ignore anchors for links to GitHub project pages -- GitHub adds anchors from diff --git a/source/discussions/index.rst b/source/discussions/index.rst index 4a14fbfba..1f5ff1f2b 100644 --- a/source/discussions/index.rst +++ b/source/discussions/index.rst @@ -12,7 +12,8 @@ specific topic. If you're just trying to get stuff done, see deploying-python-applications pip-vs-easy-install install-requires-vs-requirements - wheel-vs-egg distribution-package-vs-import-package + package-formats src-layout-vs-flat-layout setup-py-deprecated + single-source-version diff --git a/source/discussions/package-formats.rst b/source/discussions/package-formats.rst new file mode 100644 index 000000000..6d4dee35c --- /dev/null +++ b/source/discussions/package-formats.rst @@ -0,0 +1,193 @@ +.. _package-formats: + +=============== +Package Formats +=============== + +This page discusses the file formats that are used to distribute Python packages +and the differences between them. + +You will find files in two formats on package indices such as PyPI_: **source +distributions**, or **sdists** for short, and **binary distributions**, commonly +called **wheels**. For example, the `PyPI page for pip 23.3.1 `_ +lets you download two files, ``pip-23.3.1.tar.gz`` and +``pip-23.3.1-py3-none-any.whl``. The former is an sdist, the latter is a +wheel. As explained below, these serve different purposes. When publishing a +package on PyPI (or elsewhere), you should always upload both an sdist and one +or more wheel. + + +What is a source distribution? +============================== + +Conceptually, a source distribution is an archive of the source code in raw +form. Concretely, an sdist is a ``.tar.gz`` archive containing the source code +plus an additional special file called ``PKG-INFO``, which holds the project +metadata. The presence of this file helps packaging tools to be more efficient +by not needing to compute the metadata themselves. The ``PKG-INFO`` file follows +the format specified in :ref:`core-metadata` and is not intended to be written +by hand [#core-metadata-format]_. + +You can thus inspect the contents of an sdist by unpacking it using standard +tools to work with tar archives, such as ``tar -xvf`` on UNIX platforms (like +Linux and macOS), or :ref:`the command line interface of Python's tarfile module +` on any platform. + +Sdists serve several purposes in the packaging ecosystem. When :ref:`pip`, the +standard Python package installer, cannot find a wheel to install, it will fall +back on downloading a source distribution, compiling a wheel from it, and +installing the wheel. Furthermore, sdists are often used as the package source +by downstream packagers (such as Linux distributions, Conda, Homebrew and +MacPorts on macOS, ...), who, for various reasons, may prefer them over, e.g., +pulling from a Git repository. + +A source distribution is recognized by its file name, which has the form +:samp:`{package_name}-{version}.tar.gz`, e.g., ``pip-23.3.1.tar.gz``. + +.. TODO: provide clear guidance on whether sdists should contain docs and tests. + Discussion: https://discuss.python.org/t/should-sdists-include-docs-and-tests/14578 + +If you want technical details on the sdist format, read the :ref:`sdist +specification `. + + +What is a wheel? +================ + +Conceptually, a wheel contains exactly the files that need to be copied when +installing the package. + +There is a big difference between sdists and wheels for packages with +:term:`extension modules `, written in compiled languages like +C, C++ and Rust, which need to be compiled into platform-dependent machine code. +With these packages, wheels do not contain source code (like C source files) but +compiled, executable code (like ``.so`` files on Linux or DLLs on Windows). + +Furthermore, while there is only one sdist per version of a project, there may +be many wheels. Again, this is most relevant in the context of extension +modules. The compiled code of an extension module is tied to an operating system +and processor architecture, and often also to the version of the Python +interpreter (unless the :ref:`Python stable ABI ` is used). + +For pure-Python packages, the difference between sdists and wheels is less +marked. There is normally one single wheel, for all platforms and Python +versions. Python is an interpreted language, which does not need ahead-of-time +compilation, so wheels contain ``.py`` files just like sdists. + +If you are wondering about ``.pyc`` bytecode files: they are not included in +wheels, since they are cheap to generate, and including them would unnecessarily +force a huge number of packages to distribute one wheel per Python version +instead of one single wheel. Instead, installers like :ref:`pip` generate them +while installing the package. + +With that being said, there are still important differences between sdists and +wheels, even for pure Python projects. Wheels are meant to contain exactly what +is to be installed, and nothing more. In particular, wheels should never include +tests and documentation, while sdists commonly do. Also, the wheel format is +more complex than sdist. For example, it includes a special file -- called +``RECORD`` -- that lists all files in the wheel along with a hash of their +content, as a safety check of the download's integrity. + +At a glance, you might wonder if wheels are really needed for "plain and basic" +pure Python projects. Keep in mind that due to the flexibility of sdists, +installers like pip cannot install from sdists directly -- they need to first +build a wheel, by invoking the :term:`build backend` that the sdist specifies +(the build backend may do all sorts of transformations while building the wheel, +such as compiling C extensions). For this reason, even for a pure Python +project, you should always upload *both* an sdist and a wheel to PyPI or other +package indices. This makes installation much faster for your users, since a +wheel is directly installable. By only including files that must be installed, +wheels also make for smaller downloads. + +On the technical level, a wheel is a ZIP archive (unlike sdists which are TAR +archives). You can inspect its contents by unpacking it as a normal ZIP archive, +e.g., using ``unzip`` on UNIX platforms like Linux and macOS, ``Expand-Archive`` +in Powershell on Windows, or :ref:`the command line interface of Python's +zipfile module `. This can be very useful to check +that the wheel includes all the files you need it to. + +Inside a wheel, you will find the package's files, plus an additional directory +called :samp:`{package_name}-{version}.dist-info`. This directory contains +various files, including a ``METADATA`` file which is the equivalent of +``PKG-INFO`` in sdists, as well as ``RECORD``. This can be useful to ensure no +files are missing from your wheels. + +The file name of a wheel (ignoring some rarely used features) looks like this: +:samp:`{package_name}-{version}-{python_tag}-{abi_tag}-{platform_tag}.whl`. +This naming convention identifies which platforms and Python versions the wheel +is compatible with. For example, the name ``pip-23.3.1-py3-none-any.whl`` means +that: + +- (``py3``) This wheel can be installed on any implementation of Python 3, + whether CPython, the most widely used Python implementation, or an alternative + implementation like PyPy_; +- (``none``) It does not depend on the Python version; +- (``any``) It does not depend on the platform. + +The pattern ``py3-none-any`` is common for pure Python projects. Packages with +extension modules typically ship multiple wheels with more complex tags. + +All technical details on the wheel format can be found in the :ref:`wheel +specification `. + + +.. _egg-format: +.. _`Wheel vs Egg`: + +What about eggs? +================ + +"Egg" is an old package format that has been replaced with the wheel format. It +should not be used anymore. Since August 2023, PyPI `rejects egg uploads +`_. + +Here's a breakdown of the important differences between wheel and egg. + +* The egg format was introduced by :ref:`setuptools` in 2004, whereas the wheel + format was introduced by :pep:`427` in 2012. + +* Wheel has an :doc:`official standard specification + `. Egg did not. + +* Wheel is a :term:`distribution ` format, i.e a packaging + format. [#wheel-importable]_ Egg was both a distribution format and a runtime + installation format (if left zipped), and was designed to be importable. + +* Wheel archives do not include ``.pyc`` files. Therefore, when the distribution + only contains Python files (i.e. no compiled extensions), and is compatible + with Python 2 and 3, it's possible for a wheel to be "universal", similar to + an :term:`sdist `. + +* Wheel uses standard :ref:`.dist-info directories + `. Egg used ``.egg-info``. + +* Wheel has a :ref:`richer file naming convention `. A + single wheel archive can indicate its compatibility with a number of Python + language versions and implementations, ABIs, and system architectures. + +* Wheel is versioned. Every wheel file contains the version of the wheel + specification and the implementation that packaged it. + +* Wheel is internally organized by `sysconfig path type + `_, + therefore making it easier to convert to other formats. + +-------------------------------------------------------------------------------- + +.. [#core-metadata-format] This format is email-based. Although this would + be unlikely to be chosen today, backwards compatibility considerations lead to + it being kept as the canonical format. From the user point of view, this + is mostly invisible, since the metadata is specified by the user in a way + understood by the build backend, typically ``[project]`` in ``pyproject.toml``, + and translated by the build backend into ``PKG-INFO``. + +.. [#wheel-importable] Circumstantially, in some cases, wheels can be used + as an importable runtime format, although :ref:`this is not officially supported + at this time `. + + + +.. _pip-pypi: https://pypi.org/project/pip/23.3.1/#files +.. _pypi: https://pypi.org +.. _pypi-eggs-deprecation: https://blog.pypi.org/posts/2023-06-26-deprecate-egg-uploads/ +.. _pypy: https://pypy.org diff --git a/source/discussions/single-source-version.rst b/source/discussions/single-source-version.rst new file mode 100644 index 000000000..9bcab6291 --- /dev/null +++ b/source/discussions/single-source-version.rst @@ -0,0 +1,47 @@ +.. _`Single sourcing the version discussion`: + +=================================== +Single-sourcing the Project Version +=================================== + +:Page Status: Complete +:Last Reviewed: 2024-08-24 + +One of the challenges in building packages is that the version string can be required in multiple places. + +* It needs to be specified when building the package (e.g. in :file:`pyproject.toml`) + This will make it available in the installed package’s metadata, from where it will be accessible at runtime using ``importlib.metadata.version("distribution_name")``. + +* A package may set a module attribute (e.g., ``__version__``) to provide an alternative means of runtime access to the version of the imported package. If this is done, the value of the attribute and that used by the build system to set the distribution's version should be kept in sync in :ref:`the build systems's recommended way `. + +* If the code is in in a version control system (VCS), e.g. Git, the version may appear in a *tag* such as ``v1.2.3``. + +To ensure that version numbers do not get out of sync, it is recommended that there is a single source of truth for the version number. + +In general, the options are: + +1) If the code is in a version control system (VCS), e.g. Git, then the version can be extracted from the VCS. + +2) The version can be hard-coded into the :file:`pyproject.toml` file -- and the build system can copy it into other locations it may be required. + +3) The version string can be hard-coded into the source code -- either in a special purpose file, such as :file:`_version.txt`, or as a attribute in a module, such as :file:`__init__.py`, and the build system can extract it at build time. + + +Consult your build system's documentation for their recommended method. + +.. _Build system version handling: + +Build System Version Handling +----------------------------- + +The following are links to some build system's documentation for handling version strings. + +* `Flit `_ + +* `Hatchling `_ + +* `PDM `_ + +* `Setuptools `_ + + - `setuptools_scm `_ diff --git a/source/discussions/src-layout-vs-flat-layout.rst b/source/discussions/src-layout-vs-flat-layout.rst index bfa405729..c38968345 100644 --- a/source/discussions/src-layout-vs-flat-layout.rst +++ b/source/discussions/src-layout-vs-flat-layout.rst @@ -79,3 +79,27 @@ layout and the flat layout: ``tox.ini``) and packaging/tooling configuration files (eg: ``setup.py``, ``noxfile.py``) on the import path. This would make certain imports work in editable installations but not regular installations. + +.. _running-cli-from-source-src-layout: + +Running a command-line interface from source with src-layout +============================================================ + +Due to the firstly mentioned specialty of the src layout, a command-line +interface can not be run directly from the :term:`source tree `, +but requires installation of the package in +:doc:`Development Mode ` +for testing purposes. Since this can be unpractical in some situations, +a workaround could be to prepend the package folder to Python's +:py:data:`sys.path` when called via its :file:`__main__.py` file: + +.. code-block:: python + + import os + import sys + + if not __package__: + # Make CLI runnable from source tree with + # python src/package + package_source_path = os.path.dirname(os.path.dirname(__file__)) + sys.path.insert(0, package_source_path) diff --git a/source/discussions/wheel-vs-egg.rst b/source/discussions/wheel-vs-egg.rst deleted file mode 100644 index d4a1114fb..000000000 --- a/source/discussions/wheel-vs-egg.rst +++ /dev/null @@ -1,55 +0,0 @@ -.. _`Wheel vs Egg`: - -============ -Wheel vs Egg -============ - -:term:`Wheel` and :term:`Egg` are both packaging formats that aim to support the -use case of needing an install artifact that doesn't require building or -compilation, which can be costly in testing and production workflows. - -The :term:`Egg` format was introduced by :ref:`setuptools` in 2004, whereas the -:term:`Wheel` format was introduced by :pep:`427` in 2012. - -:term:`Wheel` is currently considered the standard for :term:`built ` and :term:`binary ` packaging for Python. - -Here's a breakdown of the important differences between :term:`Wheel` and :term:`Egg`. - - -* :term:`Wheel` has an :doc:`official standard specification - `. - :term:`Egg` did not. - -* :term:`Wheel` is a :term:`distribution ` format, i.e a packaging - format. [1]_ :term:`Egg` was both a distribution format and a runtime - installation format (if left zipped), and was designed to be importable. - -* :term:`Wheel` archives do not include .pyc files. Therefore, when the - distribution only contains Python files (i.e. no compiled extensions), and is - compatible with Python 2 and 3, it's possible for a wheel to be "universal", - similar to an :term:`sdist `. - -* :term:`Wheel` uses :pep:`PEP376-compliant <376>` ``.dist-info`` - directories. Egg used ``.egg-info``. - -* :term:`Wheel` has a :pep:`richer file naming convention <425>`. A single - wheel archive can indicate its compatibility with a number of Python language - versions and implementations, ABIs, and system architectures. - -* :term:`Wheel` is versioned. Every wheel file contains the version of the wheel - specification and the implementation that packaged it. - -* :term:`Wheel` is internally organized by `sysconfig path type - `_, - therefore making it easier to convert to other formats. - -* :term:`Egg` uploads have been disabled for upload to PyPI, per :pep:`715`. - Read the `deprecation notice `_ - for more information. - ----- - -.. [1] Circumstantially, in some cases, wheels can be used as an importable - runtime format, although :ref:`this is not officially supported at this time - `. diff --git a/source/glossary.rst b/source/glossary.rst index 1f55871fa..00d798e39 100644 --- a/source/glossary.rst +++ b/source/glossary.rst @@ -14,9 +14,9 @@ Glossary Build Backend - A library that takes a source tree or - :term:`source distribution ` - and builds a source distribution or :term:`wheel ` from it. + A library that takes a source tree + and builds a :term:`source distribution ` or + :term:`built distribution ` from it. The build is delegated to the backend by a :term:`frontend `. All backends offer a standardized interface. @@ -47,11 +47,44 @@ Glossary A :term:`Distribution ` format containing files and metadata that only need to be moved to the correct location on the target system, to be installed. :term:`Wheel` is such a format, whereas - distutil's :term:`Source Distribution ` is not, in that it requires a build step before it can be installed. This format does not imply that Python files have to be precompiled (:term:`Wheel` intentionally does not include compiled - Python files). + Python files). See :ref:`package-formats` for more information. + + + Built Metadata + + The concrete form :term:`Core Metadata` takes + when included inside an installed :term:`Project` (``METADATA`` file) + or a :term:`Distribution Archive` + (``PKG-INFO`` in a + :term:`Sdist ` + and ``METADATA`` in a :term:`Wheel`). + + + Core Metadata + + The :ref:`specification ` + and the set of :term:`Core Metadata Field`\s it defines + that describe key static attributes of + a :term:`Distribution Package` or :term:`Installed Project`. + + + Core Metadata Field + + A single key-value pair + (or sequence of such with the same name, for multiple-use fields) + defined in the :term:`Core Metadata` spec + and stored in the :term:`Built Metadata`. + Notably, distinct from a :term:`Pyproject Metadata Key`. + + + Distribution Archive + + The physical distribution artifact (i.e. a file on disk) + for a :term:`Distribution Package`. Distribution Package @@ -73,9 +106,8 @@ Glossary Egg A :term:`Built Distribution` format introduced by :ref:`setuptools`, - which is being replaced by :term:`Wheel`. For details, see - :doc:`The Internal Structure of Python Eggs ` and - `Python Eggs `_ + which has been replaced by :term:`Wheel`. For details, see + :ref:`egg-format`. Extension Module @@ -107,6 +139,14 @@ Glossary is also commonly called a "package". See :ref:`distribution-package-vs-import-package` for a breakdown of the differences. + + Installed Project + + A :term:`Project` that is installed for use with + a Python interpreter or :term:`Virtual Environment`, + as described in the specicifcation :ref:`recording-installed-packages`. + + Module The basic unit of code reusability in Python, existing in one of two @@ -150,12 +190,64 @@ Glossary 'bar'. + Project Root Directory + + The filesystem directory in which + a :term:`Project`'s :term:`source tree ` is located. + + + Project Source Tree + + The on-disk format of a :term:`Project` used for development, + containing its raw source code before being packaged + into a + :term:`Source Distribution ` + or :term:`Built Distribution`. + + + Project Source Metadata + + Metadata defined by the package author + in a :term:`Project`'s :term:`source tree `, + to be transformed into :term:`Core Metadata field`\s + in the :term:`Built Metadata` + by the project's :term:`build backend `. + Can be written as :term:`Pyproject Metadata`, + or in a tool-specific format + (under the ``[tool]`` table in ``pyproject.toml``, + or in a tool's own configuration file). + + Pure Module A :term:`Module` written in Python and contained in a single ``.py`` file (and possibly associated ``.pyc`` and/or ``.pyo`` files). + Pyproject Metadata + + The :term:`Project Source Metadata` format + defined by the :ref:`declaring-project-metadata` specification + and originally introduced in :pep:`621`, + stored as :term:`Pyproject Metadata Key`\s + under the ``[project]`` table of a :term:`pyproject.toml` file. + Notably, *not* a tool-specific source metadata format + under the ``[tool]`` table in ``pyproject.toml``. + + + Pyproject Metadata Key + + A top-level TOML key in the ``[project]`` table in ``pyproject.toml``; + part of the :term:`Pyproject Metadata`. + Notably, distinct from a :term:`Core Metadata Field`. + + + Pyproject Metadata Subkey + + A second-level TOML key under a table-valued + :term:`Pyproject Metadata Key`. + + Python Packaging Authority (PyPA) PyPA is a working group that maintains many of the relevant @@ -237,10 +329,11 @@ Glossary Source Distribution (or "sdist") - A :term:`distribution ` format (usually generated + A :term:`distribution ` format (usually generated using ``python -m build --sdist``) that provides metadata and the essential source files needed for installing by a tool like :ref:`pip`, - or for generating a :term:`Built Distribution`. + or for generating a :term:`Built Distribution`. See :ref:`package-formats` + for more information. System Package @@ -264,13 +357,22 @@ Glossary wide. For more information, see the section on :ref:`Creating and using Virtual Environments`. + + Wheel Format Wheel - A :term:`Built Distribution` format introduced by an official - :doc:`standard specification - `, - which is intended to replace the :term:`Egg` format. Wheel is currently - supported by :ref:`pip`. + The standard :term:`Built Distribution` format + originally introduced in :pep:`427` + and defined by the :ref:`binary-distribution-format` specification. + See :ref:`package-formats` for more information. + Not to be confused with its reference implementation, + the :term:`Wheel Project`. + + + Wheel Project + + The PyPA reference implementation of the :term:`Wheel Format`; see :ref:`wheel`. + Working Set diff --git a/source/guides/analyzing-pypi-package-downloads.rst b/source/guides/analyzing-pypi-package-downloads.rst index 62efea7ab..5fecc99c1 100644 --- a/source/guides/analyzing-pypi-package-downloads.rst +++ b/source/guides/analyzing-pypi-package-downloads.rst @@ -1,3 +1,5 @@ +.. _analyzing-pypi-package-downloads: + ================================ Analyzing PyPI package downloads ================================ diff --git a/source/guides/creating-command-line-tools.rst b/source/guides/creating-command-line-tools.rst new file mode 100644 index 000000000..49ce0c9ed --- /dev/null +++ b/source/guides/creating-command-line-tools.rst @@ -0,0 +1,192 @@ +.. _creating-command-line-tools: + +========================================= +Creating and packaging command-line tools +========================================= + +This guide will walk you through creating and packaging a standalone command-line application +that can be installed with :ref:`pipx`, a tool creating and managing :term:`Python Virtual Environments ` +and exposing the executable scripts of packages (and available manual pages) for use on the command-line. + +Creating the package +==================== + +First of all, create a source tree for the :term:`project `. For the sake of an example, we'll +build a simple tool outputting a greeting (a string) for a person based on arguments given on the command-line. + +.. todo:: Advise on the optimal structure of a Python package in another guide or discussion and link to it here. + +This project will adhere to :ref:`src-layout ` and in the end be alike this file tree, +with the top-level folder and package name ``greetings``: + +:: + + . + ├── pyproject.toml + └── src + └── greetings + ├── cli.py + ├── greet.py + ├── __init__.py + └── __main__.py + +The actual code responsible for the tool's functionality will be stored in the file :file:`greet.py`, +named after the main module: + +.. code-block:: python + + import typer + from typing_extensions import Annotated + + + def greet( + name: Annotated[str, typer.Argument(help="The (last, if --gender is given) name of the person to greet")] = "", + gender: Annotated[str, typer.Option(help="The gender of the person to greet")] = "", + knight: Annotated[bool, typer.Option(help="Whether the person is a knight")] = False, + count: Annotated[int, typer.Option(help="Number of times to greet the person")] = 1 + ): + greeting = "Greetings, dear " + masculine = gender == "masculine" + feminine = gender == "feminine" + if gender or knight: + salutation = "" + if knight: + salutation = "Sir " + elif masculine: + salutation = "Mr. " + elif feminine: + salutation = "Ms. " + greeting += salutation + if name: + greeting += f"{name}!" + else: + pronoun = "her" if feminine else "his" if masculine or knight else "its" + greeting += f"what's-{pronoun}-name" + else: + if name: + greeting += f"{name}!" + elif not gender: + greeting += "friend!" + for i in range(0, count): + print(greeting) + +The above function receives several keyword arguments that determine how the greeting to output is constructed. +Now, construct the command-line interface to provision it with the same, which is done +in :file:`cli.py`: + +.. code-block:: python + + import typer + + from .greet import greet + + + app = typer.Typer() + app.command()(greet) + + + if __name__ == "__main__": + app() + +The command-line interface is built with typer_, an easy-to-use CLI parser based on Python type hints. It provides +auto-completion and nicely styled command-line help out of the box. Another option would be :py:mod:`argparse`, +a command-line parser which is included in Python's standard library. It is sufficient for most needs, but requires +a lot of code, usually in ``cli.py``, to function properly. Alternatively, docopt_ makes it possible to create CLI +interfaces based solely on docstrings; advanced users are encouraged to make use of click_ (on which ``typer`` is based). + +Now, add an empty :file:`__init__.py` file, to define the project as a regular :term:`import package `. + +The file :file:`__main__.py` marks the main entry point for the application when running it via :mod:`runpy` +(i.e. ``python -m greetings``, which works immediately with flat layout, but requires installation of the package with src layout), +so initizalize the command-line interface here: + +.. code-block:: python + + if __name__ == "__main__": + from greetings.cli import app + app() + +.. note:: + + In order to enable calling the command-line interface directly from the :term:`source tree `, + i.e. as ``python src/greetings``, a certain hack could be placed in this file; read more at + :ref:`running-cli-from-source-src-layout`. + + +``pyproject.toml`` +------------------ + +The project's :term:`metadata ` is placed in :term:`pyproject.toml`. The :term:`pyproject metadata keys ` and the ``[build-system]`` table may be filled in as described in :ref:`writing-pyproject-toml`, adding a dependency +on ``typer`` (this tutorial uses version *0.12.3*). + +For the project to be recognised as a command-line tool, additionally a ``console_scripts`` :ref:`entry point ` (see :ref:`console_scripts`) needs to be added as a :term:`subkey `: + +.. code-block:: toml + + [project.scripts] + greet = "greetings.cli:app" + +Now, the project's source tree is ready to be transformed into a :term:`distribution package `, +which makes it installable. + + +Installing the package with ``pipx`` +==================================== + +After installing ``pipx`` as described in :ref:`installing-stand-alone-command-line-tools`, install your project: + +.. code-block:: console + + $ cd path/to/greetings/ + $ pipx install . + +This will expose the executable script we defined as an entry point and make the command ``greet`` available. +Let's test it: + +.. code-block:: console + + $ greet --knight Lancelot + Greetings, dear Sir Lancelot! + $ greet --gender feminine Parks + Greetings, dear Ms. Parks! + $ greet --gender masculine + Greetings, dear Mr. what's-his-name! + +Since this example uses ``typer``, you could now also get an overview of the program's usage by calling it with +the ``--help`` option, or configure completions via the ``--install-completion`` option. + +To just run the program without installing it permanently, use ``pipx run``, which will create a temporary +(but cached) virtual environment for it: + +.. code-block:: console + + $ pipx run --spec . greet --knight + +This syntax is a bit unpractical, however; as the name of the entry point we defined above does not match the package name, +we need to state explicitly which executable script to run (even though there is only on in existence). + +There is, however, a more practical solution to this problem, in the form of an entry point specific to ``pipx run``. +The same can be defined as follows in :file:`pyproject.toml`: + +.. code-block:: toml + + [project.entry-points."pipx.run"] + greetings = "greetings.cli:app" + + +Thanks to this entry point (which *must* match the package name), ``pipx`` will pick up the executable script as the +default one and run it, which makes this command possible: + +.. code-block:: console + + $ pipx run . --knight + +Conclusion +========== + +You know by now how to package a command-line application written in Python. A further step could be to distribute you package, +meaning uploading it to a :term:`package index `, most commonly :term:`PyPI `. To do that, follow the instructions at :ref:`Packaging your project`. And once you're done, don't forget to :ref:`do some research ` on how your package is received! + +.. _click: https://click.palletsprojects.com/ +.. _docopt: https://docopt.readthedocs.io/en/latest/ +.. _typer: https://typer.tiangolo.com/ diff --git a/source/guides/dropping-older-python-versions.rst b/source/guides/dropping-older-python-versions.rst index c0c2b4434..267d7b923 100644 --- a/source/guides/dropping-older-python-versions.rst +++ b/source/guides/dropping-older-python-versions.rst @@ -4,34 +4,27 @@ Dropping support for older Python versions ========================================== -Dropping support for older Python versions is supported by the standard :ref:`core-metadata` 1.2 specification via a "Requires-Python" attribute. +The ability to drop support for older Python versions is enabled by the standard :ref:`core-metadata` 1.2 specification via the :ref:`"Requires-Python" ` attribute. -Metadata 1.2+ clients, such as Pip 9.0+, will adhere to this specification by matching the current Python runtime and comparing it with the required version +Metadata 1.2+ installers, such as Pip, will adhere to this specification by matching the current Python runtime and comparing it with the required version in the package metadata. If they do not match, it will attempt to install the last package distribution that supported that Python runtime. -This mechanism can be used to drop support for older Python versions, by amending the "Requires-Python" attribute in the package metadata. - -This guide is specifically for users of :ref:`setuptools`, other packaging tools such as ``flit`` may offer similar functionality but users will need to consult relevant documentation. +This mechanism can be used to drop support for older Python versions, by amending the ``Requires-Python`` attribute in the package metadata. Requirements ------------ -This workflow requires that: - -1. The publisher is using the latest version of :ref:`setuptools`, -2. The latest version of :ref:`twine` is used to upload the package, -3. The user installing the package has at least Pip 9.0, or a client that supports the Metadata 1.2 specification. +This workflow requires that the user installing the package uses Pip [#]_, or another installer that supports the Metadata 1.2 specification. Dealing with the universal wheels --------------------------------- -Traditionally, projects providing Python code that is semantically +Traditionally, :ref:`setuptools` projects providing Python code that is semantically compatible with both Python 2 and Python 3, produce :term:`wheels ` that have a ``py2.py3`` tag in their names. When dropping support for Python 2, it is important not to forget to change this tag to just ``py3``. It is often configured within :file:`setup.cfg` under -the ``[bdist_wheel]`` section by setting ``universal = 1`` if they -use setuptools. +the ``[bdist_wheel]`` section by setting ``universal = 1``. If you use this method, either remove this option or section, or explicitly set ``universal`` to ``0``: @@ -43,69 +36,69 @@ explicitly set ``universal`` to ``0``: [bdist_wheel] universal = 0 # Make the generated wheels have "py3" tag -.. tip:: +.. hint:: - Since it is possible to override the :file:`setup.cfg` settings via - CLI flags, make sure that your scripts don't have ``--universal`` in - your package creation scripts. + Regarding :ref:`deprecated ` direct ``setup.py`` invocations, + passing the ``--universal`` flag on the command line could override this setting. Defining the Python version required ------------------------------------ -1. Download the newest version of Setuptools -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Ensure that before you generate source distributions or binary distributions, you update Setuptools and install twine. +1. Install twine +~~~~~~~~~~~~~~~~ +Ensure that you have twine available at its latest version. Steps: .. tab:: Unix/macOS .. code-block:: bash - python3 -m pip install --upgrade setuptools twine + python3 -m pip install --upgrade twine .. tab:: Windows .. code-block:: bat - py -m pip install --upgrade setuptools twine - -``setuptools`` version should be above 24.0.0. + py -m pip install --upgrade twine 2. Specify the version ranges for supported Python distributions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -You can specify version ranges and exclusion rules, such as at least Python 3. Or, Python 2.7, 3.4 and beyond. +Set the version ranges declaring which Python distributions are supported +within your project's :file:`pyproject.toml`. The :ref:`requires-python` configuration field +corresponds to the :ref:`Requires-Python ` core metadata field: -Examples: +.. code-block:: toml -.. code-block:: text + [build-system] + ... - Requires-Python: ">=3" - Requires-Python: ">2.7,!=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + [project] + requires-python = ">= 3.8" # At least Python 3.8 -The way to set those values is within the call to ``setup`` within your -:file:`setup.py` script. This will insert the ``Requires-Python`` -metadata values based on the argument you provide in ``python_requires``. +You can specify version ranges and exclusion rules (complying with the :ref:`version-specifiers` specification), +such as at least Python 3.9. Or, at least Python 3.7 and beyond, skipping the 3.7.0 and 3.7.1 point releases: -.. code-block:: python +.. code-block:: toml - from setuptools import setup + requires-python = ">= 3.9" + requires-python = ">= 3.7, != 3.7.0, != 3.7.1" - setup( - # Your setup arguments - python_requires='>=2.7', # Your supported Python ranges - ) +If using the :ref:`setuptools` build backend, consult the `dependency-management`_ documentation for more options. + +.. caution:: + Avoid adding upper bounds to the version ranges, e. g. ``">= 3.8, < 3.10"``. Doing so can cause different errors + and version conflicts. See the `discourse-discussion`_ for more information. 3. Validating the Metadata before publishing ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Within a Python source package (the zip or the tar-gz file you download) is a text file called PKG-INFO. -This file is generated by :ref:`distutils` or :ref:`setuptools` when it generates the source package. -The file contains a set of keys and values, the list of keys is part of the PyPa standard metadata format. +This file is generated by the :term:`build backend ` when it generates the source package. +The file contains a set of keys and values, the list of keys is part of the PyPA standard metadata format. You can see the contents of the generated file like this: @@ -115,24 +108,31 @@ You can see the contents of the generated file like this: Validate that the following is in place, before publishing the package: -- If you have upgraded correctly, the Metadata-Version value should be 1.2 or higher. -- The Requires-Python field is set and matches your specification in setup.py. +- If you have upgraded correctly, the ``Metadata-Version`` value should be 1.2 or higher. +- The ``Requires-Python`` field is set and matches your specification in the configuration file. -4. Using Twine to publish +4. Publishing the package ~~~~~~~~~~~~~~~~~~~~~~~~~ -Twine has a number of advantages, apart from being faster it is now the supported method for publishing packages. - -Make sure you are using the newest version of Twine, at least 1.9. +Proceed as suggested in :ref:`Uploading your Project to PyPI`. -Dropping a Python release +Dropping a Python version ------------------------- -Once you have published a package with the Requires-Python metadata, you can then make a further update removing that Python runtime from support. +In principle, at least metadata support for Python versions should be kept as long as possible, because +once that has been dropped, people still depending on a version will be forced to downgrade. +If however supporting a specific version becomes a blocker for a new feature or other issues occur, the metadata +``Requires-Python`` should be amended. Of course this also depends on whether the project needs to be stable and +well-covered for a wider range of users. + +Each version compatibility change should have its own release. + +.. tip:: -It must be done in this order for the automated fallback to work. + When dropping a Python version, it might also be rewarding to upgrade the project's code syntax generally, apart from updating the versions used in visible places (like the testing environment). Tools like pyupgrade_ or `ruff `_ can automate some of this work. -For example, you published the Requires-Python: ">=2.7" as version 1.0.0 of your package. +.. _discourse-discussion: https://discuss.python.org/t/requires-python-upper-limits/12663 +.. _pyupgrade: https://pypi.org/project/pyupgrade/ +.. _dependency-management: https://setuptools.pypa.io/en/latest/userguide/dependency_management.html#python-requirement -If you were then to update the version string to ">=3.5", and publish a new version 2.0.0 of your package, any users running Pip 9.0+ from version 2.7 will -have version 1.0.0 of the package installed, and any >=3.5 users will receive version 2.0.0. +.. [#] Support for the Metadata 1.2 specification has been added in Pip 9.0. diff --git a/source/guides/github-actions-ci-cd-sample/publish-to-test-pypi.yml b/source/guides/github-actions-ci-cd-sample/publish-to-test-pypi.yml index 72a3eb7b2..3bd06cccc 100644 --- a/source/guides/github-actions-ci-cd-sample/publish-to-test-pypi.yml +++ b/source/guides/github-actions-ci-cd-sample/publish-to-test-pypi.yml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.x" - name: Install pypa/build @@ -68,7 +68,7 @@ jobs: name: python-package-distributions path: dist/ - name: Sign the dists with Sigstore - uses: sigstore/gh-action-sigstore-python@v1.2.3 + uses: sigstore/gh-action-sigstore-python@v2.1.1 with: inputs: >- ./dist/*.tar.gz diff --git a/source/guides/installing-stand-alone-command-line-tools.rst b/source/guides/installing-stand-alone-command-line-tools.rst index ecc44a487..c078fd1e4 100644 --- a/source/guides/installing-stand-alone-command-line-tools.rst +++ b/source/guides/installing-stand-alone-command-line-tools.rst @@ -1,3 +1,5 @@ +.. _installing-stand-alone-command-line-tools: + Installing stand alone command line tools ========================================= @@ -54,11 +56,11 @@ For example: .. code-block:: console $ pipx install cowsay - installed package cowsay 2.0, Python 3.6.2+ - These binaries are now globally available + installed package cowsay 6.1, installed using Python 3.12.2 + These apps are now globally available - cowsay done! ✨ 🌟 ✨ - $ cowsay moo + $ cowsay -t moo ___ < moo > === @@ -77,18 +79,21 @@ available, use ``pipx list``: .. code-block:: console $ pipx list - venvs are in /Users/user/.local/pipx/venvs - symlinks to binaries are in /Users/user/.local/bin - package black 18.9b0, Python 3.6.2+ + venvs are in /Users/user/Library/Application Support/pipx/venvs + apps are exposed on your $PATH at /Users/user/.local/bin + manual pages are exposed at /Users/user/.local/share/man + package black 24.2.0, installed using Python 3.12.2 - black - blackd - package cowsay 2.0, Python 3.6.2+ + package cowsay 6.1, installed using Python 3.12.2 - cowsay - package mypy 0.660, Python 3.6.2+ + package mypy 1.9.0, installed using Python 3.12.2 - dmypy - mypy + - mypyc - stubgen - package nox 2018.10.17, Python 3.6.2+ + - stubtest + package nox 2024.3.2, installed using Python 3.12.2 - nox - tox-to-nox @@ -120,7 +125,7 @@ in a temporary, ephemeral environment. For example: .. code-block:: bash - pipx run cowsay moooo + pipx run cowsay -t moooo To see the full list of commands pipx offers, run: diff --git a/source/guides/installing-using-linux-tools.rst b/source/guides/installing-using-linux-tools.rst index f0914f8dc..56647f3e9 100644 --- a/source/guides/installing-using-linux-tools.rst +++ b/source/guides/installing-using-linux-tools.rst @@ -51,7 +51,7 @@ To install pip and wheel for the system Python, there are two options: 1. Enable the `EPEL repository `_ using `these instructions - `__. + `__. On EPEL 7, you can install pip and wheel like so: .. code-block:: bash diff --git a/source/guides/packaging-binary-extensions.rst b/source/guides/packaging-binary-extensions.rst index 589ed89c8..de8a9d2d6 100644 --- a/source/guides/packaging-binary-extensions.rst +++ b/source/guides/packaging-binary-extensions.rst @@ -403,3 +403,15 @@ a Debian system, see the following articles: * `What are (c)python extension modules? `_ * `Releasing the gil `_ * `Writing cpython extension modules using C++ `_ + +Additional considerations for binary wheels +------------------------------------------- + +The `pypackaging-native `_ website has +additional coverage of packaging Python packages with native code. It aims to +provide an overview of the most important packaging issues for such projects, +with in-depth explanations and references. + +Examples of topics covered are non-Python compiled dependencies ("native +dependencies"), the importance of the ABI (Application Binary Interface) of +native code, dependency on SIMD code and cross compilation. diff --git a/source/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows.rst b/source/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows.rst index 99ac3a9e2..049fba15c 100644 --- a/source/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows.rst +++ b/source/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows.rst @@ -171,6 +171,14 @@ from the current tag is created using the ``gh`` CLI. Note this step can be furt customised. See the `gh release documentation `_ as a reference. +.. tip:: + + You may need to manage your ``GITHUB_TOKEN`` permissions to + enable creating the GitHub Release. See the `GitHub + documentation `_ + for instructions. Specifically, the token needs the + ``contents: write`` permission. + Finally, the signed distributions are uploaded to the GitHub Release. .. literalinclude:: github-actions-ci-cd-sample/publish-to-test-pypi.yml diff --git a/source/guides/section-build-and-publish.rst b/source/guides/section-build-and-publish.rst index 2af29d763..8e0c9ab3b 100644 --- a/source/guides/section-build-and-publish.rst +++ b/source/guides/section-build-and-publish.rst @@ -11,6 +11,7 @@ Building and Publishing dropping-older-python-versions packaging-binary-extensions packaging-namespace-packages + creating-command-line-tools creating-and-discovering-plugins using-testpypi making-a-pypi-friendly-readme diff --git a/source/guides/writing-pyproject-toml.rst b/source/guides/writing-pyproject-toml.rst index 938ebbb93..d11b8a87c 100644 --- a/source/guides/writing-pyproject-toml.rst +++ b/source/guides/writing-pyproject-toml.rst @@ -22,19 +22,19 @@ three possible TOML tables in this file. .. note:: - There is a significant difference between the ``[build-system]`` and - ``[project]`` tables. The former should always be present, regardless of - which build backend you use (since it *defines* the tool you use). The latter - is understood by *most* build backends, but some build backends use a - different format. + The ``[build-system]`` table should always be present, + regardless of which build backend you use (``[build-system]`` *defines* the + build tool you use). - At the time of writing this (November 2023), Poetry_ is a notable build - backend that does not use the ``[project]`` table (it uses the - ``[tool.poetry]`` table instead). + On the other hand, the ``[project]`` table is understood by *most* build + backends, but some build backends use a different format. + As of August 2024, Poetry_ is a notable build backend that does not use + the ``[project]`` table, it uses the ``[tool.poetry]`` table instead. Also, the setuptools_ build backend supports both the ``[project]`` table, - and the older format in ``setup.cfg`` or ``setup.py``. For new projects, it - is recommended to use the ``[project]`` table, and keep ``setup.py`` only if + and the older format in ``setup.cfg`` or ``setup.py``. + + For new projects, use the ``[project]`` table, and keep ``setup.py`` only if some programmatic configuration is needed (such as building C extensions), but the ``setup.cfg`` and ``setup.py`` formats are still valid. See :ref:`setup-py-deprecated`. @@ -130,7 +130,7 @@ only field that cannot be marked as dynamic. [project] name = "spam-eggs" -The project name must consists of ASCII letters, digits, underscores "``_``", +The project name must consist of ASCII letters, digits, underscores "``_``", hyphens "``-``" and periods "``.``". It must not start or end with an underscore, hyphen or period. @@ -340,7 +340,7 @@ or you can write the name of the license: license = {text = "MIT License"} If you are using a standard, well-known license, it is not necessary to use this -field. Instead, you should one of the :ref:`classifiers` starting with ``License +field. Instead, you should use one of the :ref:`classifiers` starting with ``License ::``. (As a general rule, it is a good idea to use a standard, well-known license, both to avoid confusion and because some organizations avoid software whose license is unapproved.) diff --git a/source/specifications/binary-distribution-format.rst b/source/specifications/binary-distribution-format.rst index 3ca3cc785..8da38357a 100644 --- a/source/specifications/binary-distribution-format.rst +++ b/source/specifications/binary-distribution-format.rst @@ -43,8 +43,8 @@ Wheel installation notionally consists of two phases: destination path. Each subdirectory of ``distribution-1.0.data/`` is a key into a dict of destination directories, such as ``distribution-1.0.data/(purelib|platlib|headers|scripts|data)``. - The initially supported paths are taken from - ``distutils.command.install``. + These subdirectories are :ref:`installation paths defined by sysconfig + `. c. If applicable, update scripts starting with ``#!python`` to point to the correct interpreter. d. Update ``distribution-1.0.dist-info/RECORD`` with the installed @@ -84,6 +84,8 @@ Place ``.dist-info`` at the end of the archive. File Format ----------- +.. _wheel-file-name-spec: + File name convention '''''''''''''''''''' diff --git a/source/specifications/core-metadata.rst b/source/specifications/core-metadata.rst index 828c1ade9..40fa3322e 100644 --- a/source/specifications/core-metadata.rst +++ b/source/specifications/core-metadata.rst @@ -729,7 +729,7 @@ This field may be followed by an environment marker after a semicolon. Examples:: Provides-Dist: OtherProject - Provides-Dist: AnotherProject (3.4) + Provides-Dist: AnotherProject==3.4 Provides-Dist: virtual_package; python_version >= "3.4" .. _core-metadata-obsoletes-dist: diff --git a/source/specifications/dependency-specifiers.rst b/source/specifications/dependency-specifiers.rst index 51c0b3645..d6713f713 100644 --- a/source/specifications/dependency-specifiers.rst +++ b/source/specifications/dependency-specifiers.rst @@ -66,14 +66,14 @@ URI is defined in :rfc:`std-66 <3986>`):: version_cmp = wsp* '<' | '<=' | '!=' | '==' | '>=' | '>' | '~=' | '===' version = wsp* ( letterOrDigit | '-' | '_' | '.' | '*' | '+' | '!' )+ version_one = version_cmp version wsp* - version_many = version_one (wsp* ',' version_one)* + version_many = version_one (',' version_one)* (',' wsp*)? versionspec = ( '(' version_many ')' ) | version_many urlspec = '@' wsp* Environment markers allow making a specification only take effect in some environments:: - marker_op = version_cmp | (wsp* 'in') | (wsp* 'not' wsp+ 'in') + marker_op = version_cmp | (wsp+ 'in' wsp+) | (wsp+ 'not' wsp+ 'in' wsp+) python_str_c = (wsp | letter | digit | '(' | ')' | '.' | '{' | '}' | '-' | '_' | '*' | '#' | ':' | ';' | ',' | '/' | '?' | '[' | ']' | '!' | '~' | '`' | '@' | '$' | '%' | '^' | @@ -116,7 +116,7 @@ Giving us a rule for name based requirements:: And a rule for direct reference specifications:: - url_req = name wsp* extras? wsp* urlspec wsp+ quoted_marker? + url_req = name wsp* extras? wsp* urlspec (wsp+ quoted_marker?)? Leading to the unified rule that can specify a dependency.:: @@ -303,7 +303,7 @@ The complete parsley grammar:: version_cmp = wsp* <'<=' | '<' | '!=' | '==' | '>=' | '>' | '~=' | '==='> version = wsp* <( letterOrDigit | '-' | '_' | '.' | '*' | '+' | '!' )+> version_one = version_cmp:op version:v wsp* -> (op, v) - version_many = version_one:v1 (wsp* ',' version_one)*:v2 -> [v1] + v2 + version_many = version_one:v1 (',' version_one)*:v2 (',' wsp*)? -> [v1] + v2 versionspec = ('(' version_many:v ')' ->v) | version_many urlspec = '@' wsp* marker_op = version_cmp | (wsp* 'in') | (wsp* 'not' wsp+ 'in') @@ -424,6 +424,7 @@ A test program - if the grammar is in a string ``grammar``: "name", "name<=1", "name>=3", + "name>=3,", "name>=3,<2", "name@http://foo.com", "name [fred,bar] @ http://foo.com ; python_version=='2.7'", @@ -481,6 +482,9 @@ History ``'.'.join(platform.python_version_tuple()[:2])``, to accommodate potential future versions of Python with 2-digit major and minor versions (e.g. 3.10). [#future_versions]_ +- June 2024: The definition of ``version_many`` was changed to allow trailing + commas, matching with the behavior of the Python implementation that has been + in use since late 2022. References diff --git a/source/specifications/direct-url-data-structure.rst b/source/specifications/direct-url-data-structure.rst index da4d959e4..6a4e8fe01 100644 --- a/source/specifications/direct-url-data-structure.rst +++ b/source/specifications/direct-url-data-structure.rst @@ -10,10 +10,10 @@ This document specifies a JSON-serializable abstract data structure that can rep URLs to python projects and distribution artifacts such as VCS source trees, local source trees, source distributions and wheels. -The representation of the components of this data structure as a :rfc:`1738` URL -is not formally specified at time of writing. A common representation is the pip URL -format. Other examples are provided in the :ref:`Version specifier specification `. - +At time of writing, it is not formally specified how to merge the parts of this +data structure into a single URL that can be passed to tools. A common representation is the +pip URL format (`VCS Support `_), other examples are provided in the +:ref:`Version specifier specification `. Specification ============= @@ -22,12 +22,18 @@ The Direct URL Data Structure MUST be a dictionary, serializable to JSON accordi :rfc:`8259`. It MUST contain at least two fields. The first one is ``url``, with -type ``string``. Depending on what ``url`` refers to, the second field MUST be -one of ``vcs_info`` (if ``url`` is a VCS reference), ``archive_info`` (if -``url`` is a source archives or a wheel), or ``dir_info`` (if ``url`` is a +type ``string``. Its content must be a valid URL according to the +`WHATWG URL Standard `_. + +Depending on what ``url`` refers to, the second field MUST be one of ``vcs_info`` +(if ``url`` is a VCS reference), ``archive_info`` (if +``url`` is a source archive or a wheel), or ``dir_info`` (if ``url`` is a local directory). These info fields have a (possibly empty) subdictionary as value, with the possible keys defined below. +Security Considerations +----------------------- + When persisted, ``url`` MUST be stripped of any sensitive authentication information, for security reasons. @@ -41,7 +47,7 @@ expression: Additionally, the user:password section of the URL MAY be a well-known, non security sensitive string. A typical example is ``git`` -in the case of an URL such as ``ssh://git@gitlab.com/user/repo``. +in the case of a URL such as ``ssh://git@gitlab.com/user/repo``. VCS URLs -------- @@ -56,7 +62,9 @@ as a dictionary with the following keys: so an installer can hand it off without transformation to a checkout/download command of the VCS. - A ``requested_revision`` key (type ``string``) MAY be present naming a - branch/tag/ref/commit/revision/etc (in a format compatible with the VCS). + branch/tag/ref/commit/revision/etc (in a format compatible with the VCS). This field + MUST match the revision requested by the user and MUST NOT exist when the user did + not select a specific revision. - A ``commit_id`` key (type ``string``) MUST be present, containing the exact commit/revision number that was/is to be installed. If the VCS supports commit-hash @@ -396,3 +404,5 @@ History .. _archive-info-hashes: https://discuss.python.org/t/22299 +.. _pip-vcs-support: https://pip.pypa.io/en/stable/topics/vcs-support/ +.. _whatwg-url-standard: https://url.spec.whatwg.org/ diff --git a/source/specifications/externally-managed-environments.rst b/source/specifications/externally-managed-environments.rst index 2944eb3da..65fc14a62 100644 --- a/source/specifications/externally-managed-environments.rst +++ b/source/specifications/externally-managed-environments.rst @@ -205,7 +205,7 @@ virtual environment to install packages. Software distributors who have a non-Python-specific package manager that manages libraries in the ``sys.path`` of their Python package -should, in general, ship a ``EXTERNALLY-MANAGED`` file in their +should, in general, ship an ``EXTERNALLY-MANAGED`` file in their standard library directory. For instance, Debian may ship a file in ``/usr/lib/python3.9/EXTERNALLY-MANAGED`` consisting of something like diff --git a/source/specifications/inline-script-metadata.rst b/source/specifications/inline-script-metadata.rst index 352614e81..f40b9ac4a 100644 --- a/source/specifications/inline-script-metadata.rst +++ b/source/specifications/inline-script-metadata.rst @@ -1,3 +1,5 @@ +.. _inline-script-metadata: + ====================== Inline script metadata ====================== diff --git a/source/specifications/platform-compatibility-tags.rst b/source/specifications/platform-compatibility-tags.rst index 381b84ca9..740a28a1a 100644 --- a/source/specifications/platform-compatibility-tags.rst +++ b/source/specifications/platform-compatibility-tags.rst @@ -256,7 +256,7 @@ The full list of simple tags is:: for x in pytag.split('.'): for y in abitag.split('.'): - for z in archtag.split('.'): + for z in platformtag.split('.'): yield '-'.join((x, y, z)) A bdist format that implements this scheme should include the expanded diff --git a/source/specifications/pyproject-toml.rst b/source/specifications/pyproject-toml.rst index 1580b3772..efa562a73 100644 --- a/source/specifications/pyproject-toml.rst +++ b/source/specifications/pyproject-toml.rst @@ -180,7 +180,8 @@ Users SHOULD prefer to specify already-normalized versions. - Corresponding :ref:`core metadata ` field: :ref:`Summary ` -The summary description of the project. +The summary description of the project in one line. Tools MAY error +if this includes multiple lines. ``readme`` diff --git a/source/specifications/simple-repository-api.rst b/source/specifications/simple-repository-api.rst index b9f87ce7b..0d65a58aa 100644 --- a/source/specifications/simple-repository-api.rst +++ b/source/specifications/simple-repository-api.rst @@ -360,7 +360,7 @@ spec: * All JSON responses will *always* be a JSON object rather than an array or other type. -* While JSON doesn't natively support an URL type, any value that represents an +* While JSON doesn't natively support a URL type, any value that represents an URL in this API may be either absolute or relative as long as they point to the correct location. If relative, they are relative to the current URL as if it were HTML. @@ -616,7 +616,7 @@ likely just be treated the same as a ``406 Not Acceptable`` error. This spec **does** require that if the meta version ``latest`` is being used, the server **MUST** respond with the content type for the actual version that is contained in the response -(i.e. A ``Accept: application/vnd.pypi.simple.latest+json`` request that returns +(i.e. an ``Accept: application/vnd.pypi.simple.latest+json`` request that returns a ``v1.x`` response should have a ``Content-Type`` of ``application/vnd.pypi.simple.v1+json``). @@ -725,7 +725,7 @@ may *optionally* be used instead. URL Parameter ^^^^^^^^^^^^^ -Servers that implement the Simple API may choose to support an URL parameter named +Servers that implement the Simple API may choose to support a URL parameter named ``format`` to allow the clients to request a specific version of the URL. The value of the ``format`` parameter should be **one** of the valid content types. diff --git a/source/specifications/version-specifiers.rst b/source/specifications/version-specifiers.rst index cde0bc49a..a5ba36498 100644 --- a/source/specifications/version-specifiers.rst +++ b/source/specifications/version-specifiers.rst @@ -1178,7 +1178,7 @@ more information on ``file://`` URLs on Windows see Summary of differences from pkg_resources.parse_version ======================================================= -* Note: this comparison is to ``pkg_resourses.parse_version`` as it existed at +* Note: this comparison is to ``pkg_resources.parse_version`` as it existed at the time :pep:`440` was written. After the PEP was accepted, setuptools 6.0 and later versions adopted the behaviour described here. diff --git a/source/tutorials/installing-packages.rst b/source/tutorials/installing-packages.rst index 0e80642ae..817148d06 100644 --- a/source/tutorials/installing-packages.rst +++ b/source/tutorials/installing-packages.rst @@ -224,8 +224,8 @@ environments. Currently, there are two common tools for creating Python virtual environments: * :doc:`venv ` is available by default in Python 3.3 and later, and installs - :ref:`pip` and :ref:`setuptools` into created virtual environments in - Python 3.4 and later. + :ref:`pip` into created virtual environments in Python 3.4 and later + (Python versions prior to 3.12 also installed :ref:`setuptools`). * :ref:`virtualenv` needs to be installed separately, but supports Python 2.7+ and Python 3.3+, and :ref:`pip`, :ref:`setuptools` and :ref:`wheel` are always installed into created virtual environments by default (regardless of diff --git a/source/tutorials/packaging-projects.rst b/source/tutorials/packaging-projects.rst index 761b2748f..4c205e28f 100644 --- a/source/tutorials/packaging-projects.rst +++ b/source/tutorials/packaging-projects.rst @@ -54,8 +54,10 @@ Create the following file structure locally: The directory containing the Python files should match the project name. This simplifies the configuration and is more obvious to users who install the package. -:file:`__init__.py` is recommended to import the directory as a regular package, -even if as is our case for this tutorial that file is empty [#namespace-packages]_. +Creating the file :file:`__init__.py` is recommended because the existence of an +:file:`__init__.py` file allows users to import the directory as a regular package, +even if (as is the case in this tutorial) :file:`__init__.py` is empty. +[#namespace-packages]_ :file:`example.py` is an example of a module within the package that could contain the logic (functions, classes, constants, etc.) of your package.