creosote: Exception in package_to_module

Tried it on my project: https://github.com/staticf0x/mrmonitor

Invocation: creosote --venv (poetry env info --path) --deps-file pyproject.toml --paths . --sections tool.poetry.dependencies

Parsing mrmonitor.py
Parsing reviews.py
Parsing pyproject.toml for packages
Found packages in pyproject.toml: arrow, click, python-dotenv, python-gitlab, rich
Resolving...
Traceback (most recent call last):
  File "/home/username/.local/bin/creosote", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/home/username/.local/lib/python3.11/site-packages/creosote/cli.py", line 90, in main
    deps_resolver.resolve()
  File "/home/username/.local/lib/python3.11/site-packages/creosote/resolvers.py", line 109, in resolve
    self.populate_packages()
  File "/home/username/.local/lib/python3.11/site-packages/creosote/resolvers.py", line 87, in populate_packages
    self.package_to_module(package)
  File "/home/username/.local/lib/python3.11/site-packages/creosote/resolvers.py", line 53, in package_to_module
    for filename, _, _ in dist.list_installed_files():
  File "/home/username/.local/lib/python3.11/site-packages/distlib/database.py", line 677, in list_installed_files
    for result in self._get_records():
                  ^^^^^^^^^^^^^^^^^^^
  File "/home/username/.local/lib/python3.11/site-packages/distlib/database.py", line 596, in _get_records
    with contextlib.closing(r.as_stream()) as stream:
                            ^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'as_stream'

Same thing in another project with requirements.txt

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 17 (9 by maintainers)

Most upvoted comments

@staticf0x the related code which crashed here originally has been completely rewritten and I just tried it out here without issues:

FROM fedora:37

WORKDIR /workspace

RUN dnf install -y python3-click python3-pip git
RUN pip3 install --user poetry arrow
RUN pip3 install --user creosote==2.6.0rc1 --pre
RUN git clone https://github.com/staticf0x/mrmonitor.git

WORKDIR /workspace/mrmonitor

ENV PATH /root/.local/bin:$PATH

RUN poetry install

ENTRYPOINT creosote --venv $(poetry env info --path) --deps-file pyproject.toml --paths mrmonitor.py --sections tool.poetry.dependencies --verbose
Creosote version: 2.6.0rc1
Command: creosote --venv /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11 --deps-file pyproject.toml --paths mrmonitor.py --sections tool.poetry.dependencies --verbose
Arguments: Namespace(verbose=True, format='default', paths=['mrmonitor.py'], sections=['tool.poetry.dependencies'], exclude_deps=[], deps_file='pyproject.toml', venvs=['/root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11'], features=[])
Parsing /workspace/mrmonitor/mrmonitor.py
Imports found in code:
- ImportInfo(module=[], name=['os'], alias=None)
- ImportInfo(module=[], name=['re'], alias=None)
- ImportInfo(module=[], name=['arrow'], alias=None)
- ImportInfo(module=[], name=['click'], alias=None)
- ImportInfo(module=[], name=['dotenv'], alias=None)
- ImportInfo(module=[], name=['gitlab'], alias=None)
- ImportInfo(module=[], name=['rich'], alias=None)
Parsing pyproject.toml for dependencies...
['tool.poetry.dependencies']: {'python': '^3.9', 'python-gitlab': '^3.12', 'python-dotenv': '^0.21', 'rich': '^13.1', 'arrow': '^1.2', 'click': '^8.1'}
Detected Poetry toml section in pyproject.toml
Found dependencies in pyproject.toml: arrow, click, python-dotenv, python-gitlab, rich
Gathering all top_level.txt files in venv /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11...
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/wheel-0.38.4.dist-info/top_level.txt
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/python_dateutil-2.8.2.dist-info/top_level.txt
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/requests_toolbelt-0.10.1.dist-info/top_level.txt
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/Pygments-2.14.0.dist-info/top_level.txt
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/python_gitlab-3.12.0.dist-info/top_level.txt
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/six-1.16.0.dist-info/top_level.txt
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/requests-2.28.2.dist-info/top_level.txt
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/commonmark-0.9.1.dist-info/top_level.txt
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/setuptools-67.4.0.dist-info/top_level.txt
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/python_dotenv-0.21.0.dist-info/top_level.txt
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/urllib3-1.26.14.dist-info/top_level.txt
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/click-8.1.3.dist-info/top_level.txt
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/arrow-1.2.3.dist-info/top_level.txt
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/certifi-2022.12.7.dist-info/top_level.txt
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/pip-23.0.1.dist-info/top_level.txt
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib64/python3.11/site-packages/charset_normalizer-3.0.1.dist-info/top_level.txt
Gathering all top_level.txt files in venv /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11...
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/rich-13.1.0.dist-info/RECORD
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/wheel-0.38.4.dist-info/RECORD
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/python_dateutil-2.8.2.dist-info/RECORD
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/requests_toolbelt-0.10.1.dist-info/RECORD
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/Pygments-2.14.0.dist-info/RECORD
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/python_gitlab-3.12.0.dist-info/RECORD
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/six-1.16.0.dist-info/RECORD
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/requests-2.28.2.dist-info/RECORD
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/commonmark-0.9.1.dist-info/RECORD
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/idna-3.4.dist-info/RECORD
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/setuptools-67.4.0.dist-info/RECORD
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/python_dotenv-0.21.0.dist-info/RECORD
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/mrmonitor-0.1.0.dist-info/RECORD
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/urllib3-1.26.14.dist-info/RECORD
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/click-8.1.3.dist-info/RECORD
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/arrow-1.2.3.dist-info/RECORD
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/certifi-2022.12.7.dist-info/RECORD
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib/python3.11/site-packages/pip-23.0.1.dist-info/RECORD
Found /root/.cache/pypoetry/virtualenvs/mrmonitor-WlclguGu-py3.11/lib64/python3.11/site-packages/charset_normalizer-3.0.1.dist-info/RECORD
Attempting to find import names...
[python-dotenv] found import name(s) via top_level.txt: dotenv ⭐️
[python-dotenv] found import name via RECORD: dotenv ⭐️
[click] found import name(s) via top_level.txt: click ⭐️
[click] found import name via RECORD: click ⭐️
[arrow] found import name(s) via top_level.txt: arrow ⭐️
[arrow] found import name via RECORD: arrow ⭐️
[python-gitlab] found import name(s) via top_level.txt: gitlab ⭐️
[python-gitlab] found import name via RECORD: gitlab ⭐️
[rich] did not find top_level.txt in venv
[rich] found import name via RECORD: rich ⭐️
Dependencies with populated 'associated_import' attribute are used in code. End result of resolve:
- DependencyInfo(name='python-dotenv', top_level_import_names=['dotenv'], record_import_names=['dotenv'], canonicalized_dep_name='python_dotenv', associated_imports=[ImportInfo(module=[], name=['dotenv'], alias=None), ImportInfo(module=[], name=['dotenv'], alias=None)])
- DependencyInfo(name='click', top_level_import_names=['click'], record_import_names=['click'], canonicalized_dep_name='click', associated_imports=[ImportInfo(module=[], name=['click'], alias=None), ImportInfo(module=[], name=['click'], alias=None), ImportInfo(module=[], name=['click'], alias=None)])
- DependencyInfo(name='arrow', top_level_import_names=['arrow'], record_import_names=['arrow'], canonicalized_dep_name='arrow', associated_imports=[ImportInfo(module=[], name=['arrow'], alias=None), ImportInfo(module=[], name=['arrow'], alias=None), ImportInfo(module=[], name=['arrow'], alias=None)])
- DependencyInfo(name='python-gitlab', top_level_import_names=['gitlab'], record_import_names=['gitlab'], canonicalized_dep_name='python_gitlab', associated_imports=[ImportInfo(module=[], name=['gitlab'], alias=None), ImportInfo(module=[], name=['gitlab'], alias=None)])
- DependencyInfo(name='rich', top_level_import_names=None, record_import_names=['rich'], canonicalized_dep_name='rich', associated_imports=[ImportInfo(module=[], name=['rich'], alias=None), ImportInfo(module=[], name=['rich'], alias=None)])
No unused dependencies found! ✨

@staticf0x I’m glad to hear #139 solves this for you. I’m merging it in then 😄

Many thanks for taking the time to create the repro steps. I’ll try to follow them later, so to make sure this won’t happen either way. I’ll leave this issue open until I’ve done so.

If you want to reproduce the bug, you could follow these steps:

  1. Install Fedora 37 (VM is fine, Docker could work too)
  2. Run dnf install python3-click
  3. Run pip3 install --user arrow
  4. Clone my repo
  5. Run the command I used

This should hopefully be enough to replicate the behavior

In map_package_to_module_via_distlib there’s a call to dist.list_installed_files(), for arrow it works, but not for click.

In _get_records() (https://github.com/pypa/distlib/blob/05375908c1b2d6b0e74bdeb574569d3609db9f56/distlib/database.py#L587) you can see this line:

r = self.get_distinfo_resource('RECORD')

For arrow it returns a resource with path /home/username/.local/lib/python3.11/site-packages/arrow-1.2.3.dist-info/RECORD, which is my user python directory (installed via pip3 install --user).

Now look at pip3 show arrow:

Name: arrow
Version: 1.2.3
Summary: Better dates & times for Python
Home-page: https://arrow.readthedocs.io
Author: Chris Smith
Author-email: crsmithdev@gmail.com
License: Apache 2.0
Location: /home/username/.local/lib/python3.11/site-packages  # <- USER install
Requires: python-dateutil
Required-by: isoduration

but for pip3 show click:

Name: click
Version: 8.1.3
Summary: Composable command line interface toolkit
Home-page: https://palletsprojects.com/p/click/
Author: Armin Ronacher
Author-email: armin.ronacher@active-4.com
License: BSD-3-Clause
Location: /usr/lib/python3.11/site-packages  # <- SYSTEM install
Requires: 
Required-by: black

and the resource is not returned, because the RECORD file is not there.

Now back to creosote: see this line: https://github.com/fredrikaverpil/creosote/blob/main/src/creosote/resolvers.py#L122

First it maps the package using venv, which is correct, that method runs just fine, but then it tries to map those packages again using distlib, but it’s not aware of venv. Shouldn’t it be “if venv else distlib” instead of “venv then distlib”?

See output from pdb:

# Processing arrow (user install)

Mapped package/import: arrow/arrow
> /home/username/.local/lib/python3.11/site-packages/creosote/resolvers.py(126)gather_import_info()
-> self.map_package_to_module_via_distlib(package)
(Pdb) p package
Package(name='arrow', top_level_import_names=['arrow'], distlib_db_import_name=None, canonicalized_package_name=None, associated_imports=[])
(Pdb) c
Performing fallback module name resolution...
Found module name for arrow: arrow

# Processing click (system install)

Mapped package/import: click/click
> /home/username/.local/lib/python3.11/site-packages/creosote/resolvers.py(126)gather_import_info()
-> self.map_package_to_module_via_distlib(package)
(Pdb) p package
Package(name='click', top_level_import_names=['click'], distlib_db_import_name=None, canonicalized_package_name=None, associated_imports=[])
(Pdb) n
Performing fallback module name resolution...
AttributeError: 'NoneType' object has no attribute 'as_stream'

# Ends here

So both packages are correctly mapped in map_package_to_import_via_top_level_txt_file, but click cannot be mapped in map_package_to_module_via_distlib after that.

Hope this helps.

pip3 show creosote:

Name: creosote
Version: 2.3.0
Summary: Identify unused dependencies and avoid a bloated virtual environment.
Home-page: 
Author: 
Author-email: Fredrik Averpil <fredrik.averpil@gmail.com>
License: 
Location: /home/username/.local/lib/python3.11/site-packages
Requires: distlib, dotty-dict, loguru, pip-requirements-parser, toml
Required-by:

creosote --venv (poetry env info --path) --deps-file pyproject.toml --paths mrmonitor.py --sections tool.poetry.dependencies --verbose:

Parsing /home/username/projects/personal/mrmonitor/mrmonitor.py
Imports found in code (provided by --paths):
- Import(module=[], name=['os'], alias=None)
- Import(module=[], name=['re'], alias=None)
- Import(module=[], name=['arrow'], alias=None)
- Import(module=[], name=['click'], alias=None)
- Import(module=[], name=['dotenv'], alias=None)
- Import(module=[], name=['gitlab'], alias=None)
- Import(module=[], name=['rich'], alias=None)
Parsing pyproject.toml for packages
Detected Poetry toml section in pyproject.toml
Found packages in pyproject.toml: arrow, click, python-dotenv, python-gitlab, rich
Packages found in pyproject.toml (provided by --deps-file):
- Package(name='arrow', top_level_names=None, module_name=None, importable_as=None, associated_imports=[])
- Package(name='click', top_level_names=None, module_name=None, importable_as=None, associated_imports=[])
- Package(name='python-dotenv', top_level_names=None, module_name=None, importable_as=None, associated_imports=[])
- Package(name='python-gitlab', top_level_names=None, module_name=None, importable_as=None, associated_imports=[])
- Package(name='rich', top_level_names=None, module_name=None, importable_as=None, associated_imports=[])
Resolving package<->import name... (uses venv provided by --venv):
Traceback (most recent call last):
  File "/home/username/.local/bin/creosote", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/home/username/.local/lib/python3.11/site-packages/creosote/cli.py", line 106, in main
    deps_resolver.resolve()
  File "/home/username/.local/lib/python3.11/site-packages/creosote/resolvers.py", line 109, in resolve
    self.populate_packages()
  File "/home/username/.local/lib/python3.11/site-packages/creosote/resolvers.py", line 87, in populate_packages
    self.package_to_module(package)
  File "/home/username/.local/lib/python3.11/site-packages/creosote/resolvers.py", line 53, in package_to_module
    for filename, _, _ in dist.list_installed_files():
  File "/home/username/.local/lib/python3.11/site-packages/distlib/database.py", line 677, in list_installed_files
    for result in self._get_records():
                  ^^^^^^^^^^^^^^^^^^^
  File "/home/username/.local/lib/python3.11/site-packages/distlib/database.py", line 596, in _get_records
    with contextlib.closing(r.as_stream()) as stream:
                            ^^^^^^^^^^^
AttributeError: 'NoneType' object has no attribute 'as_stream'