Setuptools (requirements.txt/setup.py)
requirements.txt, alongside setup.py, is the most common -yet imprecise -- approach to dependency management in python projects.
Overview
requirements.txt, alongside setup.py, is the most common -- yet imprecise -- approach to dependency management in python projects.
Project Discovery
requirements.txt: Find all files matching the glob: req*.txt
setup.py: Find all files named setup.py
Analysis: requirements.txt
For the CLI to identify transitive dependencies from a requirements.txt file, certain conditions must be met. First, the specified packages must be installed using a command like python -m pip install -r requirements.txt. The packages should be installed within a virtual environment, ensuring a clean and isolated environment. Subsequently, the CLI should be executed within the same virtual environment the packages were installed in. Finally, the environment should include both python and pip, as the CLI parses the output from executing python -m pip list and python -m pip show to determine transitive dependencies. Importantly, the CLI only reports transitive dependencies for packages explicitly listed in the requirements.txt file and installed within the current environment.
If neither python nor pip are installed the CLI will only to reporting only direct dependencies.
requirements.txt contains direct dependencies, and is parsed compliant to its file format spec.
pip-cli options, URLs, absolute paths, and relative paths are ignored -- though this may be revisited in the future.
Dependencies found in requirements.txt have a spec defined by PEP-508. Dependencies often have version ranges and environment markers (e.g. python version, OS, ...). The resulting graph contains packages tagged with environment markers.
Analysis: setup.py
Installed packages
For the CLI to detect transitives dependencies for packages specified in a setup.py file's install_requires field, certain conditions must be met. The packages must be installed with command similar to python -m pip install .. The packages should be installed within a virtual environment, ensuring a clean and isolated environment. Subsequently, the CLI should be executed with the same environment. Finally, the environment should include both python and pip, as the CLI will parse the output from executing python -m pip list and python -m pip show to determine the transitive dependencies. The CLI will naively scan a setup.py file for a name attribute and attempt to match the name with an installed package. If the name matches an installed package, the CLI will exclusively report the transitive dependencies required by the found package. If a name is not matched then the CLI will naively scan for install_requires as explained in the next section.
If neither python nor pip are installed the CLI will report dependencies found by naively scanning for install_requires as explained in the next section.
Naively scanning for install_requires
setup.py is naively scanned for its install_requires=[...] field, which often fails on projects encountered in the wild. Short of implementing a robust python parser, or running a python script in their environment (which may have unintended consequences!), reliable output from setup.py is difficult to obtain.
Entries in the install_requires array are parsed compliant to the PEP-508 spec, similar to requirements.txt
If setup.cfg exists in the same directory as setup.py, fossa-cli also naively scans for its install_requires=[...] attributes, similar to setup.py. If both setup.cfg and setup.py exists and both have install_requires attribute, fossa-cli concatenates requirements from both files.
Limitations
- Python requirements files and setup.py files do not provide any data about edges between dependencies.
- Requirements files can be completely different than an existing setup.py specification, as there is no built-in synchronization between them.
- The CLI will catch variables named
install_requires, as long as they are declared earlier in the file than theinstall_requireskeyword argument tosetup. - Because the CLI doesn't actually run
setup.pyor do its own interpretation of the Python code therein,install_requiresnot defined as a literal array of string literals done in thesetup.pyfile will hide the trueinstall_requireslist from the CLI's view. For example, FOSSA CLI does not have a general way to findinstall_requiresset up this way:Pythona = ['package1==1.0.0']b = ['package2==2.0.0']install_requires = a + b - Often, the
requirements.txtfile entirely overlaps thesetup.pyfile. This is almost always by design.
Examples
Assuming no virtual environment, given the following files:
setup.py (manually created):
setup( name='Foo-project', version='1.0', description='Python example project', author='Jeff Jefferson', author_email='bug-catcher@butterfly.net', url='https://this.url/means#nothing', packages=['foo'], # And now the important part... install_requires=[ "requests", ],)requirements.txt: Note: your requirements file may have different versions. This file is just a reference example.
certifi==2021.5.30
chardet==4.0.0
idna==2.10
requests==2.25.1
urllib3==1.26.6We will produce a list of these direct dependencies with no edges between them (see #limitations):
certifi==2021.5.30
chardet==4.0.0
idna==2.10
requests==2.25.1
urllib3==1.26.6F.A.Q
Why is fossa-cli reporting incorrect transitive dependencies or edges?
fossa-cli uses python -m pip list and python -m pip show command to infer transitive dependencies, and it's edges. It's paramount that you are in project's venv prior to invoking fossa analyze command. If you are not in project's virtual environment, fossa-cli will use global python environment to infer transitive dependencies, edges, and dependency versions.
For example, you should always invoke fossa-cli in project's python's virtual environment.
python -m venv ~/envs/my-venvsource ~/envs/my-venv/bin/activate # this command may differ depending on your python version!pip install -r requirements.txt fossa analyzeIf you are unable to get any transitive dependencies, ensure you can run following command in project's directory:
python -m pip list --format jsonpython -m pip show