8000 [PEP 771] Experimental implementation of supporting [] disabling default extras by astrofrog · Pull Request #1 · wheelnext/pip · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

[PEP 771] Experimental implementation of supporting [] disabling default extras #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: pep_771
Choose a base branch
from

Conversation

astrofrog
Copy link

@DEKHTIARJonathan - I was playing around with finding a way to have package[] disable default extras as it seems to be something people are generally in favor of, and I found a way to make it work with minimal changes (though obviously this is not necessarily a complete implementation, it is just a proof of concept). The idea is that as as soon as a requirement is parsed, we insert a sentinel value which is a default extras name but unlikely to collide with a real extras name, and we later use that to determine if defaults should be ignored. We shouldn't merge this at this point because this is not what the PEP specifies yet, but just wanted to share it with you in case you had thoughts about it.

@DEKHTIARJonathan
Copy link
Member
DEKHTIARJonathan commented Feb 8, 2025

In theory the idea is good - I was thinking about playing with None and [].

I would agree with you that the magic value is near impossible to collide. I don't know how "excited" Paul will be about this idea ... In general "magic values" are not regarded as "best practice".


Though I agree with the objective, I really believe pip install package[] is the most intuitive API we can come up with.
And it has an "added bonus" - If you think about it - today if someone uses [] query, he gets no extra dependency group. Using this syntax as a "deselector" allows us to guarantee even more backward compatibility in user API, which is undeniably good.

@DEKHTIARJonathan
Copy link
Member
DEKHTIARJonathan commented Feb 8, 2025

I needed more time to investigate None vs [].

@astrofrog if you could choose, which one you think is a better design:

  • Magic Value
  • None vs []
    • None => user didn't specify anything
    • [] => user explicitly requested nothing

If we are honest with ourselves, even the second approach is a "magic value", we just give different meanings to None and [] and this needs to be very well documented in any case.

@astrofrog
Copy link
Author
astrofrog commented Feb 10, 2025

I don't have strong feelings about the approach but agree an approach without a magic value would be preferable - I wanted to check that at least there is at least one solution, which I think there is, so now we can just try and find a better one 😄 .

We could indeed try internally to distinguish between extras being set to None or [], though we'll have to be a lot more careful than with my approach as any statement such as if extras will need to be updated. I'm sure we can do it though, it just requires some more investigation.

@DEKHTIARJonathan
Copy link
Member

I'm sure we can do it though, it just requires some more investigation.
After some time moving around in the pip codebase, I've learned to not promised things ahaha

I agree that it looks better than I initially thought - but until we manage to get a full implementation that passes the PIP unittests, we probably shouldn't claim it works. I still have problems with pip install package==A.B.C, it doesn't work in this case

@astrofrog
Copy link
Author

FYI I've pushed a quick fix for pip install package[]==version

Copy link
Member
@DEKHTIARJonathan DEKHTIARJonathan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inside src/pip/_internal/resolution/resolvelib/factory.py you're missing an import:

from pip._internal.req.constructors import MAGIC_EXPLICIT_EMPTY_EXTRAS

@DEKHTIARJonathan
Copy link
Member
DEKHTIARJonathan commented Feb 12, 2025
8000

I'm getting the following error:

pip install pep-771-demo-a[]
Looking in indexes: https://pypi.org/simple, https://mockhouse.wheelnext.dev/pep-771/
Collecting pep-771-demo-a
  Using cached https://mockhouse.wheelnext.dev/pep-771/pep-771-demo-a/pep_771_demo_a-1.0.0-py3-none-any.whl (1.1 kB)
WARNING: pep-771-demo-a 1.0.0 does not provide the extra '8c950990-39f5-4fe5-920f-0981cbcb3236'
Ignoring flask: markers 'extra == "flask"' don't match your environment

[notice] A new release of pip is available: 25.0.dev0+pep.771 -> 25.0.1
[notice] To update, run: pip install --upgrade pip
ERROR: Exception:
Traceback (most recent call last):
  File "/workspace/pip/src/pip/_internal/cli/base_command.py", line 106, in _run_wrapper
    status = _inner_run()
  File "/workspace/pip/src/pip/_internal/cli/base_command.py", line 97, in _inner_run
    return self.run(options, args)
           ~~~~~~~~^^^^^^^^^^^^^^^
  File "/workspace/pip/src/pip/_internal/cli/req_command.py", line 67, in wrapper
    return func(self, options, args)
  File "/workspace/pip/src/pip/_internal/commands/install.py", line 386, in run
    requirement_set = resolver.resolve(
        reqs, check_supported_wheels=not options.target_dir
    )
  File "/workspace/pip/src/pip/_internal/resolution/resolvelib/resolver.py", line 95, in resolve
    result = self._result = resolver.resolve(
                            ~~~~~~~~~~~~~~~~^
        collected.requirements, max_rounds=limit_how_complex_resolution_can_be
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    )
    ^
  File "/workspace/pip/src/pip/_vendor/resolvelib/resolvers.py", line 551, in resolve
    state = resolution.resolve(requirements, max_rounds=max_rounds)
  File "/workspace/pip/src/pip/_vendor/resolvelib/resolvers.py", line 432, in resolve
    failure_causes = self._attempt_to_pin_criterion(name)
  File "/workspace/pip/src/pip/_vendor/resolvelib/resolvers.py", line 254, in _attempt_to_pin_criterion
    satisfied = all(
        self._p.is_satisfied_by(requirement=r, candidate=candidate)
        for r in criterion.iter_requirement()
    )
  File "/workspace/pip/src/pip/_vendor/resolvelib/resolvers.py", line 255, in <genexpr>
    self._p.is_satisfied_by(requirement=r, candidate=candidate)
    ~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspace/pip/src/pip/_internal/resolution/resolvelib/provider.py", line 243, in is_satisfied_by
    return requirement.is_satisfied_by(candidate)
           ~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
  File "/workspace/pip/src/pip/_internal/resolution/resolvelib/requirements.py", line 110, in is_satisfied_by
    assert candidate.name == self.name, (
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: Internal issue: Candidate is not for this requirement pep-771-demo-a[8c950990-39f5-4fe5-920f-0981cbcb3236] vs pep-771-demo-a

Welcome to the PIP debugging headache :D

There's multiple issues of desynchronized information:

  • Ignoring flask: markers 'extra == "flask"' don't match your environment
  • WARNING: pep-771-demo-a 1.0.0 does not provide the extra '8c950990-39f5-4fe5-920f-0981cbcb3236'

And then you have the very obvious fatal crash at the end (which is most likely unrelated to the two above).

That's what I meant on DPO, it's very "delicate" to get the recipe working, the extra information flows up and down in the object stack. pip is using extensively object mutability which makes tracking object changes excessively difficult.

I recommend using a debugger, if you're familiar with VSCode I included some debugger configs here: https://github.com/wheelnext/pep_771/blob/main/.vscode/launch.json

Feel free to use and customize as you need

@astrofrog
Copy link
Author

@DEKHTIARJonathan - I'll investigate! In the mean time, I was wondering whether it would be possible for you to enable the CI on this repo for this branch? (it might be we need to edit the actions workflow to not restrict the CI to the main branch?)

@astrofrog
Copy link
Author

While investigating this, I noticed that we need to update the implementation - in the PEP we now explicitly say that if none of the supplied extras are recognized, we should install the defaults. So pip install pep-771-demo-a[invalid] should emit the warning and then install the default extras. Would you be able to update the implementation to reflect this?

@DEKHTIARJonathan
Copy link
Member

While investigating this, I noticed that we need to update the implementation - in the PEP we now explicitly say that if none of the supplied extras are recognized, we should install the defaults. So pip install pep-771-demo-a[invalid] should emit the warning and then install the default extras. Would you be able to update the implementation to reflect this?

Do you really think it's a good idea though ? I didn't want to shake to much on that on DPO.

I think this can create confusion because it may "hide problems", instead of getting a "broken install" that forces you to look and check what's wrong (something is obviously wrong given you passed an extra that doesn't exist). Now you're getting a sort of "hot patch" that the user doesn't ask for, and potentially is unaware about...

I have a feeling this is not so much of a good "user experience" behavior to have

@DEKHTIARJonathan
Copy link
Member

@DEKHTIARJonathan - I'll investigate! In the mean time, I was wondering whether it would be possible for you to enable the CI on this repo for this branch? (it might be we need to edit the actions workflow to not restrict the CI to the main branch?)

@DEKHTIARJonathan
Copy link
Member

Sorry misclicked (I didn't want to close the PR)

@henryiii
Copy link
henryiii commented Feb 13, 2025

in the PEP we now explicitly say that if none of the supplied extras are recognized, we should install the defaults. So pip install pep-771-demo-a[invalid] should emit the warning and then install the default extras.

I think the [] case make this very simple: package means install the package and it's extras, and package[...] means install the package with explicit extras. package[] is just an extension of that with no extras. package[known_extra] will install just the known_extra, and not the default. So package[typo] will emit a warning and not install the default extras, just like package[known_extra,typo] would not be expected to install a default extra either. If you have the square brackets, it means you are not explicitly listing extras. The PEP should be updated for that if [] is added, IMO.

The [] is backward compatible, isn't it? So packages that depend on thing can update today to thing[] and still work on older versions of pip and uv?

@astrofrog
Copy link
Author

I agree that if we do indeed consider [] to mean no default extras, then [invalid] simplifies to [] meaning no default extras.

@astrofrog
Copy link
Author
astrofrog commented Feb 16, 2025

@DEKHTIARJonathan @henryiii - I've pushed a simplified implementation that should now work for the demo package with [], and also cases like package[]==version. In fact what this does is simply inject a fake extra name as soon as the package specification is parsed, and then keep it forever, but not emitting any warnings about it not being recognized. Could you let me know if this now works for you?

Of course this feels a little hacky but at the same time it is the easiest way to deal with the fact that pip has a complex stack, and I think it might be quite a bit more complex to use [] vs None. Anyway as mentioned above my aim here is to make sure it is possible for now.

@DEKHTIARJonathan
Copy link
Member

@astrofrog I'll give it a shot :) For me - so long as it "works" I'm happy with it. We can discuss "elegance and design preferences" all night long once the PEP is accepted ahaha. Let's just focus on "proving the point".

@astrofrog since you prove that pip install package[] (I still need to check that it works) - do you mind submitting a PR to the PEP changing the point @henryiii and myself are highlighting. I think we all agree that invalid_extra should not install the default

@DEKHTIARJonathan
Copy link
Member
DEKHTIARJonathan commented Feb 18, 2025

@DEKHTIARJonathan - I'll investigate! In the mean time, I was wondering whether it would be possible for you to enable the CI on this repo for this branch? (it might be we need to edit the actions workflow to not restrict the CI to the main branch?)

@astrofrog btw you have been given access to the WheelNext github org on Thursday. Feel free to push directly on the branch. If you just adapt the CI - you really don't need me to review that :)

And since you modify that part, do you think you can bring back these externally defined jobs:

https://github.com/wheelnext/pep_771/blob/main/.github/workflows/main.yml

jobs:
  test:
    uses: OpenAstronomy/github-actions-workflows/.github/workflows/tox.yml@v1
    with:
      envs: |
        - linux: demoa-noextra
        - linux: demoa-explicit
        - linux: demoa-extra
        - linux: demoa-minimal
        - linux: demob-noextra
        - linux: demob-explicit
        - linux: demob-extra
        - linux: demob-minimal

That way we can have everything self-contained

@astrofrog
Copy link
Author

Yes I can do that, in fact I also need to make changes to reflect the use of [] for minimal installs in the first place.

@astrofrog
Copy link
Author

I'm mostly off work this week though, so it will probably be next week, let's see

@astrofrog
Copy link
Author

@DEKHTIARJonathan before making any updates I will wait for you to confirm whether the new [] implementation works though - just let me know!

@DEKHTIARJonathan
Copy link
Member
DEKHTIARJonathan commented Feb 18, 2025

@astrofrog

This works well

  • pip install package
  • pip install package==1.0.0
  • pip install package[]
  • pip install package[valid_extra]
  • pip install package[invalid_extra]

This doesn't work well

  • pip install package[]==1.0.0. => It installs the default
  • pip install package[valid_extra]==1.0.0 => it installs both the valid_extra and the default
  • pip install package[invalid_extra]==1.0.0 => It installs the default

Ironically it's a problem I also have in my implementation - pip is very hard to steer for these things. I just didn't have the time to look into it

@astrofrog
Copy link
Author

Thanks for the report! Will see if I can look into it soon.

@DEKHTIARJonathan DEKHTIARJonathan added the tracked Tracked on WheelNext Board label Mar 3, 2025
@DEKHTIARJonathan DEKHTIARJonathan self-assigned this Mar 3, 2025
@DEKHTIARJonathan DEKHTIARJonathan changed the title Experimental implementation of supporting [] disabling default extras [PEP 771] Experimental implementation of supporting [] disabling default extras Mar 4, 2025
@DEKHTIARJonathan DEKHTIARJonathan moved this from Backlog to In progress in WheelNext - Project Tracker Mar 18, 2025
@astrofrog
Copy link
Author

@DEKHTIARJonathan - apologies for the long delay in getting back to this. I can try and dive into it again, but wanted to check if you have made any progress in the meantime to avoid duplication of effort?

@astrofrog
Copy link
Author

Just for ease of testing, I adjusted the tox file from the pep_771 repo to test all the different cases you mentioned above (and more), and using the wheel from the wheelhouse - I've put this tox file here for now:

https://github.com/astrofrog/tmp-pep771-pip-tests/blob/main/tox.ini

This is separate from the pep_771 tox config we have for now since that builds the wheels on-the-fly for an end-to-end test - but because we install the wheels directly from the repos, we can't use the ==1.0.0 syntax. Hence the above version of the tox file which installs the pre-build wheels. We can figure out how/whether to merge these later, but for now this is just for ease of testing PRs like this one fully.

Running it on the current PR, I get:

  demoa-noextra: OK (28.09=setup[24.39]+cmd[0.01,0.32,0.25,2.68,0.28,0.17] seconds)
  demoa-noextra-version: OK (26.79=setup[22.71]+cmd[0.02,0.45,0.28,2.74,0.35,0.24] seconds)
  demoa-empty: OK (22.80=setup[20.44]+cmd[0.00,0.40,0.30,1.24,0.39,0.02] seconds)
  demoa-empty-version: FAIL code 1 (27.81=setup[24.10]+cmd[0.00,0.35,0.28,2.58,0.28,0.22] seconds)
  demoa-explicit: OK (26.03=setup[21.88]+cmd[0.03,0.40,0.33,2.75,0.37,0.27] seconds)
  demoa-explicit-version: OK (26.75=setup[22.57]+cmd[0.07,0.46,0.31,2.79,0.33,0.22] seconds)
  demoa-extra: OK (30.37=setup[24.41]+cmd[0.00,0.33,0.25,4.71,0.24,0.44] seconds)
  demoa-extra-version: FAIL code 1 (30.89=setup[23.52]+cmd[0.00,0.38,0.28,6.20,0.31,0.19] seconds)
  demoa-minimal: OK (25.81=setup[23.64]+cmd[0.03,0.38,0.32,1.07,0.35,0.01] seconds)
  demoa-minimal-version: FAIL code 1 (26.08=setup[21.95]+cmd[0.00,0.35,0.32,2.88,0.34,0.23] seconds)
  demoa-invalid: OK (27.15=setup[25.09]+cmd[0.02,0.33,0.29,1.07,0.34,0.02] seconds)
  demoa-invalid-version: FAIL code 1 (24.56=setup[19.86]+cmd[0.02,0.47,0.33,3.24,0.37,0.27] seconds)

which means that as you pointed out, the following don't work:

  • package[]==1.0.0
  • package[valid_extra]==1.0.0
  • package[invalid-extra]==1.0.0
  • package[minimal]==1.0.0 (same as valid-extra really)

Now that I have an easy testing mechanism in place, it will be 'easier' to start trying to go through a solution that doesn't break other use cases.

@astrofrog
Copy link
Author

@henryiii @DEKHTIARJonathan - see astrofrog/peps#2 for a draft PR to track changes to the PEP

@astrofrog
Copy link
Author

@DEKHTIARJonathan - would it make sense to merge this so that at least some of the [] cases work if anyone tries this out once they read the updated PEP 771?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
tracked Tracked on WheelNext Board
Projects
Status: In progress
Development

Successfully merging this pull request may close these issues.

3 participants
0