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

The flag does not respect package resolver results and leads to inconsistent lock files. #5613

Closed
matejsp opened this issue Feb 17, 2023 · 22 comments · Fixed by #5617
Closed
Labels
--keep-outdated/--selective-upgrade PR: awaiting-review The PR related to this issue is awaiting review by a maintainer. Type: Question ❔ This is a question or a request for support.

Comments

@matejsp
Copy link

matejsp commented Feb 17, 2023

Issue description

We have just updated pipenv and were negatively surprised that this feature is about to be removed.

The flag --keep-outdated has been deprecated for removal.The flag does not respect package resolver results and leads to inconsistent lock files. Please pin relevant requirements in your Pipfile and discontinue use of this flag.

I never saw any discussion regarding keeping keep-outdated flag but this feature is one of the most useful features in pipenv at least in very large projects such as monoliths. Please don't remove it. #5544 by @matteius

In our project we have around 110 depedencies and we rely on pipenv keep-updated so the developers update only their and not all dependencies. We specify only directly used dependencies and rely on lock file to have a list of transient dependencies.
We occasionally pin transient dependencies when we see incompatibilities.

Since we have the work split between different teams and dependencies are common in our monolith we have a problem if one team member updates all other dependencies and very often breaks the project. Some (binary wheels) are really problematic such as cryptography because it could fails just on some target systems.

Based on proposal that we should pin ALL transient dependencies if we want to retain control over dependencies making everything unreadable and hard to maintain:

  • you cannot distinct between pinning a transient or first level dependency (at least in not current Pipfile format)
  • you cannot do updates one by one without affecting all other dependencies
  • you cannot split dev process of updating all dependencies a release with just updated dependencies
  • pipenv graph is broken since it displays all pinned dependencies as first level dependencies.
  • every developer when introducing new depedency or even upgrading will need to track down all the transient dependencies and pin them. if they don't other developers will broke their code by accidentally updating their dependencies.

Basically we rely on pipenv to do hard lifting for us and not manually track all dependencies. Manual work is error prone and would like to avoid. This is such important feature to us that we will migrate to another solution (like poetry).

The main reason why we even need such feature at all is because pipenv is lacking tooling for updating one by one dependency:
Like https://python-poetry.org/docs/cli/#update or npm or basically every other dep manager.

Expected result

Retain this feature or introduce new commands for updating controllably dependencies.

Actual result

The flag --keep-outdated has been deprecated for removal.The flag does not respect package resolver results and leads to inconsistent lock files. Please pin relevant requirements in your Pipfile and discontinue use of this flag.

Steps to replicate

use flag --keep-outdated

$ pipenv --support

Pipenv version: '2023.2.4'

Pipenv location: '/Users/myuser/.virtualenvs/bitstamp38/lib/python3.8/site-packages/pipenv'

Python location: '/Users/myuser/.virtualenvs/bitstamp38/bin/python3'

OS Name: 'posix'

User pip version: '22.3.1'

user Python installations found:

  • 3.11.2: /usr/local/bin/python3
  • 3.10.10: /Users/myuser/.pyenv/versions/3.10.10/bin/python3
  • 3.10.7: /Users/myuser/.pyenv/versions/3.10.7/bin/python3
  • 3.10.6: /Users/myuser/.pyenv/versions/3.10.6/bin/python3
  • 3.9.16: /usr/local/bin/python3.9
  • 3.9.13: /Users/myuser/.pyenv/versions/3.9.13/bin/python3
  • 3.9.6: /usr/bin/python3
  • 3.8.16: /Users/myuser/.virtualenvs/bitstamp38/bin/python3
  • 3.8.16: /Users/myuser/.virtualenvs/bitstamp38/bin/python
  • 3.8.16: /Users/myuser/.virtualenvs/bitstamp38/bin/python3
  • 3.8.16: /Users/myuser/.virtualenvs/bitstamp38/bin/python
  • 3.8.16: /usr/local/bin/python3.8
  • 3.8.16: /Users/myuser/.pyenv/versions/3.8.16/bin/python3
  • 3.8.13: /Users/myuser/.pyenv/versions/3.8.13/bin/python3
  • 3.8.12: /Users/myuser/.pyenv/versions/bitstamp38/bin/python3
  • 3.8.12: /Users/myuser/.pyenv/versions/3.8.12/bin/python3
  • 3.6.8: /usr/local/bin/python3.6
  • 3.6.8: /usr/local/bin/python3.6m

PEP 508 Information:

{'implementation_name': 'cpython',
 'implementation_version': '3.8.16',
 'os_name': 'posix',
 'platform_machine': 'x86_64',
 'platform_python_implementation': 'CPython',
 'platform_release': '22.3.0',
 'platform_system': 'Darwin',
 'platform_version': 'Darwin Kernel Version 22.3.0: Thu Jan  5 20:53:49 PST '
                     '2023; root:xnu-8792.81.2~2/RELEASE_X86_64',
 'python_full_version': '3.8.16',
 'python_version': '3.8',
 'sys_platform': 'darwin'}

System environment variables:

  • TERM_PROGRAM
  • SHELL
  • TERM
  • TMPDIR
  • TERM_PROGRAM_VERSION
  • TERM_SESSION_ID
  • USER
  • SSH_AUTH_SOCK
  • PATH
  • LaunchInstanceID
  • __CFBundleIdentifier
  • PWD
  • XPC_FLAGS
  • XPC_SERVICE_NAME
  • SHLVL
  • HOME
  • LOGNAME
  • SECURITYSESSIONID
  • OLDPWD
  • ZSH
  • PAGER
  • LESS
  • LSCOLORS
  • _VIRTUALENVWRAPPER_API
  • VIRTUALENVWRAPPER_SCRIPT
  • VIRTUALENVWRAPPER_PYTHON
  • NVM_DIR
  • NVM_CD_FLAGS
  • NVM_BIN
  • NVM_INC
  • VIRTUALENVWRAPPER_PROJECT_FILENAME
  • VIRTUALENVWRAPPER_WORKON_CD
  • WORKON_HOME
  • VIRTUALENVWRAPPER_HOOK_DIR
  • VIRTUAL_ENV
  • PS1
  • CD_VIRTUAL_ENV
  • LANG
  • LC_ALL
  • LC_CTYPE
  • _
  • __CF_USER_TEXT_ENCODING
  • PIP_DISABLE_PIP_VERSION_CHECK
  • PIP_PYTHON_PATH
  • PYTHONDONTWRITEBYTECODE
  • PYTHONFINDER_IGNORE_UNSUPPORTED

Pipenv–specific environment variables:

Debug–specific environment variables:

  • PATH: /Users/myuser/.virtualenvs/b38/bin:/Users/myuser/.rd/bin:/Users/myuser/.nvm/versions/node/v14.18.2/bin:/Users/myuser/apache-maven-3.8.2/bin:/usr/local/opt/mysql@5.7/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin
  • SHELL: /bin/bash
  • LANG: en_US.UTF-8
  • PWD: /Users/myuser/projects/b/pipenvs
  • VIRTUAL_ENV: /Users/myuser/.virtualenvs/b38

@matteius
Copy link
Member

In our project we have around 110 dependencies and we rely on pipenv keep-updated so the developers update only their and not all dependencies

How do you handle when the resolver creates an inconsistent or lock file that simply cannot be installed? The whole point of removing --keep-outdated is because it creates tons of support tickets to the pipenv maintainers about why their project lock file has issues, and it when numerous times it has relates back to the implementation of --keep-outdated, it has been determined there is no fix --keep-outdated because the idea behind it is flawed. It requires a complete lock lock file and takes that and adds any new things from a complete package resolution without any regard for what is compatible in the dependency chain.

Since we have the work split between different teams and dependencies are common in our monolith we have a problem if one team member updates all other dependencies and very often breaks the project. Some (binary wheels) are really problematic such as cryptography because it could fails just on some target systems.

Ok, pin the appropriate problematic packages and start using named package categories where it makes sense if some of these different teams don't actually need the whole set of 110 packages.

Based on proposal that we should pin ALL transient dependencies if we want to retain control over dependencies making everything unreadable and hard to maintain:

The proposal was never that you pin ALL transient dependencies, that is exactly what the Lock file is -- the proposal was that you pin ALL problematic dependencies such that you can trust when you lock that the specifiers you don't want to be affected won't be. It was less of a proposal -- this is how pipenv is intended to work and --keep-outdated was never documented because it was a hack meant to appease those that refuse to listen about how the resolver requirements and the intentions of the Pipfile.

Basically we rely on pipenv to do hard lifting for us and not manually track all dependencies. . Manual work is error prone and would like to avoid. This is such important feature to us that we will migrate to another solution (like poetry).

--keep-outdated is also quite error prone and I would like if you told that side of the story. Have you personally encountered any times that --keep-outdated didn't actually yield a healthy or valid result? I am not sure I understand the argument that manual work is error prone, or what the manual work is even that you are describing? Using the Pipfile more appropriately should be a goal for any organization; I would also be curious to hear about how poetry doesn't pull in transient dependencies and installs things one-by-one -- that doesn't really line up with my understanding of how that tool works.

I personally cannot support the --keep-outdated flag and I have never heard of a reasonable proposal of how to make it create consistent locking results.

@matteius
Copy link
Member

Here is an example of the problem:

Let's try: First I install old Django, imagine this was some time ago:
pipenv install Django==2.*

Then now today I want to add django-model-utils:
pipenv install django-model-utils==4.2.0 --keep-outdated

So it "works" and generates a lockfile, but it is not valid. Django-model-utils 4.2.0 will not work with even Django 3.0 (support was dropped) so having it install 4.2.0 along side 2.2.28 is begging for problems.

The Lockfile:

matteius@matteius-VirtualBox:~/pipenv-triage/pipenv-4536$ cat Pipfile.lock 
{
    "_meta": {
        "hash": {
            "sha256": "44175eea762b2510933033b1a915a8cfeaa2edbe36be9d5407be47cc103fef52"
        },
        "pipfile-spec": 6,
        "requires": {
            "python_version": "3.10"
        },
        "sources": [
            {
                "name": "pypi",
                "url": "https://pypi.org/simple",
                "verify_ssl": true
            }
        ]
    },
    "default": {
        "django": {
            "hashes": [
                "sha256:0200b657afbf1bc08003845ddda053c7641b9b24951e52acd51f6abda33a7413",
                "sha256:365429d07c1336eb42ba15aa79f45e1c13a0b04d5c21569e7d596696418a6a45"
            ],
            "index": "pypi",
            "version": "==2.2.28"
        },
        "django-model-utils": {
            "hashes": [
                "sha256:a768a25c80514e0ad4e4a6f9c02c44498985f36c5dfdea47b5b1e8cf994beba6",
                "sha256:e7a95e102f9c9653427eadab980d5d59e1dea972913b9c9e01ac37f86bba0ddf"
            ],
            "index": "pypi",
            "version": "==4.2.0"
        },
        "pytz": {
            "hashes": [
                "sha256:1e760e2fe6a8163bc0b3d9a19c4f84342afa0a2affebfaa84b01b978a02ecaa7",
                "sha256:e68985985296d9a66a881eb3193b0906246245294a881e7c8afe623866ac6a5c"
            ],
            "version": "==2022.1"
        },
        "sqlparse": {
            "hashes": [
                "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae",
                "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"
            ],
            "markers": "python_version >= '3.5'",
            "version": "==0.4.2"
        }
    },
    "develop": {}
}

@matejsp
Copy link
Author

matejsp commented Feb 17, 2023

How do you handle when the resolver creates an inconsistent or lock file that simply cannot be installed? The whole point of removing --keep-outdated is because it creates tons of support tickets to the pipenv maintainers about why their project lock file has issues, and it when numerous times it has relates back to the implementation of --keep-outdated, it has been determined there is no fix --keep-outdated because the idea behind it is flawed. It requires a complete lock lock file and takes that and adds any new things from a complete package resolution without any regard for what is compatible in the dependency chain.

Yes it occasionally happens. That's the reason why we run pipenv lock & sync twice. Sometimes first fails and second one usually succeeds. And we also generate requirements and install into empty venv. So we can detect such issues. But it outweighs the downside of not being able to upgrade in small chunks at all. This is HUGE minus for handling packages especially in large project in production.

Ok, pin the appropriate problematic packages and start using named package categories where it makes sense if some of these different teams don't actually need the whole set of 110 packages.

One version that is not problematic today, usually is tomorrow. Every package breaks compatibility at one point. That's why we always check the release notes for each package when we upgrade it. That's why iterative upgrading is important to us. Categories don't work for us, since we have monolith and we need all packages installed at the same time. We also pick upgrading of dependencies in such way that it is easy to see if anything breaks in production (or testing). For example major version upgrade of core library is a planned effort and not something that just accidentally slips in.

The proposal was never that you pin ALL transient dependencies, that is exactly what the Lock file is -- the proposal was that you pin ALL problematic dependencies such that you can trust when you lock that the specifiers you don't want to be affected won't be. It was less of a proposal -- this is how pipenv is intended to work and --keep-outdated was never documented because it was a hack meant to appease those that refuse to listen about how the resolver requirements and the intentions of the Pipfile.

Defeats the purpose if you cannot not control what you want to upgrade. I understand it was a hack and poorly documented. But people were apparently using this feature, if they were complaining about it :) Solution to take the feature away without some alternative is a bad solution.

--keep-outdated is also quite error prone and I would like if you told that side of the story. Have you personally encountered any times that --keep-outdated didn't actually yield a healthy or valid result? I am not sure I understand the argument that manual work is error prone, or what the manual work is even that you are describing? Using the Pipfile more appropriately should be a goal for any organization; I would also be curious to hear about how poetry doesn't pull in transient dependencies and installs things one-by-one -- that doesn't really line up with my understanding of how that tool works.
I personally cannot support the --keep-outdated flag and I have never heard of a reasonable proposal of how to make it create consistent locking results.

The fact that it is error prone is not such a bit deal, because you have GIT, pull requests and CI. So if pipenv would produce incorrect results this would be caught very early in the process. No big deal really. Committing only parts of lock file seems more error prone to me.

Regarding your example:

  • what happens if you have private repository and dont upload the latest Django. Will you also get inconsistent result if you pipenv install django-model-utils==4.2.0. If that would happen then you have a bigger problem. You should detect and complain.
  • what happens if you try to install older version of django-model-utils that is not compatible with current latest Django? Will you get inconsistent result or complain or use latest version still compatible?

If you would add pipenv upgrade 1-package-name 2-package-name (and leave the rest intact) like this would remove the need for this. This is the way npm update works if you list packages. And if you upgrade library that creates incompatibility in lock file you get an error and package.json (or pipfile) is not updated remaining in the consistent state. I saw the same solution used in poetry (BUT haven't tested it, have plan for sometime in the future).

Leave it buggy, it's not perfect, but it works just fine at least for us :)

@matteius
Copy link
Member

That's the reason why we run pipenv lock & sync twice. Sometimes first fails and second one usually succeeds

Can you explain this in more detail.

If you are running pipenv lock it is essentially locking based on your Pipfile constraints and that would update all dependencies that aren't pinned anyway. The same thing install does.

Defeats the purpose if you cannot not control what you want to upgrade. Every package breaks compatibility at one point.

That is debatable -- you can control it with the specifiers in the Pipfile. Every package has the potential to break compatibility, review the packages being updated as part of the PR that adds a new dependency and decide at that time if it needs to be pinned in the Pipfile. You are describing having a testing cycle, but the scope of testing you want to control using --keep-outdated is less predictable than maintaining a well defined Pipfile.

The fact that it is error prone is not such a bit deal, because you have GIT, pull requests and CI. So if pipenv would produce incorrect results this would be caught very early in the process.

The same could be said about what is recommended -- that you go through a PR process and review your lock file constraints.

Committing only parts of lock file seems more error prone to me.

That is precisely what --keep-outdated does though, it commits only part of the lock file resolution.

If you would add pipenv upgrade 1-package-name 2-package-name (and leave the rest intact) like this would remove the need for this.

There is an upgrade option though I have not used it much -- but when you upgrade a package and it has a dependency that needs to be upgraded too, you would expect both to be upgraded, right?

Side note: Have you ever explored the behavior of --selective-upgrade? To me its a more complicated version of --keep-outdated that may be safer, but I have not done enough analysis of that code path to know for sure.

--keep-outdated in the current form is definitely on the chopping block, but there is not a timeline on when it will be removed. Having it be deprecated allows me to close any issue that the user happens to be passing that flag, because for me my time is valuable and there is no reasonable way to support it.

@matteius matteius added --keep-outdated/--selective-upgrade Type: Question ❔ This is a question or a request for support. labels Feb 17, 2023
@matteius
Copy link
Member

FWIW poetry does let you install incompatible versions of things together and won't affect the lockfile. I don't know why you would want that behavior, as the package authors already specify that they are not compatible, and the pip resolver respects that. Pipenv uses the pip resolver and poetry does not. Therefore:

matteius@matteius-VirtualBox:~/pipenv-triage/outdated$ poetry add django-model-utils==4.2.0

Updating dependencies
Resolving dependencies... (0.1s)

Writing lock file

Package operations: 1 install, 0 updates, 0 removals

  • Installing django-model-utils (4.2.0)


matteius@matteius-VirtualBox:~/pipenv-triage/outdated$ poetry add Django==2.*
Creating virtualenv outdated-cSQLnpBf-py3.10 in /home/matteius/.cache/pypoetry/virtualenvs

Updating dependencies
Resolving dependencies... Downloading https://files.pythonhosted.org/packages/97/d3/31dd2c3e48fc2060819f4acb0686248250a0f2326356306b38a42Resolving dependencies... Downloading https://files.pythonhosted.org/packages/2e/09/fbd3c46dce130958ee8e0090f910f1fe39e502cc5ba0aadca1e8aResolving dependencies... Downloading https://files.pythonhosted.org/packages/2e/09/fbd3c46dce130958ee8e0090f910f1fe39e502cc5ba0aadca1e8aResolving dependencies... (1.0s)

Writing lock file

Package operations: 3 installs, 0 updates, 0 removals

  • Installing pytz (2022.7.1)
  • Installing sqlparse (0.4.3)
  • Installing django (2.2.28)



matteius@matteius-VirtualBox:~/pipenv-triage/outdated$ cat poetry.lock 
# This file is automatically @generated by Poetry and should not be changed by hand.

[[package]]
name = "django"
version = "2.2.28"
description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design."
category = "main"
optional = false
python-versions = ">=3.5"
files = [
    {file = "Django-2.2.28-py3-none-any.whl", hash = "sha256:365429d07c1336eb42ba15aa79f45e1c13a0b04d5c21569e7d596696418a6a45"},
    {file = "Django-2.2.28.tar.gz", hash = "sha256:0200b657afbf1bc08003845ddda053c7641b9b24951e52acd51f6abda33a7413"},
]

[package.dependencies]
pytz = "*"
sqlparse = ">=0.2.2"

[package.extras]
argon2 = ["argon2-cffi (>=16.1.0)"]
bcrypt = ["bcrypt"]

[[package]]
name = "django-model-utils"
version = "4.2.0"
description = "Django model mixins and utilities"
category = "main"
optional = false
python-versions = "*"
files = [
    {file = "django-model-utils-4.2.0.tar.gz", hash = "sha256:e7a95e102f9c9653427eadab980d5d59e1dea972913b9c9e01ac37f86bba0ddf"},
    {file = "django_model_utils-4.2.0-py3-none-any.whl", hash = "sha256:a768a25c80514e0ad4e4a6f9c02c44498985f36c5dfdea47b5b1e8cf994beba6"},
]

[package.dependencies]
Django = ">=2.0.1"

[[package]]
name = "pytz"
version = "2022.7.1"
description = "World timezone definitions, modern and historical"
category = "main"
optional = false
python-versions = "*"
files = [
    {file = "pytz-2022.7.1-py2.py3-none-any.whl", hash = "sha256:78f4f37d8198e0627c5f1143240bb0206b8691d8d7ac6d78fee88b78733f8c4a"},
    {file = "pytz-2022.7.1.tar.gz", hash = "sha256:01a0681c4b9684a28304615eba55d1ab31ae00bf68ec157ec3708a8182dbbcd0"},
]

[[package]]
name = "sqlparse"
version = "0.4.3"
description = "A non-validating SQL parser."
category = "main"
optional = false
python-versions = ">=3.5"
files = [
    {file = "sqlparse-0.4.3-py3-none-any.whl", hash = "sha256:0323c0ec29cd52bceabc1b4d9d579e311f3e4961b98d174201d5622a23b85e34"},
    {file = "sqlparse-0.4.3.tar.gz", hash = "sha256:69ca804846bb114d2ec380e4360a8a340db83f0ccf3afceeb1404df028f57268"},
]

[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "b0508819df0416bff36c4a482ce7c3f0a9e7341bbbb9879bce3cbccaaeefb314"


matteius@matteius-VirtualBox:~/pipenv-triage/outdated$ poetry show
django             2.2.28   A high-level Python Web framework that encourages rapid development and clean, pragmatic design.
django-model-utils 4.2.0    Django model mixins and utilities
pytz               2022.7.1 World timezone definitions, modern and historical
sqlparse           0.4.3    A non-validating SQL parser.

@matejsp
Copy link
Author

matejsp commented Feb 18, 2023

Can you explain this in more detail.

I believe it is related to #4351 and problems regarding updating of hashes. We update dependency in pipfile and run lock --keep-outdated and in first run it updates the hashes in lock file BUT it runs install with old hashes and in another run it "fixes" itself. There is also another issue but I presume it is related to --keep-outdated, that old hashes are left inside even if version is updated.

How we run it:

pipenv lock --dev --keep-outdated --pre --clear
pipenv sync --dev --pre

That is debatable -- you can control it with the specifiers in the Pipfile. Every package has the potential to break compatibility, review the packages being updated as part of the PR that adds a new dependency and decide at that time if it needs to be pinned in the Pipfile. You are describing having a testing cycle, but the scope of testing you want to control using --keep-outdated is less predictable than maintaining a well defined Pipfile.
We want to have granular control what and when it is updated with tooling support if possible.
The same could be said about what is recommended -- that you go through a PR process and review your lock file constraints.

How would that work in practice? You cannot partially accept PR. So you do CI, QA test with 5 lib changes. You send to production and it fails for some reason. You rollback PR. Then you want to selectively update one version at the time (or even using bisection). How would pipenv help without keep outdated? Manually fiddling with lock file? We had that case multiple times. Last time was grpc lib introduced perf degradation (still opened issue with new io lib) doubling the cpu usage in production under high load increasing our latencies. We easily tracked it to grpc and pin it in pipfile, since we only upgraded grpc dep.
Good luck tracking such issues with updates all over the place. Sometimes we don't update some specific lib because we don't have enough QA resources in current week or we need more info which functionality it affects. So we should pin it in pipfile and remove it one week later?

That is precisely what --keep-outdated does though, it commits only part of the lock file resolution.

Yes, manual is more still more errors prone and fiddle with lock files directly is bad.

There is an upgrade option though I have not used it much -- but when you upgrade a package and it has a dependency that needs to be upgraded too, you would expect both to be upgraded, right?

Not in one go. I would expected to get and error if I introduced incompatibilities (NIT: flag with --update-needed).

pipenv upgrade django-model-utils==4.2.0
 You cannot upgrade because django-model-utils requires Django >= 3.0
pipenv upgrade django-model-utils==4.2.0 Django==3.2 (we want LTS not latest for example)
 OK! lock updated. Good to go!

Side note: Have you ever explored the behavior of --selective-upgrade? To me its a more complicated version of --keep-outdated that may be safer, but I have not done enough analysis of that code path to know for sure.

Thank you I will try. I haven't explored it yet, but I will try, it sounds what could work for us if it works. But I need to see how to do selective-upgrade and update pipfile version or do install of new dep.

--keep-outdated in the current form is definitely on the chopping block, but there is not a timeline on when it will be removed. Having it be deprecated allows me to close any issue that the user happens to be passing that flag, because for me my time is valuable and there is no reasonable way to support it.

I understand. That's why I would like shed light on my use case and perhaps also from other people using this feature because nobody just happens to accidently pass this flag :) Perhaps fixing this issue: #4351 would solve a lot (especially if you document those alternatives).

FWIW poetry does let you install incompatible versions of things together and won't affect the lockfile. I don't know why you would want that behavior

I don't want that and I never did. I want to have upgrading under control without breaking everything as reliably as possible. If tooling sometimes produces wrong output you can easily create automated test for this (like installing into new venv). Not perfect but acceptable.

I don't want to sound pushy or negative or anything. I really do appreciate pipenv and your work. Just want to get our use case across (and from people using this "complex" feature).

@matejsp
Copy link
Author

matejsp commented Feb 18, 2023

I have tried the solution that you proposed. I don't have time to investigate how poetry works. I still prefer pipenv for now.

I was using our own existing pipenv lock with pipfile.

Inside it was (and many many others):
requests = {extras = ["use_chardet_on_py3"], version = "==2.28.1"}
and chardet 4.0.0 as transitive depedency.

Option 1 [broken]:

pipenv update --selective-upgrade requests
  • requests was NOT updated to latest version not in lock and not in pipfile
  • hashes were added for some deps (that's fine)
  • pbr and pyparsing depedencies were removed for some reason (maybe it detected that is is no longer needed)
  • it updated chardet to latest version -> ok fine requests depends on it
  • it updated around 8 other unrelated depedencies (like isort, colorama, packaging)

Option 2 [working as workaround for direct dependency if you revert pipfile]:

 pipenv install --selective-upgrade requests==2.28.2
  • requests were changed in pipfile (but all formatting lost :( and also extras)
  • updated requests in lock file
  • no other depedencies changed (good!)
  • if I run without selective-upgrade it updates also unrelated dependencies (good!)

Option 3 [broken]:

pipenv update --selective-upgrade chardet
  • chardet is transient dependency
  • pipenv error: Warning: chardet was not found in your Pipfile! Aborting.

Option 4 [as expected]:

pipenv install --selective-upgrade chardet
  • it adds chardet = "*" to pipfile and nothing else happens since it is already in a lock file

Option 5 [working as workaround if you revert pipfile changes]:

pipenv install --selective-upgrade chardet==5.1.0
  • it adds chardet = "5.1.0" to pipfile and reformats it
  • updates only chardet in lock file
  • you need to revert whole pipfile to leave chardet transitive depedency

Option 6 [broken]:

update manually pipfile to point to requests 2.28.2
pipenv install --selective-upgrade chardet==5.1.0
  • versions are updated in lock file and pipfile for requests & chardet
  • hashes are not updated not for requests and not for chardet
  • lock file is broken and unable to recover
(.venv) ➜  py38 pipenv install --selective-upgrade chardet==5.1.0
Courtesy Notice: Pipenv found itself running within a virtual environment, so it will automatically use that environment, instead of creating its own for any project. You can set PIPENV_IGNORE_VIRTUALENVS=1 to force pipenv to ignore that environment and create its own instead. You can set PIPENV_VERBOSITY=-1 to suppress this warning.
Installing chardet==5.1.0...
Resolving chardet==5.1.0...
Installing...
Adding chardet to Pipfile's [packages] ...
✔ Installation Succeeded
Pipfile.lock (197860) out of date, updating to (606399)...
Locking [packages] dependencies...
Building requirements...
Resolving dependencies...
✔ Success!
Locking [dev-packages] dependencies...
Building requirements...
Resolving dependencies...
✔ Success!
Updated Pipfile.lock (9271a329ce45de2e5e5a762b1fc04edd891ce37724a2c3df057cd08bef606399)!
Installing dependencies from Pipfile.lock (606399)...
An error occurred while installing requests[use_chardet_on_py3]==2.28.2 --hash=sha256:907470a00798ef67935597a924c8af60aeab20e9666d76ef7e1b7793cd34a3d5 --hash=sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983! Will try again.
Installing initially failed dependencies...
[pipenv.exceptions.InstallError]: Looking in indexes: https://nexus.xyz.net/repository/pypi/simple
[pipenv.exceptions.InstallError]: Collecting requests[use_chardet_on_py3]==2.28.2
[pipenv.exceptions.InstallError]:   Using cached https://nexus.xyz.net/repository/pypi/packages/requests/2.28.2/requests-2.28.2-py3-none-any.whl (62 kB)
[pipenv.exceptions.InstallError]: ERROR: THESE PACKAGES DO NOT MATCH THE HASHES FROM THE REQUIREMENTS FILE. If you have updated the package versions, please update the hashes. Otherwise, examine the package contents carefully; someone may have tampered with them.
[pipenv.exceptions.InstallError]:     requests[use_chardet_on_py3]==2.28.2 from https://nexus.xyz.net/repository/pypi/packages/requests/2.28.2/requests-2.28.2-py3-none-any.whl#sha256=dd4a51d9add94a243743d19fb7beb5f5063255359a02b45282e1812e46087f4d (from -r /var/folders/l6/nmk965l50gv_90_c4xh5x0_4ctpk94/T/pipenv-g0dkm4nc-requirements/pipenv-zmku8mrq-hashed-reqs.txt (line 1)):
[pipenv.exceptions.InstallError]:         Expected sha256 7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983
[pipenv.exceptions.InstallError]:         Expected     or 907470a00798ef67935597a924c8af60aeab20e9666d76ef7e1b7793cd34a3d5
[pipenv.exceptions.InstallError]:              Got        dd4a51d9add94a243743d19fb7beb5f5063255359a02b45282e1812e46087f4d
ERROR: Couldn't install package: [Requirement(_name='requests', vcs=None, req=NamedRequirement(name='requests', version='==2.28.2', req=Requirement.parse('requests[use_chardet_on_py3]==2.28.2'), extras=['use_chardet_on_py3'], editable=False, _parsed_line=<Line (editable=False, name=requests, path=None, uri=None, extras=('use_chardet_on_py3',), markers=None, vcs=None, specifier===2.28.2, pyproject=None, pyproject_requires=None, pyproject_backend=None, ireq=requests[use_chardet_on_py3]==2.28.2)>), markers=None, _specifiers='==2.28.2', index='nxsbts', editable=False, hashes=frozenset({'sha256:907470a00798ef67935597a924c8af60aeab20e9666d76ef7e1b7793cd34a3d5', 'sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983'}), extras=('use_chardet_on_py3',), abstract_dep=None, _line_instance=<Line (editable=False, name=requests, path=None, uri=None, extras=('use_chardet_on_py3',), markers=None, vcs=None, specifier===2.28.2, pyproject=None, pyproject_requires=None, pyproject_backend=None, ireq=requests[use_chardet_on_py3]==2.28.2)>, _ireq=None)]
 Package installation failed...

@matejsp
Copy link
Author

matejsp commented Feb 18, 2023

Tested another case that you mentioned:

[requires]
python_version = "3.8"

[packages]
Django = "==2.2.18"
test> PIPENV_IGNORE_VIRTUALENVS=1 pipenv lock
Locking [packages] dependencies...
Building requirements...
Resolving dependencies...
✔ Success!

added to pipfile:
django-model-utils = "==4.3.1"

test> PIPENV_IGNORE_VIRTUALENVS=1 pipenv lock
or
test> PIPENV_IGNORE_VIRTUALENVS=1 pipenv lock --keep-outdated

are getting the same result (like expected!):

✘ Locking Failed!
⠇ Locking...
CRITICAL:pipenv.patched.pip._internal.resolution.resolvelib.factory:Cannot install -r /var/folders/l6/nmk965l50gv_90_c4xh5x0_4ctpk94/T/pipenvf0hll074requirements/pipenv-h_q7gcay-constraints.txt (line 3) and django==2.2.18 because these package versions have conflicting dependencies.
[ResolutionFailure]:   File "/Users/myuser/.virtualenvs/b38/lib/python3.8/site-packages/pipenv/resolver.py", line 811, in _main
[ResolutionFailure]:       resolve_packages(
[ResolutionFailure]:   File "/Users/myuser/.virtualenvs/b38/lib/python3.8/site-packages/pipenv/resolver.py", line 759, in resolve_packages
[ResolutionFailure]:       results, resolver = resolve(
[ResolutionFailure]:   File "/Users/myuser/.virtualenvs/b38/lib/python3.8/site-packages/pipenv/resolver.py", line 738, in resolve
[ResolutionFailure]:       return resolve_deps(
[ResolutionFailure]:   File "/Users/myuser/.virtualenvs/b38/lib/python3.8/site-packages/pipenv/utils/resolver.py", line 1102, in resolve_deps
[ResolutionFailure]:       results, hashes, markers_lookup, resolver, skipped = actually_resolve_deps(
[ResolutionFailure]:   File "/Users/myuser/.virtualenvs/b38/lib/python3.8/site-packages/pipenv/utils/resolver.py", line 899, in actually_resolve_deps
[ResolutionFailure]:       resolver.resolve()
[ResolutionFailure]:   File "/Users/myuser/.virtualenvs/b38/lib/python3.8/site-packages/pipenv/utils/resolver.py", line 687, in resolve
[ResolutionFailure]:       raise ResolutionFailure(message=str(e))
[pipenv.exceptions.ResolutionFailure]: Warning: Your dependencies could not be resolved. You likely have a mismatch in your sub-dependencies.
  You can use $ pipenv install --skip-lock to bypass this mechanism, then run $ pipenv graph to inspect the situation.
  Hint: try $ pipenv lock --pre if it is a pre-release dependency.
ERROR: ResolutionImpossible: for help visit https://pip.pypa.io/en/latest/topics/dependency-resolution/#dealing-with-dependency-conflicts

It's the same in poetry. Both detect conflict.

➜  my-package poetry add django-model-utils==4.3.1
Updating dependencies
Resolving dependencies... Downloading https://files.pythonhosted.org/packages/17/38/5381cfbee475ddf6c109e434516ac969163124dcaad2cfc258586c571515/django_model_utils-4.3.1-py3-none-any.whl (0.3s)

Because django-model-utils (4.3.1) depends on Django (>=3.2)
 and my-package depends on django (2.2.18), django-model-utils is forbidden.
So, because my-package depends on django-model-utils (4.3.1), version solving failed.

4.2.0 django-models-utils requires Django > 2.0.0. That why you didn't get the error in poetry and pipenv.

@matteius
Copy link
Member

@matejsp Thanks for your detailed responses ... I am working through them slowly.

How would that work in practice? You cannot partially accept PR. So you do CI, QA test with 5 lib changes. You send to production and it fails for some reason. You rollback PR. Then you want to selectively update one version at the time (or even using bisection). How would pipenv help without keep outdated? Manually fiddling with lock file?

Say you roll back because its critical, or its not critical so you have a bit of time to fix it -- in either case, the path forward would be to isolate the offending upgrade and pin that in your Pipfile and re-lock pipenv lock and re-test.

Yes, manual is more still more errors prone and fiddle with lock files directly is bad.

I am not, and never have, recommended people fiddle with the lock files directly.

Sounds like there are numerous bugs in --selective-upgrade as well -- I am wondering though, as I've thought more about what an ideal behavior would be. Either --selective-upgrade or pipenv upgrade could consider resolving the package in question to figure out which packages are required (not necessarily the versions), then run a complete lock phase and pull the required packages (determined from the isolated resolve phase) and their allowed versions (determined from the complete lock phase) and just merge in those updates to the Lock file. This may be more deterministic and prevent inconsistent lock files (which is what I am trying to solve for here with removing --keep-outdated) -- it does mean that if upgrading requests brings in a new package or requires one you already have to be updated, that it would be changed in the lock file, but the idea is everything else would be left the same. Thoughts?

@matejsp
Copy link
Author

matejsp commented Feb 19, 2023

Say you roll back because its critical, or its not critical so you have a bit of time to fix it -- in either case, the path forward would be to isolate the offending upgrade and pin that in your Pipfile and re-lock pipenv lock and re-test.

My issue was how to isolate the offending upgrade (since we couldn't reproduce it in QA). So for each dep & pin & deploy to production, rollback if perf degradation & repeat.. And at the end unpin all the deps that were not problematic.

We can both agree that your ideal way does not fit how we do it :) Did I mention our SecOps team that is doing a fast upgrade of only CVE impacted deps and not touching others because the hotfix release will go fast to prod without much testing. Like last two CVEs in Django this month.

Sounds like there are numerous bugs in --selective-upgrade as well -- I am wondering though, as I've thought more about what an ideal behavior would be. Either --selective-upgrade or pipenv upgrade could consider resolving the package in question to figure out which packages are required (not necessarily the versions), then run a complete lock phase and pull the required packages (determined from the isolated resolve phase) and their allowed versions (determined from the complete lock phase) and just merge in those updates to the Lock file. This may be more deterministic and prevent inconsistent lock files (which is what I am trying to solve for here with removing --keep-outdated) -- it does mean that if upgrading requests brings in a new package or requires one you already have to be updated, that it would be changed in the lock file, but the idea is everything else would be left the same. Thoughts?

That would be ok solution, but really can't speculate about corner cases. One thing that I don't understand is if you produce corrupted lock file for some reason, why isn't there a validation at the end? Better to show error than to silently produce corrupted lock file? In this case developer could update/pin more libraries to help pipenv resolver (or file a bug report).

Now to the fun part :D PoC :)
After I tried how it fares with django and django-model-utils I tested also our env with poetry (using https://pypi.org/project/pipenv-poetry-migrate/). It is a good case since it's big and ugly monolith :D

Differences:

  • poetry lock --no-update: (works the same as --keep-outdated) Do not update locked versions, only refresh lock file.
  • It is brutally fast. Pipenv takes 90 seconds each time to process lock file while poetry's first time was around 90 seconds, but now it is cached and we are under 10 seconds. bazinga
  • Wierd behaviour in poetry that is pulling LONG obsolete dependencies (futures, backport-abc, singledispatch) while pipenv doesn't (Poetry not honouring (deprecated) dependencies with tornado 5.1.1 python-poetry/poetry#7534).
  • Poetry lock file hashes are tied to a wheel name (so it is easy to see if any platform is missing)
  • Poetry lock file has information about dependencies (easier to find issues)
  • Pipenv lock file is json and poetry is toml (I still prefer json)
  • Both support pyenv
  • I haven't really do a proper testing

Example from lock file:

[[package]]
name = "sphinx"
version = "5.3.0"
description = "Python documentation generator"
category = "dev"
optional = false
python-versions = ">=3.6"
files = [
    {file = "Sphinx-5.3.0.tar.gz", hash = "sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5"},
    {file = "sphinx-5.3.0-py3-none-any.whl", hash = "sha256:2724a6c7894cdbd21b011446d1b92f9b8201741fb826483fcdd3caae3af009c3"},
]

[package.dependencies]
alabaster = ">=0.7,<0.8"
babel = ">=2.9"
colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""}
docutils = ">=0.14,<0.20"
imagesize = ">=1.3"
importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""}
Jinja2 = ">=3.0"
packaging = ">=21.0"
Pygments = ">=2.12"
requests = ">=2.5.0"
snowballstemmer = ">=2.0"
sphinxcontrib-applehelp = "*"
sphinxcontrib-devhelp = "*"
sphinxcontrib-htmlhelp = ">=2.0.0"
sphinxcontrib-jsmath = "*"
sphinxcontrib-qthelp = "*"
sphinxcontrib-serializinghtml = ">=1.1.5"

@matteius
Copy link
Member

That would be ok solution, but really can't speculate about corner cases.

There would be fewer corner cases than --keep-outdated because keep outdated doesn't update a sub-dependency if its already present in your lockfile (but needs to be updated).

One thing that I don't understand is if you produce corrupted lock file for some reason, why isn't there a validation at the end? Better to show error than to silently produce corrupted lock file? In this case developer could update/pin more libraries to help pipenv resolver (or file a bug report).

There is validation if the lock phase cannot be resolved -- but I don't understand your comment. As far as I know, we don't produce a "corrupted lock file", its just that using --keep-outdated will not consider the case of:

resolving the package in question to figure out which packages are required (not necessarily the versions), then run a complete lock phase and pull the required packages (determined from the isolated resolve phase) and their allowed versions (determined from the complete lock phase) and just merge in those updates to the Lock file.

That is to say, the correct way to isolate a package upgrade is to consider all the required packages that it has and ensure they all meet specified version requirements, which is not what --keep-outdated does. The problem you are running into is complete resolver phase includes other package upgrades that are not specific to your upgrade in question. The biggest corner case I could see with this new approach is it maybe would leave behind old stale packages in the lock file because it wouldn't be taking the complete result of the new lock (say package A required package B, but now requires C--your lock file would likely have A, B and C, but at least the versions would not conflict against what the authors allowed).

Last note -- I do know that poetry uses its own resolver different from pip, so that can explain some of the differences you are seeing there.

@matejsp
Copy link
Author

matejsp commented Feb 19, 2023

There is validation if the lock phase cannot be resolved -- but I don't understand your comment. As far as I know, we don't produce a "corrupted lock file", its just that using --keep-outdated will not consider the case of

Sorry from earlier comments I was under the impression that --keep-outdated sometimes produces inconsistent/corrupted lock files.

What we observed are the following cases:

  • hashes were not updated to new version, but version was -> resulting in a corrupted lock (second run usually fixes this)
  • new hashes were added but old ones for old version were not cleared -> soft corrupted since lock is still working (we manually delete them from lock file)
  • no longer needed transient dependencies due to version upgrade were not removed -> soft corruption, we manually deleted them from lock file

Regarding A,B,C case ... try updating, if conflict, detect and report an error :) no need to do a magic here and auto resolve ... say C is incompatible and let the developer pin the C to correct version.

IMHO if it is hard to maintain the magic with keep-outdated ... make it simpler :D it will still cover 95% use cases. Alternative as --selective-upgrade are fine too if working :)

@dimbleby
Copy link

Just a drive-by comment on the claim that

poetry does let you install incompatible versions of things together

I see no incompatibility between django-model-utils 4.2.0 and django 2.*. The requirement from django-model-utils is for django >= 2.0.1

I know nothing about pipenv but so far as I can see this output is incorrect:

pipenv upgrade django-model-utils==4.2.0
 You cannot upgrade because django-model-utils requires Django >= 3.0

@matteius
Copy link
Member

matteius commented Feb 19, 2023

@dimbleby It was already corrected above by @matejsp when he pointed out django-model-utils==4.2.0 does allow django 2.* so it was not a valid test of poetry. Corrected example shows this is poetry's behavior, which is similar to what you would get during pipenv install if your --keep-outdated result is not valid:

matteius@matteius-VirtualBox:~/pipenv-triage/outdated$ poetry lock
Updating dependencies
Resolving dependencies... (0.5s)

Because django-model-utils (4.2.0) depends on Django (>=2.0.1)
and outdated depends on django (<2.0.0), django-model-utils is forbidden.
So, because outdated depends on django-model-utils (4.2.0), version solving failed.

@dimbleby
Copy link

Ah, I didn't see that. (Actually I still don't, but it's a long thread and if we're all agreeing then it couldn't matter less). I'll drop out.

@matteius
Copy link
Member

matteius commented Feb 19, 2023

@matejsp Here is a rough implementation (it updates the lock how I would expect, but not the Pipfile yet): https://github.com/pypa/pipenv/pull/5617/files

Example:

1.) Installing requests :

matteius@matteius-VirtualBox:~/opensensor-api$ pipenv upgrade requests
Loading .env environment variables...
Building requirements...
Resolving dependencies...
✔ Success!
Building requirements...
Resolving dependencies...
✔ Success!
matteius@matteius-VirtualBox:~/opensensor-api$ git diff
diff --git a/Pipfile.lock b/Pipfile.lock
index a0c7bb5..b818aff 100644
--- a/Pipfile.lock
+++ b/Pipfile.lock
@@ -38,6 +38,107 @@
             ],
             "version": "==0.2.0"
         },
+        "certifi": {
+            "hashes": [
+                "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3",
+                "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"
+            ],
+            "markers": "python_version >= '3.6'",
+            "version": "==2022.12.7"
+        },
+        "charset-normalizer": {
+            "hashes": [
+                "sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b",
+                "sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42",
+                "sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d",
+                "sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b",
+                "sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a",
+                "sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59",
+                "sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154",
+                "sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1",
+                "sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c",
+                "sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a",
+                "sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d",
+                "sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6",
+                "sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b",
+                "sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b",
+                "sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783",
+                "sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5",
+                "sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918",
+                "sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555",
+                "sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639",
+                "sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786",
+                "sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e",
+                "sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed",
+                "sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820",
+                "sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8",
+                "sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3",
+                "sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541",
+                "sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14",
+                "sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be",
+                "sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e",
+                "sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76",
+                "sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b",
+                "sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c",
+                "sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b",
+                "sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3",
+                "sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc",
+                "sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6",
+                "sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59",
+                "sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4",
+                "sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d",
+                "sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d",
+                "sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3",
+                "sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a",
+                "sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea",
+                "sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6",
+                "sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e",
+                "sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603",
+                "sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24",
+                "sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a",
+                "sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58",
+                "sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678",
+                "sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a",
+                "sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c",
+                "sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6",
+                "sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18",
+                "sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174",
+                "sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317",
+                "sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f",
+                "sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc",
+                "sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837",
+                "sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41",
+                "sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c",
+                "sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579",
+                "sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753",
+                "sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8",
+                "sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291",
+                "sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087",
+                "sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866",
+                "sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3",
+                "sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d",
+                "sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1",
+                "sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca",
+                "sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e",
+                "sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db",
+                "sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72",
+                "sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d",
+                "sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc",
+                "sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539",
+                "sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d",
+                "sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af",
+                "sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b",
+                "sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602",
+                "sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f",
+                "sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478",
+                "sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c",
+                "sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e",
+                "sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479",
+                "sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7",
+                "sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8"
+            ],
+            "version": "==3.0.1"
+        },
         "click": {
             "hashes": [
                 "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e",
@@ -306,6 +407,14 @@
             "index": "pypi",
             "version": "==4.2.0"
         },
+        "requests": {
+            "hashes": [
+                "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa",
+                "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"
+            ],
+            "index": "pypi",
+            "version": "==2.28.2"
+        },
         "setuptools": {
             "hashes": [
                 "sha256:0d33c374d41c7863419fc8f6c10bfe25b7b498aa34164d135c622e52580c6b16",
@@ -430,11 +539,11 @@
         },
         "urllib3": {
             "hashes": [
-                "sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2",
-                "sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e"
+                "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72",
+                "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"
             ],
-            "index": "pypi",
-            "version": "==1.25.11"
+            "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
+            "version": "==1.26.14"
         },
         "uvicorn": {
             "hashes": [

Where as locking this file would have had many other dependency updates.

@matteius
Copy link
Member

@matejsp I've made some more updates to the PR for the pipenv upgrade command, and would like you to check it out. Here is another example of the differences it can provide comparing pipenv lock with pipenv upgrade pydantic: #5617 (comment)

@matteius matteius added the PR: awaiting-review The PR related to this issue is awaiting review by a maintainer. label Feb 20, 2023
@matejsp
Copy link
Author

matejsp commented Feb 20, 2023

@matteius
I have checked upgrade command looks like a great start. I would like to test this branch with couple of cases with our pipfile.

Just need some time (hopefully today).

@matteius
Copy link
Member

Thanks @matejsp -- I've just pushed some updates to integrate it with the pipenv update command so the idea is:
pipenv update -- runs a full lock and sync
pipenv update packageA -- runs an upgrade of the packageA requirements, and sync
pipenv upgrade packageA -- Only update the Pipfile/lock of the packageA requirements (no sync) -- this will be useful for dependabot.

@matejsp
Copy link
Author

matejsp commented Feb 20, 2023

Tested on our monolith with your branch. Hope it helps :)

I installed via:
pip install https://github.com/pypa/pipenv/archive/refs/heads/selective-upgrades.zip

Classic update

(.venv) ➜  py38 PIPENV_IGNORE_VIRTUALENVS=1 pipenv upgrade 
✔ Success!
  • All dependencies updated to latest possible version based. Essentially recreating lock file. (not what we want most of the time, but it works as advertised)

Trying no op operation:

Used after git merge to check if pipenv is in sync with lock file when resolving conflicts.

(.venv) ➜  py38 PIPENV_IGNORE_VIRTUALENVS=1 pipenv upgrade 
Building requirements...
Resolving dependencies...
✔ Success!
Traceback (most recent call last):
  File "/Users/myuser/.virtualenvs/myproject/bin/pipenv", line 8, in <module>
    sys.exit(cli())
  File "/Users/myuser/.virtualenvs/myproject/lib/python3.8/site-packages/pipenv/vendor/click/core.py", line 1128, in __call__
    return self.main(*args, **kwargs)
  File "/Users/myuser/.virtualenvs/myproject/lib/python3.8/site-packages/pipenv/cli/options.py", line 58, in main
    return super().main(*args, **kwargs, windows_expand_args=False)
  File "/Users/myuser/.virtualenvs/myproject/lib/python3.8/site-packages/pipenv/vendor/click/core.py", line 1053, in main
    rv = self.invoke(ctx)
  File "/Users/myuser/.virtualenvs/myproject/lib/python3.8/site-packages/pipenv/vendor/click/core.py", line 1659, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "/Users/myuser/.virtualenvs/myproject/lib/python3.8/site-packages/pipenv/vendor/click/core.py", line 1395, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "/Users/myuser/.virtualenvs/myproject/lib/python3.8/site-packages/pipenv/vendor/click/core.py", line 754, in invoke
    return __callback(*args, **kwargs)
  File "/Users/myuser/.virtualenvs/myproject/lib/python3.8/site-packages/pipenv/vendor/click/decorators.py", line 84, in new_func
    return ctx.invoke(f, obj, *args, **kwargs)
  File "/Users/myuser/.virtualenvs/myproject/lib/python3.8/site-packages/pipenv/vendor/click/core.py", line 754, in invoke
    return __callback(*args, **kwargs)
  File "/Users/myuser/.virtualenvs/myproject/lib/python3.8/site-packages/pipenv/cli/command.py", line 279, in upgrade
    upgrade(
  File "/Users/myuser/.virtualenvs/myproject/lib/python3.8/site-packages/pipenv/routines/update.py", line 173, in upgrade
    for package_name, _ in upgrade_lock_data.items():
AttributeError: 'NoneType' object has no attribute 'items'

Updated dependency in pipfile directly: requests = "==2.28.2"

(.venv) ➜  py38 PIPENV_IGNORE_VIRTUALENVS=1 pipenv upgrade
  • same crash as above

Pinned dependency update (without touching pipfile):

(.venv) ➜  py38 PIPENV_IGNORE_VIRTUALENVS=1 pipenv upgrade requests==2.28.2
✔ Success!
  • correctly updated in pipfile, lock file, pipfile reformated (whitespaces removed, it would be nice to preserve formating sometime in the future)

Transitive depedency update:

(.venv) ➜  py38 PIPENV_IGNORE_VIRTUALENVS=1 pipenv upgrade chardet==5.1.0  
✔ Success!
(.venv) ➜  py38 PIPENV_IGNORE_VIRTUALENVS=1 pipenv update chardet==5.1.0
✔ Success!
  • transitive depedency pinned and updated in lock file -> better if it would stay unpinned and just updated in lock file
  • both update and upgrade behaves the same

Updated dependency in pipfile:

requests = "==2.28.2"
(.venv) ➜  py38 PIPENV_IGNORE_VIRTUALENVS=1 pipenv upgrade chardet==5.1.0
  • chardet was updated to 5.1.0, requests weren't updated (but it should because it is locked in pipfile)

Upgrade depedency that cannot be upgraded:

(.venv) ➜  py38 PIPENV_IGNORE_VIRTUALENVS=1 pipenv upgrade torndato==6.2.0
Building requirements...
Resolving dependencies...
✘ Locking Failed!
⠇ Locking...
CRITICAL:pipenv.patched.pip._internal.resolution.resolvelib.factory:Could not find a version that satisfies the requirement torndato==6.2.0 (from versions: none)
[ResolutionFailure]:   File "/Users/myuser/.virtualenvs/myproject/lib/python3.8/site-packages/pipenv/resolver.py", line 811, in _main
[ResolutionFailure]:       resolve_packages(
[ResolutionFailure]:   File "/Users/myuser/.virtualenvs/myproject/lib/python3.8/site-packages/pipenv/resolver.py", line 759, in resolve_packages
[ResolutionFailure]:       results, resolver = resolve(
[ResolutionFailure]:   File "/Users/myuser/.virtualenvs/myproject/lib/python3.8/site-packages/pipenv/resolver.py", line 738, in resolve
[ResolutionFailure]:       return resolve_deps(
[ResolutionFailure]:   File "/Users/myuser/.virtualenvs/myproject/lib/python3.8/site-packages/pipenv/utils/resolver.py", line 1100, in resolve_deps
[ResolutionFailure]:       results, hashes, markers_lookup, resolver, skipped = actually_resolve_deps(
[ResolutionFailure]:   File "/Users/myuser/.virtualenvs/myproject/lib/python3.8/site-packages/pipenv/utils/resolver.py", line 899, in actually_resolve_deps
[ResolutionFailure]:       resolver.resolve()
[ResolutionFailure]:   File "/Users/myuser/.virtualenvs/myproject/lib/python3.8/site-packages/pipenv/utils/resolver.py", line 687, in resolve
[ResolutionFailure]:       raise ResolutionFailure(message=str(e))
[pipenv.exceptions.ResolutionFailure]: Warning: Your dependencies could not be resolved. You likely have a mismatch in your sub-dependencies.
  You can use $ pipenv install --skip-lock to bypass this mechanism, then run $ pipenv graph to inspect the situation.
  Hint: try $ pipenv lock --pre if it is a pre-release dependency.
ERROR: No matching distribution found for torndato==6.2.0
  • Works as expected. I like how poetry displays what actually went wrong. But just nitpicking :)

@matteius
Copy link
Member

@matejsp Thanks for your detailed analysis. I've pushed a fix for the no-op case you've identified:

matteius@matteius-VirtualBox:~/pipenv$ pipenv upgrade
Loading .env environment variables...
Nothing to upgrade!

And a few notes on your other points:

correctly updated in pipfile, lock file, pipfile reformated (whitespaces removed, it would be nice to preserve formating sometime in the future)

Yeah, turns out its rather hard to merge to sub-strings in a TOML file. In working on this I fixed another bug that was happening in project.py, and there was a weird test case that was like If we have a non pep(??)-compliant package name, that the old code would try to update it and maintain the comments, but fixing the actual bug was more important than trying to maintain that behavior (at least for now).

transitive depedency pinned and updated in lock file -> better if it would stay unpinned and just updated in lock file

Hmmm, my intention was you would run upgrade on a top level dependency you wanted in the Pipfile, but as I think of this as a possible dependabot replacement to how it uses --keep-outdated that I think having an option like --lock-only or similar thing that would omit adjusting it to the Pipfile.

chardet was updated to 5.1.0, requests weren't updated (but it should because it is locked in pipfile)

This is actually working as intended -- the upgrade command was given chardet and requests is not a dependency of this, if I did re-lock things because they are in the Pipfile, then you would end up with the result of pipenv lock.

Works as expected. I like how poetry displays what actually went wrong. But just nitpicking :)

This is because pip's resolver is not quite clear in its messaging about what the issues are in backtracking, I think this might be resolvelib. I also think, IIRC, that pip developers are working on improving the messaging for this case, but I honestly am not sure to what extent that is true (I want to believe it is).

Anyway -- that's all I have for now. Thanks again!

@matejsp
Copy link
Author

matejsp commented Feb 21, 2023

Great! :) I agree with most of the comments.

This is actually working as intended -- the upgrade command was given chardet and requests is not a dependency of this, if I did re-lock things because they are in the Pipfile, then you would end up with the result of pipenv lock.

I understand the issue with relocking. The problem is that upgrade updates pipfile and lock file giving you the false sense that everything is ok. Maybe some validation and warning that lock file is outdated.

The reason why one of my tests were also updating pipfile is because when merging using git flow, pipfile and lock file gets merged without a problem (except _meta hash -> manual resolving). Now to check lock we used pipenv lock --keep-outdated and relied on pipenv to check and fix if anything was broken or missing. With upgrade you could now get inconsistent state.

Another nit from poetry that I really loved is that meta hash is checked against specification and it warns you if lock file is stale. So for Updated dependency in pipfile test case and others you could check if lock hash matches pipfile and display an error. Wouldn't solve the git flow issue but would save you from inconsistent output (unless you can list inconsistent dependencies and we can manually upgrade them).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
--keep-outdated/--selective-upgrade PR: awaiting-review The PR related to this issue is awaiting review by a maintainer. Type: Question ❔ This is a question or a request for support.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants