text:
PEP: 317 Title: Eliminate Implicit Exception Instantiation Version:
$Revision$ Last-Modified: $Date$ Author: Steven Taschuk
<EMAIL_ADDRESS>Status: Rejected Type: Standards Track
Content-Type: text/x-rst Created: 06-May-2003 Python-Version: 2.4
Post-History: 09-Jun-2003

Abstract

  "For clarity in new code, the form raise class(argument, ...) is
  recommended (i.e. make an explicit call to the constructor)."

  -- Guido van Rossum, in 1997[1]

This PEP proposes the formal deprecation and eventual elimination of
forms of the raise statement which implicitly instantiate an exception.
For example, statements such as :

    raise HullBreachError
    raise KitchenError, 'all out of baked beans'

must under this proposal be replaced with their synonyms :

    raise HullBreachError()
    raise KitchenError('all out of baked beans')

Note that these latter statements are already legal, and that this PEP
does not change their meaning.

Eliminating these forms of raise makes it impossible to use string
exceptions; accordingly, this PEP also proposes the formal deprecation
and eventual elimination of string exceptions.

Adoption of this proposal breaks backwards compatibility. Under the
proposed implementation schedule, Python 2.4 will introduce warnings
about uses of raise which will eventually become incorrect, and Python
3.0 will eliminate them entirely. (It is assumed that this transition
period -- 2.4 to 3.0 -- will be at least one year long, to comply with
the guidelines of PEP 5.)

Motivation

String Exceptions

It is assumed that removing string exceptions will be uncontroversial,
since it has been intended since at least Python 1.5, when the standard
exception types were changed to classes[2].

For the record: string exceptions should be removed because the presence
of two kinds of exception complicates the language without any
compensation. Instance exceptions are superior because, for example,

-   the class-instance relationship more naturally expresses the
    relationship between the exception type and value,
-   they can be organized naturally using superclass-subclass
    relationships, and
-   they can encapsulate error-reporting behaviour (for example).

Implicit Instantiation

Guido's 1997 essay[3] on changing the standard exceptions into classes
makes clear why raise can instantiate implicitly:

  "The raise statement has been extended to allow raising a class
  exception without explicit instantiation. The following forms, called
  the "compatibility forms" of the raise statement [...] The motivation
  for introducing the compatibility forms was to allow backward
  compatibility with old code that raised a standard exception."

For example, it was desired that pre-1.5 code which used string
exception syntax such as :

    raise TypeError, 'not an int'

would work both on versions of Python in which TypeError was a string,
and on versions in which it was a class.

When no such consideration obtains -- that is, when the desired
exception type is not a string in any version of the software which the
code must support -- there is no good reason to instantiate implicitly,
and it is clearer not to. For example:

1.  In the code :

        try:
            raise MyError, raised
        except MyError, caught:
            pass

    the syntactic parallel between the raise and except statements
    strongly suggests that raised and caught refer to the same object.
    For string exceptions this actually is the case, but for instance
    exceptions it is not.

2.  When instantiation is implicit, it is not obvious when it occurs,
    for example, whether it occurs when the exception is raised or when
    it is caught. Since it actually happens at the raise, the code
    should say so.

    (Note that at the level of the C API, an exception can be "raised"
    and "caught" without being instantiated; this is used as an
    optimization by, for example, PyIter_Next. But in Python, no such
    optimization is or should be available.)

3.  An implicitly instantiating raise statement with no arguments, such
    as :

        raise MyError

    simply does not do what it says: it does not raise the named object.

4.  The equivalence of :

        raise MyError
        raise MyError()

    conflates classes and instances, creating a possible source of
    confusion for beginners. (Moreover, it is not clear that the
    interpreter could distinguish between a new-style class and an
    instance of such a class, so implicit instantiation may be an
    obstacle to any future plan to let exceptions be new-style objects.)

In short, implicit instantiation has no advantages other than backwards
compatibility, and so should be phased out along with what it exists to
ensure compatibility with, namely, string exceptions.

Specification

The syntax of raise_stmt[4] is to be changed from :

    raise_stmt ::= "raise" [expression ["," expression ["," expression]]]

to :

    raise_stmt ::= "raise" [expression ["," expression]]

If no expressions are present, the raise statement behaves as it does
presently: it re-raises the last exception that was active in the
current scope, and if no exception has been active in the current scope,
a TypeError is raised indicating that this is the problem.

Otherwise, the first expression is evaluated, producing the raised
object. Then the second expression is evaluated, if present, producing
the substituted traceback. If no second expression is present, the
substituted traceback is None.

The raised object must be an instance. The class of the instance is the
exception type, and the instance itself is the exception value. If the
raised object is not an instance -- for example, if it is a class or
string -- a TypeError is raised.

If the substituted traceback is not None, it must be a traceback object,
and it is substituted instead of the current location as the place where
the exception occurred. If it is neither a traceback object nor None, a
TypeError is raised.

Backwards Compatibility

Migration Plan

Future Statement

Under the PEP 236 future statement:

    from __future__ import raise_with_two_args

the syntax and semantics of the raise statement will be as described
above. This future feature is to appear in Python 2.4; its effect is to
become standard in Python 3.0.

As the examples below illustrate, this future statement is only needed
for code which uses the substituted traceback argument to raise; simple
exception raising does not require it.

Warnings

Three new warnings <230>, all of category DeprecationWarning, are to be
issued to point out uses of raise which will become incorrect under the
proposed changes.

The first warning is issued when a raise statement is executed in which
the first expression evaluates to a string. The message for this warning
is:

    raising strings will be impossible in the future

The second warning is issued when a raise statement is executed in which
the first expression evaluates to a class. The message for this warning
is:

    raising classes will be impossible in the future

The third warning is issued when a raise statement with three
expressions is compiled. (Not, note, when it is executed; this is
important because the SyntaxError which this warning presages will occur
at compile-time.) The message for this warning is:

    raising with three arguments will be impossible in the future

These warnings are to appear in Python 2.4, and disappear in Python 3.0,
when the conditions which cause them are simply errors.

Examples

Code Using Implicit Instantiation

Code such as :

    class MyError(Exception):
        pass

    raise MyError, 'spam'

will issue a warning when the raise statement is executed. The raise
statement should be changed to instantiate explicitly:

    raise MyError('spam')

Code Using String Exceptions

Code such as :

    MyError = 'spam'
    raise MyError, 'eggs'

will issue a warning when the raise statement is executed. The exception
type should be changed to a class:

    class MyError(Exception):
        pass

and, as in the previous example, the raise statement should be changed
to instantiate explicitly :

    raise MyError('eggs')

Code Supplying a Traceback Object

Code such as :

    raise MyError, 'spam', mytraceback

will issue a warning when compiled. The statement should be changed to :

    raise MyError('spam'), mytraceback

and the future statement :

    from __future__ import raise_with_two_args

should be added at the top of the module. Note that adding this future
statement also turns the other two warnings into errors, so the changes
described in the previous examples must also be applied.

The special case :

    raise sys.exc_type, sys.exc_info, sys.exc_traceback

(which is intended to re-raise a previous exception) should be changed
simply to :

    raise

A Failure of the Plan

It may occur that a raise statement which raises a string or implicitly
instantiates is not executed in production or testing during the
phase-in period for this PEP. In that case, it will not issue any
warnings, but will instead suddenly fail one day in Python 3.0 or a
subsequent version. (The failure is that the wrong exception gets
raised, namely a TypeError complaining about the arguments to raise,
instead of the exception intended.)

Such cases can be made rarer by prolonging the phase-in period; they
cannot be made impossible short of issuing at compile-time a warning for
every raise statement.

Rejection

If this PEP were accepted, nearly all existing Python code would need to
be reviewed and probably revised; even if all the above arguments in
favour of explicit instantiation are accepted, the improvement in
clarity is too minor to justify the cost of doing the revision and the
risk of new bugs introduced thereby.

This proposal has therefore been rejected[5].

Note that string exceptions are slated for removal independently of this
proposal; what is rejected is the removal of implicit exception
instantiation.

Summary of Discussion

A small minority of respondents were in favour of the proposal, but the
dominant response was that any such migration would be costly out of
proportion to the putative benefit. As noted above, this point is
sufficient in itself to reject the PEP.

New-Style Exceptions

Implicit instantiation might conflict with future plans to allow
instances of new-style classes to be used as exceptions. In order to
decide whether to instantiate implicitly, the raise machinery must
determine whether the first argument is a class or an instance -- but
with new-style classes there is no clear and strong distinction.

Under this proposal, the problem would be avoided because the exception
would already have been instantiated. However, there are two plausible
alternative solutions:

1.  Require exception types to be subclasses of Exception, and
    instantiate implicitly if and only if :

        issubclass(firstarg, Exception)

2.  Instantiate implicitly if and only if :

        isinstance(firstarg, type)

Thus eliminating implicit instantiation entirely is not necessary to
solve this problem.

Ugliness of Explicit Instantiation

Some respondents felt that the explicitly instantiating syntax is
uglier, especially in cases when no arguments are supplied to the
exception constructor:

    raise TypeError()

The problem is particularly acute when the exception instance itself is
not of interest, that is, when the only relevant point is the exception
type:

    try:
        # ... deeply nested search loop ...
            raise Found
    except Found:
        # ...

In such cases the symmetry between raise and except can be more
expressive of the intent of the code.

Guido opined that the implicitly instantiating syntax is "a tad
prettier" even for cases with a single argument, since it has less
punctuation.

Performance Penalty of Warnings

Experience with deprecating apply() shows that use of the warning
framework can incur a significant performance penalty.

Code which instantiates explicitly would not be affected, since the
run-time checks necessary to determine whether to issue a warning are
exactly those which are needed to determine whether to instantiate
implicitly in the first place. That is, such statements are already
incurring the cost of these checks.

Code which instantiates implicitly would incur a large cost: timing
trials indicate that issuing a warning (whether it is suppressed or not)
takes about five times more time than simply instantiating, raising, and
catching an exception.

This penalty is mitigated by the fact that raise statements are rarely
on performance-critical execution paths.

Traceback Argument

As the proposal stands, it would be impossible to use the traceback
argument to raise conveniently with all 2.x versions of Python.

For compatibility with versions < 2.4, the three-argument form must be
used; but this form would produce warnings with versions >= 2.4. Those
warnings could be suppressed, but doing so is awkward because the
relevant type of warning is issued at compile-time.

If this PEP were still under consideration, this objection would be met
by extending the phase-in period. For example, warnings could first be
issued in 3.0, and become errors in some later release.

References

Copyright

This document has been placed in the public domain.

  Local Variables: mode: indented-text indent-tabs-mode: nil
  sentence-end-double-space: t fill-column: 70 End:

[1] "Standard Exception Classes in Python 1.5", Guido van Rossum.
http://www.python.org/doc/essays/stdexceptions.html

[2] "Standard Exception Classes in Python 1.5", Guido van Rossum.
http://www.python.org/doc/essays/stdexceptions.html

[3] "Standard Exception Classes in Python 1.5", Guido van Rossum.
http://www.python.org/doc/essays/stdexceptions.html

[4] "Python Language Reference", Guido van Rossum.
http://docs.python.org/reference/simple_stmts.html#raise

[5] Guido van Rossum, 11 June 2003 post to python-dev.
https://mail.python.org/pipermail/python-dev/2003-June/036176.html


text:
PEP: 694 Title: Upload 2.0 API for Python Package Repositories Author:
Donald Stufft<EMAIL_ADDRESS>Discussions-To:
https://discuss.python.org/t/pep-694-upload-2-0-api-for-python-package-repositories/16879
Status: Draft Type: Standards Track Topic: Packaging Content-Type:
text/x-rst Created: 11-Jun-2022 Post-History: 27-Jun-2022

Abstract

There is currently no standardized API for uploading files to a Python
package repository such as PyPI. Instead, everyone has been forced to
reverse engineer the non-standard API from PyPI.

That API, while functional, leaks a lot of implementation details of the
original PyPI code base, which have now had to have been faithfully
replicated in the new code base, and alternative implementations.

Beyond the above, there are a number of major issues with the current
API:

-   It is a fully synchronous API, which means that we're forced to have
    a single request being held open for potentially a long time, both
    for the upload itself, and then while the repository processes the
    uploaded file to determine success or failure.
-   It does not support any mechanism for resuming an upload, with the
    largest file size on PyPI being just under 1GB in size, that's a lot
    of wasted bandwidth if a large file has a network blip towards the
    end of an upload.
-   It treats a single file as the atomic unit of operation, which can
    be problematic when a release might have multiple binary wheels
    which can cause people to get different versions while the files are
    uploading, and if the sdist happens to not go last, possibly some
    hard to build packages are attempting to be built from source.
-   It has very limited support for communicating back to the user, with
    no support for multiple errors, warnings, deprecations, etc. It is
    limited entirely to the HTTP status code and reason phrase, of which
    the reason phrase has been deprecated since HTTP/2
    (RFC 7540 <7540#section-<IP_ADDRESS>>).
-   The metadata for a release/file is submitted alongside the file,
    however this metadata is famously unreliable, and most installers
    instead choose to download the entire file and read that in part due
    to that unreliability.
-   There is no mechanism for allowing a repository to do any sort of
    sanity checks before bandwidth starts getting expended on an upload,
    whereas a lot of the cases of invalid metadata or incorrect
    permissions could be checked prior to upload.
-   It has no support for "staging" a draft release prior to publishing
    it to the repository.
-   It has no support for creating new projects, without uploading a
    file.

This PEP proposes a new API for uploads, and deprecates the existing non
standard API.

Status Quo

This does not attempt to be a fully exhaustive documentation of the
current API, but give a high level overview of the existing API.

Endpoint

The existing upload API (and the now removed register API) lives at an
url, currently https://upload.pypi.org/legacy/, and to communicate which
specific API you want to call, you add a :action url parameter with a
value of file_upload. The values of submit, submit_pkg_info, and
doc_upload also used to be supported, but no longer are.

It also has a protocol_version parameter, in theory to allow new
versions of the API to be written, but in practice that has never
happened, and the value is always 1.

So in practice, on PyPI, the endpoint is
https://upload.pypi.org/legacy/?:action=file_upload&protocol_version=1.

Encoding

The data to be submitted is submitted as a POST request with the content
type of multipart/form-data. This is due to the historical nature, that
this API was not actually designed as an API, but rather was a form on
the initial PyPI implementation, then client code was written to
programmatically submit that form.

Content

Roughly speaking, the metadata contained within the package is submitted
as parts where the content-disposition is form-data, and the name is the
name of the field. The names of these various pieces of metadata are not
documented, and they sometimes, but not always match the names used in
the METADATA files. The casing rarely matches though, but overall the
METADATA to form-data conversion is extremely inconsistent.

The file itself is then sent as a application/octet-stream part with the
name of content, and if there is a PGP signature attached, then it will
be included as a application/octet-stream part with the name of
gpg_signature.

Specification

This PEP traces the root cause of most of the issues with the existing
API to be roughly two things:

-   The metadata is submitted alongside the file, rather than being
    parsed from the file itself.
    -   This is actually fine if used as a pre-check, but it should be
        validated against the actual METADATA or similar files within
        the distribution.
-   It supports a single request, using nothing but form data, that
    either succeeds or fails, and everything is done and contained
    within that single request.

We then propose a multi-request workflow, that essentially boils down
to:

1.  Initiate an upload session.
2.  Upload the file(s) as part of the upload session.
3.  Complete the upload session.
4.  (Optional) Check the status of an upload session.

All URLs described here will be relative to the root endpoint, which may
be located anywhere within the url structure of a domain. So it could be
at https://upload.example.com/, or https://example.com/upload/.

Versioning

This PEP uses the same MAJOR.MINOR versioning system as used in PEP 691,
but it is otherwise independently versioned. The existing API is
considered by this spec to be version 1.0, but it otherwise does not
attempt to modify that API in any way.

Endpoints

Create an Upload Session

To create a new upload session, you can send a POST request to /, with a
payload that looks like:

    {
      "meta": {
        "api-version": "2.0"
      },
      "name": "foo",
      "version": "1.0"
    }

This currently has three keys, meta, name, and version.

The meta key is included in all payloads, and it describes information
about the payload itself.

The name key is the name of the project that this session is attempting
to add files to.

The version key is the version of the project that this session is
attepmting to add files to.

If creating the session was successful, then the server must return a
response that looks like:

    {
      "meta": {
        "api-version": "2.0"
      },
      "urls": {
        "upload": "...",
        "draft": "...",
        "publish": "..."
      },
      "valid-for": 604800,
      "status": "pending",
      "files": {},
      "notices": [
        "a notice to display to the user"
      ]
    }

Besides the meta key, this response has five keys, urls, valid-for,
status, files, and notices.

The urls key is a dictionary mapping identifiers to related URLs to this
session.

The valid-for key is an integer representing how long, in seconds, until
the server itself will expire this session (and thus all of the URLs
contained in it). The session SHOULD live at least this much longer
unless the client itself has canceled the session. Servers MAY choose to
increase this time, but should never decrease it, except naturally
through the passage of time.

The status key is a string that contains one of pending, published,
errored, or canceled, this string represents the overall status of the
session.

The files key is a mapping containing the filenames that have been
uploaded to this session, to a mapping containing details about each
file.

The notices key is an optional key that points to an array of notices
that the server wishes to communicate to the end user that are not
specific to any one file.

For each filename in files the mapping has three keys, status, url, and
notices.

The status key is the same as the top level status key, except that it
indicates the status of a specific file.

The url key is the absolute URL that the client should upload that
specific file to (or use to delete that file).

The notices key is an optional key, that is an array of notices that the
server wishes to communicate to the end user that are specific to this
file.

The required response code to a successful creation of the session is a
201 Created response and it MUST include a Location header that is the
URL for this session, which may be used to check its status or cancel
it.

For the urls key, there are currently three keys that may appear:

The upload key, which is the upload endpoint for this session to
initiate a file upload.

The draft key, which is the repository URL that these files are
available at prior to publishing.

The publish key, which is the endpoint to trigger publishing the
session.

In addition to the above, if a second session is created for the same
name+version pair, then the upload server MUST return the already
existing session rather than creating a new, empty one.

Upload Each File

Once you have initiated an upload session for one or more files, then
you have to actually upload each of those files.

There is no set endpoint for actually uploading the file, that is given
to the client by the server as part of the creation of the upload
session, and clients MUST NOT assume that there is any commonality to
what those URLs look like from one session to the next.

To initiate a file upload, a client sends a POST request to the upload
URL in the session, with a request body that looks like:

    {
      "meta": {
        "api-version": "2.0"
      },
      "filename": "foo-1.0.tar.gz",
      "size": 1000,
      "hashes": {"sha256": "...", "blake2b": "..."},
      "metadata": "..."
    }

Besides the standard meta key, this currently has 4 keys:

-   filename: The filename of the file being uploaded.

-   size: The size, in bytes, of the file that is being uploaded.

-   hashes: A mapping of hash names to hex encoded digests, each of
    these digests are the digests of that file, when hashed by the hash
    identified in the name.

    By default, any hash algorithm available via hashlib (specifically
    any that can be passed to hashlib.new() and do not require
    additional parameters) can be used as a key for the hashes
    dictionary. At least one secure algorithm from
    hashlib.algorithms_guaranteed MUST always be included. At the time
    of this PEP, sha256 specifically is recommended.

    Multiple hashes may be passed at a time, but all hashes must be
    valid for the file.

-   metadata: An optional key that is a string containing the file's
    core metadata.

Servers MAY use the data provided in this response to do some sanity
checking prior to allowing the file to be uploaded, which may include
but is not limited to:

-   Checking if the filename already exists.
-   Checking if the size would invalidate some quota.
-   Checking if the contents of the metadata, if provided, are valid.

If the server determines that the client should attempt the upload, it
will return a 201 Created response, with an empty body, and a Location
header pointing to the URL that the file itself should be uploaded to.

At this point, the status of the session should show the filename, with
the above url included in it.

Upload Data

To upload the file, a client has two choices, they may upload the file
as either a single chunk, or as multiple chunks. Either option is
acceptable, but it is recommended that most clients should choose to
upload each file as a single chunk as that requires fewer requests and
typically has better performance.

However for particularly large files, uploading within a single request
may result in timeouts, so larger files may need to be uploaded in
multiple chunks.

In either case, the client must generate a unique token (or nonce) for
each upload attempt for a file, and MUST include that token in each
request in the Upload-Token header. The Upload-Token is a binary blob
encoded using base64 surrounded by a : on either side. Clients SHOULD
use at least 32 bytes of cryptographically random data. You can generate
it using the following:

    import base64
    import secrets

    header = ":" + base64.b64encode(secrets.token_bytes(32)).decode() + ":"

The one time that it is permissible to omit the Upload-Token from an
upload request is when a client wishes to opt out of the resumable or
chunked file upload feature completely. In that case, they MAY omit the
Upload-Token, and the file must be successfully uploaded in a single
HTTP request, and if it fails, the entire file must be resent in another
single HTTP request.

To upload in a single chunk, a client sends a POST request to the URL
from the session response for that filename. The client MUST include a
Content-Length header that is equal to the size of the file in bytes,
and this MUST match the size given in the original session creation.

As an example, if uploading a 100,000 byte file, you would send headers
like:

    Content-Length: 100000
    Upload-Token: :nYuc7Lg2/Lv9S4EYoT9WE6nwFZgN/TcUXyk9wtwoABg=:

If the upload completes successfully, the server MUST respond with a
201 Created status. At this point this file MUST not be present in the
repository, but merely staged until the upload session has completed.

To upload in multiple chunks, a client sends multiple POST requests to
the same URL as before, one for each chunk.

This time however, the Content-Length is equal to the size, in bytes, of
the chunk that they are sending. In addition, the client MUST include a
Upload-Offset header which indicates a byte offset that the content
included in this request starts at and a Upload-Incomplete header set to
1.

As an example, if uploading a 100,000 byte file in 1000 byte chunks, and
this chunk represents bytes 1001 through 2000, you would send headers
like:

    Content-Length: 1000
    Upload-Token: :nYuc7Lg2/Lv9S4EYoT9WE6nwFZgN/TcUXyk9wtwoABg=:
    Upload-Offset: 1001
    Upload-Incomplete: 1

However, the final chunk of data omits the Upload-Incomplete header,
since at that point the upload is no longer incomplete.

For each successful chunk, the server MUST respond with a 202 Accepted
header, except for the final chunk, which MUST be a 201 Created.

The following constraints are placed on uploads regardless of whether
they are single chunk or multiple chunks:

-   A client MUST NOT perform multiple POST requests in parallel for the
    same file to avoid race conditions and data loss or corruption. The
    server MAY terminate any ongoing POST request that utilizes the same
    Upload-Token.
-   If the offset provided in Upload-Offset is not 0 or the next chunk
    in an incomplete upload, then the server MUST respond with a 409
    Conflict.
-   Once an upload has started with a specific token, you may not use
    another token for that file without deleting the in progress upload.
-   Once a file has uploaded successfully, you may initiate another
    upload for that file, and doing so will replace that file.

Resume Upload

To resume an upload, you first have to know how much of the data the
server has already received, regardless of if you were originally
uploading the file as a single chunk, or in multiple chunks.

To get the status of an individual upload, a client can make a HEAD
request with their existing Upload-Token to the same URL they were
uploading to.

The server MUST respond back with a 204 No Content response, with an
Upload-Offset header that indicates what offset the client should
continue uploading from. If the server has not received any data, then
this would be 0, if it has received 1007 bytes then it would be 1007.

Once the client has retrieved the offset that they need to start from,
they can upload the rest of the file as described above, either in a
single request containing all of the remaining data or in multiple
chunks.

Canceling an In Progress Upload

If a client wishes to cancel an upload of a specific file, for instance
because they need to upload a different file, they may do so by issuing
a DELETE request to the file upload URL with the Upload-Token used to
upload the file in the first place.

A successful cancellation request MUST response with a 204 No Content.

Delete an uploaded File

Already uploaded files may be deleted by issuing a DELETE request to the
file upload URL without the Upload-Token.

A successful deletion request MUST response with a 204 No Content.

Session Status

Similarly to file upload, the session URL is provided in the response to
creating the upload session, and clients MUST NOT assume that there is
any commonality to what those URLs look like from one session to the
next.

To check the status of a session, clients issue a GET request to the
session URL, to which the server will respond with the same response
that they got when they initially created the upload session, except
with any changes to status, valid-for, or updated files reflected.

Session Cancellation

To cancel an upload session, a client issues a DELETE request to the
same session URL as before. At which point the server marks the session
as canceled, MAY purge any data that was uploaded as part of that
session, and future attempts to access that session URL or any of the
file upload URLs MAY return a 404 Not Found.

To prevent a lot of dangling sessions, servers may also choose to cancel
a session on their own accord. It is recommended that servers expunge
their sessions after no less than a week, but each server may choose
their own schedule.

Session Completion

To complete a session, and publish the files that have been included in
it, a client MUST send a POST request to the publish url in the session
status payload.

If the server is able to immediately complete the session, it may do so
and return a 201 Created response. If it is unable to immediately
complete the session (for instance, if it needs to do processing that
may take longer than reasonable in a single HTTP request), then it may
return a 202 Accepted response.

In either case, the server should include a Location header pointing
back to the session status url, and if the server returned a
202 Accepted, the client may poll that URL to watch for the status to
change.

Errors

All Error responses that contain a body will have a body that looks
like:

    {
      "meta": {
        "api-version": "2.0"
      },
      "message": "...",
      "errors": [
        {
          "source": "...",
          "message": "..."
        }
      ]
    }

Besides the standard meta key, this has two top level keys, message and
errors.

The message key is a singular message that encapsulates all errors that
may have happened on this request.

The errors key is an array of specific errors, each of which contains a
source key, which is a string that indicates what the source of the
error is, and a message key for that specific error.

The message and source strings do not have any specific meaning, and are
intended for human interpretation to figure out what the underlying
issue was.

Content-Types

Like PEP 691, this PEP proposes that all requests and responses from the
Upload API will have a standard content type that describes what the
content is, what version of the API it represents, and what
serialization format has been used.

The structure of this content type will be:

    application/vnd.pypi.upload.$version+format

Since only major versions should be disruptive to systems attempting to
understand one of these API content bodies, only the major version will
be included in the content type, and will be prefixed with a v to
clarify that it is a version number.

Unlike PEP 691, this PEP does not change the existing 1.0 API in any
way, so servers will be required to host the new API described in this
PEP at a different endpoint than the existing upload API.

Which means that for the new 2.0 API, the content types would be:

-   JSON: application/vnd.pypi.upload.v2+json

In addition to the above, a special "meta" version is supported named
latest, whose purpose is to allow clients to request the absolute latest
version, without having to know ahead of time what that version is. It
is recommended however, that clients be explicit about what versions
they support.

These content types DO NOT apply to the file uploads themselves, only to
the other API requests/responses in the upload API. The files themselves
should use the application/octet-stream content-type.

Version + Format Selection

Again similar to PEP 691, this PEP standardizes on using server-driven
content negotiation to allow clients to request different versions or
serialization formats, which includes the format url parameter.

Since this PEP expects the existing legacy 1.0 upload API to exist at a
different endpoint, and it currently only provides for JSON
serialization, this mechanism is not particularly useful, and clients
only have a single version and serialization they can request. However
clients SHOULD be setup to handle content negotiation gracefully in the
case that additional formats or versions are added in the future.

FAQ

Does this mean PyPI is planning to drop support for the existing upload API?

At this time PyPI does not have any specific plans to drop support for
the existing upload API.

Unlike with PEP 691 there are wide benefits to doing so, so it is likely
that we will want to drop support for it at some point in the future,
but until this API is implemented, and receiving broad use it would be
premature to make any plans for actually dropping support for it.

Is this Resumable Upload protocol based on anything?

Yes!

It's actually the protocol specified in an Active Internet-Draft, where
the authors took what they learned implementing tus to provide the idea
of resumable uploads in a wholly generic, standards based way.

The only deviation we've made from that spec is that we don't use the
104 Upload Resumption Supported informational response in the first POST
request. This decision was made for a few reasons:

-   The 104 Upload Resumption Supported is the only part of that draft
    which does not rely entirely on things that are already supported in
    the existing standards, since it was adding a new informational
    status.
-   Many clients and web frameworks don't support 1xx informational
    responses in a very good way, if at all, adding it would complicate
    implementation for very little benefit.
-   The purpose of the 104 Upload Resumption Supported support is to
    allow clients to determine that an arbitrary endpoint that they're
    interacting with supports resumable uploads. Since this PEP is
    mandating support for that in servers, clients can just assume that
    the server they are interacting with supports it, which makes using
    it unneeded.
-   In theory, if the support for 1xx responses got resolved and the
    draft gets accepted with it in, we can add that in at a later date
    without changing the overall flow of the API.

There is a risk that the above draft doesn't get accepted, but even if
it does not, that doesn't actually affect us. It would just mean that
our support for resumable uploads is an application specific protocol,
but is still wholly standards compliant.

Open Questions

Multipart Uploads vs tus

This PEP currently bases the actual uploading of files on an internet
draft from tus.io that supports resumable file uploads.

That protocol requires a few things:

-   That the client selects a secure Upload-Token that they use to
    identify uploading a single file.
-   That if clients don't upload the entire file in one shot, that they
    have to submit the chunks serially, and in the correct order, with
    all but the final chunk having a Upload-Incomplete: 1 header.
-   Resumption of an upload is essentially just querying the server to
    see how much data they've gotten, then sending the remaining bytes
    (either as a single request, or in chunks).
-   The upload implicitly is completed when the server successfully gets
    all of the data from the client.

This has one big benefit, that if a client doesn't care about resuming
their download, the work to support, from a client side, resumable
uploads is able to be completely ignored. They can just POST the file to
the URL, and if it doesn't succeed, they can just POST the whole file
again.

The other benefit is that even if you do want to support resumption, you
can still just POST the file, and unless you need to resume the
download, that's all you have to do.

Another, possibly theoretical, benefit is that for hashing the uploaded
files, the serial chunks requirement means that the server can maintain
hashing state between requests, update it for each request, then write
that file back to storage. Unfortunately this isn't actually possible to
do with Python's hashlib, though there are some libraries like Rehash
that implement it, but they don't support every hash that hashlib does
(specifically not blake2 or sha3 at the time of writing).

We might also need to reconstitute the download for processing anyways
to do things like extract metadata, etc from it, which would make it a
moot point.

The downside is that there is no ability to parallelize the upload of a
single file because each chunk has to be submitted serially.

AWS S3 has a similar API (and most blob stores have copied it either
wholesale or something like it) which they call multipart uploading.

The basic flow for a multipart upload is:

1.  Initiate a Multipart Upload to get an Upload ID.
2.  Break your file up into chunks, and upload each one of them
    individually.
3.  Once all chunks have been uploaded, finalize the upload.
    -   This is the step where any errors would occur.

It does not directly support resuming an upload, but it allows clients
to control the "blast radius" of failure by adjusting the size of each
part they upload, and if any of the parts fail, they only have to resend
those specific parts.

This has a big benefit in that it allows parallelization in uploading
files, allowing clients to maximize their bandwidth using multiple
threads to send the data.

We wouldn't need an explicit step (1), because our session would
implicitly initiate a multipart upload for each file.

It does have its own downsides:

-   Clients have to do more work on every request to have something
    resembling resumable uploads. They would have to break the file up
    into multiple parts rather than just making a single POST request,
    and only needing to deal with the complexity if something fails.
-   Clients that don't care about resumption at all still have to deal
    with the third explicit step, though they could just upload the file
    all as a single part.
    -   S3 works around this by having another API for one shot uploads,
        but I'd rather not have two different APIs for uploading the
        same file.
-   Verifying hashes gets somewhat more complicated. AWS implements
    hashing multipart uploads by hashing each part, then the overall
    hash is just a hash of those hashes, not of the content itself. We
    need to know the actual hash of the file itself for PyPI, so we
    would have to reconstitute the file and read its content and hash it
    once it's been fully uploaded, though we could still use the hash of
    hashes trick for checksumming the upload itself.
    -   See above about whether this is actually a downside in practice,
        or if it's just in theory.

I lean towards the tus style resumable uploads as I think they're
simpler to use and to implement, and the main downside is that we
possibly leave some multi-threaded performance on the table, which I
think that I'm personally fine with?

I guess one additional benefit of the S3 style multi part uploads is
that you don't have to try and do any sort of protection against
parallel uploads, since they're just supported. That alone might erase
most of the server side implementation simplification.

Copyright

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

license: Public Domain


text:
PEP: 801 Title: Reserved Author: Barry Warsaw<EMAIL_ADDRESS>Status:
Active Type: Informational Content-Type: text/x-rst Created: 21-Jun-2018

Abstract

This PEP is reserved for future use, because We are the 801. Contact the
author for details.

Copyright

This document has been placed in the public domain.


text:
PEP: 3116 Title: New I/O Version: $Revision$ Last-Modified: $Date$
Author: Daniel Stutzbach<EMAIL_ADDRESS>Guido van
Rossum<EMAIL_ADDRESS>Mike Verdone<EMAIL_ADDRESS>Status:
Final Type: Standards Track Content-Type: text/x-rst Created:
26-Feb-2007 Python-Version: 3.0 Post-History: 26-Feb-2007

Rationale and Goals

Python allows for a variety of stream-like (a.k.a. file-like) objects
that can be used via read() and write() calls. Anything that provides
read() and write() is stream-like. However, more exotic and extremely
useful functions like readline() or seek() may or may not be available
on every stream-like object. Python needs a specification for basic
byte-based I/O streams to which we can add buffering and text-handling
features.

Once we have a defined raw byte-based I/O interface, we can add
buffering and text handling layers on top of any byte-based I/O class.
The same buffering and text handling logic can be used for files,
sockets, byte arrays, or custom I/O classes developed by Python
programmers. Developing a standard definition of a stream lets us
separate stream-based operations like read() and write() from
implementation specific operations like fileno() and isatty(). It
encourages programmers to write code that uses streams as streams and
not require that all streams support file-specific or socket-specific
operations.

The new I/O spec is intended to be similar to the Java I/O libraries,
but generally less confusing. Programmers who don't want to muck about
in the new I/O world can expect that the open() factory method will
produce an object backwards-compatible with old-style file objects.

Specification

The Python I/O Library will consist of three layers: a raw I/O layer, a
buffered I/O layer, and a text I/O layer. Each layer is defined by an
abstract base class, which may have multiple implementations. The raw
I/O and buffered I/O layers deal with units of bytes, while the text I/O
layer deals with units of characters.

Raw I/O

The abstract base class for raw I/O is RawIOBase. It has several methods
which are wrappers around the appropriate operating system calls. If one
of these functions would not make sense on the object, the
implementation must raise an IOError exception. For example, if a file
is opened read-only, the .write() method will raise an IOError. As
another example, if the object represents a socket, then .seek(),
.tell(), and .truncate() will raise an IOError. Generally, a call to one
of these functions maps to exactly one operating system call.

  .read(n: int) -> bytes

    Read up to n bytes from the object and return them. Fewer than n
    bytes may be returned if the operating system call returns fewer
    than n bytes. If 0 bytes are returned, this indicates end of file.
    If the object is in non-blocking mode and no bytes are available,
    the call returns None.

  .readinto(b: bytes) -> int

    Read up to len(b) bytes from the object and stores them in b,
    returning the number of bytes read. Like .read, fewer than len(b)
    bytes may be read, and 0 indicates end of file. None is returned if
    a non-blocking object has no bytes available. The length of b is
    never changed.

  .write(b: bytes) -> int

    Returns number of bytes written, which may be < len(b).

  .seek(pos: int, whence: int = 0) -> int

  .tell() -> int

  .truncate(n: int = None) -> int

  .close() -> None

Additionally, it defines a few other methods:

  .readable() -> bool

    Returns True if the object was opened for reading, False otherwise.
    If False, .read() will raise an IOError if called.

  .writable() -> bool

    Returns True if the object was opened for writing, False otherwise.
    If False, .write() and .truncate() will raise an IOError if called.

  .seekable() -> bool

    Returns True if the object supports random access (such as disk
    files), or False if the object only supports sequential access (such
    as sockets, pipes, and ttys). If False, .seek(), .tell(), and
    .truncate() will raise an IOError if called.

  .__enter__() -> ContextManager

    Context management protocol. Returns self.

  .__exit__(...) -> None

    Context management protocol. Same as .close().

If and only if a RawIOBase implementation operates on an underlying file
descriptor, it must additionally provide a .fileno() member function.
This could be defined specifically by the implementation, or a mix-in
class could be used (need to decide about this).

  .fileno() -> int

    Returns the underlying file descriptor (an integer)

Initially, three implementations will be provided that implement the
RawIOBase interface: FileIO, SocketIO (in the socket module), and
ByteIO. Each implementation must determine whether the object supports
random access as the information provided by the user may not be
sufficient (consider open("/dev/tty", "rw") or
open("/tmp/named-pipe", "rw")). As an example, FileIO can determine this
by calling the seek() system call; if it returns an error, the object
does not support random access. Each implementation may provided
additional methods appropriate to its type. The ByteIO object is
analogous to Python 2's cStringIO library, but operating on the new
bytes type instead of strings.

Buffered I/O

The next layer is the Buffered I/O layer which provides more efficient
access to file-like objects. The abstract base class for all Buffered
I/O implementations is BufferedIOBase, which provides similar methods to
RawIOBase:

  .read(n: int = -1) -> bytes

    Returns the next n bytes from the object. It may return fewer than n
    bytes if end-of-file is reached or the object is non-blocking. 0
    bytes indicates end-of-file. This method may make multiple calls to
    RawIOBase.read() to gather the bytes, or may make no calls to
    RawIOBase.read() if all of the needed bytes are already buffered.

  .readinto(b: bytes) -> int

  .write(b: bytes) -> int

    Write b bytes to the buffer. The bytes are not guaranteed to be
    written to the Raw I/O object immediately; they may be buffered.
    Returns len(b).

  .seek(pos: int, whence: int = 0) -> int

  .tell() -> int

  .truncate(pos: int = None) -> int

  .flush() -> None

  .close() -> None

  .readable() -> bool

  .writable() -> bool

  .seekable() -> bool

  .__enter__() -> ContextManager

  .__exit__(...) -> None

Additionally, the abstract base class provides one member variable:

  .raw

    A reference to the underlying RawIOBase object.

The BufferedIOBase methods signatures are mostly identical to that of
RawIOBase (exceptions: write() returns None, read()'s argument is
optional), but may have different semantics. In particular,
BufferedIOBase implementations may read more data than requested or
delay writing data using buffers. For the most part, this will be
transparent to the user (unless, for example, they open the same file
through a different descriptor). Also, raw reads may return a short read
without any particular reason; buffered reads will only return a short
read if EOF is reached; and raw writes may return a short count (even
when non-blocking I/O is not enabled!), while buffered writes will raise
IOError when not all bytes could be written or buffered.

There are four implementations of the BufferedIOBase abstract base
class, described below.

BufferedReader

The BufferedReader implementation is for sequential-access read-only
objects. Its .flush() method is a no-op.

BufferedWriter

The BufferedWriter implementation is for sequential-access write-only
objects. Its .flush() method forces all cached data to be written to the
underlying RawIOBase object.

BufferedRWPair

The BufferedRWPair implementation is for sequential-access read-write
objects such as sockets and ttys. As the read and write streams of these
objects are completely independent, it could be implemented by simply
incorporating a BufferedReader and BufferedWriter instance. It provides
a .flush() method that has the same semantics as a BufferedWriter's
.flush() method.

BufferedRandom

The BufferedRandom implementation is for all random-access objects,
whether they are read-only, write-only, or read-write. Compared to the
previous classes that operate on sequential-access objects, the
BufferedRandom class must contend with the user calling .seek() to
reposition the stream. Therefore, an instance of BufferedRandom must
keep track of both the logical and true position within the object. It
provides a .flush() method that forces all cached write data to be
written to the underlying RawIOBase object and all cached read data to
be forgotten (so that future reads are forced to go back to the disk).

Q: Do we want to mandate in the specification that switching between
reading and writing on a read-write object implies a .flush()? Or is
that an implementation convenience that users should not rely on?

For a read-only BufferedRandom object, .writable() returns False and the
.write() and .truncate() methods throw IOError.

For a write-only BufferedRandom object, .readable() returns False and
the .read() method throws IOError.

Text I/O

The text I/O layer provides functions to read and write strings from
streams. Some new features include universal newlines and character set
encoding and decoding. The Text I/O layer is defined by a TextIOBase
abstract base class. It provides several methods that are similar to the
BufferedIOBase methods, but operate on a per-character basis instead of
a per-byte basis. These methods are:

  .read(n: int = -1) -> str

  .write(s: str) -> int

  .tell() -> object

    Return a cookie describing the current file position. The only
    supported use for the cookie is with .seek() with whence set to 0
    (i.e. absolute seek).

  .seek(pos: object, whence: int = 0) -> int

    Seek to position pos. If pos is non-zero, it must be a cookie
    returned from .tell() and whence must be zero.

  .truncate(pos: object = None) -> int

    Like BufferedIOBase.truncate(), except that pos (if not None) must
    be a cookie previously returned by .tell().

Unlike with raw I/O, the units for .seek() are not specified - some
implementations (e.g. StringIO) use characters and others (e.g.
TextIOWrapper) use bytes. The special case for zero is to allow going to
the start or end of a stream without a prior .tell(). An implementation
could include stream encoder state in the cookie returned from .tell().

TextIOBase implementations also provide several methods that are
pass-throughs to the underlying BufferedIOBase objects:

  .flush() -> None

  .close() -> None

  .readable() -> bool

  .writable() -> bool

  .seekable() -> bool

TextIOBase class implementations additionally provide the following
methods:

  .readline() -> str

    Read until newline or EOF and return the line, or "" if EOF hit
    immediately.

  .__iter__() -> Iterator

    Returns an iterator that returns lines from the file (which happens
    to be self).

  .next() -> str

    Same as readline() except raises StopIteration if EOF hit
    immediately.

Two implementations will be provided by the Python library. The primary
implementation, TextIOWrapper, wraps a Buffered I/O object. Each
TextIOWrapper object has a property named ".buffer" that provides a
reference to the underlying BufferedIOBase object. Its initializer has
the following signature:

  .__init__(self, buffer, encoding=None, errors=None, newline=None, line_buffering=False)

    buffer is a reference to the BufferedIOBase object to be wrapped
    with the TextIOWrapper.

    encoding refers to an encoding to be used for translating between
    the byte-representation and character-representation. If it is None,
    then the system's locale setting will be used as the default.

    errors is an optional string indicating error handling. It may be
    set whenever encoding may be set. It defaults to 'strict'.

    newline can be None, '', '\n', '\r', or '\r\n'; all other values are
    illegal. It controls the handling of line endings. It works as
    follows:

    -   On input, if newline is None, universal newlines mode is
        enabled. Lines in the input can end in '\n', '\r', or '\r\n',
        and these are translated into '\n' before being returned to the
        caller. If it is '', universal newline mode is enabled, but line
        endings are returned to the caller untranslated. If it has any
        of the other legal values, input lines are only terminated by
        the given string, and the line ending is returned to the caller
        untranslated. (In other words, translation to '\n' only occurs
        if newline is None.)
    -   On output, if newline is None, any '\n' characters written are
        translated to the system default line separator, os.linesep. If
        newline is '', no translation takes place. If newline is any of
        the other legal values, any '\n' characters written are
        translated to the given string. (Note that the rules guiding
        translation are different for output than for input.)

    line_buffering, if True, causes write() calls to imply a flush() if
    the string written contains at least one '\n' or '\r' character.
    This is set by open() when it detects that the underlying stream is
    a TTY device, or when a buffering argument of 1 is passed.

    Further notes on the newline parameter:

    -   '\r' support is still needed for some OSX applications that
        produce files using '\r' line endings; Excel (when exporting to
        text) and Adobe Illustrator EPS files are the most common
        examples.
    -   If translation is enabled, it happens regardless of which method
        is called for reading or writing. For example, f.read() will
        always produce the same result as ''.join(f.readlines()).
    -   If universal newlines without translation are requested on input
        (i.e. newline=''), if a system read operation returns a buffer
        ending in '\r', another system read operation is done to
        determine whether it is followed by '\n' or not. In universal
        newlines mode with translation, the second system read operation
        may be postponed until the next read request, and if the
        following system read operation returns a buffer starting with
        '\n', that character is simply discarded.

Another implementation, StringIO, creates a file-like TextIO
implementation without an underlying Buffered I/O object. While similar
functionality could be provided by wrapping a BytesIO object in a
TextIOWrapper, the StringIO object allows for much greater efficiency as
it does not need to actually performing encoding and decoding. A String
I/O object can just store the encoded string as-is. The StringIO
object's __init__ signature takes an optional string specifying the
initial value; the initial position is always 0. It does not support
encodings or newline translations; you always read back exactly the
characters you wrote.

Unicode encoding/decoding Issues

We should allow changing the encoding and error-handling setting later.
The behavior of Text I/O operations in the face of Unicode problems and
ambiguities (e.g. diacritics, surrogates, invalid bytes in an encoding)
should be the same as that of the unicode encode()/decode() methods.
UnicodeError may be raised.

Implementation note: we should be able to reuse much of the
infrastructure provided by the codecs module. If it doesn't provide the
exact APIs we need, we should refactor it to avoid reinventing the
wheel.

Non-blocking I/O

Non-blocking I/O is fully supported on the Raw I/O level only. If a raw
object is in non-blocking mode and an operation would block, then
.read() and .readinto() return None, while .write() returns 0. In order
to put an object in non-blocking mode, the user must extract the fileno
and do it by hand.

At the Buffered I/O and Text I/O layers, if a read or write fails due a
non-blocking condition, they raise an IOError with errno set to EAGAIN.

Originally, we considered propagating up the Raw I/O behavior, but many
corner cases and problems were raised. To address these issues,
significant changes would need to have been made to the Buffered I/O and
Text I/O layers. For example, what should .flush() do on a Buffered
non-blocking object? How would the user instruct the object to "Write as
much as you can from your buffer, but don't block"? A non-blocking
.flush() that doesn't necessarily flush all available data is
counter-intuitive. Since non-blocking and blocking objects would have
such different semantics at these layers, it was agreed to abandon
efforts to combine them into a single type.

The open() Built-in Function

The open() built-in function is specified by the following pseudo-code:

    def open(filename, mode="r", buffering=None, *,
             encoding=None, errors=None, newline=None):
        assert isinstance(filename, (str, int))
        assert isinstance(mode, str)
        assert buffering is None or isinstance(buffering, int)
        assert encoding is None or isinstance(encoding, str)
        assert newline in (None, "", "\n", "\r", "\r\n")
        modes = set(mode)
        if modes - set("arwb+t") or len(mode) > len(modes):
            raise ValueError("invalid mode: %r" % mode)
        reading = "r" in modes
        writing = "w" in modes
        binary = "b" in modes
        appending = "a" in modes
        updating = "+" in modes
        text = "t" in modes or not binary
        if text and binary:
            raise ValueError("can't have text and binary mode at once")
        if reading + writing + appending > 1:
            raise ValueError("can't have read/write/append mode at once")
        if not (reading or writing or appending):
            raise ValueError("must have exactly one of read/write/append mode")
        if binary and encoding is not None:
            raise ValueError("binary modes doesn't take an encoding arg")
        if binary and errors is not None:
            raise ValueError("binary modes doesn't take an errors arg")
        if binary and newline is not None:
            raise ValueError("binary modes doesn't take a newline arg")
        # XXX Need to spec the signature for FileIO()
        raw = FileIO(filename, mode)
        line_buffering = (buffering == 1 or buffering is None and raw.isatty())
        if line_buffering or buffering is None:
            buffering = 8*1024  # International standard buffer size
            # XXX Try setting it to fstat().st_blksize
        if buffering < 0:
            raise ValueError("invalid buffering size")
        if buffering == 0:
            if binary:
                return raw
            raise ValueError("can't have unbuffered text I/O")
        if updating:
            buffer = BufferedRandom(raw, buffering)
        elif writing or appending:
            buffer = BufferedWriter(raw, buffering)
        else:
            assert reading
            buffer = BufferedReader(raw, buffering)
        if binary:
            return buffer
        assert text
        return TextIOWrapper(buffer, encoding, errors, newline, line_buffering)

Copyright

This document has been placed in the public domain.

  Local Variables: mode: indented-text indent-tabs-mode: nil
  sentence-end-double-space: t fill-column: 70 coding: utf-8 End:


text:
PEP: 257 Title: Docstring Conventions Author: David Goodger
<EMAIL_ADDRESS>Guido van Rossum<EMAIL_ADDRESS>Discussions-To<EMAIL_ADDRESS>Status: Active Type: Informational
Created: 29-May-2001 Post-History: 13-Jun-2001

Abstract

This PEP documents the semantics and conventions associated with Python
docstrings.

Rationale

The aim of this PEP is to standardize the high-level structure of
docstrings: what they should contain, and how to say it (without
touching on any markup syntax within docstrings). The PEP contains
conventions, not laws or syntax.

  "A universal convention supplies all of maintainability, clarity,
  consistency, and a foundation for good programming habits too. What it
  doesn't do is insist that you follow it against your will. That's
  Python!"

  -- Tim Peters on comp.lang.python, 2001-06-16

If you violate these conventions, the worst you'll get is some dirty
looks. But some software (such as the Docutils docstring processing
system PEP 256, PEP 258) will be aware of the conventions, so following
them will get you the best results.

Specification

What is a Docstring?

A docstring is a string literal that occurs as the first statement in a
module, function, class, or method definition. Such a docstring becomes
the __doc__ special attribute of that object.

All modules should normally have docstrings, and all functions and
classes exported by a module should also have docstrings. Public methods
(including the __init__ constructor) should also have docstrings. A
package may be documented in the module docstring of the __init__.py
file in the package directory.

String literals occurring elsewhere in Python code may also act as
documentation. They are not recognized by the Python bytecode compiler
and are not accessible as runtime object attributes (i.e. not assigned
to __doc__), but two types of extra docstrings may be extracted by
software tools:

1.  String literals occurring immediately after a simple assignment at
    the top level of a module, class, or __init__ method are called
    "attribute docstrings".
2.  String literals occurring immediately after another docstring are
    called "additional docstrings".

Please see PEP 258, "Docutils Design Specification", for a detailed
description of attribute and additional docstrings.

For consistency, always use """triple double quotes""" around
docstrings. Use r"""raw triple double quotes""" if you use any
backslashes in your docstrings.

There are two forms of docstrings: one-liners and multi-line docstrings.

One-line Docstrings

One-liners are for really obvious cases. They should really fit on one
line. For example:

    def kos_root():
        """Return the pathname of the KOS root directory."""
        global _kos_root
        if _kos_root: return _kos_root
        ...

Notes:

-   Triple quotes are used even though the string fits on one line. This
    makes it easy to later expand it.

-   The closing quotes are on the same line as the opening quotes. This
    looks better for one-liners.

-   There's no blank line either before or after the docstring.

-   The docstring is a phrase ending in a period. It prescribes the
    function or method's effect as a command ("Do this", "Return that"),
    not as a description; e.g. don't write "Returns the pathname ...".

-   The one-line docstring should NOT be a "signature" reiterating the
    function/method parameters (which can be obtained by introspection).
    Don't do:

        def function(a, b):
            """function(a, b) -> list"""

    This type of docstring is only appropriate for C functions (such as
    built-ins), where introspection is not possible. However, the nature
    of the return value cannot be determined by introspection, so it
    should be mentioned. The preferred form for such a docstring would
    be something like:

        def function(a, b):
            """Do X and return a list."""

    (Of course "Do X" should be replaced by a useful description!)

Multi-line Docstrings

Multi-line docstrings consist of a summary line just like a one-line
docstring, followed by a blank line, followed by a more elaborate
description. The summary line may be used by automatic indexing tools;
it is important that it fits on one line and is separated from the rest
of the docstring by a blank line. The summary line may be on the same
line as the opening quotes or on the next line. The entire docstring is
indented the same as the quotes at its first line (see example below).

Insert a blank line after all docstrings (one-line or multi-line) that
document a class -- generally speaking, the class's methods are
separated from each other by a single blank line, and the docstring
needs to be offset from the first method by a blank line.

The docstring of a script (a stand-alone program) should be usable as
its "usage" message, printed when the script is invoked with incorrect
or missing arguments (or perhaps with a "-h" option, for "help"). Such a
docstring should document the script's function and command line syntax,
environment variables, and files. Usage messages can be fairly elaborate
(several screens full) and should be sufficient for a new user to use
the command properly, as well as a complete quick reference to all
options and arguments for the sophisticated user.

The docstring for a module should generally list the classes, exceptions
and functions (and any other objects) that are exported by the module,
with a one-line summary of each. (These summaries generally give less
detail than the summary line in the object's docstring.) The docstring
for a package (i.e., the docstring of the package's __init__.py module)
should also list the modules and subpackages exported by the package.

The docstring for a function or method should summarize its behavior and
document its arguments, return value(s), side effects, exceptions
raised, and restrictions on when it can be called (all if applicable).
Optional arguments should be indicated. It should be documented whether
keyword arguments are part of the interface.

The docstring for a class should summarize its behavior and list the
public methods and instance variables. If the class is intended to be
subclassed, and has an additional interface for subclasses, this
interface should be listed separately (in the docstring). The class
constructor should be documented in the docstring for its __init__
method. Individual methods should be documented by their own docstring.

If a class subclasses another class and its behavior is mostly inherited
from that class, its docstring should mention this and summarize the
differences. Use the verb "override" to indicate that a subclass method
replaces a superclass method and does not call the superclass method;
use the verb "extend" to indicate that a subclass method calls the
superclass method (in addition to its own behavior).

Do not use the Emacs convention of mentioning the arguments of functions
or methods in upper case in running text. Python is case sensitive and
the argument names can be used for keyword arguments, so the docstring
should document the correct argument names. It is best to list each
argument on a separate line. For example:

    def complex(real=0.0, imag=0.0):
        """Form a complex number.

        Keyword arguments:
        real -- the real part (default 0.0)
        imag -- the imaginary part (default 0.0)
        """
        if imag == 0.0 and real == 0.0:
            return complex_zero
        ...

Unless the entire docstring fits on a line, place the closing quotes on
a line by themselves. This way, Emacs' fill-paragraph command can be
used on it.

Handling Docstring Indentation

Docstring processing tools will strip a uniform amount of indentation
from the second and further lines of the docstring, equal to the minimum
indentation of all non-blank lines after the first line. Any indentation
in the first line of the docstring (i.e., up to the first newline) is
insignificant and removed. Relative indentation of later lines in the
docstring is retained. Blank lines should be removed from the beginning
and end of the docstring.

Since code is much more precise than words, here is an implementation of
the algorithm:

    def trim(docstring):
        if not docstring:
            return ''
        # Convert tabs to spaces (following the normal Python rules)
        # and split into a list of lines:
        lines = docstring.expandtabs().splitlines()
        # Determine minimum indentation (first line doesn't count):
        indent = sys.maxsize
        for line in lines[1:]:
            stripped = line.lstrip()
            if stripped:
                indent = min(indent, len(line) - len(stripped))
        # Remove indentation (first line is special):
        trimmed = [lines[0].strip()]
        if indent < sys.maxsize:
            for line in lines[1:]:
                trimmed.append(line[indent:].rstrip())
        # Strip off trailing and leading blank lines:
        while trimmed and not trimmed[-1]:
            trimmed.pop()
        while trimmed and not trimmed[0]:
            trimmed.pop(0)
        # Return a single string:
        return '\n'.join(trimmed)

The docstring in this example contains two newline characters and is
therefore 3 lines long. The first and last lines are blank:

    def foo():
        """
        This is the second line of the docstring.
        """

To illustrate:

    >>> print repr(foo.__doc__)
    '\n    This is the second line of the docstring.\n    '
    >>> foo.__doc__.splitlines()
    ['', '    This is the second line of the docstring.', '    ']
    >>> trim(foo.__doc__)
    'This is the second line of the docstring.'

Once trimmed, these docstrings are equivalent:

    def foo():
        """A multi-line
        docstring.
        """

    def bar():
        """
        A multi-line
        docstring.
        """

References and Footnotes

Copyright

This document has been placed in the public domain.

Acknowledgements

The "Specification" text comes mostly verbatim from PEP 8 by Guido van
Rossum.

This document borrows ideas from the archives of the Python Doc-SIG.
Thanks to all members past and present.


text:
PEP: 2026 Title: Calendar versioning for Python Author: Hugo van
Kemenade Discussions-To:
https://discuss.python.org/t/pep-2026-calendar-versioning-for-python/55782
Status: Draft Type: Process Created: 11-Jun-2024 Python-Version: 3.26
Post-History: 14-Jun-2024

Abstract

This PEP proposes updating the versioning scheme for Python to include
the calendar year.

Calendar Versioning (CalVer) makes everything easier to translate into
calendar time rather than counting versions and looking up when they
will be (or were) released:

-   The support lifecycle is clear, making it easy to see when a version
    was first released.
-   Deprecations are easier to manage for maintainers and users.
-   It's easier to work out when a version will reach end of life (EOL).
-   It helps people, especially new learners, understand how old their
    installation is.
-   It's easier to reason about which versions of Python to support for
    libraries and applications.

Starting with what would have been Python 3.15, the version is
3.YY.micro where YY is the year of initial release:

-   Python 3.26 will be released in 2026 instead of Python 3.15. EOL is
    five years after initial release, therefore Python 3.26 will reach
    EOL in 2031.
-   Python 3.27 will be released in 2027, and so on.

Motivation and rationale

In 2019, we adopted an annual release cycle with PEP 602, which opened
the door for calendar versioning:

  Adopting an annual release calendar allows for natural switching to
  calendar versioning, for example by calling Python 3.9 “Python 3.20”
  since it’s released in October ‘20 and so on (“Python 3.23” would be
  the one released in October ‘23).

  While the ease of switching to calendar versioning can be treated as
  an advantage of an annual release cycle, this PEP does not advocate
  for or against a change in how Python is versioned. Should the annual
  release cycle be adopted, the versioning question will be dealt with
  in a separate PEP.

This is that PEP.

Current scheme

From the General Python FAQ: faq-version-numbering-scheme

  Python versions are numbered "A.B.C" or "A.B":

  -   A is the major version number -- it is only incremented for really
      major changes in the language.
  -   B is the minor version number -- it is incremented for less
      earth-shattering changes.
  -   C is the micro version number -- it is incremented for each bugfix
      release.

Python predates SemVer

Semantic Versioning (SemVer) is a popular scheme which aims to
communicate the intent of a release (though it doesn't always succeed).

  Given a version number MAJOR.MINOR.PATCH, increment the:

  1.  MAJOR version when you make incompatible API changes
  2.  MINOR version when you add functionality in a backward compatible
      manner
  3.  PATCH version when you make backward compatible bug fixes

People often assume Python follows SemVer and complain about breaking
changes in feature releases. But Python predates SemVer by at least 15
years: the SemVer spec was introduced in 2009 and the bespoke Python
scheme was added to source control in 1994 for the 1.0 release.

If Python adopted SemVer, that would imply a new major bump every year
when we remove deprecations.

Instead of SemVer, however, some projects have adopted another
versioning scheme based on the calendar.

Calendar versioning

With Calendar Versioning (CalVer), you include some element of the date
in the version number. For example, Ubuntu and Black use the year and
month -- Ubuntu 24.04 came out in April 2024; pip and PyCharm use only
the year.

+-------------+-------------+----------------+------------------+
| Ubuntu      | Black       | pip            | PyCharm          |
+=============+=============+================+==================+
| YY.0M.micro | YY.MM.micro | YY.minor.micro | YYYY.minor.micro |
+-------------+-------------+----------------+------------------+
| 23.04       | 23.12.1     | 23.3           | 2023.3.5         |
| 23.10       | 24.1.0      | 23.3.1         | 2024.1           |
| 24.04       | 24.1.1      | 23.3.2         | 2024.1.1         |
| 24.10       | 24.2.0      | 24.0           | 2024.1.2         |
+-------------+-------------+----------------+------------------+

And here are some programming language standards, all using some form of
the year:

+-----------+-------+----+-----+-----------+----------------+
| Ada       | Algol | C  | C++ | Fortran   | ECMAScript     |
|           |       |    |     |           | aka JavaScript |
+===========+=======+====+=====+===========+================+
| YY / YYYY | YY    | YY | YY  | YY / YYYY | YYYY           |
+-----------+-------+----+-----+-----------+----------------+
| 83        | 58    | 89 | 98  | 66        | 2020           |
| 95        | 60    | 99 | 03  | 90        | 2021           |
| 2012      | 68    | 11 | 11  | 2003      | 2022           |
| 2022      |       | 23 | 23  | 2023      | 2023           |
+-----------+-------+----+-----+-----------+----------------+

Annual release cadence

Since 2019, we've made a release each year:

-   3.15.0 will be released in 2026
-   3.16.0 will be released in 2027
-   3.17.0 will be released in 2028
-   3.18.0 will be released in 2029
-   3.19.0 will be released in 2030

This is sort of calendar-based, it’s just that it’s offset by 11 years.

CalVer for Python

The simplest CalVer option would be to stick with major version 3, and
encode the year in the minor version:

-   3.26.0 will be released in 2026
-   3.27.0 will be released in 2027
-   3.28.0 will be released in 2028
-   3.29.0 will be released in 2029
-   3.30.0 will be released in 2030

For example, 3.26 will be released in 2026. It makes it obvious when a
release first came out.

Clarity of deprecation removal

Warnings for deprecations often mention the version they will be removed
in. For example:

  DeprecationWarning: 'ctypes.SetPointerType' is deprecated and slated
  for removal in Python 3.15

However, once aware of CalVer, it is immediately obvious from the
warning how long you have to take action:

  DeprecationWarning: 'ctypes.SetPointerType' is deprecated and slated
  for removal in Python 3.26

Clarity of support lifecycle

Right now, it’s a little tricky to work out when a release is
end-of-life. First you have to look up when it was initially released,
then add 5 years:

  "When will Python 3.11 be EOL?"

  "Well, let's see... PEP 664 is the 3.11 release schedule, it says 3.11
  was released in 2022, EOL after 5 years, so 2022 + 5 = 2027."

But if the initial release year is right there in the version, it’s much
easier:

  "When will Python 3.26 be EOL?"

  "26 + 5 = [20]31"

Clarity of installation age

With the year in the version, it’s easier to work out how old your
installation is. For example, with the current scheme, if you're using
Python 3.15 in 2035, it's not immediately clear that it was first
released in 2026 (and has been EOL since 2031).

With knowledge of CalVer, if you're using Python 3.26 in 2035, it's
clear it was first released nine years ago and it's probably time to
upgrade.

This can help prompt people to switch to supported releases still under
security support, and help in teaching new users who may have older
installations.

Clarity of version support

CalVer makes it easier to reason about which versions of Python to
support.

For example, without CalVer, setting your minimum compatible Python
version to 3.19 in 2031 sets an aggressive assumption regarding version
adoption and support.

However, with CalVer, this is more obvious if setting the minimum to
3.30 in 2031. For wider support, perhaps you prefer setting it to 3.26.

Similarly, library maintainers supporting all CPython upstream versions
need to test against five versions (or six including the pre-release).

For example, in 2030, the supported versions without CalVer would be:

-   3.15, 3.16, 3.17, 3.18, 3.19

With CalVer they would be:

-   3.26, 3.27, 3.28, 3.29, 3.30

A maintainer can see at a glance which versions are current and need
testing.

Non-goals

Like the current scheme, only the micro version will be incremented for
bug fix and security releases, with no change to the major and minor.
For example:

                                     Current scheme   Proposed 3.YY.micro
  ---------------------------------- ---------------- ---------------------
  Initial release (Oct ’26)          3.15.0           3.26.0
  1st bugfix release (Dec ’26)       3.15.1           3.26.1
  2nd bugfix release (Feb ’27)       3.15.2           3.26.2
  ...                                ...              ...
  Final security release (Oct ’31)   3.15.17          3.26.17

No change to PEP 602 (Annual Release Cycle for Python):

-   No change to the 17 months to develop a feature version: alphas,
    betas and release candidates.
-   No change to the support duration: two years of full support and
    three years of security fixes.
-   No change to the annual October release cadence.

Specification

Python versions are numbered 3.YY.micro where:

-   3 is the major version number – it is always 3.
-   YY is the minor version number
    -   it is the short year number: {year} - 2000.
-   micro is the micro version number
    -   it is incremented for each bugfix or security release.

We'll keep major version 3. Python 3 is the brand; there will be no
Python 4.

In the year 2100, the minor will be 2100-2000 = 100, therefore the
version will be 3.100.0.

Python 3.14 will be the last version before this change, released in
2025. Python 3.26 will be the first version after this change, released
in 2026. There will be no Python 3.15 to 3.25 inclusive.

Security implications

None known. No change to durations or timing of bug fix and security
phases.

How to teach this

We will announce this on blogs, in the 3.14 release notes,
documentation, and through outreach to the community.

This change targets the version following 3.14: instead of 3.15 it will
be 3.26. This PEP was proposed in June 2024. Development for the
3.15/3.26 release will begin in May 2025, with the first alpha in
October 2025 and initial release in October 2026. We can already update
documentation during the 3.14 cycle. This gives plenty of notice.

We can make preview builds which only change the version for early
testing.

We could ship a python3.15 command as part of Python 3.26 that
immediately errors out and tells the user to use python3.26 instead.

Rejected ideas

YY.0

For example, Python 26.0 would be released in 2026.

There's not much appetite for Python version 4. We don’t want to repeat
2-to-3, and 4 has a lot of expectations by now. We don’t want
“earth-shattering changes”.

Perhaps Python 4 could be reserved for something big like removing the
GIL (PEP 703), but the Steering Council made it clear the free-threading
rollout must be gradual. Will we stick with version 3 forever?

Another option would be to put the year in the major version and jump to
26.0. This could mean we could leapfrog all that 4.0 baggage.

Platform compatibility tags

Changing the major version would complicate packaging, however.

The packaging:platform-compatibility-tags specification says the Python
version tag used in wheel filenames is given by
sysconfig.get_config_var("py_version_nodot"), where the major and minor
versions are joined together without a dot. For example, 3.9 is 39.

During the 3.10 alpha, there was ambiguity because 310 can be
interpreted as 3.10, 31.0, or 310.

The specification says an underscore can be used if needed, and PEP 641
("Using an underscore in the version portion of Python 3.10
compatibility tags") proposed this:

                         Version → tag → version             PEP 641 proposed version
  ---------------------- ----------------------------------- --------------------------
  Pre-3.10               3.9 → 39
  Ambiguity after 3.10   3.10 → 310 → 3.10 or 31.0 or 310?   3_10
  Ambiguity with YY.xx   26.0 → 260 → 2.60 or 26.0 or 260?   26_0

However, PEP 641 was rejected because it was unknown what side effects
there would be on code we’re not aware of.

We would need something like this for YY.0 versioning, which would be a
significant amount of complex work.

Ecosystem changes

Would changing the major version to double digits break code?

Yes, any novel change to the version inevitably does because people make
assumptions, such as the major always being 3, or that the version parts
are always single digits. For example:

+------------+----------------------------------------+-------+-----+
| Version    | Example                                | Exp   | Act |
| change     |                                        | ected | ual |
+============+========================================+=======+=====+
| 2.7.9 →    |     'this                              | 2     | 2.  |
| 2.7.10     |  is Python {}'.format(sys.version[:5]) | .7.10 | 7.1 |
+------------+----------------------------------------+-------+-----+
| 3.9 → 3.10 |     ".%s-%s                            | 3.10  | 3.1 |
|            | " % (get_platform(), sys.version[0:3]) |       |     |
+------------+----------------------------------------+-------+-----+
| 3 → 4      |     if sys.version_info[1] >= 9:       | 4.0   | 0   |
+------------+----------------------------------------+-------+-----+
| 3 → 26     |     if sys.version[0] == '3':          | 26    | 2   |
+------------+----------------------------------------+-------+-----+

The last one here is most relevant for YY.0 versioning. Therefore the
3.YY scheme is the safest and requires fewest changes, because the shape
of the version doesn't change: it's still a 3 followed by two digits.

Tip

Use Ruff's YTT rules or Flake8's flake8-2020 plugin to help find the
problems like these.

python3 command

PEP 394 (The “python” Command on Unix-Like Systems) outlines
recommendations for the python, python2 and python3 commands. python can
map to either python2 or python3. These would need revisiting if the
major version changed, and started changing annually.

Four years after Python 2.7's end-of-life, we could recommend python
only maps to the latest Python 3+ version. But what would python3 map to
when Python 26.0 is out? This would introduce additional complexity and
cost.

CPython changes

In addition to python3 command changes, there are at least four places
in CPython that assume the major version is 3 and would need updating:

-   Lib/ast.py
-   Parser/pegen.c
-   Parser/pegen.h
-   Parser/string_parser.c

YY.0 rejection

The benefits of calendar versioning are not so big compared to the
combined costs for YY.0 versioning. Therefore, YY.0 versioning is
rejected.

YY.MM

For example, Python 26.10 would be released in October 2026.

Building upon YY.0 versioning, we could also include the release month
as the minor version, like Ubuntu and Black. This would make it clear
when in the year it was released, and also when in the year it will
reach end-of-life.

However, YY.MM versioning is rejected for many of the same reasons as
YY.0 versioning.

3.YYYY

For example, Python 3.2026 would be released in 2026.

It's clearer that the minor version is a year when using a four digits,
and avoids confusion with Ubuntu versions which use YY.MM.

PY_VERSION_HEX

CPython's C API PY_VERSION_HEX macro currently uses eight bits to encode
the minor version, accommodating a maximum minor version of 255. To hold
a four-digit year, it would need to be expanded to 11 bits to fit 2047
or rather 12 bits for 4095.

This looks feasible, as it's intended for numeric comparisons, such as
#if PY_VERSION_HEX >= .... In the top 8,000 PyPI projects only one
instance was found of bit shifting
(hexversion >> 16 != PY_VERSION_HEX >> 16).

However, 3.YYYY is rejected as changing from two to four digits would
nevertheless need more work and break more code than simpler 3.YY
versioning.

Editions

For example, Python 3.15 (2026 Edition) would be released in 2026.

The Rust language uses "Editions" to introduce breaking changes.
Applying this to Python would require big changes to PEP 387 (Backwards
Compatibility Policy) and is out of scope for this PEP.

We could apply a year label to releases, such as "Python 3.15 (2026
Edition)", but this is rejected because we'd have to keep track of two
numbers.

Adopt SemVer and skip 4

For example, Python 5.0 would be released in 2026, 6.0 in 2027, and so
on.

We could skip the problematic 4.0 entirely and adopt SemVer. Because
deprecations are removed in every feature release, we would get a new
major bump every year.

This is rejected because we wouldn't get the benefit of calendar
versioning, and moving away from 3.x would also break code.

Change during 3.14 cycle

The Python 3.14 release must go ahead because: π.

Backwards compatibility

This version change is the safest of the CalVer options considered (see
rejected ideas): we keep 3 as the major version, and the minor version
is still two digits. The minor will eventually change to three digits
but this is predictable, a long way off and can be planned for.

We retain the python3 executable.

Version mapping

Versions 3.15 to 3.25 inclusive will be skipped. Features, deprecations
and removals planned for these will be remapped to the new version
numbers.

For example, a deprecation initially planned for removal in 3.16 will
instead be removed in 3.27.

  --------------------------------------------------
  Old version   New version        Initial release
  ------------- ------------------ -----------------
  3.14          3.14 (no change)   2025

  3.15          3.26               2026

  3.16          3.27               2027

  3.17          3.28               2028

  3.18          3.29               2029

  3.19          3.30               2030

  3.20          3.31               2031

  3.21          3.32               2032

  3.22          3.33               2033

  3.23          3.34               2034

  3.24          3.35               2035

  3.25          3.36               2036
  --------------------------------------------------

Forwards compatibility

Future change in cadence

This PEP proposes no change to the annual release cadence as defined in
PEP 602, which lays out
many good reasons for annual releases <602#rationale-and-goals> (for
example, smaller releases with a predictable release calendar, and
syncing with external redistributors). However unlikely, should we
decide to change the cadence in the future, CalVer does not preclude
doing so.

Less frequent

If we went to fewer than one release per year, the proposed CalVer
scheme still works; indeed, it even helps people know in which year to
expect the release. For example, if we released every second year
starting in 2036:

-   3.36.0 would be released in 2036
-   3.38.0 would be released in 2038
-   and so on

Ecosystem changes depend in part on how the the hypothetical
cadence-changing PEP updates PEP 387 (Backwards Compatibility Policy).
If, for example, it requires that the deprecation period must be at
least one feature release and not the current two (to maintain the
minimum two years), CalVer has the benefit over the status quo in
requiring no changes to planned removal versions (other than adjusting
any falling in non-release years).

More frequent

If we went to more than one release per year, here are some options. For
example, if we released in April and October starting in 2036, the next
four releases could be:

  ---------------------------------------------------------------------------------------
  Scheme          Notes                   2036 a      2036 b      2037 a      2037 b
  --------------- ----------------------- ----------- ----------- ----------- -----------
  YY.MM.micro     Year as major, month as 36.04.0     36.10.0     37.04.0     37.10.0
                  minor

  YY.x.micro      Year as major, serial   36.1.0      36.2.0      37.1.0      37.2.0
                  number as minor

  3.YYMM.micro    Combine year and month  3.3604.0    3.3610.0    3.3704.0    3.3710.0
                  as minor

  3.YYx.micro     Combine year and serial 3.360.0     3.361.0     3.370.0     3.371.0
                  number as minor

  3.YY.MM.micro   Add an extra month      <IP_ADDRESS>   <IP_ADDRESS>   <IP_ADDRESS>   <IP_ADDRESS>
                  segment

  3.major.micro   No more CalVer:         3.36.0      3.37.0      3.38.0      3.39.0
                  increment minor

                                          3.50.0      3.51.0      3.52.0      3.53.0

                                          3.100.0     3.101.0     3.102.0     3.103.0

  4.major.micro   No more CalVer:         4.0.0       4.1.0       4.2.0       4.3.0
                  increment major

  5.major.micro                           5.0.0       5.1.0       5.2.0       5.3.0
  ---------------------------------------------------------------------------------------

The YY options would require addressing issues around the platform
compatibility tags, the python3 command, and code assuming the version
always begins with 3.

The options keeping major version 3 but changing the minor to three or
four digits would also need to address code assuming the version is
always two digits.

The option adding an extra month segment is the biggest change as code
would need to deal with a four-part version instead of three.

The options dropping CalVer would be the most conservative allowing the
major and minor to be chosen freely.

No more CalVer

Adopting CalVer now does not preclude moving away CalVer in the future,
for example, back to the original scheme, to SemVer or another scheme.
Some options are listed in the table above. If wanting to make it clear
the minor is no longer the year, it can be bumped to a higher round
number (for example, 3.50 or 3.100) or the major version can be bumped
(for example, to 4.0 or 5.0). Additionally, a version epoch could be
considered.

Footnotes

The author proposed calendar versioning at the Python Language Summit
2024; this PEP is a result of discussions there and during PyCon US.

Read the slides and blogpost of the summit talk.

Acknowledgements

Thanks to Seth Michael Larson for the Language Summit Q&A notes and
blogpost, and to everyone who gave feedback at the summit and PyCon US.

Thank you to Łukasz Langa and Alex Waygood for reviewing a draft of this
PEP.

Copyright

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