Implement PEP 451 (ModuleSpec).

This commit is contained in:
Eric Snow 2013-11-22 09:05:39 -07:00
parent 9e6097ebe7
commit b523f8433a
37 changed files with 6972 additions and 4387 deletions

View File

@ -310,11 +310,11 @@ ABC hierarchy::
from the import. If the loader inserted a module and the load fails, it 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 must be removed by the loader from :data:`sys.modules`; modules already
in :data:`sys.modules` before the loader began execution should be left 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. The loader should set several attributes on the module.
(Note that some of these attributes can change when a module is (Note that some of these attributes can change when a module is
reloaded; see :meth:`init_module_attrs`): reloaded):
- :attr:`__name__` - :attr:`__name__`
The name of the module. The name of the module.
@ -357,17 +357,6 @@ ABC hierarchy::
.. versionchanged:: 3.4 .. versionchanged:: 3.4
Made optional instead of an abstractmethod. 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 .. class:: ResourceLoader
@ -442,14 +431,6 @@ ABC hierarchy::
.. versionadded:: 3.4 .. 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) .. method:: load_module(fullname)
Implementation of :meth:`Loader.load_module`. Implementation of :meth:`Loader.load_module`.
@ -474,15 +455,6 @@ ABC hierarchy::
.. versionchanged:: 3.4 .. versionchanged:: 3.4
Raises :exc:`ImportError` instead of :exc:`NotImplementedError`. 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) .. class:: FileLoader(fullname, path)
@ -599,14 +571,6 @@ ABC hierarchy::
``__init__`` when the file extension is removed **and** the module name ``__init__`` when the file extension is removed **and** the module name
itself does not end in ``__init__``. 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 :mod:`importlib.machinery` -- Importers and path hooks
------------------------------------------------------ ------------------------------------------------------
@ -882,6 +846,64 @@ find and load modules.
.. versionadded:: 3.4 .. 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 :mod:`importlib.util` -- Utility code for importers
--------------------------------------------------- ---------------------------------------------------
@ -952,20 +974,6 @@ an :term:`importer`.
.. versionadded:: 3.3 .. 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 .. decorator:: module_for_loader
A :term:`decorator` for :meth:`importlib.abc.Loader.load_module` A :term:`decorator` for :meth:`importlib.abc.Loader.load_module`
@ -999,9 +1007,8 @@ an :term:`importer`.
unconditionally to support reloading. unconditionally to support reloading.
.. deprecated:: 3.4 .. deprecated:: 3.4
For the benefit of :term:`loader` subclasses, please use The import machinery now directly performs all the functionality
:func:`module_to_load` and provided by this function.
:meth:`importlib.abc.Loader.init_module_attrs` instead.
.. decorator:: set_loader .. decorator:: set_loader
@ -1012,11 +1019,6 @@ an :term:`importer`.
the wrapped method (i.e. ``self``) is what :attr:`__loader__` should be set the wrapped method (i.e. ``self``) is what :attr:`__loader__` should be set
to. 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 .. versionchanged:: 3.4
Set ``__loader__`` if set to ``None``, as if the attribute does not Set ``__loader__`` if set to ``None``, as if the attribute does not
exist. 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__` 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. is set and has a value other than ``None`` it will not be changed.
.. note:: .. function:: spec_from_loader(name, loader, *, origin=None, is_package=None)
As this decorator sets :attr:`__package__` after loading the module, it is
recommended to use :meth:`importlib.abc.Loader.init_module_attrs` instead A factory function for creating a :class:`ModuleSpec` instance based
when appropriate. 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

View File

@ -210,6 +210,7 @@ Finders and loaders
.. index:: .. index::
single: finder single: finder
single: loader single: loader
single: module spec
If the named module is not found in :data:`sys.modules`, then Python's import 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 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. range and scope of module searching.
Finders do not actually load modules. If they can find the named module, they 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 return a :term:`module spec`, an encapsulation of the module's import-related
module and create the corresponding module object. information, which the import machinery then uses when loading the module.
The following sections describe the protocol for finders and loaders in more 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 detail, including how you can create and register new ones to extend the
import machinery. 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 Import hooks
------------ ------------
@ -270,24 +275,23 @@ The meta path
.. index:: .. index::
single: sys.meta_path single: sys.meta_path
pair: finder; find_module pair: finder; find_spec
pair: finder; find_loader
When the named module is not found in :data:`sys.modules`, Python next 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 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 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 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 The meta path finder can use any strategy it wants to determine whether it can
handle the named module or not. handle the named module or not.
If the meta path finder knows how to handle the named module, it returns a 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 :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. 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 arguments. The first is the fully qualified name of the module being
imported, for example ``foo.bar.baz``. The second argument is the path 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 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. 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, For example, assuming none of the modules involved has already been cached,
importing ``foo.bar.baz`` will first perform a top level import, calling 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 ``foo`` has been imported, ``foo.bar`` will be imported by traversing the
meta path a second time, calling 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 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 Some meta path finders only support top level imports. These importers will
always return ``None`` when anything other than ``None`` is passed as the 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` modules, and one that knows how to import modules from an :term:`import path`
(i.e. the :term:`path based finder`). (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 If and when a module spec is found, the import machinery will use it (and
:meth:`~importlib.abc.Loader.load_module` method is called, with a single the loader it contains) when loading the module. Here is an approximation
argument, the fully qualified name of the module being imported. This method of what happens during the loading portion of import::
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.
In many cases, the finder and loader can be the same object; in such cases the module = None
:meth:`finder.find_module()` would just return ``self``. 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 * If there is an existing module object with the given name in
:data:`sys.modules`, the loader must use that existing module. (Otherwise, :data:`sys.modules`, import will have already returned it.
: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`.
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 executes the module code. This is crucial because the module code may
(directly or indirectly) import itself; adding it to :data:`sys.modules` (directly or indirectly) import itself; adding it to :data:`sys.modules`
beforehand prevents unbounded recursion in the worst case and multiple beforehand prevents unbounded recursion in the worst case and multiple
loading in the best. loading in the best.
If loading fails, the loader must remove any modules it has inserted into * If loading fails, the failing module -- and only the failing module --
:data:`sys.modules`, but it must remove **only** the failing module, and gets removed from :data:`sys.modules`. Any module already in the
only if the loader itself has loaded it explicitly. Any module already in :data:`sys.modules` cache, and any module that was successfully loaded
the :data:`sys.modules` cache, and any module that was successfully loaded as a side-effect, must remain in the cache. This contrasts with
as a side-effect, must remain in the cache. 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 * After the module is created but before execution, the import machinery
attribute's value must be a string. The loader may opt to leave sets the import-related module attributes ("init_module_attrs"), as
``__file__`` unset if it has no semantic meaning (e.g. a module loaded from summarized in a `later section <Import-related module attributes>`_.
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`).
* The loader may set the ``__name__`` attribute of the module. While not * Module execution is the key moment of loading in which the module's
required, setting this attribute is highly recommended so that the namespace gets populated. Execution is entirely delegated to the
:meth:`repr()` of the module is more informative. loader, which gets to decide what gets populated and how.
* If the module is a package (either regular or namespace), the loader must * The module created during loading and passed to exec_module() may
set the module object's ``__path__`` attribute. The value must be not be the one returned at the end of import [#fnlo]_.
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 ``__loader__`` attribute must be set to the loader object that loaded .. versionchanged:: 3.4
the module. This is mostly for introspection and reloading, but can be The import system has taken over the boilerplate responsibilities of
used for additional loader-specific functionality, for example getting loaders. These were previously performed by the :meth:`load_module()`
data associated with a loader. If the attribute is missing or set to ``None`` method.
then the import machinery will automatically set it **after** the module has
been imported.
* The module's ``__package__`` attribute must be set. Its value must be a Loaders
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.
This attribute is used instead of ``__name__`` to calculate explicit Module loaders provide the critical function of loading: module execution.
relative imports for main modules, as defined in :pep:`366`. 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 * 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 dynamically loaded extension), the loader should execute the module's code
in the module's global name space (``module.__dict__``). 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 Module loaders may opt in to creating the module object during loading
attributes set above, and hooks in the loader, you can more explicitly control by implementing a :meth:`create_module()` method. It takes one argument,
the repr of module objects. 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 .. versionadded:: 3.4
argument, the module object. When ``repr(module)`` is called for a module The create_module() method of loaders.
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.
If the module has no ``__loader__`` attribute, or the loader has no .. versionchanged:: 3.4
:meth:`module_repr()` method, then the module object implementation itself The load_module() method was replaced by exec_module() and the import
will craft a default repr using whatever information is available. It will machinery assumed all the boilerplate responsibilities of loading.
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: 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 The load_module() method must implement all the boilerplate loading
:meth:`module_repr()` method, call it with a single argument, which is the functionality described above in addition to executing the module. All
module object. The value returned is used as the module's repr. the same constraints apply, with some additional clarification:
* If an exception occurs in :meth:`module_repr()`, the exception is caught * If there is an existing module object with the given name in
and discarded, and the calculation of the module's repr continues as if :data:`sys.modules`, the loader must use that existing module.
:meth:`module_repr()` did not exist. (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 * The module *must* exist in :data:`sys.modules` before the loader
module's repr. 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 * If loading fails, the loader must remove any modules it has inserted
``None``, then the loader's repr is used as part of the module's repr. 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 The import machinery uses a variety of information about each module
repr:: 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: Using a spec during import allows state to be transferred between import
@classmethod system components, e.g. between the finder that creates the module spec
def module_repr(cls, module): and the loader that executes it. Most importantly, it allows the
return "<module '{}' (namespace)>".format(module.__name__) 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: .. _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 attribute, and this was typically the way namespace packages were implemented
prior to :pep:`420`. With the adoption of :pep:`420`, namespace packages no prior to :pep:`420`. With the adoption of :pep:`420`, namespace packages no
longer need to supply ``__init__.py`` files containing only ``__path__`` 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. 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 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. not be limited to this.
As a meta path finder, the :term:`path based finder` implements the 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 additional hooks that can be used to customize how modules are found and
loaded from the :term:`import path`. 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 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 machinery begins the :term:`import path` search by calling the path
based finder's :meth:`find_module()` method as described previously. When based finder's :meth:`find_spec()` method as described previously. When
the ``path`` argument to :meth:`find_module()` is given, it will be a the ``path`` argument to :meth:`find_spec()` is given, it will be a
list of string paths to traverse - typically a package's ``__path__`` list of string paths to traverse - typically a package's ``__path__``
attribute for an import within that package. If the ``path`` argument 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. 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`. argument, it should raise :exc:`ImportError`.
If :data:`sys.path_hooks` iteration ends with no :term:`path entry finder` 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 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 there is no finder for this path entry) and return ``None``, indicating that
this :term:`meta path finder` could not find the module. 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 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 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 Path entry finder protocol
-------------------------- --------------------------
In order to support imports of modules and initialized packages and also to In order to support imports of modules and initialized packages and also to
contribute portions to namespace packages, path entry finders must implement 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 :meth:`find_spec()` takes one argument, the fully qualified name of the
module being imported. :meth:`find_loader()` returns a 2-tuple where the module being imported. :meth:`find_spec()` returns a fully populated
first item is the loader and the second item is a namespace :term:`portion`. spec for the module. This spec will always have "loader" set (with one
When the first item (i.e. the loader) is ``None``, this means that while the exception).
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 To indicate to the import machinery that the spec represents a namespace
ignored and the loader is returned from the path based finder, terminating :term:`portion`. the path entry finder sets "loader" on the spec to
the search through the path entries. ``None`` and "submodule_search_locations" to a list containing the
portion.
For backwards compatibility with other implementations of the import .. versionchanged:: 3.4
protocol, many path entry finders also support the same, find_spec() replaced find_loader() and find_module(), but of which
traditional :meth:`find_module()` method that meta path finders support. are now deprecated, but will be used if find_spec() is not defined.
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, Older path entry finders may implement one of these two deprecated methods
as it does not allow the path entry finder to contribute portions to instead of :meth:`find_spec()`. The methods are still respected for the
namespace packages. Instead path entry finders should implement the sake of backward compatibility. Howevever, if find_spec() is implemented
:meth:`find_loader()` method as described above. If it exists on the path on the path entry finder, the legacy methods are ignored.
entry finder, the import system will always call :meth:`find_loader()`
in preference to :meth:`find_module()`. :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 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 To selectively prevent import of some modules from a hook early on the
meta path (rather than disabling the standard import system entirely), meta path (rather than disabling the standard import system entirely),
it is sufficient to raise :exc:`ImportError` directly from 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 that the meta path search should continue. while raising an exception
terminates it immediately. 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:`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 .. rubric:: Footnotes

View File

@ -236,6 +236,26 @@ name of the codec responsible for producing the error::
(Contributed by Nick Coghlan in :issue:`17827`, :issue:`17828` and (Contributed by Nick Coghlan in :issue:`17827`, :issue:`17828` and
:issue:`19619`) :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 Other Language Changes
====================== ======================

View File

@ -16,7 +16,7 @@ except ImportError:
# Platform doesn't support dynamic loading. # Platform doesn't support dynamic loading.
load_dynamic = None 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 machinery
from importlib import util from importlib import util
@ -162,11 +162,17 @@ class _LoadSourceCompatibility(_HackedGetData, machinery.SourceFileLoader):
def load_source(name, pathname, file=None): def load_source(name, pathname, file=None):
_LoadSourceCompatibility(name, pathname, file).load_module(name) loader = _LoadSourceCompatibility(name, pathname, file)
module = sys.modules[name] 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 # To allow reloading to potentially work, use a non-hacked loader which
# won't rely on a now-closed file object. # won't rely on a now-closed file object.
module.__loader__ = machinery.SourceFileLoader(name, pathname) module.__loader__ = machinery.SourceFileLoader(name, pathname)
module.__spec__.loader = module.__loader__
return module return module
@ -177,11 +183,17 @@ class _LoadCompiledCompatibility(_HackedGetData, SourcelessFileLoader):
def load_compiled(name, pathname, file=None): def load_compiled(name, pathname, file=None):
"""**DEPRECATED**""" """**DEPRECATED**"""
_LoadCompiledCompatibility(name, pathname, file).load_module(name) loader = _LoadCompiledCompatibility(name, pathname, file)
module = sys.modules[name] 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 # To allow reloading to potentially work, use a non-hacked loader which
# won't rely on a now-closed file object. # won't rely on a now-closed file object.
module.__loader__ = SourcelessFileLoader(name, pathname) module.__loader__ = SourcelessFileLoader(name, pathname)
module.__spec__.loader = module.__loader__
return module return module
@ -196,7 +208,13 @@ def load_package(name, path):
break break
else: else:
raise ValueError('{!r} is not a package'.format(path)) 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): def load_module(name, file, filename, details):

View File

@ -46,19 +46,42 @@ def invalidate_caches():
finder.invalidate_caches() finder.invalidate_caches()
def find_loader(name, path=None): def find_spec(name, path=None):
"""Find the loader for the specified module. """Return the spec for the specified module.
First, sys.modules is checked to see if the module was already imported. If 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 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 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 loader could value of 'path' given to the finders. None is returned if no spec could
be found. be found.
Dotted names do not have their parent packages implicitly imported. You will Dotted names do not have their parent packages implicitly imported. You will
most likely need to explicitly import all parent packages in the proper 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: try:
@ -71,7 +94,18 @@ def find_loader(name, path=None):
pass pass
except AttributeError: except AttributeError:
raise ValueError('{}.__loader__ is not set'.format(name)) 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): def import_module(name, package=None):
@ -106,7 +140,11 @@ def reload(module):
""" """
if not module or not isinstance(module, types.ModuleType): if not module or not isinstance(module, types.ModuleType):
raise TypeError("reload() argument must be module") raise TypeError("reload() argument must be module")
name = module.__name__ try:
name = module.__spec__.name
except AttributeError:
name = module.__name__
if sys.modules.get(name) is not module: if sys.modules.get(name) is not module:
msg = "module {} not in sys.modules" msg = "module {} not in sys.modules"
raise ImportError(msg.format(name), name=name) raise ImportError(msg.format(name), name=name)
@ -118,13 +156,11 @@ def reload(module):
if parent_name and parent_name not in sys.modules: if parent_name and parent_name not in sys.modules:
msg = "parent {!r} not in sys.modules" msg = "parent {!r} not in sys.modules"
raise ImportError(msg.format(parent_name), name=parent_name) raise ImportError(msg.format(parent_name), name=parent_name)
loader = _bootstrap._find_module(name, None) spec = module.__spec__ = _bootstrap._find_spec(name, None, module)
if loader is None: methods = _bootstrap._SpecMethods(spec)
raise ImportError(_bootstrap._ERR_MSG.format(name), name=name) methods.exec(module)
module.__loader__ = loader
loader.load_module(name)
# The module may have replaced itself in sys.modules! # The module may have replaced itself in sys.modules!
return sys.modules[module.__name__] return sys.modules[name]
finally: finally:
try: try:
del _RELOADING[name] del _RELOADING[name]

File diff suppressed because it is too large Load Diff

View File

@ -40,12 +40,18 @@ class MetaPathFinder(Finder):
"""Abstract base class for import finders on sys.meta_path.""" """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): def find_module(self, fullname, path):
"""Abstract method which, when implemented, should find a module. """Return a loader for the module.
The fullname is a str and the path is a list of strings or None.
Returns a Loader object or None. 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): def invalidate_caches(self):
"""An optional method for clearing the finder's cache, if any. """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.""" """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): def find_loader(self, fullname):
"""Abstract method which, when implemented, returns a module loader or """Return (loader, namespace portion) for the path entry.
a possible part of a namespace.
The fullname is a str. Returns a 2-tuple of (Loader, portion) where The fullname is a str. The namespace portion is a sequence of
portion is a sequence of file system locations contributing to part of path entries contributing to part of a namespace package. The
a namespace package. The sequence may be empty and the loader may be sequence may be empty. If loader is not None, the portion will
None. be ignored.
The portion will be discarded if another path entry finder
locates the module as a normal module or package.
""" """
return None, [] return None, []
# XXX Deprecate.
find_module = _bootstrap._find_module_shim find_module = _bootstrap._find_module_shim
def invalidate_caches(self): def invalidate_caches(self):
@ -83,34 +97,46 @@ _register(PathEntryFinder, machinery.FileFinder)
class Loader(metaclass=abc.ABCMeta): 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 def create_module(self, spec):
repr for a module when appropriate (see PEP 420). The __repr__() method on """Return a module to initialize and into which to load.
the module type will use the method as appropriate.
""" 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.
@abc.abstractmethod create_module() is optional.
"""
# By default, defer to _SpecMethods.create() for the new module.
return None
# 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): def load_module(self, fullname):
"""Abstract method which when implemented should load a module. """Return the loaded module.
The fullname is a str.
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. ImportError is raised on failure.
""" """
raise ImportError raise ImportError
# XXX Deprecate.
def module_repr(self, module): def module_repr(self, module):
"""Return a module's repr. """Return a module's repr.
Used by the module type when the method does not raise Used by the module type when the method does not raise
NotImplementedError. NotImplementedError.
"""
raise NotImplementedError
def init_module_attrs(self, module): """
"""Set the module's __loader__ attribute.""" # The exception will cause ModuleType.__repr__ to ignore this method.
module.__loader__ = self raise NotImplementedError
class ResourceLoader(Loader): class ResourceLoader(Loader):
@ -138,12 +164,11 @@ class InspectLoader(Loader):
""" """
@abc.abstractmethod
def is_package(self, fullname): 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. 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 raise ImportError
@ -176,19 +201,10 @@ class InspectLoader(Loader):
argument should be where the data was retrieved (when applicable).""" argument should be where the data was retrieved (when applicable)."""
return compile(data, path, 'exec', dont_inherit=True) return compile(data, path, 'exec', dont_inherit=True)
def init_module_attrs(self, module): exec_module = _bootstrap._LoaderBasics.exec_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)
load_module = _bootstrap._LoaderBasics.load_module load_module = _bootstrap._LoaderBasics.load_module
_register(InspectLoader, machinery.BuiltinImporter, machinery.FrozenImporter, _register(InspectLoader, machinery.BuiltinImporter, machinery.FrozenImporter)
_bootstrap.NamespaceLoader)
class ExecutionLoader(InspectLoader): class ExecutionLoader(InspectLoader):
@ -225,18 +241,6 @@ class ExecutionLoader(InspectLoader):
else: else:
return self.source_to_code(source, path) 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) _register(ExecutionLoader, machinery.ExtensionFileLoader)

View File

@ -5,6 +5,7 @@ import _imp
from ._bootstrap import (SOURCE_SUFFIXES, DEBUG_BYTECODE_SUFFIXES, from ._bootstrap import (SOURCE_SUFFIXES, DEBUG_BYTECODE_SUFFIXES,
OPTIMIZED_BYTECODE_SUFFIXES, BYTECODE_SUFFIXES, OPTIMIZED_BYTECODE_SUFFIXES, BYTECODE_SUFFIXES,
EXTENSION_SUFFIXES) EXTENSION_SUFFIXES)
from ._bootstrap import ModuleSpec
from ._bootstrap import BuiltinImporter from ._bootstrap import BuiltinImporter
from ._bootstrap import FrozenImporter from ._bootstrap import FrozenImporter
from ._bootstrap import WindowsRegistryFinder from ._bootstrap import WindowsRegistryFinder

View File

@ -3,13 +3,14 @@
from ._bootstrap import MAGIC_NUMBER from ._bootstrap import MAGIC_NUMBER
from ._bootstrap import cache_from_source from ._bootstrap import cache_from_source
from ._bootstrap import decode_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 source_from_cache
from ._bootstrap import spec_from_loader
from ._bootstrap import spec_from_file_location
from ._bootstrap import _resolve_name from ._bootstrap import _resolve_name
from contextlib import contextmanager
import functools import functools
import sys
import warnings import warnings
@ -28,6 +29,58 @@ def resolve_name(name, package):
return _resolve_name(name[level:], package, level) 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): def module_for_loader(fxn):
"""Decorator to handle selecting the proper module for loaders. """Decorator to handle selecting the proper module for loaders.
@ -46,13 +99,11 @@ def module_for_loader(fxn):
the second argument. the second argument.
""" """
warnings.warn('To make it easier for subclasses, please use ' warnings.warn('The import system now takes care of this automatically.',
'importlib.util.module_to_load() and '
'importlib.abc.Loader.init_module_attrs()',
PendingDeprecationWarning, stacklevel=2) PendingDeprecationWarning, stacklevel=2)
@functools.wraps(fxn) @functools.wraps(fxn)
def module_for_loader_wrapper(self, fullname, *args, **kwargs): 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 module.__loader__ = self
try: try:
is_package = self.is_package(fullname) is_package = self.is_package(fullname)

View File

@ -245,14 +245,13 @@ def import_main_path(main_path):
# We should not try to load __main__ # We should not try to load __main__
# since that would execute 'if __name__ == "__main__"' # since that would execute 'if __name__ == "__main__"'
# clauses, potentially causing a psuedo fork bomb. # clauses, potentially causing a psuedo fork bomb.
loader = importlib.find_loader(main_name, path=dirs)
main_module = types.ModuleType(main_name) main_module = types.ModuleType(main_name)
try: # XXX Use a target of main_module?
loader.init_module_attrs(main_module) spec = importlib.find_spec(main_name, path=dirs)
except AttributeError: # init_module_attrs is optional methods = importlib._bootstrap._SpecMethods(spec)
pass methods.init_module_attrs(main_module)
main_module.__name__ = '__mp_main__' main_module.__name__ = '__mp_main__'
code = loader.get_code(main_name) code = spec.loader.get_code(main_name)
exec(code, main_module.__dict__) exec(code, main_module.__dict__)
old_main_modules.append(sys.modules['__main__']) old_main_modules.append(sys.modules['__main__'])

View File

@ -430,6 +430,7 @@ def iter_importers(fullname=""):
for item in path: for item in path:
yield get_importer(item) yield get_importer(item)
def get_loader(module_or_name): def get_loader(module_or_name):
"""Get a PEP 302 "loader" object for module_or_name """Get a PEP 302 "loader" object for module_or_name
@ -570,6 +571,7 @@ def extend_path(path, name):
return path return path
def get_data(package, resource): def get_data(package, resource):
"""Get a resource from a package. """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. 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'): if loader is None or not hasattr(loader, 'get_data'):
return None 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__'): if mod is None or not hasattr(mod, '__file__'):
return None return None

View File

@ -167,8 +167,9 @@ def _split_list(s, predicate):
def visiblename(name, all=None, obj=None): def visiblename(name, all=None, obj=None):
"""Decide whether to show documentation on a variable.""" """Decide whether to show documentation on a variable."""
# Certain special names are redundant or internal. # Certain special names are redundant or internal.
# XXX Remove __initializing__?
if name in {'__author__', '__builtins__', '__cached__', '__credits__', if name in {'__author__', '__builtins__', '__cached__', '__credits__',
'__date__', '__doc__', '__file__', '__initializing__', '__date__', '__doc__', '__file__', '__spec__',
'__loader__', '__module__', '__name__', '__package__', '__loader__', '__module__', '__name__', '__package__',
'__path__', '__qualname__', '__slots__', '__version__'}: '__path__', '__qualname__', '__slots__', '__version__'}:
return 0 return 0

View File

@ -2259,7 +2259,7 @@ order (MRO) for bases """
minstance.b = 2 minstance.b = 2
minstance.a = 1 minstance.a = 1
default_attributes = ['__name__', '__doc__', '__package__', default_attributes = ['__name__', '__doc__', '__package__',
'__loader__'] '__loader__', '__spec__']
names = [x for x in dir(minstance) if x not in default_attributes] names = [x for x in dir(minstance) if x not in default_attributes]
self.assertEqual(names, ['a', 'b']) self.assertEqual(names, ['a', 'b'])

View File

@ -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()

View File

@ -1036,11 +1036,14 @@ class ImportTracebackTests(unittest.TestCase):
# away from the traceback. # away from the traceback.
self.create_module("foo", "") self.create_module("foo", "")
importlib = sys.modules['_frozen_importlib'] 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: try:
def load_module(*args): def exec_module(*args):
1/0 1/0
importlib.SourceLoader.load_module = load_module importlib.SourceLoader.exec_module = exec_module
try: try:
import foo import foo
except ZeroDivisionError as e: except ZeroDivisionError as e:
@ -1049,7 +1052,10 @@ class ImportTracebackTests(unittest.TestCase):
self.fail("ZeroDivisionError should have been raised") self.fail("ZeroDivisionError should have been raised")
self.assert_traceback(tb, [__file__, '<frozen importlib', __file__]) self.assert_traceback(tb, [__file__, '<frozen importlib', __file__])
finally: 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') @unittest.skipUnless(TESTFN_UNENCODABLE, 'need TESTFN_UNENCODABLE')
def test_unencodable_filename(self): def test_unencodable_filename(self):

View File

@ -80,11 +80,6 @@ class LoaderTests(metaclass=abc.ABCMeta):
imported.""" imported."""
pass pass
@abc.abstractmethod
def test_module_reuse(self):
"""If a module is already in sys.modules, it should be reused."""
pass
@abc.abstractmethod @abc.abstractmethod
def test_state_after_failure(self): def test_state_after_failure(self):
"""If a module is already in sys.modules and a reload fails """If a module is already in sys.modules and a reload fails

View File

@ -8,6 +8,46 @@ import sys
import unittest 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): class FinderTests(abc.FinderTests):
"""Test find_module() for built-in modules.""" """Test find_module() for built-in modules."""
@ -17,22 +57,13 @@ class FinderTests(abc.FinderTests):
with util.uncache(builtin_util.NAME): with util.uncache(builtin_util.NAME):
found = self.machinery.BuiltinImporter.find_module(builtin_util.NAME) found = self.machinery.BuiltinImporter.find_module(builtin_util.NAME)
self.assertTrue(found) self.assertTrue(found)
self.assertTrue(hasattr(found, 'load_module'))
def test_package(self): # Built-in modules cannot be a package.
# Built-in modules cannot be a package. test_package = test_package_in_package = test_package_over_module = None
pass
def test_module_in_package(self): # Built-in modules cannot be in a package.
# Built-in modules cannobt be in a package. test_module_in_package = None
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
def test_failure(self): def test_failure(self):
assert 'importlib' not in sys.builtin_module_names assert 'importlib' not in sys.builtin_module_names

View File

@ -9,6 +9,70 @@ import types
import unittest 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): class LoaderTests(abc.LoaderTests):
"""Test load_module() for built-in modules.""" """Test load_module() for built-in modules."""
@ -33,17 +97,11 @@ class LoaderTests(abc.LoaderTests):
module = self.load_module(builtin_util.NAME) module = self.load_module(builtin_util.NAME)
self.verify(module) self.verify(module)
def test_package(self): # Built-in modules cannot be a package.
# Built-in modules cannot be a package. test_package = test_lacking_parent = None
pass
def test_lacking_parent(self): # No way to force an import failure.
# Built-in modules cannot be a package. test_state_after_failure = None
pass
def test_state_after_failure(self):
# Not way to force an imoprt failure.
pass
def test_module_reuse(self): def test_module_reuse(self):
# Test that the same module is used in a reload. # Test that the same module is used in a reload.

View File

@ -9,6 +9,8 @@ from . import util as ext_util
frozen_machinery, source_machinery = util.import_importlib('importlib.machinery') frozen_machinery, source_machinery = util.import_importlib('importlib.machinery')
# XXX find_spec tests
@unittest.skipIf(ext_util.FILENAME is None, '_testcapi not available') @unittest.skipIf(ext_util.FILENAME is None, '_testcapi not available')
@util.case_insensitive_tests @util.case_insensitive_tests
class ExtensionModuleCaseSensitivityTest: class ExtensionModuleCaseSensitivityTest:

View File

@ -6,6 +6,7 @@ machinery = test_util.import_importlib('importlib.machinery')
import unittest import unittest
# XXX find_spec tests
class FinderTests(abc.FinderTests): class FinderTests(abc.FinderTests):
@ -20,21 +21,14 @@ class FinderTests(abc.FinderTests):
def test_module(self): def test_module(self):
self.assertTrue(self.find_module(util.NAME)) self.assertTrue(self.find_module(util.NAME))
def test_package(self): # No extension module as an __init__ available for testing.
# No extension module as an __init__ available for testing. test_package = test_package_in_package = None
pass
def test_module_in_package(self): # No extension module in a package available for testing.
# No extension module in a package available for testing. test_module_in_package = None
pass
def test_package_in_package(self): # Extension modules cannot be an __init__ for a package.
# No extension module as an __init__ available for testing. test_package_over_module = None
pass
def test_package_over_module(self):
# Extension modules cannot be an __init__ for a package.
pass
def test_failure(self): def test_failure(self):
self.assertIsNone(self.find_module('asdfjkl;')) self.assertIsNone(self.find_module('asdfjkl;'))

View File

@ -6,9 +6,75 @@ machinery = util.import_importlib('importlib.machinery')
import os.path import os.path
import sys import sys
import types
import unittest 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): class LoaderTests(abc.LoaderTests):
"""Test load_module() for extension modules.""" """Test load_module() for extension modules."""
@ -39,13 +105,11 @@ class LoaderTests(abc.LoaderTests):
self.assertIsInstance(module.__loader__, self.assertIsInstance(module.__loader__,
self.machinery.ExtensionFileLoader) self.machinery.ExtensionFileLoader)
def test_package(self): # No extension module as __init__ available for testing.
# No extension module as __init__ available for testing. test_package = None
pass
def test_lacking_parent(self): # No extension module in a package available for testing.
# No extension module in a package available for testing. test_lacking_parent = None
pass
def test_module_reuse(self): def test_module_reuse(self):
with util.uncache(ext_util.NAME): with util.uncache(ext_util.NAME):
@ -53,9 +117,8 @@ class LoaderTests(abc.LoaderTests):
module2 = self.load_module(ext_util.NAME) module2 = self.load_module(ext_util.NAME)
self.assertIs(module1, module2) self.assertIs(module1, module2)
def test_state_after_failure(self): # No easy way to trigger a failure after a successful import.
# No easy way to trigger a failure after a successful import. test_state_after_failure = None
pass
def test_unloadable(self): def test_unloadable(self):
name = 'asdfjkl;' name = 'asdfjkl;'

View File

@ -6,6 +6,41 @@ machinery = util.import_importlib('importlib.machinery')
import unittest 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): class FinderTests(abc.FinderTests):
"""Test finding frozen modules.""" """Test finding frozen modules."""
@ -27,13 +62,11 @@ class FinderTests(abc.FinderTests):
loader = self.find('__phello__.spam', ['__phello__']) loader = self.find('__phello__.spam', ['__phello__'])
self.assertTrue(hasattr(loader, 'load_module')) self.assertTrue(hasattr(loader, 'load_module'))
def test_package_in_package(self): # No frozen package within another package to test with.
# No frozen package within another package to test with. test_package_in_package = None
pass
def test_package_over_module(self): # No easy way to test.
# No easy way to test. test_package_over_module = None
pass
def test_failure(self): def test_failure(self):
loader = self.find('<not real>') loader = self.find('<not real>')

View File

@ -3,9 +3,81 @@ from .. import util
machinery = util.import_importlib('importlib.machinery') machinery = util.import_importlib('importlib.machinery')
import unittest
import sys
from test.support import captured_stdout from test.support import captured_stdout
import types 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): class LoaderTests(abc.LoaderTests):
@ -68,9 +140,8 @@ class LoaderTests(abc.LoaderTests):
self.assertEqual(repr(module), self.assertEqual(repr(module),
"<module '__hello__' (frozen)>") "<module '__hello__' (frozen)>")
def test_state_after_failure(self): # No way to trigger an error in a frozen module.
# No way to trigger an error in a frozen module. test_state_after_failure = None
pass
def test_unloadable(self): def test_unloadable(self):
assert self.machinery.FrozenImporter.find_module('_not_real') is None assert self.machinery.FrozenImporter.find_module('_not_real') is None

View File

@ -46,8 +46,8 @@ class CallingOrder:
with util.import_state(meta_path=[]): with util.import_state(meta_path=[]):
with warnings.catch_warnings(record=True) as w: with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always') warnings.simplefilter('always')
self.assertIsNone(importlib._bootstrap._find_module('nothing', self.assertIsNone(importlib._bootstrap._find_spec('nothing',
None)) None))
self.assertEqual(len(w), 1) self.assertEqual(len(w), 1)
self.assertTrue(issubclass(w[-1].category, ImportWarning)) self.assertTrue(issubclass(w[-1].category, ImportWarning))

View File

@ -284,22 +284,6 @@ class ExecutionLoaderDefaultsTests:
tests = make_return_value_tests(ExecutionLoader, InspectLoaderDefaultsTests) tests = make_return_value_tests(ExecutionLoader, InspectLoaderDefaultsTests)
Frozen_ELDefaultTests, Source_ELDefaultsTests = tests 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 ########################################### ##### InspectLoader concrete methods ###########################################
class InspectLoaderSourceToCodeTests: class InspectLoaderSourceToCodeTests:
@ -385,60 +369,6 @@ class Source_ILGetCodeTests(InspectLoaderGetCodeTests, unittest.TestCase):
InspectLoaderSubclass = Source_IL 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: class InspectLoaderLoadModuleTests:
"""Test InspectLoader.load_module().""" """Test InspectLoader.load_module()."""
@ -550,80 +480,6 @@ class Source_ELGetCodeTests(ExecutionLoaderGetCodeTests, unittest.TestCase):
ExecutionLoaderSubclass = Source_EL 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 ############################################ ##### SourceLoader concrete methods ############################################
class SourceLoader: class SourceLoader:
@ -952,58 +808,5 @@ class Source_SourceOnlyLGetSourceTests(SourceLoaderGetSourceTests, unittest.Test
SourceOnlyLoaderMock = Source_SourceOnlyL 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__': if __name__ == '__main__':
unittest.main() unittest.main()

View File

@ -165,6 +165,96 @@ class Source_FindLoaderTests(FindLoaderTests, unittest.TestCase):
init = source_init 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: class ReloadTests:
"""Test module reloading for builtin and extension modules.""" """Test module reloading for builtin and extension modules."""
@ -219,6 +309,7 @@ class ReloadTests:
with support.temp_cwd(None) as cwd: with support.temp_cwd(None) as cwd:
with util.uncache('spam'): with util.uncache('spam'):
with support.DirsOnSysPath(cwd): with support.DirsOnSysPath(cwd):
# Start as a plain module.
self.init.invalidate_caches() self.init.invalidate_caches()
path = os.path.join(cwd, name + '.py') path = os.path.join(cwd, name + '.py')
cached = self.util.cache_from_source(path) cached = self.util.cache_from_source(path)
@ -232,11 +323,14 @@ class ReloadTests:
support.create_empty_file(path) support.create_empty_file(path)
module = self.init.import_module(name) module = self.init.import_module(name)
ns = vars(module) ns = vars(module)
del ns['__initializing__']
loader = ns.pop('__loader__') 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(loader.path, path)
self.assertEqual(ns, expected) self.assertEqual(ns, expected)
# Change to a package.
self.init.invalidate_caches() self.init.invalidate_caches()
init_path = os.path.join(cwd, name, '__init__.py') init_path = os.path.join(cwd, name, '__init__.py')
cached = self.util.cache_from_source(init_path) cached = self.util.cache_from_source(init_path)
@ -252,18 +346,21 @@ class ReloadTests:
os.rename(path, init_path) os.rename(path, init_path)
reloaded = self.init.reload(module) reloaded = self.init.reload(module)
ns = vars(reloaded) ns = vars(reloaded)
del ns['__initializing__']
loader = ns.pop('__loader__') loader = ns.pop('__loader__')
spec = ns.pop('__spec__')
self.assertEqual(spec.name, name)
self.assertEqual(spec.loader, loader)
self.assertIs(reloaded, module) self.assertIs(reloaded, module)
self.assertEqual(loader.path, init_path) self.assertEqual(loader.path, init_path)
self.maxDiff = None
self.assertEqual(ns, expected) self.assertEqual(ns, expected)
def test_reload_namespace_changed(self): def test_reload_namespace_changed(self):
self.maxDiff = None
name = 'spam' name = 'spam'
with support.temp_cwd(None) as cwd: with support.temp_cwd(None) as cwd:
with util.uncache('spam'): with util.uncache('spam'):
with support.DirsOnSysPath(cwd): with support.DirsOnSysPath(cwd):
# Start as a namespace package.
self.init.invalidate_caches() self.init.invalidate_caches()
bad_path = os.path.join(cwd, name, '__init.py') bad_path = os.path.join(cwd, name, '__init.py')
cached = self.util.cache_from_source(bad_path) cached = self.util.cache_from_source(bad_path)
@ -276,9 +373,12 @@ class ReloadTests:
init_file.write('eggs = None') init_file.write('eggs = None')
module = self.init.import_module(name) module = self.init.import_module(name)
ns = vars(module) ns = vars(module)
del ns['__initializing__']
loader = ns.pop('__loader__') loader = ns.pop('__loader__')
path = ns.pop('__path__') 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), self.assertEqual(set(path),
set([os.path.dirname(bad_path)])) set([os.path.dirname(bad_path)]))
with self.assertRaises(AttributeError): with self.assertRaises(AttributeError):
@ -286,6 +386,7 @@ class ReloadTests:
loader.path loader.path
self.assertEqual(ns, expected) self.assertEqual(ns, expected)
# Change to a regular package.
self.init.invalidate_caches() self.init.invalidate_caches()
init_path = os.path.join(cwd, name, '__init__.py') init_path = os.path.join(cwd, name, '__init__.py')
cached = self.util.cache_from_source(init_path) cached = self.util.cache_from_source(init_path)
@ -301,8 +402,10 @@ class ReloadTests:
os.rename(bad_path, init_path) os.rename(bad_path, init_path)
reloaded = self.init.reload(module) reloaded = self.init.reload(module)
ns = vars(reloaded) ns = vars(reloaded)
del ns['__initializing__']
loader = ns.pop('__loader__') loader = ns.pop('__loader__')
spec = ns.pop('__spec__')
self.assertEqual(spec.name, name)
self.assertEqual(spec.loader, loader)
self.assertIs(reloaded, module) self.assertIs(reloaded, module)
self.assertEqual(loader.path, init_path) self.assertEqual(loader.path, init_path)
self.assertEqual(ns, expected) self.assertEqual(ns, expected)
@ -371,12 +474,23 @@ class StartupTests:
# Issue #17098: all modules should have __loader__ defined. # Issue #17098: all modules should have __loader__ defined.
for name, module in sys.modules.items(): for name, module in sys.modules.items():
if isinstance(module, types.ModuleType): if isinstance(module, types.ModuleType):
self.assertTrue(hasattr(module, '__loader__'), with self.subTest(name=name):
'{!r} lacks a __loader__ attribute'.format(name)) self.assertTrue(hasattr(module, '__loader__'),
if self.machinery.BuiltinImporter.find_module(name): '{!r} lacks a __loader__ attribute'.format(name))
self.assertIsNot(module.__loader__, None) if self.machinery.BuiltinImporter.find_module(name):
elif self.machinery.FrozenImporter.find_module(name): self.assertIsNot(module.__loader__, None)
self.assertIsNot(module.__loader__, None) 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): class Frozen_StartupTests(StartupTests, unittest.TestCase):
machinery = frozen_machinery machinery = frozen_machinery

View File

@ -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

View File

@ -34,71 +34,6 @@ Frozen_DecodeSourceBytesTests, Source_DecodeSourceBytesTests = test_util.test_bo
DecodeSourceBytesTests, util=[frozen_util, source_util]) 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: class ModuleForLoaderTests:
"""Tests for importlib.util.module_for_loader.""" """Tests for importlib.util.module_for_loader."""

View File

@ -37,8 +37,10 @@ class ModuleTests(unittest.TestCase):
self.assertEqual(foo.__doc__, None) self.assertEqual(foo.__doc__, None)
self.assertIs(foo.__loader__, None) self.assertIs(foo.__loader__, None)
self.assertIs(foo.__package__, None) self.assertIs(foo.__package__, None)
self.assertIs(foo.__spec__, None)
self.assertEqual(foo.__dict__, {"__name__": "foo", "__doc__": None, self.assertEqual(foo.__dict__, {"__name__": "foo", "__doc__": None,
"__loader__": None, "__package__": None}) "__loader__": None, "__package__": None,
"__spec__": None})
def test_ascii_docstring(self): def test_ascii_docstring(self):
# ASCII docstring # ASCII docstring
@ -47,7 +49,8 @@ class ModuleTests(unittest.TestCase):
self.assertEqual(foo.__doc__, "foodoc") self.assertEqual(foo.__doc__, "foodoc")
self.assertEqual(foo.__dict__, self.assertEqual(foo.__dict__,
{"__name__": "foo", "__doc__": "foodoc", {"__name__": "foo", "__doc__": "foodoc",
"__loader__": None, "__package__": None}) "__loader__": None, "__package__": None,
"__spec__": None})
def test_unicode_docstring(self): def test_unicode_docstring(self):
# Unicode docstring # Unicode docstring
@ -56,7 +59,8 @@ class ModuleTests(unittest.TestCase):
self.assertEqual(foo.__doc__, "foodoc\u1234") self.assertEqual(foo.__doc__, "foodoc\u1234")
self.assertEqual(foo.__dict__, self.assertEqual(foo.__dict__,
{"__name__": "foo", "__doc__": "foodoc\u1234", {"__name__": "foo", "__doc__": "foodoc\u1234",
"__loader__": None, "__package__": None}) "__loader__": None, "__package__": None,
"__spec__": None})
def test_reinit(self): def test_reinit(self):
# Reinitialization should not replace the __dict__ # Reinitialization should not replace the __dict__
@ -69,7 +73,7 @@ class ModuleTests(unittest.TestCase):
self.assertEqual(foo.bar, 42) self.assertEqual(foo.bar, 42)
self.assertEqual(foo.__dict__, self.assertEqual(foo.__dict__,
{"__name__": "foo", "__doc__": "foodoc", "bar": 42, {"__name__": "foo", "__doc__": "foodoc", "bar": 42,
"__loader__": None, "__package__": None}) "__loader__": None, "__package__": None, "__spec__": None})
self.assertTrue(foo.__dict__ is d) self.assertTrue(foo.__dict__ is d)
def test_dont_clear_dict(self): def test_dont_clear_dict(self):

View File

@ -1,5 +1,4 @@
import contextlib import contextlib
from importlib._bootstrap import NamespaceLoader
import importlib.abc import importlib.abc
import importlib.machinery import importlib.machinery
import os import os
@ -290,24 +289,5 @@ class ModuleAndNamespacePackageInSameDir(NamespacePackageTest):
self.assertEqual(a_test.attr, 'in module') 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__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@ -199,14 +199,14 @@ class TestPkg(unittest.TestCase):
import t5 import t5
self.assertEqual(fixdir(dir(t5)), self.assertEqual(fixdir(dir(t5)),
['__cached__', '__doc__', '__file__', '__loader__', ['__cached__', '__doc__', '__file__', '__loader__',
'__name__', '__package__', '__path__', 'foo', '__name__', '__package__', '__path__', '__spec__',
'string', 't5']) 'foo', 'string', 't5'])
self.assertEqual(fixdir(dir(t5.foo)), self.assertEqual(fixdir(dir(t5.foo)),
['__cached__', '__doc__', '__file__', '__loader__', ['__cached__', '__doc__', '__file__', '__loader__',
'__name__', '__package__', 'string']) '__name__', '__package__', '__spec__', 'string'])
self.assertEqual(fixdir(dir(t5.string)), self.assertEqual(fixdir(dir(t5.string)),
['__cached__', '__doc__', '__file__', '__loader__', ['__cached__', '__doc__', '__file__', '__loader__',
'__name__', '__package__', 'spam']) '__name__', '__package__', '__spec__', 'spam'])
def test_6(self): def test_6(self):
hier = [ hier = [
@ -222,14 +222,15 @@ class TestPkg(unittest.TestCase):
import t6 import t6
self.assertEqual(fixdir(dir(t6)), self.assertEqual(fixdir(dir(t6)),
['__all__', '__cached__', '__doc__', '__file__', ['__all__', '__cached__', '__doc__', '__file__',
'__loader__', '__name__', '__package__', '__path__']) '__loader__', '__name__', '__package__', '__path__',
'__spec__'])
s = """ s = """
import t6 import t6
from t6 import * from t6 import *
self.assertEqual(fixdir(dir(t6)), self.assertEqual(fixdir(dir(t6)),
['__all__', '__cached__', '__doc__', '__file__', ['__all__', '__cached__', '__doc__', '__file__',
'__loader__', '__name__', '__package__', '__loader__', '__name__', '__package__',
'__path__', 'eggs', 'ham', 'spam']) '__path__', '__spec__', 'eggs', 'ham', 'spam'])
self.assertEqual(dir(), ['eggs', 'ham', 'self', 'spam', 't6']) self.assertEqual(dir(), ['eggs', 'ham', 'self', 'spam', 't6'])
""" """
self.run_code(s) self.run_code(s)
@ -256,18 +257,19 @@ class TestPkg(unittest.TestCase):
import t7 as tas import t7 as tas
self.assertEqual(fixdir(dir(tas)), self.assertEqual(fixdir(dir(tas)),
['__cached__', '__doc__', '__file__', '__loader__', ['__cached__', '__doc__', '__file__', '__loader__',
'__name__', '__package__', '__path__']) '__name__', '__package__', '__path__', '__spec__'])
self.assertFalse(t7) self.assertFalse(t7)
from t7 import sub as subpar from t7 import sub as subpar
self.assertEqual(fixdir(dir(subpar)), self.assertEqual(fixdir(dir(subpar)),
['__cached__', '__doc__', '__file__', '__loader__', ['__cached__', '__doc__', '__file__', '__loader__',
'__name__', '__package__', '__path__']) '__name__', '__package__', '__path__', '__spec__'])
self.assertFalse(t7) self.assertFalse(t7)
self.assertFalse(sub) self.assertFalse(sub)
from t7.sub import subsub as subsubsub from t7.sub import subsub as subsubsub
self.assertEqual(fixdir(dir(subsubsub)), self.assertEqual(fixdir(dir(subsubsub)),
['__cached__', '__doc__', '__file__', '__loader__', ['__cached__', '__doc__', '__file__', '__loader__',
'__name__', '__package__', '__path__', 'spam']) '__name__', '__package__', '__path__', '__spec__',
'spam'])
self.assertFalse(t7) self.assertFalse(t7)
self.assertFalse(sub) self.assertFalse(sub)
self.assertFalse(subsub) self.assertFalse(subsub)

View File

@ -208,9 +208,16 @@ class ExtendPathTests(unittest.TestCase):
importers = list(iter_importers(fullname)) importers = list(iter_importers(fullname))
expected_importer = get_importer(pathitem) expected_importer = get_importer(pathitem)
for finder in importers: 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.assertIsInstance(finder, importlib.machinery.FileFinder)
self.assertEqual(finder, expected_importer) self.assertEqual(finder, expected_importer)
self.assertIsInstance(finder.find_module(fullname), self.assertIsInstance(loader,
importlib.machinery.SourceFileLoader) importlib.machinery.SourceFileLoader)
self.assertIsNone(finder.find_module(pkgname)) self.assertIsNone(finder.find_module(pkgname))
@ -222,8 +229,11 @@ class ExtendPathTests(unittest.TestCase):
finally: finally:
shutil.rmtree(dirname) shutil.rmtree(dirname)
del sys.path[0] del sys.path[0]
del sys.modules['spam'] try:
del sys.modules['spam.eggs'] del sys.modules['spam']
del sys.modules['spam.eggs']
except KeyError:
pass
def test_mixed_namespace(self): def test_mixed_namespace(self):

View File

@ -253,6 +253,7 @@ class LongReprTest(unittest.TestCase):
print("cached_path_len =", cached_path_len) print("cached_path_len =", cached_path_len)
def test_module(self): def test_module(self):
self.maxDiff = None
self._check_path_limitations(self.pkgname) self._check_path_limitations(self.pkgname)
create_empty_file(os.path.join(self.subpkgname, self.pkgname + '.py')) create_empty_file(os.path.join(self.subpkgname, self.pkgname + '.py'))
importlib.invalidate_caches() importlib.invalidate_caches()

View File

@ -47,6 +47,7 @@ implicit_namespace = {
"__cached__": None, "__cached__": None,
"__package__": None, "__package__": None,
"__doc__": None, "__doc__": None,
# "__spec__": None, # XXX Uncomment.
} }
example_namespace = { example_namespace = {
"sys": sys, "sys": sys,
@ -56,7 +57,7 @@ example_namespace = {
"run_name_in_sys_modules": False, "run_name_in_sys_modules": False,
"module_in_sys_modules": False, "module_in_sys_modules": False,
"nested": dict(implicit_namespace, "nested": dict(implicit_namespace,
x=1, __name__="<run>", __loader__=None), x=1, __name__="<run>", __loader__=None, __spec__=None),
} }
example_namespace.update(implicit_namespace) example_namespace.update(implicit_namespace)
@ -243,6 +244,7 @@ class RunModuleTestCase(unittest.TestCase, CodeExecutionMixin):
"__name__": mod_name, "__name__": mod_name,
"__file__": mod_fname, "__file__": mod_fname,
"__package__": mod_name.rpartition(".")[0], "__package__": mod_name.rpartition(".")[0],
# "__spec__": None, # XXX Needs to be set.
}) })
if alter_sys: if alter_sys:
expected_ns.update({ expected_ns.update({
@ -279,6 +281,7 @@ class RunModuleTestCase(unittest.TestCase, CodeExecutionMixin):
"__name__": mod_name, "__name__": mod_name,
"__file__": mod_fname, "__file__": mod_fname,
"__package__": pkg_name, "__package__": pkg_name,
# "__spec__": None, # XXX Needs to be set.
}) })
if alter_sys: if alter_sys:
expected_ns.update({ expected_ns.update({

View File

@ -45,6 +45,8 @@ module_init_dict(PyModuleObject *mod, PyObject *md_dict,
return -1; return -1;
if (PyDict_SetItemString(md_dict, "__loader__", Py_None) != 0) if (PyDict_SetItemString(md_dict, "__loader__", Py_None) != 0)
return -1; return -1;
if (PyDict_SetItemString(md_dict, "__spec__", Py_None) != 0)
return -1;
if (PyUnicode_CheckExact(name)) { if (PyUnicode_CheckExact(name)) {
Py_INCREF(name); Py_INCREF(name);
Py_XDECREF(mod->md_name); Py_XDECREF(mod->md_name);
@ -398,55 +400,10 @@ module_dealloc(PyModuleObject *m)
static PyObject * static PyObject *
module_repr(PyModuleObject *m) 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 return PyObject_CallMethod(interp->importlib, "_module_repr", "O", m);
* 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;
} }
static int static int

View File

@ -1232,7 +1232,8 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *given_globals,
int level) int level)
{ {
_Py_IDENTIFIER(__import__); _Py_IDENTIFIER(__import__);
_Py_IDENTIFIER(__initializing__); _Py_IDENTIFIER(__spec__);
_Py_IDENTIFIER(_initializing);
_Py_IDENTIFIER(__package__); _Py_IDENTIFIER(__package__);
_Py_IDENTIFIER(__path__); _Py_IDENTIFIER(__path__);
_Py_IDENTIFIER(__name__); _Py_IDENTIFIER(__name__);
@ -1426,16 +1427,21 @@ PyImport_ImportModuleLevelObject(PyObject *name, PyObject *given_globals,
goto error_with_unlock; goto error_with_unlock;
} }
else if (mod != NULL) { else if (mod != NULL) {
PyObject *value; PyObject *value = NULL;
PyObject *spec;
int initializing = 0; int initializing = 0;
Py_INCREF(mod); Py_INCREF(mod);
/* Optimization: only call _bootstrap._lock_unlock_module() if /* Optimization: only call _bootstrap._lock_unlock_module() if
__initializing__ is true. __spec__._initializing is true.
NOTE: because of this, __initializing__ must be set *before* NOTE: because of this, initializing must be set *before*
stuffing the new module in sys.modules. 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) if (value == NULL)
PyErr_Clear(); PyErr_Clear();
else { else {

File diff suppressed because it is too large Load Diff