Update importlib from v3.14.2

This commit is contained in:
CPython Developers authored and Jeong, YunWon committed 2026-02-05 00:21:34 +09:00
1 parent 648223ade2
commit afdf8cefe8
70 files changed
+2850 -939

No files matched your search

+16 -6
View File
@@ -382,6 +382,9 @@ class _ModuleLock:
self.waiters.pop()
self.wakeup.release()
def locked(self):
return bool(self.count)
def __repr__(self):
return f'_ModuleLock({self.name!r}) at {id(self)}'
@@ -490,8 +493,7 @@ def _call_with_frames_removed(f, *args, **kwds):
def _verbose_message(message, *args, verbosity=1):
"""Print the message to stderr if -v/PYTHONVERBOSE is turned on."""
# XXX RUSTPYTHON: hasattr check because we might be bootstrapping and we wouldn't have stderr yet
if sys.flags.verbose >= verbosity and hasattr(sys, "stderr"):
if sys.flags.verbose >= verbosity:
if not message.startswith(('#', 'import ')):
message = '# ' + message
print(message.format(*args), file=sys.stderr)
@@ -1242,10 +1244,12 @@ def _find_spec(name, path, target=None):
"""Find a module's spec."""
meta_path = sys.meta_path
if meta_path is None:
# PyImport_Cleanup() is running or has been called.
raise ImportError("sys.meta_path is None, Python is likely "
"shutting down")
# gh-130094: Copy sys.meta_path so that we have a consistent view of the
# list while iterating over it.
meta_path = list(meta_path)
if not meta_path:
_warnings.warn('sys.meta_path is empty', ImportWarning)
@@ -1300,7 +1304,6 @@ def _sanity_check(name, package, level):
_ERR_MSG_PREFIX = 'No module named '
_ERR_MSG = _ERR_MSG_PREFIX + '{!r}'
def _find_and_load_unlocked(name, import_):
path = None
@@ -1310,8 +1313,9 @@ def _find_and_load_unlocked(name, import_):
if parent not in sys.modules:
_call_with_frames_removed(import_, parent)
# Crazy side-effects!
if name in sys.modules:
return sys.modules[name]
module = sys.modules.get(name)
if module is not None:
return module
parent_module = sys.modules[parent]
try:
path = parent_module.__path__
@@ -1319,6 +1323,12 @@ def _find_and_load_unlocked(name, import_):
msg = f'{_ERR_MSG_PREFIX}{name!r}; {parent!r} is not a package'
raise ModuleNotFoundError(msg, name=name) from None
parent_spec = parent_module.__spec__
if getattr(parent_spec, '_initializing', False):
_call_with_frames_removed(import_, parent)
# Crazy side-effects (again)!
module = sys.modules.get(name)
if module is not None:
return module
child = name.rpartition('.')[2]
spec = _find_spec(name, path)
if spec is None:
+10 -274
View File
@@ -221,277 +221,7 @@ def _write_atomic(path, data, mode=0o666):
_code_type = type(_write_atomic.__code__)
# Finder/loader utility code ###############################################
# Magic word to reject .pyc files generated by other Python versions.
# It should change for each incompatible change to the bytecode.
#
# The value of CR and LF is incorporated so if you ever read or write
# a .pyc file in text mode the magic number will be wrong; also, the
# Apple MPW compiler swaps their values, botching string constants.
#
# There were a variety of old schemes for setting the magic number.
# The current working scheme is to increment the previous value by
# 10.
#
# Starting with the adoption of PEP 3147 in Python 3.2, every bump in magic
# number also includes a new "magic tag", i.e. a human readable string used
# to represent the magic number in __pycache__ directories. When you change
# the magic number, you must also set a new unique magic tag. Generally this
# can be named after the Python major version of the magic number bump, but
# it can really be anything, as long as it's different than anything else
# that's come before. The tags are included in the following table, starting
# with Python 3.2a0.
#
# Known values:
# Python 1.5: 20121
# Python 1.5.1: 20121
# Python 1.5.2: 20121
# Python 1.6: 50428
# Python 2.0: 50823
# Python 2.0.1: 50823
# Python 2.1: 60202
# Python 2.1.1: 60202
# Python 2.1.2: 60202
# Python 2.2: 60717
# Python 2.3a0: 62011
# Python 2.3a0: 62021
# Python 2.3a0: 62011 (!)
# Python 2.4a0: 62041
# Python 2.4a3: 62051
# Python 2.4b1: 62061
# Python 2.5a0: 62071
# Python 2.5a0: 62081 (ast-branch)
# Python 2.5a0: 62091 (with)
# Python 2.5a0: 62092 (changed WITH_CLEANUP opcode)
# Python 2.5b3: 62101 (fix wrong code: for x, in ...)
# Python 2.5b3: 62111 (fix wrong code: x += yield)
# Python 2.5c1: 62121 (fix wrong lnotab with for loops and
# storing constants that should have been removed)
# Python 2.5c2: 62131 (fix wrong code: for x, in ... in listcomp/genexp)
# Python 2.6a0: 62151 (peephole optimizations and STORE_MAP opcode)
# Python 2.6a1: 62161 (WITH_CLEANUP optimization)
# Python 2.7a0: 62171 (optimize list comprehensions/change LIST_APPEND)
# Python 2.7a0: 62181 (optimize conditional branches:
# introduce POP_JUMP_IF_FALSE and POP_JUMP_IF_TRUE)
# Python 2.7a0 62191 (introduce SETUP_WITH)
# Python 2.7a0 62201 (introduce BUILD_SET)
# Python 2.7a0 62211 (introduce MAP_ADD and SET_ADD)
# Python 3000: 3000
# 3010 (removed UNARY_CONVERT)
# 3020 (added BUILD_SET)
# 3030 (added keyword-only parameters)
# 3040 (added signature annotations)
# 3050 (print becomes a function)
# 3060 (PEP 3115 metaclass syntax)
# 3061 (string literals become unicode)
# 3071 (PEP 3109 raise changes)
# 3081 (PEP 3137 make __file__ and __name__ unicode)
# 3091 (kill str8 interning)
# 3101 (merge from 2.6a0, see 62151)
# 3103 (__file__ points to source file)
# Python 3.0a4: 3111 (WITH_CLEANUP optimization).
# Python 3.0b1: 3131 (lexical exception stacking, including POP_EXCEPT
#3021)
# Python 3.1a1: 3141 (optimize list, set and dict comprehensions:
# change LIST_APPEND and SET_ADD, add MAP_ADD #2183)
# Python 3.1a1: 3151 (optimize conditional branches:
# introduce POP_JUMP_IF_FALSE and POP_JUMP_IF_TRUE
#4715)
# Python 3.2a1: 3160 (add SETUP_WITH #6101)
# tag: cpython-32
# Python 3.2a2: 3170 (add DUP_TOP_TWO, remove DUP_TOPX and ROT_FOUR #9225)
# tag: cpython-32
# Python 3.2a3 3180 (add DELETE_DEREF #4617)
# Python 3.3a1 3190 (__class__ super closure changed)
# Python 3.3a1 3200 (PEP 3155 __qualname__ added #13448)
# Python 3.3a1 3210 (added size modulo 2**32 to the pyc header #13645)
# Python 3.3a2 3220 (changed PEP 380 implementation #14230)
# Python 3.3a4 3230 (revert changes to implicit __class__ closure #14857)
# Python 3.4a1 3250 (evaluate positional default arguments before
# keyword-only defaults #16967)
# Python 3.4a1 3260 (add LOAD_CLASSDEREF; allow locals of class to override
# free vars #17853)
# Python 3.4a1 3270 (various tweaks to the __class__ closure #12370)
# Python 3.4a1 3280 (remove implicit class argument)
# Python 3.4a4 3290 (changes to __qualname__ computation #19301)
# Python 3.4a4 3300 (more changes to __qualname__ computation #19301)
# Python 3.4rc2 3310 (alter __qualname__ computation #20625)
# Python 3.5a1 3320 (PEP 465: Matrix multiplication operator #21176)
# Python 3.5b1 3330 (PEP 448: Additional Unpacking Generalizations #2292)
# Python 3.5b2 3340 (fix dictionary display evaluation order #11205)
# Python 3.5b3 3350 (add GET_YIELD_FROM_ITER opcode #24400)
# Python 3.5.2 3351 (fix BUILD_MAP_UNPACK_WITH_CALL opcode #27286)
# Python 3.6a0 3360 (add FORMAT_VALUE opcode #25483)
# Python 3.6a1 3361 (lineno delta of code.co_lnotab becomes signed #26107)
# Python 3.6a2 3370 (16 bit wordcode #26647)
# Python 3.6a2 3371 (add BUILD_CONST_KEY_MAP opcode #27140)
# Python 3.6a2 3372 (MAKE_FUNCTION simplification, remove MAKE_CLOSURE
# #27095)
# Python 3.6b1 3373 (add BUILD_STRING opcode #27078)
# Python 3.6b1 3375 (add SETUP_ANNOTATIONS and STORE_ANNOTATION opcodes
# #27985)
# Python 3.6b1 3376 (simplify CALL_FUNCTIONs & BUILD_MAP_UNPACK_WITH_CALL
#27213)
# Python 3.6b1 3377 (set __class__ cell from type.__new__ #23722)
# Python 3.6b2 3378 (add BUILD_TUPLE_UNPACK_WITH_CALL #28257)
# Python 3.6rc1 3379 (more thorough __class__ validation #23722)
# Python 3.7a1 3390 (add LOAD_METHOD and CALL_METHOD opcodes #26110)
# Python 3.7a2 3391 (update GET_AITER #31709)
# Python 3.7a4 3392 (PEP 552: Deterministic pycs #31650)
# Python 3.7b1 3393 (remove STORE_ANNOTATION opcode #32550)
# Python 3.7b5 3394 (restored docstring as the first stmt in the body;
# this might affected the first line number #32911)
# Python 3.8a1 3400 (move frame block handling to compiler #17611)
# Python 3.8a1 3401 (add END_ASYNC_FOR #33041)
# Python 3.8a1 3410 (PEP570 Python Positional-Only Parameters #36540)
# Python 3.8b2 3411 (Reverse evaluation order of key: value in dict
# comprehensions #35224)
# Python 3.8b2 3412 (Swap the position of positional args and positional
# only args in ast.arguments #37593)
# Python 3.8b4 3413 (Fix "break" and "continue" in "finally" #37830)
# Python 3.9a0 3420 (add LOAD_ASSERTION_ERROR #34880)
# Python 3.9a0 3421 (simplified bytecode for with blocks #32949)
# Python 3.9a0 3422 (remove BEGIN_FINALLY, END_FINALLY, CALL_FINALLY, POP_FINALLY bytecodes #33387)
# Python 3.9a2 3423 (add IS_OP, CONTAINS_OP and JUMP_IF_NOT_EXC_MATCH bytecodes #39156)
# Python 3.9a2 3424 (simplify bytecodes for *value unpacking)
# Python 3.9a2 3425 (simplify bytecodes for **value unpacking)
# Python 3.10a1 3430 (Make 'annotations' future by default)
# Python 3.10a1 3431 (New line number table format -- PEP 626)
# Python 3.10a2 3432 (Function annotation for MAKE_FUNCTION is changed from dict to tuple bpo-42202)
# Python 3.10a2 3433 (RERAISE restores f_lasti if oparg != 0)
# Python 3.10a6 3434 (PEP 634: Structural Pattern Matching)
# Python 3.10a7 3435 Use instruction offsets (as opposed to byte offsets).
# Python 3.10b1 3436 (Add GEN_START bytecode #43683)
# Python 3.10b1 3437 (Undo making 'annotations' future by default - We like to dance among core devs!)
# Python 3.10b1 3438 Safer line number table handling.
# Python 3.10b1 3439 (Add ROT_N)
# Python 3.11a1 3450 Use exception table for unwinding ("zero cost" exception handling)
# Python 3.11a1 3451 (Add CALL_METHOD_KW)
# Python 3.11a1 3452 (drop nlocals from marshaled code objects)
# Python 3.11a1 3453 (add co_fastlocalnames and co_fastlocalkinds)
# Python 3.11a1 3454 (compute cell offsets relative to locals bpo-43693)
# Python 3.11a1 3455 (add MAKE_CELL bpo-43693)
# Python 3.11a1 3456 (interleave cell args bpo-43693)
# Python 3.11a1 3457 (Change localsplus to a bytes object bpo-43693)
# Python 3.11a1 3458 (imported objects now don't use LOAD_METHOD/CALL_METHOD)
# Python 3.11a1 3459 (PEP 657: add end line numbers and column offsets for instructions)
# Python 3.11a1 3460 (Add co_qualname field to PyCodeObject bpo-44530)
# Python 3.11a1 3461 (JUMP_ABSOLUTE must jump backwards)
# Python 3.11a2 3462 (bpo-44511: remove COPY_DICT_WITHOUT_KEYS, change
# MATCH_CLASS and MATCH_KEYS, and add COPY)
# Python 3.11a3 3463 (bpo-45711: JUMP_IF_NOT_EXC_MATCH no longer pops the
# active exception)
# Python 3.11a3 3464 (bpo-45636: Merge numeric BINARY_*/INPLACE_* into
# BINARY_OP)
# Python 3.11a3 3465 (Add COPY_FREE_VARS opcode)
# Python 3.11a4 3466 (bpo-45292: PEP-654 except*)
# Python 3.11a4 3467 (Change CALL_xxx opcodes)
# Python 3.11a4 3468 (Add SEND opcode)
# Python 3.11a4 3469 (bpo-45711: remove type, traceback from exc_info)
# Python 3.11a4 3470 (bpo-46221: PREP_RERAISE_STAR no longer pushes lasti)
# Python 3.11a4 3471 (bpo-46202: remove pop POP_EXCEPT_AND_RERAISE)
# Python 3.11a4 3472 (bpo-46009: replace GEN_START with POP_TOP)
# Python 3.11a4 3473 (Add POP_JUMP_IF_NOT_NONE/POP_JUMP_IF_NONE opcodes)
# Python 3.11a4 3474 (Add RESUME opcode)
# Python 3.11a5 3475 (Add RETURN_GENERATOR opcode)
# Python 3.11a5 3476 (Add ASYNC_GEN_WRAP opcode)
# Python 3.11a5 3477 (Replace DUP_TOP/DUP_TOP_TWO with COPY and
# ROT_TWO/ROT_THREE/ROT_FOUR/ROT_N with SWAP)
# Python 3.11a5 3478 (New CALL opcodes)
# Python 3.11a5 3479 (Add PUSH_NULL opcode)
# Python 3.11a5 3480 (New CALL opcodes, second iteration)
# Python 3.11a5 3481 (Use inline cache for BINARY_OP)
# Python 3.11a5 3482 (Use inline caching for UNPACK_SEQUENCE and LOAD_GLOBAL)
# Python 3.11a5 3483 (Use inline caching for COMPARE_OP and BINARY_SUBSCR)
# Python 3.11a5 3484 (Use inline caching for LOAD_ATTR, LOAD_METHOD, and
# STORE_ATTR)
# Python 3.11a5 3485 (Add an oparg to GET_AWAITABLE)
# Python 3.11a6 3486 (Use inline caching for PRECALL and CALL)
# Python 3.11a6 3487 (Remove the adaptive "oparg counter" mechanism)
# Python 3.11a6 3488 (LOAD_GLOBAL can push additional NULL)
# Python 3.11a6 3489 (Add JUMP_BACKWARD, remove JUMP_ABSOLUTE)
# Python 3.11a6 3490 (remove JUMP_IF_NOT_EXC_MATCH, add CHECK_EXC_MATCH)
# Python 3.11a6 3491 (remove JUMP_IF_NOT_EG_MATCH, add CHECK_EG_MATCH,
# add JUMP_BACKWARD_NO_INTERRUPT, make JUMP_NO_INTERRUPT virtual)
# Python 3.11a7 3492 (make POP_JUMP_IF_NONE/NOT_NONE/TRUE/FALSE relative)
# Python 3.11a7 3493 (Make JUMP_IF_TRUE_OR_POP/JUMP_IF_FALSE_OR_POP relative)
# Python 3.11a7 3494 (New location info table)
# Python 3.11b4 3495 (Set line number of module's RESUME instr to 0 per PEP 626)
# Python 3.12a1 3500 (Remove PRECALL opcode)
# Python 3.12a1 3501 (YIELD_VALUE oparg == stack_depth)
# Python 3.12a1 3502 (LOAD_FAST_CHECK, no NULL-check in LOAD_FAST)
# Python 3.12a1 3503 (Shrink LOAD_METHOD cache)
# Python 3.12a1 3504 (Merge LOAD_METHOD back into LOAD_ATTR)
# Python 3.12a1 3505 (Specialization/Cache for FOR_ITER)
# Python 3.12a1 3506 (Add BINARY_SLICE and STORE_SLICE instructions)
# Python 3.12a1 3507 (Set lineno of module's RESUME to 0)
# Python 3.12a1 3508 (Add CLEANUP_THROW)
# Python 3.12a1 3509 (Conditional jumps only jump forward)
# Python 3.12a2 3510 (FOR_ITER leaves iterator on the stack)
# Python 3.12a2 3511 (Add STOPITERATION_ERROR instruction)
# Python 3.12a2 3512 (Remove all unused consts from code objects)
# Python 3.12a4 3513 (Add CALL_INTRINSIC_1 instruction, removed STOPITERATION_ERROR, PRINT_EXPR, IMPORT_STAR)
# Python 3.12a4 3514 (Remove ASYNC_GEN_WRAP, LIST_TO_TUPLE, and UNARY_POSITIVE)
# Python 3.12a5 3515 (Embed jump mask in COMPARE_OP oparg)
# Python 3.12a5 3516 (Add COMPARE_AND_BRANCH instruction)
# Python 3.12a5 3517 (Change YIELD_VALUE oparg to exception block depth)
# Python 3.12a6 3518 (Add RETURN_CONST instruction)
# Python 3.12a6 3519 (Modify SEND instruction)
# Python 3.12a6 3520 (Remove PREP_RERAISE_STAR, add CALL_INTRINSIC_2)
# Python 3.12a7 3521 (Shrink the LOAD_GLOBAL caches)
# Python 3.12a7 3522 (Removed JUMP_IF_FALSE_OR_POP/JUMP_IF_TRUE_OR_POP)
# Python 3.12a7 3523 (Convert COMPARE_AND_BRANCH back to COMPARE_OP)
# Python 3.12a7 3524 (Shrink the BINARY_SUBSCR caches)
# Python 3.12b1 3525 (Shrink the CALL caches)
# Python 3.12b1 3526 (Add instrumentation support)
# Python 3.12b1 3527 (Add LOAD_SUPER_ATTR)
# Python 3.12b1 3528 (Add LOAD_SUPER_ATTR_METHOD specialization)
# Python 3.12b1 3529 (Inline list/dict/set comprehensions)
# Python 3.12b1 3530 (Shrink the LOAD_SUPER_ATTR caches)
# Python 3.12b1 3531 (Add PEP 695 changes)
# Python 3.13a1 3550 (Plugin optimizer support)
# Python 3.13a1 3551 (Compact superinstructions)
# Python 3.13a1 3552 (Remove LOAD_FAST__LOAD_CONST and LOAD_CONST__LOAD_FAST)
# Python 3.13a1 3553 (Add SET_FUNCTION_ATTRIBUTE)
# Python 3.13a1 3554 (more efficient bytecodes for f-strings)
# Python 3.13a1 3555 (generate specialized opcodes metadata from bytecodes.c)
# Python 3.13a1 3556 (Convert LOAD_CLOSURE to a pseudo-op)
# Python 3.13a1 3557 (Make the conversion to boolean in jumps explicit)
# Python 3.13a1 3558 (Reorder the stack items for CALL)
# Python 3.13a1 3559 (Generate opcode IDs from bytecodes.c)
# Python 3.13a1 3560 (Add RESUME_CHECK instruction)
# Python 3.13a1 3561 (Add cache entry to branch instructions)
# Python 3.13a1 3562 (Assign opcode IDs for internal ops in separate range)
# Python 3.13a1 3563 (Add CALL_KW and remove KW_NAMES)
# Python 3.13a1 3564 (Removed oparg from YIELD_VALUE, changed oparg values of RESUME)
# Python 3.13a1 3565 (Oparg of YIELD_VALUE indicates whether it is in a yield-from)
# Python 3.13a1 3566 (Emit JUMP_NO_INTERRUPT instead of JUMP for non-loop no-lineno cases)
# Python 3.13a1 3567 (Reimplement line number propagation by the compiler)
# Python 3.13a1 3568 (Change semantics of END_FOR)
# Python 3.13a5 3569 (Specialize CONTAINS_OP)
# Python 3.13a6 3570 (Add __firstlineno__ class attribute)
# Python 3.13b1 3571 (Fix miscompilation of private names in generic classes)
# Python 3.14 will start with 3600
# Please don't copy-paste the same pre-release tag for new entries above!!!
# You should always use the *upcoming* tag. For example, if 3.12a6 came out
# a week ago, I should put "Python 3.12a7" next to my new magic number.
# MAGIC must change whenever the bytecode emitted by the compiler may no
# longer be understood by older implementations of the eval loop (usually
# due to the addition of new opcodes).
#
# Starting with Python 3.11, Python 3.n starts with magic number 2900+50n.
#
# Whenever MAGIC_NUMBER is changed, the ranges in the magic_values array
# in PC/launcher.c must also be updated.
MAGIC_NUMBER = (2996).to_bytes(2, 'little') + b'\r\n'
_RAW_MAGIC_NUMBER = int.from_bytes(MAGIC_NUMBER, 'little') # For import.c
MAGIC_NUMBER = _imp.pyc_magic_number_token.to_bytes(4, 'little')
_PYCACHE = '__pycache__'
_OPT = 'opt-'
@@ -983,6 +713,12 @@ class WindowsRegistryFinder:
@classmethod
def find_spec(cls, fullname, path=None, target=None):
_warnings.warn('importlib.machinery.WindowsRegistryFinder is '
'deprecated; use site configuration instead. '
'Future versions of Python may not enable this '
'finder by default.',
DeprecationWarning, stacklevel=2)
filepath = cls._search_registry(fullname)
if filepath is None:
return None
@@ -1131,7 +867,7 @@ class SourceLoader(_LoaderBasics):
_imp.check_hash_based_pycs == 'always')):
source_bytes = self.get_data(source_path)
source_hash = _imp.source_hash(
_RAW_MAGIC_NUMBER,
_imp.pyc_magic_number_token,
source_bytes,
)
_validate_hash_pyc(data, source_hash, fullname,
@@ -1160,7 +896,7 @@ class SourceLoader(_LoaderBasics):
source_mtime is not None):
if hash_based:
if source_hash is None:
source_hash = _imp.source_hash(_RAW_MAGIC_NUMBER,
source_hash = _imp.source_hash(_imp.pyc_magic_number_token,
source_bytes)
data = _code_to_hash_pyc(code_object, source_hash, check_source)
else:
@@ -1505,7 +1241,7 @@ class PathFinder:
if path == '':
try:
path = _os.getcwd()
except FileNotFoundError:
except (FileNotFoundError, PermissionError):
# Don't cache the failure as the cwd can easily change to
# a valid directory later on.
return None
+8 -17
View File
@@ -13,9 +13,6 @@ except ImportError:
_frozen_importlib_external = _bootstrap_external
from ._abc import Loader
import abc
import warnings
from .resources import abc as _resources_abc
__all__ = [
@@ -25,19 +22,6 @@ __all__ = [
]
def __getattr__(name):
"""
For backwards compatibility, continue to make names
from _resources_abc available through this module. #93963
"""
if name in _resources_abc.__all__:
obj = getattr(_resources_abc, name)
warnings._deprecated(f"{__name__}.{name}", remove=(3, 14))
globals()[name] = obj
return obj
raise AttributeError(f'module {__name__!r} has no attribute {name!r}')
def _register(abstract_cls, *classes):
for cls in classes:
abstract_cls.register(cls)
@@ -80,10 +64,13 @@ _register(PathEntryFinder, machinery.FileFinder)
class ResourceLoader(Loader):
"""Abstract base class for loaders which can return data from their
back-end storage.
back-end storage to facilitate reading data to perform an import.
This ABC represents one of the optional protocols specified by PEP 302.
For directly loading resources, use TraversableResources instead. This class
primarily exists for backwards compatibility with other ABCs in this module.
"""
@abc.abstractmethod
@@ -215,6 +202,10 @@ class SourceLoader(_bootstrap_external.SourceLoader, ResourceLoader, ExecutionLo
def path_mtime(self, path):
"""Return the (int) modification time for the path (str)."""
import warnings
warnings.warn('SourceLoader.path_mtime is deprecated in favour of '
'SourceLoader.path_stats().',
DeprecationWarning, stacklevel=2)
if self.path_stats.__func__ is SourceLoader.path_stats:
raise OSError
return int(self.path_stats(path)['mtime'])
+32 -3
View File
@@ -3,9 +3,11 @@
from ._bootstrap import ModuleSpec
from ._bootstrap import BuiltinImporter
from ._bootstrap import FrozenImporter
from ._bootstrap_external import (SOURCE_SUFFIXES, DEBUG_BYTECODE_SUFFIXES,
OPTIMIZED_BYTECODE_SUFFIXES, BYTECODE_SUFFIXES,
EXTENSION_SUFFIXES)
from ._bootstrap_external import (
SOURCE_SUFFIXES, BYTECODE_SUFFIXES, EXTENSION_SUFFIXES,
DEBUG_BYTECODE_SUFFIXES as _DEBUG_BYTECODE_SUFFIXES,
OPTIMIZED_BYTECODE_SUFFIXES as _OPTIMIZED_BYTECODE_SUFFIXES
)
from ._bootstrap_external import WindowsRegistryFinder
from ._bootstrap_external import PathFinder
from ._bootstrap_external import FileFinder
@@ -19,3 +21,30 @@ from ._bootstrap_external import NamespaceLoader
def all_suffixes():
"""Returns a list of all recognized module suffixes for this process"""
return SOURCE_SUFFIXES + BYTECODE_SUFFIXES + EXTENSION_SUFFIXES
__all__ = ['AppleFrameworkLoader', 'BYTECODE_SUFFIXES', 'BuiltinImporter',
'DEBUG_BYTECODE_SUFFIXES', 'EXTENSION_SUFFIXES',
'ExtensionFileLoader', 'FileFinder', 'FrozenImporter', 'ModuleSpec',
'NamespaceLoader', 'OPTIMIZED_BYTECODE_SUFFIXES', 'PathFinder',
'SOURCE_SUFFIXES', 'SourceFileLoader', 'SourcelessFileLoader',
'WindowsRegistryFinder', 'all_suffixes']
def __getattr__(name):
import warnings
if name == 'DEBUG_BYTECODE_SUFFIXES':
warnings.warn('importlib.machinery.DEBUG_BYTECODE_SUFFIXES is '
'deprecated; use importlib.machinery.BYTECODE_SUFFIXES '
'instead.',
DeprecationWarning, stacklevel=2)
return _DEBUG_BYTECODE_SUFFIXES
elif name == 'OPTIMIZED_BYTECODE_SUFFIXES':
warnings.warn('importlib.machinery.OPTIMIZED_BYTECODE_SUFFIXES is '
'deprecated; use importlib.machinery.BYTECODE_SUFFIXES '
'instead.',
DeprecationWarning, stacklevel=2)
return _OPTIMIZED_BYTECODE_SUFFIXES
raise AttributeError(f'module {__name__!r} has no attribute {name!r}')
+3 -3
View File
@@ -77,12 +77,12 @@ def resolve(cand: Optional[Anchor]) -> types.ModuleType:
return cast(types.ModuleType, cand)
@resolve.register(str) # TODO: RUSTPYTHON; manual type annotation
@resolve.register
def _(cand: str) -> types.ModuleType:
return importlib.import_module(cand)
@resolve.register(type(None)) # TODO: RUSTPYTHON; manual type annotation
@resolve.register
def _(cand: None) -> types.ModuleType:
return resolve(_infer_caller().f_globals['__name__'])
@@ -183,7 +183,7 @@ def _(path):
@contextlib.contextmanager
def _temp_path(dir: tempfile.TemporaryDirectory):
"""
Wrap tempfile.TemporyDirectory to return a pathlib object.
Wrap tempfile.TemporaryDirectory to return a pathlib object.
"""
with dir as result:
yield pathlib.Path(result)
-120
View File
@@ -1,120 +0,0 @@
import functools
import os
import pathlib
import types
import warnings
from typing import Union, Iterable, ContextManager, BinaryIO, TextIO, Any
from . import _common
Package = Union[types.ModuleType, str]
Resource = str
def deprecated(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
warnings.warn(
f"{func.__name__} is deprecated. Use files() instead. "
"Refer to https://importlib-resources.readthedocs.io"
"/en/latest/using.html#migrating-from-legacy for migration advice.",
DeprecationWarning,
stacklevel=2,
)
return func(*args, **kwargs)
return wrapper
def normalize_path(path: Any) -> str:
"""Normalize a path by ensuring it is a string.
If the resulting string contains path separators, an exception is raised.
"""
str_path = str(path)
parent, file_name = os.path.split(str_path)
if parent:
raise ValueError(f'{path!r} must be only a file name')
return file_name
@deprecated
def open_binary(package: Package, resource: Resource) -> BinaryIO:
"""Return a file-like object opened for binary reading of the resource."""
return (_common.files(package) / normalize_path(resource)).open('rb')
@deprecated
def read_binary(package: Package, resource: Resource) -> bytes:
"""Return the binary contents of the resource."""
return (_common.files(package) / normalize_path(resource)).read_bytes()
@deprecated
def open_text(
package: Package,
resource: Resource,
encoding: str = 'utf-8',
errors: str = 'strict',
) -> TextIO:
"""Return a file-like object opened for text reading of the resource."""
return (_common.files(package) / normalize_path(resource)).open(
'r', encoding=encoding, errors=errors
)
@deprecated
def read_text(
package: Package,
resource: Resource,
encoding: str = 'utf-8',
errors: str = 'strict',
) -> str:
"""Return the decoded string of the resource.
The decoding-related arguments have the same semantics as those of
bytes.decode().
"""
with open_text(package, resource, encoding, errors) as fp:
return fp.read()
@deprecated
def contents(package: Package) -> Iterable[str]:
"""Return an iterable of entries in `package`.
Note that not all entries are resources. Specifically, directories are
not considered resources. Use `is_resource()` on each entry returned here
to check if it is a resource or not.
"""
return [path.name for path in _common.files(package).iterdir()]
@deprecated
def is_resource(package: Package, name: str) -> bool:
"""True if `name` is a resource inside `package`.
Directories are *not* resources.
"""
resource = normalize_path(name)
return any(
traversable.name == resource and traversable.is_file()
for traversable in _common.files(package).iterdir()
)
@deprecated
def path(
package: Package,
resource: Resource,
) -> ContextManager[pathlib.Path]:
"""A context manager providing a file path object to the resource.
If the resource does not already exist on its own on the file system,
a temporary file will be created. If the file was created, the file
will be deleted upon exiting the context manager (no exception is
raised if the file was deleted prior to the context manager
exiting).
"""
return _common.as_file(_common.files(package) / normalize_path(resource))
+7 -2
View File
@@ -5,7 +5,6 @@ from ._bootstrap import _resolve_name
from ._bootstrap import spec_from_loader
from ._bootstrap import _find_spec
from ._bootstrap_external import MAGIC_NUMBER
from ._bootstrap_external import _RAW_MAGIC_NUMBER
from ._bootstrap_external import cache_from_source
from ._bootstrap_external import decode_source
from ._bootstrap_external import source_from_cache
@@ -18,7 +17,7 @@ import types
def source_hash(source_bytes):
"Return the hash of *source_bytes* as used in hash-based pyc files."
return _imp.source_hash(_RAW_MAGIC_NUMBER, source_bytes)
return _imp.source_hash(_imp.pyc_magic_number_token, source_bytes)
def resolve_name(name, package):
@@ -272,3 +271,9 @@ class LazyLoader(Loader):
loader_state['is_loading'] = False
module.__spec__.loader_state = loader_state
module.__class__ = _LazyModule
__all__ = ['LazyLoader', 'Loader', 'MAGIC_NUMBER',
'cache_from_source', 'decode_source', 'find_spec',
'module_from_spec', 'resolve_name', 'source_from_cache',
'source_hash', 'spec_from_file_location', 'spec_from_loader']
+52 -2
View File
@@ -162,7 +162,7 @@ class BaseLockTests(BaseTestCase):
self.assertFalse(result[0])
lock.release()
@unittest.skip('TODO: RUSTPYTHON; sometimes hangs')
@unittest.skip("TODO: RUSTPYTHON; sometimes hangs")
def test_acquire_contended(self):
lock = self.locktype()
lock.acquire()
@@ -210,7 +210,7 @@ class BaseLockTests(BaseTestCase):
with Bunch(f, 1):
pass
@unittest.skip('TODO: RUSTPYTHON; sometimes hangs')
@unittest.skip("TODO: RUSTPYTHON; sometimes hangs")
def test_thread_leak(self):
# The lock shouldn't leak a Thread instance when used from a foreign
# (non-threading) thread.
@@ -334,6 +334,26 @@ class RLockTests(BaseLockTests):
"""
Tests for recursive locks.
"""
def test_repr_count(self):
# see gh-134322: check that count values are correct:
# when a rlock is just created,
# in a second thread when rlock is acquired in the main thread.
lock = self.locktype()
self.assertIn("count=0", repr(lock))
self.assertIn("<unlocked", repr(lock))
lock.acquire()
lock.acquire()
self.assertIn("count=2", repr(lock))
self.assertIn("<locked", repr(lock))
result = []
def call_repr():
result.append(repr(lock))
with Bunch(call_repr, 1):
pass
self.assertIn("count=2", result[0])
self.assertIn("<locked", result[0])
def test_reacquire(self):
lock = self.locktype()
lock.acquire()
@@ -355,6 +375,36 @@ class RLockTests(BaseLockTests):
lock.release()
self.assertRaises(RuntimeError, lock.release)
def test_locked(self):
lock = self.locktype()
self.assertFalse(lock.locked())
lock.acquire()
self.assertTrue(lock.locked())
lock.acquire()
self.assertTrue(lock.locked())
lock.release()
self.assertTrue(lock.locked())
lock.release()
self.assertFalse(lock.locked())
def test_locked_with_2threads(self):
# see gh-134323: check that a rlock which
# is acquired in a different thread,
# is still locked in the main thread.
result = []
rlock = self.locktype()
self.assertFalse(rlock.locked())
def acquire():
result.append(rlock.locked())
rlock.acquire()
result.append(rlock.locked())
with Bunch(acquire, 1):
pass
self.assertTrue(rlock.locked())
self.assertFalse(result[0])
self.assertTrue(result[1])
def test_release_save_unacquired(self):
# Cannot _release_save an unacquired lock
lock = self.locktype()
-1
View File
@@ -4,7 +4,6 @@ machinery = util.import_importlib('importlib.machinery')
import sys
import unittest
import warnings
@unittest.skipIf(util.BUILTINS.good_name is None, 'no reasonable builtin module')
@@ -0,0 +1,44 @@
import types
import unittest
from test.test_importlib import util
machinery = util.import_importlib('importlib.machinery')
from test.test_importlib.extension.test_loader import MultiPhaseExtensionModuleTests
class NonModuleExtensionTests:
setUp = MultiPhaseExtensionModuleTests.setUp
load_module_by_name = MultiPhaseExtensionModuleTests.load_module_by_name
def _test_nonmodule(self):
# Test returning a non-module object from create works.
name = self.name + '_nonmodule'
mod = self.load_module_by_name(name)
self.assertNotEqual(type(mod), type(unittest))
self.assertEqual(mod.three, 3)
# issue 27782
def test_nonmodule_with_methods(self):
# Test creating a non-module object with methods defined.
name = self.name + '_nonmodule_with_methods'
mod = self.load_module_by_name(name)
self.assertNotEqual(type(mod), type(unittest))
self.assertEqual(mod.three, 3)
self.assertEqual(mod.bar(10, 1), 9)
def test_null_slots(self):
# Test that NULL slots aren't a problem.
name = self.name + '_null_slots'
module = self.load_module_by_name(name)
self.assertIsInstance(module, types.ModuleType)
self.assertEqual(module.__name__, name)
(Frozen_NonModuleExtensionTests,
Source_NonModuleExtensionTests
) = util.test_both(NonModuleExtensionTests, machinery=machinery)
if __name__ == '__main__':
unittest.main()
+2 -2
View File
@@ -1,4 +1,3 @@
from importlib import _bootstrap_external
from test.support import os_helper
import unittest
import sys
@@ -8,7 +7,8 @@ importlib = util.import_importlib('importlib')
machinery = util.import_importlib('importlib.machinery')
@unittest.skipIf(util.EXTENSIONS.filename is None, f'{util.EXTENSIONS.name} not available')
@unittest.skipIf(util.EXTENSIONS is None or util.EXTENSIONS.filename is None,
'dynamic loading not supported or test module not available')
@util.case_insensitive_tests
class ExtensionModuleCaseSensitivityTest(util.CASEOKTestBase):
+23 -6
View File
@@ -1,3 +1,4 @@
from test.support import is_apple_mobile
from test.test_importlib import abc, util
machinery = util.import_importlib('importlib.machinery')
@@ -11,7 +12,7 @@ class FinderTests(abc.FinderTests):
"""Test the finder for extension modules."""
def setUp(self):
if not self.machinery.EXTENSION_SUFFIXES:
if not self.machinery.EXTENSION_SUFFIXES or not util.EXTENSIONS:
raise unittest.SkipTest("Requires dynamic loading support.")
if util.EXTENSIONS.name in sys.builtin_module_names:
raise unittest.SkipTest(
@@ -19,14 +20,30 @@ class FinderTests(abc.FinderTests):
)
def find_spec(self, fullname):
importer = self.machinery.FileFinder(util.EXTENSIONS.path,
(self.machinery.ExtensionFileLoader,
self.machinery.EXTENSION_SUFFIXES))
if is_apple_mobile:
# Apple mobile platforms require a specialist loader that uses
# .fwork files as placeholders for the true `.so` files.
loaders = [
(
self.machinery.AppleFrameworkLoader,
[
ext.replace(".so", ".fwork")
for ext in self.machinery.EXTENSION_SUFFIXES
]
)
]
else:
loaders = [
(
self.machinery.ExtensionFileLoader,
self.machinery.EXTENSION_SUFFIXES
)
]
importer = self.machinery.FileFinder(util.EXTENSIONS.path, *loaders)
return importer.find_spec(fullname)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_module(self):
self.assertTrue(self.find_spec(util.EXTENSIONS.name))
+53 -53
View File
@@ -1,4 +1,4 @@
from warnings import catch_warnings
from test.support import is_apple_mobile
from test.test_importlib import abc, util
machinery = util.import_importlib('importlib.machinery')
@@ -10,7 +10,8 @@ import unittest
import warnings
import importlib.util
import importlib
from test.support.script_helper import assert_python_failure
from test import support
from test.support import MISSING_C_DOCSTRINGS, script_helper
class LoaderTests:
@@ -18,14 +19,21 @@ class LoaderTests:
"""Test ExtensionFileLoader."""
def setUp(self):
if not self.machinery.EXTENSION_SUFFIXES:
if not self.machinery.EXTENSION_SUFFIXES or not util.EXTENSIONS:
raise unittest.SkipTest("Requires dynamic loading support.")
if util.EXTENSIONS.name in sys.builtin_module_names:
raise unittest.SkipTest(
f"{util.EXTENSIONS.name} is a builtin module"
)
self.loader = self.machinery.ExtensionFileLoader(util.EXTENSIONS.name,
util.EXTENSIONS.file_path)
# Apple extensions must be distributed as frameworks. This requires
# a specialist loader.
if is_apple_mobile:
self.LoaderClass = self.machinery.AppleFrameworkLoader
else:
self.LoaderClass = self.machinery.ExtensionFileLoader
self.loader = self.LoaderClass(util.EXTENSIONS.name, util.EXTENSIONS.file_path)
def load_module(self, fullname):
with warnings.catch_warnings():
@@ -33,13 +41,11 @@ class LoaderTests:
return self.loader.load_module(fullname)
def test_equality(self):
other = self.machinery.ExtensionFileLoader(util.EXTENSIONS.name,
util.EXTENSIONS.file_path)
other = self.LoaderClass(util.EXTENSIONS.name, util.EXTENSIONS.file_path)
self.assertEqual(self.loader, other)
def test_inequality(self):
other = self.machinery.ExtensionFileLoader('_' + util.EXTENSIONS.name,
util.EXTENSIONS.file_path)
other = self.LoaderClass('_' + util.EXTENSIONS.name, util.EXTENSIONS.file_path)
self.assertNotEqual(self.loader, other)
def test_load_module_API(self):
@@ -51,8 +57,6 @@ class LoaderTests:
with self.assertRaises(ImportError):
self.load_module('XXX')
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_module(self):
with util.uncache(util.EXTENSIONS.name):
module = self.load_module(util.EXTENSIONS.name)
@@ -61,8 +65,7 @@ class LoaderTests:
('__package__', '')]:
self.assertEqual(getattr(module, attr), value)
self.assertIn(util.EXTENSIONS.name, sys.modules)
self.assertIsInstance(module.__loader__,
self.machinery.ExtensionFileLoader)
self.assertIsInstance(module.__loader__, self.LoaderClass)
# No extension module as __init__ available for testing.
test_package = None
@@ -85,13 +88,11 @@ class LoaderTests:
module2 = self.load_module(util.EXTENSIONS.name)
self.assertIs(module1, module2)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_is_package(self):
self.assertFalse(self.loader.is_package(util.EXTENSIONS.name))
for suffix in self.machinery.EXTENSION_SUFFIXES:
path = os.path.join('some', 'path', 'pkg', '__init__' + suffix)
loader = self.machinery.ExtensionFileLoader('pkg', path)
loader = self.LoaderClass('pkg', path)
self.assertTrue(loader.is_package('pkg'))
@@ -104,8 +105,16 @@ class SinglePhaseExtensionModuleTests(abc.LoaderTests):
# Test loading extension modules without multi-phase initialization.
def setUp(self):
if not self.machinery.EXTENSION_SUFFIXES:
if not self.machinery.EXTENSION_SUFFIXES or not util.EXTENSIONS:
raise unittest.SkipTest("Requires dynamic loading support.")
# Apple extensions must be distributed as frameworks. This requires
# a specialist loader.
if is_apple_mobile:
self.LoaderClass = self.machinery.AppleFrameworkLoader
else:
self.LoaderClass = self.machinery.ExtensionFileLoader
self.name = '_testsinglephase'
if self.name in sys.builtin_module_names:
raise unittest.SkipTest(
@@ -114,8 +123,8 @@ class SinglePhaseExtensionModuleTests(abc.LoaderTests):
finder = self.machinery.FileFinder(None)
self.spec = importlib.util.find_spec(self.name)
assert self.spec
self.loader = self.machinery.ExtensionFileLoader(
self.name, self.spec.origin)
self.loader = self.LoaderClass(self.name, self.spec.origin)
def load_module(self):
with warnings.catch_warnings():
@@ -125,7 +134,7 @@ class SinglePhaseExtensionModuleTests(abc.LoaderTests):
def load_module_by_name(self, fullname):
# Load a module from the test extension by name.
origin = self.spec.origin
loader = self.machinery.ExtensionFileLoader(fullname, origin)
loader = self.LoaderClass(fullname, origin)
spec = importlib.util.spec_from_loader(fullname, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
@@ -142,8 +151,7 @@ class SinglePhaseExtensionModuleTests(abc.LoaderTests):
with self.assertRaises(AttributeError):
module.__path__
self.assertIs(module, sys.modules[self.name])
self.assertIsInstance(module.__loader__,
self.machinery.ExtensionFileLoader)
self.assertIsInstance(module.__loader__, self.LoaderClass)
# No extension module as __init__ available for testing.
test_package = None
@@ -181,13 +189,20 @@ class SinglePhaseExtensionModuleTests(abc.LoaderTests):
) = util.test_both(SinglePhaseExtensionModuleTests, machinery=machinery)
# @unittest.skip("TODO: RUSTPYTHON, AssertionError")
class MultiPhaseExtensionModuleTests(abc.LoaderTests):
# Test loading extension modules with multi-phase initialization (PEP 489).
def setUp(self):
if not self.machinery.EXTENSION_SUFFIXES:
if not self.machinery.EXTENSION_SUFFIXES or not util.EXTENSIONS:
raise unittest.SkipTest("Requires dynamic loading support.")
# Apple extensions must be distributed as frameworks. This requires
# a specialist loader.
if is_apple_mobile:
self.LoaderClass = self.machinery.AppleFrameworkLoader
else:
self.LoaderClass = self.machinery.ExtensionFileLoader
self.name = '_testmultiphase'
if self.name in sys.builtin_module_names:
raise unittest.SkipTest(
@@ -196,8 +211,7 @@ class MultiPhaseExtensionModuleTests(abc.LoaderTests):
finder = self.machinery.FileFinder(None)
self.spec = importlib.util.find_spec(self.name)
assert self.spec
self.loader = self.machinery.ExtensionFileLoader(
self.name, self.spec.origin)
self.loader = self.LoaderClass(self.name, self.spec.origin)
def load_module(self):
# Load the module from the test extension.
@@ -208,7 +222,7 @@ class MultiPhaseExtensionModuleTests(abc.LoaderTests):
def load_module_by_name(self, fullname):
# Load a module from the test extension by name.
origin = self.spec.origin
loader = self.machinery.ExtensionFileLoader(fullname, origin)
loader = self.LoaderClass(fullname, origin)
spec = importlib.util.spec_from_loader(fullname, loader)
module = importlib.util.module_from_spec(spec)
loader.exec_module(module)
@@ -234,8 +248,7 @@ class MultiPhaseExtensionModuleTests(abc.LoaderTests):
with self.assertRaises(AttributeError):
module.__path__
self.assertIs(module, sys.modules[self.name])
self.assertIsInstance(module.__loader__,
self.machinery.ExtensionFileLoader)
self.assertIsInstance(module.__loader__, self.LoaderClass)
def test_functionality(self):
# Test basic functionality of stuff defined in an extension module.
@@ -313,29 +326,6 @@ class MultiPhaseExtensionModuleTests(abc.LoaderTests):
self.load_module_by_name(name)
self.assertEqual(cm.exception.name, name)
def test_nonmodule(self):
# Test returning a non-module object from create works.
name = self.name + '_nonmodule'
mod = self.load_module_by_name(name)
self.assertNotEqual(type(mod), type(unittest))
self.assertEqual(mod.three, 3)
# issue 27782
def test_nonmodule_with_methods(self):
# Test creating a non-module object with methods defined.
name = self.name + '_nonmodule_with_methods'
mod = self.load_module_by_name(name)
self.assertNotEqual(type(mod), type(unittest))
self.assertEqual(mod.three, 3)
self.assertEqual(mod.bar(10, 1), 9)
def test_null_slots(self):
# Test that NULL slots aren't a problem.
name = self.name + '_null_slots'
module = self.load_module_by_name(name)
self.assertIsInstance(module, types.ModuleType)
self.assertEqual(module.__name__, name)
def test_bad_modules(self):
# Test SystemError is raised for misbehaving extensions.
for name_base in [
@@ -380,7 +370,8 @@ class MultiPhaseExtensionModuleTests(abc.LoaderTests):
with self.subTest(name):
module = self.load_module_by_name(name)
self.assertEqual(module.__name__, name)
self.assertEqual(module.__doc__, "Module named in %s" % lang)
if not MISSING_C_DOCSTRINGS:
self.assertEqual(module.__doc__, "Module named in %s" % lang)
(Frozen_MultiPhaseExtensionModuleTests,
@@ -388,5 +379,14 @@ class MultiPhaseExtensionModuleTests(abc.LoaderTests):
) = util.test_both(MultiPhaseExtensionModuleTests, machinery=machinery)
class NonModuleExtensionTests(unittest.TestCase):
def test_nonmodule_cases(self):
# The test cases in this file cause the GIL to be enabled permanently
# in free-threaded builds, so they are run in a subprocess to isolate
# this effect.
script = support.findfile("test_importlib/extension/_test_nonmodule_cases.py")
script_helper.run_test_script(script)
if __name__ == '__main__':
unittest.main()
+3 -1
View File
@@ -5,6 +5,8 @@ machinery = util.import_importlib('importlib.machinery')
import unittest
@unittest.skipIf(util.EXTENSIONS is None or util.EXTENSIONS.filename is None,
'dynamic loading not supported or test module not available')
class PathHookTests:
"""Test the path hook for extension modules."""
@@ -19,7 +21,7 @@ class PathHookTests:
def test_success(self):
# Path hook should handle a directory where a known extension module
# exists.
self.assertTrue(hasattr(self.hook(util.EXTENSIONS.path), 'find_spec'))
self.assertHasAttr(self.hook(util.EXTENSIONS.path), 'find_spec')
(Frozen_PathHooksTests,
-9
View File
@@ -2,11 +2,8 @@ from test.test_importlib import abc, util
machinery = util.import_importlib('importlib.machinery')
import _imp
import marshal
import os.path
import unittest
import warnings
from test.support import import_helper, REPO_ROOT, STDLIB_DIR
@@ -70,8 +67,6 @@ class FindSpecTests(abc.FinderTests):
expected = [os.path.dirname(filename)]
self.assertListEqual(spec.submodule_search_locations, expected)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_module(self):
modules = [
'__hello__',
@@ -114,8 +109,6 @@ class FindSpecTests(abc.FinderTests):
self.check_basic(spec, name)
self.check_loader_state(spec, origname, filename)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_package(self):
packages = [
'__phello__',
@@ -170,8 +163,6 @@ class FindSpecTests(abc.FinderTests):
spec = self.find('<not real>')
self.assertIsNone(spec)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_not_using_frozen(self):
finder = self.machinery.FrozenImporter
with import_helper.frozen_modules(enabled=False):
+3 -7
View File
@@ -3,11 +3,8 @@ from test.test_importlib import abc, util
machinery = util.import_importlib('importlib.machinery')
from test.support import captured_stdout, import_helper, STDLIB_DIR
import _imp
import contextlib
import marshal
import os.path
import sys
import types
import unittest
import warnings
@@ -64,7 +61,7 @@ class ExecModuleTests(abc.LoaderTests):
module.main()
self.assertTrue(module.initialized)
self.assertTrue(hasattr(module, '__spec__'))
self.assertHasAttr(module, '__spec__')
self.assertEqual(module.__spec__.origin, 'frozen')
return module, stdout.getvalue()
@@ -75,7 +72,7 @@ class ExecModuleTests(abc.LoaderTests):
for attr, value in check.items():
self.assertEqual(getattr(module, attr), value)
self.assertEqual(output, 'Hello world!\n')
self.assertTrue(hasattr(module, '__spec__'))
self.assertHasAttr(module, '__spec__')
self.assertEqual(module.__spec__.loader_state.origname, name)
@unittest.skipIf(sys.platform == "win32", "TODO: RUSTPYTHON")
@@ -92,7 +89,6 @@ class ExecModuleTests(abc.LoaderTests):
self.assertEqual(output, 'Hello world!\n')
self.assertEqual(module.__spec__.loader_state.origname, name)
@unittest.skipIf(sys.platform == 'win32', "TODO:RUSTPYTHON Flaky on Windows")
def test_lacking_parent(self):
name = '__phello__.spam'
with util.uncache('__phello__'):
@@ -141,7 +137,7 @@ class InspectLoaderTests:
exec(code, mod.__dict__)
with captured_stdout() as stdout:
mod.main()
self.assertTrue(hasattr(mod, 'initialized'))
self.assertHasAttr(mod, 'initialized')
self.assertEqual(stdout.getvalue(), 'Hello world!\n')
def test_get_source(self):
-3
View File
@@ -1,8 +1,5 @@
from importlib import machinery
import sys
import types
import unittest
import warnings
from test.test_importlib import util
+2 -2
View File
@@ -78,7 +78,7 @@ class ImportlibUseCache(UseCache, unittest.TestCase):
with self.create_mock('pkg.__init__', 'pkg.module') as importer:
with util.import_state(meta_path=[importer]):
module = self.__import__('pkg.module')
self.assertTrue(hasattr(module, 'module'))
self.assertHasAttr(module, 'module')
self.assertEqual(id(module.module),
id(sys.modules['pkg.module']))
@@ -88,7 +88,7 @@ class ImportlibUseCache(UseCache, unittest.TestCase):
with self.create_mock('pkg.__init__', 'pkg.module') as importer:
with util.import_state(meta_path=[importer]):
module = self.__import__('pkg', fromlist=['module'])
self.assertTrue(hasattr(module, 'module'))
self.assertHasAttr(module, 'module')
self.assertEqual(id(module.module),
id(sys.modules['pkg.module']))
+8 -8
View File
@@ -63,7 +63,7 @@ class HandlingFromlist:
with util.import_state(meta_path=[importer]):
module = self.__import__('module', fromlist=['non_existent'])
self.assertEqual(module.__name__, 'module')
self.assertFalse(hasattr(module, 'non_existent'))
self.assertNotHasAttr(module, 'non_existent')
def test_module_from_package(self):
# [module]
@@ -71,7 +71,7 @@ class HandlingFromlist:
with util.import_state(meta_path=[importer]):
module = self.__import__('pkg', fromlist=['module'])
self.assertEqual(module.__name__, 'pkg')
self.assertTrue(hasattr(module, 'module'))
self.assertHasAttr(module, 'module')
self.assertEqual(module.module.__name__, 'pkg.module')
def test_nonexistent_from_package(self):
@@ -79,7 +79,7 @@ class HandlingFromlist:
with util.import_state(meta_path=[importer]):
module = self.__import__('pkg', fromlist=['non_existent'])
self.assertEqual(module.__name__, 'pkg')
self.assertFalse(hasattr(module, 'non_existent'))
self.assertNotHasAttr(module, 'non_existent')
def test_module_from_package_triggers_ModuleNotFoundError(self):
# If a submodule causes an ModuleNotFoundError because it tries
@@ -107,7 +107,7 @@ class HandlingFromlist:
mock['pkg'].__all__ = ['module']
module = self.__import__('pkg', fromlist=fromlist)
self.assertEqual(module.__name__, 'pkg')
self.assertTrue(hasattr(module, 'module'))
self.assertHasAttr(module, 'module')
self.assertEqual(module.module.__name__, 'pkg.module')
def test_using_star(self):
@@ -125,8 +125,8 @@ class HandlingFromlist:
mock['pkg'].__all__ = ['module1']
module = self.__import__('pkg', fromlist=['module2', '*'])
self.assertEqual(module.__name__, 'pkg')
self.assertTrue(hasattr(module, 'module1'))
self.assertTrue(hasattr(module, 'module2'))
self.assertHasAttr(module, 'module1')
self.assertHasAttr(module, 'module2')
self.assertEqual(module.module1.__name__, 'pkg.module1')
self.assertEqual(module.module2.__name__, 'pkg.module2')
@@ -136,7 +136,7 @@ class HandlingFromlist:
importer['pkg'].__all__ = ['non_existent']
module = self.__import__('pkg', fromlist=['*'])
self.assertEqual(module.__name__, 'pkg')
self.assertFalse(hasattr(module, 'non_existent'))
self.assertNotHasAttr(module, 'non_existent')
def test_star_in_all(self):
with util.mock_spec('pkg.__init__') as importer:
@@ -144,7 +144,7 @@ class HandlingFromlist:
importer['pkg'].__all__ = ['*']
module = self.__import__('pkg', fromlist=['*'])
self.assertEqual(module.__name__, 'pkg')
self.assertFalse(hasattr(module, '*'))
self.assertNotHasAttr(module, '*')
def test_invalid_type(self):
with util.mock_spec('pkg.__init__') as importer:
+1 -1
View File
@@ -43,7 +43,7 @@ class CallingOrder:
self.assertIsNone(importlib._bootstrap._find_spec('nothing',
None))
self.assertEqual(len(w), 1)
self.assertTrue(issubclass(w[-1].category, ImportWarning))
self.assertIsSubclass(w[-1].category, ImportWarning)
(Frozen_CallingOrder,
-1
View File
@@ -1,7 +1,6 @@
from test.test_importlib import util
import sys
import unittest
from test import support
from test.support import import_helper
+28 -1
View File
@@ -1,3 +1,4 @@
from test.support import os_helper
from test.test_importlib import util
importlib = util.import_importlib('importlib')
@@ -80,7 +81,7 @@ class FinderTests:
self.assertIsNone(self.find('os'))
self.assertIsNone(sys.path_importer_cache[path_entry])
self.assertEqual(len(w), 1)
self.assertTrue(issubclass(w[-1].category, ImportWarning))
self.assertIsSubclass(w[-1].category, ImportWarning)
def test_path_importer_cache_empty_string(self):
# The empty string should create a finder using the cwd.
@@ -153,6 +154,32 @@ class FinderTests:
# Do not want FileNotFoundError raised.
self.assertIsNone(self.machinery.PathFinder.find_spec('whatever'))
@os_helper.skip_unless_working_chmod
def test_permission_error_cwd(self):
# gh-115911: Test that an unreadable CWD does not break imports, in
# particular during early stages of interpreter startup.
def noop_hook(*args):
raise ImportError
with (
os_helper.temp_dir() as new_dir,
os_helper.save_mode(new_dir),
os_helper.change_cwd(new_dir),
util.import_state(path=[''], path_hooks=[noop_hook]),
):
# chmod() is done here (inside the 'with' block) because the order
# of teardown operations cannot be the reverse of setup order. See
# https://github.com/python/cpython/pull/116131#discussion_r1739649390
try:
os.chmod(new_dir, 0o000)
except OSError:
self.skipTest("platform does not allow "
"changing mode of the cwd")
# Do not want PermissionError raised.
self.assertIsNone(self.machinery.PathFinder.find_spec('whatever'))
def test_invalidate_caches_finders(self):
# Finders with an invalidate_caches() method have it called.
class FakeFinder:
+18 -3
View File
@@ -81,7 +81,7 @@ class RelativeImports:
self.__import__('pkg') # For __import__().
module = self.__import__('', global_, fromlist=['mod2'], level=1)
self.assertEqual(module.__name__, 'pkg')
self.assertTrue(hasattr(module, 'mod2'))
self.assertHasAttr(module, 'mod2')
self.assertEqual(module.mod2.attr, 'pkg.mod2')
self.relative_import_test(create, globals_, callback)
@@ -107,7 +107,7 @@ class RelativeImports:
module = self.__import__('', global_, fromlist=['module'],
level=1)
self.assertEqual(module.__name__, 'pkg')
self.assertTrue(hasattr(module, 'module'))
self.assertHasAttr(module, 'module')
self.assertEqual(module.module.attr, 'pkg.module')
self.relative_import_test(create, globals_, callback)
@@ -131,7 +131,7 @@ class RelativeImports:
module = self.__import__('', global_, fromlist=['subpkg2'],
level=2)
self.assertEqual(module.__name__, 'pkg')
self.assertTrue(hasattr(module, 'subpkg2'))
self.assertHasAttr(module, 'subpkg2')
self.assertEqual(module.subpkg2.attr, 'pkg.subpkg2.__init__')
self.relative_import_test(create, globals_, callback)
@@ -223,6 +223,21 @@ class RelativeImports:
self.__import__('sys', {'__package__': '', '__spec__': None},
level=1)
def test_malicious_relative_import(self):
# https://github.com/python/cpython/issues/134100
# Test to make sure UAF bug with error msg doesn't come back to life
import sys
loooong = "".ljust(0x23000, "b")
name = f"a.{loooong}.c"
with util.uncache(name):
sys.modules[name] = {}
with self.assertRaisesRegex(
KeyError,
r"'a\.b+' not in sys\.modules as expected"
):
__import__(f"{loooong}.c", {"__package__": "a"}, level=1)
(Frozen_RelativeImports,
Source_RelativeImports
+1
View File
@@ -0,0 +1 @@
+13
View File
@@ -0,0 +1,13 @@
import contextlib
# from jaraco.context 4.3
class suppress(contextlib.suppress, contextlib.ContextDecorator):
"""
A version of contextlib.suppress with decorator support.
>>> @suppress(KeyError)
... def key_error():
... {}['']
>>> key_error()
"""
+115
View File
@@ -0,0 +1,115 @@
# from jaraco.path 3.7
import functools
import pathlib
from typing import Dict, Protocol, Union
from typing import runtime_checkable
class Symlink(str):
"""
A string indicating the target of a symlink.
"""
FilesSpec = Dict[str, Union[str, bytes, Symlink, 'FilesSpec']] # type: ignore
@runtime_checkable
class TreeMaker(Protocol):
def __truediv__(self, *args, **kwargs): ... # pragma: no cover
def mkdir(self, **kwargs): ... # pragma: no cover
def write_text(self, content, **kwargs): ... # pragma: no cover
def write_bytes(self, content): ... # pragma: no cover
def symlink_to(self, target): ... # pragma: no cover
def _ensure_tree_maker(obj: Union[str, TreeMaker]) -> TreeMaker:
return obj if isinstance(obj, TreeMaker) else pathlib.Path(obj) # type: ignore
def build(
spec: FilesSpec,
prefix: Union[str, TreeMaker] = pathlib.Path(), # type: ignore
):
"""
Build a set of files/directories, as described by the spec.
Each key represents a pathname, and the value represents
the content. Content may be a nested directory.
>>> spec = {
... 'README.txt': "A README file",
... "foo": {
... "__init__.py": "",
... "bar": {
... "__init__.py": "",
... },
... "baz.py": "# Some code",
... "bar.py": Symlink("baz.py"),
... },
... "bing": Symlink("foo"),
... }
>>> target = getfixture('tmp_path')
>>> build(spec, target)
>>> target.joinpath('foo/baz.py').read_text(encoding='utf-8')
'# Some code'
>>> target.joinpath('bing/bar.py').read_text(encoding='utf-8')
'# Some code'
"""
for name, contents in spec.items():
create(contents, _ensure_tree_maker(prefix) / name)
@functools.singledispatch
def create(content: Union[str, bytes, FilesSpec], path):
path.mkdir(exist_ok=True)
build(content, prefix=path) # type: ignore
@create.register
def _(content: bytes, path):
path.write_bytes(content)
@create.register
def _(content: str, path):
path.write_text(content, encoding='utf-8')
@create.register
def _(content: Symlink, path):
path.symlink_to(content)
class Recording:
"""
A TreeMaker object that records everything that would be written.
>>> r = Recording()
>>> build({'foo': {'foo1.txt': 'yes'}, 'bar.txt': 'abc'}, r)
>>> r.record
['foo/foo1.txt', 'bar.txt']
"""
def __init__(self, loc=pathlib.PurePosixPath(), record=None):
self.loc = loc
self.record = record if record is not None else []
def __truediv__(self, other):
return Recording(self.loc / other, self.record)
def write_text(self, content, **kwargs):
self.record.append(str(self.loc))
write_bytes = write_text
def mkdir(self, **kwargs):
return
def symlink_to(self, target):
pass
+1
View File
@@ -0,0 +1 @@
Binary file not shown.
@@ -0,0 +1,2 @@
def main():
return 'example'
@@ -0,0 +1,11 @@
from setuptools import setup
setup(
name='example',
version='21.12',
license='Apache Software License',
packages=['example'],
entry_points={
'console_scripts': ['example = example:main', 'Example=example:main'],
},
)
@@ -0,0 +1,2 @@
def main():
return "example"
@@ -0,0 +1,10 @@
[build-system]
build-backend = 'trampolim'
requires = ['trampolim']
[project]
name = 'example2'
version = '1.0.0'
[project.scripts]
example = 'example2:main'
+395
View File
@@ -0,0 +1,395 @@
import sys
import copy
import json
import shutil
import pathlib
import textwrap
import functools
import contextlib
from test.support import import_helper
from test.support import os_helper
from test.support import requires_zlib
from . import _path
from ._path import FilesSpec
try:
from importlib import resources # type: ignore
getattr(resources, 'files')
getattr(resources, 'as_file')
except (ImportError, AttributeError):
import importlib_resources as resources # type: ignore
@contextlib.contextmanager
def tmp_path():
"""
Like os_helper.temp_dir, but yields a pathlib.Path.
"""
with os_helper.temp_dir() as path:
yield pathlib.Path(path)
@contextlib.contextmanager
def install_finder(finder):
sys.meta_path.append(finder)
try:
yield
finally:
sys.meta_path.remove(finder)
class Fixtures:
def setUp(self):
self.fixtures = contextlib.ExitStack()
self.addCleanup(self.fixtures.close)
class SiteDir(Fixtures):
def setUp(self):
super().setUp()
self.site_dir = self.fixtures.enter_context(tmp_path())
class OnSysPath(Fixtures):
@staticmethod
@contextlib.contextmanager
def add_sys_path(dir):
sys.path[:0] = [str(dir)]
try:
yield
finally:
sys.path.remove(str(dir))
def setUp(self):
super().setUp()
self.fixtures.enter_context(self.add_sys_path(self.site_dir))
self.fixtures.enter_context(import_helper.isolated_modules())
class SiteBuilder(SiteDir):
def setUp(self):
super().setUp()
for cls in self.__class__.mro():
with contextlib.suppress(AttributeError):
build_files(cls.files, prefix=self.site_dir)
class DistInfoPkg(OnSysPath, SiteBuilder):
files: FilesSpec = {
"distinfo_pkg-1.0.0.dist-info": {
"METADATA": """
Name: distinfo-pkg
Author: Steven Ma
Version: 1.0.0
Requires-Dist: wheel >= 1.0
Requires-Dist: pytest; extra == 'test'
Keywords: sample package
Once upon a time
There was a distinfo pkg
""",
"RECORD": "mod.py,sha256=abc,20\n",
"entry_points.txt": """
[entries]
main = mod:main
ns:sub = mod:main
""",
},
"mod.py": """
def main():
print("hello world")
""",
}
def make_uppercase(self):
"""
Rewrite metadata with everything uppercase.
"""
shutil.rmtree(self.site_dir / "distinfo_pkg-1.0.0.dist-info")
files = copy.deepcopy(DistInfoPkg.files)
info = files["distinfo_pkg-1.0.0.dist-info"]
info["METADATA"] = info["METADATA"].upper()
build_files(files, self.site_dir)
class DistInfoPkgEditable(DistInfoPkg):
"""
Package with a PEP 660 direct_url.json.
"""
some_hash = '524127ce937f7cb65665130c695abd18ca386f60bb29687efb976faa1596fdcc'
files: FilesSpec = {
'distinfo_pkg-1.0.0.dist-info': {
'direct_url.json': json.dumps({
"archive_info": {
"hash": f"sha256={some_hash}",
"hashes": {"sha256": f"{some_hash}"},
},
"url": "file:///path/to/distinfo_pkg-1.0.0.editable-py3-none-any.whl",
})
},
}
class DistInfoPkgWithDot(OnSysPath, SiteBuilder):
files: FilesSpec = {
"pkg_dot-1.0.0.dist-info": {
"METADATA": """
Name: pkg.dot
Version: 1.0.0
""",
},
}
class DistInfoPkgWithDotLegacy(OnSysPath, SiteBuilder):
files: FilesSpec = {
"pkg.dot-1.0.0.dist-info": {
"METADATA": """
Name: pkg.dot
Version: 1.0.0
""",
},
"pkg.lot.egg-info": {
"METADATA": """
Name: pkg.lot
Version: 1.0.0
""",
},
}
class DistInfoPkgOffPath(SiteBuilder):
files = DistInfoPkg.files
class EggInfoPkg(OnSysPath, SiteBuilder):
files: FilesSpec = {
"egginfo_pkg.egg-info": {
"PKG-INFO": """
Name: egginfo-pkg
Author: Steven Ma
License: Unknown
Version: 1.0.0
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries
Keywords: sample package
Description: Once upon a time
There was an egginfo package
""",
"SOURCES.txt": """
mod.py
egginfo_pkg.egg-info/top_level.txt
""",
"entry_points.txt": """
[entries]
main = mod:main
""",
"requires.txt": """
wheel >= 1.0; python_version >= "2.7"
[test]
pytest
""",
"top_level.txt": "mod\n",
},
"mod.py": """
def main():
print("hello world")
""",
}
class EggInfoPkgPipInstalledNoToplevel(OnSysPath, SiteBuilder):
files: FilesSpec = {
"egg_with_module_pkg.egg-info": {
"PKG-INFO": "Name: egg_with_module-pkg",
# SOURCES.txt is made from the source archive, and contains files
# (setup.py) that are not present after installation.
"SOURCES.txt": """
egg_with_module.py
setup.py
egg_with_module_pkg.egg-info/PKG-INFO
egg_with_module_pkg.egg-info/SOURCES.txt
egg_with_module_pkg.egg-info/top_level.txt
""",
# installed-files.txt is written by pip, and is a strictly more
# accurate source than SOURCES.txt as to the installed contents of
# the package.
"installed-files.txt": """
../egg_with_module.py
PKG-INFO
SOURCES.txt
top_level.txt
""",
# missing top_level.txt (to trigger fallback to installed-files.txt)
},
"egg_with_module.py": """
def main():
print("hello world")
""",
}
class EggInfoPkgPipInstalledExternalDataFiles(OnSysPath, SiteBuilder):
files: FilesSpec = {
"egg_with_module_pkg.egg-info": {
"PKG-INFO": "Name: egg_with_module-pkg",
# SOURCES.txt is made from the source archive, and contains files
# (setup.py) that are not present after installation.
"SOURCES.txt": """
egg_with_module.py
setup.py
egg_with_module.json
egg_with_module_pkg.egg-info/PKG-INFO
egg_with_module_pkg.egg-info/SOURCES.txt
egg_with_module_pkg.egg-info/top_level.txt
""",
# installed-files.txt is written by pip, and is a strictly more
# accurate source than SOURCES.txt as to the installed contents of
# the package.
"installed-files.txt": """
../../../etc/jupyter/jupyter_notebook_config.d/relative.json
/etc/jupyter/jupyter_notebook_config.d/absolute.json
../egg_with_module.py
PKG-INFO
SOURCES.txt
top_level.txt
""",
# missing top_level.txt (to trigger fallback to installed-files.txt)
},
"egg_with_module.py": """
def main():
print("hello world")
""",
}
class EggInfoPkgPipInstalledNoModules(OnSysPath, SiteBuilder):
files: FilesSpec = {
"egg_with_no_modules_pkg.egg-info": {
"PKG-INFO": "Name: egg_with_no_modules-pkg",
# SOURCES.txt is made from the source archive, and contains files
# (setup.py) that are not present after installation.
"SOURCES.txt": """
setup.py
egg_with_no_modules_pkg.egg-info/PKG-INFO
egg_with_no_modules_pkg.egg-info/SOURCES.txt
egg_with_no_modules_pkg.egg-info/top_level.txt
""",
# installed-files.txt is written by pip, and is a strictly more
# accurate source than SOURCES.txt as to the installed contents of
# the package.
"installed-files.txt": """
PKG-INFO
SOURCES.txt
top_level.txt
""",
# top_level.txt correctly reflects that no modules are installed
"top_level.txt": b"\n",
},
}
class EggInfoPkgSourcesFallback(OnSysPath, SiteBuilder):
files: FilesSpec = {
"sources_fallback_pkg.egg-info": {
"PKG-INFO": "Name: sources_fallback-pkg",
# SOURCES.txt is made from the source archive, and contains files
# (setup.py) that are not present after installation.
"SOURCES.txt": """
sources_fallback.py
setup.py
sources_fallback_pkg.egg-info/PKG-INFO
sources_fallback_pkg.egg-info/SOURCES.txt
""",
# missing installed-files.txt (i.e. not installed by pip) and
# missing top_level.txt (to trigger fallback to SOURCES.txt)
},
"sources_fallback.py": """
def main():
print("hello world")
""",
}
class EggInfoFile(OnSysPath, SiteBuilder):
files: FilesSpec = {
"egginfo_file.egg-info": """
Metadata-Version: 1.0
Name: egginfo_file
Version: 0.1
Summary: An example package
Home-page: www.example.com
Author: Eric Haffa-Vee
Author-email: eric@example.coms
License: UNKNOWN
Description: UNKNOWN
Platform: UNKNOWN
""",
}
# dedent all text strings before writing
orig = _path.create.registry[str]
_path.create.register(str, lambda content, path: orig(DALS(content), path))
build_files = _path.build
def build_record(file_defs):
return ''.join(f'{name},,\n' for name in record_names(file_defs))
def record_names(file_defs):
recording = _path.Recording()
_path.build(file_defs, recording)
return recording.record
class FileBuilder:
def unicode_filename(self):
return os_helper.FS_NONASCII or self.skip(
"File system does not support non-ascii."
)
def DALS(str):
"Dedent and left-strip"
return textwrap.dedent(str).lstrip()
@requires_zlib()
class ZipFixtures:
root = 'test.test_importlib.metadata.data'
def _fixture_on_path(self, filename):
pkg_file = resources.files(self.root).joinpath(filename)
file = self.resources.enter_context(resources.as_file(pkg_file))
assert file.name.startswith('example'), file.name
sys.path.insert(0, str(file))
self.resources.callback(sys.path.pop, 0)
def setUp(self):
# Add self.zip_name to the front of sys.path.
self.resources = contextlib.ExitStack()
self.addCleanup(self.resources.close)
def parameterize(*args_set):
"""Run test method with a series of parameters."""
def wrapper(func):
@functools.wraps(func)
def _inner(self):
for args in args_set:
with self.subTest(**args):
func(self, **args)
return _inner
return wrapper
+10
View File
@@ -0,0 +1,10 @@
import unittest
class fake_filesystem_unittest:
"""
Stubbed version of the pyfakefs module
"""
class TestCase(unittest.TestCase):
def setUpPyfakefs(self):
self.skipTest("pyfakefs not available")
+323
View File
@@ -0,0 +1,323 @@
import re
import textwrap
import unittest
import warnings
import importlib
import contextlib
from . import fixtures
from importlib.metadata import (
Distribution,
PackageNotFoundError,
distribution,
entry_points,
files,
metadata,
requires,
version,
)
@contextlib.contextmanager
def suppress_known_deprecation():
with warnings.catch_warnings(record=True) as ctx:
warnings.simplefilter('default', category=DeprecationWarning)
yield ctx
class APITests(
fixtures.EggInfoPkg,
fixtures.EggInfoPkgPipInstalledNoToplevel,
fixtures.EggInfoPkgPipInstalledNoModules,
fixtures.EggInfoPkgPipInstalledExternalDataFiles,
fixtures.EggInfoPkgSourcesFallback,
fixtures.DistInfoPkg,
fixtures.DistInfoPkgWithDot,
fixtures.EggInfoFile,
unittest.TestCase,
):
version_pattern = r'\d+\.\d+(\.\d)?'
def test_retrieves_version_of_self(self):
pkg_version = version('egginfo-pkg')
assert isinstance(pkg_version, str)
assert re.match(self.version_pattern, pkg_version)
def test_retrieves_version_of_distinfo_pkg(self):
pkg_version = version('distinfo-pkg')
assert isinstance(pkg_version, str)
assert re.match(self.version_pattern, pkg_version)
def test_for_name_does_not_exist(self):
with self.assertRaises(PackageNotFoundError):
distribution('does-not-exist')
def test_name_normalization(self):
names = 'pkg.dot', 'pkg_dot', 'pkg-dot', 'pkg..dot', 'Pkg.Dot'
for name in names:
with self.subTest(name):
assert distribution(name).metadata['Name'] == 'pkg.dot'
def test_prefix_not_matched(self):
prefixes = 'p', 'pkg', 'pkg.'
for prefix in prefixes:
with self.subTest(prefix):
with self.assertRaises(PackageNotFoundError):
distribution(prefix)
def test_for_top_level(self):
tests = [
('egginfo-pkg', 'mod'),
('egg_with_no_modules-pkg', ''),
]
for pkg_name, expect_content in tests:
with self.subTest(pkg_name):
self.assertEqual(
distribution(pkg_name).read_text('top_level.txt').strip(),
expect_content,
)
def test_read_text(self):
tests = [
('egginfo-pkg', 'mod\n'),
('egg_with_no_modules-pkg', '\n'),
]
for pkg_name, expect_content in tests:
with self.subTest(pkg_name):
top_level = [
path for path in files(pkg_name) if path.name == 'top_level.txt'
][0]
self.assertEqual(top_level.read_text(), expect_content)
def test_entry_points(self):
eps = entry_points()
assert 'entries' in eps.groups
entries = eps.select(group='entries')
assert 'main' in entries.names
ep = entries['main']
self.assertEqual(ep.value, 'mod:main')
self.assertEqual(ep.extras, [])
def test_entry_points_distribution(self):
entries = entry_points(group='entries')
for entry in ("main", "ns:sub"):
ep = entries[entry]
self.assertIn(ep.dist.name, ('distinfo-pkg', 'egginfo-pkg'))
self.assertEqual(ep.dist.version, "1.0.0")
def test_entry_points_unique_packages_normalized(self):
"""
Entry points should only be exposed for the first package
on sys.path with a given name (even when normalized).
"""
alt_site_dir = self.fixtures.enter_context(fixtures.tmp_path())
self.fixtures.enter_context(self.add_sys_path(alt_site_dir))
alt_pkg = {
"DistInfo_pkg-1.1.0.dist-info": {
"METADATA": """
Name: distinfo-pkg
Version: 1.1.0
""",
"entry_points.txt": """
[entries]
main = mod:altmain
""",
},
}
fixtures.build_files(alt_pkg, alt_site_dir)
entries = entry_points(group='entries')
assert not any(
ep.dist.name == 'distinfo-pkg' and ep.dist.version == '1.0.0'
for ep in entries
)
# ns:sub doesn't exist in alt_pkg
assert 'ns:sub' not in entries.names
def test_entry_points_missing_name(self):
with self.assertRaises(KeyError):
entry_points(group='entries')['missing']
def test_entry_points_missing_group(self):
assert entry_points(group='missing') == ()
def test_entry_points_allows_no_attributes(self):
ep = entry_points().select(group='entries', name='main')
with self.assertRaises(AttributeError):
ep.foo = 4
def test_metadata_for_this_package(self):
md = metadata('egginfo-pkg')
assert md['author'] == 'Steven Ma'
assert md['LICENSE'] == 'Unknown'
assert md['Name'] == 'egginfo-pkg'
classifiers = md.get_all('Classifier')
assert 'Topic :: Software Development :: Libraries' in classifiers
def test_missing_key_legacy(self):
"""
Requesting a missing key will still return None, but warn.
"""
md = metadata('distinfo-pkg')
with suppress_known_deprecation():
assert md['does-not-exist'] is None
def test_get_key(self):
"""
Getting a key gets the key.
"""
md = metadata('egginfo-pkg')
assert md.get('Name') == 'egginfo-pkg'
def test_get_missing_key(self):
"""
Requesting a missing key will return None.
"""
md = metadata('distinfo-pkg')
assert md.get('does-not-exist') is None
@staticmethod
def _test_files(files):
root = files[0].root
for file in files:
assert file.root == root
assert not file.hash or file.hash.value
assert not file.hash or file.hash.mode == 'sha256'
assert not file.size or file.size >= 0
assert file.locate().exists()
assert isinstance(file.read_binary(), bytes)
if file.name.endswith('.py'):
file.read_text()
def test_file_hash_repr(self):
util = [p for p in files('distinfo-pkg') if p.name == 'mod.py'][0]
self.assertRegex(repr(util.hash), '<FileHash mode: sha256 value: .*>')
def test_files_dist_info(self):
self._test_files(files('distinfo-pkg'))
def test_files_egg_info(self):
self._test_files(files('egginfo-pkg'))
self._test_files(files('egg_with_module-pkg'))
self._test_files(files('egg_with_no_modules-pkg'))
self._test_files(files('sources_fallback-pkg'))
def test_version_egg_info_file(self):
self.assertEqual(version('egginfo-file'), '0.1')
def test_requires_egg_info_file(self):
requirements = requires('egginfo-file')
self.assertIsNone(requirements)
def test_requires_egg_info(self):
deps = requires('egginfo-pkg')
assert len(deps) == 2
assert any(dep == 'wheel >= 1.0; python_version >= "2.7"' for dep in deps)
def test_requires_egg_info_empty(self):
fixtures.build_files(
{
'requires.txt': '',
},
self.site_dir.joinpath('egginfo_pkg.egg-info'),
)
deps = requires('egginfo-pkg')
assert deps == []
def test_requires_dist_info(self):
deps = requires('distinfo-pkg')
assert len(deps) == 2
assert all(deps)
assert 'wheel >= 1.0' in deps
assert "pytest; extra == 'test'" in deps
def test_more_complex_deps_requires_text(self):
requires = textwrap.dedent(
"""
dep1
dep2
[:python_version < "3"]
dep3
[extra1]
dep4
dep6@ git+https://example.com/python/dep.git@v1.0.0
[extra2:python_version < "3"]
dep5
"""
)
deps = sorted(Distribution._deps_from_requires_text(requires))
expected = [
'dep1',
'dep2',
'dep3; python_version < "3"',
'dep4; extra == "extra1"',
'dep5; (python_version < "3") and extra == "extra2"',
'dep6@ git+https://example.com/python/dep.git@v1.0.0 ; extra == "extra1"',
]
# It's important that the environment marker expression be
# wrapped in parentheses to avoid the following 'and' binding more
# tightly than some other part of the environment expression.
assert deps == expected
def test_as_json(self):
md = metadata('distinfo-pkg').json
assert 'name' in md
assert md['keywords'] == ['sample', 'package']
desc = md['description']
assert desc.startswith('Once upon a time\nThere was')
assert len(md['requires_dist']) == 2
def test_as_json_egg_info(self):
md = metadata('egginfo-pkg').json
assert 'name' in md
assert md['keywords'] == ['sample', 'package']
desc = md['description']
assert desc.startswith('Once upon a time\nThere was')
assert len(md['classifier']) == 2
def test_as_json_odd_case(self):
self.make_uppercase()
md = metadata('distinfo-pkg').json
assert 'name' in md
assert len(md['requires_dist']) == 2
assert md['keywords'] == ['SAMPLE', 'PACKAGE']
class LegacyDots(fixtures.DistInfoPkgWithDotLegacy, unittest.TestCase):
def test_name_normalization(self):
names = 'pkg.dot', 'pkg_dot', 'pkg-dot', 'pkg..dot', 'Pkg.Dot'
for name in names:
with self.subTest(name):
assert distribution(name).metadata['Name'] == 'pkg.dot'
def test_name_normalization_versionless_egg_info(self):
names = 'pkg.lot', 'pkg_lot', 'pkg-lot', 'pkg..lot', 'Pkg.Lot'
for name in names:
with self.subTest(name):
assert distribution(name).metadata['Name'] == 'pkg.lot'
class OffSysPathTests(fixtures.DistInfoPkgOffPath, unittest.TestCase):
def test_find_distributions_specified_path(self):
dists = Distribution.discover(path=[str(self.site_dir)])
assert any(dist.metadata['Name'] == 'distinfo-pkg' for dist in dists)
def test_distribution_at_pathlib(self):
"""Demonstrate how to load metadata direct from a directory."""
dist_info_path = self.site_dir / 'distinfo_pkg-1.0.0.dist-info'
dist = Distribution.at(dist_info_path)
assert dist.version == '1.0.0'
def test_distribution_at_str(self):
dist_info_path = self.site_dir / 'distinfo_pkg-1.0.0.dist-info'
dist = Distribution.at(str(dist_info_path))
assert dist.version == '1.0.0'
class InvalidateCache(unittest.TestCase):
def test_invalidate_cache(self):
# No externally observable behavior, but ensures test coverage...
importlib.invalidate_caches()
+468
View File
@@ -0,0 +1,468 @@
import re
import pickle
import unittest
import warnings
import importlib
import importlib.metadata
import contextlib
from test.support import os_helper
try:
import pyfakefs.fake_filesystem_unittest as ffs
except ImportError:
from .stubs import fake_filesystem_unittest as ffs
from . import fixtures
from ._context import suppress
from ._path import Symlink
from importlib.metadata import (
Distribution,
EntryPoint,
PackageNotFoundError,
_unique,
distributions,
entry_points,
metadata,
packages_distributions,
version,
)
@contextlib.contextmanager
def suppress_known_deprecation():
with warnings.catch_warnings(record=True) as ctx:
warnings.simplefilter('default', category=DeprecationWarning)
yield ctx
class BasicTests(fixtures.DistInfoPkg, unittest.TestCase):
version_pattern = r'\d+\.\d+(\.\d)?'
def test_retrieves_version_of_self(self):
dist = Distribution.from_name('distinfo-pkg')
assert isinstance(dist.version, str)
assert re.match(self.version_pattern, dist.version)
def test_for_name_does_not_exist(self):
with self.assertRaises(PackageNotFoundError):
Distribution.from_name('does-not-exist')
def test_package_not_found_mentions_metadata(self):
"""
When a package is not found, that could indicate that the
package is not installed or that it is installed without
metadata. Ensure the exception mentions metadata to help
guide users toward the cause. See #124.
"""
with self.assertRaises(PackageNotFoundError) as ctx:
Distribution.from_name('does-not-exist')
assert "metadata" in str(ctx.exception)
# expected to fail until ABC is enforced
@suppress(AssertionError)
@suppress_known_deprecation()
def test_abc_enforced(self):
with self.assertRaises(TypeError):
type('DistributionSubclass', (Distribution,), {})()
@fixtures.parameterize(
dict(name=None),
dict(name=''),
)
def test_invalid_inputs_to_from_name(self, name):
with self.assertRaises(Exception):
Distribution.from_name(name)
class ImportTests(fixtures.DistInfoPkg, unittest.TestCase):
def test_import_nonexistent_module(self):
# Ensure that the MetadataPathFinder does not crash an import of a
# non-existent module.
with self.assertRaises(ImportError):
importlib.import_module('does_not_exist')
def test_resolve(self):
ep = entry_points(group='entries')['main']
self.assertEqual(ep.load().__name__, "main")
def test_entrypoint_with_colon_in_name(self):
ep = entry_points(group='entries')['ns:sub']
self.assertEqual(ep.value, 'mod:main')
def test_resolve_without_attr(self):
ep = EntryPoint(
name='ep',
value='importlib.metadata',
group='grp',
)
assert ep.load() is importlib.metadata
class NameNormalizationTests(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase):
@staticmethod
def make_pkg(name):
"""
Create minimal metadata for a dist-info package with
the indicated name on the file system.
"""
return {
f'{name}.dist-info': {
'METADATA': 'VERSION: 1.0\n',
},
}
def test_dashes_in_dist_name_found_as_underscores(self):
"""
For a package with a dash in the name, the dist-info metadata
uses underscores in the name. Ensure the metadata loads.
"""
fixtures.build_files(self.make_pkg('my_pkg'), self.site_dir)
assert version('my-pkg') == '1.0'
def test_dist_name_found_as_any_case(self):
"""
Ensure the metadata loads when queried with any case.
"""
pkg_name = 'CherryPy'
fixtures.build_files(self.make_pkg(pkg_name), self.site_dir)
assert version(pkg_name) == '1.0'
assert version(pkg_name.lower()) == '1.0'
assert version(pkg_name.upper()) == '1.0'
def test_unique_distributions(self):
"""
Two distributions varying only by non-normalized name on
the file system should resolve as the same.
"""
fixtures.build_files(self.make_pkg('abc'), self.site_dir)
before = list(_unique(distributions()))
alt_site_dir = self.fixtures.enter_context(fixtures.tmp_path())
self.fixtures.enter_context(self.add_sys_path(alt_site_dir))
fixtures.build_files(self.make_pkg('ABC'), alt_site_dir)
after = list(_unique(distributions()))
assert len(after) == len(before)
class NonASCIITests(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase):
@staticmethod
def pkg_with_non_ascii_description(site_dir):
"""
Create minimal metadata for a package with non-ASCII in
the description.
"""
contents = {
'portend.dist-info': {
'METADATA': 'Description: pôrˈtend',
},
}
fixtures.build_files(contents, site_dir)
return 'portend'
@staticmethod
def pkg_with_non_ascii_description_egg_info(site_dir):
"""
Create minimal metadata for an egg-info package with
non-ASCII in the description.
"""
contents = {
'portend.dist-info': {
'METADATA': """
Name: portend
pôrˈtend""",
},
}
fixtures.build_files(contents, site_dir)
return 'portend'
def test_metadata_loads(self):
pkg_name = self.pkg_with_non_ascii_description(self.site_dir)
meta = metadata(pkg_name)
assert meta['Description'] == 'pôrˈtend'
def test_metadata_loads_egg_info(self):
pkg_name = self.pkg_with_non_ascii_description_egg_info(self.site_dir)
meta = metadata(pkg_name)
assert meta['Description'] == 'pôrˈtend'
class DiscoveryTests(
fixtures.EggInfoPkg,
fixtures.EggInfoPkgPipInstalledNoToplevel,
fixtures.EggInfoPkgPipInstalledNoModules,
fixtures.EggInfoPkgSourcesFallback,
fixtures.DistInfoPkg,
unittest.TestCase,
):
def test_package_discovery(self):
dists = list(distributions())
assert all(isinstance(dist, Distribution) for dist in dists)
assert any(dist.metadata['Name'] == 'egginfo-pkg' for dist in dists)
assert any(dist.metadata['Name'] == 'egg_with_module-pkg' for dist in dists)
assert any(dist.metadata['Name'] == 'egg_with_no_modules-pkg' for dist in dists)
assert any(dist.metadata['Name'] == 'sources_fallback-pkg' for dist in dists)
assert any(dist.metadata['Name'] == 'distinfo-pkg' for dist in dists)
def test_invalid_usage(self):
with self.assertRaises(ValueError):
list(distributions(context='something', name='else'))
def test_interleaved_discovery(self):
"""
Ensure interleaved searches are safe.
When the search is cached, it is possible for searches to be
interleaved, so make sure those use-cases are safe.
Ref #293
"""
dists = distributions()
next(dists)
version('egginfo-pkg')
next(dists)
class DirectoryTest(fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase):
def test_egg_info(self):
# make an `EGG-INFO` directory that's unrelated
self.site_dir.joinpath('EGG-INFO').mkdir()
# used to crash with `IsADirectoryError`
with self.assertRaises(PackageNotFoundError):
version('unknown-package')
def test_egg(self):
egg = self.site_dir.joinpath('foo-3.6.egg')
egg.mkdir()
with self.add_sys_path(egg):
with self.assertRaises(PackageNotFoundError):
version('foo')
class MissingSysPath(fixtures.OnSysPath, unittest.TestCase):
site_dir = '/does-not-exist'
def test_discovery(self):
"""
Discovering distributions should succeed even if
there is an invalid path on sys.path.
"""
importlib.metadata.distributions()
class InaccessibleSysPath(fixtures.OnSysPath, ffs.TestCase):
site_dir = '/access-denied'
def setUp(self):
super().setUp()
self.setUpPyfakefs()
self.fs.create_dir(self.site_dir, perm_bits=000)
def test_discovery(self):
"""
Discovering distributions should succeed even if
there is an invalid path on sys.path.
"""
list(importlib.metadata.distributions())
class TestEntryPoints(unittest.TestCase):
def __init__(self, *args):
super().__init__(*args)
self.ep = importlib.metadata.EntryPoint(
name='name', value='value', group='group'
)
def test_entry_point_pickleable(self):
revived = pickle.loads(pickle.dumps(self.ep))
assert revived == self.ep
def test_positional_args(self):
"""
Capture legacy (namedtuple) construction, discouraged.
"""
EntryPoint('name', 'value', 'group')
def test_immutable(self):
"""EntryPoints should be immutable"""
with self.assertRaises(AttributeError):
self.ep.name = 'badactor'
def test_repr(self):
assert 'EntryPoint' in repr(self.ep)
assert 'name=' in repr(self.ep)
assert "'name'" in repr(self.ep)
def test_hashable(self):
"""EntryPoints should be hashable"""
hash(self.ep)
def test_module(self):
assert self.ep.module == 'value'
def test_attr(self):
assert self.ep.attr is None
def test_sortable(self):
"""
EntryPoint objects are sortable, but result is undefined.
"""
sorted([
EntryPoint(name='b', value='val', group='group'),
EntryPoint(name='a', value='val', group='group'),
])
class FileSystem(
fixtures.OnSysPath, fixtures.SiteDir, fixtures.FileBuilder, unittest.TestCase
):
def test_unicode_dir_on_sys_path(self):
"""
Ensure a Unicode subdirectory of a directory on sys.path
does not crash.
"""
fixtures.build_files(
{self.unicode_filename(): {}},
prefix=self.site_dir,
)
list(distributions())
class PackagesDistributionsPrebuiltTest(fixtures.ZipFixtures, unittest.TestCase):
def test_packages_distributions_example(self):
self._fixture_on_path('example-21.12-py3-none-any.whl')
assert packages_distributions()['example'] == ['example']
def test_packages_distributions_example2(self):
"""
Test packages_distributions on a wheel built
by trampolim.
"""
self._fixture_on_path('example2-1.0.0-py3-none-any.whl')
assert packages_distributions()['example2'] == ['example2']
class PackagesDistributionsTest(
fixtures.OnSysPath, fixtures.SiteDir, unittest.TestCase
):
def test_packages_distributions_neither_toplevel_nor_files(self):
"""
Test a package built without 'top-level.txt' or a file list.
"""
fixtures.build_files(
{
'trim_example-1.0.0.dist-info': {
'METADATA': """
Name: trim_example
Version: 1.0.0
""",
}
},
prefix=self.site_dir,
)
packages_distributions()
def test_packages_distributions_all_module_types(self):
"""
Test top-level modules detected on a package without 'top-level.txt'.
"""
suffixes = importlib.machinery.all_suffixes()
metadata = dict(
METADATA="""
Name: all_distributions
Version: 1.0.0
""",
)
files = {
'all_distributions-1.0.0.dist-info': metadata,
}
for i, suffix in enumerate(suffixes):
files.update({
f'importable-name {i}{suffix}': '',
f'in_namespace_{i}': {
f'mod{suffix}': '',
},
f'in_package_{i}': {
'__init__.py': '',
f'mod{suffix}': '',
},
})
metadata.update(RECORD=fixtures.build_record(files))
fixtures.build_files(files, prefix=self.site_dir)
distributions = packages_distributions()
for i in range(len(suffixes)):
assert distributions[f'importable-name {i}'] == ['all_distributions']
assert distributions[f'in_namespace_{i}'] == ['all_distributions']
assert distributions[f'in_package_{i}'] == ['all_distributions']
assert not any(name.endswith('.dist-info') for name in distributions)
@os_helper.skip_unless_symlink
def test_packages_distributions_symlinked_top_level(self) -> None:
"""
Distribution is resolvable from a simple top-level symlink in RECORD.
See #452.
"""
files: fixtures.FilesSpec = {
"symlinked_pkg-1.0.0.dist-info": {
"METADATA": """
Name: symlinked-pkg
Version: 1.0.0
""",
"RECORD": "symlinked,,\n",
},
".symlink.target": {},
"symlinked": Symlink(".symlink.target"),
}
fixtures.build_files(files, self.site_dir)
assert packages_distributions()['symlinked'] == ['symlinked-pkg']
class PackagesDistributionsEggTest(
fixtures.EggInfoPkg,
fixtures.EggInfoPkgPipInstalledNoToplevel,
fixtures.EggInfoPkgPipInstalledNoModules,
fixtures.EggInfoPkgSourcesFallback,
unittest.TestCase,
):
def test_packages_distributions_on_eggs(self):
"""
Test old-style egg packages with a variation of 'top_level.txt',
'SOURCES.txt', and 'installed-files.txt', available.
"""
distributions = packages_distributions()
def import_names_from_package(package_name):
return {
import_name
for import_name, package_names in distributions.items()
if package_name in package_names
}
# egginfo-pkg declares one import ('mod') via top_level.txt
assert import_names_from_package('egginfo-pkg') == {'mod'}
# egg_with_module-pkg has one import ('egg_with_module') inferred from
# installed-files.txt (top_level.txt is missing)
assert import_names_from_package('egg_with_module-pkg') == {'egg_with_module'}
# egg_with_no_modules-pkg should not be associated with any import names
# (top_level.txt is empty, and installed-files.txt has no .py files)
assert import_names_from_package('egg_with_no_modules-pkg') == set()
# sources_fallback-pkg has one import ('sources_fallback') inferred from
# SOURCES.txt (top_level.txt and installed-files.txt is missing)
assert import_names_from_package('sources_fallback-pkg') == {'sources_fallback'}
class EditableDistributionTest(fixtures.DistInfoPkgEditable, unittest.TestCase):
def test_origin(self):
dist = Distribution.from_name('distinfo-pkg')
assert dist.origin.url.endswith('.whl')
assert dist.origin.archive_info.hashes.sha256
+62
View File
@@ -0,0 +1,62 @@
import sys
import unittest
from . import fixtures
from importlib.metadata import (
PackageNotFoundError,
distribution,
distributions,
entry_points,
files,
version,
)
class TestZip(fixtures.ZipFixtures, unittest.TestCase):
def setUp(self):
super().setUp()
self._fixture_on_path('example-21.12-py3-none-any.whl')
def test_zip_version(self):
self.assertEqual(version('example'), '21.12')
def test_zip_version_does_not_match(self):
with self.assertRaises(PackageNotFoundError):
version('definitely-not-installed')
def test_zip_entry_points(self):
scripts = entry_points(group='console_scripts')
entry_point = scripts['example']
self.assertEqual(entry_point.value, 'example:main')
entry_point = scripts['Example']
self.assertEqual(entry_point.value, 'example:main')
def test_missing_metadata(self):
self.assertIsNone(distribution('example').read_text('does not exist'))
def test_case_insensitive(self):
self.assertEqual(version('Example'), '21.12')
def test_files(self):
for file in files('example'):
path = str(file.dist.locate_file(file))
assert '.whl/' in path, path
def test_one_distribution(self):
dists = list(distributions(path=sys.path[:1]))
assert len(dists) == 1
class TestEgg(TestZip):
def setUp(self):
super().setUp()
self._fixture_on_path('example-21.12-py3.6.egg')
def test_files(self):
for file in files('example'):
path = str(file.dist.locate_file(file))
assert '.egg/' in path, path
def test_normalized_name(self):
dist = distribution('example')
assert dist._normalized_name == 'example'
@@ -0,0 +1 @@
+1
View File
@@ -0,0 +1 @@
+45 -7
View File
@@ -2,15 +2,44 @@ import pathlib
import functools
from typing import Dict, Union
from typing import runtime_checkable
from typing import Protocol
####
# from jaraco.path 3.4.1
FilesSpec = Dict[str, Union[str, bytes, 'FilesSpec']] # type: ignore
# from jaraco.path 3.7.1
def build(spec: FilesSpec, prefix=pathlib.Path()):
class Symlink(str):
"""
A string indicating the target of a symlink.
"""
FilesSpec = Dict[str, Union[str, bytes, Symlink, 'FilesSpec']]
@runtime_checkable
class TreeMaker(Protocol):
def __truediv__(self, *args, **kwargs): ... # pragma: no cover
def mkdir(self, **kwargs): ... # pragma: no cover
def write_text(self, content, **kwargs): ... # pragma: no cover
def write_bytes(self, content): ... # pragma: no cover
def symlink_to(self, target): ... # pragma: no cover
def _ensure_tree_maker(obj: Union[str, TreeMaker]) -> TreeMaker:
return obj if isinstance(obj, TreeMaker) else pathlib.Path(obj) # type: ignore[return-value]
def build(
spec: FilesSpec,
prefix: Union[str, TreeMaker] = pathlib.Path(), # type: ignore[assignment]
):
"""
Build a set of files/directories, as described by the spec.
@@ -25,21 +54,25 @@ def build(spec: FilesSpec, prefix=pathlib.Path()):
... "__init__.py": "",
... },
... "baz.py": "# Some code",
... }
... "bar.py": Symlink("baz.py"),
... },
... "bing": Symlink("foo"),
... }
>>> target = getfixture('tmp_path')
>>> build(spec, target)
>>> target.joinpath('foo/baz.py').read_text(encoding='utf-8')
'# Some code'
>>> target.joinpath('bing/bar.py').read_text(encoding='utf-8')
'# Some code'
"""
for name, contents in spec.items():
create(contents, pathlib.Path(prefix) / name)
create(contents, _ensure_tree_maker(prefix) / name)
@functools.singledispatch
def create(content: Union[str, bytes, FilesSpec], path):
path.mkdir(exist_ok=True)
build(content, prefix=path) # type: ignore
build(content, prefix=path) # type: ignore[arg-type]
@create.register
@@ -52,5 +85,10 @@ def _(content: str, path):
path.write_text(content, encoding='utf-8')
@create.register
def _(content: Symlink, path):
path.symlink_to(content)
# end from jaraco.path
####
+6 -11
View File
@@ -1,7 +1,6 @@
import unittest
from importlib import resources
from . import data01
from . import util
@@ -19,25 +18,21 @@ class ContentsTests:
assert self.expected <= contents
class ContentsDiskTests(ContentsTests, unittest.TestCase):
def setUp(self):
self.data = data01
class ContentsDiskTests(ContentsTests, util.DiskSetup, unittest.TestCase):
pass
class ContentsZipTests(ContentsTests, util.ZipSetup, unittest.TestCase):
pass
class ContentsNamespaceTests(ContentsTests, unittest.TestCase):
class ContentsNamespaceTests(ContentsTests, util.DiskSetup, unittest.TestCase):
MODULE = 'namespacedata01'
expected = {
# no __init__ because of namespace design
# no subdirectory as incidental difference in fixture
'binary.file',
'subdirectory',
'utf-16.file',
'utf-8.file',
}
def setUp(self):
from . import namespacedata01
self.data = namespacedata01
+4 -2
View File
@@ -5,6 +5,7 @@ import pathlib
from test.support import os_helper
from importlib import resources
from importlib.resources import abc
from importlib.resources.abc import TraversableResources, ResourceReader
from . import util
@@ -39,8 +40,9 @@ class CustomTraversableResourcesTests(unittest.TestCase):
self.addCleanup(self.fixtures.close)
def test_custom_loader(self):
temp_dir = self.fixtures.enter_context(os_helper.temp_dir())
temp_dir = pathlib.Path(self.fixtures.enter_context(os_helper.temp_dir()))
loader = SimpleLoader(MagicResources(temp_dir))
pkg = util.create_package_from_loader(loader)
files = resources.files(pkg)
assert files is temp_dir
assert isinstance(files, abc.Traversable)
assert list(files.iterdir()) == []
+127 -49
View File
@@ -1,4 +1,5 @@
import typing
import pathlib
import py_compile
import textwrap
import unittest
import warnings
@@ -7,11 +8,8 @@ import contextlib
from importlib import resources
from importlib.resources.abc import Traversable
from . import data01
from . import util
from . import _path
from test.support import os_helper
from test.support import import_helper
from test.support import os_helper, import_helper
@contextlib.contextmanager
@@ -32,13 +30,14 @@ class FilesTests:
actual = files.joinpath('utf-8.file').read_text(encoding='utf-8')
assert actual == 'Hello, UTF-8 world!\n'
@unittest.skipUnless(
hasattr(typing, 'runtime_checkable'),
"Only suitable when typing supports runtime_checkable",
)
def test_traversable(self):
assert isinstance(resources.files(self.data), Traversable)
def test_joinpath_with_multiple_args(self):
files = resources.files(self.data)
binfile = files.joinpath('subdirectory', 'binary.file')
self.assertTrue(binfile.is_file())
def test_old_parameter(self):
"""
Files used to take a 'package' parameter. Make sure anyone
@@ -48,74 +47,153 @@ class FilesTests:
resources.files(package=self.data)
class OpenDiskTests(FilesTests, unittest.TestCase):
def setUp(self):
self.data = data01
class OpenDiskTests(FilesTests, util.DiskSetup, unittest.TestCase):
pass
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON, line ending issue")
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; line ending issue")
def test_read_bytes(self):
super().test_read_bytes()
return super().test_read_bytes()
class OpenZipTests(FilesTests, util.ZipSetup, unittest.TestCase):
pass
class OpenNamespaceTests(FilesTests, unittest.TestCase):
def setUp(self):
from . import namespacedata01
class OpenNamespaceTests(FilesTests, util.DiskSetup, unittest.TestCase):
MODULE = 'namespacedata01'
self.data = namespacedata01
def test_non_paths_in_dunder_path(self):
"""
Non-path items in a namespace package's ``__path__`` are ignored.
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON, line ending issue")
As reported in python/importlib_resources#311, some tools
like Setuptools, when creating editable packages, will inject
non-paths into a namespace package's ``__path__``, a
sentinel like
``__editable__.sample_namespace-1.0.finder.__path_hook__``
to cause the ``PathEntryFinder`` to be called when searching
for packages. In that case, resources should still be loadable.
"""
import namespacedata01
namespacedata01.__path__.append(
'__editable__.sample_namespace-1.0.finder.__path_hook__'
)
resources.files(namespacedata01)
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON; line ending issue")
def test_read_bytes(self):
super().test_read_bytes()
return super().test_read_bytes()
class SiteDir:
def setUp(self):
self.fixtures = contextlib.ExitStack()
self.addCleanup(self.fixtures.close)
self.site_dir = self.fixtures.enter_context(os_helper.temp_dir())
self.fixtures.enter_context(import_helper.DirsOnSysPath(self.site_dir))
self.fixtures.enter_context(import_helper.CleanImport())
class OpenNamespaceZipTests(FilesTests, util.ZipSetup, unittest.TestCase):
ZIP_MODULE = 'namespacedata01'
class ModulesFilesTests(SiteDir, unittest.TestCase):
class DirectSpec:
"""
Override behavior of ModuleSetup to write a full spec directly.
"""
MODULE = 'unused'
def load_fixture(self, name):
self.tree_on_path(self.spec)
class ModulesFiles:
spec = {
'mod.py': '',
'res.txt': 'resources are the best',
}
def test_module_resources(self):
"""
A module can have resources found adjacent to the module.
"""
spec = {
'mod.py': '',
'res.txt': 'resources are the best',
}
_path.build(spec, self.site_dir)
import mod
import mod # type: ignore[import-not-found]
actual = resources.files(mod).joinpath('res.txt').read_text(encoding='utf-8')
assert actual == spec['res.txt']
assert actual == self.spec['res.txt']
class ImplicitContextFilesTests(SiteDir, unittest.TestCase):
def test_implicit_files(self):
class ModuleFilesDiskTests(DirectSpec, util.DiskSetup, ModulesFiles, unittest.TestCase):
pass
class ModuleFilesZipTests(DirectSpec, util.ZipSetup, ModulesFiles, unittest.TestCase):
pass
class ImplicitContextFiles:
set_val = textwrap.dedent(
f"""
import {resources.__name__} as res
val = res.files().joinpath('res.txt').read_text(encoding='utf-8')
"""
)
spec = {
'somepkg': {
'__init__.py': set_val,
'submod.py': set_val,
'res.txt': 'resources are the best',
},
'frozenpkg': {
'__init__.py': set_val.replace(resources.__name__, 'c_resources'),
'res.txt': 'resources are the best',
},
}
def test_implicit_files_package(self):
"""
Without any parameter, files() will infer the location as the caller.
"""
spec = {
'somepkg': {
'__init__.py': textwrap.dedent(
"""
import importlib.resources as res
val = res.files().joinpath('res.txt').read_text(encoding='utf-8')
"""
),
'res.txt': 'resources are the best',
},
}
_path.build(spec, self.site_dir)
assert importlib.import_module('somepkg').val == 'resources are the best'
def test_implicit_files_submodule(self):
"""
Without any parameter, files() will infer the location as the caller.
"""
assert importlib.import_module('somepkg.submod').val == 'resources are the best'
def _compile_importlib(self):
"""
Make a compiled-only copy of the importlib resources package.
Currently only code is copied, as importlib resources doesn't itself
have any resources.
"""
bin_site = self.fixtures.enter_context(os_helper.temp_dir())
c_resources = pathlib.Path(bin_site, 'c_resources')
sources = pathlib.Path(resources.__file__).parent
for source_path in sources.glob('**/*.py'):
c_path = c_resources.joinpath(source_path.relative_to(sources)).with_suffix('.pyc')
py_compile.compile(source_path, c_path)
self.fixtures.enter_context(import_helper.DirsOnSysPath(bin_site))
def test_implicit_files_with_compiled_importlib(self):
"""
Caller detection works for compiled-only resources module.
python/cpython#123085
"""
self._compile_importlib()
assert importlib.import_module('frozenpkg').val == 'resources are the best'
class ImplicitContextFilesDiskTests(
DirectSpec, util.DiskSetup, ImplicitContextFiles, unittest.TestCase
):
pass
class ImplicitContextFilesZipTests(
DirectSpec, util.ZipSetup, ImplicitContextFiles, unittest.TestCase
):
pass
if __name__ == '__main__':
unittest.main()
+249
View File
@@ -0,0 +1,249 @@
import unittest
import os
import importlib
from test.support import warnings_helper
from importlib import resources
from . import util
# Since the functional API forwards to Traversable, we only test
# filesystem resources here -- not zip files, namespace packages etc.
# We do test for two kinds of Anchor, though.
class StringAnchorMixin:
anchor01 = 'data01'
anchor02 = 'data02'
class ModuleAnchorMixin:
@property
def anchor01(self):
return importlib.import_module('data01')
@property
def anchor02(self):
return importlib.import_module('data02')
class FunctionalAPIBase(util.DiskSetup):
def setUp(self):
super().setUp()
self.load_fixture('data02')
def _gen_resourcetxt_path_parts(self):
"""Yield various names of a text file in anchor02, each in a subTest"""
for path_parts in (
('subdirectory', 'subsubdir', 'resource.txt'),
('subdirectory/subsubdir/resource.txt',),
('subdirectory/subsubdir', 'resource.txt'),
):
with self.subTest(path_parts=path_parts):
yield path_parts
def test_read_text(self):
self.assertEqual(
resources.read_text(self.anchor01, 'utf-8.file'),
'Hello, UTF-8 world!\n',
)
self.assertEqual(
resources.read_text(
self.anchor02,
'subdirectory',
'subsubdir',
'resource.txt',
encoding='utf-8',
),
'a resource',
)
for path_parts in self._gen_resourcetxt_path_parts():
self.assertEqual(
resources.read_text(
self.anchor02,
*path_parts,
encoding='utf-8',
),
'a resource',
)
# Use generic OSError, since e.g. attempting to read a directory can
# fail with PermissionError rather than IsADirectoryError
with self.assertRaises(OSError):
resources.read_text(self.anchor01)
with self.assertRaises(OSError):
resources.read_text(self.anchor01, 'no-such-file')
with self.assertRaises(UnicodeDecodeError):
resources.read_text(self.anchor01, 'utf-16.file')
self.assertEqual(
resources.read_text(
self.anchor01,
'binary.file',
encoding='latin1',
),
'\x00\x01\x02\x03',
)
self.assertEndsWith( # ignore the BOM
resources.read_text(
self.anchor01,
'utf-16.file',
errors='backslashreplace',
),
'Hello, UTF-16 world!\n'.encode('utf-16-le').decode(
errors='backslashreplace',
),
)
def test_read_binary(self):
self.assertEqual(
resources.read_binary(self.anchor01, 'utf-8.file'),
b'Hello, UTF-8 world!\n',
)
for path_parts in self._gen_resourcetxt_path_parts():
self.assertEqual(
resources.read_binary(self.anchor02, *path_parts),
b'a resource',
)
def test_open_text(self):
with resources.open_text(self.anchor01, 'utf-8.file') as f:
self.assertEqual(f.read(), 'Hello, UTF-8 world!\n')
for path_parts in self._gen_resourcetxt_path_parts():
with resources.open_text(
self.anchor02,
*path_parts,
encoding='utf-8',
) as f:
self.assertEqual(f.read(), 'a resource')
# Use generic OSError, since e.g. attempting to read a directory can
# fail with PermissionError rather than IsADirectoryError
with self.assertRaises(OSError):
resources.open_text(self.anchor01)
with self.assertRaises(OSError):
resources.open_text(self.anchor01, 'no-such-file')
with resources.open_text(self.anchor01, 'utf-16.file') as f:
with self.assertRaises(UnicodeDecodeError):
f.read()
with resources.open_text(
self.anchor01,
'binary.file',
encoding='latin1',
) as f:
self.assertEqual(f.read(), '\x00\x01\x02\x03')
with resources.open_text(
self.anchor01,
'utf-16.file',
errors='backslashreplace',
) as f:
self.assertEndsWith( # ignore the BOM
f.read(),
'Hello, UTF-16 world!\n'.encode('utf-16-le').decode(
errors='backslashreplace',
),
)
def test_open_binary(self):
with resources.open_binary(self.anchor01, 'utf-8.file') as f:
self.assertEqual(f.read(), b'Hello, UTF-8 world!\n')
for path_parts in self._gen_resourcetxt_path_parts():
with resources.open_binary(
self.anchor02,
*path_parts,
) as f:
self.assertEqual(f.read(), b'a resource')
def test_path(self):
with resources.path(self.anchor01, 'utf-8.file') as path:
with open(str(path), encoding='utf-8') as f:
self.assertEqual(f.read(), 'Hello, UTF-8 world!\n')
with resources.path(self.anchor01) as path:
with open(os.path.join(path, 'utf-8.file'), encoding='utf-8') as f:
self.assertEqual(f.read(), 'Hello, UTF-8 world!\n')
def test_is_resource(self):
is_resource = resources.is_resource
self.assertTrue(is_resource(self.anchor01, 'utf-8.file'))
self.assertFalse(is_resource(self.anchor01, 'no_such_file'))
self.assertFalse(is_resource(self.anchor01))
self.assertFalse(is_resource(self.anchor01, 'subdirectory'))
for path_parts in self._gen_resourcetxt_path_parts():
self.assertTrue(is_resource(self.anchor02, *path_parts))
def test_contents(self):
with warnings_helper.check_warnings((".*contents.*", DeprecationWarning)):
c = resources.contents(self.anchor01)
self.assertGreaterEqual(
set(c),
{'utf-8.file', 'utf-16.file', 'binary.file', 'subdirectory'},
)
with self.assertRaises(OSError), warnings_helper.check_warnings((
".*contents.*",
DeprecationWarning,
)):
list(resources.contents(self.anchor01, 'utf-8.file'))
for path_parts in self._gen_resourcetxt_path_parts():
with self.assertRaises(OSError), warnings_helper.check_warnings((
".*contents.*",
DeprecationWarning,
)):
list(resources.contents(self.anchor01, *path_parts))
with warnings_helper.check_warnings((".*contents.*", DeprecationWarning)):
c = resources.contents(self.anchor01, 'subdirectory')
self.assertGreaterEqual(
set(c),
{'binary.file'},
)
@warnings_helper.ignore_warnings(category=DeprecationWarning)
def test_common_errors(self):
for func in (
resources.read_text,
resources.read_binary,
resources.open_text,
resources.open_binary,
resources.path,
resources.is_resource,
resources.contents,
):
with self.subTest(func=func):
# Rejecting None anchor
with self.assertRaises(TypeError):
func(None)
# Rejecting invalid anchor type
with self.assertRaises((TypeError, AttributeError)):
func(1234)
# Unknown module
with self.assertRaises(ModuleNotFoundError):
func('$missing module$')
def test_text_errors(self):
for func in (
resources.read_text,
resources.open_text,
):
with self.subTest(func=func):
# Multiple path arguments need explicit encoding argument.
with self.assertRaises(TypeError):
func(
self.anchor02,
'subdirectory',
'subsubdir',
'resource.txt',
)
class FunctionalAPITest_StringAnchor(
StringAnchorMixin,
FunctionalAPIBase,
unittest.TestCase,
):
pass
class FunctionalAPITest_ModuleAnchor(
ModuleAnchorMixin,
FunctionalAPIBase,
unittest.TestCase,
):
pass
+9 -10
View File
@@ -1,7 +1,6 @@
import unittest
from importlib import resources
from . import data01
from . import util
@@ -24,7 +23,7 @@ class OpenTests:
target = resources.files(self.data) / 'binary.file'
with target.open('rb') as fp:
result = fp.read()
self.assertEqual(result, b'\x00\x01\x02\x03')
self.assertEqual(result, bytes(range(4)))
def test_open_text_default_encoding(self):
target = resources.files(self.data) / 'utf-8.file'
@@ -65,21 +64,21 @@ class OpenTests:
target.open(encoding='utf-8')
class OpenDiskTests(OpenTests, unittest.TestCase):
def setUp(self):
self.data = data01
class OpenDiskTests(OpenTests, util.DiskSetup, unittest.TestCase):
pass
class OpenDiskNamespaceTests(OpenTests, unittest.TestCase):
def setUp(self):
from . import namespacedata01
self.data = namespacedata01
class OpenDiskNamespaceTests(OpenTests, util.DiskSetup, unittest.TestCase):
MODULE = 'namespacedata01'
class OpenZipTests(OpenTests, util.ZipSetup, unittest.TestCase):
pass
class OpenNamespaceZipTests(OpenTests, util.ZipSetup, unittest.TestCase):
MODULE = 'namespacedata01'
if __name__ == '__main__':
unittest.main()
+6 -13
View File
@@ -1,8 +1,8 @@
import io
import pathlib
import unittest
from importlib import resources
from . import data01
from . import util
@@ -15,23 +15,16 @@ class CommonTests(util.CommonTests, unittest.TestCase):
class PathTests:
def test_reading(self):
"""
Path should be readable.
Test also implicitly verifies the returned object is a pathlib.Path
instance.
Path should be readable and a pathlib.Path instance.
"""
target = resources.files(self.data) / 'utf-8.file'
with resources.as_file(target) as path:
self.assertTrue(path.name.endswith("utf-8.file"), repr(path))
# pathlib.Path.read_text() was introduced in Python 3.5.
with path.open('r', encoding='utf-8') as file:
text = file.read()
self.assertEqual('Hello, UTF-8 world!\n', text)
self.assertIsInstance(path, pathlib.Path)
self.assertEndsWith(path.name, "utf-8.file")
self.assertEqual('Hello, UTF-8 world!\n', path.read_text(encoding='utf-8'))
class PathDiskTests(PathTests, unittest.TestCase):
data = data01
class PathDiskTests(PathTests, util.DiskSetup, unittest.TestCase):
def test_natural_path(self):
# Guarantee the internal implementation detail that
# file-system-backed resources do not get the tempdir
+27 -15
View File
@@ -1,7 +1,7 @@
import unittest
from importlib import import_module, resources
from . import data01
from . import util
@@ -18,7 +18,7 @@ class CommonTextTests(util.CommonTests, unittest.TestCase):
class ReadTests:
def test_read_bytes(self):
result = resources.files(self.data).joinpath('binary.file').read_bytes()
self.assertEqual(result, b'\0\1\2\3')
self.assertEqual(result, bytes(range(4)))
def test_read_text_default_encoding(self):
result = (
@@ -51,30 +51,42 @@ class ReadTests:
)
class ReadDiskTests(ReadTests, unittest.TestCase):
data = data01
class ReadDiskTests(ReadTests, util.DiskSetup, unittest.TestCase):
pass
class ReadZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
def test_read_submodule_resource(self):
submodule = import_module('ziptestdata.subdirectory')
submodule = import_module('data01.subdirectory')
result = resources.files(submodule).joinpath('binary.file').read_bytes()
self.assertEqual(result, b'\0\1\2\3')
self.assertEqual(result, bytes(range(4, 8)))
def test_read_submodule_resource_by_name(self):
result = (
resources.files('ziptestdata.subdirectory')
resources.files('data01.subdirectory').joinpath('binary.file').read_bytes()
)
self.assertEqual(result, bytes(range(4, 8)))
class ReadNamespaceTests(ReadTests, util.DiskSetup, unittest.TestCase):
MODULE = 'namespacedata01'
class ReadNamespaceZipTests(ReadTests, util.ZipSetup, unittest.TestCase):
MODULE = 'namespacedata01'
def test_read_submodule_resource(self):
submodule = import_module('namespacedata01.subdirectory')
result = resources.files(submodule).joinpath('binary.file').read_bytes()
self.assertEqual(result, bytes(range(12, 16)))
def test_read_submodule_resource_by_name(self):
result = (
resources.files('namespacedata01.subdirectory')
.joinpath('binary.file')
.read_bytes()
)
self.assertEqual(result, b'\0\1\2\3')
class ReadNamespaceTests(ReadTests, unittest.TestCase):
def setUp(self):
from . import namespacedata01
self.data = namespacedata01
self.assertEqual(result, bytes(range(12, 16)))
if __name__ == '__main__':
+28 -35
View File
@@ -1,17 +1,21 @@
import os.path
import sys
import pathlib
import unittest
from importlib import import_module
from importlib.readers import MultiplexedPath, NamespaceReader
from . import util
class MultiplexedPathTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
path = pathlib.Path(__file__).parent / 'namespacedata01'
cls.folder = str(path)
class MultiplexedPathTest(util.DiskSetup, unittest.TestCase):
MODULE = 'namespacedata01'
def setUp(self):
super().setUp()
self.folder = pathlib.Path(self.data.__path__[0])
self.data01 = pathlib.Path(self.load_fixture('data01').__file__).parent
self.data02 = pathlib.Path(self.load_fixture('data02').__file__).parent
def test_init_no_paths(self):
with self.assertRaises(FileNotFoundError):
@@ -19,7 +23,7 @@ class MultiplexedPathTest(unittest.TestCase):
def test_init_file(self):
with self.assertRaises(NotADirectoryError):
MultiplexedPath(os.path.join(self.folder, 'binary.file'))
MultiplexedPath(self.folder / 'binary.file')
def test_iterdir(self):
contents = {path.name for path in MultiplexedPath(self.folder).iterdir()}
@@ -27,12 +31,13 @@ class MultiplexedPathTest(unittest.TestCase):
contents.remove('__pycache__')
except (KeyError, ValueError):
pass
self.assertEqual(contents, {'binary.file', 'utf-16.file', 'utf-8.file'})
self.assertEqual(
contents, {'subdirectory', 'binary.file', 'utf-16.file', 'utf-8.file'}
)
def test_iterdir_duplicate(self):
data01 = os.path.abspath(os.path.join(__file__, '..', 'data01'))
contents = {
path.name for path in MultiplexedPath(self.folder, data01).iterdir()
path.name for path in MultiplexedPath(self.folder, self.data01).iterdir()
}
for remove in ('__pycache__', '__init__.pyc'):
try:
@@ -60,17 +65,16 @@ class MultiplexedPathTest(unittest.TestCase):
path.open()
def test_join_path(self):
prefix = os.path.abspath(os.path.join(__file__, '..'))
data01 = os.path.join(prefix, 'data01')
path = MultiplexedPath(self.folder, data01)
prefix = str(self.folder.parent)
path = MultiplexedPath(self.folder, self.data01)
self.assertEqual(
str(path.joinpath('binary.file'))[len(prefix) + 1 :],
os.path.join('namespacedata01', 'binary.file'),
)
self.assertEqual(
str(path.joinpath('subdirectory'))[len(prefix) + 1 :],
os.path.join('data01', 'subdirectory'),
)
sub = path.joinpath('subdirectory')
assert isinstance(sub, MultiplexedPath)
assert 'namespacedata01' in str(sub)
assert 'data01' in str(sub)
self.assertEqual(
str(path.joinpath('imaginary'))[len(prefix) + 1 :],
os.path.join('namespacedata01', 'imaginary'),
@@ -82,10 +86,8 @@ class MultiplexedPathTest(unittest.TestCase):
assert not path.joinpath('imaginary/foo.py').exists()
def test_join_path_common_subdir(self):
prefix = os.path.abspath(os.path.join(__file__, '..'))
data01 = os.path.join(prefix, 'data01')
data02 = os.path.join(prefix, 'data02')
path = MultiplexedPath(data01, data02)
prefix = str(self.data02.parent)
path = MultiplexedPath(self.data01, self.data02)
self.assertIsInstance(path.joinpath('subdirectory'), MultiplexedPath)
self.assertEqual(
str(path.joinpath('subdirectory', 'subsubdir'))[len(prefix) + 1 :],
@@ -105,16 +107,8 @@ class MultiplexedPathTest(unittest.TestCase):
)
class NamespaceReaderTest(unittest.TestCase):
site_dir = str(pathlib.Path(__file__).parent)
@classmethod
def setUpClass(cls):
sys.path.append(cls.site_dir)
@classmethod
def tearDownClass(cls):
sys.path.remove(cls.site_dir)
class NamespaceReaderTest(util.DiskSetup, unittest.TestCase):
MODULE = 'namespacedata01'
def test_init_error(self):
with self.assertRaises(ValueError):
@@ -124,7 +118,7 @@ class NamespaceReaderTest(unittest.TestCase):
namespacedata01 = import_module('namespacedata01')
reader = NamespaceReader(namespacedata01.__spec__.submodule_search_locations)
root = os.path.abspath(os.path.join(__file__, '..', 'namespacedata01'))
root = self.data.__path__[0]
self.assertEqual(
reader.resource_path('binary.file'), os.path.join(root, 'binary.file')
)
@@ -133,9 +127,8 @@ class NamespaceReaderTest(unittest.TestCase):
)
def test_files(self):
namespacedata01 = import_module('namespacedata01')
reader = NamespaceReader(namespacedata01.__spec__.submodule_search_locations)
root = os.path.abspath(os.path.join(__file__, '..', 'namespacedata01'))
reader = NamespaceReader(self.data.__spec__.submodule_search_locations)
root = self.data.__path__[0]
self.assertIsInstance(reader.files(), MultiplexedPath)
self.assertEqual(repr(reader.files()), f"MultiplexedPath('{root}')")
+75 -94
View File
@@ -1,15 +1,8 @@
import contextlib
import sys
import os
import unittest
import uuid
import pathlib
from . import data01
from . import zipdata01, zipdata02
from . import util
from importlib import resources, import_module
from test.support import import_helper, os_helper
from test.support.os_helper import unlink
class ResourceTests:
@@ -29,9 +22,8 @@ class ResourceTests:
self.assertTrue(target.is_dir())
class ResourceDiskTests(ResourceTests, unittest.TestCase):
def setUp(self):
self.data = data01
class ResourceDiskTests(ResourceTests, util.DiskSetup, unittest.TestCase):
pass
class ResourceZipTests(ResourceTests, util.ZipSetup, unittest.TestCase):
@@ -42,33 +34,39 @@ def names(traversable):
return {item.name for item in traversable.iterdir()}
class ResourceLoaderTests(unittest.TestCase):
class ResourceLoaderTests(util.DiskSetup, unittest.TestCase):
def test_resource_contents(self):
package = util.create_package(
file=data01, path=data01.__file__, contents=['A', 'B', 'C']
file=self.data, path=self.data.__file__, contents=['A', 'B', 'C']
)
self.assertEqual(names(resources.files(package)), {'A', 'B', 'C'})
def test_is_file(self):
package = util.create_package(
file=data01, path=data01.__file__, contents=['A', 'B', 'C', 'D/E', 'D/F']
file=self.data,
path=self.data.__file__,
contents=['A', 'B', 'C', 'D/E', 'D/F'],
)
self.assertTrue(resources.files(package).joinpath('B').is_file())
def test_is_dir(self):
package = util.create_package(
file=data01, path=data01.__file__, contents=['A', 'B', 'C', 'D/E', 'D/F']
file=self.data,
path=self.data.__file__,
contents=['A', 'B', 'C', 'D/E', 'D/F'],
)
self.assertTrue(resources.files(package).joinpath('D').is_dir())
def test_resource_missing(self):
package = util.create_package(
file=data01, path=data01.__file__, contents=['A', 'B', 'C', 'D/E', 'D/F']
file=self.data,
path=self.data.__file__,
contents=['A', 'B', 'C', 'D/E', 'D/F'],
)
self.assertFalse(resources.files(package).joinpath('Z').is_file())
class ResourceCornerCaseTests(unittest.TestCase):
class ResourceCornerCaseTests(util.DiskSetup, unittest.TestCase):
def test_package_has_no_reader_fallback(self):
"""
Test odd ball packages which:
@@ -77,7 +75,7 @@ class ResourceCornerCaseTests(unittest.TestCase):
# 3. Are not in a zip file
"""
module = util.create_package(
file=data01, path=data01.__file__, contents=['A', 'B', 'C']
file=self.data, path=self.data.__file__, contents=['A', 'B', 'C']
)
# Give the module a dummy loader.
module.__loader__ = object()
@@ -88,43 +86,39 @@ class ResourceCornerCaseTests(unittest.TestCase):
self.assertFalse(resources.files(module).joinpath('A').is_file())
class ResourceFromZipsTest01(util.ZipSetupBase, unittest.TestCase):
ZIP_MODULE = zipdata01 # type: ignore
class ResourceFromZipsTest01(util.ZipSetup, unittest.TestCase):
def test_is_submodule_resource(self):
submodule = import_module('ziptestdata.subdirectory')
submodule = import_module('data01.subdirectory')
self.assertTrue(resources.files(submodule).joinpath('binary.file').is_file())
def test_read_submodule_resource_by_name(self):
self.assertTrue(
resources.files('ziptestdata.subdirectory')
.joinpath('binary.file')
.is_file()
resources.files('data01.subdirectory').joinpath('binary.file').is_file()
)
def test_submodule_contents(self):
submodule = import_module('ziptestdata.subdirectory')
submodule = import_module('data01.subdirectory')
self.assertEqual(
names(resources.files(submodule)), {'__init__.py', 'binary.file'}
)
def test_submodule_contents_by_name(self):
self.assertEqual(
names(resources.files('ziptestdata.subdirectory')),
names(resources.files('data01.subdirectory')),
{'__init__.py', 'binary.file'},
)
def test_as_file_directory(self):
with resources.as_file(resources.files('ziptestdata')) as data:
assert data.name == 'ziptestdata'
with resources.as_file(resources.files('data01')) as data:
assert data.name == 'data01'
assert data.is_dir()
assert data.joinpath('subdirectory').is_dir()
assert len(list(data.iterdir()))
assert not data.parent.exists()
class ResourceFromZipsTest02(util.ZipSetupBase, unittest.TestCase):
ZIP_MODULE = zipdata02 # type: ignore
class ResourceFromZipsTest02(util.ZipSetup, unittest.TestCase):
MODULE = 'data02'
def test_unrelated_contents(self):
"""
@@ -132,98 +126,49 @@ class ResourceFromZipsTest02(util.ZipSetupBase, unittest.TestCase):
distinct resources. Ref python/importlib_resources#44.
"""
self.assertEqual(
names(resources.files('ziptestdata.one')),
names(resources.files('data02.one')),
{'__init__.py', 'resource1.txt'},
)
self.assertEqual(
names(resources.files('ziptestdata.two')),
names(resources.files('data02.two')),
{'__init__.py', 'resource2.txt'},
)
@contextlib.contextmanager
def zip_on_path(dir):
data_path = pathlib.Path(zipdata01.__file__)
source_zip_path = data_path.parent.joinpath('ziptestdata.zip')
zip_path = pathlib.Path(dir) / f'{uuid.uuid4()}.zip'
zip_path.write_bytes(source_zip_path.read_bytes())
sys.path.append(str(zip_path))
import_module('ziptestdata')
try:
yield
finally:
with contextlib.suppress(ValueError):
sys.path.remove(str(zip_path))
with contextlib.suppress(KeyError):
del sys.path_importer_cache[str(zip_path)]
del sys.modules['ziptestdata']
with contextlib.suppress(OSError):
unlink(zip_path)
class DeletingZipsTest(unittest.TestCase):
class DeletingZipsTest(util.ZipSetup, unittest.TestCase):
"""Having accessed resources in a zip file should not keep an open
reference to the zip.
"""
def setUp(self):
self.fixtures = contextlib.ExitStack()
self.addCleanup(self.fixtures.close)
modules = import_helper.modules_setup()
self.addCleanup(import_helper.modules_cleanup, *modules)
temp_dir = self.fixtures.enter_context(os_helper.temp_dir())
self.fixtures.enter_context(zip_on_path(temp_dir))
def test_iterdir_does_not_keep_open(self):
[item.name for item in resources.files('ziptestdata').iterdir()]
[item.name for item in resources.files('data01').iterdir()]
def test_is_file_does_not_keep_open(self):
resources.files('ziptestdata').joinpath('binary.file').is_file()
resources.files('data01').joinpath('binary.file').is_file()
def test_is_file_failure_does_not_keep_open(self):
resources.files('ziptestdata').joinpath('not-present').is_file()
resources.files('data01').joinpath('not-present').is_file()
@unittest.skip("Desired but not supported.")
def test_as_file_does_not_keep_open(self): # pragma: no cover
resources.as_file(resources.files('ziptestdata') / 'binary.file')
resources.as_file(resources.files('data01') / 'binary.file')
import os # TODO: RUSTPYTHON see below
@unittest.skipIf(
'RUSTPYTHON_SKIP_ENV_POLLUTERS' in os.environ,
"TODO: RUSTPYTHON environment pollution when running rustpython -m test --fail-env-changed due to tmpfile leak"
)
@unittest.skipIf("RUSTPYTHON_SKIP_ENV_POLLUTERS" in os.environ, "TODO: RUSTPYTHON; environment pollution when running rustpython -m test --fail-env-changed due to tmpfile leak")
def test_entered_path_does_not_keep_open(self):
"""
Mimic what certifi does on import to make its bundle
available for the process duration.
"""
resources.as_file(resources.files('ziptestdata') / 'binary.file').__enter__()
resources.as_file(resources.files('data01') / 'binary.file').__enter__()
def test_read_binary_does_not_keep_open(self):
resources.files('ziptestdata').joinpath('binary.file').read_bytes()
resources.files('data01').joinpath('binary.file').read_bytes()
def test_read_text_does_not_keep_open(self):
resources.files('ziptestdata').joinpath('utf-8.file').read_text(
encoding='utf-8'
)
resources.files('data01').joinpath('utf-8.file').read_text(encoding='utf-8')
class ResourceFromNamespaceTest01(unittest.TestCase):
site_dir = str(pathlib.Path(__file__).parent)
@classmethod
def setUpClass(cls):
sys.path.append(cls.site_dir)
@classmethod
def tearDownClass(cls):
sys.path.remove(cls.site_dir)
class ResourceFromNamespaceTests:
def test_is_submodule_resource(self):
self.assertTrue(
resources.files(import_module('namespacedata01'))
@@ -242,7 +187,9 @@ class ResourceFromNamespaceTest01(unittest.TestCase):
contents.remove('__pycache__')
except KeyError:
pass
self.assertEqual(contents, {'binary.file', 'utf-8.file', 'utf-16.file'})
self.assertEqual(
contents, {'subdirectory', 'binary.file', 'utf-8.file', 'utf-16.file'}
)
def test_submodule_contents_by_name(self):
contents = names(resources.files('namespacedata01'))
@@ -250,7 +197,41 @@ class ResourceFromNamespaceTest01(unittest.TestCase):
contents.remove('__pycache__')
except KeyError:
pass
self.assertEqual(contents, {'binary.file', 'utf-8.file', 'utf-16.file'})
self.assertEqual(
contents, {'subdirectory', 'binary.file', 'utf-8.file', 'utf-16.file'}
)
def test_submodule_sub_contents(self):
contents = names(resources.files(import_module('namespacedata01.subdirectory')))
try:
contents.remove('__pycache__')
except KeyError:
pass
self.assertEqual(contents, {'binary.file'})
def test_submodule_sub_contents_by_name(self):
contents = names(resources.files('namespacedata01.subdirectory'))
try:
contents.remove('__pycache__')
except KeyError:
pass
self.assertEqual(contents, {'binary.file'})
class ResourceFromNamespaceDiskTests(
util.DiskSetup,
ResourceFromNamespaceTests,
unittest.TestCase,
):
MODULE = 'namespacedata01'
class ResourceFromNamespaceZipTests(
util.ZipSetup,
ResourceFromNamespaceTests,
unittest.TestCase,
):
MODULE = 'namespacedata01'
if __name__ == '__main__':
+69 -42
View File
@@ -4,11 +4,12 @@ import io
import sys
import types
import pathlib
import contextlib
from . import data01
from . import zipdata01
from importlib.resources.abc import ResourceReader
from test.support import import_helper
from test.support import import_helper, os_helper
from . import zip as zip_
from . import _path
from importlib.machinery import ModuleSpec
@@ -67,7 +68,7 @@ def create_package(file=None, path=None, is_package=True, contents=()):
)
class CommonTests(metaclass=abc.ABCMeta):
class CommonTestsBase(metaclass=abc.ABCMeta):
"""
Tests shared by test_open, test_path, and test_read.
"""
@@ -83,34 +84,34 @@ class CommonTests(metaclass=abc.ABCMeta):
"""
Passing in the package name should succeed.
"""
self.execute(data01.__name__, 'utf-8.file')
self.execute(self.data.__name__, 'utf-8.file')
def test_package_object(self):
"""
Passing in the package itself should succeed.
"""
self.execute(data01, 'utf-8.file')
self.execute(self.data, 'utf-8.file')
def test_string_path(self):
"""
Passing in a string for the path should succeed.
"""
path = 'utf-8.file'
self.execute(data01, path)
self.execute(self.data, path)
def test_pathlib_path(self):
"""
Passing in a pathlib.PurePath object for the path should succeed.
"""
path = pathlib.PurePath('utf-8.file')
self.execute(data01, path)
self.execute(self.data, path)
def test_importing_module_as_side_effect(self):
"""
The anchor package can already be imported.
"""
del sys.modules[data01.__name__]
self.execute(data01.__name__, 'utf-8.file')
del sys.modules[self.data.__name__]
self.execute(self.data.__name__, 'utf-8.file')
def test_missing_path(self):
"""
@@ -140,40 +141,66 @@ class CommonTests(metaclass=abc.ABCMeta):
self.execute(package, 'utf-8.file')
class ZipSetupBase:
ZIP_MODULE = None
fixtures = dict(
data01={
'__init__.py': '',
'binary.file': bytes(range(4)),
'utf-16.file': '\ufeffHello, UTF-16 world!\n'.encode('utf-16-le'),
'utf-8.file': 'Hello, UTF-8 world!\n'.encode('utf-8'),
'subdirectory': {
'__init__.py': '',
'binary.file': bytes(range(4, 8)),
},
},
data02={
'__init__.py': '',
'one': {'__init__.py': '', 'resource1.txt': 'one resource'},
'two': {'__init__.py': '', 'resource2.txt': 'two resource'},
'subdirectory': {'subsubdir': {'resource.txt': 'a resource'}},
},
namespacedata01={
'binary.file': bytes(range(4)),
'utf-16.file': '\ufeffHello, UTF-16 world!\n'.encode('utf-16-le'),
'utf-8.file': 'Hello, UTF-8 world!\n'.encode('utf-8'),
'subdirectory': {
'binary.file': bytes(range(12, 16)),
},
},
)
@classmethod
def setUpClass(cls):
data_path = pathlib.Path(cls.ZIP_MODULE.__file__)
data_dir = data_path.parent
cls._zip_path = str(data_dir / 'ziptestdata.zip')
sys.path.append(cls._zip_path)
cls.data = importlib.import_module('ziptestdata')
@classmethod
def tearDownClass(cls):
try:
sys.path.remove(cls._zip_path)
except ValueError:
pass
try:
del sys.path_importer_cache[cls._zip_path]
del sys.modules[cls.data.__name__]
except KeyError:
pass
try:
del cls.data
del cls._zip_path
except AttributeError:
pass
class ModuleSetup:
def setUp(self):
modules = import_helper.modules_setup()
self.addCleanup(import_helper.modules_cleanup, *modules)
self.fixtures = contextlib.ExitStack()
self.addCleanup(self.fixtures.close)
self.fixtures.enter_context(import_helper.isolated_modules())
self.data = self.load_fixture(self.MODULE)
def load_fixture(self, module):
self.tree_on_path({module: fixtures[module]})
return importlib.import_module(module)
class ZipSetup(ZipSetupBase):
ZIP_MODULE = zipdata01 # type: ignore
class ZipSetup(ModuleSetup):
MODULE = 'data01'
def tree_on_path(self, spec):
temp_dir = self.fixtures.enter_context(os_helper.temp_dir())
modules = pathlib.Path(temp_dir) / 'zipped modules.zip'
self.fixtures.enter_context(
import_helper.DirsOnSysPath(str(zip_.make_zip_file(spec, modules)))
)
class DiskSetup(ModuleSetup):
MODULE = 'data01'
def tree_on_path(self, spec):
temp_dir = self.fixtures.enter_context(os_helper.temp_dir())
_path.build(spec, pathlib.Path(temp_dir))
self.fixtures.enter_context(import_helper.DirsOnSysPath(temp_dir))
class CommonTests(DiskSetup, CommonTestsBase):
pass
+24
View File
@@ -0,0 +1,24 @@
"""
Generate zip test data files.
"""
import zipfile
def make_zip_file(tree, dst):
"""
Zip the files in tree into a new zipfile at dst.
"""
with zipfile.ZipFile(dst, 'w') as zf:
for name, contents in walk(tree):
zf.writestr(name, contents)
zipfile._path.CompleteDirs.inject(zf)
return dst
def walk(tree, prefix=''):
for name, contents in tree.items():
if isinstance(contents, dict):
yield from walk(contents, prefix=f'{prefix}{name}/')
else:
yield f'{prefix}{name}', contents
@@ -9,7 +9,6 @@ machinery = util.import_importlib('importlib.machinery')
import os
from test.support import os_helper
import unittest
import warnings
@util.case_insensitive_tests
+5
View File
@@ -359,6 +359,7 @@ class SimpleTest(abc.LoaderTests):
) = util.test_both(SimpleTest, importlib=importlib, machinery=machinery,
abc=importlib_abc, util=importlib_util)
class SourceDateEpochTestMeta(SourceDateEpochTestMeta,
type(Source_SimpleTest)):
pass
@@ -679,6 +680,7 @@ class SourceLoaderBadBytecodeTestPEP451(
machinery=machinery, abc=importlib_abc,
util=importlib_util)
class SourceLoaderBadBytecodeTestPEP302(
SourceLoaderBadBytecodeTest, BadBytecodeTestPEP302):
pass
@@ -690,6 +692,7 @@ class SourceLoaderBadBytecodeTestPEP302(
machinery=machinery, abc=importlib_abc,
util=importlib_util)
class SourcelessLoaderBadBytecodeTest:
@classmethod
@@ -775,6 +778,7 @@ class SourcelessLoaderBadBytecodeTestPEP451(SourcelessLoaderBadBytecodeTest,
machinery=machinery, abc=importlib_abc,
util=importlib_util)
class SourcelessLoaderBadBytecodeTestPEP302(SourcelessLoaderBadBytecodeTest,
BadBytecodeTestPEP302):
pass
@@ -786,5 +790,6 @@ class SourcelessLoaderBadBytecodeTestPEP302(SourcelessLoaderBadBytecodeTest,
machinery=machinery, abc=importlib_abc,
util=importlib_util)
if __name__ == '__main__':
unittest.main()
+4 -5
View File
@@ -10,7 +10,6 @@ import sys
import tempfile
from test.support.import_helper import make_legacy_pyc
import unittest
import warnings
class FinderTests(abc.FinderTests):
@@ -74,7 +73,7 @@ class FinderTests(abc.FinderTests):
if error.errno != errno.ENOENT:
raise
loader = self.import_(mapping['.root'], test)
self.assertTrue(hasattr(loader, 'load_module'))
self.assertHasAttr(loader, 'load_module')
return loader
def test_module(self):
@@ -101,7 +100,7 @@ class FinderTests(abc.FinderTests):
with util.create_modules('pkg.__init__', 'pkg.sub') as mapping:
pkg_dir = os.path.dirname(mapping['pkg.__init__'])
loader = self.import_(pkg_dir, 'pkg.sub')
self.assertTrue(hasattr(loader, 'load_module'))
self.assertHasAttr(loader, 'load_module')
# [sub package]
def test_package_in_package(self):
@@ -109,7 +108,7 @@ class FinderTests(abc.FinderTests):
with context as mapping:
pkg_dir = os.path.dirname(mapping['pkg.__init__'])
loader = self.import_(pkg_dir, 'pkg.sub')
self.assertTrue(hasattr(loader, 'load_module'))
self.assertHasAttr(loader, 'load_module')
# [package over modules]
def test_package_over_module(self):
@@ -130,7 +129,7 @@ class FinderTests(abc.FinderTests):
file.write("# test file for importlib")
try:
loader = self._find(finder, 'mod', loader_only=True)
self.assertTrue(hasattr(loader, 'load_module'))
self.assertHasAttr(loader, 'load_module')
finally:
os.unlink('mod.py')
+3 -3
View File
@@ -15,12 +15,12 @@ class PathHookTest:
def test_success(self):
with util.create_modules('dummy') as mapping:
self.assertTrue(hasattr(self.path_hook()(mapping['.root']),
'find_spec'))
self.assertHasAttr(self.path_hook()(mapping['.root']),
'find_spec')
def test_empty_string(self):
# The empty string represents the cwd.
self.assertTrue(hasattr(self.path_hook()(''), 'find_spec'))
self.assertHasAttr(self.path_hook()(''), 'find_spec')
(Frozen_PathHookTest,
+3 -6
View File
@@ -61,17 +61,15 @@ class EncodingTest:
def test_default_encoding(self):
self.run_test(self.source_line.encode('utf-8'))
# TODO: RUSTPYTHON, UnicodeDecodeError: invalid utf-8 sequence of 1 bytes from index 17
@unittest.expectedFailure
# [encoding first line]
@unittest.expectedFailure # TODO: RUSTPYTHON; UnicodeDecodeError: invalid utf-8 sequence of 1 bytes from index 17
def test_encoding_on_first_line(self):
encoding = 'Latin-1'
source = self.create_source(encoding)
self.run_test(source)
# TODO: RUSTPYTHON, UnicodeDecodeError: invalid utf-8 sequence of 1 bytes from index 34
@unittest.expectedFailure
# [encoding second line]
@unittest.expectedFailure # TODO: RUSTPYTHON; UnicodeDecodeError: invalid utf-8 sequence of 1 bytes from index 34
def test_encoding_on_second_line(self):
source = b"#/usr/bin/python\n" + self.create_source('Latin-1')
self.run_test(source)
@@ -85,9 +83,8 @@ class EncodingTest:
source = codecs.BOM_UTF8 + self.create_source('utf-8')
self.run_test(source)
# TODO: RUSTPYTHON, UnicodeDecodeError: invalid utf-8 sequence of 1 bytes from index 20
@unittest.expectedFailure
# [BOM conflict]
@unittest.expectedFailure # TODO: RUSTPYTHON; UnicodeDecodeError: invalid utf-8 sequence of 1 bytes from index 20
def test_bom_conflict(self):
source = codecs.BOM_UTF8 + self.create_source('latin-1')
with self.assertRaises(SyntaxError):
+33 -7
View File
@@ -43,14 +43,12 @@ class InheritanceTests:
def test_subclasses(self):
# Test that the expected subclasses inherit.
for subclass in self.subclasses:
self.assertTrue(issubclass(subclass, self.__test),
"{0} is not a subclass of {1}".format(subclass, self.__test))
self.assertIsSubclass(subclass, self.__test)
def test_superclasses(self):
# Test that the class inherits from the expected superclasses.
for superclass in self.superclasses:
self.assertTrue(issubclass(self.__test, superclass),
"{0} is not a superclass of {1}".format(superclass, self.__test))
self.assertIsSubclass(self.__test, superclass)
class MetaPathFinder(InheritanceTests):
@@ -416,14 +414,14 @@ class InspectLoaderSourceToCodeTests:
# Since compile() can handle strings, so should source_to_code().
source = 'attr = 42'
module = self.source_to_module(source)
self.assertTrue(hasattr(module, 'attr'))
self.assertHasAttr(module, 'attr')
self.assertEqual(module.attr, 42)
def test_source_to_code_bytes(self):
# Since compile() can handle bytes, so should source_to_code().
source = b'attr = 42'
module = self.source_to_module(source)
self.assertTrue(hasattr(module, 'attr'))
self.assertHasAttr(module, 'attr')
self.assertEqual(module.attr, 42)
def test_source_to_code_path(self):
@@ -757,7 +755,7 @@ class SourceOnlyLoaderTests(SourceLoaderTestHarness):
warnings.simplefilter('ignore', DeprecationWarning)
module = self.loader.load_module(self.name)
self.verify_module(module)
self.assertFalse(hasattr(module, '__path__'))
self.assertNotHasAttr(module, '__path__')
def test_get_source_encoding(self):
# Source is considered encoded in UTF-8 by default unless otherwise
@@ -795,6 +793,9 @@ class SourceLoaderBytecodeTests(SourceLoaderTestHarness):
data.extend(self.init._pack_uint32(0))
data.extend(self.init._pack_uint32(self.loader.source_mtime))
data.extend(self.init._pack_uint32(self.loader.source_size))
# Make sure there's > 1 reference to code_object so that the
# marshaled representation below matches the cached representation
l = [code_object]
data.extend(marshal.dumps(code_object))
self.assertEqual(self.loader.written[self.cached], bytes(data))
@@ -913,5 +914,30 @@ class SourceLoaderGetSourceTests:
SourceOnlyLoaderMock=SPLIT_SOL)
class SourceLoaderDeprecationWarningsTests(unittest.TestCase):
"""Tests SourceLoader deprecation warnings."""
def test_deprecated_path_mtime(self):
from importlib.abc import SourceLoader
class DummySourceLoader(SourceLoader):
def get_data(self, path):
return b''
def get_filename(self, fullname):
return 'foo.py'
def path_stats(self, path):
return {'mtime': 1}
loader = DummySourceLoader()
with self.assertWarnsRegex(
DeprecationWarning,
r"SourceLoader\.path_mtime is deprecated in favour of "
r"SourceLoader\.path_stats\(\)\."
):
loader.path_mtime('foo.py')
if __name__ == '__main__':
unittest.main()
+70 -4
View File
@@ -6,11 +6,12 @@ machinery = test_util.import_importlib('importlib.machinery')
import os.path
import sys
from test import support
from test.support import import_helper
from test.support import os_helper
import traceback
import types
import unittest
import warnings
class ImportModuleTests:
@@ -354,6 +355,20 @@ class ReloadTests:
with self.assertRaises(ModuleNotFoundError):
self.init.reload(module)
def test_reload_traceback_with_non_str(self):
# gh-125519
with support.captured_stdout() as stdout:
try:
self.init.reload("typing")
except TypeError as exc:
traceback.print_exception(exc, file=stdout)
else:
self.fail("Expected TypeError to be raised")
printed_traceback = stdout.getvalue()
self.assertIn("TypeError", printed_traceback)
self.assertNotIn("AttributeError", printed_traceback)
self.assertNotIn("module.__spec__.name", printed_traceback)
(Frozen_ReloadTests,
Source_ReloadTests
@@ -415,8 +430,7 @@ class StartupTests:
for name, module in sys.modules.items():
if isinstance(module, types.ModuleType):
with self.subTest(name=name):
self.assertTrue(hasattr(module, '__loader__'),
'{!r} lacks a __loader__ attribute'.format(name))
self.assertHasAttr(module, '__loader__')
if self.machinery.BuiltinImporter.find_spec(name):
self.assertIsNot(module.__loader__, None)
elif self.machinery.FrozenImporter.find_spec(name):
@@ -426,7 +440,7 @@ class StartupTests:
for name, module in sys.modules.items():
if isinstance(module, types.ModuleType):
with self.subTest(name=name):
self.assertTrue(hasattr(module, '__spec__'))
self.assertHasAttr(module, '__spec__')
if self.machinery.BuiltinImporter.find_spec(name):
self.assertIsNot(module.__spec__, None)
elif self.machinery.FrozenImporter.find_spec(name):
@@ -438,5 +452,57 @@ class StartupTests:
) = test_util.test_both(StartupTests, machinery=machinery)
class TestModuleAll(unittest.TestCase):
def test_machinery(self):
extra = (
# from importlib._bootstrap and importlib._bootstrap_external
'AppleFrameworkLoader',
'BYTECODE_SUFFIXES',
'BuiltinImporter',
'DEBUG_BYTECODE_SUFFIXES',
'EXTENSION_SUFFIXES',
'ExtensionFileLoader',
'FileFinder',
'FrozenImporter',
'ModuleSpec',
'NamespaceLoader',
'OPTIMIZED_BYTECODE_SUFFIXES',
'PathFinder',
'SOURCE_SUFFIXES',
'SourceFileLoader',
'SourcelessFileLoader',
'WindowsRegistryFinder',
)
support.check__all__(self, machinery['Source'], extra=extra)
def test_util(self):
extra = (
# from importlib.abc, importlib._bootstrap
# and importlib._bootstrap_external
'Loader',
'MAGIC_NUMBER',
'cache_from_source',
'decode_source',
'module_from_spec',
'source_from_cache',
'spec_from_file_location',
'spec_from_loader',
)
support.check__all__(self, util['Source'], extra=extra)
class TestDeprecations(unittest.TestCase):
def test_machinery_deprecated_attributes(self):
from importlib import machinery
attributes = (
'DEBUG_BYTECODE_SUFFIXES',
'OPTIMIZED_BYTECODE_SUFFIXES',
)
for attr in attributes:
with self.subTest(attr=attr):
with self.assertWarns(DeprecationWarning):
getattr(machinery, attr)
if __name__ == '__main__':
unittest.main()
+88 -4
View File
@@ -2,9 +2,12 @@ import importlib
from importlib import abc
from importlib import util
import sys
import time
import threading
import types
import unittest
from test.support import threading_helper
from test.test_importlib import util as test_util
@@ -40,6 +43,7 @@ class TestingImporter(abc.MetaPathFinder, abc.Loader):
module_name = 'lazy_loader_test'
mutated_name = 'changed'
loaded = None
load_count = 0
source_code = 'attr = 42; __name__ = {!r}'.format(mutated_name)
def find_spec(self, name, path, target=None):
@@ -48,8 +52,10 @@ class TestingImporter(abc.MetaPathFinder, abc.Loader):
return util.spec_from_loader(name, util.LazyLoader(self))
def exec_module(self, module):
time.sleep(0.01) # Simulate a slow load.
exec(self.source_code, module.__dict__)
self.loaded = module
self.load_count += 1
class LazyLoaderTests(unittest.TestCase):
@@ -59,8 +65,9 @@ class LazyLoaderTests(unittest.TestCase):
# Classes that don't define exec_module() trigger TypeError.
util.LazyLoader(object)
def new_module(self, source_code=None):
loader = TestingImporter()
def new_module(self, source_code=None, loader=None):
if loader is None:
loader = TestingImporter()
if source_code is not None:
loader.source_code = source_code
spec = util.spec_from_loader(TestingImporter.module_name,
@@ -118,12 +125,12 @@ class LazyLoaderTests(unittest.TestCase):
# Deleting an attribute should stay deleted.
module = self.new_module()
del module.attr
self.assertFalse(hasattr(module, 'attr'))
self.assertNotHasAttr(module, 'attr')
def test_delete_preexisting_attr(self):
module = self.new_module()
del module.__name__
self.assertFalse(hasattr(module, '__name__'))
self.assertNotHasAttr(module, '__name__')
def test_module_substitution_error(self):
with test_util.uncache(TestingImporter.module_name):
@@ -140,6 +147,83 @@ class LazyLoaderTests(unittest.TestCase):
# Force the load; just care that no exception is raised.
module.__name__
@threading_helper.requires_working_threading()
def test_module_load_race(self):
with test_util.uncache(TestingImporter.module_name):
loader = TestingImporter()
module = self.new_module(loader=loader)
self.assertEqual(loader.load_count, 0)
class RaisingThread(threading.Thread):
exc = None
def run(self):
try:
super().run()
except Exception as exc:
self.exc = exc
def access_module():
return module.attr
threads = []
for _ in range(2):
threads.append(thread := RaisingThread(target=access_module))
thread.start()
# Races could cause errors
for thread in threads:
thread.join()
self.assertIsNone(thread.exc)
# Or multiple load attempts
self.assertEqual(loader.load_count, 1)
def test_lazy_self_referential_modules(self):
# Directory modules with submodules that reference the parent can attempt to access
# the parent module during a load. Verify that this common pattern works with lazy loading.
# json is a good example in the stdlib.
json_modules = [name for name in sys.modules if name.startswith('json')]
with test_util.uncache(*json_modules):
# Standard lazy loading, unwrapped
spec = util.find_spec('json')
loader = util.LazyLoader(spec.loader)
spec.loader = loader
module = util.module_from_spec(spec)
sys.modules['json'] = module
loader.exec_module(module)
# Trigger load with attribute lookup, ensure expected behavior
test_load = module.loads('{}')
self.assertEqual(test_load, {})
def test_lazy_module_type_override(self):
# Verify that lazy loading works with a module that modifies
# its __class__ to be a custom type.
# Example module from PEP 726
module = self.new_module(source_code="""\
import sys
from types import ModuleType
CONSTANT = 3.14
class ImmutableModule(ModuleType):
def __setattr__(self, name, value):
raise AttributeError('Read-only attribute!')
def __delattr__(self, name):
raise AttributeError('Read-only attribute!')
sys.modules[__name__].__class__ = ImmutableModule
""")
sys.modules[TestingImporter.module_name] = module
self.assertIsInstance(module, util._LazyModule)
self.assertEqual(module.CONSTANT, 3.14)
with self.assertRaises(AttributeError):
module.CONSTANT = 2.71
with self.assertRaises(AttributeError):
del module.CONSTANT
if __name__ == '__main__':
unittest.main()
+7 -7
View File
@@ -34,6 +34,7 @@ class ModuleLockAsRLockTests:
# lock status in repr unsupported
test_repr = None
test_locked_repr = None
test_repr_count = None
def tearDown(self):
for splitinit in init.values():
@@ -49,7 +50,6 @@ LOCK_TYPES = {kind: splitinit._bootstrap._ModuleLock
LockType=LOCK_TYPES)
@unittest.skipIf(sys.platform == "darwin", "TODO: RUSTPYTHON")
class DeadlockAvoidanceTests:
def setUp(self):
@@ -99,7 +99,7 @@ class DeadlockAvoidanceTests:
self.assertEqual(len(results), NTHREADS)
return results
@unittest.skip("TODO: RUSTPYTHON, sometimes hangs")
@unittest.skip("TODO: RUSTPYTHON; sometimes hangs")
def test_deadlock(self):
results = self.run_deadlock_avoidance_test(True)
# At least one of the threads detected a potential deadlock on its
@@ -109,7 +109,7 @@ class DeadlockAvoidanceTests:
self.assertGreaterEqual(nb_deadlocks, 1)
self.assertEqual(results.count((True, True)), len(results) - nb_deadlocks)
@unittest.skip("TODO: RUSTPYTHON, flaky test")
@unittest.skip("TODO: RUSTPYTHON; flaky test")
def test_no_deadlock(self):
results = self.run_deadlock_avoidance_test(False)
self.assertEqual(results.count((True, False)), 0)
@@ -148,10 +148,10 @@ class LifetimeTests:
self.assertEqual(0, len(self.bootstrap._module_locks),
self.bootstrap._module_locks)
# TODO: RUSTPYTHON
# (Frozen_LifetimeTests,
# Source_LifetimeTests
# ) = test_util.test_both(LifetimeTests, init=init)
(Frozen_LifetimeTests,
Source_LifetimeTests
) = test_util.test_both(LifetimeTests, init=init)
def setUpModule():
+14 -15
View File
@@ -6,7 +6,6 @@ import os
import sys
import tempfile
import unittest
import warnings
from test.test_importlib import util
@@ -81,7 +80,7 @@ class SingleNamespacePackage(NamespacePackageTest):
def test_simple_repr(self):
import foo.one
assert repr(foo).startswith("<module 'foo' (namespace) from [")
self.assertStartsWith(repr(foo), "<module 'foo' (namespace) from [")
class DynamicPathNamespacePackage(NamespacePackageTest):
@@ -287,24 +286,24 @@ class DynamicPathCalculation(NamespacePackageTest):
class ZipWithMissingDirectory(NamespacePackageTest):
paths = ['missing_directory.zip']
# missing_directory.zip contains:
# Length Date Time Name
# --------- ---------- ----- ----
# 29 2012-05-03 18:13 foo/one.py
# 0 2012-05-03 20:57 bar/
# 38 2012-05-03 20:57 bar/two.py
# --------- -------
# 67 3 files
def test_missing_directory(self):
# This will fail because missing_directory.zip contains:
# Length Date Time Name
# --------- ---------- ----- ----
# 29 2012-05-03 18:13 foo/one.py
# 0 2012-05-03 20:57 bar/
# 38 2012-05-03 20:57 bar/two.py
# --------- -------
# 67 3 files
# Because there is no 'foo/', the zipimporter currently doesn't
# know that foo is a namespace package
import foo.one
self.assertEqual(foo.one.attr, 'portion1 foo one')
def test_missing_directory2(self):
import foo
self.assertNotHasAttr(foo, 'one')
def test_present_directory(self):
# This succeeds because there is a "bar/" in the zip file
import bar.two
self.assertEqual(bar.two.attr, 'missing_directory foo two')
+1 -1
View File
@@ -55,7 +55,7 @@ class TestImport(unittest.TestCase):
except SyntaxError: pass
else: raise RuntimeError('Failed to induce SyntaxError') # self.fail()?
self.assertNotIn(self.module_name, sys.modules)
self.assertFalse(hasattr(sys.modules[self.package_name], 'foo'))
self.assertNotHasAttr(sys.modules[self.package_name], 'foo')
# ...make up a variable name that isn't bound in __builtins__
var = 'a'
+13 -11
View File
@@ -237,7 +237,7 @@ class ModuleSpecMethodsTests:
self.spec.loader = NewLoader()
module = self.util.module_from_spec(self.spec)
sys.modules[self.name] = module
self.assertFalse(hasattr(module, 'eggs'))
self.assertNotHasAttr(module, 'eggs')
self.bootstrap._exec(self.spec, module)
self.assertEqual(module.eggs, 1)
@@ -348,9 +348,9 @@ class ModuleSpecMethodsTests:
self.assertIs(loaded.__loader__, self.spec.loader)
self.assertEqual(loaded.__package__, self.spec.parent)
self.assertIs(loaded.__spec__, self.spec)
self.assertFalse(hasattr(loaded, '__path__'))
self.assertFalse(hasattr(loaded, '__file__'))
self.assertFalse(hasattr(loaded, '__cached__'))
self.assertNotHasAttr(loaded, '__path__')
self.assertNotHasAttr(loaded, '__file__')
self.assertNotHasAttr(loaded, '__cached__')
(Frozen_ModuleSpecMethodsTests,
@@ -502,7 +502,8 @@ class FactoryTests:
self.assertEqual(spec.loader, self.fileloader)
self.assertEqual(spec.origin, self.path)
self.assertIs(spec.loader_state, None)
self.assertEqual(spec.submodule_search_locations, [os.getcwd()])
location = cwd if (cwd := os.getcwd()) != '/' else ''
self.assertEqual(spec.submodule_search_locations, [location])
self.assertEqual(spec.cached, self.cached)
self.assertTrue(spec.has_location)
@@ -601,7 +602,8 @@ class FactoryTests:
self.assertEqual(spec.loader, self.fileloader)
self.assertEqual(spec.origin, self.path)
self.assertIs(spec.loader_state, None)
self.assertEqual(spec.submodule_search_locations, [os.getcwd()])
location = cwd if (cwd := os.getcwd()) != '/' else ''
self.assertEqual(spec.submodule_search_locations, [location])
self.assertEqual(spec.cached, self.cached)
self.assertTrue(spec.has_location)
@@ -626,7 +628,8 @@ class FactoryTests:
self.assertEqual(spec.loader, self.pkgloader)
self.assertEqual(spec.origin, self.path)
self.assertIs(spec.loader_state, None)
self.assertEqual(spec.submodule_search_locations, [os.getcwd()])
location = cwd if (cwd := os.getcwd()) != '/' else ''
self.assertEqual(spec.submodule_search_locations, [location])
self.assertEqual(spec.cached, self.cached)
self.assertTrue(spec.has_location)
@@ -686,10 +689,9 @@ class FactoryTests:
self.assertEqual(spec.cached, self.cached)
self.assertTrue(spec.has_location)
# TODO: RUSTPYTHON
# (Frozen_FactoryTests,
# Source_FactoryTests
# ) = test_util.test_both(FactoryTests, util=util, machinery=machinery)
(Frozen_FactoryTests,
Source_FactoryTests
) = test_util.test_both(FactoryTests, util=util, machinery=machinery)
if __name__ == '__main__':
+15 -14
View File
@@ -6,7 +6,6 @@
# randrange, and then Python hangs.
import _imp as imp
import _multiprocessing # TODO: RUSTPYTHON
import os
import importlib
import sys
@@ -14,7 +13,7 @@ import time
import shutil
import threading
import unittest
from unittest import mock
from test import support
from test.support import verbose
from test.support.import_helper import forget, mock_register_at_fork
from test.support.os_helper import (TESTFN, unlink, rmtree)
@@ -137,12 +136,12 @@ class ThreadedImportTests(unittest.TestCase):
print("OK.")
@unittest.skip("TODO: RUSTPYTHON; flaky")
def test_parallel_module_init(self):
@support.bigmemtest(size=50, memuse=76*2**20, dry_run=False)
def test_parallel_module_init(self, size):
self.check_parallel_module_init()
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_parallel_meta_path(self):
@support.bigmemtest(size=50, memuse=76*2**20, dry_run=False)
def test_parallel_meta_path(self, size):
finder = Finder()
sys.meta_path.insert(0, finder)
try:
@@ -152,9 +151,9 @@ class ThreadedImportTests(unittest.TestCase):
finally:
sys.meta_path.remove(finder)
# TODO: RUSTPYTHON; maybe hang?
@unittest.expectedFailure
def test_parallel_path_hooks(self):
@unittest.expectedFailure # TODO: RUSTPYTHON; maybe hang?
@support.bigmemtest(size=50, memuse=76*2**20, dry_run=False)
def test_parallel_path_hooks(self, size):
# Here the Finder instance is only used to check concurrent calls
# to path_hook().
finder = Finder()
@@ -248,15 +247,17 @@ class ThreadedImportTests(unittest.TestCase):
__import__(TESTFN)
del sys.modules[TESTFN]
@unittest.skip('TODO: RUSTPYTHON; hang; Suspected cause of crashes in Windows CI - PermissionError: [WinError 32] Permission denied: "C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\test_python_0cdrhhs_\\test_python_6340æ"')
def test_concurrent_futures_circular_import(self):
@unittest.skip("TODO: RUSTPYTHON; hang; Suspected cause of crashes in Windows CI - PermissionError: [WinError 32] Permission denied: \"C:\\Users\\RUNNER~1\\AppData\\Local\\Temp\\test_python_0cdrhhs_\\test_python_6340æ\"")
@support.bigmemtest(size=1, memuse=1.8*2**30, dry_run=False)
def test_concurrent_futures_circular_import(self, size):
# Regression test for bpo-43515
fn = os.path.join(os.path.dirname(__file__),
'partial', 'cfimport.py')
script_helper.assert_python_ok(fn)
@unittest.skip('TODO: RUSTPYTHON; hang')
def test_multiprocessing_pool_circular_import(self):
@unittest.skip("TODO: RUSTPYTHON; hang")
@support.bigmemtest(size=1, memuse=1.8*2**30, dry_run=False)
def test_multiprocessing_pool_circular_import(self, size):
# Regression test for bpo-41567
fn = os.path.join(os.path.dirname(__file__),
'partial', 'pool_in_threads.py')
@@ -269,7 +270,7 @@ def setUpModule():
try:
old_switchinterval = sys.getswitchinterval()
unittest.addModuleCleanup(sys.setswitchinterval, old_switchinterval)
sys.setswitchinterval(1e-5)
support.setswitchinterval(1e-5)
except AttributeError:
pass
+122 -25
View File
@@ -6,12 +6,13 @@ machinery = util.import_importlib('importlib.machinery')
importlib_util = util.import_importlib('importlib.util')
import importlib.util
from importlib import _bootstrap_external
import os
import pathlib
import re
import string
import sys
from test import support
from test.support import os_helper
import textwrap
import types
import unittest
@@ -27,7 +28,7 @@ try:
except ImportError:
_testmultiphase = None
try:
import _xxsubinterpreters as _interpreters
import _interpreters
except ModuleNotFoundError:
_interpreters = None
@@ -319,7 +320,7 @@ class MagicNumberTests:
def test_incorporates_rn(self):
# The magic number uses \r\n to come out wrong when splitting on lines.
self.assertTrue(self.util.MAGIC_NUMBER.endswith(b'\r\n'))
self.assertEndsWith(self.util.MAGIC_NUMBER, b'\r\n')
(Frozen_MagicNumberTests,
@@ -358,8 +359,6 @@ class PEP3147Tests:
self.assertEqual(self.util.cache_from_source(path, optimization=''),
expect)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_cache_from_source_debug_override(self):
# Given the path to a .py file, return the path to its PEP 3147/PEP 488
# defined .pyc file (i.e. under __pycache__).
@@ -579,7 +578,19 @@ class PEP3147Tests:
with util.temporary_pycache_prefix(pycache_prefix):
self.assertEqual(
self.util.cache_from_source(path, optimization=''),
expect)
os.path.normpath(expect))
@unittest.skipIf(sys.implementation.cache_tag is None,
'requires sys.implementation.cache_tag to not be None')
def test_cache_from_source_in_root_with_pycache_prefix(self):
# Regression test for gh-82916
pycache_prefix = os.path.join(os.path.sep, 'tmp', 'bytecode')
path = 'qux.py'
expect = os.path.join(os.path.sep, 'tmp', 'bytecode',
f'qux.{self.tag}.pyc')
with util.temporary_pycache_prefix(pycache_prefix):
with os_helper.change_cwd('/'):
self.assertEqual(self.util.cache_from_source(path), expect)
@unittest.skipIf(sys.implementation.cache_tag is None,
'requires sys.implementation.cache_tag to not be None')
@@ -636,7 +647,7 @@ class MagicNumberTests(unittest.TestCase):
# stakeholders such as OS package maintainers must be notified
# in advance. Such exceptional releases will then require an
# adjustment to this test case.
EXPECTED_MAGIC_NUMBER = 2996
EXPECTED_MAGIC_NUMBER = 3627
actual = int.from_bytes(importlib.util.MAGIC_NUMBER[:2], 'little')
msg = (
@@ -657,27 +668,36 @@ class MagicNumberTests(unittest.TestCase):
@unittest.skipIf(_interpreters is None, 'subinterpreters required')
class IncompatibleExtensionModuleRestrictionsTests(unittest.TestCase):
ERROR = re.compile("^<class 'ImportError'>: module (.*) does not support loading in subinterpreters")
def run_with_own_gil(self, script):
interpid = _interpreters.create(isolated=True)
try:
_interpreters.run_string(interpid, script)
except _interpreters.RunFailedError as exc:
if m := self.ERROR.match(str(exc)):
modname, = m.groups()
raise ImportError(modname)
interpid = _interpreters.create('isolated')
def ensure_destroyed():
try:
_interpreters.destroy(interpid)
except _interpreters.InterpreterNotFoundError:
pass
self.addCleanup(ensure_destroyed)
excsnap = _interpreters.exec(interpid, script)
if excsnap is not None:
if excsnap.type.__name__ == 'ImportError':
raise ImportError(excsnap.msg)
def run_with_shared_gil(self, script):
interpid = _interpreters.create(isolated=False)
try:
_interpreters.run_string(interpid, script)
except _interpreters.RunFailedError as exc:
if m := self.ERROR.match(str(exc)):
modname, = m.groups()
raise ImportError(modname)
interpid = _interpreters.create('legacy')
def ensure_destroyed():
try:
_interpreters.destroy(interpid)
except _interpreters.InterpreterNotFoundError:
pass
self.addCleanup(ensure_destroyed)
excsnap = _interpreters.exec(interpid, script)
if excsnap is not None:
if excsnap.type.__name__ == 'ImportError':
raise ImportError(excsnap.msg)
@unittest.skipIf(_testsinglephase is None, "test requires _testsinglephase module")
# gh-117649: single-phase init modules are not currently supported in
# subinterpreters in the free-threaded build
@support.expected_failure_if_gil_disabled()
def test_single_phase_init_module(self):
script = textwrap.dedent('''
from importlib.util import _incompatible_extension_module_restrictions
@@ -702,14 +722,22 @@ class IncompatibleExtensionModuleRestrictionsTests(unittest.TestCase):
self.run_with_own_gil(script)
@unittest.skipIf(_testmultiphase is None, "test requires _testmultiphase module")
@support.requires_gil_enabled("gh-117649: not supported in free-threaded build")
def test_incomplete_multi_phase_init_module(self):
# Apple extensions must be distributed as frameworks. This requires
# a specialist loader.
if support.is_apple_mobile:
loader = "AppleFrameworkLoader"
else:
loader = "ExtensionFileLoader"
prescript = textwrap.dedent(f'''
from importlib.util import spec_from_loader, module_from_spec
from importlib.machinery import ExtensionFileLoader
from importlib.machinery import {loader}
name = '_test_shared_gil_only'
filename = {_testmultiphase.__file__!r}
loader = ExtensionFileLoader(name, filename)
loader = {loader}(name, filename)
spec = spec_from_loader(name, loader)
''')
@@ -760,5 +788,74 @@ class IncompatibleExtensionModuleRestrictionsTests(unittest.TestCase):
self.run_with_own_gil(script)
class PatchAtomicWrites:
def __init__(self, truncate_at_length, never_complete=False):
self.truncate_at_length = truncate_at_length
self.never_complete = never_complete
self.seen_write = False
self._children = []
def __enter__(self):
import _pyio
oldwrite = os.write
# Emulate an os.write that only writes partial data.
def write(fd, data):
if self.seen_write and self.never_complete:
return None
self.seen_write = True
return oldwrite(fd, data[:self.truncate_at_length])
# Need to patch _io to be _pyio, so that io.FileIO is affected by the
# os.write patch.
self.children = [
support.swap_attr(_bootstrap_external, '_io', _pyio),
support.swap_attr(os, 'write', write)
]
for child in self.children:
child.__enter__()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
for child in self.children:
child.__exit__(exc_type, exc_val, exc_tb)
class MiscTests(unittest.TestCase):
def test_atomic_write_retries_incomplete_writes(self):
truncate_at_length = 100
length = truncate_at_length * 2
with PatchAtomicWrites(truncate_at_length=truncate_at_length) as cm:
# Make sure we write something longer than the point where we
# truncate.
content = b'x' * length
_bootstrap_external._write_atomic(os_helper.TESTFN, content)
self.assertTrue(cm.seen_write)
self.assertEqual(os.stat(support.os_helper.TESTFN).st_size, length)
os.unlink(support.os_helper.TESTFN)
def test_atomic_write_errors_if_unable_to_complete(self):
truncate_at_length = 100
with (
PatchAtomicWrites(
truncate_at_length=truncate_at_length, never_complete=True,
) as cm,
self.assertRaises(OSError)
):
# Make sure we write something longer than the point where we
# truncate.
content = b'x' * (truncate_at_length * 2)
_bootstrap_external._write_atomic(os_helper.TESTFN, content)
self.assertTrue(cm.seen_write)
with self.assertRaises(OSError):
os.stat(support.os_helper.TESTFN) # Check that the file did not get written.
if __name__ == '__main__':
unittest.main()
+37 -8
View File
@@ -5,7 +5,7 @@ import os
import re
import sys
import unittest
import warnings
from test import support
from test.support import import_helper
from contextlib import contextmanager
from test.test_importlib.util import temp_module
@@ -91,31 +91,60 @@ class WindowsRegistryFinderTests:
test_module = "spamham{}".format(os.getpid())
def test_find_spec_missing(self):
spec = self.machinery.WindowsRegistryFinder.find_spec('spam')
with self.assertWarnsRegex(
DeprecationWarning,
r"importlib\.machinery\.WindowsRegistryFinder is deprecated; "
r"use site configuration instead\. Future versions of Python may "
r"not enable this finder by default\."
):
spec = self.machinery.WindowsRegistryFinder.find_spec('spam')
self.assertIsNone(spec)
def test_module_found(self):
with setup_module(self.machinery, self.test_module):
spec = self.machinery.WindowsRegistryFinder.find_spec(self.test_module)
with self.assertWarnsRegex(
DeprecationWarning,
r"importlib\.machinery\.WindowsRegistryFinder is deprecated; "
r"use site configuration instead\. Future versions of Python may "
r"not enable this finder by default\."
):
spec = self.machinery.WindowsRegistryFinder.find_spec(self.test_module)
self.assertIsNotNone(spec)
def test_module_not_found(self):
with setup_module(self.machinery, self.test_module, path="."):
spec = self.machinery.WindowsRegistryFinder.find_spec(self.test_module)
with self.assertWarnsRegex(
DeprecationWarning,
r"importlib\.machinery\.WindowsRegistryFinder is deprecated; "
r"use site configuration instead\. Future versions of Python may "
r"not enable this finder by default\."
):
spec = self.machinery.WindowsRegistryFinder.find_spec(self.test_module)
self.assertIsNone(spec)
def test_raises_deprecation_warning(self):
# WindowsRegistryFinder is not meant to be instantiated, so the
# deprecation warning is raised in the 'find_spec' method instead.
with self.assertWarnsRegex(
DeprecationWarning,
r"importlib\.machinery\.WindowsRegistryFinder is deprecated; "
r"use site configuration instead\. Future versions of Python may "
r"not enable this finder by default\."
):
self.machinery.WindowsRegistryFinder.find_spec('spam')
(Frozen_WindowsRegistryFinderTests,
Source_WindowsRegistryFinderTests
) = test_util.test_both(WindowsRegistryFinderTests, machinery=machinery)
@unittest.skipUnless(sys.platform.startswith('win'), 'requires Windows')
class WindowsExtensionSuffixTests:
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_tagged_suffix(self):
suffixes = self.machinery.EXTENSION_SUFFIXES
expected_tag = ".cp{0.major}{0.minor}-{1}.pyd".format(sys.version_info,
re.sub('[^a-zA-Z0-9]', '_', get_platform()))
abi_flags = "t" if support.Py_GIL_DISABLED else ""
ver = sys.version_info
platform = re.sub('[^a-zA-Z0-9]', '_', get_platform())
expected_tag = f".cp{ver.major}{ver.minor}{abi_flags}-{platform}.pyd"
try:
untagged_i = suffixes.index(".pyd")
except ValueError:
+36 -18
View File
@@ -6,13 +6,17 @@ from importlib import machinery, util, invalidate_caches
import marshal
import os
import os.path
from test import support
from test.support import import_helper
from test.support import is_apple_mobile
from test.support import os_helper
import unittest
import sys
import tempfile
import types
_testsinglephase = import_helper.import_module("_testsinglephase")
BUILTINS = types.SimpleNamespace()
BUILTINS.good_name = None
@@ -22,25 +26,39 @@ if 'errno' in sys.builtin_module_names:
if 'importlib' not in sys.builtin_module_names:
BUILTINS.bad_name = 'importlib'
EXTENSIONS = types.SimpleNamespace()
EXTENSIONS.path = None
EXTENSIONS.ext = None
EXTENSIONS.filename = None
EXTENSIONS.file_path = None
EXTENSIONS.name = '_testsinglephase'
if support.is_wasi:
# dlopen() is a shim for WASI as of WASI SDK which fails by default.
# We don't provide an implementation, so tests will fail.
# But we also don't want to turn off dynamic loading for those that provide
# a working implementation.
def _extension_details():
global EXTENSIONS
EXTENSIONS = None
else:
EXTENSIONS = types.SimpleNamespace()
EXTENSIONS.path = None
EXTENSIONS.ext = None
EXTENSIONS.filename = None
EXTENSIONS.file_path = None
EXTENSIONS.name = '_testsinglephase'
def _extension_details():
global EXTENSIONS
for path in sys.path:
for ext in machinery.EXTENSION_SUFFIXES:
filename = EXTENSIONS.name + ext
file_path = os.path.join(path, filename)
if os.path.exists(file_path):
EXTENSIONS.path = path
EXTENSIONS.ext = ext
EXTENSIONS.filename = filename
EXTENSIONS.file_path = file_path
return
def _extension_details():
global EXTENSIONS
for path in sys.path:
for ext in machinery.EXTENSION_SUFFIXES:
# Apple mobile platforms mechanically load .so files,
# but the findable files are labelled .fwork
if is_apple_mobile:
ext = ext.replace(".so", ".fwork")
filename = EXTENSIONS.name + ext
file_path = os.path.join(path, filename)
if os.path.exists(file_path):
EXTENSIONS.path = path
EXTENSIONS.ext = ext
EXTENSIONS.filename = filename
EXTENSIONS.file_path = file_path
return
_extension_details()
+2 -7
View File
@@ -109,12 +109,7 @@ class PyCompileTestsBase:
self.assertTrue(os.path.exists(self.pyc_path))
self.assertFalse(os.path.exists(self.cache_path))
@unittest.expectedFailureIf(
sys.platform == "darwin" and int(
__import__("platform").release().split(".")[0]
) < 20,
"TODO: RUSTPYTHON"
)
@unittest.expectedFailureIf(sys.platform == "darwin" and int(__import__("platform").release().split(".")[0]) < 20, "TODO: RUSTPYTHON")
def test_relative_path(self):
py_compile.compile(os.path.relpath(self.source_path),
os.path.relpath(self.pyc_path))
@@ -203,7 +198,7 @@ class PyCompileTestsBase:
fp.read(), 'test', {})
self.assertEqual(flags, 0b1)
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.expectedFailure # TODO: RUSTPYTHON
def test_quiet(self):
bad_coding = os.path.join(os.path.dirname(__file__),
'tokenizedata',