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.

Mittenchops
  • 289
  • 1
  • 8
  • 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 '22 at 20:51
  • This doesn’t change the results of ‘os.getcwd()’ which remain in the original directory – Mittenchops Dec 30 '22 at 21:27

2 Answers2

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.

C4ffeine Add1ct
  • 460
  • 3
  • 8
  • 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. – Light Matters Jun 07 '23 at 09: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.

Ian
  • 1,321
  • 10
  • 12
  • I’m working in a codebase that requires absolute imports instead of relative ones and this cannot be changed. – Mittenchops Dec 21 '22 at 15:24