Does editable installation (`pip install -e .`) not run `setup.py`?

Hi all,

Does pip install -e . – editable installation with pip for a project available locally with pyproject.toml – not run setup.py? Because I have a project I am developing, with a pyproject.toml that installs with some custom steps expressed with setup.py which works with pip install . and python -m build (as per PyPa, IIRC), but the custom steps seem to be entirely omitted when installing in “editable” mode.

The pyproject.toml, edited for brevity:

[build-system]
requires = [ "setuptools", "setuptools-scm", "wheel" ]
build-backend = "setuptools.build_meta"

[project]
name = "foobar"
dynamic = [ "version" ]
requires-python = ">=3.11"

[tool.setuptools_scm]

The reason I have a setup.py is because there’s an additional step it expresses before the wheel is built – I run make to pre-process some Python code. Shouldn’t really matter how, but I need said additional step, and setup.py ended up being the way I could enable running the step:

import setuptools.command.build
from setuptools import Command, setup

import os
import os.path
import subprocess

class MakeCommand(Command):
    """Class of `setuptools`/`distutils` commands which invoke a `make` program.

    GNU Make (http://www.gnu.org/software/make) is currently assumed for providing `make`. The program is invoked in a manner where it changes the working directory to the build directory advertised for the command (utilizing `self.set_undefined_options` as hinted at by [the documentation](http://setuptools.pypa.io/en/latest/userguide/extension.html) which defers to `help(setuptools.command.build.SubCommand)`).

    The command is expected to produce build artefacts which will be added to the wheel.
    """
    build_lib: str | None
    def finalize_options(self) -> None:
        self.set_undefined_options('build_py', ('build_lib', 'build_lib'))
    def initialize_options(self) -> None:
        self.build_lib = None
    def run(self, *args, **kwargs) -> None:
        os.makedirs(self.build_lib, exist_ok=True)
        subprocess.check_call(('make', '-C', self.build_lib, '-f', os.path.realpath('Makefile')))

class BuildCommand(setuptools.command.build.build):
    sub_commands = [ ('build_make', None) ] + setuptools.command.build.build.sub_commands # Makes the `build_make` command a sub-command of the `build` command, which has the effect of the former being invoked when the latter is invoked (which is invoked in turn when the wheel must be built, through the `bdist_wheel` command)

setup(cmdclass={ 'build': BuildCommand, 'build_make': MakeCommand })

I am not against abandoning setup.py – I am not sure if my particular set-up (in the broad(er) sense of the term) is “deprecated”. It was only an accident I noticed while doing local development that pip install -e . doesn’t end up invoking make as expressed with setup.py, which is essential for the project. Everything works exactly as intended with pip install ., in comparison. I am not sure I am doing this all correctly anymore – I want to declare what can be declared with pyproject.toml while make is run when building the wheel – my sdist should be the original (before make artifacts) source code.

In pure Python projects, editable installs link to the src dir (whereever that my be) and add some metadata. So that other type doesn’t need to run setup.py

How should an editable install of a project that needs to run GNU Make behave anyway? IS it setup to import the outputs from GNU Make in the repo dir, just as well as from their installed location.

You need to figure out which setuptools commands and sub-commands are involved in an editable installation. Maybe the setuptools command build is not involved, I think build_ext might be, but I have not checked. For sure, you can still have a setup.py with custom commands and they will be taken into account, the difficulty is in figuring out which commands and how to customize them properly.

In case you have not seen it yet, here is probably the most relevant documentation:

I could be wrong but I think you will want to override editable_wheel, at least, that’s what we had to do in Bokeh:

Thank you, James. I understood some of what you said and how it related to my problem, but not all of it like that:

What is the “other type” here? I only authored a setup.py because I couldn’t find any way to plug in running of make, something that is essential in my set-up as it builds artefacts (Python modules) that make it into the wheel as part of the distribution.

The same way that it would if I just ran make “manually” in the repository root directory (there’s a Makefile there, obviously), before I ran e.g. python -m build? Maybe I am asking of the “pipeline” something that it just was never meant to support? I imagine there are projects that use “custom” steps as part of building the wheel?

I am sorry, I did not understand what you meant here at all.

Am I better off writing a small build.py module that imports and invokes build_wheel from setuptools after running make, and using that as my build backend explicitly? E.g.:

[build-system]
requires = ["setuptools", "wheel"]
build-backend = "build"
from setuptools.build_meta import build_wheel as _build_wheel
from setuptools.build_meta import build_editable as _build_editable
import subprocess

def run_make():
    print("Running `make` before building...")
    subprocess.run(["make"], check=True)  # Ensure `make` runs before any build

def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
    run_make()
    return _build_wheel(wheel_directory, config_settings, metadata_directory)

def build_editable(wheel_directory, config_settings=None, metadata_directory=None):
    run_make()
    return _build_editable(wheel_directory, config_settings, metadata_directory)

I can, but I want to avoid writing code where achieving my goal is idiomatically done differently.

Mind you, I think part of my problem, as general as it is, is to give instructions to downstream participants of the project so they ideally have to only keep two sets of instructions at hand, a) how to build the wheel (for distribution, normally) ,and b) how to rapidly develop (normally implying editable install, but again, only if that is the idiomatic solution, which I have understood it is).

I am not sure if you saw it in my original post, but I do have a setup.py that does just that – involving setuptools API for sub-classing command(s). I think you’re implying I am not using the right set of commands? Or that I am not using sufficient set? Like I said, my setup.py works “perfectly” when it is invoked automatically as part of python -m build or pip install . but the script isn’t even executed with the -e switch to the latter. If the script isn’t even run, what use is there of twiddling with different setuptools API constructs? None of it is invoked. (turns out my observation was incorrect, as clarified since)

Thank you for the Bokeh tip, but I don’t want to rely on a third-party build backend, if I can avoid it (which I think I can). (again, a misunderstanding on my part because I didn’t bother looking into Bokeh and what I was being referred to specifically, in depth) If setuptools cannot support my use case (that cooks down to a custom/extra step for building the wheel), then I’d take my chances with a simple build script that replaces it from the perspective of pyproject.toml, implementing PEP 517.

Hi Armén. Happy to help (hopefully).

What is the “other type” here?

Non-Python extension modules, e.g. containing C, C++, Rust, Go or even Fortran code, that require compilation (either by the user’s pip, especially if they have gcc, or by the packager into a wheel for the user’s platform).

Lets just assume a repo looks like: path_to_repo/src/module_name/main_code.py, and the make script, or any other system process a build hook can call, just creates or copies in some_extra_code.py into module_name. As I understand it pip install -e . adds a .pth file to path_to_repo/src. But then the editable installation will not include subsequent changes to some_extra_code.py unless pip does something special like a sym link, or a second .pth file, and I don’t think pip tries to guess what every possible third party tool will do, (even widespread ones like Make).

So a new proper install needs to be made, every time the Python files generated or copied by Make change. If they are not likely to change, maybe that’s fine, and it might be worth adding something to let you work on editable installs of the main code, together with extra non-editable-installed modules from a build step.

I think this is a misconception, setuptools is a third-party build backend. Python used to (before Python 3.12) ship a built-in build backend as part of distutils but no longer does.

I think you should feel free to use a third-party build backend, there are lots of options available these days.

Also looking closer at the bokeh setup.py - they are “just” using setuptools, just like your library, so I don’t see why you can’t crib off their setup.

2 Likes

Correct, editable_wheel comes from setuptools.command.editable_wheel and we use our subclass to customize setuptools in the standard way:

setup(cmdclass={
    "build": Build, 
    "editable_wheel": EditableWheel, 
    "sdist": Sdist
})

with the typical build-backend = "setuptools.build_meta" in pyproject.toml.

Then running pip install -v -e . --config-settings quiet=true both the custom build steps and the editable install can be observed:

Using pip 25.0 from /Users/bryan/anaconda3/envs/dev312/lib/python3.12/site-packages/pip (python 3.12)
Obtaining file:///Users/bryan/work/bokeh
  Running command pip subprocess to install build dependencies
  Using pip 25.0 from /Users/bryan/anaconda3/envs/dev312/lib/python3.12/site-packages/pip (python 3.12)
  Collecting setuptools>=64
    Obtaining dependency information for setuptools>=64 from https://files.pythonhosted.org/packages/37/66/d2d7e6ad554f3a7c7297c3f8ef6e22643ad3d35ef5c63bf488bc89f32f31/setuptools-76.0.0-py3-none-any.whl.metadata
    Using cached setuptools-76.0.0-py3-none-any.whl.metadata (6.7 kB)
  Collecting setuptools-git-versioning
    Obtaining dependency information for setuptools-git-versioning from https://files.pythonhosted.org/packages/c0/ba/daf16c2d1965bf6237fb696639e3e93645ac6801f7dcaf9ec694a74e9326/setuptools_git_versioning-2.1.0-py3-none-any.whl.metadata
    Using cached setuptools_git_versioning-2.1.0-py3-none-any.whl.metadata (6.1 kB)
  Collecting colorama
    Obtaining dependency information for colorama from https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl.metadata
    Using cached colorama-0.4.6-py2.py3-none-any.whl.metadata (17 kB)
  Collecting packaging (from setuptools-git-versioning)
    Obtaining dependency information for packaging from https://files.pythonhosted.org/packages/88/ef/eb23f262cca3c0c4eb7ab1933c3b1f03d021f2c48f54763065b6f0e321be/packaging-24.2-py3-none-any.whl.metadata
    Using cached packaging-24.2-py3-none-any.whl.metadata (3.2 kB)
  Using cached setuptools-76.0.0-py3-none-any.whl (1.2 MB)
  Using cached setuptools_git_versioning-2.1.0-py3-none-any.whl (10 kB)
  Using cached colorama-0.4.6-py2.py3-none-any.whl (25 kB)
  Using cached packaging-24.2-py3-none-any.whl (65 kB)
  Installing collected packages: setuptools, packaging, colorama, setuptools-git-versioning
    Creating /private/var/folders/g5/0qds8m2n00qcfqkfxggy2jf40000gn/T/pip-build-env-8vapj1ua/overlay/bin
    changing mode of /private/var/folders/g5/0qds8m2n00qcfqkfxggy2jf40000gn/T/pip-build-env-8vapj1ua/overlay/bin/setuptools-git-versioning to 755
  Successfully installed colorama-0.4.6 packaging-24.2 setuptools-76.0.0 setuptools-git-versioning-2.1.0
  Installing build dependencies ... done
  Running command Checking if build backend supports build_editable
  Checking if build backend supports build_editable ... done
  Running command Getting requirements to build editable
  warning: no directories found matching 'src/bokeh/sampledata/_data'
  warning: no files found matching 'src/bokeh/_sri.json'
  Getting requirements to build editable ... done
  Running command Preparing editable metadata (pyproject.toml)
  warning: no directories found matching 'src/bokeh/sampledata/_data'
  warning: no files found matching 'src/bokeh/_sri.json'
  no previously-included directories found matching 'src/bokeh/server/static'
  Preparing editable metadata (pyproject.toml) ... done
Requirement already satisfied: Jinja2>=2.9 in /Users/bryan/anaconda3/envs/dev312/lib/python3.12/site-packages (from bokeh==3.7.0.dev8+31.g84dbadfb.dirty) (3.1.5)
Requirement already satisfied: contourpy>=1.2 in /Users/bryan/anaconda3/envs/dev312/lib/python3.12/site-packages (from bokeh==3.7.0.dev8+31.g84dbadfb.dirty) (1.3.1)
Requirement already satisfied: narwhals>=1.13 in /Users/bryan/anaconda3/envs/dev312/lib/python3.12/site-packages (from bokeh==3.7.0.dev8+31.g84dbadfb.dirty) (1.25.2)
Requirement already satisfied: numpy>=1.16 in /Users/bryan/anaconda3/envs/dev312/lib/python3.12/site-packages (from bokeh==3.7.0.dev8+31.g84dbadfb.dirty) (2.1.3)
Requirement already satisfied: packaging>=16.8 in /Users/bryan/anaconda3/envs/dev312/lib/python3.12/site-packages (from bokeh==3.7.0.dev8+31.g84dbadfb.dirty) (24.2)
Requirement already satisfied: pandas>=1.2 in /Users/bryan/anaconda3/envs/dev312/lib/python3.12/site-packages (from bokeh==3.7.0.dev8+31.g84dbadfb.dirty) (2.2.3)
Requirement already satisfied: pillow>=7.1.0 in /Users/bryan/anaconda3/envs/dev312/lib/python3.12/site-packages (from bokeh==3.7.0.dev8+31.g84dbadfb.dirty) (11.1.0)
Requirement already satisfied: PyYAML>=3.10 in /Users/bryan/anaconda3/envs/dev312/lib/python3.12/site-packages (from bokeh==3.7.0.dev8+31.g84dbadfb.dirty) (6.0.2)
Requirement already satisfied: tornado>=6.2 in /Users/bryan/anaconda3/envs/dev312/lib/python3.12/site-packages (from bokeh==3.7.0.dev8+31.g84dbadfb.dirty) (6.4.2)
Requirement already satisfied: xyzservices>=2021.09.1 in /Users/bryan/anaconda3/envs/dev312/lib/python3.12/site-packages (from bokeh==3.7.0.dev8+31.g84dbadfb.dirty) (2025.1.0)
Requirement already satisfied: MarkupSafe>=2.0 in /Users/bryan/anaconda3/envs/dev312/lib/python3.12/site-packages (from Jinja2>=2.9->bokeh==3.7.0.dev8+31.g84dbadfb.dirty) (3.0.2)
Requirement already satisfied: python-dateutil>=2.8.2 in /Users/bryan/anaconda3/envs/dev312/lib/python3.12/site-packages (from pandas>=1.2->bokeh==3.7.0.dev8+31.g84dbadfb.dirty) (2.9.0.post0)
Requirement already satisfied: pytz>=2020.1 in /Users/bryan/anaconda3/envs/dev312/lib/python3.12/site-packages (from pandas>=1.2->bokeh==3.7.0.dev8+31.g84dbadfb.dirty) (2024.1)
Requirement already satisfied: tzdata>=2022.7 in /Users/bryan/anaconda3/envs/dev312/lib/python3.12/site-packages (from pandas>=1.2->bokeh==3.7.0.dev8+31.g84dbadfb.dirty) (2025.1)
Requirement already satisfied: six>=1.5 in /Users/bryan/anaconda3/envs/dev312/lib/python3.12/site-packages (from python-dateutil>=2.8.2->pandas>=1.2->bokeh==3.7.0.dev8+31.g84dbadfb.dirty) (1.17.0)
Building wheels for collected packages: bokeh
  Running command Building editable for bokeh (pyproject.toml)
  warning: no directories found matching 'src/bokeh/sampledata/_data'
  warning: no files found matching 'src/bokeh/_sri.json'
  no previously-included directories found matching 'src/bokeh/server/static'
  warning: no directories found matching 'src/bokeh/sampledata/_data'
  warning: no files found matching 'src/bokeh/_sri.json'
  no previously-included directories found matching 'src/bokeh/server/static'

          Editable install will be performed using .pth file to extend `sys.path` with:
          ['src']

  Options like `package-data`, `include/exclude-package-data` or
  `packages.find.exclude/include` may have no effect.


  Building BokehJS... Success!

  Build output:

     [09:32:20] Using nodejs v20.18.1 and npm 11.1.0
     [09:32:20] Starting 'styles:compile'...
     [09:32:21] Finished 'styles:compile' after 86 ms
     [09:32:21] Starting 'scripts:styles'...
     [09:32:21] Finished 'scripts:styles' after 19 ms
     [09:32:21] Starting 'scripts:glsl'...
     [09:32:21] Finished 'scripts:glsl' after 4 ms
     [09:32:21] Starting 'scripts:version'...
     [09:32:21] Finished 'scripts:version' after 0 ms
     [09:32:21] Starting 'scripts:compile'...
     [09:32:24] Finished 'scripts:compile' after 3.42 s
     [09:32:24] Starting 'scripts:bundle'...
     [09:32:25] Finished 'scripts:bundle' after 584 ms
     [09:32:25] Finished 'lib:build'
     [09:32:25] Finished 'scripts:build'
     [09:32:25] Starting 'compiler:ts'...
     [09:32:25] Finished 'compiler:ts' after 352 ms
     [09:32:25] Starting 'compiler:build'...
     [09:32:25] Finished 'compiler:build' after 498 ms
     [09:32:25] Starting 'examples:compile'...
     [09:32:26] Finished 'examples:compile' after 379 ms
     [09:32:26] Finished 'examples:build'
     [09:32:26] Starting 'pack'...
     [09:32:27] Finished 'pack' after 1.60 s
     [09:32:27] Finished 'build'
     [09:32:27] Finished 'top-level'

   Build time: 8.0 seconds

  Build artifact sizes:
    - bokeh.js             : 2543.5 KB
    - bokeh.min.js         : 1103.0 KB
    - bokeh-widgets.js     :  799.9 KB
    - bokeh-widgets.min.js :  305.7 KB
    - bokeh-tables.js      :  922.6 KB
    - bokeh-tables.min.js  :  294.2 KB
    - bokeh-api.js         :  217.2 KB
    - bokeh-api.min.js     :  118.7 KB
    - bokeh-gl.js          :  553.6 KB
    - bokeh-gl.min.js      :  206.1 KB
    - bokeh-mathjax.js     : 2686.0 KB
    - bokeh-mathjax.min.js : 1744.0 KB

  Installing BokehJS... Success!

  Used NEWLY BUILT BokehJS from bokehjs/build

  Building editable for bokeh (pyproject.toml) ... done
  Created wheel for bokeh: filename=bokeh-3.7.0.dev8+31.g84dbadfb.dirty-0.editable-py3-none-any.whl size=6768 sha256=c8806c8c4ad62e3080aaa5ec66e871a75fd50ca781c04d86032ea51d0d9940da
  Stored in directory: /private/var/folders/g5/0qds8m2n00qcfqkfxggy2jf40000gn/T/pip-ephem-wheel-cache-20abllgm/wheels/a4/1d/20/dd0dcceacf6cdc4be5fd20dd55dd3eb693e2761203795e29c7
Successfully built bokeh
Installing collected packages: bokeh
  Attempting uninstall: bokeh
    Found existing installation: bokeh 3.7.0.dev8+31.g84dbadfb.dirty
    Uninstalling bokeh-3.7.0.dev8+31.g84dbadfb.dirty:
      Removing file or directory /Users/bryan/anaconda3/envs/dev312/bin/bokeh
      Removing file or directory /Users/bryan/anaconda3/envs/dev312/lib/python3.12/site-packages/__editable__.bokeh-3.7.0.dev8+31.g84dbadfb.dirty.pth
      Removing file or directory /Users/bryan/anaconda3/envs/dev312/lib/python3.12/site-packages/bokeh-3.7.0.dev8+31.g84dbadfb.dirty.dist-info/
      Successfully uninstalled bokeh-3.7.0.dev8+31.g84dbadfb.dirty
  changing mode of /Users/bryan/anaconda3/envs/dev312/bin/bokeh to 755
Successfully installed bokeh-3.7.0.dev8+31.g84dbadfb.dirty
2 Likes

Yes, of course, I saw this.

That was my guess, yes. That editable installations uses a different subset of setuptools (sub)-commands, that the one you already customized. But maybe I am wrong, I have not used custom setuptools commands in years…

That would be very surprising to me. On my side, a quick and dirty test showed that pip install --editable . lead to the usage of setup.py and a custom build command defined in it.


In my case, an empty project with only those two files, and running pip install --editable . fails, but shows that the custom build command is executed.

pyproject.toml

[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[project]
name = "foo"
version = "0"

setup.py

import setuptools

class MyCommand(setuptools.Command):

    def run(self):
        print("run")

    def finalize_options(self):
        print("finalize_options")

    def initialize_options(self):
        print("initialize_options")

    def get_source_files(self):
        print("get_source_files")
        return []

setuptools.setup(
    cmdclass={'build': MyCommand},
)

(puts on pip maintainer hat)

I’m sure many folks are already aware of the nuance here, but I’ll note that pip entirely delegates wheel building to the build backend. Modern editable installs are implemented by requesting that the build backend produce a special wheel that result in an editable install when unpacked. pip does not create any .pth files or do any symlink trickery. It’s entirely up to the build backend to how they want to implement editable installs. Matter of fact, setuptools has several modes of editable installs.

As other people have pointed out, setuptools is responsible for running setup.py. All pip will do is call the build_editable hook[1] to ask setuptools to build the aforementioned special wheel. I would assume setuptools runs setup.py at some point (it definitely does for a normal wheel build), but in what exact circumstances, I don’t know. That is the question to be answered in this thread, anyway.


  1. There are other hooks called by pip, but they aren’t relevant here. ↩︎

1 Like

I am sorry, I was wrong – I must have missed it somehow. Testing more rigorously, I see that my run method in a setuptools.Command subclass is invoked also for pip install -e .. I am investigating further now why I had come to the conclusion make wasn’t run (since run creates the subprocess as per my setup.py).

1 Like

That’s super helpful - thanks.

@sinoroc, the reason I had come to the somewhat “wrong” conclusion that make wasn’t run for editable installation that invoked setup.py after all, is because I can observe that self.build_lib which is usually src relative to project root (where pyproject.toml) when the wheel is being built, for instance, is a temporary directory for editable installation mode. Meaning that make is executed, it’s just that it’s executed in the temporary directory (where artifacts per Makefile then end up being built instead of src) so the sum effect is that nothing of the custom build artifacts ends up in src.

I will now play with the editable_wheel key of the cmdclass dictionary passed to setup and see if what new I have learned since I posted about my problem, will help me piece the puzzle together.

Thank you for providing valuable insight here!

1 Like

That’s pretty much my scenario indeed. I have a templates folder beside src, which ends at the head of a template processor, spitting out artifacts that should rest in src. Without the template processing step, content of src is incomplete as some modules that are referenced from other modules (that aren’t produced by template processor) in src, are missing, breaking the code that unconditionally imports them (as is convention with Python).

As I understand, .pth (PTH) files are just a better means to modify the import path without mucking about with amending sys.path with Python proper. To that end, I’d expect pip install -e ., if it adds a PTH file such that import path includes e.g. src (accomplishing the “editable” part) then indeed it needs not care about e.g. make – all pip install -e . needs to do is to make sure module(s) in src are first priority (prepended to sys.path, effectively) or in the very least the only choice for importing project modules. I totally do not expect Pip to be Make-aware, in fact had it been so aware, I’d consider it a serious design flaw – I’d expect people to be able to plug in any one of different generic building tools, of which there is plethora indeed (Meson, Scones, Ninja, Cargo, what have you).

Let me clarify some things first since “files” is so many different things – my make takes code from templates as input and spits out Python modules in src. The former I’d call build source code / pre-requisites, while the latter is build artifacts (which I want in the wheel and which are de-facto part of the installed package and used at run-time). But when you say “new proper install needs to be made, every time the Python files generated or copied by Make, change” I am not sure what you meant – quoting Development Mode (a.k.a. “Editable Installs”) - setuptools 76.0.0.post20250312 documentation below, I understand that only src needs be in the import path and so long make is run every time it needs to – when files that contribute to content of src, change (the mentioned pre-requisites) – then nothing else needs to be done for the editable install to work to my satisfaction I think:

An “editable installation” works very similarly to a regular install with pip install ., except that it only installs your package dependencies, metadata and wrappers for console and GUI scripts. Under the hood, setuptools will try to create a special .pth file in the target directory (usually site-packages) that extends the PYTHONPATH or install a custom import hook.

I think I understand now where our thinking diverges – pip install -e . only needs to be done “once” to set up current environment so that from then on editing module code in src doesn’t need subsequent pip install -e ., correct?

However, since some modules in src are built from templates through make, what you may have been trying to tell me is that one way or another I will have to be invoking make myself every time templates may have changed.

The last part is what I thought I could somehow automate with pip install -e . but now I realize that I do not want to run the command line every time code in templates (or src) has changed.

My reasoning above would suggest I accept the possibility that the editable one-off installation doesn’t invoke make but that the last part is something that must be done as templates changes.

I guess I am still partly confused because coming from C/C++ background (where I have tools that run Make automatically as I edit files under the “project” directory), I am not sure how to solve this Pythonically – I do rely on template processing but I want the rest of my project to be declared as simply as possible.

I have written a custom build backend – a simple script that delegates everything to setuptools.build_meta, really, but also runs make. I am not sure I need this approach, now that I think what I have through using setuptools.build_meta for backend and the setup.py file (possibly adding an editable_wheel property to cmdclass), may have been sufficient and more “idiomatic”.

Sorry for a lot of detail, it all reflects the big picture in my head currently :slight_smile:

Thank you, Bryan. Now that I have actually taken a look at what Bokeh is (I thought it was another Python build backend like Hatchling, Flit etc), and at its setup.py that you tried to point me at, I understand what you mean. It’s indeed very useful code, in the very least I am now wondering if my setup.py – using a sub-command for invoking Make – is inferior to Bokeh’s approach. I remember asking about setuptools API here on the forums, as I already then were struggling to make sense of Setuptools and how to augment building pipeline without throwing Setuptools out. I had gotten it to work, but only now I learn about the editable_wheel command (although I can see tracing sys.argv in setup.py that it’s being used with pip install -e . so technically something I could have discovered empirically earlier).

The way an “editable install” works is that the installation sets things up so that the source environment is available directly on sys.path. A .pth file is one way of doing this, but it’s not the only way - and it’s up to the build backend to choose the best approach for its needs.

The complexity comes from the fact that what it means to say “the source environment is on sys.path” isn’t well-defined. For pure Python code, it’s straightforward - the files in the source are used directly, and if you edit them, the updated code will be importable without needing a new install. But for code that needs to be “compiled” (whether that’s C code, or a Python source template as in your case) the compilation step is not automated - generally the recommendation is that you should reinstall in any case where you need to recompile, but the build backend may offer a shortcut. In your case, it would make sense that you don’t have to do a full reinstall, but you would still need to manually run make to regenerate the code from the template.

I’m not 100% sure what you mean when you say you don’t want to “run the command line” every time code in templates changes. If you mean you don’t want to run pip install -e ., then that’s fair, and you don’t have to. If you mean you don’t even want to run make, then IMO you’re expecting too much - an editable install has no way of knowing that a rebuild command needs to be run, or how to run it.

It can do the initial run of make when installing, but it won’t re-run make for you when templates changes. That’s up to you to do.

No worries - there are a lot of interacting parts here, and it’s not easy to describe all the details. Particularly as different tools are responsible for different parts of the process, and even precisely what it means to be “editable” is at the discretion of the build backend[1].


  1. That’s one reason setuptools has multiple modes for editable installs - they offer different trade-offs ↩︎

1 Like

I should have been clearer in the way I express myself, again – yes, I meant that I am not sure I want to run pip install -e . every time templates changes, but of course I can’t avoid running make since that’s what I wrote the Makefile for – the expectation to run make (which will, well, make nothing in case nothing’s changed) is entirely valid and sound. I hope this clears it up.

1 Like