Skip to content
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

Implement pex3 lock sync. #2373

Merged
merged 18 commits into from
Mar 28, 2024
Merged

Implement pex3 lock sync. #2373

merged 18 commits into from
Mar 28, 2024

Conversation

jsirois
Copy link
Member

@jsirois jsirois commented Feb 18, 2024

Introduce the high level pex3 lock sync command. This should generally
suffice for typical use cases and works as follows:

  • On first use (where the specified --lock does not yet exist), it
    acts just like pex3 lock create.
  • On subsequent uses it does a minimal synchronization of the lock based
    on the diff of the given current requirements against the requirements
    used to generate the specified --lock. This amounts to formulating a
    pex3 lock update command with the appropriate -p, -R and -d
    arguments.

In addition to creating and syncing a lock, it can also create and sync
a venv (--venv) based on the lock. Further, a command can be specified
to run in the synchronized venv with arguments following the -- option
terminator.

This latter set of features allow Pex to act as a concise tool in tox /
nox / invoke / make setups to implement a simple build system.

Fixes #2344

Introduce the high level `pex3 lock sync` command. This should generally
suffice for typical use cases and works as follows:

+ On first use (where the specified `--lock` does not yet exist), it
  acts just like `pex3 lock create`.
+ On subsequent uses it does a minimal synchronization of the lock based
  on the diff of the given current requirements against the requirements
  used to generate the specified `--lock`. This amounts to formulating a
  `pex3 lock update` command with the appropriate `-p`, `-R` and `-d`
  arguments.

In addition to creating and syncing a lock, it can also create and sync
a venv (--venv) based on the lock. Further, a command can be specified
to run in the synchronized venv with arguments following the `--` option
terminator.

This latter set of features allow Pex to act as a concise tool in `tox` /
`nox` / `invoke` / `make` setups to implement a simple build system.

Fixes pex-tool#2344
@jsirois jsirois marked this pull request as ready for review February 20, 2024 05:20
@jsirois
Copy link
Member Author

jsirois commented Feb 20, 2024

Another pretty huge one. Thanks in advance for any scrutiny you can provide.

I did not convert Pex's tox setup to use itself as the venv provisioner in this PR to keep the already large diff size down. There will be a follow up for that though as well as similar use in a-scie/lift (science) where Pex is already heavily used, but in a cumbersome way, to drive the underlying nox venvs.

@jsirois
Copy link
Member Author

jsirois commented Feb 20, 2024

@cburroughs I sent you an invite. I'd like to have you review, especially with an eye towards making sure you have everything you need for your Pants work with this change.

@cburroughs
Copy link
Collaborator

Thank you! I'll take a long look this week.

@jsirois
Copy link
Member Author

jsirois commented Feb 20, 2024

N.B.: I've noticed a few things and will be pushing a few individually reviewable commits here at the tail. None of these change the nature of the large 1st commit; so any review effort there is not wasted.

The specific case of a user Pip requirement was failing with a KeyError.
This is needed to pick up IC changes in universal locks amongst many other
configuration changes that can affect what versions are chosen and which
artifacts are locked.
@jsirois
Copy link
Member Author

jsirois commented Feb 21, 2024

Ok, with 120fe0d I think this is complete and I'll stay hands off until there is time to review.

@benjyw
Copy link
Collaborator

benjyw commented Feb 21, 2024

Nice! I'll take a close look over the next couple of days.

Copy link
Collaborator

@huonw huonw left a comment

Choose a reason for hiding this comment

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

This is very cool. I've focused on the core change in lock.py and skimmed the tests, but haven't had time to look beyond that.

venv = Virtualenv.enclosing(python=argv0_path)
if not venv:
try:
venv = Virtualenv(os.path.dirname(os.path.dirname(argv0_path)))
Copy link
Collaborator

@huonw huonw Feb 23, 2024

Choose a reason for hiding this comment

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

I see that this double parent is used a few places in the code base so probably isn't "new"... what does it mean? What --venv argument value might a user pass that falls into this path?

Copy link
Member Author

@jsirois jsirois Feb 23, 2024

Choose a reason for hiding this comment

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

The venv bin dir. So /a/venv/bin/script. The parent is /a/venv/bin/, and its parent is /a/venv/ - the venv dir.

Copy link
Member Author

Choose a reason for hiding this comment

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

N.B.: For Windows the structure is C:\a\venv\Scripts\script.exe and so the ..\.. treatment holds.


to_remove = [] # type: List[Distribution]
to_install = [] # type: List[Distribution]
for distribution in distributions:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Confirming my understanding of transitive dependencies of requirements (e.g. explicit requires hidden, and the command is something like pex3 lock sync explicit --venv ...):

  1. does distributions include all of them, not just the top level requirements?
  2. I don't see explicit tests for these sort of transitive dependencies, either for updating lockfiles (including something transitioning from transitive-only to top-level requirement and vice versa) or for updating a venv. Have I missed some? If not, is that something worth testing or is it well covered elsewhere?

Copy link
Member Author

Choose a reason for hiding this comment

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

  1. Yes, distributions come from a full transitive lock resolve.
  2. There are extensive tests of lock create and lock update in other files. This change just re-uses that 2 year old infra.

That said re 2, the transition you mention is probably interesting enough to test here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, tested all of 2 in test_sync_venv_transitive_to_direct_and_vice_versa.

if to_unlink:
to_unlink_by_pin[
(distribution.metadata.project_name, distribution.metadata.version)
] = [file for file in to_unlink if abs_venv_dir == commonpath((abs_venv_dir, file))]
Copy link
Collaborator

Choose a reason for hiding this comment

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

I've noticed this idiom is used in a quite a few places (not just this PR), is it worth a dedicated is_prefix_of (or something) function for it?

Copy link
Member Author

Choose a reason for hiding this comment

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

I guess I haven't thought so. The commonpath function is stdlib and this is about the only way to use it; so why obscure a stdlib idiom behind a bespoke to the Pex codebase helper.

for file in itertools.chain.from_iterable(to_unlink_by_pin.values()):
safe_delete(file)
parent_dirs.add(os.path.dirname(file))
for parent_dir in sorted(parent_dirs, reverse=True):
Copy link
Collaborator

Choose a reason for hiding this comment

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

Cool trick with the reverse=True 👍

Comment on lines 682 to 686
"When creating a venv that doesn't exist, install pip in it. When syncing an "
"existing venv, retain its pip, if any, even if pip is not present in the lock "
"file being synced. N.B.: When in `--no-retain-pip` mode (the default) only remove "
"pip if it is both present in the venv and not present in the lock file being "
"synced."
Copy link
Collaborator

Choose a reason for hiding this comment

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

If a lockfile has pip in it:

  • does the --no-retain-pip (a) install pip to a new venv (or existing one without it), and (b) update it if an existing venv has a different version?
  • does --pip use the version from the lockfile?

If so, the docs might be slightly clearer trying to convey something like:

  • In the default --no-retain-pip mode, pip isn't special, and behaves like any other package in the lock (i.e. installed/updated/removed as required)
  • With --retain-pip, pip is explicitly installed (aka, not removed) in venv, even if it is not in the lockfile (and if it is in the lockfile, the lockfile's version is used?)

Copy link
Member Author

@jsirois jsirois Feb 23, 2024

Choose a reason for hiding this comment

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

This is the grid that makes sense as I see it (keep in mind there is only a single bool here and the --no-* mean False / no Pip and the other two mean True, the venv should have Pip:

Existing venv + resolve has Pip Existing venv + resolve does not have Pip No venv + resolve has Pip No venv + resolve does not have Pip
--pip install resolve's Pip -m ensurepip install resolve's Pip -m ensurepip
--no-pip install resolve's Pip anyway uninstall Pip if installed install resolve's Pip anyway no-op

In other words the idea is --no-pip is ignored if the the resolve does in fact contain Pip. One tactic there would be to error instead: you told me no Pip but your resolve contains Pip. My thinking here is the main point is "sync"; so clearly the lock trumps. In a scenario where there are many venvs being synced, it makes sense to say pex3 lock sync --no-pip ... (in code) to ensure final venvs do not have pip unless in the lock (i.e.: a use case where you generally want the venv <-> lock mapping to be 1-1 / controlled by sync with no cowboying the venv by using Pip later ... unless the lock explicitly asks for Pip).

Copy link
Member Author

@jsirois jsirois Feb 23, 2024

Choose a reason for hiding this comment

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

I believe the 1 case in the grid not handled is --pip + "Existing venv + resolve does not have Pip". According to the grid, Pip should be installed via -m ensurepip but it is not in that case. I'll fix that and add a test as well as take a second go at the option help docs.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, the grid above is now tested and the option help improved, hopefully.

if not sync_target:
return Ok()

if self.options.dry_run:
Copy link
Collaborator

Choose a reason for hiding this comment

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

From this, it looks like --dry-run only applies to the sync to the venv, and the --lock lockfile is still created/mutated, which I find a bit surprising: I'd expect a --dry-run flag to mean not mutate anything "real" on disk, only caches and temporary files. It looks like the docs for --dry-run say "Don't update the lock file".

Am I missing something?

Copy link
Member Author

@jsirois jsirois Feb 23, 2024

Choose a reason for hiding this comment

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

You are, partly. The --dry-run option is accessed by helpers that perform the update and so a --dry-run update does not mutate the existing lock. The create path, though, does not handle --dry-run and always creates the lock file. I'm not sure what to do about that. A dry-run venv mutation makes sense (the create path can sync an existing venv instead of creating a new one). A dry run new lock file creation makes a lot less sense.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, there is now a trimmed down dry run report for lock creation such that no lock file hits disk.

@jsirois
Copy link
Member Author

jsirois commented Feb 23, 2024

I've piece-meal responded. I'll have full time to respond to all comments on 2/26 / 2/27.

Some of the questions, though are answered by tests in this PR if you get impatient.

Expand test coverage and improve option help.
Test a transitive <-> intransitive flip-flop.
@jsirois
Copy link
Member Author

jsirois commented Feb 23, 2024

I've piece-meal responded. I'll have full time to respond to all comments on 2/26 / 2/27.

@huonw I've fully responded as far as I can tell saving for the case of what to do with --dry-run when there is no lock file yet (the create case). I still have no good idea there besides the current inconsistency. Certainly printing out the contents of the lock that would be written, in JSON form or the sentency form that update --dry-run uses, seems not useful. Perhaps just outputting something like a pip freeze?:

Would lock 37 projects:
  ansicolors 1.1.8
  cowsay 5.0
  foo 1
  bar 1
  ...
  spam 1

... I actually went with this in c1e26df

@jsirois
Copy link
Member Author

jsirois commented Feb 23, 2024

Ok, all of @huonw's feedback is addressed AFAICT; so ready for round 2 of feedback.

@jsirois
Copy link
Member Author

jsirois commented Feb 23, 2024

Tests are bloody, but it appears to be a GitHub outage with action repos timing out during clone and ghcr.io also timing out when downloading Docker images.

@cburroughs
Copy link
Collaborator

Thank you! I'll take a long look this week.

Still looking and prototyping; I hope to have more useful feedback early next week.

pex/cli/commands/lock.py Outdated Show resolved Hide resolved
Copy link
Collaborator

@huonw huonw left a comment

Choose a reason for hiding this comment

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

The code looks good; thanks for the updates. I haven't (and won't, sorry) had time to execute it/play with it, yet, though; sounds like @cburroughs may be able to cover that more practical validation.

@@ -1414,8 +1428,28 @@ def _sync(self):
pip_configuration=pip_configuration,
)
)
with safe_open(lock_file_path, "w") as fp:
self._dump_lockfile(lockfile, output=fp)
if self.options.dry_run:
Copy link
Collaborator

Choose a reason for hiding this comment

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

This seems like an appropriate way to handle creating with --dry-run, thanks for getting it in.

@cburroughs
Copy link
Collaborator

Thank you! This is great and I've been kicking the tires so to speak and try it out with our internal lockfile. A few in progress comments so far.

Conflicting output

While syncing an internal lock file I've gotten this apparently conflicting message:

Updates for lock generated by universal:
  There were no updates for mypy

But I've been unable to figure out a smaller reproduction case so far.

Extras/Constraints

A bug where a requirement with extras resulted in an invalid tmp constraints file for the sync.

$ cat tmp/dhub.txt 
ansicolors==1.1.5
acryl-datahub[datahub-rest,feast,postgres,snowflake]==0.12.1.4
altair>=4.2.0,<5.2
$ cat tmp/dhub-c.txt 
awscli >= 1.30.0
boto3 >= 1.33.0
botocore >= 1.33.0
cachetools >= 5.3.0
click >= 8.1.0
coverage >= 7.3.0
dash >= 2.10.0
dash_bootstrap_components >= 1.4.0
flake8-pep585 >= 0.1.0
freezegun >= 1.3.0
graphene >= 3.0
holidays >= 0.30
ipython >= 8.0.0
kubernetes >= 28.0.0
loguru >= 0.6.0
matplotlib >= 3.7.0
pytest-mock >= 3.9.0
pytest-xdist >= 3.0.0
pytz >= 2023.3
requests >= 2.20.0
ruamel.yaml >= 0.17.0
s3fs >= 2023.1.0
setuptools >= 69.0.0
shap >= 0.40.0
xarray >= 2023.10.1
$ python3.10 -m pex.cli lock create '--indent=2'  --manylinux manylinux2014 --interpreter-constraint $'CPython==3.10.*' --style=universal --pip-version=23.3.2 --resolver-version pip-2020-resolver --target-system=linux --target-system=mac --prefer-binary  -r tmp/dhub.txt --constraints tmp/dhub-c.txt   -o tmp/dub.json
$ python3.10 -m pex.cli lock sync '--indent=2'  --manylinux manylinux2014 --interpreter-constraint $'CPython==3.10.*' --style=universal --pip-version=23.3.2 --resolver-version pip-2020-resolver --target-system=linux --target-system=mac --prefer-binary  -r tmp/dhub.txt --constraints tmp/dhub-c.txt  --lock tmp/dub.json
Encountered 1 error updating tmp/dub.json:
1.) cp310-cp310-manylinux_2_37_x86_64: /tmp/lock_update.32vgd7eh.constraints.txt line 1:
acryl-datahub[datahub-rest,feast,postgres,snowflake]==0.12.1.4

Constraint files do not support VCS, URL or local project requirementsand they do not support requirements with extras. Search for 'We are also changing our support for Constraints Files' here: https://pip.pypa.io/en/stable/user_guide/#changes-to-the-pip-dependency-resolver-in-20-3-2020.

Interpreter Constraints and friends

I see 120fe0d but I'm not sure how these lock options are intended to be "sync-able" changes. That is which can switch to "new" values while trying to hold the versions constant, or only only values that are subsets of the original range.

Naive example trying to "upgrade" Python versions with --interpreter-constraints. (I'm not sure this should be a supported operation by lock sync, just that I don't know how someone looking at --help would know.) In contrast, --pip-version can be changed on a new sync.

$ cat tmp/a-req.txt 
ansicolors==1.1.5
requests
watchfiles
$ python3.10 -m pex.cli lock create '--indent=2'  --manylinux manylinux2014 --interpreter-constraint $'CPython==3.10.*' --style=universal --pip-version=23.3.2 --resolver-version pip-2020-resolver --target-system=linux --target-system=mac --no-build --wheel -r  tmp/a-req.txt -o tmp/a.json
$ cat tmp/a-req.txt 
ansicolors==1.1.8
requests
watchfiles
$ python3.10 -m pex.cli lock sync '--indent=2'  --manylinux manylinux2014 --interpreter-constraint $'CPython==3.11.*' --style=universal --pip-version=23.3.2 --resolver-version pip-2020-resolver --target-system=linux --target-system=mac -r   tmp/a-req.txt --lock tmp/a.json 
Failed to resolve compatible artifacts from lock tmp/a.json for 1 target:
1. /usr/bin/python3.11:
    Failed to resolve all requirements for cp311-cp311-manylinux_2_37_x86_64 interpreter at /usr/bin/python3.11 from tmp/a.json:

Configured with:
    build: False
    use_wheel: True

Dependency on watchfiles not satisfied, 1 incompatible candidate found:
1.) watchfiles 0.21 (via: watchfiles) does not have any compatible artifacts:
    https://files.pythonhosted.org/packages/70/76/8d124e14cf51af4d6bba926c7473f253c6efd1539ba62577f079a2d71537/watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
    https://files.pythonhosted.org/packages/14/d0/662800e778ca20e7664dd5df57751aa79ef18b6abb92224b03c8c2e852a6/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl
    https://files.pythonhosted.org/packages/18/c4/ad5ad16cad900a29aaa792e0ed121ff70d76f74062b051661090d88c6dfd/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl
    https://files.pythonhosted.org/packages/37/17/4825999346f15d650f4c69093efa64fb040fbff4f706a20e8c4745f64070/watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
    https://files.pythonhosted.org/packages/41/0e/3333b986b1889bb71f0e44b3fac0591824a679619b8b8ddd70ff8858edc4/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
    https://files.pythonhosted.org/packages/4e/d2/769254ff04ba88ceb179a6e892606ac4da17338eb010e85ca7a9c3339234/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl
    https://files.pythonhosted.org/packages/5b/79/ecd0dfb04443a1900cd3952d7ea6493bf655c2db9a0d3736a5d98a15da39/watchfiles-0.21.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl
    https://files.pythonhosted.org/packages/62/66/7463ceb43eabc6deaa795c7969ff4d4fd938de54e655035483dfd1e97c84/watchfiles-0.21.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl
    https://files.pythonhosted.org/packages/6e/85/ea2a035b7d86bf0a29ee1c32bc2df8ad4da77e6602806e679d9735ff28cb/watchfiles-0.21.0-cp310-cp310-macosx_10_7_x86_64.whl
    https://files.pythonhosted.org/packages/92/ff/75cc1b30c5abcad13a2a72e75625ec619c7a393028a111d7d24dba578d5e/watchfiles-0.21.0-cp310-cp310-musllinux_1_1_aarch64.whl
    https://files.pythonhosted.org/packages/9a/65/12cbeb363bf220482a559c48107edfd87f09248f55e1ac315a36c2098a0f/watchfiles-0.21.0-cp310-cp310-musllinux_1_1_x86_64.whl
    https://files.pythonhosted.org/packages/b5/e5/240e5eb3ff0ee3da3b028ac5be2019c407bdd0f3fdb02bd75fdf3bd10aff/watchfiles-0.21.0-cp310-cp310-macosx_11_0_arm64.whl
    https://files.pythonhosted.org/packages/f7/4b/b90dcdc3bbaf3bb2db733e1beea2d01566b601c15fcf8e71dfcc8686c097/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
    https://files.pythonhosted.org/packages/fe/a3/42686af3a089f34aba35c39abac852869661938dae7025c1a0580dfe0fbf/watchfiles-0.21.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl

@jsirois
Copy link
Member Author

jsirois commented Feb 29, 2024

@cburroughs for Extras/Constraints you did not provide the contents of --constraints tmp/dhub-c.txt. Can you please provide everything needed to repro? Aha - you did. My eye skipped over the break.

@jsirois
Copy link
Member Author

jsirois commented Mar 8, 2024

An update since its been a while again:

I've not looked at "Conflicting output" and "Extras/Constraints" since those both look small / easy.
I have been working on "Interpreter Constraints and friends" which is hard. It's not hard for Pants' use case of --style universal locks or even --style {strict,sources} locks with 1 locked resolve in the lock file - I have those working with tests. Pex supports multiple --style {strict,sources} locked resolves in 1 lock file though, and that case is taking time to even fully understand. I had a full day today to poke at it, but I'm AFK through the 13th. On that next 14th-17th stint I'll cut my losses on the morning of the 15th if I haven't sussed that case and ship with a failure in sync for that case as the starting point and add the functionality to cover the case in a subsequent release.

@jsirois
Copy link
Member Author

jsirois commented Mar 15, 2024

Ok, I am going to proceed without support for lockfiles with multiple locked resolves, that case did prove too much to get working in a way I'm satisfied with yesterday.

Previously a subset needed to be successful to select a locked resolve
for syncing. This would fail if the lock was strict or else any single
locked requirement had platform specific artifacts with no available
sdist.
for target in update_targets
]
else:
subset_result = try_(
Copy link
Member Author

Choose a reason for hiding this comment

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

N.B.: This is the old logic and it still applies for lock files with more than one locked resolve within; so these locks will still be subject to the more strict locked resolve selection rules and potentially fail to sync. I'll add a comment here pointing to an issue that tracks possibly handling those style locks better with a less strict, but still correct, scheme for selecting locked resolves to sync.

Copy link
Member Author

Choose a reason for hiding this comment

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

Comment added and filed #2386

@jsirois
Copy link
Member Author

jsirois commented Mar 17, 2024

@cburroughs this is good to go afaict.

  • "Conflicting output": It would be good to improve this, but it seems pretty harmless. Without a repro case I'd like to push forward.
  • "Extras/Constraints": I did no fix here, but I also don't repro and the code that generates the tmp constraints file in the Pex lock update code derives the constraints from Pin objects and not any original requirements; so it seems impossible for that file to contain markers or extras.
  • "Interpreter Constraints and friends":
    Integration tests were added and your example now yields:
    :; cp /tmp/a.json /tmp/a.json.orig && python3.10 -m pex.cli lock sync '--indent=2'  --manylinux manylinux2014 --interpreter-constraint $'CPython==3.11.*' --style=universal --pip-version=23.3.2 --resolver-version pip-2020-resolver --target-system=linux --target-system=mac -r /tmp/a-req.txt --lock /tmp/a.json 
    Updates for lock generated by universal:
      Updated ansicolors 1.1.8 artifacts:
        + https://files.pythonhosted.org/packages/76/31/7faed52088732704523c259e24c26ce6f2f33fbeff2ff59274560c27628e/ansicolors-1.1.8.zip
      Updated anyio 4.3 artifacts:
        + https://files.pythonhosted.org/packages/db/4d/3970183622f0330d3c23d9b8a5f52e365e50381fd484d08e3285104333d3/anyio-4.3.0.tar.gz
      Updated certifi 2024.2.2 artifacts:
        + https://files.pythonhosted.org/packages/71/da/e94e26401b62acd6d91df2b52954aceb7f561743aa5ccc32152886c76c96/certifi-2024.2.2.tar.gz
      Updated charset-normalizer 3.3.2 artifacts:
        + https://files.pythonhosted.org/packages/05/31/e1f51c76db7be1d4aef220d29fbfa5dbb4a99165d9833dcbf166753b6dc0/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl
        + https://files.pythonhosted.org/packages/07/07/7e554f2bbce3295e191f7e653ff15d55309a9ca40d0362fcdab36f01063c/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl
        + https://files.pythonhosted.org/packages/19/28/573147271fd041d351b438a5665be8223f1dd92f273713cb882ddafe214c/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl
        + https://files.pythonhosted.org/packages/1e/49/7ab74d4ac537ece3bc3334ee08645e231f39f7d6df6347b29a74b0537103/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl
        + https://files.pythonhosted.org/packages/2d/dc/9dacba68c9ac0ae781d40e1a0c0058e26302ea0660e574ddf6797a0347f7/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl
        + https://files.pythonhosted.org/packages/3e/33/21a875a61057165e92227466e54ee076b73af1e21fe1b31f1e292251aa1e/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl
        + https://files.pythonhosted.org/packages/40/26/f35951c45070edc957ba40a5b1db3cf60a9dbb1b350c2d5bef03e01e61de/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
        + https://files.pythonhosted.org/packages/63/09/c1bc53dab74b1816a00d8d030de5bf98f724c52c1635e07681d312f20be8/charset-normalizer-3.3.2.tar.gz
        + https://files.pythonhosted.org/packages/68/77/02839016f6fbbf808e8b38601df6e0e66c17bbab76dff4613f7511413597/charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl
        + https://files.pythonhosted.org/packages/74/f1/0d9fe69ac441467b737ba7f48c68241487df2f4522dd7246d9426e7c690e/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl
        + https://files.pythonhosted.org/packages/cf/7c/f3b682fa053cc21373c9a839e6beba7705857075686a05c72e0f8c4980ca/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl
        + https://files.pythonhosted.org/packages/d8/b5/eb705c313100defa57da79277d9207dc8d8e45931035862fa64b625bfead/charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl
        + https://files.pythonhosted.org/packages/dd/51/68b61b90b24ca35495956b718f35a9756ef7d3dd4b3c1508056fa98d1a1b/charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl
        + https://files.pythonhosted.org/packages/e4/a6/7ee57823d46331ddc37dd00749c95b0edec2c79b15fc0d6e6efb532e89ac/charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
        - https://files.pythonhosted.org/packages/05/8c/eb854996d5fef5e4f33ad56927ad053d04dc820e4a3d39023f35cad72617/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl
        - https://files.pythonhosted.org/packages/2b/61/095a0aa1a84d1481998b534177c8566fdc50bb1233ea9a0478cd3cc075bd/charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl
        - https://files.pythonhosted.org/packages/33/c3/3b96a435c5109dd5b6adc8a59ba1d678b302a97938f032e3770cc84cd354/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl
        - https://files.pythonhosted.org/packages/3f/ba/3f5e7be00b215fa10e13d64b1f6237eb6ebea66676a41b2bcdd09fe74323/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl
        - https://files.pythonhosted.org/packages/43/05/3bf613e719efe68fb3a77f9c536a389f35b95d75424b96b426a47a45ef1d/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl
        - https://files.pythonhosted.org/packages/46/6a/d5c26c41c49b546860cc1acabdddf48b0b3fb2685f4f5617ac59261b44ae/charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl
        - https://files.pythonhosted.org/packages/58/78/a0bc646900994df12e07b4ae5c713f2b3e5998f58b9d3720cce2aa45652f/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl
        - https://files.pythonhosted.org/packages/a8/31/47d018ef89f95b8aded95c589a77c072c55e94b50a41aa99c0a2008a45a4/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl
        - https://files.pythonhosted.org/packages/b8/60/e2f67915a51be59d4539ed189eb0a2b0d292bf79270410746becb32bc2c3/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
        - https://files.pythonhosted.org/packages/cc/94/f7cf5e5134175de79ad2059edf2adce18e0685ebdb9227ff0139975d0e93/charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl
        - https://files.pythonhosted.org/packages/da/f1/3702ba2a7470666a62fd81c58a4c40be00670e5006a67f4d626e57f013ae/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
        - https://files.pythonhosted.org/packages/eb/5c/97d97248af4920bc68687d9c3b3c0f47c910e21a8ff80af4565a576bd2f0/charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl
        - https://files.pythonhosted.org/packages/f6/93/bb6cbeec3bf9da9b2eba458c15966658d1daa8b982c642f81c93ad9b40e1/charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl
      Updated idna 3.6 artifacts:
        + https://files.pythonhosted.org/packages/bf/3f/ea4b9117521a1e9c50344b909be7886dd00a519552724809bb1f486986c2/idna-3.6.tar.gz
      Updated requests 2.31 artifacts:
        + https://files.pythonhosted.org/packages/9d/be/10918a2eac4ae9f02f6cfe6414b7a155ccd8f7f9d4380d62fd5b955065c3/requests-2.31.0.tar.gz
      Updated sniffio 1.3.1 artifacts:
        + https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz
      Updated urllib3 2.2.1 artifacts:
        + https://files.pythonhosted.org/packages/7a/50/7fd50a27caa0652cd4caf224aa87741ea41d3265ad13f010886167cfcc79/urllib3-2.2.1.tar.gz
      Updated watchfiles 0.21 artifacts:
        + https://files.pythonhosted.org/packages/bd/51/d7539aa258d8f0e5d7b870af8b9b8964b4f88a1e4517eeb8a2efb838e9b3/watchfiles-0.21.0-cp311-cp311-musllinux_1_1_x86_64.whl
        + https://files.pythonhosted.org/packages/0e/cf/126f0a8683f326d190c3539a769e45e747a80a5fcbf797de82e738c946ae/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
        + https://files.pythonhosted.org/packages/20/6e/6cffd795ec65dbc82f15d95b73d3042c1ddaffc4dd25f6c8240bfcf0640f/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl
        + https://files.pythonhosted.org/packages/35/e0/e8a9c1fe30e98c5b3507ad381abc4d9ee2c3b9c0ae62ffe9c164a5838186/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
        + https://files.pythonhosted.org/packages/57/b9/2667286003dd305b81d3a3aa824d3dfc63dacbf2a96faae09e72d953c430/watchfiles-0.21.0-cp311-cp311-macosx_10_7_x86_64.whl
        + https://files.pythonhosted.org/packages/5d/12/e1d1d220c5b99196eea38c9a878964f30a2b55ec9d72fd713191725b35e8/watchfiles-0.21.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.whl
        + https://files.pythonhosted.org/packages/66/79/0ee412e1228aaf6f9568aa180b43cb482472de52560fbd7c283c786534af/watchfiles-0.21.0.tar.gz
        + https://files.pythonhosted.org/packages/a3/87/6793ac60d2e20c9c1883aec7431c2e7b501ee44a839f6da1b747c13baa23/watchfiles-0.21.0-cp311-cp311-macosx_11_0_arm64.whl
        + https://files.pythonhosted.org/packages/ba/66/873739dd7defdfaee4b880114de9463fae18ba13ae2ddd784806b0ee33b6/watchfiles-0.21.0-cp311-cp311-musllinux_1_1_aarch64.whl
        + https://files.pythonhosted.org/packages/d5/2a/f9633279d8937ad84c532997405dd106fa6100e8d2b83e364f1c87561f96/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl
        + https://files.pythonhosted.org/packages/d7/49/9b2199bbf3c89e7c8dd795fced9dac29f201be8a28a5df0c8ff625737df6/watchfiles-0.21.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl
        - https://files.pythonhosted.org/packages/70/76/8d124e14cf51af4d6bba926c7473f253c6efd1539ba62577f079a2d71537/watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
        - https://files.pythonhosted.org/packages/14/d0/662800e778ca20e7664dd5df57751aa79ef18b6abb92224b03c8c2e852a6/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl
        - https://files.pythonhosted.org/packages/18/c4/ad5ad16cad900a29aaa792e0ed121ff70d76f74062b051661090d88c6dfd/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl
        - https://files.pythonhosted.org/packages/37/17/4825999346f15d650f4c69093efa64fb040fbff4f706a20e8c4745f64070/watchfiles-0.21.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
        - https://files.pythonhosted.org/packages/41/0e/3333b986b1889bb71f0e44b3fac0591824a679619b8b8ddd70ff8858edc4/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
        - https://files.pythonhosted.org/packages/4e/d2/769254ff04ba88ceb179a6e892606ac4da17338eb010e85ca7a9c3339234/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl
        - https://files.pythonhosted.org/packages/5b/79/ecd0dfb04443a1900cd3952d7ea6493bf655c2db9a0d3736a5d98a15da39/watchfiles-0.21.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl
        - https://files.pythonhosted.org/packages/62/66/7463ceb43eabc6deaa795c7969ff4d4fd938de54e655035483dfd1e97c84/watchfiles-0.21.0-pp310-pypy310_pp73-macosx_10_7_x86_64.whl
        - https://files.pythonhosted.org/packages/6e/85/ea2a035b7d86bf0a29ee1c32bc2df8ad4da77e6602806e679d9735ff28cb/watchfiles-0.21.0-cp310-cp310-macosx_10_7_x86_64.whl
        - https://files.pythonhosted.org/packages/92/ff/75cc1b30c5abcad13a2a72e75625ec619c7a393028a111d7d24dba578d5e/watchfiles-0.21.0-cp310-cp310-musllinux_1_1_aarch64.whl
        - https://files.pythonhosted.org/packages/9a/65/12cbeb363bf220482a559c48107edfd87f09248f55e1ac315a36c2098a0f/watchfiles-0.21.0-cp310-cp310-musllinux_1_1_x86_64.whl
        - https://files.pythonhosted.org/packages/b5/e5/240e5eb3ff0ee3da3b028ac5be2019c407bdd0f3fdb02bd75fdf3bd10aff/watchfiles-0.21.0-cp310-cp310-macosx_11_0_arm64.whl
        - https://files.pythonhosted.org/packages/f7/4b/b90dcdc3bbaf3bb2db733e1beea2d01566b601c15fcf8e71dfcc8686c097/watchfiles-0.21.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
        - https://files.pythonhosted.org/packages/fe/a3/42686af3a089f34aba35c39abac852869661938dae7025c1a0580dfe0fbf/watchfiles-0.21.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl
    

@jsirois
Copy link
Member Author

jsirois commented Mar 18, 2024

Ok, I looked more deeply at "Extras/Constraints" and although I cannot repro using that case, I do see a bug in the code by inspection. I'll add a failing test and fix.

Previously contraints were taken directly from requirement strings which
led to invalid constraints for requirements with extras or a direct
reference URL. Introduce a distinct `Constraint` type that conforms to
Pip's constraint expectations and fix up the lock model and lock updates
to use `Constraints` to ensure Pip's expectations are met.
@jsirois
Copy link
Member Author

jsirois commented Mar 18, 2024

Alright @cburroughs this should be truly good to hammer now. The only outstanding known issue is the cosmetic "updates: no updates" message.

original_locked_requirements = collect_locked_requirements()
assert ProjectName("sphinx-click") not in original_locked_requirements

run_pex3("lock", "sync", "shiv[rtd]==1.0.5", "--indent", "2", "--lock", lock).assert_success()
Copy link
Member Author

Choose a reason for hiding this comment

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

Before the fix this failed with:

E       AssertionError: integration test failed: return_code=1, output=, error=Encountered 1 error updating /tmp/pytest-of-jsirois/pytest-9/test_sync_extras0/lock.json:
E       1.) cp312-cp312-manylinux_2_35_x86_64: /tmp/lock_update.zcz8gryb.constraints.txt line 4:
E       shiv[rtd]==1.0.5
E       Constraint files do not support VCS, URL or local project requirementsand they do not support requirements with extras. Search for 'We are also changing our support for Constraints Files' here: https://pip.pypa.io/en/stable/user_guide/#changes-to-the-pip-dependency-resolver-in-20-3-2020.

@cburroughs
Copy link
Collaborator

Alright @cburroughs this should be truly good to hammer now. The only outstanding known issue is the cosmetic "updates: no updates" message.

Thanks! Have been swamped but will try to get my hammer out tomorrow.

@cburroughs
Copy link
Collaborator

Conflicting output

Still haven't figured out what triggers this

Extras/Constraints

👍

Sorry for my slowness. My pattern here has been to hammer on a large internal lockfile, find something that errors out, and then tease out if that's a real thing that happens in public. Usually that has involved a bunch of stumbling over internal packages, local PyPi registries, PEBCAK, or other things that don't generalize. I think I've found a case where if the transitive dependencies are strict (==) it interferes with the generated constraints. That is in this example I think the user intent is reasonably clear ("change the version of this single package") but the sync has a conflict:

$ python3.10 -m pex.cli lock create '--indent=2'  --manylinux manylinux2014 --interpreter-constraint $'CPython==3.10.*' --style=universal --pip-version=23.3.2 --resolver-version pip-2020-resolver --target-system=linux --target-system=mac  'acryl-datahub[postgres,snowflake]==0.12.1.5' -o tmp6/lock.json
$ python3.10 -m pex.cli lock sync '--indent=2'  --manylinux manylinux2014 --interpreter-constraint $'CPython==3.10.*' --style=universal --pip-version=23.3.2 --resolver-version pip-2020-resolver --target-system=linux --target-system=mac  'acryl-datahub[postgres,snowflake]==0.12.1.4' --lock tmp6/lock.json
ERROR: Given the lock requirements:
acryl-datahub[postgres,snowflake]==0.12.1.4

The following lock update constraints could not all be satisfied:
acryl-datahub==0.12.1.4
acryl-datahub-classify==0.0.9
acryl-sqlglot==20.4.1.dev14
aiohttp==3.9.3
aiosignal==1.3.1
altair==4.2
anyio==3.7.1
appnope==0.1.4
argon2-cffi==23.1
argon2-cffi-bindings==21.2
asn1crypto==1.5.1
asttokens==2.4.1
async-timeout==4.0.3
attrs==23.2
avro==1.11.3
avro-gen3==0.7.11
beautifulsoup4==4.12.3
bleach==6.1
blis==0.7.11
cached-property==1.5.2
catalogue==2.0.10
certifi==2024.2.2
cffi==1.16
charset-normalizer==3.3.2
click==8.1.7
click-default-group==1.2.4
click-spinner==0.1.10
colorama==0.4.6
comm==0.2.2
confection==0.1.4
cryptography==42.0.5
cymem==2.0.8
debugpy==1.8.1
decorator==5.1.1
defusedxml==0.7.1
deprecated==1.2.14
docker==7
entrypoints==0.4
exceptiongroup==1.2
executing==2.0.1
expandvars==0.12
fastjsonschema==2.19.1
filelock==3.13.3
frozenlist==1.4.1
geoalchemy2==0.14.6
great-expectations==0.15.50
greenlet==3.0.3
humanfriendly==10
idna==3.6
ijson==3.2.3
importlib-metadata==7.1
ipaddress==1.0.23
ipykernel==6.17.1
ipython==8.21
ipython-genutils==0.2
ipywidgets==8.1.2
iso3166==2.1.1
jedi==0.19.1
jinja2==3.1.3
jsonpatch==1.33
jsonpointer==2.4
jsonref==1.1
jsonschema==4.21.1
jsonschema-specifications==2023.12.1
jupyter-client==7.4.9
jupyter-core==4.12
jupyter-server==1.24
jupyterlab-pygments==0.3
jupyterlab-widgets==3.0.10
langcodes==3.3
makefun==1.15.2
markupsafe==2.1.5
marshmallow==3.21.1
matplotlib-inline==0.1.6
mistune==3.0.2
mixpanel==4.10.1
msal==1.28
multidict==6.0.5
murmurhash==1.0.10
mypy-extensions==1
nbclassic==1
nbclient==0.6.3
nbconvert==7.16.3
nbformat==5.10.2
nest-asyncio==1.6
notebook==6.5.6
notebook-shim==0.2.4
numpy==1.26.4
packaging==24
pandas==2.2.1
pandocfilters==1.5.1
parso==0.8.3
pathlib-abc==0.1.1
pathy==0.11
pexpect==4.9
phonenumbers==8.13
platformdirs==3.11
preshed==3.0.9
progressbar2==4.4.2
prometheus-client==0.20
prompt-toolkit==3.0.43
psutil==5.9.8
psycopg2-binary==2.9.9
ptyprocess==0.7
pure-eval==0.2.2
py==1.11
pycountry==23.12.11
pycparser==2.21
pydantic==1.10.14
pygments==2.17.2
pyjwt==2.8
pyopenssl==24.1
pyparsing==3.1.2
python-dateutil==2.9.post0
python-stdnum==1.20
python-utils==3.8.2
pytz==2024.1
pyyaml==6.0.1
pyzmq==24.0.1
referencing==0.34
requests==2.31
requests-file==2
rpds-py==0.18
ruamel-yaml==0.17.17
schwifty==2024.1.1.post0
scipy==1.12
send2trash==1.8.2
sentry-sdk==1.43
setuptools==69.2
six==1.16
smart-open==6.4
sniffio==1.3.1
snowflake-connector-python==3.7.1
snowflake-sqlalchemy==1.5.1
sortedcontainers==2.4
soupsieve==2.5
spacy==3.5
spacy-legacy==3.0.12
spacy-loggers==1.0.5
sqlalchemy==1.4.52
sqlparse==0.4.4
srsly==2.4.8
stack-data==0.6.3
tabulate==0.9
termcolor==2.4
terminado==0.18.1
thinc==8.1.12
tinycss2==1.2.1
toml==0.10.2
tomlkit==0.12.4
toolz==0.12.1
tornado==6.4
tqdm==4.66.2
traitlets==5.2.1.post0
typer==0.7
typing-extensions==4.10
typing-inspect==0.9
tzdata==2024.1
tzlocal==5.2
urllib3==1.26.18
vininfo==1.8
wasabi==1.1.2
wcwidth==0.2.13
webencodings==0.5.1
websocket-client==1.7
widgetsnbextension==4.0.10
wrapt==1.16
yarl==1.9.4
zipp==3.18.1

Encountered 1 error updating tmp6/lock.json:
1.) cp310-cp310-manylinux_2_37_x86_64: pid 17028 -> /home/ecsb/.pex/venvs/5c1db138e499727676fa1d0ca449202de8f6f34d/5985ed09b49a653d6596b0e14d134c5456cf1a9f/bin/python -sE /home/ecsb/.pex/venvs/5c1db138e499727676fa1d0ca449202de8f6f34d/5985ed09b49a653d6596b0e14d134c5456cf1a9f/pex --disable-pip-version-check --no-python-version-warning --exists-action a --no-input --isolated -q --cache-dir /home/ecsb/.pex/pip/23.3.2/pip_cache --log /tmp/pex-pip-log.6yqis34i/pip.log download --dest /tmp/tmp_a25uxoz/usr.bin.python3.10 --constraint /tmp/lock_update.53w8bkah.constraints.txt acryl-datahub[postgres,snowflake]==0.12.1.4 --retries 5 --timeout 15 exited with 1 and STDERR:
ERROR: Cannot install acryl-datahub[postgres,snowflake]==0.12.1.4 because these package versions have conflicting dependencies.
ERROR: ResolutionImpossible: for help visit https://pip.pypa.io/en/latest/topics/dependency-resolution/#dealing-with-dependency-conflicts
 
 The conflict is caused by:
     acryl-datahub[postgres,snowflake] 0.12.1.4 depends on spacy==3.4.3; extra == "snowflake"
     The user requested (constraint) spacy==3.5
 
 To fix this you could try to:
 1. loosen the range of package versions you've specified
 2. remove package versions to allow pip attempt to solve the dependency conflict

@jsirois
Copy link
Member Author

jsirois commented Mar 26, 2024

@cburroughs my next work stint runs the afternoon of 3/27 through 4/1. I'll take a look then.

@jsirois
Copy link
Member Author

jsirois commented Mar 27, 2024

Okay, @cburroughs that last behavior you mentioned is what you get. You can fix by following the reccomendation (and then once more when you get a new but similar conflict) to arrive at a successful:

:; python3.10 -m pex.cli lock sync --indent 2 --interpreter-constraint "CPython==3.10.*" --style universal --pip-version 23.3.2 --resolver-version pip-2020-resolver --target-system linux --target-system mac "acryl-datahub[postgres,snowflake]==0.12.1.4" spacy==3.4.3 "wasabi>=0.9.1,<1.1.0" --lock lock.json
Updates for lock generated by universal:
  Updated acryl-datahub from 0.12.1.5 to 0.12.1.4
  Updated spacy from 3.5 to 3.4.3
  Updated wasabi from 1.1.2 to 0.10.1
Updates to lock input requirements:
  Updated 'acryl-datahub[postgres,snowflake]==0.12.1.5' to 'acryl-datahub[postgres,snowflake]==0.12.1.4'
  Added 'spacy==3.4.3'
  Added 'wasabi<1.1.0,>=0.9.1'

The issue here is that acryl-datahub pins a whole heck of alot of deps (instead of, say, constraining to a non-breaking semver range):

:; unzip -qc ~/downloads/acryl_datahub-0.12.1.4-py3-none-any.whl acryl_datahub-0.12.1.4.dist-info/METADATA | grep Requires | grep -E "==[0-9]" | cut -d';' -f1 | sort -u
Requires-Dist: acryl-datahub-airflow-plugin ==0.12.1.4
Requires-Dist: acryl-datahub-classify ==0.0.9
Requires-Dist: acryl-pyhive[hive-pure-sasl] ==0.6.16
Requires-Dist: acryl-sqlglot ==20.4.1.dev14
Requires-Dist: avro-gen3 ==0.7.11
Requires-Dist: avro-gen3 ==0.7.11
Requires-Dist: black ==22.12.0
Requires-Dist: boto3-stubs[glue,s3,sagemaker,sts] ==1.28.15
Requires-Dist: elasticsearch ==7.13.4
Requires-Dist: flake8-bugbear ==23.3.12
Requires-Dist: google-cloud-datacatalog-lineage ==0.2.2
Requires-Dist: lark[regex] ==1.1.4
Requires-Dist: looker-sdk ==23.0.0
Requires-Dist: msal ==1.22.0
Requires-Dist: mypy ==1.0.0
Requires-Dist: mypy-boto3-sagemaker ==1.28.15
Requires-Dist: spacy ==3.4.3
Requires-Dist: sql-metadata ==2.2.2
Requires-Dist: sqllineage ==1.3.8
Requires-Dist: sqlparse ==0.4.4
Requires-Dist: types-click ==0.1.12
Requires-Dist: vertica-sqlalchemy-dialect[vertica-python] ==0.0.8.1

That sort of mass pinning will lead to issues when the pins change in another version given that Pex updates have these rules:

  1. Pex is not a resolver, it uses Pip to do resolving; as such Pex cannot backtrack, etc. Pip does all that opaquely.
  2. The pex3 lock {update,sync} commands attempt to do a minimal update by masking off unchanged portions of the resolve (as can be divined from the input requirements alone anyhow) with constraints pinning.

Afaict, to have these sorts of situations resolve automatically would either require Pex tries, fails, retries switching constraints pins to semver range pins? But that would lead to more disruptive lock updates growing akin to re-running pex3 lock create - which I think is pointedly the thing you're trying to avoid / fix.

So, how do you wish to proceed? Afaict Pex cannot do better than this without becoming a resolver itself which is not in the cards in Pex 2.x. The user has to be able to read the Pip conflict output and do what I did above or else Pants needs to have an escape hatch to re-run pex3 lock create for locks that are overly constrained preventing update. Maybe though there is some clever strategy that uses constraints ranging instead of pinning to cut down on lock disturbance but still allow updates.

@jsirois
Copy link
Member Author

jsirois commented Mar 27, 2024

@cburroughs I may move forward with this iteration this afternoon. I'm getting uncomfortable with the number of changes queued behind a release and it seems like Pants is at liberty to skip this initial release of pex3 lock sync if you find improvements or bugs that we can follow up with.

@cburroughs
Copy link
Collaborator

(Yeah some of these large packages with narrow pins are challenging to deal with in a few different ways.)

The mental model of "Pex is not a resolver and thus can't 'backtrack'" makes sense to me. I'll have to think about how to present that through Pants in a way that's helpful without getting in the users way, but that was the essential design problem there anyway.

Thanks for putting this all together.

@jsirois
Copy link
Member Author

jsirois commented Mar 28, 2024

I'll have to think about how to present that through Pants in a way that's helpful without getting in the users way

One thing to point out is rm lock.json && pex3 lock sync ... reverts to the old Pants behavior of re-locking every time; so, at worst, if a user can't figure out Pip conflict messages and Pants gives some guidance, you can always work your way out of an overly constrained lock by re-creating it this way even if pex3 lock sync ... is the only command Pants uses behind the scenes.

@jsirois jsirois merged commit 56ae935 into pex-tool:main Mar 28, 2024
26 checks passed
@jsirois jsirois deleted the issues/2344 branch March 28, 2024 20:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Support syncing input requirements to a lock file more easily.
5 participants