-
Notifications
You must be signed in to change notification settings - Fork 1.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix reloader for modules run as scripts #1336
base: master
Are you sure you want to change the base?
Conversation
prepare test fixtures consisting of a package and module with relative & absolute import test scripting server runs with these variations - from package or from module - with or without reloader the server code is based off servertest.py
python doesn't have an obvious way to reveal the original command and module name, so we check the __main__ module for __package__ and infer it's a module running as a script unless the value is None, which implies a file path was provided modules running as scripts are invoked with -m flags packages running with scripts have base filename __main__.py from this we recreate an invocation
8c4acd4
to
47a55ac
Compare
I don't see a bug here that needs fixing. Let me quote https://docs.python.org/3/tutorial/modules.html#intra-package-references
So, relative imports are supposed to not work. And if |
Are you saying that code that runs fine without reloader should crash with reloader? The specs are more authoritative PEP 366, and that bit of advice in the tutorial only applies to unsupported versions of Python.
Moreover, the semantics of Line 3671 in f9b1849
does: it runs a child process without -m using the main module's file path, resulting in these failures.
|
No, I was saying that "I'm not sure if I understand the problem.". Thanks for the clarifying explanation. So, it looks like python is lying to us when called with $ python3 -mpkg
__name__ = __main__
__package__ = pkg
sys.path = ['/tmp', ...]
sys.executable = /usr/bin/python3
sys.argv = ['/tmp/pkg/__main__.py']
$ python3 /tmp/pkg/__main__.py
__name__ = __main__
__package__ = None
sys.path = ['/tmp/pkg', ...]
sys.executable = /usr/bin/python3
sys.argv = ['/tmp/pkg/__main__.py'] In short: If we detect a |
Nah, that would have been too easy. Bottle needs to check |
Sooo... would that work? args = [sys.executable] + sys.argv
if getattr(sys.modules.get('__main__'), '__package__', None):
# If a package was loaded with `python -m`, then `sys.argv`
# is wrong and needs fixing in some cases. See #1336
args[1:1] = ["-m", sys.modules['__main__'].__package__] Reasoning: There is always a Edit: This is basically what you did, but a bit shorter. Did I miss some edge-case here? |
Close: non-package modules are affected, too.
I included the package test case for thoroughness. In the module case, I'd think you need the full name: Even in the single file case, About the reasoning, I'd try to confirm all assumptions in the specs, and test them even then.
In light of this, my approach misses the top-level case ( |
these will support top-level module script test cases
arrange package script to import submodule new test cases check the top-level module name is prepared correctly module tests cases are renamed submodule for clarity
refactor function to return main script arguments
__main__ can be imported
It's been a while and I (accidentally) pushed the (broken) partial solution I did a while ago to master now :/ My current best guess for a complete solution is this monster: def _fixed_argv():
main = sys.modules.get('__main__')
package = getattr(main, '__package__', None)
if package is None: # python3 /path/to/file.py
return sys.argv
filepath = getattr(main, '__file__', sys.argv[0])
module = os.path.splitext(os.path.basename(filepath))[0]
if module == '__main__': # python3 -m package
return ["-m", package] + sys.argv[1:]
if package == '': # python3 -m module
return ["-m", module] + sys.argv[1:]
# python3 -m package.module
return ["-m", package + "." + module] + sys.argv[1:] Lots of code for an edge case that was never really supported. And even with that, it still breaks if the app changes the working directory (as the test server scripts do). Perhaps we should drop support for |
Modules run as scripts with
python -m
flag crash forrun(reloader=True)
, eg,bottle.py generates its own command missing
-m
and using the module's file path to spawn the child process, which breaks the import system.To see the failures, checkout the previous commit in this branch & run the tests.
Through some guesswork, these commits attempt to reproduce the original command, so that doesn't happen.
I'm unsure this is the best way to get the original command.
Is there a better way?