fredrik
fredrik

Reputation: 10281

How to detect if module is installed in "editable mode"?

I'm pip-installing my module like so:

cd my_working_dir
pip install -e .

When I later import the module from within Python, can I somehow detect if the module is installed in this editable mode?

Right now, I'm just checking if there's a .git folder in os.path.dirname(mymodule.__file__)) which, well, only works if there's actually a .git folder there. Is there a more reliable way?

Upvotes: 22

Views: 4789

Answers (6)

Janosh
Janosh

Reputation: 4692

There is actually a public-facing std-lib API for this in importlib.metadata.

import json
from importlib.metadata import Distribution

direct_url = Distribution.from_name("pkg-name").read_text("direct_url.json")
pkg_is_editable = json.loads(direct_url).get("dir_info", {}).get("editable", False)

Found out about it in this GH issue. See here for the direct_url.json specification.

If you have importlib 6.11.0 or later, use Distribution.origin to get a Namespace representation of direct_url.json directly.

Upvotes: 4

Thomas Dietert
Thomas Dietert

Reputation: 69

Run pip list, and there is a column called "Editable project location". If that column has a value, specifically the directory from which you installed it, then the package is pip installed in editable mode.

Upvotes: 6

Eyal Levin
Eyal Levin

Reputation: 18426

Haven't found a definite source for this, but if the output of

$ pip show my-package -f

is something like this:

..
..
Files:
  __editable__.my-package-0.0.5.pth
  my-package-0.0.5.dist-info/INSTALLER
  my-package-0.0.5.dist-info/METADATA
  my-package-0.0.5.dist-info/RECORD
  my-package-0.0.5.dist-info/REQUESTED
  my-package-0.0.5.dist-info/WHEEL
  my-package-0.0.5.dist-info/direct_url.json
  my-package-0.0.5.dist-info/top_level.txt

then it's probably editable.

Upvotes: 1

egeres
egeres

Reputation: 117

Recently I had to test if various packages were installed in editable mode across different machines. Running pip show <package name> reveals not only the version, but other information about it, which includes the location of the source code. If the package was not installed in editable mode, this location will point to site-packages, so for my case it was sufficient with checking the output of such command:

import subprocess

def check_if_editable(name_of_the_package:str) -> bool:
    out = subprocess.check_output(["pip", "show", f"{name_of_the_package}"]).decode()
    return "site-packages" in out

Upvotes: 0

betontalpfa
betontalpfa

Reputation: 3752

Another workaround:

Place an "not to install" file into your package. This can be a README.md, or a not_to_install.txt file. Use any non-pythonic extension, to prevent that file installation. Then check if that file exists in your package.

The suggested source structure:

my_repo
|-- setup.py
`-- awesome_package
    |-- __init__.py
    |-- not_to_install.txt
    `-- awesome_module.py

setup.py:

# setup.py
from setuptools import setup, find_packages

setup(
    name='awesome_package',
    version='1.0.0',

    # find_packages() will ignore non-python files.
    packages=find_packages(),
)

The __init__.py or the awesome_module.py:

import os

# The current directory
__here__ = os.path.dirname(os.path.realpath(__file__))

# Place the following function into __init__.py or into awesome_module.py

def check_editable_installation():
    '''
        Returns true if the package was installed with the editable flag.
    '''
    not_to_install_exists = os.path.isfile(os.path.join(__here__, 'not_to_install.txt'))
    return not_to_install_exists

Upvotes: 3

florisla
florisla

Reputation: 13528

I don't know of a way to detect this directly (e.g. ask setuptools).

You could try to detect that you package can not be reached through the paths in sys.path. But that's tedious. It's also not bullet proof -- what if it can be reached through sys.path but it's also installed as en editable package?

The best option is to look at the artifacts an editable install leaves in your site-packages folder. There's a file called my_package.egg-link there.

from pathlib import Path

# get site packages folder through some other magic

# assuming this current file is located in the root of your package
current_package_root = str(Path(__file__).parent.parent)

installed_as_editable  = False
egg_link_file = Path(site_packages_folder) / "my_package.egg-link"
try:
    linked_folder = egg_link_file.read_text()
    installed_as_editable = current_package_root in linked_folder
except FileNotFoundError:
    installed_as_editable = False

Note: to make this a bit more bullet-proof, read only the first line of the egg-link file and parse it using Path() as well to account for proper slashes etc.

Upvotes: 0

Related Questions