-1

I'm running some custom Python code in Sphinx and need to get the path to the caller module. (Essentially this is the caller's __file__ object; I need to interpret a filename relative to this location.)

I can get the filename from inspect.stack() as per How to use inspect to get the caller's info from callee in Python?, but apparently I need to interpret this filename in the context of the Python startup directory. (Sometimes inspect.stack()[k][1] is an absolute path but sometimes it is a relative path like conf.py; the inspect.stack() function doesn't seem to document this but unutbu claims in a comment that it is relative to the Python startup directory. )

Sphinx does some unintentionally evil things like this comment:

# This file is execfile()d with the current directory set to its
# containing dir.

so os.path.abspath(filename) doesn't work, and

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath('extensions'))

so sys.path[0] is corrupted by the time my code gets to it.

How do I find the startup directory in Python, if sys.path has been modified?

Or is there another way to get the path to the caller module?


If I run Jean-François Fabre's answer

for file,line,w1,w2 in traceback.extract_stack():
    sys.stdout.write('  File "{}", line {}, in {}\n'.format(file,line,w1))

I get this:

File "c:\app\python\anaconda\1.6.0\Scripts\sphinx-build-script.py", line 5, in <module>
File "c:\app\python\anaconda\1.6.0\lib\site-packages\Sphinx-1.4.1-py2.7.egg\sphinx\__init__.py", line 51, in main
File "c:\app\python\anaconda\1.6.0\lib\site-packages\Sphinx-1.4.1-py2.7.egg\sphinx\__init__.py", line 92, in build_main
File "c:\app\python\anaconda\1.6.0\lib\site-packages\Sphinx-1.4.1-py2.7.egg\sphinx\cmdline.py", line 243, in main
File "c:\app\python\anaconda\1.6.0\lib\site-packages\Sphinx-1.4.1-py2.7.egg\sphinx\application.py", line 155, in __init__
File "conf.py", line 512, in setup
[more lines elided, the conf.py is the one that matters]

so the problem is that I need to find the path to conf.py but the current directory has been changed by Sphinx so I can't just do os.path.abspath(caller_filename)

Community
  • 1
  • 1
Jason S
  • 184,598
  • 164
  • 608
  • 970
  • 3
    `os.getcwd()` is the directory where python is running `__file__` will tell you the path and filename of any .py file ... – Joran Beasley Sep 02 '16 at 19:23
  • other than that i dont know what you mean by "startup directory" – Joran Beasley Sep 02 '16 at 19:24
  • "but I need to interpret this filename in the context of some directory." what do you mean? – Jean-François Fabre Sep 02 '16 at 19:24
  • I can't use `__file__`, I need to find the *caller's* `__file__` – Jason S Sep 02 '16 at 19:25
  • @JoranBeasley I know both of those facts already and neither helps me. – Jason S Sep 02 '16 at 19:28
  • 2
    What's the actual problem you're trying to solve? – jonrsharpe Sep 02 '16 at 19:29
  • I would rather avoid playing the XY-problem game if that's what you're thinking of @jonrsharpe. I need to get the path to the caller's module. – Jason S Sep 02 '16 at 19:30
  • @JasonS does `sys.argv[0]` work in this case which wil be the path to the *actual* script? – Jon Clements Sep 02 '16 at 19:32
  • thanks NinjaPuppy -- nope, that doesn't work for me in this case either since it returns the Sphinx build script c:\app\python\anaconda\1.6.0\Scripts\sphinx-build-script.py which calls execfile() to run my conf.py – Jason S Sep 02 '16 at 19:35
  • If Sphinx execfile()s a script `with the current directory set to its containing dir` then a "relative path" within that process should be _relative to that_? So if **Python** just says `conf.py` in that Traceback, then it seems that must be relative to the "containing dir". (Seems logical, not tested) – jalanb Feb 26 '18 at 21:52

2 Answers2

4

you can get what you want using the traceback module. I've written this sample code in PyScripter:

import traceback,sys

def demo():
   for file,line,w1,w2 in traceback.extract_stack():
       sys.stdout.write('  File "{}", line {}, in {}\n'.format(file,line,w1))

def foo():
    demo()

foo()

which gives on my Windows PC running PyScripter:

  File "C:\Users\dartypc\AppData\Roaming\PyScripter\remserver.py", line 63, in <module>
  File "C:\Users\dartypc\AppData\Roaming\PyScripter\remserver.py", line 60, in main
  File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\utils\server.py", line 227, in start
  File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\utils\server.py", line 139, in accept
  File "C:\Users\dartypc\AppData\Roaming\PyScripter\remserver.py", line 14, in _accept_method
  File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\utils\server.py", line 191, in _serve_client
  File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\core\protocol.py", line 391, in serve_all
  File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\core\protocol.py", line 382, in serve
  File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\core\protocol.py", line 350, in _dispatch
  File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\core\protocol.py", line 298, in _dispatch_request
  File "C:\Program Files\PyScripter\Lib\rpyc.zip\rpyc\core\protocol.py", line 528, in _handle_call
  File "<string>", line 420, in run_nodebug
  File "C:\DATA\jff\data\python\stackoverflow\simple_traceback.py", line 10, in <module>
  File "C:\DATA\jff\data\python\stackoverflow\simple_traceback.py", line 8, in foo
  File "C:\DATA\jff\data\python\stackoverflow\simple_traceback.py", line 4, in demo
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
  • That doesn't seem to work; I still get stuck with a relative file (I'll comment in my answer) – Jason S Sep 02 '16 at 19:41
  • damn: tough luck! Would trying to interpret `PYTHONPATH` environment variable a possibility? At least it is not butchered like `sys.path`. – Jean-François Fabre Sep 02 '16 at 19:44
  • no, I think the problem is that Sphinx runs `execfile()` and this messes up introspection. – Jason S Sep 02 '16 at 19:45
  • oh, that's why they removed it in python 3 then :) do you know where this call to `execfile` is done? wouldn't it be possible to patch the file so it avoids the use of `execfile`? – Jean-François Fabre Sep 02 '16 at 19:47
3

Bah, I'm just going to get around the issue by allowing callers to pass in their __file__ value :-(

my function:

def do_something(app, filename, relroot=None):
   if relroot is None:
      relroot = '.'
   else:
      relroot = os.path.dirname(relroot)
   path = os.path.join(relroot, filename)
   ...

in conf.py:

def setup(app):
   mymodule.do_something(app, 'path/to/file', relroot=__file__)
Jason S
  • 184,598
  • 164
  • 608
  • 970