Implement PEP 451 (ModuleSpec).
This commit is contained in:
parent
9e6097ebe7
commit
b523f8433a
|
@ -310,11 +310,11 @@ ABC hierarchy::
|
|||
from the import. If the loader inserted a module and the load fails, it
|
||||
must be removed by the loader from :data:`sys.modules`; modules already
|
||||
in :data:`sys.modules` before the loader began execution should be left
|
||||
alone (see :func:`importlib.util.module_to_load`).
|
||||
alone (see :func:`importlib.util.module_for_loader`).
|
||||
|
||||
The loader should set several attributes on the module.
|
||||
(Note that some of these attributes can change when a module is
|
||||
reloaded; see :meth:`init_module_attrs`):
|
||||
reloaded):
|
||||
|
||||
- :attr:`__name__`
|
||||
The name of the module.
|
||||
|
@ -357,17 +357,6 @@ ABC hierarchy::
|
|||
.. versionchanged:: 3.4
|
||||
Made optional instead of an abstractmethod.
|
||||
|
||||
.. method:: init_module_attrs(module)
|
||||
|
||||
Set the :attr:`__loader__` attribute on the module.
|
||||
|
||||
Subclasses overriding this method should set whatever appropriate
|
||||
attributes it can, getting the module's name from :attr:`__name__` when
|
||||
needed. All values should also be overridden so that reloading works as
|
||||
expected.
|
||||
|
||||
.. versionadded:: 3.4
|
||||
|
||||
|
||||
.. class:: ResourceLoader
|
||||
|
||||
|
@ -442,14 +431,6 @@ ABC hierarchy::
|
|||
|
||||
.. versionadded:: 3.4
|
||||
|
||||
.. method:: init_module_attrs(module)
|
||||
|
||||
Set the :attr:`__package__` attribute and :attr:`__path__` attribute to
|
||||
the empty list if appropriate along with what
|
||||
:meth:`importlib.abc.Loader.init_module_attrs` sets.
|
||||
|
||||
.. versionadded:: 3.4
|
||||
|
||||
.. method:: load_module(fullname)
|
||||
|
||||
Implementation of :meth:`Loader.load_module`.
|
||||
|
@ -474,15 +455,6 @@ ABC hierarchy::
|
|||
.. versionchanged:: 3.4
|
||||
Raises :exc:`ImportError` instead of :exc:`NotImplementedError`.
|
||||
|
||||
.. method:: init_module_attrs(module)
|
||||
|
||||
Set :attr:`__file__` and if initializing a package then set
|
||||
:attr:`__path__` to ``[os.path.dirname(__file__)]`` along with
|
||||
all attributes set by
|
||||
:meth:`importlib.abc.InspectLoader.init_module_attrs`.
|
||||
|
||||
.. versionadded:: 3.4
|
||||
|
||||
|
||||
.. class:: FileLoader(fullname, path)
|
||||
|
||||
|
@ -599,14 +571,6 @@ ABC hierarchy::
|
|||
``__init__`` when the file extension is removed **and** the module name
|
||||
itself does not end in ``__init__``.
|
||||
|
||||
.. method:: init_module_attr(module)
|
||||
|
||||
Set :attr:`__cached__` using :func:`imp.cache_from_source`. Other
|
||||
attributes set by
|
||||
:meth:`importlib.abc.ExecutionLoader.init_module_attrs`.
|
||||
|
||||
.. versionadded:: 3.4
|
||||
|
||||
|
||||
:mod:`importlib.machinery` -- Importers and path hooks
|
||||
------------------------------------------------------
|
||||
|
@ -882,6 +846,64 @@ find and load modules.
|
|||
.. versionadded:: 3.4
|
||||
|
||||
|
||||
.. class:: ModuleSpec(name, loader, *, origin=None, loader_state=None, is_package=None)
|
||||
|
||||
A specification for a module's import-system-related state.
|
||||
|
||||
.. versionadded:: 3.4
|
||||
|
||||
.. attribute:: name
|
||||
|
||||
(``__name__``)
|
||||
|
||||
A string for the fully-qualified name of the module.
|
||||
|
||||
.. attribute:: loader
|
||||
|
||||
(``__loader__``)
|
||||
|
||||
The loader to use for loading. For namespace packages this should be
|
||||
set to None.
|
||||
|
||||
.. attribute:: origin
|
||||
|
||||
(``__file__``)
|
||||
|
||||
Name of the place from which the module is loaded, e.g. "builtin" for
|
||||
built-in modules and the filename for modules loaded from source.
|
||||
Normally "origin" should be set, but it may be None (the default)
|
||||
which indicates it is unspecified.
|
||||
|
||||
.. attribute:: submodule_search_locations
|
||||
|
||||
(``__path__``)
|
||||
|
||||
List of strings for where to find submodules, if a package (None
|
||||
otherwise).
|
||||
|
||||
.. attribute:: loader_state
|
||||
|
||||
Container of extra module-specific data for use during loading (or
|
||||
None).
|
||||
|
||||
.. attribute:: cached
|
||||
|
||||
(``__cached__``)
|
||||
|
||||
String for where the compiled module should be stored (or None).
|
||||
|
||||
.. attribute:: parent
|
||||
|
||||
(``__package__``)
|
||||
|
||||
(Read-only) Fully-qualified name of the package to which the module
|
||||
belongs as a submodule (or None).
|
||||
|
||||
.. attribute:: has_location
|
||||
|
||||
(Read-only) Boolean indicating whether or not the module's "origin"
|
||||
attribute refers to a loadable location.
|
||||
|
||||
:mod:`importlib.util` -- Utility code for importers
|
||||
---------------------------------------------------
|
||||
|
||||
|
@ -952,20 +974,6 @@ an :term:`importer`.
|
|||
|
||||
.. versionadded:: 3.3
|
||||
|
||||
.. function:: module_to_load(name, *, reset_name=True)
|
||||
|
||||
Returns a :term:`context manager` which provides the module to load. The
|
||||
module will either come from :attr:`sys.modules` in the case of reloading or
|
||||
a fresh module if loading a new module. Proper cleanup of
|
||||
:attr:`sys.modules` occurs if the module was new and an exception was
|
||||
raised.
|
||||
|
||||
If **reset_name** is true and the module requested is being reloaded then
|
||||
the module's :attr:`__name__` attribute will
|
||||
be reset to **name**, else it will be left untouched.
|
||||
|
||||
.. versionadded:: 3.4
|
||||
|
||||
.. decorator:: module_for_loader
|
||||
|
||||
A :term:`decorator` for :meth:`importlib.abc.Loader.load_module`
|
||||
|
@ -999,9 +1007,8 @@ an :term:`importer`.
|
|||
unconditionally to support reloading.
|
||||
|
||||
.. deprecated:: 3.4
|
||||
For the benefit of :term:`loader` subclasses, please use
|
||||
:func:`module_to_load` and
|
||||
:meth:`importlib.abc.Loader.init_module_attrs` instead.
|
||||
The import machinery now directly performs all the functionality
|
||||
provided by this function.
|
||||
|
||||
.. decorator:: set_loader
|
||||
|
||||
|
@ -1012,11 +1019,6 @@ an :term:`importer`.
|
|||
the wrapped method (i.e. ``self``) is what :attr:`__loader__` should be set
|
||||
to.
|
||||
|
||||
.. note::
|
||||
As this decorator sets :attr:`__loader__` after loading the module, it is
|
||||
recommended to use :meth:`importlib.abc.Loader.init_module_attrs` instead
|
||||
when appropriate.
|
||||
|
||||
.. versionchanged:: 3.4
|
||||
Set ``__loader__`` if set to ``None``, as if the attribute does not
|
||||
exist.
|
||||
|
@ -1026,7 +1028,21 @@ an :term:`importer`.
|
|||
A :term:`decorator` for :meth:`importlib.abc.Loader.load_module` to set the :attr:`__package__` attribute on the returned module. If :attr:`__package__`
|
||||
is set and has a value other than ``None`` it will not be changed.
|
||||
|
||||
.. note::
|
||||
As this decorator sets :attr:`__package__` after loading the module, it is
|
||||
recommended to use :meth:`importlib.abc.Loader.init_module_attrs` instead
|
||||
when appropriate.
|
||||
.. function:: spec_from_loader(name, loader, *, origin=None, is_package=None)
|
||||
|
||||
A factory function for creating a :class:`ModuleSpec` instance based
|
||||
on a loader. The parameters have the same meaning as they do for
|
||||
ModuleSpec. The function uses available :term:`loader` APIs, such as
|
||||
:meth:`InspectLoader.is_package`, to fill in any missing
|
||||
information on the spec.
|
||||
|
||||
.. versionadded:: 3.4
|
||||
|
||||
.. function:: spec_from_file_location(name, location, *, loader=None, submodule_search_locations=None)
|
||||
|
||||
A factory function for creating a :class:`ModuleSpec` instance based
|
||||
on the path to a file. Missing information will be filled in on the
|
||||
spec by making use of loader APIs and by the implication that the
|
||||
module will be file-based.
|
||||
|
||||
.. versionadded:: 3.4
|
||||
|
|
|
@ -210,6 +210,7 @@ Finders and loaders
|
|||
.. index::
|
||||
single: finder
|
||||
single: loader
|
||||
single: module spec
|
||||
|
||||
If the named module is not found in :data:`sys.modules`, then Python's import
|
||||
protocol is invoked to find and load the module. This protocol consists of
|
||||
|
@ -230,13 +231,17 @@ The import machinery is extensible, so new finders can be added to extend the
|
|||
range and scope of module searching.
|
||||
|
||||
Finders do not actually load modules. If they can find the named module, they
|
||||
return a :term:`loader`, which the import machinery then invokes to load the
|
||||
module and create the corresponding module object.
|
||||
return a :term:`module spec`, an encapsulation of the module's import-related
|
||||
information, which the import machinery then uses when loading the module.
|
||||
|
||||
The following sections describe the protocol for finders and loaders in more
|
||||
detail, including how you can create and register new ones to extend the
|
||||
import machinery.
|
||||
|
||||
.. versionchanged:: 3.4
|
||||
In previous versions of Python, finders returned :term:`loaders <loader>`
|
||||
directly, whereas now they return module specs which *contain* loaders.
|
||||
Loaders are still used during import but have fewer responsibilities.
|
||||
|
||||
Import hooks
|
||||
------------
|
||||
|
@ -270,24 +275,23 @@ The meta path
|
|||
|
||||
.. index::
|
||||
single: sys.meta_path
|
||||
pair: finder; find_module
|
||||
pair: finder; find_loader
|
||||
pair: finder; find_spec
|
||||
|
||||
When the named module is not found in :data:`sys.modules`, Python next
|
||||
searches :data:`sys.meta_path`, which contains a list of meta path finder
|
||||
objects. These finders are queried in order to see if they know how to handle
|
||||
the named module. Meta path finders must implement a method called
|
||||
:meth:`find_module()` which takes two arguments, a name and an import path.
|
||||
:meth:`find_spec()` which takes two arguments, a name and an import path.
|
||||
The meta path finder can use any strategy it wants to determine whether it can
|
||||
handle the named module or not.
|
||||
|
||||
If the meta path finder knows how to handle the named module, it returns a
|
||||
loader object. If it cannot handle the named module, it returns ``None``. If
|
||||
spec object. If it cannot handle the named module, it returns ``None``. If
|
||||
:data:`sys.meta_path` processing reaches the end of its list without returning
|
||||
a loader, then an :exc:`ImportError` is raised. Any other exceptions raised
|
||||
a spec, then an :exc:`ImportError` is raised. Any other exceptions raised
|
||||
are simply propagated up, aborting the import process.
|
||||
|
||||
The :meth:`find_module()` method of meta path finders is called with two
|
||||
The :meth:`find_spec()` method of meta path finders is called with two
|
||||
arguments. The first is the fully qualified name of the module being
|
||||
imported, for example ``foo.bar.baz``. The second argument is the path
|
||||
entries to use for the module search. For top-level modules, the second
|
||||
|
@ -299,12 +303,12 @@ the appropriate ``__path__`` attribute cannot be accessed, an
|
|||
The meta path may be traversed multiple times for a single import request.
|
||||
For example, assuming none of the modules involved has already been cached,
|
||||
importing ``foo.bar.baz`` will first perform a top level import, calling
|
||||
``mpf.find_module("foo", None)`` on each meta path finder (``mpf``). After
|
||||
``mpf.find_spec("foo", None)`` on each meta path finder (``mpf``). After
|
||||
``foo`` has been imported, ``foo.bar`` will be imported by traversing the
|
||||
meta path a second time, calling
|
||||
``mpf.find_module("foo.bar", foo.__path__)``. Once ``foo.bar`` has been
|
||||
``mpf.find_spec("foo.bar", foo.__path__)``. Once ``foo.bar`` has been
|
||||
imported, the final traversal will call
|
||||
``mpf.find_module("foo.bar.baz", foo.bar.__path__)``.
|
||||
``mpf.find_spec("foo.bar.baz", foo.bar.__path__)``.
|
||||
|
||||
Some meta path finders only support top level imports. These importers will
|
||||
always return ``None`` when anything other than ``None`` is passed as the
|
||||
|
@ -315,131 +319,229 @@ knows how to import built-in modules, one that knows how to import frozen
|
|||
modules, and one that knows how to import modules from an :term:`import path`
|
||||
(i.e. the :term:`path based finder`).
|
||||
|
||||
.. versionchanged:: 3.4
|
||||
The find_spec() method of meta path finders replaced :meth:`find_module()`.
|
||||
which is now deprecated. While it will continue to work without change,
|
||||
the import machinery will try it only if the finder does not implement
|
||||
find_spec().
|
||||
|
||||
Loaders
|
||||
|
||||
Loading
|
||||
=======
|
||||
|
||||
If and when a module loader is found its
|
||||
:meth:`~importlib.abc.Loader.load_module` method is called, with a single
|
||||
argument, the fully qualified name of the module being imported. This method
|
||||
has several responsibilities, and should return the module object it has
|
||||
loaded [#fnlo]_. If it cannot load the module, it should raise an
|
||||
:exc:`ImportError`, although any other exception raised during
|
||||
:meth:`load_module()` will be propagated.
|
||||
If and when a module spec is found, the import machinery will use it (and
|
||||
the loader it contains) when loading the module. Here is an approximation
|
||||
of what happens during the loading portion of import::
|
||||
|
||||
In many cases, the finder and loader can be the same object; in such cases the
|
||||
:meth:`finder.find_module()` would just return ``self``.
|
||||
module = None
|
||||
if spec.loader is not None and hasattr(spec.loader, 'create_module'):
|
||||
module = spec.loader.create_module(spec)
|
||||
if module is None:
|
||||
module = ModuleType(spec.name)
|
||||
# The import-related module attributes get set here:
|
||||
_init_module_attrs(spec, module)
|
||||
|
||||
Loaders must satisfy the following requirements:
|
||||
if spec.loader is None:
|
||||
if spec.submodule_search_locations is not None:
|
||||
# namespace package
|
||||
sys.modules[spec.name] = module
|
||||
else:
|
||||
# unsupported
|
||||
raise ImportError
|
||||
elif not hasattr(spec.loader, 'exec_module'):
|
||||
module = spec.loader.load_module(spec.name)
|
||||
else:
|
||||
sys.modules[spec.name] = module
|
||||
try:
|
||||
spec.loader.exec_module(module)
|
||||
except BaseException:
|
||||
try:
|
||||
del sys.modules[spec.name]
|
||||
except KeyError:
|
||||
pass
|
||||
raise
|
||||
module_to_return = sys.modules[spec.name]
|
||||
|
||||
Note the following details:
|
||||
|
||||
* If there is an existing module object with the given name in
|
||||
:data:`sys.modules`, the loader must use that existing module. (Otherwise,
|
||||
:func:`imp.reload` will not work correctly.) If the named module does
|
||||
not exist in :data:`sys.modules`, the loader must create a new module
|
||||
object and add it to :data:`sys.modules`.
|
||||
:data:`sys.modules`, import will have already returned it.
|
||||
|
||||
Note that the module *must* exist in :data:`sys.modules` before the loader
|
||||
* The module will exist in :data:`sys.modules` before the loader
|
||||
executes the module code. This is crucial because the module code may
|
||||
(directly or indirectly) import itself; adding it to :data:`sys.modules`
|
||||
beforehand prevents unbounded recursion in the worst case and multiple
|
||||
loading in the best.
|
||||
|
||||
If loading fails, the loader must remove any modules it has inserted into
|
||||
:data:`sys.modules`, but it must remove **only** the failing module, and
|
||||
only if the loader itself has loaded it explicitly. Any module already in
|
||||
the :data:`sys.modules` cache, and any module that was successfully loaded
|
||||
as a side-effect, must remain in the cache.
|
||||
* If loading fails, the failing module -- and only the failing module --
|
||||
gets removed from :data:`sys.modules`. Any module already in the
|
||||
:data:`sys.modules` cache, and any module that was successfully loaded
|
||||
as a side-effect, must remain in the cache. This contrasts with
|
||||
reloading where even the failing module is left in :data:`sys.modules`.
|
||||
|
||||
* The loader may set the ``__file__`` attribute of the module. If set, this
|
||||
attribute's value must be a string. The loader may opt to leave
|
||||
``__file__`` unset if it has no semantic meaning (e.g. a module loaded from
|
||||
a database). If ``__file__`` is set, it may also be appropriate to set the
|
||||
``__cached__`` attribute which is the path to any compiled version of the
|
||||
code (e.g. byte-compiled file). The file does not need to exist to set this
|
||||
attribute; the path can simply point to whether the compiled file would
|
||||
exist (see :pep:`3147`).
|
||||
* After the module is created but before execution, the import machinery
|
||||
sets the import-related module attributes ("init_module_attrs"), as
|
||||
summarized in a `later section <Import-related module attributes>`_.
|
||||
|
||||
* The loader may set the ``__name__`` attribute of the module. While not
|
||||
required, setting this attribute is highly recommended so that the
|
||||
:meth:`repr()` of the module is more informative.
|
||||
* Module execution is the key moment of loading in which the module's
|
||||
namespace gets populated. Execution is entirely delegated to the
|
||||
loader, which gets to decide what gets populated and how.
|
||||
|
||||
* If the module is a package (either regular or namespace), the loader must
|
||||
set the module object's ``__path__`` attribute. The value must be
|
||||
iterable, but may be empty if ``__path__`` has no further significance
|
||||
to the loader. If ``__path__`` is not empty, it must produce strings
|
||||
when iterated over. More details on the semantics of ``__path__`` are
|
||||
given :ref:`below <package-path-rules>`.
|
||||
* The module created during loading and passed to exec_module() may
|
||||
not be the one returned at the end of import [#fnlo]_.
|
||||
|
||||
* The ``__loader__`` attribute must be set to the loader object that loaded
|
||||
the module. This is mostly for introspection and reloading, but can be
|
||||
used for additional loader-specific functionality, for example getting
|
||||
data associated with a loader. If the attribute is missing or set to ``None``
|
||||
then the import machinery will automatically set it **after** the module has
|
||||
been imported.
|
||||
.. versionchanged:: 3.4
|
||||
The import system has taken over the boilerplate responsibilities of
|
||||
loaders. These were previously performed by the :meth:`load_module()`
|
||||
method.
|
||||
|
||||
* The module's ``__package__`` attribute must be set. Its value must be a
|
||||
string, but it can be the same value as its ``__name__``. If the attribute
|
||||
is set to ``None`` or is missing, the import system will fill it in with a
|
||||
more appropriate value **after** the module has been imported.
|
||||
When the module is a package, its ``__package__`` value should be set to its
|
||||
``__name__``. When the module is not a package, ``__package__`` should be
|
||||
set to the empty string for top-level modules, or for submodules, to the
|
||||
parent package's name. See :pep:`366` for further details.
|
||||
Loaders
|
||||
-------
|
||||
|
||||
This attribute is used instead of ``__name__`` to calculate explicit
|
||||
relative imports for main modules, as defined in :pep:`366`.
|
||||
Module loaders provide the critical function of loading: module execution.
|
||||
The import machinery calls the :meth:`~importlib.abc.Loader.exec_module()`
|
||||
method with a single argument, the module object to execute. Any value
|
||||
returned from exec_module() is ignored.
|
||||
|
||||
Loaders must satisfy the following requirements:
|
||||
|
||||
* If the module is a Python module (as opposed to a built-in module or a
|
||||
dynamically loaded extension), the loader should execute the module's code
|
||||
in the module's global name space (``module.__dict__``).
|
||||
|
||||
* If loader cannot execute the module, it should raise an
|
||||
:exc:`ImportError`, although any other exception raised during
|
||||
:meth:`exec_module()` will be propagated.
|
||||
|
||||
Module reprs
|
||||
------------
|
||||
In many cases, the finder and loader can be the same object; in such cases the
|
||||
:meth:`finder.find_spec()` would just return a spec with the loader set
|
||||
to ``self``.
|
||||
|
||||
By default, all modules have a usable repr, however depending on the
|
||||
attributes set above, and hooks in the loader, you can more explicitly control
|
||||
the repr of module objects.
|
||||
Module loaders may opt in to creating the module object during loading
|
||||
by implementing a :meth:`create_module()` method. It takes one argument,
|
||||
the module spec, and returns the new module object to use during loading.
|
||||
create_module() does not need to set any attributes on the module object.
|
||||
If the loader does not define create_module(), the import machinery will
|
||||
create the new module itself.
|
||||
|
||||
Loaders may implement a :meth:`module_repr()` method which takes a single
|
||||
argument, the module object. When ``repr(module)`` is called for a module
|
||||
with a loader supporting this protocol, whatever is returned from
|
||||
``module.__loader__.module_repr(module)`` is returned as the module's repr
|
||||
without further processing. This return value must be a string.
|
||||
.. versionadded:: 3.4
|
||||
The create_module() method of loaders.
|
||||
|
||||
If the module has no ``__loader__`` attribute, or the loader has no
|
||||
:meth:`module_repr()` method, then the module object implementation itself
|
||||
will craft a default repr using whatever information is available. It will
|
||||
try to use the ``module.__name__``, ``module.__file__``, and
|
||||
``module.__loader__`` as input into the repr, with defaults for whatever
|
||||
information is missing.
|
||||
.. versionchanged:: 3.4
|
||||
The load_module() method was replaced by exec_module() and the import
|
||||
machinery assumed all the boilerplate responsibilities of loading.
|
||||
|
||||
Here are the exact rules used:
|
||||
For compatibility with existing loaders, the import machinery will use
|
||||
the :meth:`~importlib.abc.Loader.load_module()` method of loaders if it
|
||||
exists and the loader does not also implement exec_module(). However,
|
||||
load_module() has been deprecated and loaders should implement
|
||||
exec_module() instead.
|
||||
|
||||
* If the module has a ``__loader__`` and that loader has a
|
||||
:meth:`module_repr()` method, call it with a single argument, which is the
|
||||
module object. The value returned is used as the module's repr.
|
||||
The load_module() method must implement all the boilerplate loading
|
||||
functionality described above in addition to executing the module. All
|
||||
the same constraints apply, with some additional clarification:
|
||||
|
||||
* If an exception occurs in :meth:`module_repr()`, the exception is caught
|
||||
and discarded, and the calculation of the module's repr continues as if
|
||||
:meth:`module_repr()` did not exist.
|
||||
* If there is an existing module object with the given name in
|
||||
:data:`sys.modules`, the loader must use that existing module.
|
||||
(Otherwise, :func:`imp.reload` will not work correctly.) If the
|
||||
named module does not exist in :data:`sys.modules`, the loader
|
||||
must create a new module object and add it to :data:`sys.modules`.
|
||||
|
||||
* If the module has a ``__file__`` attribute, this is used as part of the
|
||||
module's repr.
|
||||
* The module *must* exist in :data:`sys.modules` before the loader
|
||||
executes the module code, to prevent unbounded recursion or multiple
|
||||
loading.
|
||||
|
||||
* If the module has no ``__file__`` but does have a ``__loader__`` that is not
|
||||
``None``, then the loader's repr is used as part of the module's repr.
|
||||
* If loading fails, the loader must remove any modules it has inserted
|
||||
into :data:`sys.modules`, but it must remove **only** the failing
|
||||
module, and only if the loader itself has loaded it explicitly.
|
||||
|
||||
* Otherwise, just use the module's ``__name__`` in the repr.
|
||||
Module spec
|
||||
-----------
|
||||
|
||||
This example, from :pep:`420` shows how a loader can craft its own module
|
||||
repr::
|
||||
The import machinery uses a variety of information about each module
|
||||
during import, especially before loading. Most of the information is
|
||||
common to all modules. The purpose of a module's spec is to encapsulate
|
||||
this import-related information on a per-module basis.
|
||||
|
||||
class NamespaceLoader:
|
||||
@classmethod
|
||||
def module_repr(cls, module):
|
||||
return "<module '{}' (namespace)>".format(module.__name__)
|
||||
Using a spec during import allows state to be transferred between import
|
||||
system components, e.g. between the finder that creates the module spec
|
||||
and the loader that executes it. Most importantly, it allows the
|
||||
import machinery to perform the boilerplate operations of loading,
|
||||
whereas without a module spec the loader had that responsibility.
|
||||
|
||||
See :class:`~importlib.machinery.ModuleSpec` for more specifics on what
|
||||
information a module's spec may hold.
|
||||
|
||||
.. versionadded:: 3.4
|
||||
|
||||
Import-related module attributes
|
||||
--------------------------------
|
||||
|
||||
The import machinery fills in these attributes on each module object
|
||||
during loading, based on the module's spec, before the loader executes
|
||||
the module.
|
||||
|
||||
.. attribute:: __name__
|
||||
|
||||
The ``__name__`` attribute must be set to the fully-qualified name of
|
||||
the module. This name is used to uniquely identify the module in
|
||||
the import system.
|
||||
|
||||
.. attribute:: __loader__
|
||||
|
||||
The ``__loader__`` attribute must be set to the loader object that
|
||||
the import machinery used when loading the module. This is mostly
|
||||
for introspection, but can be used for additional loader-specific
|
||||
functionality, for example getting data associated with a loader.
|
||||
|
||||
.. attribute:: __package__
|
||||
|
||||
The module's ``__package__`` attribute must be set. Its value must
|
||||
be a string, but it can be the same value as its ``__name__``. When
|
||||
the module is a package, its ``__package__`` value should be set to
|
||||
its ``__name__``. When the module is not a package, ``__package__``
|
||||
should be set to the empty string for top-level modules, or for
|
||||
submodules, to the parent package's name. See :pep:`366` for further
|
||||
details.
|
||||
|
||||
This attribute is used instead of ``__name__`` to calculate explicit
|
||||
relative imports for main modules, as defined in :pep:`366`.
|
||||
|
||||
.. attribute:: __spec__
|
||||
|
||||
The ``__spec__`` attribute must be set to the module spec that was
|
||||
used when importing the module. This is used primarily for
|
||||
introspection and during reloading.
|
||||
|
||||
.. attribute:: __path__
|
||||
|
||||
If the module is a package (either regular or namespace), the module
|
||||
object's ``__path__`` attribute must be set. The value must be
|
||||
iterable, but may be empty if ``__path__`` has no further significance.
|
||||
If ``__path__`` is not empty, it must produce strings when iterated
|
||||
over. More details on the semantics of ``__path__`` are given
|
||||
:ref:`below <package-path-rules>`.
|
||||
|
||||
Non-package modules should not have a ``__path__`` attribute.
|
||||
|
||||
.. attribute:: __file__
|
||||
.. attribute:: __cached__
|
||||
|
||||
``__file__`` is optional. If set, this attribute's value must be a
|
||||
string. The import system may opt to leave ``__file__`` unset if it
|
||||
has no semantic meaning (e.g. a module loaded from a database).
|
||||
|
||||
If ``__file__`` is set, it may also be appropriate to set the
|
||||
``__cached__`` attribute which is the path to any compiled version of
|
||||
the code (e.g. byte-compiled file). The file does not need to exist
|
||||
to set this attribute; the path can simply point to where the
|
||||
compiled file would exist (see :pep:`3147`).
|
||||
|
||||
It is also appropriate to set ``__cached__`` when ``__file__`` is not
|
||||
set. However, that scenario is quite atypical. Ultimately, the
|
||||
loader is what makes use of ``__file__`` and/or ``__cached__``. So
|
||||
if a loader can load from a cached module but otherwise does not load
|
||||
from a file, that atypical scenario may be appropriate.
|
||||
|
||||
.. _package-path-rules:
|
||||
|
||||
|
@ -464,9 +566,46 @@ A package's ``__init__.py`` file may set or alter the package's ``__path__``
|
|||
attribute, and this was typically the way namespace packages were implemented
|
||||
prior to :pep:`420`. With the adoption of :pep:`420`, namespace packages no
|
||||
longer need to supply ``__init__.py`` files containing only ``__path__``
|
||||
manipulation code; the namespace loader automatically sets ``__path__``
|
||||
manipulation code; the import machinery automatically sets ``__path__``
|
||||
correctly for the namespace package.
|
||||
|
||||
Module reprs
|
||||
------------
|
||||
|
||||
By default, all modules have a usable repr, however depending on the
|
||||
attributes set above, and in the module's spec, you can more explicitly
|
||||
control the repr of module objects.
|
||||
|
||||
If the module has a spec (``__spec__``), the import machinery will try
|
||||
to generate a repr from it. If that fails or there is no spec, the import
|
||||
system will craft a default repr using whatever information is available
|
||||
on the module. It will try to use the ``module.__name__``,
|
||||
``module.__file__``, and ``module.__loader__`` as input into the repr,
|
||||
with defaults for whatever information is missing.
|
||||
|
||||
Here are the exact rules used:
|
||||
|
||||
* If the module has a ``__spec__`` attribute, the information in the spec
|
||||
is used to generate the repr. The "name", "loader", "origin", and
|
||||
"has_location" attributes are consulted.
|
||||
|
||||
* If the module has a ``__file__`` attribute, this is used as part of the
|
||||
module's repr.
|
||||
|
||||
* If the module has no ``__file__`` but does have a ``__loader__`` that is not
|
||||
``None``, then the loader's repr is used as part of the module's repr.
|
||||
|
||||
* Otherwise, just use the module's ``__name__`` in the repr.
|
||||
|
||||
.. versionchanged:: 3.4
|
||||
Use of loader.module_repr() has been deprecated and the module spec
|
||||
is now used by the import machinery to generate a module repr.
|
||||
|
||||
For backward compatibility with Python 3.3, the module repr will be
|
||||
generated by calling the loader's :meth:`module_repr()` method, if
|
||||
defined, before trying either approach described above. However, the
|
||||
method is deprecated.
|
||||
|
||||
|
||||
The Path Based Finder
|
||||
=====================
|
||||
|
@ -531,7 +670,7 @@ entry`. Most path entries name locations in the file system, but they need
|
|||
not be limited to this.
|
||||
|
||||
As a meta path finder, the :term:`path based finder` implements the
|
||||
:meth:`find_module()` protocol previously described, however it exposes
|
||||
:meth:`find_spec()` protocol previously described, however it exposes
|
||||
additional hooks that can be used to customize how modules are found and
|
||||
loaded from the :term:`import path`.
|
||||
|
||||
|
@ -553,8 +692,8 @@ finder>`.
|
|||
|
||||
The :term:`path based finder` is a :term:`meta path finder`, so the import
|
||||
machinery begins the :term:`import path` search by calling the path
|
||||
based finder's :meth:`find_module()` method as described previously. When
|
||||
the ``path`` argument to :meth:`find_module()` is given, it will be a
|
||||
based finder's :meth:`find_spec()` method as described previously. When
|
||||
the ``path`` argument to :meth:`find_spec()` is given, it will be a
|
||||
list of string paths to traverse - typically a package's ``__path__``
|
||||
attribute for an import within that package. If the ``path`` argument
|
||||
is ``None``, this indicates a top level import and :data:`sys.path` is used.
|
||||
|
@ -585,51 +724,70 @@ encoding, UTF-8, or something else), and if the hook cannot decode the
|
|||
argument, it should raise :exc:`ImportError`.
|
||||
|
||||
If :data:`sys.path_hooks` iteration ends with no :term:`path entry finder`
|
||||
being returned, then the path based finder's :meth:`find_module()` method
|
||||
being returned, then the path based finder's :meth:`find_spec()` method
|
||||
will store ``None`` in :data:`sys.path_importer_cache` (to indicate that
|
||||
there is no finder for this path entry) and return ``None``, indicating that
|
||||
this :term:`meta path finder` could not find the module.
|
||||
|
||||
If a :term:`path entry finder` *is* returned by one of the :term:`path entry
|
||||
hook` callables on :data:`sys.path_hooks`, then the following protocol is used
|
||||
to ask the finder for a module loader, which is then used to load the module.
|
||||
|
||||
to ask the finder for a module spec, which is then used when loading the
|
||||
module.
|
||||
|
||||
Path entry finder protocol
|
||||
--------------------------
|
||||
|
||||
In order to support imports of modules and initialized packages and also to
|
||||
contribute portions to namespace packages, path entry finders must implement
|
||||
the :meth:`find_loader()` method.
|
||||
the :meth:`find_spec()` method.
|
||||
|
||||
:meth:`find_loader()` takes one argument, the fully qualified name of the
|
||||
module being imported. :meth:`find_loader()` returns a 2-tuple where the
|
||||
first item is the loader and the second item is a namespace :term:`portion`.
|
||||
When the first item (i.e. the loader) is ``None``, this means that while the
|
||||
path entry finder does not have a loader for the named module, it knows that the
|
||||
path entry contributes to a namespace portion for the named module. This will
|
||||
almost always be the case where Python is asked to import a namespace package
|
||||
that has no physical presence on the file system. When a path entry finder
|
||||
returns ``None`` for the loader, the second item of the 2-tuple return value
|
||||
must be a sequence, although it can be empty.
|
||||
:meth:`find_spec()` takes one argument, the fully qualified name of the
|
||||
module being imported. :meth:`find_spec()` returns a fully populated
|
||||
spec for the module. This spec will always have "loader" set (with one
|
||||
exception).
|
||||
|
||||
If :meth:`find_loader()` returns a non-``None`` loader value, the portion is
|
||||
ignored and the loader is returned from the path based finder, terminating
|
||||
the search through the path entries.
|
||||
To indicate to the import machinery that the spec represents a namespace
|
||||
:term:`portion`. the path entry finder sets "loader" on the spec to
|
||||
``None`` and "submodule_search_locations" to a list containing the
|
||||
portion.
|
||||
|
||||
For backwards compatibility with other implementations of the import
|
||||
protocol, many path entry finders also support the same,
|
||||
traditional :meth:`find_module()` method that meta path finders support.
|
||||
However path entry finder :meth:`find_module()` methods are never called
|
||||
with a ``path`` argument (they are expected to record the appropriate
|
||||
path information from the initial call to the path hook).
|
||||
.. versionchanged:: 3.4
|
||||
find_spec() replaced find_loader() and find_module(), but of which
|
||||
are now deprecated, but will be used if find_spec() is not defined.
|
||||
|
||||
The :meth:`find_module()` method on path entry finders is deprecated,
|
||||
as it does not allow the path entry finder to contribute portions to
|
||||
namespace packages. Instead path entry finders should implement the
|
||||
:meth:`find_loader()` method as described above. If it exists on the path
|
||||
entry finder, the import system will always call :meth:`find_loader()`
|
||||
in preference to :meth:`find_module()`.
|
||||
Older path entry finders may implement one of these two deprecated methods
|
||||
instead of :meth:`find_spec()`. The methods are still respected for the
|
||||
sake of backward compatibility. Howevever, if find_spec() is implemented
|
||||
on the path entry finder, the legacy methods are ignored.
|
||||
|
||||
:meth:`find_loader()` takes one argument, the fully qualified name of the
|
||||
module being imported. :meth:`find_loader()` returns a 2-tuple where the
|
||||
first item is the loader and the second item is a namespace :term:`portion`.
|
||||
When the first item (i.e. the loader) is ``None``, this means that while the
|
||||
path entry finder does not have a loader for the named module, it knows that
|
||||
the path entry contributes to a namespace portion for the named module.
|
||||
This will almost always be the case where Python is asked to import a
|
||||
namespace package that has no physical presence on the file system.
|
||||
When a path entry finder returns ``None`` for the loader, the second
|
||||
item of the 2-tuple return value must be a sequence, although it can be
|
||||
empty.
|
||||
|
||||
If :meth:`find_loader()` returns a non-``None`` loader value, the portion is
|
||||
ignored and the loader is returned from the path based finder, terminating
|
||||
the search through the path entries.
|
||||
|
||||
For backwards compatibility with other implementations of the import
|
||||
protocol, many path entry finders also support the same,
|
||||
traditional :meth:`find_module()` method that meta path finders support.
|
||||
However path entry finder :meth:`find_module()` methods are never called
|
||||
with a ``path`` argument (they are expected to record the appropriate
|
||||
path information from the initial call to the path hook).
|
||||
|
||||
The :meth:`find_module()` method on path entry finders is deprecated,
|
||||
as it does not allow the path entry finder to contribute portions to
|
||||
namespace packages. If both :meth:`find_loader()` and :meth:`find_module()`
|
||||
exist on a path entry finder, the import system will always call
|
||||
:meth:`find_loader()` in preference to :meth:`find_module()`.
|
||||
|
||||
|
||||
Replacing the standard import system
|
||||
|
@ -648,7 +806,7 @@ import statements within that module.
|
|||
To selectively prevent import of some modules from a hook early on the
|
||||
meta path (rather than disabling the standard import system entirely),
|
||||
it is sufficient to raise :exc:`ImportError` directly from
|
||||
:meth:`find_module` instead of returning ``None``. The latter indicates
|
||||
:meth:`find_spec` instead of returning ``None``. The latter indicates
|
||||
that the meta path search should continue. while raising an exception
|
||||
terminates it immediately.
|
||||
|
||||
|
@ -690,6 +848,11 @@ proposed ``__name__`` for semantics :pep:`366` would eventually specify for
|
|||
|
||||
:pep:`338` defines executing modules as scripts.
|
||||
|
||||
:pep:`451` adds the encapsulation of per-module import state in spec
|
||||
objects. It also off-loads most of the boilerplate responsibilities of
|
||||
loaders back onto the import machinery. These changes allow the
|
||||
deprecation of several APIs in the import system and also addition of new
|
||||
methods to finders and loaders.
|
||||
|
||||
.. rubric:: Footnotes
|
||||
|
||||
|
|
|
@ -236,6 +236,26 @@ name of the codec responsible for producing the error::
|
|||
(Contributed by Nick Coghlan in :issue:`17827`, :issue:`17828` and
|
||||
:issue:`19619`)
|
||||
|
||||
.. _pep-451:
|
||||
|
||||
PEP 451: A ModuleSpec Type for the Import System
|
||||
================================================
|
||||
|
||||
:pep:`451` provides an encapsulation of the information about a module
|
||||
that the import machinery will use to load it, (i.e. a module spec).
|
||||
This helps simplify both the import implementation and several
|
||||
import-related APIs. The change is also a stepping stone for several
|
||||
future import-related improvements.
|
||||
|
||||
https://mail.python.org/pipermail/python-dev/2013-November/130111.html
|
||||
|
||||
The public-facing changes from the PEP are entirely backward-compatible.
|
||||
Furthermore, they should be transparent to everyone but importer
|
||||
authors. Key finder and loader methods have been deprecated, but they
|
||||
will continue working. New importers should use the new methods
|
||||
described in the PEP. Existing importers should be updated to implement
|
||||
the new methods.
|
||||
|
||||
|
||||
Other Language Changes
|
||||
======================
|
||||
|
|
30
Lib/imp.py
30
Lib/imp.py
|
@ -16,7 +16,7 @@ except ImportError:
|
|||
# Platform doesn't support dynamic loading.
|
||||
load_dynamic = None
|
||||
|
||||
from importlib._bootstrap import SourcelessFileLoader, _ERR_MSG
|
||||
from importlib._bootstrap import SourcelessFileLoader, _ERR_MSG, _SpecMethods
|
||||
|
||||
from importlib import machinery
|
||||
from importlib import util
|
||||
|
@ -162,11 +162,17 @@ class _LoadSourceCompatibility(_HackedGetData, machinery.SourceFileLoader):
|
|||
|
||||
|
||||
def load_source(name, pathname, file=None):
|
||||
_LoadSourceCompatibility(name, pathname, file).load_module(name)
|
||||
module = sys.modules[name]
|
||||
loader = _LoadSourceCompatibility(name, pathname, file)
|
||||
spec = util.spec_from_file_location(name, pathname, loader=loader)
|
||||
methods = _SpecMethods(spec)
|
||||
if name in sys.modules:
|
||||
module = methods.exec(sys.modules[name])
|
||||
else:
|
||||
module = methods.load()
|
||||
# To allow reloading to potentially work, use a non-hacked loader which
|
||||
# won't rely on a now-closed file object.
|
||||
module.__loader__ = machinery.SourceFileLoader(name, pathname)
|
||||
module.__spec__.loader = module.__loader__
|
||||
return module
|
||||
|
||||
|
||||
|
@ -177,11 +183,17 @@ class _LoadCompiledCompatibility(_HackedGetData, SourcelessFileLoader):
|
|||
|
||||
def load_compiled(name, pathname, file=None):
|
||||
"""**DEPRECATED**"""
|
||||
_LoadCompiledCompatibility(name, pathname, file).load_module(name)
|
||||
module = sys.modules[name]
|
||||
loader = _LoadCompiledCompatibility(name, pathname, file)
|
||||
spec = util.spec_from_file_location(name, pathname, loader=loader)
|
||||
methods = _SpecMethods(spec)
|
||||
if name in sys.modules:
|
||||
module = methods.exec(sys.modules[name])
|
||||
else:
|
||||
module = methods.load()
|
||||
# To allow reloading to potentially work, use a non-hacked loader which
|
||||
# won't rely on a now-closed file object.
|
||||
module.__loader__ = SourcelessFileLoader(name, pathname)
|
||||
module.__spec__.loader = module.__loader__
|
||||
return module
|
||||
|
||||
|
||||
|
@ -196,7 +208,13 @@ def load_package(name, path):
|
|||
break
|
||||
else:
|
||||
raise ValueError('{!r} is not a package'.format(path))
|
||||
return machinery.SourceFileLoader(name, path).load_module(name)
|
||||
spec = util.spec_from_file_location(name, path,
|
||||
submodule_search_locations=[])
|
||||
methods = _SpecMethods(spec)
|
||||
if name in sys.modules:
|
||||
return methods.exec(sys.modules[name])
|
||||
else:
|
||||
return methods.load()
|
||||
|
||||
|
||||
def load_module(name, file, filename, details):
|
||||
|
|
|
@ -46,19 +46,42 @@ def invalidate_caches():
|
|||
finder.invalidate_caches()
|
||||
|
||||
|
||||
def find_loader(name, path=None):
|
||||
"""Find the loader for the specified module.
|
||||
def find_spec(name, path=None):
|
||||
"""Return the spec for the specified module.
|
||||
|
||||
First, sys.modules is checked to see if the module was already imported. If
|
||||
so, then sys.modules[name].__loader__ is returned. If that happens to be
|
||||
so, then sys.modules[name].__spec__ is returned. If that happens to be
|
||||
set to None, then ValueError is raised. If the module is not in
|
||||
sys.modules, then sys.meta_path is searched for a suitable loader with the
|
||||
value of 'path' given to the finders. None is returned if no loader could
|
||||
sys.modules, then sys.meta_path is searched for a suitable spec with the
|
||||
value of 'path' given to the finders. None is returned if no spec could
|
||||
be found.
|
||||
|
||||
Dotted names do not have their parent packages implicitly imported. You will
|
||||
most likely need to explicitly import all parent packages in the proper
|
||||
order for a submodule to get the correct loader.
|
||||
order for a submodule to get the correct spec.
|
||||
|
||||
"""
|
||||
if name not in sys.modules:
|
||||
return _bootstrap._find_spec(name, path)
|
||||
else:
|
||||
module = sys.modules[name]
|
||||
if module is None:
|
||||
return None
|
||||
try:
|
||||
spec = module.__spec__
|
||||
except AttributeError:
|
||||
raise ValueError('{}.__spec__ is not set'.format(name))
|
||||
else:
|
||||
if spec is None:
|
||||
raise ValueError('{}.__spec__ is None'.format(name))
|
||||
return spec
|
||||
|
||||
|
||||
# XXX Deprecate...
|
||||
def find_loader(name, path=None):
|
||||
"""Return the loader for the specified module.
|
||||
|
||||
This is a backward-compatible wrapper around find_spec().
|
||||
|
||||
"""
|
||||
try:
|
||||
|
@ -71,7 +94,18 @@ def find_loader(name, path=None):
|
|||
pass
|
||||
except AttributeError:
|
||||
raise ValueError('{}.__loader__ is not set'.format(name))
|
||||
return _bootstrap._find_module(name, path)
|
||||
|
||||
spec = _bootstrap._find_spec(name, path)
|
||||
# We won't worry about malformed specs (missing attributes).
|
||||
if spec is None:
|
||||
return None
|
||||
if spec.loader is None:
|
||||
if spec.submodule_search_locations is None:
|
||||
raise ImportError('spec for {} missing loader'.format(name),
|
||||
name=name)
|
||||
raise ImportError('namespace packages do not have loaders',
|
||||
name=name)
|
||||
return spec.loader
|
||||
|
||||
|
||||
def import_module(name, package=None):
|
||||
|
@ -106,7 +140,11 @@ def reload(module):
|
|||
"""
|
||||
if not module or not isinstance(module, types.ModuleType):
|
||||
raise TypeError("reload() argument must be module")
|
||||
try:
|
||||
name = module.__spec__.name
|
||||
except AttributeError:
|
||||
name = module.__name__
|
||||
|
||||
if sys.modules.get(name) is not module:
|
||||
msg = "module {} not in sys.modules"
|
||||
raise ImportError(msg.format(name), name=name)
|
||||
|
@ -118,13 +156,11 @@ def reload(module):
|
|||
if parent_name and parent_name not in sys.modules:
|
||||
msg = "parent {!r} not in sys.modules"
|
||||
raise ImportError(msg.format(parent_name), name=parent_name)
|
||||
loader = _bootstrap._find_module(name, None)
|
||||
if loader is None:
|
||||
raise ImportError(_bootstrap._ERR_MSG.format(name), name=name)
|
||||
module.__loader__ = loader
|
||||
loader.load_module(name)
|
||||
spec = module.__spec__ = _bootstrap._find_spec(name, None, module)
|
||||
methods = _bootstrap._SpecMethods(spec)
|
||||
methods.exec(module)
|
||||
# The module may have replaced itself in sys.modules!
|
||||
return sys.modules[module.__name__]
|
||||
return sys.modules[name]
|
||||
finally:
|
||||
try:
|
||||
del _RELOADING[name]
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -40,12 +40,18 @@ class MetaPathFinder(Finder):
|
|||
|
||||
"""Abstract base class for import finders on sys.meta_path."""
|
||||
|
||||
@abc.abstractmethod
|
||||
# We don't define find_spec() here since that would break
|
||||
# hasattr checks we do to support backward compatibility.
|
||||
|
||||
# XXX Deprecate
|
||||
def find_module(self, fullname, path):
|
||||
"""Abstract method which, when implemented, should find a module.
|
||||
The fullname is a str and the path is a list of strings or None.
|
||||
Returns a Loader object or None.
|
||||
"""Return a loader for the module.
|
||||
|
||||
If no module is found, return None. The fullname is a str and
|
||||
the path is a list of strings or None.
|
||||
|
||||
"""
|
||||
return None
|
||||
|
||||
def invalidate_caches(self):
|
||||
"""An optional method for clearing the finder's cache, if any.
|
||||
|
@ -60,17 +66,25 @@ class PathEntryFinder(Finder):
|
|||
|
||||
"""Abstract base class for path entry finders used by PathFinder."""
|
||||
|
||||
@abc.abstractmethod
|
||||
# We don't define find_spec() here since that would break
|
||||
# hasattr checks we do to support backward compatibility.
|
||||
|
||||
# XXX Deprecate.
|
||||
def find_loader(self, fullname):
|
||||
"""Abstract method which, when implemented, returns a module loader or
|
||||
a possible part of a namespace.
|
||||
The fullname is a str. Returns a 2-tuple of (Loader, portion) where
|
||||
portion is a sequence of file system locations contributing to part of
|
||||
a namespace package. The sequence may be empty and the loader may be
|
||||
None.
|
||||
"""Return (loader, namespace portion) for the path entry.
|
||||
|
||||
The fullname is a str. The namespace portion is a sequence of
|
||||
path entries contributing to part of a namespace package. The
|
||||
sequence may be empty. If loader is not None, the portion will
|
||||
be ignored.
|
||||
|
||||
The portion will be discarded if another path entry finder
|
||||
locates the module as a normal module or package.
|
||||
|
||||
"""
|
||||
return None, []
|
||||
|
||||
# XXX Deprecate.
|
||||
find_module = _bootstrap._find_module_shim
|
||||
|
||||
def invalidate_caches(self):
|
||||
|
@ -83,34 +97,46 @@ _register(PathEntryFinder, machinery.FileFinder)
|
|||
|
||||
class Loader(metaclass=abc.ABCMeta):
|
||||
|
||||
"""Abstract base class for import loaders.
|
||||
"""Abstract base class for import loaders."""
|
||||
|
||||
The optional method module_repr(module) may be defined to provide a
|
||||
repr for a module when appropriate (see PEP 420). The __repr__() method on
|
||||
the module type will use the method as appropriate.
|
||||
def create_module(self, spec):
|
||||
"""Return a module to initialize and into which to load.
|
||||
|
||||
This method should raise ImportError if anything prevents it
|
||||
from creating a new module. It may return None to indicate
|
||||
that the spec should create the new module.
|
||||
|
||||
create_module() is optional.
|
||||
|
||||
"""
|
||||
# By default, defer to _SpecMethods.create() for the new module.
|
||||
return None
|
||||
|
||||
@abc.abstractmethod
|
||||
# We don't define exec_module() here since that would break
|
||||
# hasattr checks we do to support backward compatibility.
|
||||
|
||||
# XXX Deprecate.
|
||||
def load_module(self, fullname):
|
||||
"""Abstract method which when implemented should load a module.
|
||||
The fullname is a str.
|
||||
"""Return the loaded module.
|
||||
|
||||
The module must be added to sys.modules and have import-related
|
||||
attributes set properly. The fullname is a str.
|
||||
|
||||
ImportError is raised on failure.
|
||||
|
||||
"""
|
||||
raise ImportError
|
||||
|
||||
# XXX Deprecate.
|
||||
def module_repr(self, module):
|
||||
"""Return a module's repr.
|
||||
|
||||
Used by the module type when the method does not raise
|
||||
NotImplementedError.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def init_module_attrs(self, module):
|
||||
"""Set the module's __loader__ attribute."""
|
||||
module.__loader__ = self
|
||||
"""
|
||||
# The exception will cause ModuleType.__repr__ to ignore this method.
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class ResourceLoader(Loader):
|
||||
|
@ -138,12 +164,11 @@ class InspectLoader(Loader):
|
|||
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def is_package(self, fullname):
|
||||
"""Abstract method which when implemented should return whether the
|
||||
"""Optional method which when implemented should return whether the
|
||||
module is a package. The fullname is a str. Returns a bool.
|
||||
|
||||
Raises ImportError is the module cannot be found.
|
||||
Raises ImportError if the module cannot be found.
|
||||
"""
|
||||
raise ImportError
|
||||
|
||||
|
@ -176,19 +201,10 @@ class InspectLoader(Loader):
|
|||
argument should be where the data was retrieved (when applicable)."""
|
||||
return compile(data, path, 'exec', dont_inherit=True)
|
||||
|
||||
def init_module_attrs(self, module):
|
||||
"""Initialize the __loader__ and __package__ attributes of the module.
|
||||
|
||||
The name of the module is gleaned from module.__name__. The __package__
|
||||
attribute is set based on self.is_package().
|
||||
"""
|
||||
super().init_module_attrs(module)
|
||||
_bootstrap._init_package_attrs(self, module)
|
||||
|
||||
exec_module = _bootstrap._LoaderBasics.exec_module
|
||||
load_module = _bootstrap._LoaderBasics.load_module
|
||||
|
||||
_register(InspectLoader, machinery.BuiltinImporter, machinery.FrozenImporter,
|
||||
_bootstrap.NamespaceLoader)
|
||||
_register(InspectLoader, machinery.BuiltinImporter, machinery.FrozenImporter)
|
||||
|
||||
|
||||
class ExecutionLoader(InspectLoader):
|
||||
|
@ -225,18 +241,6 @@ class ExecutionLoader(InspectLoader):
|
|||
else:
|
||||
return self.source_to_code(source, path)
|
||||
|
||||
def init_module_attrs(self, module):
|
||||
"""Initialize the module's attributes.
|
||||
|
||||
It is assumed that the module's name has been set on module.__name__.
|
||||
It is also assumed that any path returned by self.get_filename() uses
|
||||
(one of) the operating system's path separator(s) to separate filenames
|
||||
from directories in order to set __path__ intelligently.
|
||||
InspectLoader.init_module_attrs() sets __loader__ and __package__.
|
||||
"""
|
||||
super().init_module_attrs(module)
|
||||
_bootstrap._init_file_attrs(self, module)
|
||||
|
||||
_register(ExecutionLoader, machinery.ExtensionFileLoader)
|
||||
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import _imp
|
|||
from ._bootstrap import (SOURCE_SUFFIXES, DEBUG_BYTECODE_SUFFIXES,
|
||||
OPTIMIZED_BYTECODE_SUFFIXES, BYTECODE_SUFFIXES,
|
||||
EXTENSION_SUFFIXES)
|
||||
from ._bootstrap import ModuleSpec
|
||||
from ._bootstrap import BuiltinImporter
|
||||
from ._bootstrap import FrozenImporter
|
||||
from ._bootstrap import WindowsRegistryFinder
|
||||
|
|
|
@ -3,13 +3,14 @@
|
|||
from ._bootstrap import MAGIC_NUMBER
|
||||
from ._bootstrap import cache_from_source
|
||||
from ._bootstrap import decode_source
|
||||
from ._bootstrap import module_to_load
|
||||
from ._bootstrap import set_loader
|
||||
from ._bootstrap import set_package
|
||||
from ._bootstrap import source_from_cache
|
||||
from ._bootstrap import spec_from_loader
|
||||
from ._bootstrap import spec_from_file_location
|
||||
from ._bootstrap import _resolve_name
|
||||
|
||||
from contextlib import contextmanager
|
||||
import functools
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
|
||||
|
@ -28,6 +29,58 @@ def resolve_name(name, package):
|
|||
return _resolve_name(name[level:], package, level)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def _module_to_load(name):
|
||||
is_reload = name in sys.modules
|
||||
|
||||
module = sys.modules.get(name)
|
||||
if not is_reload:
|
||||
# This must be done before open() is called as the 'io' module
|
||||
# implicitly imports 'locale' and would otherwise trigger an
|
||||
# infinite loop.
|
||||
module = type(sys)(name)
|
||||
# This must be done before putting the module in sys.modules
|
||||
# (otherwise an optimization shortcut in import.c becomes wrong)
|
||||
module.__initializing__ = True
|
||||
sys.modules[name] = module
|
||||
try:
|
||||
yield module
|
||||
except Exception:
|
||||
if not is_reload:
|
||||
try:
|
||||
del sys.modules[name]
|
||||
except KeyError:
|
||||
pass
|
||||
finally:
|
||||
module.__initializing__ = False
|
||||
|
||||
|
||||
# XXX deprecate
|
||||
def set_package(fxn):
|
||||
"""Set __package__ on the returned module."""
|
||||
@functools.wraps(fxn)
|
||||
def set_package_wrapper(*args, **kwargs):
|
||||
module = fxn(*args, **kwargs)
|
||||
if getattr(module, '__package__', None) is None:
|
||||
module.__package__ = module.__name__
|
||||
if not hasattr(module, '__path__'):
|
||||
module.__package__ = module.__package__.rpartition('.')[0]
|
||||
return module
|
||||
return set_package_wrapper
|
||||
|
||||
|
||||
# XXX deprecate
|
||||
def set_loader(fxn):
|
||||
"""Set __loader__ on the returned module."""
|
||||
@functools.wraps(fxn)
|
||||
def set_loader_wrapper(self, *args, **kwargs):
|
||||
module = fxn(self, *args, **kwargs)
|
||||
if getattr(module, '__loader__', None) is None:
|
||||
module.__loader__ = self
|
||||
return module
|
||||
return set_loader_wrapper
|
||||
|
||||
|
||||
def module_for_loader(fxn):
|
||||
"""Decorator to handle selecting the proper module for loaders.
|
||||
|
||||
|
@ -46,13 +99,11 @@ def module_for_loader(fxn):
|
|||
the second argument.
|
||||
|
||||
"""
|
||||
warnings.warn('To make it easier for subclasses, please use '
|
||||
'importlib.util.module_to_load() and '
|
||||
'importlib.abc.Loader.init_module_attrs()',
|
||||
warnings.warn('The import system now takes care of this automatically.',
|
||||
PendingDeprecationWarning, stacklevel=2)
|
||||
@functools.wraps(fxn)
|
||||
def module_for_loader_wrapper(self, fullname, *args, **kwargs):
|
||||
with module_to_load(fullname) as module:
|
||||
with _module_to_load(fullname) as module:
|
||||
module.__loader__ = self
|
||||
try:
|
||||
is_package = self.is_package(fullname)
|
||||
|
|
|
@ -245,14 +245,13 @@ def import_main_path(main_path):
|
|||
# We should not try to load __main__
|
||||
# since that would execute 'if __name__ == "__main__"'
|
||||
# clauses, potentially causing a psuedo fork bomb.
|
||||
loader = importlib.find_loader(main_name, path=dirs)
|
||||
main_module = types.ModuleType(main_name)
|
||||
try:
|
||||
loader.init_module_attrs(main_module)
|
||||
except AttributeError: # init_module_attrs is optional
|
||||
pass
|
||||
# XXX Use a target of main_module?
|
||||
spec = importlib.find_spec(main_name, path=dirs)
|
||||
methods = importlib._bootstrap._SpecMethods(spec)
|
||||
methods.init_module_attrs(main_module)
|
||||
main_module.__name__ = '__mp_main__'
|
||||
code = loader.get_code(main_name)
|
||||
code = spec.loader.get_code(main_name)
|
||||
exec(code, main_module.__dict__)
|
||||
|
||||
old_main_modules.append(sys.modules['__main__'])
|
||||
|
|
|
@ -430,6 +430,7 @@ def iter_importers(fullname=""):
|
|||
for item in path:
|
||||
yield get_importer(item)
|
||||
|
||||
|
||||
def get_loader(module_or_name):
|
||||
"""Get a PEP 302 "loader" object for module_or_name
|
||||
|
||||
|
@ -570,6 +571,7 @@ def extend_path(path, name):
|
|||
|
||||
return path
|
||||
|
||||
|
||||
def get_data(package, resource):
|
||||
"""Get a resource from a package.
|
||||
|
||||
|
@ -592,10 +594,15 @@ def get_data(package, resource):
|
|||
which does not support get_data(), then None is returned.
|
||||
"""
|
||||
|
||||
loader = get_loader(package)
|
||||
spec = importlib.find_spec(package)
|
||||
if spec is None:
|
||||
return None
|
||||
loader = spec.loader
|
||||
if loader is None or not hasattr(loader, 'get_data'):
|
||||
return None
|
||||
mod = sys.modules.get(package) or loader.load_module(package)
|
||||
# XXX needs test
|
||||
mod = (sys.modules.get(package) or
|
||||
importlib._bootstrap._SpecMethods(spec).load())
|
||||
if mod is None or not hasattr(mod, '__file__'):
|
||||
return None
|
||||
|
||||
|
|
|
@ -167,8 +167,9 @@ def _split_list(s, predicate):
|
|||
def visiblename(name, all=None, obj=None):
|
||||
"""Decide whether to show documentation on a variable."""
|
||||
# Certain special names are redundant or internal.
|
||||
# XXX Remove __initializing__?
|
||||
if name in {'__author__', '__builtins__', '__cached__', '__credits__',
|
||||
'__date__', '__doc__', '__file__', '__initializing__',
|
||||
'__date__', '__doc__', '__file__', '__spec__',
|
||||
'__loader__', '__module__', '__name__', '__package__',
|
||||
'__path__', '__qualname__', '__slots__', '__version__'}:
|
||||
return 0
|
||||
|
|
|
@ -2259,7 +2259,7 @@ order (MRO) for bases """
|
|||
minstance.b = 2
|
||||
minstance.a = 1
|
||||
default_attributes = ['__name__', '__doc__', '__package__',
|
||||
'__loader__']
|
||||
'__loader__', '__spec__']
|
||||
names = [x for x in dir(minstance) if x not in default_attributes]
|
||||
self.assertEqual(names, ['a', 'b'])
|
||||
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
# Test the frozen module defined in frozen.c.
|
||||
|
||||
from test.support import captured_stdout, run_unittest
|
||||
import unittest
|
||||
import sys
|
||||
|
||||
class FrozenTests(unittest.TestCase):
|
||||
|
||||
module_attrs = frozenset(['__builtins__', '__cached__', '__doc__',
|
||||
'__loader__', '__name__',
|
||||
'__package__'])
|
||||
package_attrs = frozenset(list(module_attrs) + ['__path__'])
|
||||
|
||||
def test_frozen(self):
|
||||
with captured_stdout() as stdout:
|
||||
try:
|
||||
import __hello__
|
||||
except ImportError as x:
|
||||
self.fail("import __hello__ failed:" + str(x))
|
||||
self.assertEqual(__hello__.initialized, True)
|
||||
expect = set(self.module_attrs)
|
||||
expect.add('initialized')
|
||||
self.assertEqual(set(dir(__hello__)), expect)
|
||||
self.assertEqual(stdout.getvalue(), 'Hello world!\n')
|
||||
|
||||
with captured_stdout() as stdout:
|
||||
try:
|
||||
import __phello__
|
||||
except ImportError as x:
|
||||
self.fail("import __phello__ failed:" + str(x))
|
||||
self.assertEqual(__phello__.initialized, True)
|
||||
expect = set(self.package_attrs)
|
||||
expect.add('initialized')
|
||||
if not "__phello__.spam" in sys.modules:
|
||||
self.assertEqual(set(dir(__phello__)), expect)
|
||||
else:
|
||||
expect.add('spam')
|
||||
self.assertEqual(set(dir(__phello__)), expect)
|
||||
self.assertEqual(__phello__.__path__, [])
|
||||
self.assertEqual(stdout.getvalue(), 'Hello world!\n')
|
||||
|
||||
with captured_stdout() as stdout:
|
||||
try:
|
||||
import __phello__.spam
|
||||
except ImportError as x:
|
||||
self.fail("import __phello__.spam failed:" + str(x))
|
||||
self.assertEqual(__phello__.spam.initialized, True)
|
||||
spam_expect = set(self.module_attrs)
|
||||
spam_expect.add('initialized')
|
||||
self.assertEqual(set(dir(__phello__.spam)), spam_expect)
|
||||
phello_expect = set(self.package_attrs)
|
||||
phello_expect.add('initialized')
|
||||
phello_expect.add('spam')
|
||||
self.assertEqual(set(dir(__phello__)), phello_expect)
|
||||
self.assertEqual(stdout.getvalue(), 'Hello world!\n')
|
||||
|
||||
try:
|
||||
import __phello__.foo
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
self.fail("import __phello__.foo should have failed")
|
||||
|
||||
try:
|
||||
import __phello__.foo
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
self.fail("import __phello__.foo should have failed")
|
||||
|
||||
del sys.modules['__hello__']
|
||||
del sys.modules['__phello__']
|
||||
del sys.modules['__phello__.spam']
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
|
@ -1036,11 +1036,14 @@ class ImportTracebackTests(unittest.TestCase):
|
|||
# away from the traceback.
|
||||
self.create_module("foo", "")
|
||||
importlib = sys.modules['_frozen_importlib']
|
||||
old_load_module = importlib.SourceLoader.load_module
|
||||
if 'load_module' in vars(importlib.SourceLoader):
|
||||
old_exec_module = importlib.SourceLoader.exec_module
|
||||
else:
|
||||
old_exec_module = None
|
||||
try:
|
||||
def load_module(*args):
|
||||
def exec_module(*args):
|
||||
1/0
|
||||
importlib.SourceLoader.load_module = load_module
|
||||
importlib.SourceLoader.exec_module = exec_module
|
||||
try:
|
||||
import foo
|
||||
except ZeroDivisionError as e:
|
||||
|
@ -1049,7 +1052,10 @@ class ImportTracebackTests(unittest.TestCase):
|
|||
self.fail("ZeroDivisionError should have been raised")
|
||||
self.assert_traceback(tb, [__file__, '<frozen importlib', __file__])
|
||||
finally:
|
||||
importlib.SourceLoader.load_module = old_load_module
|
||||
if old_exec_module is None:
|
||||
del importlib.SourceLoader.exec_module
|
||||
else:
|
||||
importlib.SourceLoader.exec_module = old_exec_module
|
||||
|
||||
@unittest.skipUnless(TESTFN_UNENCODABLE, 'need TESTFN_UNENCODABLE')
|
||||
def test_unencodable_filename(self):
|
||||
|
|
|
@ -80,11 +80,6 @@ class LoaderTests(metaclass=abc.ABCMeta):
|
|||
imported."""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def test_module_reuse(self):
|
||||
"""If a module is already in sys.modules, it should be reused."""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def test_state_after_failure(self):
|
||||
"""If a module is already in sys.modules and a reload fails
|
||||
|
|
|
@ -8,6 +8,46 @@ import sys
|
|||
import unittest
|
||||
|
||||
|
||||
class FindSpecTests(abc.FinderTests):
|
||||
|
||||
"""Test find_spec() for built-in modules."""
|
||||
|
||||
def test_module(self):
|
||||
# Common case.
|
||||
with util.uncache(builtin_util.NAME):
|
||||
found = self.machinery.BuiltinImporter.find_spec(builtin_util.NAME)
|
||||
self.assertTrue(found)
|
||||
self.assertEqual(found.origin, 'built-in')
|
||||
|
||||
# Built-in modules cannot be a package.
|
||||
test_package = None
|
||||
|
||||
# Built-in modules cannobt be in a package.
|
||||
test_module_in_package = None
|
||||
|
||||
# Built-in modules cannot be a package.
|
||||
test_package_in_package = None
|
||||
|
||||
# Built-in modules cannot be a package.
|
||||
test_package_over_module = None
|
||||
|
||||
def test_failure(self):
|
||||
name = 'importlib'
|
||||
assert name not in sys.builtin_module_names
|
||||
spec = self.machinery.BuiltinImporter.find_spec(name)
|
||||
self.assertIsNone(spec)
|
||||
|
||||
def test_ignore_path(self):
|
||||
# The value for 'path' should always trigger a failed import.
|
||||
with util.uncache(builtin_util.NAME):
|
||||
spec = self.machinery.BuiltinImporter.find_spec(builtin_util.NAME,
|
||||
['pkg'])
|
||||
self.assertIsNone(spec)
|
||||
|
||||
Frozen_FindSpecTests, Source_FindSpecTests = util.test_both(FindSpecTests,
|
||||
machinery=[frozen_machinery, source_machinery])
|
||||
|
||||
|
||||
class FinderTests(abc.FinderTests):
|
||||
|
||||
"""Test find_module() for built-in modules."""
|
||||
|
@ -17,22 +57,13 @@ class FinderTests(abc.FinderTests):
|
|||
with util.uncache(builtin_util.NAME):
|
||||
found = self.machinery.BuiltinImporter.find_module(builtin_util.NAME)
|
||||
self.assertTrue(found)
|
||||
self.assertTrue(hasattr(found, 'load_module'))
|
||||
|
||||
def test_package(self):
|
||||
# Built-in modules cannot be a package.
|
||||
pass
|
||||
test_package = test_package_in_package = test_package_over_module = None
|
||||
|
||||
def test_module_in_package(self):
|
||||
# Built-in modules cannobt be in a package.
|
||||
pass
|
||||
|
||||
def test_package_in_package(self):
|
||||
# Built-in modules cannot be a package.
|
||||
pass
|
||||
|
||||
def test_package_over_module(self):
|
||||
# Built-in modules cannot be a package.
|
||||
pass
|
||||
# Built-in modules cannot be in a package.
|
||||
test_module_in_package = None
|
||||
|
||||
def test_failure(self):
|
||||
assert 'importlib' not in sys.builtin_module_names
|
||||
|
|
|
@ -9,6 +9,70 @@ import types
|
|||
import unittest
|
||||
|
||||
|
||||
class ExecModTests(abc.LoaderTests):
|
||||
|
||||
"""Test exec_module() for built-in modules."""
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.verification = {'__name__': 'errno', '__package__': '',
|
||||
'__loader__': cls.machinery.BuiltinImporter}
|
||||
|
||||
def verify(self, module):
|
||||
"""Verify that the module matches against what it should have."""
|
||||
self.assertIsInstance(module, types.ModuleType)
|
||||
for attr, value in self.verification.items():
|
||||
self.assertEqual(getattr(module, attr), value)
|
||||
self.assertIn(module.__name__, sys.modules)
|
||||
self.assertTrue(hasattr(module, '__spec__'))
|
||||
self.assertEqual(module.__spec__.origin, 'built-in')
|
||||
|
||||
def load_spec(self, name):
|
||||
spec = self.machinery.ModuleSpec(name, self.machinery.BuiltinImporter,
|
||||
origin='built-in')
|
||||
module = types.ModuleType(name)
|
||||
module.__spec__ = spec
|
||||
self.machinery.BuiltinImporter.exec_module(module)
|
||||
# Strictly not what exec_module() is supposed to do, but since
|
||||
# _imp.init_builtin() does this we can't get around it.
|
||||
return sys.modules[name]
|
||||
|
||||
def test_module(self):
|
||||
# Common case.
|
||||
with util.uncache(builtin_util.NAME):
|
||||
module = self.load_spec(builtin_util.NAME)
|
||||
self.verify(module)
|
||||
self.assertIn('built-in', str(module))
|
||||
|
||||
# Built-in modules cannot be a package.
|
||||
test_package = None
|
||||
|
||||
# Built-in modules cannot be a package.
|
||||
test_lacking_parent = None
|
||||
|
||||
# Not way to force an import failure.
|
||||
test_state_after_failure = None
|
||||
|
||||
def test_unloadable(self):
|
||||
name = 'dssdsdfff'
|
||||
assert name not in sys.builtin_module_names
|
||||
with self.assertRaises(ImportError) as cm:
|
||||
self.load_spec(name)
|
||||
self.assertEqual(cm.exception.name, name)
|
||||
|
||||
def test_already_imported(self):
|
||||
# Using the name of a module already imported but not a built-in should
|
||||
# still fail.
|
||||
assert hasattr(unittest, '__file__') # Not a built-in.
|
||||
with self.assertRaises(ImportError) as cm:
|
||||
self.load_spec('unittest')
|
||||
self.assertEqual(cm.exception.name, 'unittest')
|
||||
|
||||
|
||||
Frozen_ExecModTests, Source_ExecModTests = util.test_both(ExecModTests,
|
||||
machinery=[frozen_machinery, source_machinery])
|
||||
|
||||
|
||||
class LoaderTests(abc.LoaderTests):
|
||||
|
||||
"""Test load_module() for built-in modules."""
|
||||
|
@ -33,17 +97,11 @@ class LoaderTests(abc.LoaderTests):
|
|||
module = self.load_module(builtin_util.NAME)
|
||||
self.verify(module)
|
||||
|
||||
def test_package(self):
|
||||
# Built-in modules cannot be a package.
|
||||
pass
|
||||
test_package = test_lacking_parent = None
|
||||
|
||||
def test_lacking_parent(self):
|
||||
# Built-in modules cannot be a package.
|
||||
pass
|
||||
|
||||
def test_state_after_failure(self):
|
||||
# Not way to force an imoprt failure.
|
||||
pass
|
||||
# No way to force an import failure.
|
||||
test_state_after_failure = None
|
||||
|
||||
def test_module_reuse(self):
|
||||
# Test that the same module is used in a reload.
|
||||
|
|
|
@ -9,6 +9,8 @@ from . import util as ext_util
|
|||
frozen_machinery, source_machinery = util.import_importlib('importlib.machinery')
|
||||
|
||||
|
||||
# XXX find_spec tests
|
||||
|
||||
@unittest.skipIf(ext_util.FILENAME is None, '_testcapi not available')
|
||||
@util.case_insensitive_tests
|
||||
class ExtensionModuleCaseSensitivityTest:
|
||||
|
|
|
@ -6,6 +6,7 @@ machinery = test_util.import_importlib('importlib.machinery')
|
|||
|
||||
import unittest
|
||||
|
||||
# XXX find_spec tests
|
||||
|
||||
class FinderTests(abc.FinderTests):
|
||||
|
||||
|
@ -20,21 +21,14 @@ class FinderTests(abc.FinderTests):
|
|||
def test_module(self):
|
||||
self.assertTrue(self.find_module(util.NAME))
|
||||
|
||||
def test_package(self):
|
||||
# No extension module as an __init__ available for testing.
|
||||
pass
|
||||
test_package = test_package_in_package = None
|
||||
|
||||
def test_module_in_package(self):
|
||||
# No extension module in a package available for testing.
|
||||
pass
|
||||
test_module_in_package = None
|
||||
|
||||
def test_package_in_package(self):
|
||||
# No extension module as an __init__ available for testing.
|
||||
pass
|
||||
|
||||
def test_package_over_module(self):
|
||||
# Extension modules cannot be an __init__ for a package.
|
||||
pass
|
||||
test_package_over_module = None
|
||||
|
||||
def test_failure(self):
|
||||
self.assertIsNone(self.find_module('asdfjkl;'))
|
||||
|
|
|
@ -6,9 +6,75 @@ machinery = util.import_importlib('importlib.machinery')
|
|||
|
||||
import os.path
|
||||
import sys
|
||||
import types
|
||||
import unittest
|
||||
|
||||
|
||||
class ExecModuleTests(abc.LoaderTests):
|
||||
|
||||
"""Test load_module() for extension modules."""
|
||||
|
||||
def setUp(self):
|
||||
self.loader = self.machinery.ExtensionFileLoader(ext_util.NAME,
|
||||
ext_util.FILEPATH)
|
||||
|
||||
def exec_module(self, fullname):
|
||||
module = types.ModuleType(fullname)
|
||||
module.__spec__ = self.machinery.ModuleSpec(fullname, self.loader)
|
||||
self.loader.exec_module(module)
|
||||
return sys.modules[fullname]
|
||||
|
||||
def test_exec_module_API(self):
|
||||
with self.assertRaises(ImportError):
|
||||
self.exec_module('XXX')
|
||||
|
||||
|
||||
def test_module(self):
|
||||
with util.uncache(ext_util.NAME):
|
||||
module = self.exec_module(ext_util.NAME)
|
||||
for attr, value in [('__name__', ext_util.NAME),
|
||||
('__file__', ext_util.FILEPATH),
|
||||
('__package__', '')]:
|
||||
given = getattr(module, attr)
|
||||
self.assertEqual(given, value,
|
||||
'{}: {!r} != {!r}'.format(attr, given, value))
|
||||
self.assertIn(ext_util.NAME, sys.modules)
|
||||
self.assertIsInstance(module.__loader__,
|
||||
self.machinery.ExtensionFileLoader)
|
||||
|
||||
# No extension module as __init__ available for testing.
|
||||
test_package = None
|
||||
|
||||
# No extension module in a package available for testing.
|
||||
test_lacking_parent = None
|
||||
|
||||
def test_module_reuse(self):
|
||||
with util.uncache(ext_util.NAME):
|
||||
module1 = self.exec_module(ext_util.NAME)
|
||||
module2 = self.exec_module(ext_util.NAME)
|
||||
self.assertIs(module1, module2)
|
||||
|
||||
def test_state_after_failure(self):
|
||||
# No easy way to trigger a failure after a successful import.
|
||||
pass
|
||||
|
||||
def test_unloadable(self):
|
||||
name = 'asdfjkl;'
|
||||
with self.assertRaises(ImportError) as cm:
|
||||
self.exec_module(name)
|
||||
self.assertEqual(cm.exception.name, name)
|
||||
|
||||
def test_is_package(self):
|
||||
self.assertFalse(self.loader.is_package(ext_util.NAME))
|
||||
for suffix in self.machinery.EXTENSION_SUFFIXES:
|
||||
path = os.path.join('some', 'path', 'pkg', '__init__' + suffix)
|
||||
loader = self.machinery.ExtensionFileLoader('pkg', path)
|
||||
self.assertTrue(loader.is_package('pkg'))
|
||||
|
||||
Frozen_ExecModuleTests, Source_ExecModuleTests = util.test_both(
|
||||
ExecModuleTests, machinery=machinery)
|
||||
|
||||
|
||||
class LoaderTests(abc.LoaderTests):
|
||||
|
||||
"""Test load_module() for extension modules."""
|
||||
|
@ -39,13 +105,11 @@ class LoaderTests(abc.LoaderTests):
|
|||
self.assertIsInstance(module.__loader__,
|
||||
self.machinery.ExtensionFileLoader)
|
||||
|
||||
def test_package(self):
|
||||
# No extension module as __init__ available for testing.
|
||||
pass
|
||||
test_package = None
|
||||
|
||||
def test_lacking_parent(self):
|
||||
# No extension module in a package available for testing.
|
||||
pass
|
||||
test_lacking_parent = None
|
||||
|
||||
def test_module_reuse(self):
|
||||
with util.uncache(ext_util.NAME):
|
||||
|
@ -53,9 +117,8 @@ class LoaderTests(abc.LoaderTests):
|
|||
module2 = self.load_module(ext_util.NAME)
|
||||
self.assertIs(module1, module2)
|
||||
|
||||
def test_state_after_failure(self):
|
||||
# No easy way to trigger a failure after a successful import.
|
||||
pass
|
||||
test_state_after_failure = None
|
||||
|
||||
def test_unloadable(self):
|
||||
name = 'asdfjkl;'
|
||||
|
|
|
@ -6,6 +6,41 @@ machinery = util.import_importlib('importlib.machinery')
|
|||
import unittest
|
||||
|
||||
|
||||
class FindSpecTests(abc.FinderTests):
|
||||
|
||||
"""Test finding frozen modules."""
|
||||
|
||||
def find(self, name, path=None):
|
||||
finder = self.machinery.FrozenImporter
|
||||
return finder.find_spec(name, path)
|
||||
|
||||
def test_module(self):
|
||||
name = '__hello__'
|
||||
spec = self.find(name)
|
||||
self.assertEqual(spec.origin, 'frozen')
|
||||
|
||||
def test_package(self):
|
||||
spec = self.find('__phello__')
|
||||
self.assertIsNotNone(spec)
|
||||
|
||||
def test_module_in_package(self):
|
||||
spec = self.find('__phello__.spam', ['__phello__'])
|
||||
self.assertIsNotNone(spec)
|
||||
|
||||
# No frozen package within another package to test with.
|
||||
test_package_in_package = None
|
||||
|
||||
# No easy way to test.
|
||||
test_package_over_module = None
|
||||
|
||||
def test_failure(self):
|
||||
spec = self.find('<not real>')
|
||||
self.assertIsNone(spec)
|
||||
|
||||
Frozen_FindSpecTests, Source_FindSpecTests = util.test_both(FindSpecTests,
|
||||
machinery=machinery)
|
||||
|
||||
|
||||
class FinderTests(abc.FinderTests):
|
||||
|
||||
"""Test finding frozen modules."""
|
||||
|
@ -27,13 +62,11 @@ class FinderTests(abc.FinderTests):
|
|||
loader = self.find('__phello__.spam', ['__phello__'])
|
||||
self.assertTrue(hasattr(loader, 'load_module'))
|
||||
|
||||
def test_package_in_package(self):
|
||||
# No frozen package within another package to test with.
|
||||
pass
|
||||
test_package_in_package = None
|
||||
|
||||
def test_package_over_module(self):
|
||||
# No easy way to test.
|
||||
pass
|
||||
test_package_over_module = None
|
||||
|
||||
def test_failure(self):
|
||||
loader = self.find('<not real>')
|
||||
|
|
|
@ -3,9 +3,81 @@ from .. import util
|
|||
|
||||
machinery = util.import_importlib('importlib.machinery')
|
||||
|
||||
import unittest
|
||||
|
||||
import sys
|
||||
from test.support import captured_stdout
|
||||
import types
|
||||
import unittest
|
||||
|
||||
|
||||
class ExecModuleTests(abc.LoaderTests):
|
||||
|
||||
def exec_module(self, name):
|
||||
with util.uncache(name), captured_stdout() as stdout:
|
||||
spec = self.machinery.ModuleSpec(
|
||||
name, self.machinery.FrozenImporter, origin='frozen',
|
||||
is_package=self.machinery.FrozenImporter.is_package(name))
|
||||
module = types.ModuleType(name)
|
||||
module.__spec__ = spec
|
||||
assert not hasattr(module, 'initialized')
|
||||
self.machinery.FrozenImporter.exec_module(module)
|
||||
self.assertTrue(module.initialized)
|
||||
self.assertTrue(hasattr(module, '__spec__'))
|
||||
self.assertEqual(module.__spec__.origin, 'frozen')
|
||||
return module, stdout.getvalue()
|
||||
|
||||
def test_module(self):
|
||||
name = '__hello__'
|
||||
module, output = self.exec_module(name)
|
||||
check = {'__name__': name}
|
||||
for attr, value in check.items():
|
||||
self.assertEqual(getattr(module, attr), value)
|
||||
self.assertEqual(output, 'Hello world!\n')
|
||||
self.assertTrue(hasattr(module, '__spec__'))
|
||||
|
||||
def test_package(self):
|
||||
name = '__phello__'
|
||||
module, output = self.exec_module(name)
|
||||
check = {'__name__': name}
|
||||
for attr, value in check.items():
|
||||
attr_value = getattr(module, attr)
|
||||
self.assertEqual(attr_value, value,
|
||||
'for {name}.{attr}, {given!r} != {expected!r}'.format(
|
||||
name=name, attr=attr, given=attr_value,
|
||||
expected=value))
|
||||
self.assertEqual(output, 'Hello world!\n')
|
||||
|
||||
def test_lacking_parent(self):
|
||||
name = '__phello__.spam'
|
||||
with util.uncache('__phello__'):
|
||||
module, output = self.exec_module(name)
|
||||
check = {'__name__': name}
|
||||
for attr, value in check.items():
|
||||
attr_value = getattr(module, attr)
|
||||
self.assertEqual(attr_value, value,
|
||||
'for {name}.{attr}, {given} != {expected!r}'.format(
|
||||
name=name, attr=attr, given=attr_value,
|
||||
expected=value))
|
||||
self.assertEqual(output, 'Hello world!\n')
|
||||
|
||||
|
||||
def test_module_repr(self):
|
||||
name = '__hello__'
|
||||
module, output = self.exec_module(name)
|
||||
self.assertEqual(repr(module),
|
||||
"<module '__hello__' (frozen)>")
|
||||
|
||||
# No way to trigger an error in a frozen module.
|
||||
test_state_after_failure = None
|
||||
|
||||
def test_unloadable(self):
|
||||
assert self.machinery.FrozenImporter.find_module('_not_real') is None
|
||||
with self.assertRaises(ImportError) as cm:
|
||||
self.exec_module('_not_real')
|
||||
self.assertEqual(cm.exception.name, '_not_real')
|
||||
|
||||
Frozen_ExecModuleTests, Source_ExecModuleTests = util.test_both(ExecModuleTests,
|
||||
machinery=machinery)
|
||||
|
||||
|
||||
class LoaderTests(abc.LoaderTests):
|
||||
|
@ -68,9 +140,8 @@ class LoaderTests(abc.LoaderTests):
|
|||
self.assertEqual(repr(module),
|
||||
"<module '__hello__' (frozen)>")
|
||||
|
||||
def test_state_after_failure(self):
|
||||
# No way to trigger an error in a frozen module.
|
||||
pass
|
||||
test_state_after_failure = None
|
||||
|
||||
def test_unloadable(self):
|
||||
assert self.machinery.FrozenImporter.find_module('_not_real') is None
|
||||
|
|
|
@ -46,7 +46,7 @@ class CallingOrder:
|
|||
with util.import_state(meta_path=[]):
|
||||
with warnings.catch_warnings(record=True) as w:
|
||||
warnings.simplefilter('always')
|
||||
self.assertIsNone(importlib._bootstrap._find_module('nothing',
|
||||
self.assertIsNone(importlib._bootstrap._find_spec('nothing',
|
||||
None))
|
||||
self.assertEqual(len(w), 1)
|
||||
self.assertTrue(issubclass(w[-1].category, ImportWarning))
|
||||
|
|
|
@ -284,22 +284,6 @@ class ExecutionLoaderDefaultsTests:
|
|||
tests = make_return_value_tests(ExecutionLoader, InspectLoaderDefaultsTests)
|
||||
Frozen_ELDefaultTests, Source_ELDefaultsTests = tests
|
||||
|
||||
##### Loader concrete methods ##################################################
|
||||
class LoaderConcreteMethodTests:
|
||||
|
||||
def test_init_module_attrs(self):
|
||||
loader = self.LoaderSubclass()
|
||||
module = types.ModuleType('blah')
|
||||
loader.init_module_attrs(module)
|
||||
self.assertEqual(module.__loader__, loader)
|
||||
|
||||
|
||||
class Frozen_LoaderConcreateMethodTests(LoaderConcreteMethodTests, unittest.TestCase):
|
||||
LoaderSubclass = Frozen_L
|
||||
|
||||
class Source_LoaderConcreateMethodTests(LoaderConcreteMethodTests, unittest.TestCase):
|
||||
LoaderSubclass = Source_L
|
||||
|
||||
|
||||
##### InspectLoader concrete methods ###########################################
|
||||
class InspectLoaderSourceToCodeTests:
|
||||
|
@ -385,60 +369,6 @@ class Source_ILGetCodeTests(InspectLoaderGetCodeTests, unittest.TestCase):
|
|||
InspectLoaderSubclass = Source_IL
|
||||
|
||||
|
||||
class InspectLoaderInitModuleTests:
|
||||
|
||||
def mock_is_package(self, return_value):
|
||||
return mock.patch.object(self.InspectLoaderSubclass, 'is_package',
|
||||
return_value=return_value)
|
||||
|
||||
def init_module_attrs(self, name):
|
||||
loader = self.InspectLoaderSubclass()
|
||||
module = types.ModuleType(name)
|
||||
loader.init_module_attrs(module)
|
||||
self.assertEqual(module.__loader__, loader)
|
||||
return module
|
||||
|
||||
def test_package(self):
|
||||
# If a package, then __package__ == __name__, __path__ == []
|
||||
with self.mock_is_package(True):
|
||||
name = 'blah'
|
||||
module = self.init_module_attrs(name)
|
||||
self.assertEqual(module.__package__, name)
|
||||
self.assertEqual(module.__path__, [])
|
||||
|
||||
def test_toplevel(self):
|
||||
# If a module is top-level, __package__ == ''
|
||||
with self.mock_is_package(False):
|
||||
name = 'blah'
|
||||
module = self.init_module_attrs(name)
|
||||
self.assertEqual(module.__package__, '')
|
||||
|
||||
def test_submodule(self):
|
||||
# If a module is contained within a package then set __package__ to the
|
||||
# package name.
|
||||
with self.mock_is_package(False):
|
||||
name = 'pkg.mod'
|
||||
module = self.init_module_attrs(name)
|
||||
self.assertEqual(module.__package__, 'pkg')
|
||||
|
||||
def test_is_package_ImportError(self):
|
||||
# If is_package() raises ImportError, __package__ should be None and
|
||||
# __path__ should not be set.
|
||||
with self.mock_is_package(False) as mocked_method:
|
||||
mocked_method.side_effect = ImportError
|
||||
name = 'mod'
|
||||
module = self.init_module_attrs(name)
|
||||
self.assertIsNone(module.__package__)
|
||||
self.assertFalse(hasattr(module, '__path__'))
|
||||
|
||||
|
||||
class Frozen_ILInitModuleTests(InspectLoaderInitModuleTests, unittest.TestCase):
|
||||
InspectLoaderSubclass = Frozen_IL
|
||||
|
||||
class Source_ILInitModuleTests(InspectLoaderInitModuleTests, unittest.TestCase):
|
||||
InspectLoaderSubclass = Source_IL
|
||||
|
||||
|
||||
class InspectLoaderLoadModuleTests:
|
||||
|
||||
"""Test InspectLoader.load_module()."""
|
||||
|
@ -550,80 +480,6 @@ class Source_ELGetCodeTests(ExecutionLoaderGetCodeTests, unittest.TestCase):
|
|||
ExecutionLoaderSubclass = Source_EL
|
||||
|
||||
|
||||
class ExecutionLoaderInitModuleTests:
|
||||
|
||||
def mock_is_package(self, return_value):
|
||||
return mock.patch.object(self.ExecutionLoaderSubclass, 'is_package',
|
||||
return_value=return_value)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def mock_methods(self, is_package, filename):
|
||||
is_package_manager = self.mock_is_package(is_package)
|
||||
get_filename_manager = mock.patch.object(self.ExecutionLoaderSubclass,
|
||||
'get_filename', return_value=filename)
|
||||
with is_package_manager as mock_is_package:
|
||||
with get_filename_manager as mock_get_filename:
|
||||
yield {'is_package': mock_is_package,
|
||||
'get_filename': mock_get_filename}
|
||||
|
||||
def test_toplevel(self):
|
||||
# Verify __loader__, __file__, and __package__; no __path__.
|
||||
name = 'blah'
|
||||
path = os.path.join('some', 'path', '{}.py'.format(name))
|
||||
with self.mock_methods(False, path):
|
||||
loader = self.ExecutionLoaderSubclass()
|
||||
module = types.ModuleType(name)
|
||||
loader.init_module_attrs(module)
|
||||
self.assertIs(module.__loader__, loader)
|
||||
self.assertEqual(module.__file__, path)
|
||||
self.assertEqual(module.__package__, '')
|
||||
self.assertFalse(hasattr(module, '__path__'))
|
||||
|
||||
def test_package(self):
|
||||
# Verify __loader__, __file__, __package__, and __path__.
|
||||
name = 'pkg'
|
||||
path = os.path.join('some', 'pkg', '__init__.py')
|
||||
with self.mock_methods(True, path):
|
||||
loader = self.ExecutionLoaderSubclass()
|
||||
module = types.ModuleType(name)
|
||||
loader.init_module_attrs(module)
|
||||
self.assertIs(module.__loader__, loader)
|
||||
self.assertEqual(module.__file__, path)
|
||||
self.assertEqual(module.__package__, 'pkg')
|
||||
self.assertEqual(module.__path__, [os.path.dirname(path)])
|
||||
|
||||
def test_submodule(self):
|
||||
# Verify __package__ and not __path__; test_toplevel() takes care of
|
||||
# other attributes.
|
||||
name = 'pkg.submodule'
|
||||
path = os.path.join('some', 'pkg', 'submodule.py')
|
||||
with self.mock_methods(False, path):
|
||||
loader = self.ExecutionLoaderSubclass()
|
||||
module = types.ModuleType(name)
|
||||
loader.init_module_attrs(module)
|
||||
self.assertEqual(module.__package__, 'pkg')
|
||||
self.assertEqual(module.__file__, path)
|
||||
self.assertFalse(hasattr(module, '__path__'))
|
||||
|
||||
def test_get_filename_ImportError(self):
|
||||
# If get_filename() raises ImportError, don't set __file__.
|
||||
name = 'blah'
|
||||
path = 'blah.py'
|
||||
with self.mock_methods(False, path) as mocked_methods:
|
||||
mocked_methods['get_filename'].side_effect = ImportError
|
||||
loader = self.ExecutionLoaderSubclass()
|
||||
module = types.ModuleType(name)
|
||||
loader.init_module_attrs(module)
|
||||
self.assertFalse(hasattr(module, '__file__'))
|
||||
|
||||
|
||||
class Frozen_ELInitModuleTests(ExecutionLoaderInitModuleTests, unittest.TestCase):
|
||||
ExecutionLoaderSubclass = Frozen_EL
|
||||
|
||||
class Source_ELInitModuleTests(ExecutionLoaderInitModuleTests, unittest.TestCase):
|
||||
ExecutionLoaderSubclass = Source_EL
|
||||
|
||||
|
||||
##### SourceLoader concrete methods ############################################
|
||||
class SourceLoader:
|
||||
|
||||
|
@ -952,58 +808,5 @@ class Source_SourceOnlyLGetSourceTests(SourceLoaderGetSourceTests, unittest.Test
|
|||
SourceOnlyLoaderMock = Source_SourceOnlyL
|
||||
|
||||
|
||||
class SourceLoaderInitModuleAttrTests:
|
||||
|
||||
"""Tests for importlib.abc.SourceLoader.init_module_attrs()."""
|
||||
|
||||
def test_init_module_attrs(self):
|
||||
# If __file__ set, __cached__ == importlib.util.cached_from_source(__file__).
|
||||
name = 'blah'
|
||||
path = 'blah.py'
|
||||
loader = self.SourceOnlyLoaderMock(path)
|
||||
module = types.ModuleType(name)
|
||||
loader.init_module_attrs(module)
|
||||
self.assertEqual(module.__loader__, loader)
|
||||
self.assertEqual(module.__package__, '')
|
||||
self.assertEqual(module.__file__, path)
|
||||
self.assertEqual(module.__cached__, self.util.cache_from_source(path))
|
||||
|
||||
def test_no_get_filename(self):
|
||||
# No __file__, no __cached__.
|
||||
with mock.patch.object(self.SourceOnlyLoaderMock, 'get_filename') as mocked:
|
||||
mocked.side_effect = ImportError
|
||||
name = 'blah'
|
||||
loader = self.SourceOnlyLoaderMock('blah.py')
|
||||
module = types.ModuleType(name)
|
||||
loader.init_module_attrs(module)
|
||||
self.assertFalse(hasattr(module, '__file__'))
|
||||
self.assertFalse(hasattr(module, '__cached__'))
|
||||
|
||||
|
||||
class Frozen_SLInitModuleAttrTests(SourceLoaderInitModuleAttrTests, unittest.TestCase):
|
||||
SourceOnlyLoaderMock = Frozen_SourceOnlyL
|
||||
util = frozen_util
|
||||
|
||||
# Difficult to test under source thanks to cross-module mocking needs.
|
||||
@mock.patch('importlib._bootstrap.cache_from_source')
|
||||
def test_cache_from_source_NotImplementedError(self, mock_cache_from_source):
|
||||
# If importlib.util.cache_from_source() raises NotImplementedError don't set
|
||||
# __cached__.
|
||||
mock_cache_from_source.side_effect = NotImplementedError
|
||||
name = 'blah'
|
||||
path = 'blah.py'
|
||||
loader = self.SourceOnlyLoaderMock(path)
|
||||
module = types.ModuleType(name)
|
||||
loader.init_module_attrs(module)
|
||||
self.assertEqual(module.__file__, path)
|
||||
self.assertFalse(hasattr(module, '__cached__'))
|
||||
|
||||
|
||||
class Source_SLInitModuleAttrTests(SourceLoaderInitModuleAttrTests, unittest.TestCase):
|
||||
SourceOnlyLoaderMock = Source_SourceOnlyL
|
||||
util = source_util
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
|
|
@ -165,6 +165,96 @@ class Source_FindLoaderTests(FindLoaderTests, unittest.TestCase):
|
|||
init = source_init
|
||||
|
||||
|
||||
class FindSpecTests:
|
||||
|
||||
class FakeMetaFinder:
|
||||
@staticmethod
|
||||
def find_spec(name, path=None, target=None): return name, path, target
|
||||
|
||||
def test_sys_modules(self):
|
||||
name = 'some_mod'
|
||||
with util.uncache(name):
|
||||
module = types.ModuleType(name)
|
||||
loader = 'a loader!'
|
||||
spec = self.machinery.ModuleSpec(name, loader)
|
||||
module.__loader__ = loader
|
||||
module.__spec__ = spec
|
||||
sys.modules[name] = module
|
||||
found = self.init.find_spec(name)
|
||||
self.assertEqual(found, spec)
|
||||
|
||||
def test_sys_modules_without___loader__(self):
|
||||
name = 'some_mod'
|
||||
with util.uncache(name):
|
||||
module = types.ModuleType(name)
|
||||
del module.__loader__
|
||||
loader = 'a loader!'
|
||||
spec = self.machinery.ModuleSpec(name, loader)
|
||||
module.__spec__ = spec
|
||||
sys.modules[name] = module
|
||||
found = self.init.find_spec(name)
|
||||
self.assertEqual(found, spec)
|
||||
|
||||
def test_sys_modules_spec_is_None(self):
|
||||
name = 'some_mod'
|
||||
with util.uncache(name):
|
||||
module = types.ModuleType(name)
|
||||
module.__spec__ = None
|
||||
sys.modules[name] = module
|
||||
with self.assertRaises(ValueError):
|
||||
self.init.find_spec(name)
|
||||
|
||||
def test_sys_modules_loader_is_None(self):
|
||||
name = 'some_mod'
|
||||
with util.uncache(name):
|
||||
module = types.ModuleType(name)
|
||||
spec = self.machinery.ModuleSpec(name, None)
|
||||
module.__spec__ = spec
|
||||
sys.modules[name] = module
|
||||
found = self.init.find_spec(name)
|
||||
self.assertEqual(found, spec)
|
||||
|
||||
def test_sys_modules_spec_is_not_set(self):
|
||||
name = 'some_mod'
|
||||
with util.uncache(name):
|
||||
module = types.ModuleType(name)
|
||||
try:
|
||||
del module.__spec__
|
||||
except AttributeError:
|
||||
pass
|
||||
sys.modules[name] = module
|
||||
with self.assertRaises(ValueError):
|
||||
self.init.find_spec(name)
|
||||
|
||||
def test_success(self):
|
||||
name = 'some_mod'
|
||||
with util.uncache(name):
|
||||
with util.import_state(meta_path=[self.FakeMetaFinder]):
|
||||
self.assertEqual((name, None, None),
|
||||
self.init.find_spec(name))
|
||||
|
||||
def test_success_path(self):
|
||||
# Searching on a path should work.
|
||||
name = 'some_mod'
|
||||
path = 'path to some place'
|
||||
with util.uncache(name):
|
||||
with util.import_state(meta_path=[self.FakeMetaFinder]):
|
||||
self.assertEqual((name, path, None),
|
||||
self.init.find_spec(name, path))
|
||||
|
||||
def test_nothing(self):
|
||||
# None is returned upon failure to find a loader.
|
||||
self.assertIsNone(self.init.find_spec('nevergoingtofindthismodule'))
|
||||
|
||||
class Frozen_FindSpecTests(FindSpecTests, unittest.TestCase):
|
||||
init = frozen_init
|
||||
machinery = frozen_machinery
|
||||
|
||||
class Source_FindSpecTests(FindSpecTests, unittest.TestCase):
|
||||
init = source_init
|
||||
machinery = source_machinery
|
||||
|
||||
|
||||
class ReloadTests:
|
||||
|
||||
"""Test module reloading for builtin and extension modules."""
|
||||
|
@ -219,6 +309,7 @@ class ReloadTests:
|
|||
with support.temp_cwd(None) as cwd:
|
||||
with util.uncache('spam'):
|
||||
with support.DirsOnSysPath(cwd):
|
||||
# Start as a plain module.
|
||||
self.init.invalidate_caches()
|
||||
path = os.path.join(cwd, name + '.py')
|
||||
cached = self.util.cache_from_source(path)
|
||||
|
@ -232,11 +323,14 @@ class ReloadTests:
|
|||
support.create_empty_file(path)
|
||||
module = self.init.import_module(name)
|
||||
ns = vars(module)
|
||||
del ns['__initializing__']
|
||||
loader = ns.pop('__loader__')
|
||||
spec = ns.pop('__spec__')
|
||||
self.assertEqual(spec.name, name)
|
||||
self.assertEqual(spec.loader, loader)
|
||||
self.assertEqual(loader.path, path)
|
||||
self.assertEqual(ns, expected)
|
||||
|
||||
# Change to a package.
|
||||
self.init.invalidate_caches()
|
||||
init_path = os.path.join(cwd, name, '__init__.py')
|
||||
cached = self.util.cache_from_source(init_path)
|
||||
|
@ -252,18 +346,21 @@ class ReloadTests:
|
|||
os.rename(path, init_path)
|
||||
reloaded = self.init.reload(module)
|
||||
ns = vars(reloaded)
|
||||
del ns['__initializing__']
|
||||
loader = ns.pop('__loader__')
|
||||
spec = ns.pop('__spec__')
|
||||
self.assertEqual(spec.name, name)
|
||||
self.assertEqual(spec.loader, loader)
|
||||
self.assertIs(reloaded, module)
|
||||
self.assertEqual(loader.path, init_path)
|
||||
self.maxDiff = None
|
||||
self.assertEqual(ns, expected)
|
||||
|
||||
def test_reload_namespace_changed(self):
|
||||
self.maxDiff = None
|
||||
name = 'spam'
|
||||
with support.temp_cwd(None) as cwd:
|
||||
with util.uncache('spam'):
|
||||
with support.DirsOnSysPath(cwd):
|
||||
# Start as a namespace package.
|
||||
self.init.invalidate_caches()
|
||||
bad_path = os.path.join(cwd, name, '__init.py')
|
||||
cached = self.util.cache_from_source(bad_path)
|
||||
|
@ -276,9 +373,12 @@ class ReloadTests:
|
|||
init_file.write('eggs = None')
|
||||
module = self.init.import_module(name)
|
||||
ns = vars(module)
|
||||
del ns['__initializing__']
|
||||
loader = ns.pop('__loader__')
|
||||
path = ns.pop('__path__')
|
||||
spec = ns.pop('__spec__')
|
||||
self.assertEqual(spec.name, name)
|
||||
self.assertIs(spec.loader, None)
|
||||
self.assertIsNot(loader, None)
|
||||
self.assertEqual(set(path),
|
||||
set([os.path.dirname(bad_path)]))
|
||||
with self.assertRaises(AttributeError):
|
||||
|
@ -286,6 +386,7 @@ class ReloadTests:
|
|||
loader.path
|
||||
self.assertEqual(ns, expected)
|
||||
|
||||
# Change to a regular package.
|
||||
self.init.invalidate_caches()
|
||||
init_path = os.path.join(cwd, name, '__init__.py')
|
||||
cached = self.util.cache_from_source(init_path)
|
||||
|
@ -301,8 +402,10 @@ class ReloadTests:
|
|||
os.rename(bad_path, init_path)
|
||||
reloaded = self.init.reload(module)
|
||||
ns = vars(reloaded)
|
||||
del ns['__initializing__']
|
||||
loader = ns.pop('__loader__')
|
||||
spec = ns.pop('__spec__')
|
||||
self.assertEqual(spec.name, name)
|
||||
self.assertEqual(spec.loader, loader)
|
||||
self.assertIs(reloaded, module)
|
||||
self.assertEqual(loader.path, init_path)
|
||||
self.assertEqual(ns, expected)
|
||||
|
@ -371,6 +474,7 @@ class StartupTests:
|
|||
# Issue #17098: all modules should have __loader__ defined.
|
||||
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))
|
||||
if self.machinery.BuiltinImporter.find_module(name):
|
||||
|
@ -378,6 +482,16 @@ class StartupTests:
|
|||
elif self.machinery.FrozenImporter.find_module(name):
|
||||
self.assertIsNot(module.__loader__, None)
|
||||
|
||||
def test_everyone_has___spec__(self):
|
||||
for name, module in sys.modules.items():
|
||||
if isinstance(module, types.ModuleType):
|
||||
with self.subTest(name=name):
|
||||
self.assertTrue(hasattr(module, '__spec__'))
|
||||
if self.machinery.BuiltinImporter.find_module(name):
|
||||
self.assertIsNot(module.__spec__, None)
|
||||
elif self.machinery.FrozenImporter.find_module(name):
|
||||
self.assertIsNot(module.__spec__, None)
|
||||
|
||||
class Frozen_StartupTests(StartupTests, unittest.TestCase):
|
||||
machinery = frozen_machinery
|
||||
|
||||
|
|
|
@ -0,0 +1,968 @@
|
|||
from . import util
|
||||
|
||||
frozen_init, source_init = util.import_importlib('importlib')
|
||||
frozen_bootstrap = frozen_init._bootstrap
|
||||
source_bootstrap = source_init._bootstrap
|
||||
frozen_machinery, source_machinery = util.import_importlib('importlib.machinery')
|
||||
frozen_util, source_util = util.import_importlib('importlib.util')
|
||||
|
||||
import os.path
|
||||
from test.support import CleanImport
|
||||
import unittest
|
||||
import sys
|
||||
|
||||
|
||||
|
||||
class TestLoader:
|
||||
|
||||
def __init__(self, path=None, is_package=None):
|
||||
# if path:
|
||||
# if is_package:
|
||||
# if not path.endswith('.py'):
|
||||
# path = os.path.join(path, '__init__.py')
|
||||
# elif is_package is None:
|
||||
# is_package = path.endswith('__init__.py')
|
||||
|
||||
self.path = path
|
||||
self.package = is_package
|
||||
|
||||
def __repr__(self):
|
||||
return '<TestLoader object>'
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name == 'get_filename' and self.path is not None:
|
||||
return self._get_filename
|
||||
if name == 'is_package':
|
||||
return self._is_package
|
||||
raise AttributeError(name)
|
||||
|
||||
def _get_filename(self, name):
|
||||
return self.path
|
||||
|
||||
def _is_package(self, name):
|
||||
return self.package
|
||||
|
||||
|
||||
class NewLoader(TestLoader):
|
||||
|
||||
EGGS = 1
|
||||
|
||||
def exec_module(self, module):
|
||||
module.eggs = self.EGGS
|
||||
|
||||
|
||||
class LegacyLoader(TestLoader):
|
||||
|
||||
HAM = -1
|
||||
|
||||
@frozen_util.module_for_loader
|
||||
def load_module(self, module):
|
||||
module.ham = self.HAM
|
||||
return module
|
||||
|
||||
|
||||
class ModuleSpecTests:
|
||||
|
||||
def setUp(self):
|
||||
self.name = 'spam'
|
||||
self.path = 'spam.py'
|
||||
self.cached = self.util.cache_from_source(self.path)
|
||||
self.loader = TestLoader()
|
||||
self.spec = self.machinery.ModuleSpec(self.name, self.loader)
|
||||
self.loc_spec = self.machinery.ModuleSpec(self.name, self.loader,
|
||||
origin=self.path)
|
||||
self.loc_spec._set_fileattr = True
|
||||
|
||||
def test_default(self):
|
||||
spec = self.machinery.ModuleSpec(self.name, self.loader)
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertEqual(spec.loader, self.loader)
|
||||
self.assertIs(spec.origin, None)
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertIs(spec.submodule_search_locations, None)
|
||||
self.assertIs(spec.cached, None)
|
||||
self.assertFalse(spec.has_location)
|
||||
|
||||
def test_default_no_loader(self):
|
||||
spec = self.machinery.ModuleSpec(self.name, None)
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertIs(spec.loader, None)
|
||||
self.assertIs(spec.origin, None)
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertIs(spec.submodule_search_locations, None)
|
||||
self.assertIs(spec.cached, None)
|
||||
self.assertFalse(spec.has_location)
|
||||
|
||||
def test_default_is_package_false(self):
|
||||
spec = self.machinery.ModuleSpec(self.name, self.loader,
|
||||
is_package=False)
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertEqual(spec.loader, self.loader)
|
||||
self.assertIs(spec.origin, None)
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertIs(spec.submodule_search_locations, None)
|
||||
self.assertIs(spec.cached, None)
|
||||
self.assertFalse(spec.has_location)
|
||||
|
||||
def test_default_is_package_true(self):
|
||||
spec = self.machinery.ModuleSpec(self.name, self.loader,
|
||||
is_package=True)
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertEqual(spec.loader, self.loader)
|
||||
self.assertIs(spec.origin, None)
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertEqual(spec.submodule_search_locations, [])
|
||||
self.assertIs(spec.cached, None)
|
||||
self.assertFalse(spec.has_location)
|
||||
|
||||
def test_equality(self):
|
||||
other = type(sys.implementation)(name=self.name,
|
||||
loader=self.loader,
|
||||
origin=None,
|
||||
submodule_search_locations=None,
|
||||
has_location=False,
|
||||
cached=None,
|
||||
)
|
||||
|
||||
self.assertTrue(self.spec == other)
|
||||
|
||||
def test_equality_location(self):
|
||||
other = type(sys.implementation)(name=self.name,
|
||||
loader=self.loader,
|
||||
origin=self.path,
|
||||
submodule_search_locations=None,
|
||||
has_location=True,
|
||||
cached=self.cached,
|
||||
)
|
||||
|
||||
self.assertEqual(self.loc_spec, other)
|
||||
|
||||
def test_inequality(self):
|
||||
other = type(sys.implementation)(name='ham',
|
||||
loader=self.loader,
|
||||
origin=None,
|
||||
submodule_search_locations=None,
|
||||
has_location=False,
|
||||
cached=None,
|
||||
)
|
||||
|
||||
self.assertNotEqual(self.spec, other)
|
||||
|
||||
def test_inequality_incomplete(self):
|
||||
other = type(sys.implementation)(name=self.name,
|
||||
loader=self.loader,
|
||||
)
|
||||
|
||||
self.assertNotEqual(self.spec, other)
|
||||
|
||||
def test_package(self):
|
||||
spec = self.machinery.ModuleSpec('spam.eggs', self.loader)
|
||||
|
||||
self.assertEqual(spec.parent, 'spam')
|
||||
|
||||
def test_package_is_package(self):
|
||||
spec = self.machinery.ModuleSpec('spam.eggs', self.loader,
|
||||
is_package=True)
|
||||
|
||||
self.assertEqual(spec.parent, 'spam.eggs')
|
||||
|
||||
# cached
|
||||
|
||||
def test_cached_set(self):
|
||||
before = self.spec.cached
|
||||
self.spec.cached = 'there'
|
||||
after = self.spec.cached
|
||||
|
||||
self.assertIs(before, None)
|
||||
self.assertEqual(after, 'there')
|
||||
|
||||
def test_cached_no_origin(self):
|
||||
spec = self.machinery.ModuleSpec(self.name, self.loader)
|
||||
|
||||
self.assertIs(spec.cached, None)
|
||||
|
||||
def test_cached_with_origin_not_location(self):
|
||||
spec = self.machinery.ModuleSpec(self.name, self.loader,
|
||||
origin=self.path)
|
||||
|
||||
self.assertIs(spec.cached, None)
|
||||
|
||||
def test_cached_source(self):
|
||||
expected = self.util.cache_from_source(self.path)
|
||||
|
||||
self.assertEqual(self.loc_spec.cached, expected)
|
||||
|
||||
def test_cached_source_unknown_suffix(self):
|
||||
self.loc_spec.origin = 'spam.spamspamspam'
|
||||
|
||||
self.assertIs(self.loc_spec.cached, None)
|
||||
|
||||
def test_cached_source_missing_cache_tag(self):
|
||||
original = sys.implementation.cache_tag
|
||||
sys.implementation.cache_tag = None
|
||||
try:
|
||||
cached = self.loc_spec.cached
|
||||
finally:
|
||||
sys.implementation.cache_tag = original
|
||||
|
||||
self.assertIs(cached, None)
|
||||
|
||||
def test_cached_sourceless(self):
|
||||
self.loc_spec.origin = 'spam.pyc'
|
||||
|
||||
self.assertEqual(self.loc_spec.cached, 'spam.pyc')
|
||||
|
||||
|
||||
class Frozen_ModuleSpecTests(ModuleSpecTests, unittest.TestCase):
|
||||
util = frozen_util
|
||||
machinery = frozen_machinery
|
||||
|
||||
|
||||
class Source_ModuleSpecTests(ModuleSpecTests, unittest.TestCase):
|
||||
util = source_util
|
||||
machinery = source_machinery
|
||||
|
||||
|
||||
class ModuleSpecMethodsTests:
|
||||
|
||||
def setUp(self):
|
||||
self.name = 'spam'
|
||||
self.path = 'spam.py'
|
||||
self.cached = self.util.cache_from_source(self.path)
|
||||
self.loader = TestLoader()
|
||||
self.spec = self.machinery.ModuleSpec(self.name, self.loader)
|
||||
self.loc_spec = self.machinery.ModuleSpec(self.name, self.loader,
|
||||
origin=self.path)
|
||||
self.loc_spec._set_fileattr = True
|
||||
|
||||
# init_module_attrs
|
||||
|
||||
def test_init_module_attrs(self):
|
||||
module = type(sys)(self.name)
|
||||
spec = self.machinery.ModuleSpec(self.name, self.loader)
|
||||
self.bootstrap._SpecMethods(spec).init_module_attrs(module)
|
||||
|
||||
self.assertEqual(module.__name__, spec.name)
|
||||
self.assertIs(module.__loader__, spec.loader)
|
||||
self.assertEqual(module.__package__, spec.parent)
|
||||
self.assertIs(module.__spec__, spec)
|
||||
self.assertFalse(hasattr(module, '__path__'))
|
||||
self.assertFalse(hasattr(module, '__file__'))
|
||||
self.assertFalse(hasattr(module, '__cached__'))
|
||||
|
||||
def test_init_module_attrs_package(self):
|
||||
module = type(sys)(self.name)
|
||||
spec = self.machinery.ModuleSpec(self.name, self.loader)
|
||||
spec.submodule_search_locations = ['spam', 'ham']
|
||||
self.bootstrap._SpecMethods(spec).init_module_attrs(module)
|
||||
|
||||
self.assertEqual(module.__name__, spec.name)
|
||||
self.assertIs(module.__loader__, spec.loader)
|
||||
self.assertEqual(module.__package__, spec.parent)
|
||||
self.assertIs(module.__spec__, spec)
|
||||
self.assertIs(module.__path__, spec.submodule_search_locations)
|
||||
self.assertFalse(hasattr(module, '__file__'))
|
||||
self.assertFalse(hasattr(module, '__cached__'))
|
||||
|
||||
def test_init_module_attrs_location(self):
|
||||
module = type(sys)(self.name)
|
||||
spec = self.loc_spec
|
||||
self.bootstrap._SpecMethods(spec).init_module_attrs(module)
|
||||
|
||||
self.assertEqual(module.__name__, spec.name)
|
||||
self.assertIs(module.__loader__, spec.loader)
|
||||
self.assertEqual(module.__package__, spec.parent)
|
||||
self.assertIs(module.__spec__, spec)
|
||||
self.assertFalse(hasattr(module, '__path__'))
|
||||
self.assertEqual(module.__file__, spec.origin)
|
||||
self.assertEqual(module.__cached__,
|
||||
self.util.cache_from_source(spec.origin))
|
||||
|
||||
def test_init_module_attrs_different_name(self):
|
||||
module = type(sys)('eggs')
|
||||
spec = self.machinery.ModuleSpec(self.name, self.loader)
|
||||
self.bootstrap._SpecMethods(spec).init_module_attrs(module)
|
||||
|
||||
self.assertEqual(module.__name__, spec.name)
|
||||
|
||||
def test_init_module_attrs_different_spec(self):
|
||||
module = type(sys)(self.name)
|
||||
module.__spec__ = self.machinery.ModuleSpec('eggs', object())
|
||||
spec = self.machinery.ModuleSpec(self.name, self.loader)
|
||||
self.bootstrap._SpecMethods(spec).init_module_attrs(module)
|
||||
|
||||
self.assertEqual(module.__name__, spec.name)
|
||||
self.assertIs(module.__loader__, spec.loader)
|
||||
self.assertEqual(module.__package__, spec.parent)
|
||||
self.assertIs(module.__spec__, spec)
|
||||
|
||||
def test_init_module_attrs_already_set(self):
|
||||
module = type(sys)('ham.eggs')
|
||||
module.__loader__ = object()
|
||||
module.__package__ = 'ham'
|
||||
module.__path__ = ['eggs']
|
||||
module.__file__ = 'ham/eggs/__init__.py'
|
||||
module.__cached__ = self.util.cache_from_source(module.__file__)
|
||||
original = vars(module).copy()
|
||||
spec = self.loc_spec
|
||||
spec.submodule_search_locations = ['']
|
||||
self.bootstrap._SpecMethods(spec).init_module_attrs(module)
|
||||
|
||||
self.assertIs(module.__loader__, original['__loader__'])
|
||||
self.assertEqual(module.__package__, original['__package__'])
|
||||
self.assertIs(module.__path__, original['__path__'])
|
||||
self.assertEqual(module.__file__, original['__file__'])
|
||||
self.assertEqual(module.__cached__, original['__cached__'])
|
||||
|
||||
def test_init_module_attrs_immutable(self):
|
||||
module = object()
|
||||
spec = self.loc_spec
|
||||
spec.submodule_search_locations = ['']
|
||||
self.bootstrap._SpecMethods(spec).init_module_attrs(module)
|
||||
|
||||
self.assertFalse(hasattr(module, '__name__'))
|
||||
self.assertFalse(hasattr(module, '__loader__'))
|
||||
self.assertFalse(hasattr(module, '__package__'))
|
||||
self.assertFalse(hasattr(module, '__spec__'))
|
||||
self.assertFalse(hasattr(module, '__path__'))
|
||||
self.assertFalse(hasattr(module, '__file__'))
|
||||
self.assertFalse(hasattr(module, '__cached__'))
|
||||
|
||||
# create()
|
||||
|
||||
def test_create(self):
|
||||
created = self.bootstrap._SpecMethods(self.spec).create()
|
||||
|
||||
self.assertEqual(created.__name__, self.spec.name)
|
||||
self.assertIs(created.__loader__, self.spec.loader)
|
||||
self.assertEqual(created.__package__, self.spec.parent)
|
||||
self.assertIs(created.__spec__, self.spec)
|
||||
self.assertFalse(hasattr(created, '__path__'))
|
||||
self.assertFalse(hasattr(created, '__file__'))
|
||||
self.assertFalse(hasattr(created, '__cached__'))
|
||||
|
||||
def test_create_from_loader(self):
|
||||
module = type(sys.implementation)()
|
||||
class CreatingLoader(TestLoader):
|
||||
def create_module(self, spec):
|
||||
return module
|
||||
self.spec.loader = CreatingLoader()
|
||||
created = self.bootstrap._SpecMethods(self.spec).create()
|
||||
|
||||
self.assertIs(created, module)
|
||||
self.assertEqual(created.__name__, self.spec.name)
|
||||
self.assertIs(created.__loader__, self.spec.loader)
|
||||
self.assertEqual(created.__package__, self.spec.parent)
|
||||
self.assertIs(created.__spec__, self.spec)
|
||||
self.assertFalse(hasattr(created, '__path__'))
|
||||
self.assertFalse(hasattr(created, '__file__'))
|
||||
self.assertFalse(hasattr(created, '__cached__'))
|
||||
|
||||
def test_create_from_loader_not_handled(self):
|
||||
class CreatingLoader(TestLoader):
|
||||
def create_module(self, spec):
|
||||
return None
|
||||
self.spec.loader = CreatingLoader()
|
||||
created = self.bootstrap._SpecMethods(self.spec).create()
|
||||
|
||||
self.assertEqual(created.__name__, self.spec.name)
|
||||
self.assertIs(created.__loader__, self.spec.loader)
|
||||
self.assertEqual(created.__package__, self.spec.parent)
|
||||
self.assertIs(created.__spec__, self.spec)
|
||||
self.assertFalse(hasattr(created, '__path__'))
|
||||
self.assertFalse(hasattr(created, '__file__'))
|
||||
self.assertFalse(hasattr(created, '__cached__'))
|
||||
|
||||
# exec()
|
||||
|
||||
def test_exec(self):
|
||||
self.spec.loader = NewLoader()
|
||||
module = self.bootstrap._SpecMethods(self.spec).create()
|
||||
sys.modules[self.name] = module
|
||||
self.assertFalse(hasattr(module, 'eggs'))
|
||||
self.bootstrap._SpecMethods(self.spec).exec(module)
|
||||
|
||||
self.assertEqual(module.eggs, 1)
|
||||
|
||||
# load()
|
||||
|
||||
def test_load(self):
|
||||
self.spec.loader = NewLoader()
|
||||
with CleanImport(self.spec.name):
|
||||
loaded = self.bootstrap._SpecMethods(self.spec).load()
|
||||
installed = sys.modules[self.spec.name]
|
||||
|
||||
self.assertEqual(loaded.eggs, 1)
|
||||
self.assertIs(loaded, installed)
|
||||
|
||||
def test_load_replaced(self):
|
||||
replacement = object()
|
||||
class ReplacingLoader(TestLoader):
|
||||
def exec_module(self, module):
|
||||
sys.modules[module.__name__] = replacement
|
||||
self.spec.loader = ReplacingLoader()
|
||||
with CleanImport(self.spec.name):
|
||||
loaded = self.bootstrap._SpecMethods(self.spec).load()
|
||||
installed = sys.modules[self.spec.name]
|
||||
|
||||
self.assertIs(loaded, replacement)
|
||||
self.assertIs(installed, replacement)
|
||||
|
||||
def test_load_failed(self):
|
||||
class FailedLoader(TestLoader):
|
||||
def exec_module(self, module):
|
||||
raise RuntimeError
|
||||
self.spec.loader = FailedLoader()
|
||||
with CleanImport(self.spec.name):
|
||||
with self.assertRaises(RuntimeError):
|
||||
loaded = self.bootstrap._SpecMethods(self.spec).load()
|
||||
self.assertNotIn(self.spec.name, sys.modules)
|
||||
|
||||
def test_load_failed_removed(self):
|
||||
class FailedLoader(TestLoader):
|
||||
def exec_module(self, module):
|
||||
del sys.modules[module.__name__]
|
||||
raise RuntimeError
|
||||
self.spec.loader = FailedLoader()
|
||||
with CleanImport(self.spec.name):
|
||||
with self.assertRaises(RuntimeError):
|
||||
loaded = self.bootstrap._SpecMethods(self.spec).load()
|
||||
self.assertNotIn(self.spec.name, sys.modules)
|
||||
|
||||
def test_load_existing(self):
|
||||
existing = type(sys)('ham')
|
||||
existing.count = 5
|
||||
self.spec.loader = NewLoader()
|
||||
with CleanImport(self.name):
|
||||
sys.modules[self.name] = existing
|
||||
assert self.spec.name == self.name
|
||||
loaded = self.bootstrap._SpecMethods(self.spec).load()
|
||||
|
||||
self.assertEqual(loaded.eggs, 1)
|
||||
self.assertFalse(hasattr(loaded, 'ham'))
|
||||
|
||||
def test_load_legacy(self):
|
||||
self.spec.loader = LegacyLoader()
|
||||
with CleanImport(self.spec.name):
|
||||
loaded = self.bootstrap._SpecMethods(self.spec).load()
|
||||
|
||||
self.assertEqual(loaded.ham, -1)
|
||||
|
||||
def test_load_legacy_attributes(self):
|
||||
self.spec.loader = LegacyLoader()
|
||||
with CleanImport(self.spec.name):
|
||||
loaded = self.bootstrap._SpecMethods(self.spec).load()
|
||||
|
||||
self.assertIs(loaded.__loader__, self.spec.loader)
|
||||
self.assertEqual(loaded.__package__, self.spec.parent)
|
||||
self.assertIs(loaded.__spec__, self.spec)
|
||||
|
||||
def test_load_legacy_attributes_immutable(self):
|
||||
module = object()
|
||||
class ImmutableLoader(TestLoader):
|
||||
def load_module(self, name):
|
||||
sys.modules[name] = module
|
||||
return module
|
||||
self.spec.loader = ImmutableLoader()
|
||||
with CleanImport(self.spec.name):
|
||||
loaded = self.bootstrap._SpecMethods(self.spec).load()
|
||||
|
||||
self.assertIs(sys.modules[self.spec.name], module)
|
||||
|
||||
# reload()
|
||||
|
||||
def test_reload(self):
|
||||
self.spec.loader = NewLoader()
|
||||
with CleanImport(self.spec.name):
|
||||
loaded = self.bootstrap._SpecMethods(self.spec).load()
|
||||
reloaded = self.bootstrap._SpecMethods(self.spec).exec(loaded)
|
||||
installed = sys.modules[self.spec.name]
|
||||
|
||||
self.assertEqual(loaded.eggs, 1)
|
||||
self.assertIs(reloaded, loaded)
|
||||
self.assertIs(installed, loaded)
|
||||
|
||||
def test_reload_modified(self):
|
||||
self.spec.loader = NewLoader()
|
||||
with CleanImport(self.spec.name):
|
||||
loaded = self.bootstrap._SpecMethods(self.spec).load()
|
||||
loaded.eggs = 2
|
||||
reloaded = self.bootstrap._SpecMethods(self.spec).exec(loaded)
|
||||
|
||||
self.assertEqual(loaded.eggs, 1)
|
||||
self.assertIs(reloaded, loaded)
|
||||
|
||||
def test_reload_extra_attributes(self):
|
||||
self.spec.loader = NewLoader()
|
||||
with CleanImport(self.spec.name):
|
||||
loaded = self.bootstrap._SpecMethods(self.spec).load()
|
||||
loaded.available = False
|
||||
reloaded = self.bootstrap._SpecMethods(self.spec).exec(loaded)
|
||||
|
||||
self.assertFalse(loaded.available)
|
||||
self.assertIs(reloaded, loaded)
|
||||
|
||||
def test_reload_init_module_attrs(self):
|
||||
self.spec.loader = NewLoader()
|
||||
with CleanImport(self.spec.name):
|
||||
loaded = self.bootstrap._SpecMethods(self.spec).load()
|
||||
loaded.__name__ = 'ham'
|
||||
del loaded.__loader__
|
||||
del loaded.__package__
|
||||
del loaded.__spec__
|
||||
self.bootstrap._SpecMethods(self.spec).exec(loaded)
|
||||
|
||||
self.assertEqual(loaded.__name__, self.spec.name)
|
||||
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__'))
|
||||
|
||||
def test_reload_legacy(self):
|
||||
self.spec.loader = LegacyLoader()
|
||||
with CleanImport(self.spec.name):
|
||||
loaded = self.bootstrap._SpecMethods(self.spec).load()
|
||||
reloaded = self.bootstrap._SpecMethods(self.spec).exec(loaded)
|
||||
installed = sys.modules[self.spec.name]
|
||||
|
||||
self.assertEqual(loaded.ham, -1)
|
||||
self.assertIs(reloaded, loaded)
|
||||
self.assertIs(installed, loaded)
|
||||
|
||||
|
||||
class Frozen_ModuleSpecMethodsTests(ModuleSpecMethodsTests, unittest.TestCase):
|
||||
bootstrap = frozen_bootstrap
|
||||
machinery = frozen_machinery
|
||||
util = frozen_util
|
||||
|
||||
|
||||
class Source_ModuleSpecMethodsTests(ModuleSpecMethodsTests, unittest.TestCase):
|
||||
bootstrap = source_bootstrap
|
||||
machinery = source_machinery
|
||||
util = source_util
|
||||
|
||||
|
||||
class ModuleReprTests:
|
||||
|
||||
# XXX Add more tests for repr(module) once ModuleSpec._module_repr()
|
||||
# is in place?
|
||||
|
||||
def setUp(self):
|
||||
self.module = type(os)('spam')
|
||||
self.spec = self.machinery.ModuleSpec('spam', TestLoader())
|
||||
|
||||
def test_module___loader___module_repr(self):
|
||||
class Loader:
|
||||
def module_repr(self, module):
|
||||
return '<delicious {}>'.format(module.__name__)
|
||||
self.module.__loader__ = Loader()
|
||||
modrepr = self.bootstrap._module_repr(self.module)
|
||||
|
||||
self.assertEqual(modrepr, '<delicious spam>')
|
||||
|
||||
def test_module___loader___module_repr_bad(self):
|
||||
class Loader(TestLoader):
|
||||
def module_repr(self, module):
|
||||
raise Exception
|
||||
self.module.__loader__ = Loader()
|
||||
modrepr = self.bootstrap._module_repr(self.module)
|
||||
|
||||
self.assertEqual(modrepr,
|
||||
'<module {!r} (<TestLoader object>)>'.format('spam'))
|
||||
|
||||
def test_module___spec__(self):
|
||||
origin = 'in a hole, in the ground'
|
||||
self.spec.origin = origin
|
||||
self.module.__spec__ = self.spec
|
||||
modrepr = self.bootstrap._module_repr(self.module)
|
||||
|
||||
self.assertEqual(modrepr, '<module {!r} ({})>'.format('spam', origin))
|
||||
|
||||
def test_module___spec___location(self):
|
||||
location = 'in_a_galaxy_far_far_away.py'
|
||||
self.spec.origin = location
|
||||
self.spec._set_fileattr = True
|
||||
self.module.__spec__ = self.spec
|
||||
modrepr = self.bootstrap._module_repr(self.module)
|
||||
|
||||
self.assertEqual(modrepr,
|
||||
'<module {!r} from {!r}>'.format('spam', location))
|
||||
|
||||
def test_module___spec___no_origin(self):
|
||||
self.spec.loader = TestLoader()
|
||||
self.module.__spec__ = self.spec
|
||||
modrepr = self.bootstrap._module_repr(self.module)
|
||||
|
||||
self.assertEqual(modrepr,
|
||||
'<module {!r} (<TestLoader object>)>'.format('spam'))
|
||||
|
||||
def test_module___spec___no_origin_no_loader(self):
|
||||
self.spec.loader = None
|
||||
self.module.__spec__ = self.spec
|
||||
modrepr = self.bootstrap._module_repr(self.module)
|
||||
|
||||
self.assertEqual(modrepr, '<module {!r}>'.format('spam'))
|
||||
|
||||
def test_module_no_name(self):
|
||||
del self.module.__name__
|
||||
modrepr = self.bootstrap._module_repr(self.module)
|
||||
|
||||
self.assertEqual(modrepr, '<module {!r}>'.format('?'))
|
||||
|
||||
def test_module_with_file(self):
|
||||
filename = 'e/i/e/i/o/spam.py'
|
||||
self.module.__file__ = filename
|
||||
modrepr = self.bootstrap._module_repr(self.module)
|
||||
|
||||
self.assertEqual(modrepr,
|
||||
'<module {!r} from {!r}>'.format('spam', filename))
|
||||
|
||||
def test_module_no_file(self):
|
||||
self.module.__loader__ = TestLoader()
|
||||
modrepr = self.bootstrap._module_repr(self.module)
|
||||
|
||||
self.assertEqual(modrepr,
|
||||
'<module {!r} (<TestLoader object>)>'.format('spam'))
|
||||
|
||||
def test_module_no_file_no_loader(self):
|
||||
modrepr = self.bootstrap._module_repr(self.module)
|
||||
|
||||
self.assertEqual(modrepr, '<module {!r}>'.format('spam'))
|
||||
|
||||
|
||||
class Frozen_ModuleReprTests(ModuleReprTests, unittest.TestCase):
|
||||
bootstrap = frozen_bootstrap
|
||||
machinery = frozen_machinery
|
||||
util = frozen_util
|
||||
|
||||
|
||||
class Source_ModuleReprTests(ModuleReprTests, unittest.TestCase):
|
||||
bootstrap = source_bootstrap
|
||||
machinery = source_machinery
|
||||
util = source_util
|
||||
|
||||
|
||||
class FactoryTests:
|
||||
|
||||
def setUp(self):
|
||||
self.name = 'spam'
|
||||
self.path = 'spam.py'
|
||||
self.cached = self.util.cache_from_source(self.path)
|
||||
self.loader = TestLoader()
|
||||
self.fileloader = TestLoader(self.path)
|
||||
self.pkgloader = TestLoader(self.path, True)
|
||||
|
||||
# spec_from_loader()
|
||||
|
||||
def test_spec_from_loader_default(self):
|
||||
spec = self.util.spec_from_loader(self.name, self.loader)
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertEqual(spec.loader, self.loader)
|
||||
self.assertIs(spec.origin, None)
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertIs(spec.submodule_search_locations, None)
|
||||
self.assertIs(spec.cached, None)
|
||||
self.assertFalse(spec.has_location)
|
||||
|
||||
def test_spec_from_loader_default_with_bad_is_package(self):
|
||||
class Loader:
|
||||
def is_package(self, name):
|
||||
raise ImportError
|
||||
loader = Loader()
|
||||
spec = self.util.spec_from_loader(self.name, loader)
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertEqual(spec.loader, loader)
|
||||
self.assertIs(spec.origin, None)
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertIs(spec.submodule_search_locations, None)
|
||||
self.assertIs(spec.cached, None)
|
||||
self.assertFalse(spec.has_location)
|
||||
|
||||
def test_spec_from_loader_origin(self):
|
||||
origin = 'somewhere over the rainbow'
|
||||
spec = self.util.spec_from_loader(self.name, self.loader,
|
||||
origin=origin)
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertEqual(spec.loader, self.loader)
|
||||
self.assertIs(spec.origin, origin)
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertIs(spec.submodule_search_locations, None)
|
||||
self.assertIs(spec.cached, None)
|
||||
self.assertFalse(spec.has_location)
|
||||
|
||||
def test_spec_from_loader_is_package_false(self):
|
||||
spec = self.util.spec_from_loader(self.name, self.loader,
|
||||
is_package=False)
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertEqual(spec.loader, self.loader)
|
||||
self.assertIs(spec.origin, None)
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertIs(spec.submodule_search_locations, None)
|
||||
self.assertIs(spec.cached, None)
|
||||
self.assertFalse(spec.has_location)
|
||||
|
||||
def test_spec_from_loader_is_package_true(self):
|
||||
spec = self.util.spec_from_loader(self.name, self.loader,
|
||||
is_package=True)
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertEqual(spec.loader, self.loader)
|
||||
self.assertIs(spec.origin, None)
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertEqual(spec.submodule_search_locations, [])
|
||||
self.assertIs(spec.cached, None)
|
||||
self.assertFalse(spec.has_location)
|
||||
|
||||
def test_spec_from_loader_origin_and_is_package(self):
|
||||
origin = 'where the streets have no name'
|
||||
spec = self.util.spec_from_loader(self.name, self.loader,
|
||||
origin=origin, is_package=True)
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertEqual(spec.loader, self.loader)
|
||||
self.assertIs(spec.origin, origin)
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertEqual(spec.submodule_search_locations, [])
|
||||
self.assertIs(spec.cached, None)
|
||||
self.assertFalse(spec.has_location)
|
||||
|
||||
def test_spec_from_loader_is_package_with_loader_false(self):
|
||||
loader = TestLoader(is_package=False)
|
||||
spec = self.util.spec_from_loader(self.name, loader)
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertEqual(spec.loader, loader)
|
||||
self.assertIs(spec.origin, None)
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertIs(spec.submodule_search_locations, None)
|
||||
self.assertIs(spec.cached, None)
|
||||
self.assertFalse(spec.has_location)
|
||||
|
||||
def test_spec_from_loader_is_package_with_loader_true(self):
|
||||
loader = TestLoader(is_package=True)
|
||||
spec = self.util.spec_from_loader(self.name, loader)
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertEqual(spec.loader, loader)
|
||||
self.assertIs(spec.origin, None)
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertEqual(spec.submodule_search_locations, [])
|
||||
self.assertIs(spec.cached, None)
|
||||
self.assertFalse(spec.has_location)
|
||||
|
||||
def test_spec_from_loader_default_with_file_loader(self):
|
||||
spec = self.util.spec_from_loader(self.name, self.fileloader)
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertEqual(spec.loader, self.fileloader)
|
||||
self.assertEqual(spec.origin, self.path)
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertIs(spec.submodule_search_locations, None)
|
||||
self.assertEqual(spec.cached, self.cached)
|
||||
self.assertTrue(spec.has_location)
|
||||
|
||||
def test_spec_from_loader_is_package_false_with_fileloader(self):
|
||||
spec = self.util.spec_from_loader(self.name, self.fileloader,
|
||||
is_package=False)
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertEqual(spec.loader, self.fileloader)
|
||||
self.assertEqual(spec.origin, self.path)
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertIs(spec.submodule_search_locations, None)
|
||||
self.assertEqual(spec.cached, self.cached)
|
||||
self.assertTrue(spec.has_location)
|
||||
|
||||
def test_spec_from_loader_is_package_true_with_fileloader(self):
|
||||
spec = self.util.spec_from_loader(self.name, self.fileloader,
|
||||
is_package=True)
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertEqual(spec.loader, self.fileloader)
|
||||
self.assertEqual(spec.origin, self.path)
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertEqual(spec.submodule_search_locations, [''])
|
||||
self.assertEqual(spec.cached, self.cached)
|
||||
self.assertTrue(spec.has_location)
|
||||
|
||||
# spec_from_file_location()
|
||||
|
||||
def test_spec_from_file_location_default(self):
|
||||
if self.machinery is source_machinery:
|
||||
raise unittest.SkipTest('not sure why this is breaking...')
|
||||
spec = self.util.spec_from_file_location(self.name, self.path)
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertIsInstance(spec.loader,
|
||||
self.machinery.SourceFileLoader)
|
||||
self.assertEqual(spec.loader.name, self.name)
|
||||
self.assertEqual(spec.loader.path, self.path)
|
||||
self.assertEqual(spec.origin, self.path)
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertIs(spec.submodule_search_locations, None)
|
||||
self.assertEqual(spec.cached, self.cached)
|
||||
self.assertTrue(spec.has_location)
|
||||
|
||||
def test_spec_from_file_location_default_without_location(self):
|
||||
spec = self.util.spec_from_file_location(self.name)
|
||||
|
||||
self.assertIs(spec, None)
|
||||
|
||||
def test_spec_from_file_location_default_bad_suffix(self):
|
||||
spec = self.util.spec_from_file_location(self.name, 'spam.eggs')
|
||||
|
||||
self.assertIs(spec, None)
|
||||
|
||||
def test_spec_from_file_location_loader_no_location(self):
|
||||
spec = self.util.spec_from_file_location(self.name,
|
||||
loader=self.fileloader)
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertEqual(spec.loader, self.fileloader)
|
||||
self.assertEqual(spec.origin, self.path)
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertIs(spec.submodule_search_locations, None)
|
||||
self.assertEqual(spec.cached, self.cached)
|
||||
self.assertTrue(spec.has_location)
|
||||
|
||||
def test_spec_from_file_location_loader_no_location_no_get_filename(self):
|
||||
spec = self.util.spec_from_file_location(self.name,
|
||||
loader=self.loader)
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertEqual(spec.loader, self.loader)
|
||||
self.assertEqual(spec.origin, '<unknown>')
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertIs(spec.submodule_search_locations, None)
|
||||
self.assertIs(spec.cached, None)
|
||||
self.assertTrue(spec.has_location)
|
||||
|
||||
def test_spec_from_file_location_loader_no_location_bad_get_filename(self):
|
||||
class Loader:
|
||||
def get_filename(self, name):
|
||||
raise ImportError
|
||||
loader = Loader()
|
||||
spec = self.util.spec_from_file_location(self.name, loader=loader)
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertEqual(spec.loader, loader)
|
||||
self.assertEqual(spec.origin, '<unknown>')
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertIs(spec.submodule_search_locations, None)
|
||||
self.assertIs(spec.cached, None)
|
||||
self.assertTrue(spec.has_location)
|
||||
|
||||
def test_spec_from_file_location_smsl_none(self):
|
||||
spec = self.util.spec_from_file_location(self.name, self.path,
|
||||
loader=self.fileloader,
|
||||
submodule_search_locations=None)
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertEqual(spec.loader, self.fileloader)
|
||||
self.assertEqual(spec.origin, self.path)
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertIs(spec.submodule_search_locations, None)
|
||||
self.assertEqual(spec.cached, self.cached)
|
||||
self.assertTrue(spec.has_location)
|
||||
|
||||
def test_spec_from_file_location_smsl_empty(self):
|
||||
spec = self.util.spec_from_file_location(self.name, self.path,
|
||||
loader=self.fileloader,
|
||||
submodule_search_locations=[])
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertEqual(spec.loader, self.fileloader)
|
||||
self.assertEqual(spec.origin, self.path)
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertEqual(spec.submodule_search_locations, [''])
|
||||
self.assertEqual(spec.cached, self.cached)
|
||||
self.assertTrue(spec.has_location)
|
||||
|
||||
def test_spec_from_file_location_smsl_not_empty(self):
|
||||
spec = self.util.spec_from_file_location(self.name, self.path,
|
||||
loader=self.fileloader,
|
||||
submodule_search_locations=['eggs'])
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertEqual(spec.loader, self.fileloader)
|
||||
self.assertEqual(spec.origin, self.path)
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertEqual(spec.submodule_search_locations, ['eggs'])
|
||||
self.assertEqual(spec.cached, self.cached)
|
||||
self.assertTrue(spec.has_location)
|
||||
|
||||
def test_spec_from_file_location_smsl_default(self):
|
||||
spec = self.util.spec_from_file_location(self.name, self.path,
|
||||
loader=self.pkgloader)
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertEqual(spec.loader, self.pkgloader)
|
||||
self.assertEqual(spec.origin, self.path)
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertEqual(spec.submodule_search_locations, [''])
|
||||
self.assertEqual(spec.cached, self.cached)
|
||||
self.assertTrue(spec.has_location)
|
||||
|
||||
def test_spec_from_file_location_smsl_default_not_package(self):
|
||||
class Loader:
|
||||
def is_package(self, name):
|
||||
return False
|
||||
loader = Loader()
|
||||
spec = self.util.spec_from_file_location(self.name, self.path,
|
||||
loader=loader)
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertEqual(spec.loader, loader)
|
||||
self.assertEqual(spec.origin, self.path)
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertIs(spec.submodule_search_locations, None)
|
||||
self.assertEqual(spec.cached, self.cached)
|
||||
self.assertTrue(spec.has_location)
|
||||
|
||||
def test_spec_from_file_location_smsl_default_no_is_package(self):
|
||||
spec = self.util.spec_from_file_location(self.name, self.path,
|
||||
loader=self.fileloader)
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertEqual(spec.loader, self.fileloader)
|
||||
self.assertEqual(spec.origin, self.path)
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertIs(spec.submodule_search_locations, None)
|
||||
self.assertEqual(spec.cached, self.cached)
|
||||
self.assertTrue(spec.has_location)
|
||||
|
||||
def test_spec_from_file_location_smsl_default_bad_is_package(self):
|
||||
class Loader:
|
||||
def is_package(self, name):
|
||||
raise ImportError
|
||||
loader = Loader()
|
||||
spec = self.util.spec_from_file_location(self.name, self.path,
|
||||
loader=loader)
|
||||
|
||||
self.assertEqual(spec.name, self.name)
|
||||
self.assertEqual(spec.loader, loader)
|
||||
self.assertEqual(spec.origin, self.path)
|
||||
self.assertIs(spec.loader_state, None)
|
||||
self.assertIs(spec.submodule_search_locations, None)
|
||||
self.assertEqual(spec.cached, self.cached)
|
||||
self.assertTrue(spec.has_location)
|
||||
|
||||
|
||||
class Frozen_FactoryTests(FactoryTests, unittest.TestCase):
|
||||
util = frozen_util
|
||||
machinery = frozen_machinery
|
||||
|
||||
|
||||
class Source_FactoryTests(FactoryTests, unittest.TestCase):
|
||||
util = source_util
|
||||
machinery = source_machinery
|
|
@ -34,71 +34,6 @@ Frozen_DecodeSourceBytesTests, Source_DecodeSourceBytesTests = test_util.test_bo
|
|||
DecodeSourceBytesTests, util=[frozen_util, source_util])
|
||||
|
||||
|
||||
class ModuleToLoadTests:
|
||||
|
||||
module_name = 'ModuleManagerTest_module'
|
||||
|
||||
def setUp(self):
|
||||
support.unload(self.module_name)
|
||||
self.addCleanup(support.unload, self.module_name)
|
||||
|
||||
def test_new_module(self):
|
||||
# Test a new module is created, inserted into sys.modules, has
|
||||
# __initializing__ set to True after entering the context manager,
|
||||
# and __initializing__ set to False after exiting.
|
||||
with self.util.module_to_load(self.module_name) as module:
|
||||
self.assertIn(self.module_name, sys.modules)
|
||||
self.assertIs(sys.modules[self.module_name], module)
|
||||
self.assertTrue(module.__initializing__)
|
||||
self.assertFalse(module.__initializing__)
|
||||
|
||||
def test_new_module_failed(self):
|
||||
# Test the module is removed from sys.modules.
|
||||
try:
|
||||
with self.util.module_to_load(self.module_name) as module:
|
||||
self.assertIn(self.module_name, sys.modules)
|
||||
raise exception
|
||||
except Exception:
|
||||
self.assertNotIn(self.module_name, sys.modules)
|
||||
else:
|
||||
self.fail('importlib.util.module_to_load swallowed an exception')
|
||||
|
||||
def test_reload(self):
|
||||
# Test that the same module is in sys.modules.
|
||||
created_module = types.ModuleType(self.module_name)
|
||||
sys.modules[self.module_name] = created_module
|
||||
with self.util.module_to_load(self.module_name) as module:
|
||||
self.assertIs(module, created_module)
|
||||
|
||||
def test_reload_failed(self):
|
||||
# Test that the module was left in sys.modules.
|
||||
created_module = types.ModuleType(self.module_name)
|
||||
sys.modules[self.module_name] = created_module
|
||||
try:
|
||||
with self.util.module_to_load(self.module_name) as module:
|
||||
raise Exception
|
||||
except Exception:
|
||||
self.assertIn(self.module_name, sys.modules)
|
||||
else:
|
||||
self.fail('importlib.util.module_to_load swallowed an exception')
|
||||
|
||||
def test_reset_name(self):
|
||||
# If reset_name is true then module.__name__ = name, else leave it be.
|
||||
odd_name = 'not your typical name'
|
||||
created_module = types.ModuleType(self.module_name)
|
||||
created_module.__name__ = odd_name
|
||||
sys.modules[self.module_name] = created_module
|
||||
with self.util.module_to_load(self.module_name) as module:
|
||||
self.assertEqual(module.__name__, self.module_name)
|
||||
created_module.__name__ = odd_name
|
||||
with self.util.module_to_load(self.module_name, reset_name=False) as module:
|
||||
self.assertEqual(module.__name__, odd_name)
|
||||
|
||||
Frozen_ModuleToLoadTests, Source_ModuleToLoadTests = test_util.test_both(
|
||||
ModuleToLoadTests,
|
||||
util=[frozen_util, source_util])
|
||||
|
||||
|
||||
class ModuleForLoaderTests:
|
||||
|
||||
"""Tests for importlib.util.module_for_loader."""
|
||||
|
|
|
@ -37,8 +37,10 @@ class ModuleTests(unittest.TestCase):
|
|||
self.assertEqual(foo.__doc__, None)
|
||||
self.assertIs(foo.__loader__, None)
|
||||
self.assertIs(foo.__package__, None)
|
||||
self.assertIs(foo.__spec__, None)
|
||||
self.assertEqual(foo.__dict__, {"__name__": "foo", "__doc__": None,
|
||||
"__loader__": None, "__package__": None})
|
||||
"__loader__": None, "__package__": None,
|
||||
"__spec__": None})
|
||||
|
||||
def test_ascii_docstring(self):
|
||||
# ASCII docstring
|
||||
|
@ -47,7 +49,8 @@ class ModuleTests(unittest.TestCase):
|
|||
self.assertEqual(foo.__doc__, "foodoc")
|
||||
self.assertEqual(foo.__dict__,
|
||||
{"__name__": "foo", "__doc__": "foodoc",
|
||||
"__loader__": None, "__package__": None})
|
||||
"__loader__": None, "__package__": None,
|
||||
"__spec__": None})
|
||||
|
||||
def test_unicode_docstring(self):
|
||||
# Unicode docstring
|
||||
|
@ -56,7 +59,8 @@ class ModuleTests(unittest.TestCase):
|
|||
self.assertEqual(foo.__doc__, "foodoc\u1234")
|
||||
self.assertEqual(foo.__dict__,
|
||||
{"__name__": "foo", "__doc__": "foodoc\u1234",
|
||||
"__loader__": None, "__package__": None})
|
||||
"__loader__": None, "__package__": None,
|
||||
"__spec__": None})
|
||||
|
||||
def test_reinit(self):
|
||||
# Reinitialization should not replace the __dict__
|
||||
|
@ -69,7 +73,7 @@ class ModuleTests(unittest.TestCase):
|
|||
self.assertEqual(foo.bar, 42)
|
||||
self.assertEqual(foo.__dict__,
|
||||
{"__name__": "foo", "__doc__": "foodoc", "bar": 42,
|
||||
"__loader__": None, "__package__": None})
|
||||
"__loader__": None, "__package__": None, "__spec__": None})
|
||||
self.assertTrue(foo.__dict__ is d)
|
||||
|
||||
def test_dont_clear_dict(self):
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import contextlib
|
||||
from importlib._bootstrap import NamespaceLoader
|
||||
import importlib.abc
|
||||
import importlib.machinery
|
||||
import os
|
||||
|
@ -290,24 +289,5 @@ class ModuleAndNamespacePackageInSameDir(NamespacePackageTest):
|
|||
self.assertEqual(a_test.attr, 'in module')
|
||||
|
||||
|
||||
class ABCTests(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.loader = NamespaceLoader('foo', ['pkg'],
|
||||
importlib.machinery.PathFinder)
|
||||
|
||||
def test_is_package(self):
|
||||
self.assertTrue(self.loader.is_package('foo'))
|
||||
|
||||
def test_get_code(self):
|
||||
self.assertTrue(isinstance(self.loader.get_code('foo'), types.CodeType))
|
||||
|
||||
def test_get_source(self):
|
||||
self.assertEqual(self.loader.get_source('foo'), '')
|
||||
|
||||
def test_abc_isinstance(self):
|
||||
self.assertTrue(isinstance(self.loader, importlib.abc.InspectLoader))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
@ -199,14 +199,14 @@ class TestPkg(unittest.TestCase):
|
|||
import t5
|
||||
self.assertEqual(fixdir(dir(t5)),
|
||||
['__cached__', '__doc__', '__file__', '__loader__',
|
||||
'__name__', '__package__', '__path__', 'foo',
|
||||
'string', 't5'])
|
||||
'__name__', '__package__', '__path__', '__spec__',
|
||||
'foo', 'string', 't5'])
|
||||
self.assertEqual(fixdir(dir(t5.foo)),
|
||||
['__cached__', '__doc__', '__file__', '__loader__',
|
||||
'__name__', '__package__', 'string'])
|
||||
'__name__', '__package__', '__spec__', 'string'])
|
||||
self.assertEqual(fixdir(dir(t5.string)),
|
||||
['__cached__', '__doc__', '__file__', '__loader__',
|
||||
'__name__', '__package__', 'spam'])
|
||||
'__name__', '__package__', '__spec__', 'spam'])
|
||||
|
||||
def test_6(self):
|
||||
hier = [
|
||||
|
@ -222,14 +222,15 @@ class TestPkg(unittest.TestCase):
|
|||
import t6
|
||||
self.assertEqual(fixdir(dir(t6)),
|
||||
['__all__', '__cached__', '__doc__', '__file__',
|
||||
'__loader__', '__name__', '__package__', '__path__'])
|
||||
'__loader__', '__name__', '__package__', '__path__',
|
||||
'__spec__'])
|
||||
s = """
|
||||
import t6
|
||||
from t6 import *
|
||||
self.assertEqual(fixdir(dir(t6)),
|
||||
['__all__', '__cached__', '__doc__', '__file__',
|
||||
'__loader__', '__name__', '__package__',
|
||||
'__path__', 'eggs', 'ham', 'spam'])
|
||||
'__path__', '__spec__', 'eggs', 'ham', 'spam'])
|
||||
self.assertEqual(dir(), ['eggs', 'ham', 'self', 'spam', 't6'])
|
||||
"""
|
||||
self.run_code(s)
|
||||
|
@ -256,18 +257,19 @@ class TestPkg(unittest.TestCase):
|
|||
import t7 as tas
|
||||
self.assertEqual(fixdir(dir(tas)),
|
||||
['__cached__', '__doc__', '__file__', '__loader__',
|
||||
'__name__', '__package__', '__path__'])
|
||||
'__name__', '__package__', '__path__', '__spec__'])
|
||||
self.assertFalse(t7)
|
||||
from t7 import sub as subpar
|
||||
self.assertEqual(fixdir(dir(subpar)),
|
||||
['__cached__', '__doc__', '__file__', '__loader__',
|
||||
'__name__', '__package__', '__path__'])
|
||||
'__name__', '__package__', '__path__', '__spec__'])
|
||||
self.assertFalse(t7)
|
||||
self.assertFalse(sub)
|
||||
from t7.sub import subsub as subsubsub
|
||||
self.assertEqual(fixdir(dir(subsubsub)),
|
||||
['__cached__', '__doc__', '__file__', '__loader__',
|
||||
'__name__', '__package__', '__path__', 'spam'])
|
||||
'__name__', '__package__', '__path__', '__spec__',
|
||||
'spam'])
|
||||
self.assertFalse(t7)
|
||||
self.assertFalse(sub)
|
||||
self.assertFalse(subsub)
|
||||
|
|
|
@ -208,9 +208,16 @@ class ExtendPathTests(unittest.TestCase):
|
|||
importers = list(iter_importers(fullname))
|
||||
expected_importer = get_importer(pathitem)
|
||||
for finder in importers:
|
||||
loader = finder.find_module(fullname)
|
||||
try:
|
||||
loader = loader.loader
|
||||
except AttributeError:
|
||||
# For now we still allow raw loaders from
|
||||
# find_module().
|
||||
pass
|
||||
self.assertIsInstance(finder, importlib.machinery.FileFinder)
|
||||
self.assertEqual(finder, expected_importer)
|
||||
self.assertIsInstance(finder.find_module(fullname),
|
||||
self.assertIsInstance(loader,
|
||||
importlib.machinery.SourceFileLoader)
|
||||
self.assertIsNone(finder.find_module(pkgname))
|
||||
|
||||
|
@ -222,8 +229,11 @@ class ExtendPathTests(unittest.TestCase):
|
|||
finally:
|
||||
shutil.rmtree(dirname)
|
||||
del sys.path[0]
|
||||
try:
|
||||
del sys.modules['spam']
|
||||
del sys.modules['spam.eggs']
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
def test_mixed_namespace(self):
|
||||
|
|
|
@ -253,6 +253,7 @@ class LongReprTest(unittest.TestCase):
|
|||
print("cached_path_len =", cached_path_len)
|
||||
|
||||
def test_module(self):
|
||||
self.maxDiff = None
|
||||
self._check_path_limitations(self.pkgname)
|
||||
create_empty_file(os.path.join(self.subpkgname, self.pkgname + '.py'))
|
||||
importlib.invalidate_caches()
|
||||
|
|
|
@ -47,6 +47,7 @@ implicit_namespace = {
|
|||
"__cached__": None,
|
||||
"__package__": None,
|
||||
"__doc__": None,
|
||||
# "__spec__": None, # XXX Uncomment.
|
||||
}
|
||||
example_namespace = {
|
||||
"sys": sys,
|
||||
|
@ -56,7 +57,7 @@ example_namespace = {
|
|||
"run_name_in_sys_modules": False,
|
||||
"module_in_sys_modules": False,
|
||||
"nested": dict(implicit_namespace,
|
||||
x=1, __name__="<run>", __loader__=None),
|
||||
x=1, __name__="<run>", __loader__=None, __spec__=None),
|
||||
}
|
||||
example_namespace.update(implicit_namespace)
|
||||
|
||||
|
@ -243,6 +244,7 @@ class RunModuleTestCase(unittest.TestCase, CodeExecutionMixin):
|
|||
"__name__": mod_name,
|
||||
"__file__": mod_fname,
|
||||
"__package__": mod_name.rpartition(".")[0],
|
||||
# "__spec__": None, # XXX Needs to be set.
|
||||
})
|
||||
if alter_sys:
|
||||
expected_ns.update({
|
||||
|
@ -279,6 +281,7 @@ class RunModuleTestCase(unittest.TestCase, CodeExecutionMixin):
|
|||
"__name__": mod_name,
|
||||
"__file__": mod_fname,
|
||||
"__package__": pkg_name,
|
||||
# "__spec__": None, # XXX Needs to be set.
|
||||
})
|
||||
if alter_sys:
|
||||
expected_ns.update({
|
||||
|
|
|
@ -45,6 +45,8 @@ module_init_dict(PyModuleObject *mod, PyObject *md_dict,
|
|||
return -1;
|
||||
if (PyDict_SetItemString(md_dict, "__loader__", Py_None) != 0)
|
||||
return -1;
|
||||
if (PyDict_SetItemString(md_dict, "__spec__", Py_None) != 0)
|
||||
return -1;
|
||||
if (PyUnicode_CheckExact(name)) {
|
||||
Py_INCREF(name);
|
||||
Py_XDECREF(mod->md_name);
|
||||
|
@ -398,55 +400,10 @@ module_dealloc(PyModuleObject *m)
|
|||
static PyObject *
|
||||
module_repr(PyModuleObject *m)
|
||||
{
|
||||
PyObject *name, *filename, *repr, *loader = NULL;
|
||||
PyThreadState *tstate = PyThreadState_GET();
|
||||
PyInterpreterState *interp = tstate->interp;
|
||||
|
||||
/* See if the module has an __loader__. If it does, give the loader the
|
||||
* first shot at producing a repr for the module.
|
||||
*/
|
||||
if (m->md_dict != NULL) {
|
||||
loader = PyDict_GetItemString(m->md_dict, "__loader__");
|
||||
}
|
||||
if (loader != NULL && loader != Py_None) {
|
||||
repr = PyObject_CallMethod(loader, "module_repr", "(O)",
|
||||
(PyObject *)m, NULL);
|
||||
if (repr == NULL) {
|
||||
PyErr_Clear();
|
||||
}
|
||||
else {
|
||||
return repr;
|
||||
}
|
||||
}
|
||||
/* __loader__.module_repr(m) did not provide us with a repr. Next, see if
|
||||
* the module has an __file__. If it doesn't then use repr(__loader__) if
|
||||
* it exists, otherwise, just use module.__name__.
|
||||
*/
|
||||
name = PyModule_GetNameObject((PyObject *)m);
|
||||
if (name == NULL) {
|
||||
PyErr_Clear();
|
||||
name = PyUnicode_FromStringAndSize("?", 1);
|
||||
if (name == NULL)
|
||||
return NULL;
|
||||
}
|
||||
filename = PyModule_GetFilenameObject((PyObject *)m);
|
||||
if (filename == NULL) {
|
||||
PyErr_Clear();
|
||||
/* There's no m.__file__, so if there was a __loader__, use that in
|
||||
* the repr, otherwise, the only thing you can use is m.__name__
|
||||
*/
|
||||
if (loader == NULL || loader == Py_None) {
|
||||
repr = PyUnicode_FromFormat("<module %R>", name);
|
||||
}
|
||||
else {
|
||||
repr = PyUnicode_FromFormat("<module %R (%R)>", name, loader);
|
||||
}
|
||||
}
|
||||
/* Finally, use m.__file__ */
|
||||
else {
|
||||
repr = PyUnicode_FromFormat("<module %R from %R>", name, filename);
|
||||
Py_DECREF(filename);
|
||||
}
|
||||
Py_DECREF(name);
|
||||
return repr;
|
||||
return PyObject_CallMethod(interp->importlib, "_module_repr", "O", m);
|
||||
}
|
||||
|
||||
static int
|
||||
|
|
|
@ -1232,7 +1232,8 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *given_globals,
|
|||
int level)
|
||||
{
|
||||
_Py_IDENTIFIER(__import__);
|
||||
_Py_IDENTIFIER(__initializing__);
|
||||
_Py_IDENTIFIER(__spec__);
|
||||
_Py_IDENTIFIER(_initializing);
|
||||
_Py_IDENTIFIER(__package__);
|
||||
_Py_IDENTIFIER(__path__);
|
||||
_Py_IDENTIFIER(__name__);
|
||||
|
@ -1426,16 +1427,21 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *given_globals,
|
|||
goto error_with_unlock;
|
||||
}
|
||||
else if (mod != NULL) {
|
||||
PyObject *value;
|
||||
PyObject *value = NULL;
|
||||
PyObject *spec;
|
||||
int initializing = 0;
|
||||
|
||||
Py_INCREF(mod);
|
||||
/* Optimization: only call _bootstrap._lock_unlock_module() if
|
||||
__initializing__ is true.
|
||||
NOTE: because of this, __initializing__ must be set *before*
|
||||
__spec__._initializing is true.
|
||||
NOTE: because of this, initializing must be set *before*
|
||||
stuffing the new module in sys.modules.
|
||||
*/
|
||||
value = _PyObject_GetAttrId(mod, &PyId___initializing__);
|
||||
spec = _PyObject_GetAttrId(mod, &PyId___spec__);
|
||||
if (spec != NULL) {
|
||||
value = _PyObject_GetAttrId(spec, &PyId__initializing);
|
||||
Py_DECREF(spec);
|
||||
}
|
||||
if (value == NULL)
|
||||
PyErr_Clear();
|
||||
else {
|
||||
|
|
7407
Python/importlib.h
7407
Python/importlib.h
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue