1

How can I tell the shell to always open relative to my project root?

I have the following python project structure:

mypyjunk
├── mypyjunk
│   ├── common.py
│   ├── __init__.py
│   └── jitter.py
├── README.md
└── tests
    ├── __init__.py
    └── test_all.py

And mypyjunk/jitter.py contains:

from mypyjunk.common import constants


def do_things():
    print(constants)

When I open this file and open a shell interpreter with C-c C-p, I try to send the buffer to the interpreter with C-c C-r, it says:

>>> from mypyjunk.common import constants
... 
... 
... def do_things():
...     print(constants)

Traceback (most recent call last):
  File "<string>", line 17, in __PYTHON_EL_eval
  File "/home/user/mypyjunk/mypyjunk/jitter.py", line 1, in <module>
    from mypyjunk.common import constants
ModuleNotFoundError: No module named 'mypyjunk'

Which tells me this shell has an unexpected root directory, eg:

>>> import os
>>> os.getcwd()
'/home/user/mypyjunk/mypyjunk'

Instead of /home/user/mypyjunk/ where my absolute imports would make sense.

What settings do I need to change to tell python-mode or compile-mode to respect the project root? Also, assume I have lots of projects in ~/ so I can't hard-code a path here in my .emacs.d/init.el, without potentially clobbering my other projects.

2
  • Can you change the working directory of the buffer where the interpreter is running with M-x cd? If that works, then something might be done through a hook or an advice or some other Emacs means...
    – NickD
    Dec 21, 2022 at 20:51
  • This doesn’t change the results of ‘os.getcwd()’ which remain in the original directory Dec 30, 2022 at 21:27

2 Answers 2

2

I don't know if there's a 'nicer' way using already existing stuff in python.el, but if you just want the interpreter to start at the project root while you're within that project, I'd do something like:

(defvar-local my-python-dir-override nil)

(defun my-run-python ()
  (interactive)
  (let ((default-directory (or my-python-dir-override default-directory)))
    (call-interactively #'run-python)))

Then set my-python-dir-override in your .dir-locals.el in the project root directory, and bind C-c C-p to my-run-python.

If you don't want to use .dir-locals.el, you could swap out my-python-dir-override for (projectile-project-root) (if you're a projectile user) or (project-root), though this logic will then apply to all projects.

1
  • Very nice. This is the sort of answer that I was really struggling to find. Concise, descriptive code that really solves the problem and doesn't introduce lots of sys imports etc. I'm surprised that this hasn't been a problem for more people. Personally, I would structure the answer the other way around as the project-root advice solves the general problem and any mention of dir-locals, rightly or wrongly, tends to make think it's a hack. Jun 7 at 9:04
1

Importing modules is explained in this document. For your case, in jitter.py write from . common import constants (you can use full name for common)

EDIT: The specified document treat also the case for absolute path - you may use sys.path and sys.append. Other solution(s) here, see Nagev's response or use the importlib module.

1
  • I’m working in a codebase that requires absolute imports instead of relative ones and this cannot be changed. Dec 21, 2022 at 15:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.