From 59b5906908987c4b6c110521412b2ea8817e339b Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 2 Jul 2021 09:08:25 -0400 Subject: [PATCH 01/14] Refactor module/script handling to share an interface (check method). --- Lib/pdb.py | 81 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 51 insertions(+), 30 deletions(-) diff --git a/Lib/pdb.py b/Lib/pdb.py index ff40f7b2476a38..84aec6f0d4ced0 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -1534,11 +1534,16 @@ def lookupmodule(self, filename): return fullname return None - def _runmodule(self, module_name): + def _run(self, target): + if isinstance(target, ModuleTarget): + return self._runmodule(target) + elif isinstance(target, ScriptTarget): + return self._runscript(target) + + def _runmodule(self, target: 'ModuleTarget'): self._wait_for_mainpyfile = True self._user_requested_quit = False - import runpy - mod_name, mod_spec, code = runpy._get_module_details(module_name) + mod_name, mod_spec, code = target.module_details self.mainpyfile = self.canonic(code.co_filename) import __main__ __main__.__dict__.clear() @@ -1552,7 +1557,7 @@ def _runmodule(self, module_name): }) self.run(code) - def _runscript(self, filename): + def _runscript(self, filename: 'ScriptTarget'): # The script has to run in __main__ namespace (or imports from # __main__ will break). # @@ -1665,6 +1670,34 @@ def help(): To let the script run up to a given line X in the debugged file, use "-c 'until X'".""" + +class ScriptTarget(str): + def __new__(cls, val): + res = super().__new__(cls, os.path.realpath(val)) + res.orig = val + return res + + def check(self): + if not os.path.exists(self): + print('Error:', self.orig, 'does not exist') + sys.exit(1) + + # Replace pdb's dir with script's dir in front of module search path. + sys.path[0] = os.path.dirname(self) + + +class ModuleTarget(str): + def check(self): + pass + + @property + def module_details(self): + if not hasattr(self, '_details'): + import runpy + self._details = runpy._get_module_details(self) + return self._details + + def main(): import getopt @@ -1674,28 +1707,19 @@ def main(): print(_usage) sys.exit(2) - commands = [] - run_as_module = False - for opt, optarg in opts: - if opt in ['-h', '--help']: - print(_usage) - sys.exit() - elif opt in ['-c', '--command']: - commands.append(optarg) - elif opt in ['-m']: - run_as_module = True - - mainpyfile = args[0] # Get script filename - if not run_as_module and not os.path.exists(mainpyfile): - print('Error:', mainpyfile, 'does not exist') - sys.exit(1) + if any(opt in ['-h', '--help'] for opt, optarg in opts): + print(_usage) + sys.exit() - sys.argv[:] = args # Hide "pdb.py" and pdb options from argument list + commands = [optarg for opt, optarg in opts if opt in ['-c', '--command']] - if not run_as_module: - mainpyfile = os.path.realpath(mainpyfile) - # Replace pdb's dir with script's dir in front of module search path. - sys.path[0] = os.path.dirname(mainpyfile) + module_indicated = any(opt in ['-m'] for opt, optarg in opts) + cls = ModuleTarget if module_indicated else ScriptTarget + target = cls(args[0]) + + target.check() + + sys.argv[:] = args # Hide "pdb.py" and pdb options from argument list # Note on saving/restoring sys.argv: it's a good idea when sys.argv was # modified by the script being debugged. It's a bad idea when it was @@ -1705,15 +1729,12 @@ def main(): pdb.rcLines.extend(commands) while True: try: - if run_as_module: - pdb._runmodule(mainpyfile) - else: - pdb._runscript(mainpyfile) + pdb._run(target) if pdb._user_requested_quit: break print("The program finished and will be restarted") except Restart: - print("Restarting", mainpyfile, "with arguments:") + print("Restarting", target, "with arguments:") print("\t" + " ".join(sys.argv[1:])) except SystemExit: # In most cases SystemExit does not warrant a post-mortem session. @@ -1728,7 +1749,7 @@ def main(): print("Running 'cont' or 'step' will restart the program") t = sys.exc_info()[2] pdb.interaction(None, t) - print("Post mortem debugger finished. The " + mainpyfile + + print("Post mortem debugger finished. The " + target + " will be restarted") From 4df76113e6ec20b5eed92d2a8d2482568e22bb41 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 2 Jul 2021 09:20:23 -0400 Subject: [PATCH 02/14] Import functools and adjust tests for the new line number for find_function. --- Lib/pdb.py | 1 + Lib/test/test_pdb.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/pdb.py b/Lib/pdb.py index 84aec6f0d4ced0..4bc1104c07f1e4 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -80,6 +80,7 @@ import signal import inspect import tokenize +import functools import traceback import linecache diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 0724b666a3bf8d..eb329b580bbbd8 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -362,7 +362,7 @@ def test_pdb_breakpoints_preserved_across_interactive_sessions(): 1 breakpoint keep yes at ...test_pdb.py:... 2 breakpoint keep yes at ...test_pdb.py:... (Pdb) break pdb.find_function - Breakpoint 3 at ...pdb.py:94 + Breakpoint 3 at ...pdb.py:95 (Pdb) break Num Type Disp Enb Where 1 breakpoint keep yes at ...test_pdb.py:... From 86fbc39159a5793a763c8b1a45cebac4636027d9 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 2 Jul 2021 09:21:50 -0400 Subject: [PATCH 03/14] Use cached_property for details. --- Lib/pdb.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/Lib/pdb.py b/Lib/pdb.py index 4bc1104c07f1e4..f72c688b7d0666 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -1544,7 +1544,7 @@ def _run(self, target): def _runmodule(self, target: 'ModuleTarget'): self._wait_for_mainpyfile = True self._user_requested_quit = False - mod_name, mod_spec, code = target.module_details + mod_name, mod_spec, code = target.details self.mainpyfile = self.canonic(code.co_filename) import __main__ __main__.__dict__.clear() @@ -1691,12 +1691,10 @@ class ModuleTarget(str): def check(self): pass - @property - def module_details(self): - if not hasattr(self, '_details'): - import runpy - self._details = runpy._get_module_details(self) - return self._details + @functools.cached_property + def details(self): + import runpy + return runpy._get_module_details(self) def main(): From 825e35be4a552b5682d1d3c287dd5164da4c2008 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 2 Jul 2021 09:23:43 -0400 Subject: [PATCH 04/14] Add blurb. --- .../next/Library/2021-07-02-09-23-35.bpo-44461.W42G0V.rst | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2021-07-02-09-23-35.bpo-44461.W42G0V.rst diff --git a/Misc/NEWS.d/next/Library/2021-07-02-09-23-35.bpo-44461.W42G0V.rst b/Misc/NEWS.d/next/Library/2021-07-02-09-23-35.bpo-44461.W42G0V.rst new file mode 100644 index 00000000000000..b3b5f898527772 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-07-02-09-23-35.bpo-44461.W42G0V.rst @@ -0,0 +1,4 @@ +In Pdb, refactor handling of module and script targets, now modeling each +with a class encapsulating the behavior for each and opening up the +opportunity to "check" for the viability of a module target prior to +attempting to run it. From 914ec41085bd3be37d57f14f79e78d332df9edcd Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 2 Jul 2021 09:30:51 -0400 Subject: [PATCH 05/14] Add comments about 'orig' --- Lib/pdb.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Lib/pdb.py b/Lib/pdb.py index f72c688b7d0666..8c56d11f27fff8 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -1674,8 +1674,12 @@ def help(): class ScriptTarget(str): def __new__(cls, val): + # Mutate self to be the "real path". res = super().__new__(cls, os.path.realpath(val)) + + # Store the original path for error reporting. res.orig = val + return res def check(self): From b959e3ec7499ae51a97f91ea457d499cc7a4fb15 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 2 Jul 2021 09:42:05 -0400 Subject: [PATCH 06/14] Rely on singledispatch to dispatch the target. --- Lib/pdb.py | 70 +++++++++++++++++++++++++----------------------------- 1 file changed, 33 insertions(+), 37 deletions(-) diff --git a/Lib/pdb.py b/Lib/pdb.py index 8c56d11f27fff8..b1f0c7fab08154 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -129,6 +129,35 @@ def __repr__(self): return self +class ScriptTarget(str): + def __new__(cls, val): + # Mutate self to be the "real path". + res = super().__new__(cls, os.path.realpath(val)) + + # Store the original path for error reporting. + res.orig = val + + return res + + def check(self): + if not os.path.exists(self): + print('Error:', self.orig, 'does not exist') + sys.exit(1) + + # Replace pdb's dir with script's dir in front of module search path. + sys.path[0] = os.path.dirname(self) + + +class ModuleTarget(str): + def check(self): + pass + + @functools.cached_property + def details(self): + import runpy + return runpy._get_module_details(self) + + # Interaction prompt line will separate file and call info from code # text using value of line_prefix string. A newline and arrow may # be to your liking. You can set it once pdb is imported using the @@ -1535,13 +1564,8 @@ def lookupmodule(self, filename): return fullname return None - def _run(self, target): - if isinstance(target, ModuleTarget): - return self._runmodule(target) - elif isinstance(target, ScriptTarget): - return self._runscript(target) - - def _runmodule(self, target: 'ModuleTarget'): + @functools.singledispatchmethod + def _run(self, target: 'ModuleTarget'): self._wait_for_mainpyfile = True self._user_requested_quit = False mod_name, mod_spec, code = target.details @@ -1558,7 +1582,8 @@ def _runmodule(self, target: 'ModuleTarget'): }) self.run(code) - def _runscript(self, filename: 'ScriptTarget'): + @_run.register + def _(self, filename: 'ScriptTarget'): # The script has to run in __main__ namespace (or imports from # __main__ will break). # @@ -1672,35 +1697,6 @@ def help(): "-c 'until X'".""" -class ScriptTarget(str): - def __new__(cls, val): - # Mutate self to be the "real path". - res = super().__new__(cls, os.path.realpath(val)) - - # Store the original path for error reporting. - res.orig = val - - return res - - def check(self): - if not os.path.exists(self): - print('Error:', self.orig, 'does not exist') - sys.exit(1) - - # Replace pdb's dir with script's dir in front of module search path. - sys.path[0] = os.path.dirname(self) - - -class ModuleTarget(str): - def check(self): - pass - - @functools.cached_property - def details(self): - import runpy - return runpy._get_module_details(self) - - def main(): import getopt From 4fdd84edb45da3b9a01a8b79b9aaf957b6c029fa Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 2 Jul 2021 10:11:02 -0400 Subject: [PATCH 07/14] Ignore ModuleTarget in test_pyclbr. It trips up on the cached property. --- Lib/test/test_pyclbr.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_pyclbr.py b/Lib/test/test_pyclbr.py index 82c1ebb5b070fa..ac6d2fdcc58c04 100644 --- a/Lib/test/test_pyclbr.py +++ b/Lib/test/test_pyclbr.py @@ -222,7 +222,10 @@ def test_others(self): cm('pickle', ignore=('partial', 'PickleBuffer')) cm('aifc', ignore=('_aifc_params',)) # set with = in module cm('sre_parse', ignore=('dump', 'groups', 'pos')) # from sre_constants import *; property - cm('pdb') + cm( + 'pdb', + ignore=('ModuleTarget',), # cached_property + ) cm('pydoc', ignore=('input', 'output',)) # properties # Tests for modules inside packages From 9dfbe0c112ace3074bef295e05f20123453d911f Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 2 Jul 2021 10:12:32 -0400 Subject: [PATCH 08/14] Update blurb to reflect more than a refactor. --- .../next/Library/2021-07-02-09-23-35.bpo-44461.W42G0V.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2021-07-02-09-23-35.bpo-44461.W42G0V.rst b/Misc/NEWS.d/next/Library/2021-07-02-09-23-35.bpo-44461.W42G0V.rst index b3b5f898527772..95dc6e3dd36f11 100644 --- a/Misc/NEWS.d/next/Library/2021-07-02-09-23-35.bpo-44461.W42G0V.rst +++ b/Misc/NEWS.d/next/Library/2021-07-02-09-23-35.bpo-44461.W42G0V.rst @@ -1,4 +1,5 @@ In Pdb, refactor handling of module and script targets, now modeling each with a class encapsulating the behavior for each and opening up the opportunity to "check" for the viability of a module target prior to -attempting to run it. +attempting to run it. Subtly tweak the internal logic to accommodate the +refactor. From 415ea295580b317aaf899475226c0725c4b3081e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 2 Jul 2021 16:49:14 -0400 Subject: [PATCH 09/14] Rename news to reference bpo-44554. --- .../next/Library/2021-07-02-09-23-35.bpo-44554.W42G0V.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/{NEWS.d/next/Library/2021-07-02-09-23-35.bpo-44461.W42G0V.rst => News.d/next/Library/2021-07-02-09-23-35.bpo-44554.W42G0V.rst} (100%) diff --git a/Misc/NEWS.d/next/Library/2021-07-02-09-23-35.bpo-44461.W42G0V.rst b/Misc/News.d/next/Library/2021-07-02-09-23-35.bpo-44554.W42G0V.rst similarity index 100% rename from Misc/NEWS.d/next/Library/2021-07-02-09-23-35.bpo-44461.W42G0V.rst rename to Misc/News.d/next/Library/2021-07-02-09-23-35.bpo-44554.W42G0V.rst From 0d0bd5c05a4e9fea21472f59085f20aa49c5bd5e Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Fri, 2 Jul 2021 18:19:05 -0400 Subject: [PATCH 10/14] Add blurb --- .../next/Library/2021-07-02-18-17-56.bpo-44554.aBUmJo.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Misc/{News.d/next/Library/2021-07-02-09-23-35.bpo-44554.W42G0V.rst => NEWS.d/next/Library/2021-07-02-18-17-56.bpo-44554.aBUmJo.rst} (100%) diff --git a/Misc/News.d/next/Library/2021-07-02-09-23-35.bpo-44554.W42G0V.rst b/Misc/NEWS.d/next/Library/2021-07-02-18-17-56.bpo-44554.aBUmJo.rst similarity index 100% rename from Misc/News.d/next/Library/2021-07-02-09-23-35.bpo-44554.W42G0V.rst rename to Misc/NEWS.d/next/Library/2021-07-02-18-17-56.bpo-44554.aBUmJo.rst From e21f085196bc6ba05ee4e7758232568ae2172961 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 18 Jul 2021 08:42:30 -0400 Subject: [PATCH 11/14] Simplify changelog per suggestion. --- .../next/Library/2021-07-02-18-17-56.bpo-44554.aBUmJo.rst | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Misc/NEWS.d/next/Library/2021-07-02-18-17-56.bpo-44554.aBUmJo.rst b/Misc/NEWS.d/next/Library/2021-07-02-18-17-56.bpo-44554.aBUmJo.rst index 95dc6e3dd36f11..6ca8cdc22fa6e8 100644 --- a/Misc/NEWS.d/next/Library/2021-07-02-18-17-56.bpo-44554.aBUmJo.rst +++ b/Misc/NEWS.d/next/Library/2021-07-02-18-17-56.bpo-44554.aBUmJo.rst @@ -1,5 +1 @@ -In Pdb, refactor handling of module and script targets, now modeling each -with a class encapsulating the behavior for each and opening up the -opportunity to "check" for the viability of a module target prior to -attempting to run it. Subtly tweak the internal logic to accommodate the -refactor. +Refactor argument processing in :func:pdb.main to simplify detection of errors in input loading and clarify behavior around module or script invocation. From fe8aea5ae98c57cb98573b3dd0a04cb4fc3d12b0 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 18 Jul 2021 09:26:21 -0400 Subject: [PATCH 12/14] Replace singledispatch _run with a unified implementation selecting on properties of the Target object. --- Lib/pdb.py | 98 ++++++++++++++++++++++++++------------------ Lib/test/test_pdb.py | 2 +- 2 files changed, 60 insertions(+), 40 deletions(-) diff --git a/Lib/pdb.py b/Lib/pdb.py index b1f0c7fab08154..8cba4d4e480fab 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -84,6 +84,8 @@ import traceback import linecache +from typing import Union + class Restart(Exception): """Causes a debugger to be restarted for the debugged python program.""" @@ -147,6 +149,23 @@ def check(self): # Replace pdb's dir with script's dir in front of module search path. sys.path[0] = os.path.dirname(self) + @property + def filename(self): + return self + + @property + def namespace(self): + return dict( + __name__='__main__', + __file__=self, + __builtins__=__builtins__, + ) + + @property + def code(self): + with io.open(self) as fp: + return f"exec(compile({fp.read()!r}, {self!r}, 'exec'))" + class ModuleTarget(str): def check(self): @@ -157,6 +176,31 @@ def details(self): import runpy return runpy._get_module_details(self) + @property + def filename(self): + return self.code.co_filename + + @property + def code(self): + name, spec, code = self.details + return code + + @property + def spec(self): + name, spec, code = self.details + return spec + + @property + def namespace(self): + return dict( + __name__='__main__', + __file__=os.path.normcase(os.path.abspath(self.filename)), + __package__=self.spec.parent, + __loader__=self.spec.loader, + __spec__=self.spec, + __builtins__=__builtins__, + ) + # Interaction prompt line will separate file and call info from code # text using value of line_prefix string. A newline and arrow may @@ -1564,50 +1608,26 @@ def lookupmodule(self, filename): return fullname return None - @functools.singledispatchmethod - def _run(self, target: 'ModuleTarget'): + def _run(self, target: Union[ModuleTarget, ScriptTarget]): + # When bdb sets tracing, a number of call and line events happen + # BEFORE debugger even reaches user's code (and the exact sequence of + # events depends on python version). Take special measures to + # avoid stopping before reaching the main script (see user_line and + # user_call for details). self._wait_for_mainpyfile = True self._user_requested_quit = False - mod_name, mod_spec, code = target.details - self.mainpyfile = self.canonic(code.co_filename) - import __main__ - __main__.__dict__.clear() - __main__.__dict__.update({ - "__name__": "__main__", - "__file__": self.mainpyfile, - "__package__": mod_spec.parent, - "__loader__": mod_spec.loader, - "__spec__": mod_spec, - "__builtins__": __builtins__, - }) - self.run(code) - - @_run.register - def _(self, filename: 'ScriptTarget'): - # The script has to run in __main__ namespace (or imports from - # __main__ will break). - # - # So we clear up the __main__ and set several special variables - # (this gets rid of pdb's globals and cleans old variables on restarts). + + self.mainpyfile = self.canonic(target.filename) + + # The target has to run in __main__ namespace (or imports from + # __main__ will break). Clear __main__ and replace with + # the target namespace. import __main__ __main__.__dict__.clear() - __main__.__dict__.update({"__name__" : "__main__", - "__file__" : filename, - "__builtins__": __builtins__, - }) + __main__.__dict__.update(target.namespace) + + self.run(target.code) - # When bdb sets tracing, a number of call and line events happens - # BEFORE debugger even reaches user's code (and the exact sequence of - # events depends on python version). So we take special measures to - # avoid stopping before we reach the main script (see user_line and - # user_call for details). - self._wait_for_mainpyfile = True - self.mainpyfile = self.canonic(filename) - self._user_requested_quit = False - with io.open_code(filename) as fp: - statement = "exec(compile(%r, %r, 'exec'))" % \ - (fp.read(), self.mainpyfile) - self.run(statement) # Collect all command help into docstring, if not run with -OO diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index eb329b580bbbd8..08becd76e601d7 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -362,7 +362,7 @@ def test_pdb_breakpoints_preserved_across_interactive_sessions(): 1 breakpoint keep yes at ...test_pdb.py:... 2 breakpoint keep yes at ...test_pdb.py:... (Pdb) break pdb.find_function - Breakpoint 3 at ...pdb.py:95 + Breakpoint 3 at ...pdb.py:97 (Pdb) break Num Type Disp Enb Where 1 breakpoint keep yes at ...test_pdb.py:... From 0d8611c2bd155870255500f7985e860b54cba535 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 18 Jul 2021 10:11:55 -0400 Subject: [PATCH 13/14] Suppress spurious errors in pyclbr when it encounters properties and Union in the module. --- Lib/test/test_pyclbr.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_pyclbr.py b/Lib/test/test_pyclbr.py index ac6d2fdcc58c04..4bb9cfcad9a76a 100644 --- a/Lib/test/test_pyclbr.py +++ b/Lib/test/test_pyclbr.py @@ -224,7 +224,8 @@ def test_others(self): cm('sre_parse', ignore=('dump', 'groups', 'pos')) # from sre_constants import *; property cm( 'pdb', - ignore=('ModuleTarget',), # cached_property + # pyclbr does not handle elegantly `typing` or properties + ignore=('Union', 'ModuleTarget', 'ScriptTarget'), ) cm('pydoc', ignore=('input', 'output',)) # properties From c91ba806e4984ef998761d7c57275e584df0e609 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Sun, 18 Jul 2021 10:30:33 -0400 Subject: [PATCH 14/14] Make _spec and _details private. --- Lib/pdb.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Lib/pdb.py b/Lib/pdb.py index 8cba4d4e480fab..c37de8be6fe256 100755 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -172,7 +172,7 @@ def check(self): pass @functools.cached_property - def details(self): + def _details(self): import runpy return runpy._get_module_details(self) @@ -182,12 +182,12 @@ def filename(self): @property def code(self): - name, spec, code = self.details + name, spec, code = self._details return code @property - def spec(self): - name, spec, code = self.details + def _spec(self): + name, spec, code = self._details return spec @property @@ -195,9 +195,9 @@ def namespace(self): return dict( __name__='__main__', __file__=os.path.normcase(os.path.abspath(self.filename)), - __package__=self.spec.parent, - __loader__=self.spec.loader, - __spec__=self.spec, + __package__=self._spec.parent, + __loader__=self._spec.loader, + __spec__=self._spec, __builtins__=__builtins__, )