68

I wonder if as well as .deb packages for example, it is possible in my setup.py I configure the dependencies for my package, and run:

$ sudo python setup.py install

They are installed automatically. Already researched the internet but all I found out just leaving me confused, things like "requires", "install_requires" and "requirements.txt"

Jonathon Reinhart
  • 132,704
  • 33
  • 254
  • 328
adinanp
  • 935
  • 3
  • 8
  • 11

4 Answers4

39

Just create requirements.txt in your lib folder and add all dependencies like this:

gunicorn
docutils>=0.3
lxml==0.5a7

Then create a setup.py script and read requirements.txt:

import os
lib_folder = os.path.dirname(os.path.realpath(__file__))
requirement_path = f"{lib_folder}/requirements.txt"
install_requires = [] # Here we'll add: ["gunicorn", "docutils>=0.3", "lxml==0.5a7"]
if os.path.isfile(requirement_path):
    with open(requirement_path) as f:
        install_requires = f.read().splitlines()
setup(name="mypackage", install_requires=install_requires, [...])

The execution of python setup.py install will install your package and all dependencies. Like @jwodder said it is not mandatory to create a requirements.txt file, you can just set install_requires directly in the setup.py script. But writing a requirements.txt file is a best practice.

In the setup function call, you also have to set version, packages, author, etc, read the doc for a complete example: https://docs.python.org/3/distutils/setupscript.html

Your package directory should look like this:

├── mypackage
│   ├── mypackage
│   │   ├── __init__.py
│   │   └── mymodule.py
│   ├── requirements.txt
│   └── setup.py
hayj
  • 1,159
  • 13
  • 21
  • 8
    `install_requires = list(f.read().splitlines())` is much more straightforward than appending one line at a time in a loop. (The `list` call may or may not be necessary; test and see.) – jpmc26 Oct 30 '18 at 17:32
  • Or maybe something like `[line for line in f.read().splitlines() if len(line) > 0]` to prevent blank lines – hayj Oct 30 '18 at 17:35
  • 13
    Or you could just forego the `requirements.txt` file entirely and write `setup(install_requires=['gunicorn', 'docutils>=0.3', 'lxml==0.5a7'], ...)` directly in your `setup.py`. – jwodder Oct 30 '18 at 17:42
  • 2
    I'd add, one should ignore comments and whitespaces prefix `install_requires = [line for line in map(str.lstrip, f.read().splitlines()) if len(line) > 0 and not line.startswith('#')]` – Shay Ben-Sasson May 24 '20 at 05:50
  • This will connect to the internet and install all the dependencies right? how can I instruct it to install from a local repo? will simply doing ```pip install mypacke.whl --no-index --find-links deps_folder``` do the trick? – Hossein Jul 08 '20 at 03:23
  • 1
    While all proposed solutions so far somehow parse the `requirements.txt` file and use these dependencies as `install_requires` in the `setup.py`, there is an argument whether this is a good practice or not: https://caremad.io/posts/2013/07/setup-vs-requirement/ – ketza Mar 09 '21 at 19:01
10

Another possible solution

try:
    # for pip >= 10
    from pip._internal.req import parse_requirements
except ImportError:
    # for pip <= 9.0.3
    from pip.req import parse_requirements

def load_requirements(fname):
    reqs = parse_requirements(fname, session="test")
    return [str(ir.req) for ir in reqs]

setup(name="yourpackage", install_requires=load_requirements("requirements.txt"))

Logovskii Dmitrii
  • 2,629
  • 4
  • 27
  • 44
  • 1
    This worked for me. No idea how this would fail in the future though (if it ever fails). – muammar Oct 03 '19 at 18:52
  • 3
    @muammar it could fail because pip is explicitly not supposed to be used programmatically. See that `_` in `pip._internal.req`? That means it's part of a package's internals, which shouldn't be used externally and may change without warning. There is absolutely no guarantee that it won't suddenly change, and in fact it has. – MattDMo Nov 07 '20 at 00:14
5

You generate egg information from your setup.py, then you use the requirements.txt from these egg information:

$ python setup.py egg_info
$ pip install -r <your_package_name>.egg-info/requires.txt 
Hoel Iris
  • 76
  • 1
  • 6
2

In Python 3.4+, it is possible to use the Path class from pathlib, to do effectively the same thing as @hayj answer.

from pathlib import Path
import setuptools

...

def get_install_requires() -> List[str]:
    """Returns requirements.txt parsed to a list"""
    fname = Path(__file__).parent / 'requirements.txt'
    targets = []
    if fname.exists():
        with open(fname, 'r') as f:
            targets = f.read().splitlines()
    return targets

...

setuptools.setup(
    ...
    install_requires=get_install_requires(),
    ...
)
d-man
  • 476
  • 4
  • 24