PEP: 773
Title: A Python Installation Manager for Windows
Author: Steve Dower
Discussions-To: https://discuss.python.org/t/77900/
Status: Final
Type: Standards Track
Topic: Release
Created: 21-Jan-2025
Post-History:
   `18-Dec-2024 <https://discuss.python.org/t/74556/>`__,
   `21-Jan-2025 <https://discuss.python.org/t/77900/>`__,
Replaces: 397, 486
Resolution: `25-Apr-2025 <https://discuss.python.org/t/77900/180>`__

.. canonical-doc:: `Python Releases for Windows <https://www.python.org/downloads/windows/>`__


Abstract
========

Installation of the python.org Python distribution on Windows is complex.
There are three main approaches with roughly equivalent levels of user
experience, and yet all of these suffer from different limitations, including
failing to satisfy modern usage scenarios. This PEP proposes a design for
a single Windows install workflow tool that satisfies all the needs of the
existing installers for the platform, while avoiding most of their limitations,
and provides the core team with the ability to manage releases for many years
to come.

With the new installer, the recommended command to launch the default Python on
Windows will be ``python``, while the command for managing multiple versions
(including launching a specific one) will be ``py``. Users can also opt-in to
having global ``python3.x`` commands by adding an extra entry to their ``PATH``
environment variable. We recommend starting with the "How to teach this" section
for the most concise high-level overview of the features.

The existing ``.exe`` installer and ``py`` launcher will be deprecated and no
longer released from two years after this PEP is accepted. The existing
embeddable distro will no longer be listed as a download, but will be available
through the install manager. The Windows Store app will be immediately replaced
with the install manager.

Reader's Note
-------------

This document is a detailed *proposal*, intended to assist in an "approve" or
"reject" decision on the replacement of the existing install formats for Python
on Windows with a new format. It is not intended to be a binding specification,
nor is it user documentation or an interoperability specification. The actual
implementation may vary over time as needs change, and that is not to be
considered a "violation" of this PEP. Any interfaces and protocols meant for
public use will be independently documented and maintained in accordance with
standard deprecation timelines.

Documentation on installing Python on Windows can be found at
`docs.python.org/using/windows.html <https://docs.python.org/using/windows.html>`__.

Background
==========

There are a large range of needs users may have that lead to them wanting
to install a Python runtime. Many, likely most, are interested in running
(perhaps writing) short scripts, such as those that perform a simple task,
or help teach someone a concept. Some users are looking for a specific version
to integrate with existing code or another application. Some are after a full
set of different interpreter versions to perform testing.

In this section, we discuss the expectations that users have of "installing
Python", provide an overview of the existing installers for Windows, and
identify some of the gaps and challenges inherent in these offerings.

Expectations
------------

Based on significant anecdotal experience and analysis of quantitative data
available (though not necessarily public), we make the following assertions
about the majority of Python users on Windows:

* most users just want the latest stable version
* most users want a "one-click" (or fewer) install
* most users do not want to use administrator privileges
* most users will benefit from installing maintenance updates
* most users expect the ``python`` command to work after installation

The primary support for these assertions is that the most popular installers
actively chosen by users are the latest stable release on python.org, and
the latest stable release on the Windows Store, both of which meet these
requirements.

We make the following assumptions about other significant sets of users.
These may have some overlap between groups, and at least some users expect
all of them.

* some users want to install Python programmatically
* some users want to install a particular version
* some users want to install many versions
* some users want to install for all users of their machine
* some users do not want Start Menu shortcuts
* some users want to install as part of their project's build process
* some users want to install as part of their project's install process
* some users intend to never update their install
* some users expect the ``py`` command to work after installation

These assumptions have all been demonstrated over time to exist, though the
relative importance has not been quantified. The :ref:`NuGet packages
<windows-nuget>` and the :ref:`embeddable distro
<windows-embeddable>` can meet most of these needs.

Traditional Installer
---------------------

The traditional installer is an executable downloadable directly from
python.org that installs the entire development kit for Python. This includes
the CPython interpreter, the standard library, Python headers and import
libraries, builds of Tcl and Tk, the documentation as HTML files, the runtime
and standard library test suite, Start Menu shortcuts for Python and IDLE,
debugging symbols and debug builds of the binaries, the ``py.exe`` launcher
and its file associations, and functionality to modify the user's ``PATH``
environment variable, enable long-path support on their system, pre-generate
``.pyc`` files for the standard library, and install pip.
As of 3.13, it also includes a set of experimental free-threaded binaries.
Many of these components are optional.

After downloading the executable, users are presented with a "quick install"
option, which installs into their user directory with most options enabled.
We believe that most users select this option.

A second option alongside the quick install takes the user to two pages worth
of options, listing the components that they need not install, as well as other
options such as the install directory and whether to install for all users.

All of these options may be specified on the command line, and there is also an
option to proceed with the install without displaying any UI.
Based on feedback and bug reports, all of these options are used by at least
some users. However, as we do not track install telemetry, we have no way to
know which options are more important than others.

Behind the scenes, the traditional installer is a Burn bundle, generated using
the Wix Toolset installer framework, containing one or more MSI files for each
feature. This framework is used extensively by Microsoft themselves, and
provides the most direct method of using Windows Installer. The bundle is a
custom C++ application, based on their template, which allows us to customise
the overall behaviour of the installer to determine precisely which MSI files
should actually be installed. The process of copying files, updating the
registry, and generating shortcuts is handled entirely by Windows Installer.

As well as the intended uses, it is understood that many users will (attempt to)
use the traditional installer for other scenarios, such as unregistered installs
and automated CI system installs. While better alternatives are available, they
are not as obvious, and the hope is that a future design would make these
scenarios easier.

Windows Store
-------------

The Windows Store packages for CPython are produced as part of our normal
release process using almost all identical binaries to the other packages.
Due to being in an app store package, the primary ``python.exe`` is enhanced
to be able to determine its location properly, and alternative ``pip.exe`` and
other shortcuts are included to make up for the lack of ``PATH`` environment
variable settings. These are implemented in :file:`PC\\python_uwp.cpp` in our repo.

These packages are installed by searching for Python in the Microsoft Store
app, which will find results for each major version since 3.8. Users then have
to select a version and install it. These packages include the CPython
interpreter, standard library, Tcl/Tk, IDLE, and pip, and create file
associations, Start Menu shortcuts, and global commands for ``python.exe``,
``python3.exe``, ``python3.X.exe``, ``pip[3[.X]].exe`` and ``idle[3[.X]].exe``.
No ``PATH`` modification is possible or required, though users may need to
manage their global shortcuts through the "Manage App Execution Alias" settings
page.

In addition, Microsoft has added to a clean Windows install a default
``python.exe`` command. This captures attempts by users to launch Python on
a machine that has not yet installed it. When launched directly, the command
will open the Microsoft Store app to the page containing the recommended
Python app, typically the latest version. This app is entirely controlled by
Microsoft. Based on telemetry associated with the Python app (which *is*
controlled by the upstream Python project), approximately 300,000 installs
per month come through this redirector, making up about 90% of the total
installs of that version.

Behind the scenes, the Store package is based on Microsoft's new installer
technology for apps known as APPX or MSIX. These are essentially plain ZIP
files with a small amount of metadata, except that installation is handled
by the operating system. They are always extracted to a fixed location,
accessible to all users but not modifiable by any, and automatically updated
to the latest release. The user's own data is stored in an OS-managed location
in their user profile, and is able to be reset, backed-up and restored using
regular OS functionality.

NuGet Package
-------------

The NuGet packages for CPython are produced and published as part of our
normal release process. The contents are identical to the traditional
installer. A NuGet package is published to nuget.org, which is a package
manager typically associated with .NET languages, but highly integrated with
any project supported by Visual Studio. This makes it a nice format for users
who want a lightweight install of Python as part of their regular build process,
and can simplify embedding scenarios.

The packages are installed using any tool capable of using the NuGet API, or
may be downloaded directly once the URL of the package is known. The package is
a plain ZIP file with some metadata. It contains the CPython interpreter, the
standard library, development headers and import libraries, and pip. It does
not execute any code at install time, and users must locate the package
themselves in order to launch the ``python.exe`` contained within.

Embeddable Package
------------------

The embeddable package for CPython is produced and published as part of our
normal release process. It is published to python.org alongside the
traditional installer. The contents are identical, however, the layout is
changed to store all binaries at the top level, with the standard library
packed into a ZIP file. A ``._pth`` file is included to override ``sys.path``
so that only the files that are part of the distro are used, and environment
variables or registry entries are ignored.

This package does not include pip, as the intention is for it to be embedded
into a broader application. Other libraries should be installed at build time,
since after distribution the runtime is meant to be an internal implementation
detail of the app it is a part of.

As well as its intended use, some users attempt to use this package as a
development kit rather than a runtime package. This is believed to be due to
those users preferring to avoid "heavyweight" installers, and believing that
this package is intended to be a "portable" install (extract and run), likely
because it is the only ZIP file option listed on the python.org download pages
(speaking to the importance of clarity and limiting options on those pages).
It is hoped that a future installer design will avoid or limit this confusion.

Alternate Distributions
-----------------------

While outside of our purview as the core team, alternate distributions of Python
for Windows often use a project, workflow or environment-centric model for
installation of the runtime. By this, we mean that the tool is installed first,
and is used to create a working space that includes a runtime, as well as other
dependencies. Examples of these tools include conda and uv.

Two observations are worth making about these tools. Firstly, they are often
praised for being low impact, in that they usually don't install additional
entry points or files for the runtime, making the install fast and also isolated
to a single project. Secondly, their users often appreciate the ease of
selecting a particular version of a runtime, or alternatively, not having to
select at all because existing specifications (or constraints) can choose for
them.

These tools tend to meet many of the second set of expectations described above,
usually combining multiple tasks in a single command to reduce the cognitive
overhead of learning how to use and combine multiple commands.

It's also worth pointing out that the core team does not view these alternate
distributions as competitors to any upstream distribution. They are a
fundamental part of how the open source ecosystem is intended to work. Our own
distributions are a convenience for those who choose to use them, as not all
scenarios are well served by a workflow tool or even a pre-built package.


Challenges
----------

There are numerous challenges we face with the current set of installers,
which largely break down into two categories: mismatched or unachievable
user expectations, and general unreliability.

The traditional installer has the highest level of unreliability. The Windows
Installer technology is very old, and effectively no longer under development.
While its basic functionality is okay, interference may come from many sources,
such as virus scanners, other installers, system configuration, admin policies,
and even other files in the same directory as the installer. On top of this,
most of its advanced and beneficial functionality such as update patches,
incremental updates, and automatic rollback are unimportant for Python users.

Most user expectations are *defined* by the traditional installer, and so by
definition, it meets them. One primary gap is that it is not able to create an
"unmanaged" install - that is, the equivalent of only copying files onto the
user's system without registration. If you have installed it once, and you
try to install it again, you will only even be able to manage (or upgrade) the
existing install. This can lead to installs moving on update, which will
break users.

Additionally, the ``PATH`` environment variable cannot be intelligently
modified - at best, we can prepend or append the install path. This usually
results in the most recent install of Python being the highest priority. For
example, if the user has Python 3.14 installed and then installs (or updates)
3.13, the ``python`` command will switch from the later version to the earlier
version.

The ``py.exe`` launcher, defined in :pep:`397` and implicitly updated by :pep:`514`,
is an attempt to avoid this particular issue. It uses its own logic for finding
installed versions without relying on ``PATH``. However, the PEP 514 logic does
not allow for prerelease or experimental builds to be treated specially, and so
``py.exe`` often prefers these builds by default over the non-experimental
version expected by the user.

The Windows Store package is very reliable, with the exception of the global
shortcuts. Rather than modifying ``PATH`` to add its own directory, these
shortcuts are created in a single OS managed directory that has all the
shortcuts defined by any app. Users are able to modify their ``PATH`` to exclude
or de-prioritise this directory, leading to unreliable or inconsistent
behaviour, and historically we have also seen this caused by installers.
For example, installing Python from the Store followed by Python from the
traditional installer with its ``PATH`` modification enabled will almost always
shadow the Store package's Python with the later install.

User expectations that are un-met by the Store package tend to be performance
and technical. Due to the overhead of launching an app, Python starts up slower.
Because apps are designed to be isolated from each other, it is more difficult
to use hidden directories (such as ``AppData`` or ``TEMP``) to communicate
between different versions of Python, as each version has its own space. Apps
are subject to stricter security requirements that legacy applications usually
have disabled, such as DLL hijacking protection, which causes some libraries to fail.
The ``python3`` and ``python`` shortcuts are managed through system settings,
and the user interface is not very good (and not going to be improved, according
to Microsoft). Without managing these, it is relatively easy for an undesired
version to be launched, though in general the targets can only be changed
manually by the user, and not by merely installing another app.

Both the NuGet package and the embeddable distro are as simple and reliable to
install as extracting an archive file, though it's worth noting that for many
Python users this is not a common task. They provide no install management at
all, and cannot be reliably updated other than by deleting and re-extracting.
User expectations that are un-met are almost always due to users selecting the
wrong installer. Both these packages are for specialised cases, and while they
are documented as such, the attraction of a plain ZIP file leads some users into
failure.

Overview of PyManager
=====================

PyManager is the internal name of our proposed replacement installer tool. It
will be distributed both in the Windows Store and on python.org as an MSIX
package. Downloading from either source will get an identical package, and
both will support automatic updates (through the Store) for new releases.

The user visible name will be "Python Install Manager", published by the
Python Software Foundation. After publishing, we will request that Microsoft
adjust their ``python.exe`` stub to open to this new app.

This app does not directly provide a version of Python, but it does provide the
global commands that users expect to work, as well as file associations and
Start menu shortcuts. The OS will prompt users to launch the app after install,
which will trigger an automatic install of the current release of CPython and
then launch it. From the user's perspective, they have the same initial
experience as today, with one added progress bar on first launch.

The global commands provided by the app must be static and bundled into the app
itself. They can only change their behaviour at runtime, and cannot be
redirected to different executables except by the user (and then only to another
installed app). So the commands to be provided by PyManager are ``python.exe``,
``python3.exe``, ``py.exe``, ``pymanager.exe``. Each of these must have the
ability to inspect the user's system and choose the correct runtime to launch.
Additionally, ``py`` and ``pymanager`` will have management subcommands to allow
adding and removing runtimes.

In line with :pep:`394` and the default behaviour of Windows, the recommended
command for launching Python is ``python.exe``. As provided by PyManager, this
will locate an existing install, either among those that PyManager manages or
using :pep:`514`, or it will install the latest available version of CPython and
select that. The ``python3.exe`` command behaves similarly, but is only allowed
to find 3.x installs from python.org.

The ``py.exe`` command provided by PyManager will be recommended for most
management use, due to its brevity. ``py install ...``, ``py list ...`` and so
on. The proposed commands are detailed later. The existing behaviour of the
:pep:`397` launcher is preserved, however, launching through ``py`` will not
automatically install runtimes (by default). If one is requested but is not
installed, users will just get an error. The ``py exec ...`` subcommand,
however, will install automatically, and supports the same options as bare
``py``.

These commands are added at very low priority in the user's ``PATH`` by the OS.
Every existing configuration we may have created on a user's machine will take
precedence over these commands, and so these are a last resort in place of an
error message. As a result, we can generally assume that a user is launching
these commands because they haven't configured a stronger preference (for
example, a user who has activated an environment will never launch our
``python.exe``, because activation will put a different one ahead of it, and a
user who wants precisely the behaviour of the existing ``py.exe`` can just
install it and will never launch our new one).

The ``pymanager.exe`` command is to allow for handling ambiguous situations.
Existing installs of Python and the launcher may shadow ``python.exe`` and
``py.exe``, but in an automated environment, this can make administrative
scripts unreliable, and so the ``pymanager`` command is unlikely to refer to
something other than PyManager. It has all the subcommands, and launching it
with no command specified will print help for the user.


Replacing other installers
--------------------------

Our intent is to immediately stop publishing individual versions to the Windows
Store, and to deprecate and phase out the traditional installer by Python 3.16.
The embeddable distro will remain, but its listing on python.org download pages
will be phased out and it will be available only through PyManager. No changes
will be made to the NuGet packages.

PyManager will be made available as an app package downloadable manually from
python.org, and the double-click install experience is generally smooth. This
provides an equivalent to the current approach of downloading from our site.
It will bundle a recent (unspecified) version of CPython so that the download
can be moved to a non-internet connected machine and still provide a Python
runtime after install.

Some automated deployment scenarios do not work with the newer MSIX format, and
so a simple MSI will also be provided on python.org. This will have no options
or user interface, and require administrative privileges, which are typically
available for these kinds of scenarios. Additionally, the MSI will be required
by users on non-standard systems, such as Wine, that do not support the MSIX
format. While not officially supported, the MSI will enable these platforms to
continue using upstream distributions of CPython. The MSI would be discouraged
for most other users, and the MSIX is considered the default.

It's worth noting that there is no way to make the MSI install fully compatible
with the MSIX, and users with both will likely encounter confusion or problems.
It is anticipated that only users without Store app support will use the MSI.

Our release processes will start publishing plain ZIP packages to python.org.
These will be available from the FTP pages, but will not be listed directly on
regular download pages.

Third-party tools that currently distribute their own builds of CPython will be
welcome to use ours, though will be expected to be the initial point of contact
for their users requiring support.


Project ownership and development
---------------------------------

PyManager will be developed and maintained in its own repository adjacent to
the CPython repository, and under the same terms. The CPython CLA will apply,
and all (and only) core developers will have commit rights.

PyManager releases are independent from CPython releases. There is no need for
versions to match, or releases to be simultaneous. Unless otherwise arranged,
the PyManager release manager is whoever is the build manager for Windows.


Specification
=============

.. note::
   In this document, all command line options will be shown with one or two
   hyphens. In implementation, all options will support one or two hyphens or a
   forward slash, to be permissive of both Windows and UNIX conventions.

Exec subcommand
---------------

.. code:: text

   py [-V:<TAG>] [interpreter opts] [script.py|-m module|-c code] [script args]
   py [-3.*] [interpreter opts] [script.py|-m module|-c code] [script args]
   python ...
   python3 ...
   pymanager exec -V:tag ...
   pymanager exec -3.* ...

This subcommand is used to select and launch a runtime. It is the default action
for the ``py`` command, and the only action supported by the ``python`` and
``python3`` commands. The default options are subtly different in each case for
consistency with existing use of these commands.

This subcommand is available on both ``py`` and ``pymanager``. However, since
``py`` offers it by default, we would not expect users to use it there. The
intent is that the ``py``, ``python`` and ``python3`` commands are the default
ways to launch a runtime, and ``pymanager exec`` is for advanced scenarios.

The ``-V:tag`` command is used to request a specific runtime from the command
line. The tag is a ``Company\\Tag`` pair, or just ``Tag`` if no slash is
included, and is used as defined by PEP 514. The ``-3.*`` option is interpreted
as ``-V:PythonCore\\3.*``. This option is only available for ``py`` and
``py exec`` variants.

If no tag is specified on the command line, and a script file is specified,
the script will be inspected for a shebang. If one is found that matches a
recognised pattern, it will either provide the tag to be used for search, or it
will override all other processing and its specified executable will be launched
without further effort being made. This is to handle the (unfortunate) legacy
support of arbitrary Windows-specific paths being allowed in what was meant to
be a portability feature. In general, shebangs including simple patterns like
``/usr/bin/python3.13`` are intended, while those that use ``/usr/bin/env
python`` are unlikely to be of benefit since the environment tends to be less
reliable than our search.

If no tag is yet requested, the ``VIRTUAL_ENV`` environment variable will be
consulted to see if an environment has been activated. If so, that will become
the request.

If a tag has been requested at this stage, the ``python3`` command will verify
that it matches ``PythonCore\\3.*`` and exit with an error if not. This allows
allows the ``python3`` command to be used in an active environment consistent
with other platforms, but not if the environment would not have included the
command. This applies to most existing versions of Python on Windows. (The
alternative to this behaviour is to make ``python3`` always error when an
environment is active, as anything else would behave inconsistently for the
user.)

If no tag is requested, the default will be consulted. For ``python3``, this is
``PythonCore\\3``, but for all other commands it is read from configuration
(which might involve an environment variable). If it's still empty, any tag will
be allowed.

The best installed runtime matching the tag is then selected and launched with
the remaining command line.

If no matching runtime is found, the ``py exec`` and ``pymanager exec`` commands
will automatically install and launch one (user configuration permitting). The
``py``, ``python`` and ``python3`` commands will report and error and exit.
However, in the case where no runtimes are available at all, including none
detected from other installers, the first runtime will be automatically
installed. This provides a useful first-launch experience, where a new user who
has just installed PyManager will directly launch the latest (or requested)
version of CPython. After this first time, errors will again be reported when a
requested runtime is not found.


Install subcommand
------------------

.. code:: text

   py install [-s|--source <URL>] [-f|--force] [-u|--upgrade] tag [...]
   py install [-s|--source <URL>] [-t|--target <DIR>] tag
   py install [-s|--source <URL>] [-d|--download <DIR>] tag [...]
   py install --refresh

.. note::
   This and all later subcommands are also available under ``pymanager``.
   However, as we intend for ``py`` to be the usual command, we only show that
   one.

This subcommand will install one or more runtimes onto the current machine.
The tags are ``Company\\Tag`` pairs (or just ``Tag`` if no slash is included),
and are used to search the index file. Company names match as case-insensitive
prefixes, preferring a full match over a prefix, and tags use case-insensitive,
number-aware matches, with dotted numbers treated as versions. Tags must match
one of the listed "install for" tags, and entries list multiple such tags to
handle abbreviated requests. The special tag ``default`` resolves to the user's
configured default (typically ``3``; see Configuration later for details on
configuring settings).

Tags may also be specified as a constraint, using ``>``, ``>=``, ``<``, ``<=``
or ``!=`` followed by the ``Company\\Tag`` or ``Tag`` value. When matching a
constraint only the primary tag metadata is used for comparisons. Since the
comparisons are version-aware, constraints such as ``>3.10`` will select
3.11 as a minimum, while ``>3.10.0`` may select 3.10.1.

The behaviour of constraints against arbitrary tags is likely to be unintuitive
in some circumstances. It is anticipated that constraints will mainly be used
with upstream releases, which typically use version-shaped tags, and primarily
for cases where other metadata such as ``Requires-Python`` are being handled.
Users are expected to use shorter tags for convenience, rather than ranges.

The default index file is hosted on python.org, and contains install information
including package URLs and hashes for all installable versions. An alternate
index may be specified by the user using a configuration file or the
``--source`` command line option, or their administrator through a configuration
file. The requested tag is matched against the index file, and if an exact match
is found the package will be selected. In the case of no exact match, a prefix
match will be used. In both cases, numbers in the tag are treated logically -
that is, ``3.1`` is a prefix of ``3.1.2`` but not of ``3.10``. See Index Schema
below for information on exactly how install tags are specified in the index
file.

If a tag is already satisfied by an existing install, nothing will be installed.
The user must pass an ``--upgrade`` or ``--force`` option to replace the
existing install; the former will only replace it with a newer version, while
the latter will remove and replace even with the same version.

Calling the command without providing any tags will not install anything, but
will display the help text and an error (as for any invalid option). However,
passing ``--upgrade`` with no tags will attempt to upgrade all installs.

Passing ``--refresh`` will regenerate all metadata and shortcuts for all
installs. This is intentionally applied to all installs at once, as shortcut
prioritisation relies on all installs being consistent (for example, the latest
3.x version should get the ``python3.exe`` shortcut, which gets complicated if
users can choose to only refresh an older install).

If a ``--target <DIR>`` option is passed with only a single tag, that runtime
will be extracted to the specified directory without being registered as an
install (or generating aliases or shortcuts). This is intended to cover
embedding cases, or downloading the files for incompatible platforms. Passing
multiple tags with ``--target`` is an error.

If the ``--download <DIR>`` option is passed, runtime packages are downloaded
but not installed. They are stored in the specified directory as their source
packages, and an ``index.json`` is created that references these files. This
index can be used later to perform offline installs with ``python install
--source <index.json> [tag ...]``.


Uninstall subcommand
--------------------

.. code:: text

   py uninstall [-y|--yes] [--purge] [tag ...]

This subcommand will uninstall one or more runtimes on the current machine. Tags
are exactly as for the install command, including prefix matching, but only
inspect existing installs. Unless the ``--yes`` option is passed, the user will
be prompted before uninstalling each runtime.

If the ``--purge`` option is passed with no tags, then (after confirmation) all
runtimes will be removed, along with shortcuts and any cached files. Passing any
tags with ``--purge`` will produce an error.

Uninstalling PyManager does not uninstall any runtimes that were installed. For
technical reasons, this would not be reliably possible (we cannot run arbitrary
code at uninstall time), and so instead we deliberately ensure that anything
that has been installed will continue to work. Reinstalling PyManager allows
management of these installs to resume. Running ``py uninstall --purge`` before
uninstalling PyManager will perform a complete uninstall.


List subcommand
---------------

.. code:: text

   py list [-f|--format <FMT>] [-1|--one] [--only-managed] [tag ...]
   py list [-f|--format <FMT>] [-1|--one] [--online] [--source <URL>] [tag ...]
   py [--list|--list-paths|-0|-0p]

This subcommand will list any or all installs matching the specified tags or
ranges. If no tags are provided, lists all installs. Runtimes not managed by
PyManager (including an active virtual environment) may be listed separately.

The default format is user-friendly. Other formats will include machine-readable
and single string formats (e.g. ``--format=prefix`` simply prints ``sys.prefix``
on a line by itself). The exact list of formats is left to the implementation.

If ``--one`` is provided, only the best result is listed. This is to assist
shell scripts that want to locate the default (or a suitable) runtime without
launching it. (Note that "best" is loosely defined, but will always be the
user's preferred default environment if it is included in the results.)

The ``--only-managed`` option omits runtimes that were discovered but are not
managed by PyManager, for example, those found using a regular PEP 514 lookup.

Passing ``--source`` (or ``--online`` to implicitly pass the default source)
will search an online index rather than currently installed runtimes. The option
is here rather than on the ``install`` subcommand because the filtering and
formatting options are already available on ``list``.

The legacy ``--list``, ``--list-paths``, ``-0`` and ``-0p`` arguments from the
``py.exe`` launcher will also be provided. However, they will not support the
new options listed here, and are limited to reproducing the output from the
existing launcher. Unmanaged installs are not distinguishable in this listing.


Help subcommand
---------------

.. code:: text

   py help <COMMAND>

This subcommand will display the help text for each specified command, or if
none specified, will show the list of commands. Specifying one command is the
equivalent of ``py <COMMAND> --help``. Showing the list of subcommands is the
default action for the ``pymanager`` command.

The command is added primarily to offer a simple way to tell users how to find
more information: they can be told to run ``py help``. This avoids having to
override or extend the ``python -?`` output, which otherwise forwards to the
selected runtime and already prints at least one screen's worth of text.

After an automatic install (e.g. running ``python`` with nothing installed), a
message will be displayed telling users that they can run ``py help`` for more
information on how to manage their installs.


Environment Variables
---------------------

No environment variables can be updated automatically when installing a Store
app, and so no updates will be done automatically. The core commands should
already be available on a correctly functioning machine.

One directory within the user's PyManager data directory is set aside for
generated aliases. If desired, the user can add this directory to their ``PATH``
themselves. The contents of this directory will be managed by PyManager, and
will contain executables to directly launch installed runtimes (for example,
``python3.exe`` and ``python3.13.exe`` for an install of Python 3.13). Whenever
aliases are added to this directory, ``PATH`` will be checked and if it is
missing, the user will be presented a message containing the path to add.

Scripts installed by packages installed into a runtime will be in yet another
directory. Due to the current design, we do not believe it is safe to have them
all install into a single directory, or a directory shared by multiple runtimes.
However, a future development may include a command for PyManager to generate
its own entry points based on metadata in installed packages.


Start Menu Shortcuts
--------------------

A Start Menu shortcut will be added to launch PyManager documentation in the
user's default web browser. No applications are added to the Start Menu.

When installing Python runtimes, the install definition may specify Start Menu
shortcuts to create for the install.


File Associations
-----------------

Standard file associations will be created when installing PyManager, and will
launch scripts and packaged apps with PyManager's global ``python.exe`` alias.
This provides sensible behaviour for users who are double-clicking on scripts or
``.pyz`` files.


Windowed Executables
--------------------

For each of the global aliases described earlier, a ``*w.exe`` also exists.
These launch without creating or attaching a console window, which typically
means they will only display UI created by the script. For example, IDLE always
launches using ``pythonw.exe``, as this avoids an unnecessary native console.

These commands otherwise behave identically to their console counterparts.


Configuration
-------------

PyManager is configured using a hierarchy of JSON-based configuration files.
Command-line options always override configuration file options. Configuration
files in user editable locations may be disabled by a configuration or
command-line option.

In ascending order of priority, these will be located:

* within the app package
* specified by admin-only configuration (see below)
* in the ``base_config`` setting (default: none)
* in the ``user_config`` setting (default: ``%AppData%\\Python\\PyManager.json``)
* in the ``additional_config`` setting (default: ``%PYTHON_MANAGER_CONFIG%``)
* specified with the ``-c`` command line option

The specific behaviour of each configuration option is left to implementation.
However, a number of intended options are discussed in other sections.

App package configuration is provided to allow PyManager to be embedded in other
applications or packages. For example, an alternative distribution may want to
include PyManager but have it locate installs from their own index. The app
package configuration allows reusing our build and overriding the default
settings.

The ``user_config`` and ``additional_config`` settings are pre-configured in
earlier configuration files, allowing them to be overridden by admin-only
configuration or an alternate root configuration. If a configuration file
overwrites the setting that caused the file to be loaded, it is ignored.
The ``base_config`` setting is similar, but starts empty and is intended for
easy overriding through admin configuration.

Admin-only configuration is provided to allow administrators to manage systems
under their control using existing tools, such as group policy or registry
updates. By design, these controls cannot be overridden, such that it is
possible for administrators to deploy policy that prevents or limits the use of
PyManager. These controls are essential to allow PyManager to be deployed safely
into certain environments, and without them, it would simply be disallowed and
those users would have no access to Python.

The intent is for the main admin-only configuration to be a path to a new
``base_config`` configuration file that an administrator can deploy to any
controlled location. This allows a network administrator to control the source
of their users' default Python runtimes, without forcibly restricting them, or
to override the other sources for configuration files (apart from the command
line option).


Index Schema
------------

The index file is made available either online or locally, and provides
PyManager with all the information needed to find, select, install, and manage
any Python runtime.

The index is stored as JSON. The main top level key is ``versions``, which
contains a list of objects. Each version object has its own schema version, and
there is no overall file schema version. Future changes may add additional
top-level keys to provide functionality that cannot be safely integrated into
an existing one.

Version objects may be split between the index file and a ``__install__.json``
stored in the root of each package archive. The entries in the bundled file will
fill in any gaps from the index file. This is intended to allow the typically
large ``shortcuts`` key to be removed from the index file, but may also extend
to ``alias``, ``executable`` and ``executable_args``. Omitting other keys from
the index may result in problems installing the package. Ensuring correct
behaviour is left to the implementation - this is not an interoperability point,
and so we do not intend to specify the details here (beyond the normal compatibility
requirements that apply).

A second top-level key ``next`` contains an optional URL to another index. This
may be used if PyManager cannot find a suitable package in the included
versions. The intent is to allow for older indexes to be archived and only
accessed when required, reducing the size of the initial download without
cutting off users from older versions. When searching for a suitable install,
later indexes will not be searched if a viable candidate is found (in other
words, the first index consulted should have the latest versions in it).

The initial schema is shown below:

.. code:: python

   SCHEMA = {
       "versions": [
           {
               # Should be 1.
               "schema": int,

               # Unique ID used for install detection/side-by-side.
               # Must be valid as a filename.
               "id": str,

               # Name to display in the UI
               "display-name": str,

               # Version used to sort packages. Also determines prerelease status.
               # Should follow Python's format, but is only compared among releases
               # with the same Company.
               "sort-version": Version,

               # Specifies platforms to consider this package for.
               # Initially, 'win32' is the only supported value. Others may be
               # defined in the future. This condition is evaluated silently, and
               # is not intended to replace platform requests in "install-for".
               "platform": [str],

               # Company field, used for filtering and displayed as the publisher.
               "company": str,

               # Default tag, mainly for UI purposes.
               # It should also be specified in 'install-for' and 'run-for'.
               "tag": str,

               # List of tags to install this package for. This does not have to be
               # unique across all installs; the first match will be selected.
               # For example, the 3.10.5 package may list '3', '3.10' and
               # '3.10.5' so that any of those may be specified to install it.
               # Matches are number aware, so that 3.1 is not a prefix of 3.10.
               "install-for": [str],

               # List of tags to run this package for. Does not have to be unique
               # across all installs; the first match will be selected. The target
               # is the executable path relative to the root of the archive.
               # Explicit args (optional) are inserted before user args.
               "run-for": [{"tag": str, "target": str, "args": [str], "windowed": int}, ...],

               # List of global CLI aliases to create for this package. Does not
               # have to be unique across all installs; the first match will be
               # created.
               "alias": [{"name": str, "target": str, "windowed": int}, ...],

               # List of shortcuts to create for this package. Additional keys on
               # each instance are allowed based on the value of 'kind'.
               # Initially, 'kind' supports the following values:
               # * 'pep514' - other keys define registry values to set
               # * 'start' - generate shortcuts in the user's Start Menu
               # * 'uninstall' - generate an Add/Remove Programs entry
               "shortcuts": [{"kind": str, ...}, ...]

               # Default executable path, relative to the root of the archive.
               # Usually the values from 'run-for' will be used instead, and this
               # is mainly for display purposes.
               "executable": str,
               # Default executable args
               "executable_args": [str],

               # URL to download the package archive from
               "url": str,

               # Optional set of hashes to validate the download. Hashes are stored
               # as hex digests. Any hash supported by hashlib without OpenSSL is
               # permitted.
               "hash": {
                   "<hash_name>": str,
               }
           }
       ],

       # URL (or relative path) to the next index file
       "next": str,
   }


Shebang Processing
------------------

For limited compatibility with scripts designed for sh-like shells, PyManager
will check scripts for a shebang line. A shebang line specifying a Python
command will be used (when not overridden on the command line) to select a
suitable runtime for the script.

Unlike the support currently in the ``py.exe`` launcher, we propose to reduce
this functionality to only support Python commands where the command matches
a global alias listed for an install, with anything not matching being treated
as an arbitrary executable path. In practice, this is expected to produce the
same (or better) results for non-contrived cases, but may result in subtle
changes where users are relying on unspecified edge case behaviours of the
existing launcher.

The specific patterns to be detected are left to the implementation, but should
be largely compatible with the existing launcher.


Rationale
=========

"Changing" the python.exe command
---------------------------------

It may be argued that the global ``python.exe`` alias provided by PyManager is
"not real Python" and so should use a different name. While this is strictly
true, there are three reasons we argue that it should be used.

Firstly, thousands of users *daily* install through the Store page after being
led there by having typed ``python`` at the terminal of a clean machine. Due to
how this redirection is implemented, if the app they install does not provide a
``python`` command, then the redirection will remain in place. In order to
ensure that users do not get stuck always going back to the Store, we need to
provide this command. (The same applies to ``python3``.)

Second, the alternative to the "not real" alias is not "the real" one. It's
nothing. We don't have the ability to replace the global static alias with one
that follows the user's preference or installs, and so the alternative would be
to provide nothing and have ``python`` be an error in all cases. This is worse,
and in our opinion, actively harmful to Python's reputation.

Third, although the underlying implementation of the ``python`` alias is more
complex than the default ``Programs/python.c``, the experience of using it is
identical. The alias is only launched in the absence of another expressed
preference (that is, there's nothing else on ``PATH``), it respects any
indirect preferences (such as through configuration or shebangs), and it
launches the most appropriate version of Python available on the user's machine.
This is much closer to the desired behaviour of the global ``python`` command
than any alternative.

It has been noted that Gentoo also distributes an intelligent ``python``
command, in order to serve their users better than a simple symlink.


Replacing py.exe
----------------

The ``py.exe`` launcher exists to provide some of the functionality that will be
replicated by PyManager - specifically, the ability to launch an already
installed runtime. Despite its long history, the launcher does not seem to have
become the preferred method for most users, with many preferring the global
modifications to the ``PATH`` environment variable. However, the command itself
has come to be relied upon, and should be preserved as long as possible. This is
achieved in two ways.

Firstly, we install our own ``py.exe`` alias with PyManager that provides the
same functionality, along with PyManager specific functionality. This is
intended to become the default/preferred install of ``py.exe`` over time.

Second, we generate PEP 514 metadata (when requested) for each install, which
allows a legacy ``py.exe`` to continue to work normally with installs managed by
PyManager.

Due to how the existing ``py.exe`` launcher configures itself, and how the MSIX
package for PyManager is constrained, it is not possible for PyManager's ``py``
alias to override the launcher. As a result, users who install the launcher will
always find ``py`` resolving to the launcher. Ultimately, the only way to
resolve this in favour of PyManager is to uninstall the launcher, which can be
done through the standard Installed Apps control panel.

We propose to update the existing launcher, which will continue to be released
with the traditional installer, to detect attempted use of the new subcommands
and provide a useful message for the user rather than the current error. These
warnings can also detect the unlikely case where a user is intentionally
launching extensionless files by those names and retain that behaviour, allowing
a viable alternative for users who cannot make other changes to their setup.


Interaction with venv
---------------------

An activated virtual environment, as implemented by the standard library
:mod:`venv` module, will modify the user's ``PATH`` environment variable to ensure
that the venv launcher will take precedence over other executables. As a result,
when a venv has been activated, PyManager can only be launched by its aliases
other than ``python``. When an active virtual environment is detected, it will
be treated as the user's default runtime (except for uninstall), which ensures
that other commands will also behave as expected.

This means that working virtual environments will behave as they do today with
no additional support from PyManager.



Backwards Compatibility
=======================

In general, there are no compatibility guarantees to the install process between
minor versions (``3.x`` to ``3.y``), and so "having to use a different
installer" is not considered compatibility breakage. The versions of Python
installed are only impacted by this change to the extent that the install method
modified their behaviour. In general, most installs will be closer to the
behaviour of having been built from source by the user themselves.

That said, there are a number of changes that will impact certain users when
they do move to a new install process. This section outlines as many of these
changes as we are aware of, in no particular order, and will likely form the
basis of a migration guide.


Scripted downloads
------------------

Users who wrote scripts to generate the download filename of our old installer
will find those scripts are broken. These URLs were never guaranteed stable or
predictable, and so we have no recourse to do anything other than apologise and
suggest users use our own tooling for downloads.

The deprecation period for the traditional installer allows time for these users
to learn about the upcoming change. Where possible, we will add deprecation
warnings to the traditional installer.


Scripted installs
-----------------

Users who wrote scripts to execute our installer with particular options will
have to change their script. Most options have been removed, to begin with, and
those that remain have new spelling. Since it is not possible to reach a state
where options for the old installer are being passed to the new without manual
intervention (that is, someone has to change the command already), this is
considered an acceptable change.

The deprecation period for the traditional installer allows time for these users
to learn about the upcoming change. Where possible, we will add deprecation
warnings to the traditional installer.


Old runtime installed
---------------------

Users with existing runtimes installed will find them selected by PyManager and
its aliases, provided the registration is not corrupt.

The priority order among installed runtimes has changed to only include
prerelease versions when specifically requested (for example, ``-V:3`` will
match 3.14.0 rather than 3.15.0a1, but ``-V:3.15`` will match 3.15.0a1), and to
correctly sort text suffixes on tags (for example, 3.14t is now *lower*
priority than 3.14).

While it is possible to provide warnings in cases where this may be impacting a
user, such warnings would be considered very noisy (e.g. a message every time
you launch ``python`` because you have a prerelease installed that wasn't
selected) and require complicating the selection logic unnecessarily. This
change will be documented only.


Old ``py.exe`` launcher installed
---------------------------------

Users who do not manually uninstall an old ``py.exe`` launcher will find that
both their existing and new installs of Python are found, though where versions
match the existing install will take priority over the new install (whereas the
new ``py`` would select the new install).

They will also find that commands such as ``py list`` do not work. The solution
here is to use Windows Settings to uninstall the launcher.

There is no way to detect that a user has accidentally left an old ``py``
installed, or to remove it for them. This change will be documented only.


Misconfigured venv activated
----------------------------

Users who activate a corrupt or misconfigured virtual environment that is either
missing its ``python.exe`` or has it not on ``PATH`` may receive a different
error from before.

PyManager's global ``python`` alias will be found and executed instead,
suppressing any system "not found" error. As it fails to find the environment's
actual runtime, it will then fail, though the code and message may be different.

As this scenario requires an already corrupt system, this change will be
documented only.


Old version availability
------------------------

Python versions prior to the first release of PyManager can be backfilled into
the python.org index, either based on newly repackaged archives or using the
almost equivalent packages from NuGet (the latter does not include Tcl/Tk,
making them significantly incompatible for some users, but this is likely okay
for especially old versions).

As of this PEP's acceptance, the plan is to create packages for all versions of
Python 3.10.0 onwards (including patch releases, but excluding pre-releases), so that all non-EOL releases can
be fully installed using PyManager. Versions from Python 3.5.0 onwards (excluding
pre-releases) will directly reference the packages on NuGet, making them easily
available in reduced form. Neither approach requires rebuilding the existing
releases, nor are any changes required to the sources of those releases.


Administrator installs
----------------------

Installing a copy of Python for all users is no longer possible, as PyManager
will only install into the user's own directory. No scenario has been presented
to show that per-machine installs are in line with our intent for the upstream
distribution, and so we will simply not provide an option for them. Third
parties who desire this functionality are encouraged to provide their own
distributions.

PyManager can only be installed for all users, though an MSIX does not require
administrative privileges to install, and can be extensively configured by an
administrator, including to constrain the actual runtimes which users may
install. Additionally, PyManager supports local extraction for bundling, and so
embedding apps can easily generate their own layout, which can be installed for
all users if they so desire.

As this scenario requires administrator intervention with or without any
changes, this will be documented only.


Build-time installs
-------------------

Users using the embeddable distro may have to change to a new method for
discovering the URL to the packages, though the recommendation would be to use
PyManager to discover and install. No differences are anticipated due to the
change of installer, and the embeddable distro package would be identical to
today.

No changes to the NuGet packages are proposed.


Single architecture installer
-----------------------------

The current proposal only makes an Intel 64-bit build of PyManager available.
This will prevent users on 32-bit only operating systems or CPUs from being able
to install PyManager. As this is a vanishingly small proportion of machines
today, and an even smaller proportion of developer machines (the target audience
for PyManager), we are not concerned about excluding users.

Windows ARM64 machines support running binaries built for Intel 64-bit through
efficient emulation. CPython 32-bit builds will still be available as they have
an important role in integrating with 32-bit executables, which does not allow
substitution of 64-bit binaries. As PyManager runs as a standalone executable,
this is not a necessary feature for the manager.


Test suite and debug symbols
----------------------------

The existing installer optionally allows installation of the Python standard
library test suite, and optional installation of debug symbols. Neither of these
are necessary for most users, but they are convenient for some. Preliminary
testing shows that omitting the test suite and debug symbols saves about 60% of
the size of the compressed package (from 46MB to 18MB).

The "default" CPython packages installed by PyManager will therefore not include
the test suite or the debug symbols. However, there will be a second set of
packages that do include these extras, grouped under ``PythonTest`` (as opposed
to the default, ``PythonCore``). For example, where ``py install 3.13`` would
install the default, ``py install PythonTest\\3.13`` would install a second
runtime with the additional files (which can either be launched with ``py
-V:PythonTest\\3.13``, or simply ``py -V:3.13`` if no equivalent ``PythonCore``
version is installed).

Debug binaries are no longer distributed, and all other optional features are
included by default.


Global pip command
------------------

Unlike the current Windows Store install, no global ``pip`` command is included
(the traditional installer also does not include a global ``pip`` command,
unless the options to modify ``PATH`` and to install pip are selected; the first
of these is off by default). This impacts global installation of packages, which
is already discouraged, but has no impact on activated virtual environments.

The existing recommendation remains, which is to run ``python -m pip`` or
``py -V:<TAG> -m pip`` to launch pip.


Security Implications
=====================

In this section we compare the security implications of the installer itself to
the existing installers. The implications of Python being installed on a machine
are out of scope, and the ability of a malicious user to execute the installer
is also out of scope.

The typical risk introduced by an installer is that an elevated install may make
changes to a system that allow a low-privileged user to later affect a
high-privilege user, for example, by inappropriately setting access control on
shared folders. PyManager only operates as the same privilege level as the user,
and therefore cannot introduce any escalation path.

An install using the MSI described earlier may introduce additional risks, due
to using older installer technology. Typical users are directed towards the MSIX
or Windows Store install paths, which are safe, and it is assumed that users
of the MSI are capable of ensuring the security of their install process (for
example, by correctly quoting their commands to launch the installer and
ensuring the initial system configuration is suitable).

Once PyManager is installed on a machine, it is likely that malicious users will
use it to install Python. The admin-only configuration described earlier in
"Configuration" is intended to control these scenarios. Ultimately though, an
attacker who can run PyManager is able to do whatever the user can do, and only
a complete application whitelisting approach can prevent the use of Python.

Acquisition of packages over HTTPS is protected by the connection security. We
use native Windows download mechanisms that support public and enterprise
certificates, as well as authenticated proxy servers. The feed may include
hashes for downloadable packages, which will be verified, but there is no
third-party hosted verification built into PyManager. This is consistent with
today's model.

Runtime installs by PyManager are fully accessible by, and modifiable by, the
current user. This is equivalent to typical installs using the traditional
installer or a NuGet package, but is more vulnerable to tampering than a Store
install or a per-machine install with the traditional installer. It is not
possible to fully protect an install from the user who installed it.
Reinstalling or updating an install performs a clean install, which will revert
any tampering that may have occurred since the original install.

The aliases generated by PyManager when installing a runtime are designed to
use a signed, unmodified executable that uses an adjacent data file to launch
the correct target. This can be easily abused to direct the launcher to launch
an alternative, however, the only way to resolve this would sacrifice the trust
in the executable itself, making it trivial to replace it instead of the data.
Such risk already exists, and is equivalent to replacing the script that a user
may launch, or any part of the standard library. Importantly, since aliases are
not shared between users, there is no escalation of privilege along this route.

PyManager has no mechanism to perform a per-machine install. This may be useful
functionality to some users, as it would allow an install to be completely
unmodifiable by the regular user (excluding virtual environments and the user
site folders). Such functionality may be manually imitated by an administrator
using PyManager and other OS commands, but it is not considered a critical
workflow. The recommended alternative is for an administrator to provide
PyManager and override its configuration.


Impact on Existing PEPs
=======================

This proposal would effectively replace :pep:`397` ("Python launcher for Windows")
and :pep:`486` ("Make the Python Launcher aware of virtual environments") by
defining the same functionality as part of a new tool with the same name. Both
are already considered final, and the launcher is defined by its documentation
and normal compatibility processes. New functionality is based on the current
implementation, and not the original PEP text.

This proposal has no impact on :pep:`394` ("The “python” Command on Unix-Like
Systems"), and is believed to be consistent with it in devising an approach for
Windows that allows similar guidance to be given to users on all platforms.

This proposal has no impact on :pep:`514` ("Python registration in the Windows
registry"), and in fact improves our ability to follow it with a more flexible
system for registering our own runtimes. Tools that follow PEP 514 will find any
runtimes that choose to use the registration, regardless of how they were
installed.


How to Teach This
=================

Basic Use
---------

A central goal of this proposal is that "type 'python' in your terminal" will be
sufficient instruction for the most basic cases. Thanks to the redirector added
by Microsoft, following this instruction will at least result in something
useful happening, and with PyManager we can ensure that "something useful" means
that the user is running the latest version.

To explain what is actually happening, we propose the following as introductory
text:

.. code:: text

   Python installs on Windows are managed using an installer tool. After it has
   been installed, you can run ``python`` to launch the interpreter, and it will
   choose the best version already installed, available online, or referenced by
   the script you are launching (if any). If you have a preference for a
   particular version, you can specify it with ``py -V:<version>`` followed
   by the rest of your command.

   To install a version of Python without running any command, use ``py install
   <version>``. You can see all of your installs with ``py list`` and remove them
   with ``py uninstall <version>``. Run ``py help`` to see all the options that
   are available.

   Because each version of Python will be shared by all your projects, we
   recommend using virtual environments. This will usually be created for a
   particular Python version by running ``py -V:<version> -m venv .venv``, and
   activated with ``.venv\Scripts\Activate``. Now, rather than the install
   manager, ``python`` or ``py`` will always launch your virtual environment, and
   any packages you install are only available while this environment is active.
   To get access to the manager again, you can ``deactivate`` the environment, or
   use ``py <command>``.

Many Python projects include information about how to launch their projects as
part of their own README files. Historically, such information has been
complicated due to the range of options available to users. We propose that,
after the install manager is published, such guidance could be written along
these lines:

.. code:: text

   To install and use our application, first install Python following the
   guidance for your operating system at https://docs.python.org/using/. Then,
   create a virtual environment and use 'pip' to install.

   ``python3 -m venv .venv``
   ``source .venv/bin/activate`` or ``.venv\Scripts\Activate`` (on Windows)
   ``python -m pip install OurAwesomePackage``
   ...

If instructions will not include information about virtual environments, then
the ``python`` or ``python3`` command can be shown, and on Windows both will
operate as intended for users with the install manager.

Instructions currently referring to ``py`` for Windows can continue to do so, as
the install manager provides a practically equivalent command. Projects that
wish to provide Windows-specific instructions, such as by using the ``-V:``
or ``--install`` options to install the correct version, should also link to the
:ref:`documentation <using-on-windows>` as guidance for
ensuring that the install manager is installed.


Uninstallation
--------------

Complete uninstallation is an important topic to cover before a user is likely
to consider removing the install manager. Since not all parts of installs can be
automatically cleaned up when removing the manager, we choose not to remove most
of them. So while the default ``python`` and ``py`` commands will go away, all
the runtimes that were installed are still present and usable.

We suggest an explanation like this:

.. code:: text

   Before you uninstall the Python install manager, you'll want to uninstall any
   runtimes that you added. This can be done easily with the "purge" option:

   ``py uninstall --purge``

   This will remove all installs and any shortcuts that would otherwise be left
   behind. If you already removed the manager, you can reinstall it and run the
   above uninstall command again to clean up. Individual runtimes can be
   uninstalled by specifying the tag instead of ``--purge``. Tags can be found
   by looking at ``python list``.


Configuration
-------------

Configuration files are a common feature that will be documented, but do not
need to be taught to regular users. Similarly, advanced deployment options, such
as those that might be used by system administrators or organisations wanting
their users to use a preferred index, are best covered in reference material.


Custom Index
------------

We suggest that indexes only need to be introduced when instructing users to
install a specialised runtime or distribution. Administrators seeking to provide
an index are anticipated to actively seek out the relevant information in the
documentation.

To explain how and when to use an alternate index, we propose text along these
lines:

.. code:: text

   Our distribution can be installed on Windows using the Python Install Manager
   (include link) by referencing our index:

   ``py install --source <your index URL here> latest``

   This index contains all our versions. Use ``py list --source <URL>`` to
   see everything that is available.


Reference Implementation
========================

The reference implementation is available at `the author's repository
<https://github.com/zooba/pymanager/>`_
with a precompiled MSIX package under `Releases
<https://github.com/zooba/pymanager/releases>`_. This sample includes a bundled
index, rather than a hosted one, and references a range of existing NuGet
packages to allow install testing.

Reference documentation can also be found in `the same repository
<https://github.com/zooba/pymanager/blob/main/doc/using.rst>`_.


Rejected Ideas
==============

Make PyManager available on all platforms
-----------------------------------------

While we are not inherently opposed to this idea, it relies on many more
components being aligned before it can become possible.

Firstly, as it stands, the reference implementation has a lot of
Windows-specific knowledge. Equivalent knowledge for other platforms would need
to be collated and implemented, as well as any additional behaviours specific to
non-Windows platforms.

Second, we need a source of pre-built, relocatable binaries that can be
extracted onto the system. While such sources do exist, due to our position in
the supply chain, we cannot justifiably use them (they should be using us). For
Windows, our own binaries already meet these criteria, so we can repackage them
without modification.

Third, the current implementation relies on a bundled Python runtime, which must
be isolated from any user interference for obvious reasons. This would also
require the relocatable binaries mentioned above, which we currently only have
for Windows.

Due to the additional steps needed to make this functional on other platforms,
and the fact that there isn't a need to replace existing installers for those
platforms, we consider this idea out of scope for this PEP. It may be pursued in
the future (and the contributors most likely to do so have indicated that they
are looking into it and would be able to use a consistent interface).


Include a runtime pre-installed with the manager
------------------------------------------------

The proposal is to have a full Python runtime included with PyManager, so that
its ``python.exe`` alias can refer directly to it rather than resolving to the
best available version dynamically.

It is very important for stability and updates that runtime releases are
fully independent of the manager. Updating the manager should be possible
without affecting any existing runtime installs, and likewise there should be
no requirement to update the manager to get a newer runtime.

Hypothetically, if we were to include Python 3.14.0 with the manager such that
it did not need to be installed, it would be a breaking change to later replace
that with 3.15.0. As we only have a single install for the manager, this would
result in the newest installs getting the oldest runtime.

This would also ignore the status of PyManager's ``python`` alias as being of an
unspecified version - when the user is launching this alias, it's because they
didn't care what version they get enough to specify one. In that situation, we
ought to select the best available, and allow them the options to stabilise it
as is appropriate (whether through a shebang or an active environment).


Include a runtime INSTEAD OF the manager
----------------------------------------

This is the current situation, which we are trying to change. If you read this
far and still chose to make this argument, please go back and start again.


Use a built-in module rather than subcommands
---------------------------------------------

Two alternatives to using commands like ``py list`` or ``py install`` that have
been proposed are to use either dedicated modules, invoked like ``py -m list``
and ``py -m install``, or a single dedicated module invoked like ``py -m manage
list``. This idea is rejected on the basis that it attempts to reuse existing
semantics for a scenario that cannot be reliably implemented by those semantics,
and so will require a special case that is harder to explain, understand, and
maintain.

The main reason this idea is rejected is due to the interaction of two otherwise
desirable semantics: first, that the default ``py`` command should launch the
latest available runtime as if it were launched directly; and second, that the
behaviour of ``-m`` should not be treated as a special case in some
circumstances. If the first part were dropped, we would freely modify the
command to behave as users expect - nobody would be raising compatibility
concerns at all if we were agreed to completely break compatibility. However, if
the second constraint were dropped, users would bear the burden of the ensuring
confusion. (We aren't proposing dropping either - this is a rejected idea, after
all - but it helps to illustrate what the options are.)

First, since one of the subcommands is intended to install your first runtime,
we cannot treat ``python -m [manage] install`` as if it is running through the
default runtime - there isn't one! It inherently requires special case handling
in order to read the command and execute it through a different program.

Additionally, Python allows other options to precede or mingle with the ``-m``,
which would have to be supported by this special case.

Finally, the semantics of the ``-m`` option include searching the initial
``sys.path`` for matching module names. This is a considerably more broad search
than a bare name. ``py -m install`` would gladly execute ``install.py``,
``install.pyc``, ``install.pyd``, ``install\\__init__.py``, and more after
searching a number of directories found by inspecting the file system, the
environment, the registry, as well as any transitively included paths found in
those. Compared to ``py install``, which would *only* look for a file called
precisely ``install`` in the current working directory, the ``-m`` behaviour is
far more likely to be already relied upon by real scenarios. (For example,
Django projects typically have a ``manage.py`` script, meaning that ``py -m
manage`` would always behave incorrectly.)

Changing ``py -m install`` to *not* behave like ``-m``, but instead to execute
an internal command, is vastly more likely to break users than changing
``py install``. As such, this idea is rejected.


Use a new command-line option rather than subcommands
-----------------------------------------------------

A reasonable alternative to subcommands is to specify their names with leading
punctuation, like an option rather than a subcommand. For example, this may look
like ``py /install ...`` rather than ``py install``, or ``py --list``. Because
some of these are currently errors for a normal CPython interpreter, they
could be added without any backwards compatibility concern.

Notably, however, the typical Windows format of a leading slash is not an error
in CPython. Windows users therefore cannot directly transfer existing knowledge
and must learn a new way to specify options. As we are proposing a Windows
specific tool, this is a terrible start. Additionally, those users familiar with
Unix-style command lines will recognise the misuse of options as commands.

We desire to create a clean interface, and starting with a design that includes
obvious warts or learning challenges is counter to that goal. Modern tools
universally use subcommands for these purposes, and so the idea to use something
different is rejected.


Improving the current traditional installer instead
---------------------------------------------------

Rather than creating a new install mechanism, we could invest in maintaining the
current installer. At this stage, however, our current installer is based
entirely on retired technology. Windows is no longer developing the Windows
Installer service, and Wix are no longer improving the version of their toolset
that we use. Migrating to a newer Wix Toolset is a significant amount of work,
and ultimately still leaves us tied to old technologies.

As mentioned earlier, the most beneficial functionality provided by Windows
Installer is not used for CPython, and generally has caused more issues than it
has ever solved (for example, accidental downgrades due to automatically
collected file version information).

The implementation of the Burn bundle, which is our primary source of installer
logic, is in C++ and integrated into a framework that few core developers are
familiar with. This makes maintenance challenging, and is not a good long term
position to take. Migrating desired features such as registration-free installs
into the Burn bundle is not possible (without writing the end-to-end
reimplementation and integrating it as an afterthought).

Our view is that maintaining the current traditional installer is at least as
much effort as implementing a new installer, and would not provide meaningful
benefits for the core team or for our users. As such, this idea is rejected.


Delete the Store package completely
-----------------------------------

Removing the Store packages would reduce the number of options users face when
choosing a Python runtime. By all measures apart from reliability and security,
the traditional installer is entirely sufficient as a substitute. The effort to
migrate parts of the ecosystem to more secure settings (such as not relying on
DLL hijacking) has largely occurred, but some packages remain that still only
work with less secure configurations, and moving all users back to these
configurations would ensure that users of these packages would not face the
issues they face today.

However, the majority of users of the Store packages appear to have no
complaints. Anecdotally, they are often fully satisfied by the Store install,
and particularly appreciate the ease and reliability of installation. (And on a
personal note, this author has been using Store packages exclusively since
Python 3.8 with no blocking issues.)

The greatest number of issues have been caused by misconfigured ``PATH``
variables and the default ``python.exe`` redirector installed by Microsoft. In
other words, entirely unrelated to our own package (though sometimes related to
unresolvable issues in our other installer). For the sake of the high number of
successful installs through this path, we consider the burden of diagnosing and
assisting impacted users to be worthwhile, and consider the idea to simply drop
the Store package rejected.

That said, when PyManager is published to the Store, we would plan to delist all
existing runtimes on the Store to ensure users find the manager. This only
impacts new installs, and anyone who has previously installed a particular
version (even on another machine, if they were logged in) will be able to
continue to use and install those versions.


Rely on WinGet or equivalents
-----------------------------

WinGet, Chocolatey, and other similar tools are not installers in the sense that
we require. They use their own repository of metadata to download, validate, and
run installers. Without our own installer, they have nothing to run, and so
cannot be used.

It is possible that their metadata will not support installing PyManager and
then running it to install a particular runtime. If this is the case, they may
need to investigate using our binary packages directly.

Currently, none of these install tools are officially supported by CPython, and
so we have no obligation to make them work.


Make every version a Windows Store package
------------------------------------------

It is possible to release each version to the Windows Store as we currently do,
but make them unlisted and rely on an installer (potentially PyManager, WinGet,
or another tool that can install Store packages). This would avoid the risk of
overwhelming the user, while greatly simplifying our own reponsibilities for
package management.

This approach would leave a significant burden on whichever contributor has
access to the Store publishing interface, as updating packages is a manual
operation. Additionally, it would leave every Python runtime with the technical
limitations outlined earlier. As such, this idea is rejected.

Making every version a MSIX package rather than a ZIP, even though this avoids
the Store publishing interface, would still impose technical limitations on
users. It is also rejected.


Just publish the plain ZIP file
-------------------------------

Publishing the plain ZIP file is part of the plan, however, it will not be
visibly listed (for example, on the python.org download pages, though they will
be visible in the FTP view). An alternative would be to publish and list these
packages, and expect users to download and manually extract and configure them.

Given the workflows we see, we believe that most users do not want to configure
a Python install at all. Not only do they not want to choose the install
location, they do not want to choose a version, or even have to search for a
download provider or instructions. However, they do want to be able to find an
install later, launch, update or remove it, or list all known installs.

It is also worth recognising that there will be more ZIP files than are
currently listed on the Download pages, and so the list of files will become
longer. Choosing the correct download is already challenging for users (those
who bypass the primary "Download" button and view the list of all available
versions and then files), and we have no desire to make it more challenging.

The index protocol and download list will be available for tools that wish to
use it, or for users who are willing to navigate JSON in order to find the URL.
The ``--target`` option on the install command also provides a trivial download
and extract operation, allowing users to have the same experience as a ZIP file.
And the ``--download`` option can give users the ZIP file still zipped.


Only publish PyManager to one place
-----------------------------------

Whether the Windows Store or python.org, it would be viable to publish to only
one location.

However, users strongly expect to be able to download *something* from
python.org. If we were to remove any option at all, we would inevitably hurt our
users. Without an MSIX available on python.org, users have no way to transfer
the package to another machine, or to fully script the initial install of the
manager.

Many users rely on the Windows Store app to install packages, and the built-in
redirector in Windows can only open to a Store page. As such, removing the Store
app is equivalent to denying hundreds of thousands of installs each month.

The two builds are practically identical. The only difference between the MSIX
we provide to the Store and the one that goes to python.org is package signing:
we sign the python.org package ourselves, while the Store package is signed as
part of the publish process. Otherwise, there is no additional cost to producing
and publishing both packages.


Inline Script Metadata
----------------------

PEP 723 introduced inline script metadata, a structured comment intended for
third-party tools to interpret and then launch a Python script in the correct
environment. An example taken from that PEP:

.. code:: python

   # /// script
   # requires-python = ">=3.11"
   # dependencies = [
   #   "requests<3",
   #   "rich",
   # ]
   # ///

PyManager has no integrated support for installing dependencies, and does not
propose adding any. As a result, we could not fully implement handling of this
metadata, and as we consider partial handling to be worse than nothing, we
choose not to implement any.

It is possible for a user to specify the constraint directly as an option, for
example, ``py -V:>=3.11 my-script.py`` to get the selection behaviour.

We could also detect the metadata and warn if the selected runtime does not
match its requirement, but this is not part of the initial proposal.


Copyright
=========

This document is placed in the public domain or under the
CC0-1.0-Universal license, whichever is more permissive.
