adamchainz / pip-lock

Check for differences between requirements.txt files and your environment
MIT License
36 stars 7 forks source link

AttributeError for importtime-waterfall #450

Open g-nie opened 1 month ago

g-nie commented 1 month ago

Python Version

3.11.9

Package Version

2.11.0

Description

Hello, I ran into an error today after adding importtime-waterfall to my requirements and installing it in my env. check_requirements() raises an AttributeError:

Traceback (most recent call last):
  File "/src/manage.py", line 34, in <module>
    main()
  File "/src/manage.py", line 26, in main
    pip_lock.check_requirements(
  File "/usr/local/lib/python3.11/site-packages/pip_lock/__init__.py", line 96, in check_requirements
    mismatches = get_mismatches(requirements_file_path)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pip_lock/__init__.py", line 62, in get_mismatches
    installed = get_installed()
                ^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pip_lock/__init__.py", line 51, in get_installed
    return {normalize_name(d.metadata["Name"]): d.version for d in get_distributions()}
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pip_lock/__init__.py", line 51, in <dictcomp>
    return {normalize_name(d.metadata["Name"]): d.version for d in get_distributions()}
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.11/site-packages/pip_lock/__init__.py", line 55, in normalize_name
    return name.lower().replace("_", "-").replace(".", "-")
           ^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'lower'

I had a quick go at the shell and it looks like there's a bunch of None names and versions in the installed packages now:

In [1]: from pip_lock import *

In [2]: for d in get_distributions():
   ...:     print(d.version, d.metadata['name'])
   ...:
...
1.0.0 importtime-waterfall
...
None None
None None
None None
None None
None None
None None
None None
None None
None None
None None
None None

I can give this a go if you find the issue valid. Thanks!

adamchainz commented 1 month ago

Hey! Yeah, please try debugging yourself. I couldn't reproduce it locally - I set up an empty virtual environment, installed the package, and just saw:

>>> for d in get_distributions():
...     print(d.version, d.metadata['name'])
...
24.1.2 pip
1.0.0 importtime-waterfall
2.11.0 pip-lock

Also, importlib.metadata.distributions (renamed inside pip-lock to get_distributions()) reads from core package metadata, where Name and Version are required. So it seems like your Nones are not allowed...

g-nie commented 1 month ago

I couldn't exactly reproduce that again but I'm pretty sure now that it was a user error. My use case was a Django app where I'm checking on a settings variable to decide whether to check_requirements():

# manage.py
def main():
    # ...
    from django.conf import settings
    if settings.CHECK_REQUIREMENTS:
        import pip_lock
        pip_lock.check_requirements(...)
    execute_from_command_line(sys.argv)

And that was probably a bad idea. Changing it to check on an env variable directly instead: os.environ.get("CHECK_REQUIREMENTS") solved my issues there. I'm closing this

adamchainz commented 1 month ago

Gating on a setting versus an environment variable is very unlikely to have affected it. It seems most likely that your virtual environment was in some state that caused importlib.metadata to return a bunch of "empty" packages. Maybe this is still something to guard against. I'm going to keep the issue open in case others report the same.