Repository: python/peps

Issue:
Make "Provisional" an explicit PEP status
Provisionally accepted PEPs are currently marked in the PEP repo as simply "Accepted".

This doesn't cause any major problems for CPython, as the only real difference is that the PEP doesn't get marked as Final until the "Provisional" markers get removed from the module documentation.

It's a bit more of an issue for distutils-sig, as our provisionally accepted PEPs may need to be implemented in multiple tools, and the purely informational PEPs may end up going to the Active status rather than to `Final` the way normal standards track PEPs do.

Having `Provisional` as a defined state for both Standards Track and Informational PEPs (and updating the PEP 0 listing accordingly) would allow such cases to be handled in a more transparent manner, rather than relying on written notes in the PEP body, or folks having seen the appropriate posts to distutils-sig about it.

Items a PR will need to cover

- [x] Adding relevant text to PEP 1
- ~~Updating `pep2html.py` to handle the new status~~ (no changes needed)
- [x] Adding a new section to the PEP 0 generator
- [x] Reviewing the currently accepted PEPs and marking the Provisional ones
- ~~Reference https://www.pypa.io/en/latest/specifications/#provisional-acceptance for the meaning of provisional in `Discussions-to: distutils-sig` PEPs~~ (I now feel PEP 1 shouldn't be aware of this level of detail)
- ~~Reference PEP 411 for the meaning of provisional in other PEPs~~ (I now feel PEP 1 shouldn't be aware of this level of detail)


Discussion:
(Note: I deliberately haven't started a python-dev thread yet, as I don't want to do that until there's specific PR wording to discuss, rather than just a general idea)
I'm trying to tease out exactly what "accepted provisionally" means.  For example, is it possible for a provisional PEP to go to any other state than Final?  Can it be Provisional then Withdrawn?  Is it more likely that a PEP will be accepted but some of the details (e.g. the exact API) is subject to change?

An example for why this feels weird to me.  asyncio is provisional in Python 3.4, with the module documentation describing it so as defined in PEP 411.  Should PEP 3156 have been marked Provisional instead of Accepted before it became Final when Python 3.5 was released?  For the user of Python 3.4, asyncio's API is still provisional, but the PEP itself would not be.  So there's some version/historical skew inherent in the proposal.  Maybe Provisional isn't in the same dimension as Status, but more in the dimension of Python-Version or Resolution perhaps.

An alternative might be to add a Provisional header, which would contain the version of Python that the PEP (or the API) is provisional in, while Python-Version contains the version at which the PEP is stable.
Linking to Python versions wouldn't really help, since the reference implementations for PyPA PEPs will generally land in `pip`, `setuptools`, PyPI, or some combination thereof.

At the moment, we're *really* handwavy as to what we mean by "Provisional" in a PyPA context, and we can't use the PEP 411 mechanism directly, since that's focused specifically on the CPython documentation.

As far as CPython's PEPs go, my suggestion would be that either the two paths would be:

   Draft -> Accepted -> Final when first implemented
   Draft -> Provisional -> Final when provisional warning is dropped from the documentation

Or else we'd put Provisional as an optional state *between* Accepted and Final, so the second path would be:

   Draft -> Accepted -> Provisional when first implemented -> Final when provisional warning is dropped from the documentation

That last variant probably best reflects what's actually going on for both CPython and PyPA.

That said, I do like the idea of having `Provisional-In` and `Final-In` fields as more explicit alternatives to `Python-Version` that also allow version specifiers for projects published through PyPI.
On May 29, 2017, at 12:49 AM, Nick Coghlan wrote:

>As far as CPython's PEPs go, my suggestion would be that either the two paths
>would be:
>
>   Draft -> Accepted -> Provisional when first implemented -> Final when
>   provisional warning is dropped from the documentation

If we were to add Provisional for CPython, I'd prefer this flow.  For CPython,
I still want to be very clear about the difference between a provisional PEP
and a provisional API since those are not necessarily the same things.  And
for the latter in CPython we still have PEP 411.

(PyPA can have different rules as best fits that project, but I still want the
conditions to be spelled out clearly.)

>That last variant probably best reflects what's actually going on for both
>CPython and PyPA.

Cool.

>That said, I do like the idea of having `Provisional-In` and `Final-In`
>fields as more explicit alternatives to `Python-Version` that also allow
>version specifiers for projects published through PyPI.

+1

I'm in favor of having Provisional in the PEP's status -- I recently was looking for a specific PEP's status and even the acceptance note didn't mention it (it was mentioned in the linked acceptance email though). In terms of transitions, I think it's fine to go from Provisional directly to Final or via Accepted, or to Withdrawn or Rejected.

Resolving patch:
diff --git a/pep0/output.py b/pep0/output.py
--- a/pep0/output.py
+++ b/pep0/output.py
@@ -42,6 +42,7 @@ def sort_peps(peps):
     and essentially dead."""
     meta = []
     info = []
+    provisional = []
     accepted = []
     open_ = []
     finished = []
@@ -74,6 +75,8 @@ def sort_peps(peps):
                 info.append(pep)
             else:
                 historical.append(pep)
+        elif pep.status == 'Provisional':
+            provisional.append(pep)
         elif pep.status in ('Accepted', 'Active'):
             accepted.append(pep)
         elif pep.status == 'Final':
@@ -82,7 +85,8 @@ def sort_peps(peps):
             raise PEPError("unsorted (%s/%s)" %
                            (pep.type_, pep.status),
                            pep.filename, pep.number)
-    return meta, info, accepted, open_, finished, historical, deferred, dead
+    return (meta, info, provisional, accepted, open_,
+            finished, historical, deferred, dead)
 
 
 def verify_email_addresses(peps):
@@ -140,8 +144,8 @@ def write_pep0(peps, output=sys.stdout):
     print(u"Index by Category", file=output)
     print(file=output)
     write_column_headers(output)
-    (meta, info, accepted, open_, finished,
-           historical, deferred, dead) = sort_peps(peps)
+    (meta, info, provisional, accepted, open_,
+           finished, historical, deferred, dead) = sort_peps(peps)
     print(file=output)
     print(u" Meta-PEPs (PEPs about PEPs or Processes)", file=output)
     print(file=output)
@@ -153,6 +157,12 @@ def write_pep0(peps, output=sys.stdout):
     for pep in info:
         print(constants.text_type(pep), file=output)
     print(file=output)
+    print(u" Provisional PEPs (provisionally accepted; interface may still change)",
+          file=output)
+    print(file=output)
+    for pep in provisional:
+        print(constants.text_type(pep), file=output)
+    print(file=output)
     print(u" Accepted PEPs (accepted; may not be implemented yet)", file=output)
     print(file=output)
     for pep in accepted:
@@ -163,7 +173,7 @@ def write_pep0(peps, output=sys.stdout):
     for pep in open_:
         print(constants.text_type(pep), file=output)
     print(file=output)
-    print(u" Finished PEPs (done, implemented in code repository)", file=output)
+    print(u" Finished PEPs (done, with a stable interface)", file=output)
     print(file=output)
     for pep in finished:
         print(constants.text_type(pep), file=output)
diff --git a/pep0/pep.py b/pep0/pep.py
--- a/pep0/pep.py
+++ b/pep0/pep.py
@@ -169,7 +169,8 @@ class PEP(object):
     type_values = (u"Standards Track", u"Informational", u"Process")
     # Valid values for the Status header.
     # Active PEPs can only be for Informational or Process PEPs.
-    status_values = (u"Accepted", u"Rejected", u"Withdrawn", u"Deferred",
+    status_values = (u"Accepted", u"Provisional",
+                     u"Rejected", u"Withdrawn", u"Deferred",
                      u"Final", u"Active", u"Draft", u"Superseded")
 
     def __init__(self, pep_file):
@@ -229,6 +230,11 @@ def __init__(self, pep_file):
             raise PEPError("Only Process and Informational PEPs may "
                            "have an Active status", pep_file.name,
                            self.number)
+        # Special case for Provisional PEPs.
+        if (status == u"Provisional" and self.type_ != "Standards Track"):
+            raise PEPError("Only Standards Track PEPs may "
+                           "have a Provisional status", pep_file.name,
+                           self.number)
         self.status = status
         # 'Author'.
         authors_and_emails = self._parse_author(metadata['Author'])


Test patch: