pip3-multiple-versions

A package to prevent Dependency Confusion attacks against Yandex.


License
MIT
Install
pip install pip3-multiple-versions==66.0.3

Documentation

Install many versions of the same Python package with pip3 support.

Getting started

Technical stack

  1. Python3 as an interpreter (programming language) to execute the code.
    • The following list of versions has been tested: 3.4, 3.5, 3.6, 3.7.

Requirements

  1. Git as a version control system to work with current repository.
  2. pip as a package manager for Python.

Installation

Install using pip3:

$ pip3 install pip3-multiple-versions

Usage

Versions

To specify particular version, use the function named use from multiple_versions:

import multiple_versions
multiple_versions.use(name='requests', version='2.0.0')

import requests
print(requests)
<module 'requests' from '.../requests-2.0.0/requests/__init__.py'>

Distribution functionality (pkg_resources) also supported by default:

import pkg_resources
print(pkg_resources.get_distribution('requests').version)
2.0.0

Command line interface

Version

Get the version of the package — pip3-multiple-versions --version:

$ pip3-multiple-versions --version
pip3-multiple-versions, version 0.0.1

Help

Get the detailed description of all supported commands by the package — pip3-multiple-versions --help:

$ pip3-multiple-versions --help
Usage: pip3-multiple-versions [OPTIONS] COMMAND [ARGS]...

  Provide command-line interface for pip3 package's multiple versions.

Options:
  --version  Show the version and exit.
  --help     Show this message and exit.

Install

Install a package — pip3-multiple-versions package install.

You can install as many version as you wish.

Arguments Type Required Restrictions Description
name String Yes - Name of a package.
version String Yes - Version of a package.
extra-index-url String No May contain authentication token. URL to extra Python Package Index. May be private one.
$ pip3-multiple-versions package install \
      --name=avm-model \
      --version=2.0.0 \
      --extra-index-url=https://${AUTHENTICATION_TOKEN}:@${GEMFURY_PRIVATE_REPOSITORY}

How it works

Benchmark

Any cache system from Python is not used and the library does not decrease speed of imports call.

Performance without multiple versions calling:

$ python3 -mtimeit -s "import requests"
50000000 loops, best of 5: 6.62 nsec per loop

Performance with multiple versions calling:

$ python3 -mtimeit -s "import multiple_versions; multiple_versions.use(name='requests', version='2.0.0'); import requests"
50000000 loops, best of 5: 6.56 nsec per loop

Troubleshootings

Unknown module folder

You may get the unknown module folder error illustrated below.

Traceback (most recent call last):
  File "test.py", line 45, in <module>
    multiple_versions.use(name='requests', version='2.0.0')
  File "/Users/dmytro/projects/pip3-multiple-versions/multiple_versions/main.py", line 140, in use
    imported_package_folder = _get_imported_package_folder(package_name=name, package_version=version)
  File "/Users/dmytro/projects/pip3-multiple-versions/multiple_versions/main.py", line 112, in _get_imported_package_folder
    raise ModuleNotFoundError('Unknown module folder, package name is different from package folder name.')
ModuleNotFoundError: Unknown module folder, package name is different from package folder name.

It means, that the project name is different from package folder name. After installation this kind of projects, you have:

  1. Package folder you do imports from.

    import rest_framework
  2. Folder with meta data from setup.py such as version, description, etc.

    $ ls /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages
    ...
    rest_framework
    djangorestframework-3.11.0-py3.7.egg-info
    ...

And the point is that multiple_versions.use under the hood requires data for both of them and the problem is these different names — djangorestframework (package name while installing) and rest_framework (package folder name).

The library easily parse package name, but not package folder name. The solution is to use import_name variable to be passed to multiple_versions.use.

import multiple_versions
multiple_versions.use(name='djangorestframework', version='3.11.0', import_name='rest_framework')

import rest_framework

You don't need import_name variable in case package name and package folder name is the same:

$ ls /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages
...
marshmallow
marshmallow-3.3.0.dist-info
typing_inspect
typing_inspect-0.4.0.dist-info
...

Development

Clone the project

To start working with the project, clone it with the following commands.

$ git clone git@github.com:dmytrostriletskyi/pip3-multiple-versions.git
$ cd dmytrostriletskyi/pip3-multiple-versions

Distribution

Build the project with the following command:

$ python3 setup.py sdist

Install it locally:

$ pip3 install dist/*.tar.gz

After the command above, you can execute the command line interface as if you installed it through pip3:

$ pip3-multiple-versions --version
pip3-multiple-versions, version 0.0.1