summaryrefslogtreecommitdiffstats
path: root/docs/docsite/rst/dev_guide/testing/sanity/import.rst
blob: 6a5d3294503e6434bb1fa64d6ddcc24ab3f8920a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import
======

Ansible :ref:`allows unchecked imports<allowed_unchecked_imports>` of some libraries from specific directories.
Importing any other Python library requires :ref:`handling import errors<handling_import_errors>`.
This enables support for sanity tests such as :ref:`testing_validate-modules` and provides better error messages to the user.

.. _handling_import_errors:

Handling import errors
----------------------

In modules
^^^^^^^^^^

Instead of using ``import another_library``:

.. code-block:: python

   import traceback

   from ansible.module_utils.basic import missing_required_lib

   try:
       import another_library
   except ImportError:
       HAS_ANOTHER_LIBRARY = False
       ANOTHER_LIBRARY_IMPORT_ERROR = traceback.format_exc()
   else:
       HAS_ANOTHER_LIBRARY = True
       ANOTHER_LIBRARY_IMPORT_ERROR = None

.. note::

   The ``missing_required_lib`` import above will be used below.

Then in the module code:

.. code-block:: python

   module = AnsibleModule(...)

   if not HAS_ANOTHER_LIBRARY:
       module.fail_json(
           msg=missing_required_lib('another_library'),
           exception=ANOTHER_LIBRARY_IMPORT_ERROR)

In plugins
^^^^^^^^^^

Instead of using ``import another_library``:

.. code-block:: python

   try:
       import another_library
   except ImportError as imp_exc:
       ANOTHER_LIBRARY_IMPORT_ERROR = imp_exc
   else:
       ANOTHER_LIBRARY_IMPORT_ERROR = None

Then in the plugin code, for example in ``__init__`` of the plugin:

.. code-block:: python

   if ANOTHER_LIBRARY_IMPORT_ERROR:
       raise AnsibleError('another_library must be installed to use this plugin') from ANOTHER_LIBRARY_IMPORT_ERROR

When used as base classes
^^^^^^^^^^^^^^^^^^^^^^^^^

.. important::

   This solution builds on the previous two examples.
   Make sure to pick the appropriate one before continuing with this solution.

Sometimes an import is used in a base class, for example:

.. code-block:: python

   from another_library import UsefulThing

   class CustomThing(UsefulThing):
       pass

One option is make the entire class definition conditional:

.. code-block:: python

   if not ANOTHER_LIBRARY_IMPORT_ERROR:
       class CustomThing(UsefulThing):
           pass

Another option is to define a substitute base class by modifying the exception handler:

.. code-block:: python

   try:
       from another_library import UsefulThing
   except ImportError:
       class UsefulThing:
           pass
       ...

.. _allowed_unchecked_imports:

Allowed unchecked imports
-------------------------

Ansible allows the following unchecked imports from these specific directories:

* ansible-core:

  * For ``lib/ansible/modules/`` and ``lib/ansible/module_utils/``, unchecked imports are only allowed from the Python standard library;
  * For ``lib/ansible/plugins/``, unchecked imports are only allowed from the Python standard library, from public dependencies of ansible-core, and from ansible-core itself;

* collections:

  * For ``plugins/modules/`` and ``plugins/module_utils/``, unchecked imports are only allowed from the Python standard library;
  * For other directories in ``plugins/`` (see `the community collection requirements <https://github.com/ansible-collections/overview/blob/main/collection_requirements.rst#modules-plugins>`_ for a list), unchecked imports are only allowed from the Python standard library, from public dependencies of ansible-core, and from ansible-core itself.

Public dependencies of ansible-core are:

  * Jinja2
  * PyYAML
  * MarkupSafe (as a dependency of Jinja2)