PEP 517 - a plan of action
I feel that the discussion on PEP 517 is nearing an end. The outstanding questions are pretty detail-level, and there's little progress being made on them because the situations involved are mostly theoretical in the absence of actual implementations. So I propose the following plan of action: 1. Accept PEP 517 (at least provisionally). 2. Thomas to complete and publish his library of helper functions for PEP 517 consumers (optional - but I think he said he had such a thing in progress). 3. Build PEP 517 interfaces for flit and setuptools. 4. Implement PEP 517 support in pip. 5. Review what we learned from the above. 6. If needed, revise the PEP in view of actual implementation experience, and finalise it. Further frontends and backends can wait for the results of this process if they are concerned about tracking a provisional PEP. Hopefully, it shouldn't be too long a process (I'd like to see PEP 517 in the next release of pip, if possible). Notes ----- 1. On any remaining questions for the PEP, let's just go for the simplest solution for now. Let the PEP authors decide what that is, with Nick as BDFL arbitrating if there's disagreement. 2. In my view, the PEP 517 support in pip should fully embrace the principle in the PEP that it's the backend's job to ensure that sdist->wheel and build_wheel produce equivalent results (when both are possible), and should therefore *only* use build_wheel, at least until experience demonstrates that we have to protect users against badly written backends. 3. My suspicion is that the setuptools backend will pretty clearly demonstrate whether it's hard or easy to conform to the requirements in the PEP, which can inform pip's stance on a "pure build_wheel" approach. 4. If pip goes for direct wheel builds, this does mean that the build_sdist and get_requires_for_build_sdist hooks won't be exercised in any actual code during this process. IMO, that's OK, as those hooks cover important (if currently theoretical) cases for validating the "publish my project" workflow, and tools that want to address those cases (such as tox) are likely to need the sdist hooks later. If this seems like a reasonable plan, I'd suggest that people add links to issues or PRs for PEP 517 implementations in flit, setuptools and pip to this thread - as I'm sure all the participants here will want to review the concrete implementations. Please understand - I'm not trying to steamroller the process here, or force particular decisions. It's just that I know I'm burned out on the discussions, to the extent that I'm skimming mails and largely ignoring many of the hypothetical arguments, and I'm sure others are too. So we need to move to a point where we can discuss actual, in-development code rather than possibilities, and I believe the above plan gets us to that point as fast as possible. Paul
Thanks Paul for providing us with a way to move forwards. I'm in favour of what you propose. On Sun, Aug 27, 2017, at 11:37 AM, Paul Moore wrote:
2. Thomas to complete and publish his library of helper functions for PEP 517 consumers (optional - but I think he said he had such a thing in progress).
I do. It's not entirely up-to-date with the spec, but it can be found here: https://github.com/takluyver/pep517 My plan is to provide two levels of API: 1. Lower-level machinery to easily call PEP 517 hooks in a subprocess (this is what I've been writing so far) 2. A higher-level 'give me a wheel' interface which identifies the build dependencies, installs them into a build environment, and calls the build_wheel hook. Alternatively, this could be a separate package. I'd like to transfer this repo to PyPA once it's a bit more ready - assuming PyPA wants it.
3. Build PEP 517 interfaces for flit and setuptools.
The work for flit is well out of date, but it can be found here: https://github.com/takluyver/flit/tree/toml-config
1. On any remaining questions for the PEP, let's just go for the simplest solution for now. Let the PEP authors decide what that is, with Nick as BDFL arbitrating if there's disagreement.
I believe that the remaining questions are: - srcdir-on-sys.path - Notimplemented/Error (/None) Unfortunately, it's not clear to me what's the 'simplest' solution on either, and we can't easily change whichever decision we make. Nathaniel & Nick still disagree on srcdir-on-sys.path, I believe; I'm not sure how we resolve that. I still think the NotImplemented/Error issue is a prime example of bikeshedding. There clearly isn't one 'right' way to do it, because intelligent people continue to disagree about it. I'd like somebody to make the decision and end the argument, but I'm not sure who is 'above the fray' enough to be able to do that.
Please understand - I'm not trying to steamroller the process here, or force particular decisions. It's just that I know I'm burned out on the discussions, to the extent that I'm skimming mails and largely ignoring many of the hypothetical arguments, and I'm sure others are too.
+1
On Sun, Aug 27, 2017 at 5:03 AM, Thomas Kluyver <thomas@kluyver.me.uk> wrote:
I believe that the remaining questions are: - srcdir-on-sys.path - Notimplemented/Error (/None)
Unfortunately, it's not clear to me what's the 'simplest' solution on either, and we can't easily change whichever decision we make. Nathaniel & Nick still disagree on srcdir-on-sys.path, I believe; I'm not sure how we resolve that.
I still think the NotImplemented/Error issue is a prime example of bikeshedding. There clearly isn't one 'right' way to do it, because intelligent people continue to disagree about it. I'd like somebody to make the decision and end the argument, but I'm not sure who is 'above the fray' enough to be able to do that.
I just want to bring an additional point to the NotImplemented(Error)/None debate. It seems to me that this PEP is akin to the DB-API (https://www.python.org/dev/peps/pep-0249/). In DB-API, structured exceptions *are* used, just defined by the drivers themselves. It doesn't seem to be any problem for the driver implementations to provide them or for clients to handle them. That said, I would also lean towards its approach towards optional features. To quote: As with all DB API optional features, the database module authors are free to not implement these additional attributes and methods (using them will then result in an AttributeError) or to raise a NotSupportedError in case the availability can only be checked at run-time. Again, I see what is being developed here (PEP 517) as not that different from what has already been done with DB-API (PEP 249). If databases can achieve consensus, I'm sure that build tools can too. :) -- Jeremy Kloth
On 27 August 2017 at 13:41, Jeremy Kloth <jeremy.kloth@gmail.com> wrote:
Again, I see what is being developed here (PEP 517) as not that different from what has already been done with DB-API (PEP 249). If databases can achieve consensus, I'm sure that build tools can too. :)
One difference with the DB-API is that DB exceptions are intended to be seen by the user, to be caught and reported in application code. The case we're discussing here is where a hook wants to report an unusual, but normal, condition (that the backend discovers *at runtime* that it can't do what has been requested of it) and the error is to be handled by "the other end of the protocol" (the front end) and is not for reporting to the user (except in the limited sense that we're considering whether the return value/exception needs to carry "why this failed" information that the frontend can report to the user if it wishes to). Paul
On Sun, Aug 27, 2017 at 7:04 AM, Paul Moore <p.f.moore@gmail.com> wrote:
On 27 August 2017 at 13:41, Jeremy Kloth <jeremy.kloth@gmail.com> wrote:
Again, I see what is being developed here (PEP 517) as not that different from what has already been done with DB-API (PEP 249). If databases can achieve consensus, I'm sure that build tools can too. :)
One difference with the DB-API is that DB exceptions are intended to be seen by the user, to be caught and reported in application code. The case we're discussing here is where a hook wants to report an unusual, but normal, condition (that the backend discovers *at runtime* that it can't do what has been requested of it) and the error is to be handled by "the other end of the protocol" (the front end) and is not for reporting to the user (except in the limited sense that we're considering whether the return value/exception needs to carry "why this failed" information that the frontend can report to the user if it wishes to).
I guess to enhance my point, the correlation I see with DB-API is this: pip (frontend) --> sqlalchemy flit|setuptools|scons|make|my-awesome-build-tool (backend) --> mysql/ psycopg/sqlite In my usage of DB-API backends, I've certainly never thought of their exceptions as "user facing". Meaning that if a DB-API defined exception occurred, it was my responsibility as a developer to handle it; wrap it in an application-specific exception, format it for logging, retry the operation, what-have-you. Just never let it escape to the user of my application (in this case, the frontend, aka pip). Just attempting to bring a different perspective to the conversation. -- Jeremy Kloth
On 27 August 2017 at 12:03, Thomas Kluyver <thomas@kluyver.me.uk> wrote:
I believe that the remaining questions are: - srcdir-on-sys.path - Notimplemented/Error (/None)
Unfortunately, it's not clear to me what's the 'simplest' solution on either, and we can't easily change whichever decision we make. Nathaniel & Nick still disagree on srcdir-on-sys.path, I believe; I'm not sure how we resolve that.
I still think the NotImplemented/Error issue is a prime example of bikeshedding. There clearly isn't one 'right' way to do it, because intelligent people continue to disagree about it. I'd like somebody to make the decision and end the argument, but I'm not sure who is 'above the fray' enough to be able to do that.
My feeling on this one (and I know, I'm biased here) is that Guido's suggestion of returning None to mean "couldn't build a sdist" is the simplest solution, at least for now. Initial frontend code will be sdist_path = build_sdist(...) if sdist_path is None: deal with no sdist and that's easy enough to modify to either sdist_path = build_sdist(...) if sdist_path is None or sdist_path is NotImplemented: deal with no sdist or try: sdist_path = build_sdist(...) # Backward compatibility if sdist_path is None: raise NotImplementedError except NotImplementedError: deal with no sdist Add to that the fact that I'm suggesting that there mightn't even be any call to build_sdist in pip, at least in the first instance, and it doesn't seem worth doing anything more complex for now. On the srcdir-on-sys.path question, I don't really have a good answer. It's part of the discussion I've been skimming/skipping - and the fact that I have been is one of the things that prompted me to make this suggestion. If you want, I'll go back and re-read the posts and see what my view is - but you may be happier if I don't add yet another viewpoint to confuse things further ;-) Paul
On 27 August 2017 at 21:03, Thomas Kluyver <thomas@kluyver.me.uk> wrote:
Thanks Paul for providing us with a way to move forwards. I'm in favour of what you propose.
On Sun, Aug 27, 2017, at 11:37 AM, Paul Moore wrote:
2. Thomas to complete and publish his library of helper functions for PEP 517 consumers (optional - but I think he said he had such a thing in progress).
I do. It's not entirely up-to-date with the spec, but it can be found here: https://github.com/takluyver/pep517
My plan is to provide two levels of API: 1. Lower-level machinery to easily call PEP 517 hooks in a subprocess (this is what I've been writing so far) 2. A higher-level 'give me a wheel' interface which identifies the build dependencies, installs them into a build environment, and calls the build_wheel hook. Alternatively, this could be a separate package.
I'd like to transfer this repo to PyPA once it's a bit more ready - assuming PyPA wants it.
3. Build PEP 517 interfaces for flit and setuptools.
The work for flit is well out of date, but it can be found here: https://github.com/takluyver/flit/tree/toml-config
1. On any remaining questions for the PEP, let's just go for the simplest solution for now. Let the PEP authors decide what that is, with Nick as BDFL arbitrating if there's disagreement.
I believe that the remaining questions are: - srcdir-on-sys.path - Notimplemented/Error (/None)
Unfortunately, it's not clear to me what's the 'simplest' solution on either, and we can't easily change whichever decision we make. Nathaniel & Nick still disagree on srcdir-on-sys.path, I believe; I'm not sure how we resolve that.
I've actually found the arguments in favour of leaving the source directory off sys.path by default fairly persuasive, as: 1. We have enough problems with Python's default sys.path[0] handling that I'm entirely sympathetic to the desire to omit it 2. If omitting it is genuinely a problem, we'll likely find out soon enough as part of implementing a setup.py backend 3. The `tools` section in pyproject.toml already offers a way to enable bootstrapping of a meta-backend that does `sys.path.insert(0, os.getcwd())` before loading a bundled backend So I think we can deem this one resolved in favour of "Frontends must ensure the current directory is *NOT* on sys.path before importing the designated backend", as starting there will mean we maximise our chances of learning something new as part of the initial rollout of the provisionally accepted API.
I still think the NotImplemented/Error issue is a prime example of bikeshedding. There clearly isn't one 'right' way to do it, because intelligent people continue to disagree about it. I'd like somebody to make the decision and end the argument, but I'm not sure who is 'above the fray' enough to be able to do that.
------- Note: TL;DR of the below recap is that I'd accept a PEP that used any of the following three designs: - backends optionally define an AnticipatedBuildFailure exception for frontends to catch (preferred) - anticipated build failures are indicated by raising NotImplementedError - anticipated build failures are indicated by returning None I *wouldn't* accept a return value based design that used NotImplemented as the special return value So the choice amongst those 3 options then devolves to the folks actually planning to do the work - while it's my least preferred of the three, an implemented None-return based API is nevertheless immeasurably more useful than a purely theoretical exception based API. ------- Taking another go at recapping this particular design question from my perspective: - the main point of calling the PEP 517 artifact creation hooks is for their side effect of actually creating the artifact, *not* their Python return value - as such, if they fail to create the requested artifact, they should raise an exception, just like any other state mutation operation - however, it's possible for artifact creation to fail for anticipated reasons, which can be signaled by raising a specific kind of exception describing that reason - we don't want to couple the API specification to any particular library, so we either need to choose an existing builtin exception, or else let the backend declare the exception it will use In other words, if I call an API to say "create this artifact", and I don't get an artifact out the other end, then that operation failed. It may have an anticipated reason for failing, and I may want to handle those anticipated failures differently from unexpected ones (so it's worth defining a protocol for distinguishing them), but it still failed. In the standard library, probably the best exemplar of this API design pattern would be the use of UnsupportedOperation in the `io` module base classes: https://docs.python.org/3/library/io.html#io.UnsupportedOperation - from the point of view of a running application, what matters about UnsupportedOperation is that it's an exception: it indicates that the requested operation failed and can be caught using the regular exception handlers. - from the point of view of a developer debugging a failed application, what matters about UnsupportedOperation is that it indicates that there's a program bug rather than an environmental problem. For PEP 517, the situation is somewhat similar: - from the point of view of a build pipeline, what matters about NotImplementedError is that it's an exception: it indicates that the artifact failed to be created. - from the point of view of defining fallbacks in a build pipeline, what matters about NotImplementedError is that it indicates an anticipated failure mode (such as missing VCS metadata or missing external dependencies), rather than a generic "we tried it and something went wrong". Whether or not that distinction matters will depend on the specific pipeline (e.g. if you're building for publication to PyPI, the distinction is irrelevant, and anticipated failures should be treated the same as any other failure, while if you're building purely for local installation purposes, you may decide that if an sdist build fails for anticipated reasons, you will instead fall back to building a wheel directly) So I'd still approve a version of the PEP that uses NotImplementedError to indicate anticipated failures. I'd also approve a version of the PEP that works similarly to the DB API (as Jeremy Kloth suggested): rather than picking a reasonable builtin exception type, we'd instead define a new backend attribute for "AnticipatedBuildFailure" (which would be required to be a subclass of "Exception"), and then frontends would catch that for anticipated failures. While it's slightly more work for frontends to handle compared to just picking a builtin exception type, this approach does allow backends to more easily avoid the risk of the libraries they call inadvertently reporting a nominally anticipated failure that is actually nothing of the sort. One added bonus of the exception type declaration approach is that it would allow backends to explicitly indicate that there *aren't* any anticipated build failures by skipping defining the AnticipatedBuildFailure attribute (frontends can substitute in an empty tuple in that case). If I'm understanding Donald's perspective correctly, he's defining failure slightly differently from the way I am: rather then defining it in terms of "Was the end user's goal (i.e. artifact creation) achieved?", he's instead defining it in terms of "Is this outcome in compliance with the interface specification between the API client and the API implementation?", and requesting that all spec compliant outcomes be indicated by a return value, and all spec non-complaint outcomes be indicated by an exception. While there are cases where that approach does make sense (using None as a default value for dict.get, using the NotImplemented singleton to speed up binary operand coercion while still allowing None as the result of a binary operation), I don't believe those rationales apply here - the subprocess invocations and any actual artifact creation will swamp any possible performance implications from raising and catching an exception, and we don't have any particular need to avoid requiring wrapping the API call in a try/except statement if an API client wants to special case the anticipated failures. That said, while it wouldn't be my preferred outcome, I *would* accept a version of the PEP that used "None" as an error indicator for the artifact creation APIs - that's then just a conventional "Optional[str]" return type, and while I think a structured exception would be a *better* way of indicating failure to create the requested artifact (since they provide a way for the backend to provide information on how to resolve the problem), I wouldn't find the use of None for this purpose objectionable the way I do the notion of using the NotImplemented singleton. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
participants (4)
-
Jeremy Kloth
-
Nick Coghlan
-
Paul Moore
-
Thomas Kluyver