diff --git a/ansible_mitogen/connection/mitogen.py b/ansible_mitogen/connection/mitogen.py index 28e18c93..4e3c506c 100644 --- a/ansible_mitogen/connection/mitogen.py +++ b/ansible_mitogen/connection/mitogen.py @@ -30,11 +30,12 @@ import os import ansible.errors import ansible.plugins.connection -import ansible_mitogen.helpers + import mitogen.unix +from mitogen.utils import cast +import ansible_mitogen.helpers from ansible_mitogen.strategy.mitogen import ContextService -from ansible_mitogen.utils import cast class Connection(ansible.plugins.connection.ConnectionBase): diff --git a/ansible_mitogen/mixins.py b/ansible_mitogen/mixins.py index 14c543eb..ed6ec42e 100644 --- a/ansible_mitogen/mixins.py +++ b/ansible_mitogen/mixins.py @@ -25,18 +25,37 @@ # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +from __future__ import absolute_import import pwd import os import tempfile import ansible +import ansible.plugins import ansible.plugins.action +import mitogen.core import mitogen.master +from mitogen.utils import cast + import ansible_mitogen.helpers from ansible.module_utils._text import to_text -from ansible_mitogen.utils import cast -from ansible_mitogen.utils import get_command_module_name + + +def get_command_module_name(module_name): + """ + Given the name of an Ansible command module, return its canonical module + path within the ansible. + + :param module_name: + "shell" + :return: + "ansible.modules.commands.shell" + """ + path = ansible.plugins.module_loader.find_plugin(module_name, '') + relpath = os.path.relpath(path, os.path.dirname(ansible.__file__)) + root, _ = os.path.splitext(relpath) + return 'ansible.' + root.replace('/', '.') class ActionModuleMixin(ansible.plugins.action.ActionBase): diff --git a/ansible_mitogen/utils.py b/ansible_mitogen/utils.py deleted file mode 100644 index 98193a0e..00000000 --- a/ansible_mitogen/utils.py +++ /dev/null @@ -1,75 +0,0 @@ -# Copyright 2017, David Wilson -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# 3. Neither the name of the copyright holder nor the names of its contributors -# may be used to endorse or promote products derived from this software without -# specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -from __future__ import absolute_import -import os - -import ansible -import ansible.plugins -import mitogen.core - - -def cast(obj): - """ - Ansible loves to decorate built-in types to implement useful functionality - like Vault, however cPickle loves to preserve those decorations during - serialization, resulting in CallError. - - So here we recursively undecorate `obj`, ensuring that any instances of - subclasses of built-in types are downcast to the base type. - """ - if isinstance(obj, dict): - return {cast(k): cast(v) for k, v in obj.iteritems()} - if isinstance(obj, (list, tuple)): - return [cast(v) for v in obj] - if obj is None or isinstance(obj, (int, float)): - return obj - if isinstance(obj, unicode): - return unicode(obj) - if isinstance(obj, str): - return str(obj) - if isinstance(obj, (mitogen.core.Context, - mitogen.core.Dead, - mitogen.core.CallError)): - return obj - raise TypeError("Cannot serialize: %r: %r" % (type(obj), obj)) - - -def get_command_module_name(module_name): - """ - Given the name of an Ansible command module, return its canonical module - path within the ansible. - - :param module_name: - "shell" - :return: - "ansible.modules.commands.shell" - """ - path = ansible.plugins.module_loader.find_plugin(module_name, '') - relpath = os.path.relpath(path, os.path.dirname(ansible.__file__)) - root, _ = os.path.splitext(relpath) - return 'ansible.' + root.replace('/', '.') diff --git a/docs/api.rst b/docs/api.rst index 1b6b0b58..a3d5f6d1 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -971,6 +971,27 @@ Utility Functions A random assortment of utility functions useful on masters and children. +.. currentmodule:: mitogen.utils +.. function:: cast (obj) + + Many tools love to subclass built-in types in order to implement useful + functionality, such as annotating the safety of a Unicode string, or adding + additional methods to a dict. However, cPickle loves to preserve those + subtypes during serialization, resulting in CallError during :py:meth:`call + ` in the target when it tries to deserialize + the data. + + This function walks the object graph `obj`, producing a copy with any + custom sub-types removed. The functionality is not default since the + resulting walk may be computationally expensive given a large enough graph. + + See :ref:`serialization-rules` for a list of supported types. + + :param obj: + Object to undecorate. + :returns: + Undecorated object. + .. currentmodule:: mitogen.utils .. function:: disable_site_packages diff --git a/docs/getting_started.rst b/docs/getting_started.rst index 56c486fd..234b556f 100644 --- a/docs/getting_started.rst +++ b/docs/getting_started.rst @@ -281,6 +281,9 @@ User-defined types may not be used, except for: * :py:class:`mitogen.core.Context` * :py:class:`mitogen.core._DEAD` +Subclasses of built-in types must be undecorated using +:py:func:`mitogen.utils.cast`. + .. _troubleshooting: diff --git a/mitogen/utils.py b/mitogen/utils.py index f41a7dee..cd8a93d1 100644 --- a/mitogen/utils.py +++ b/mitogen/utils.py @@ -91,3 +91,22 @@ def with_router(func): return run_with_router(func, *args, **kwargs) wrapper.func_name = func.func_name return wrapper + + +def cast(obj): + if isinstance(obj, dict): + return {cast(k): cast(v) for k, v in obj.iteritems()} + if isinstance(obj, (list, tuple)): + return [cast(v) for v in obj] + if obj is None or isinstance(obj, (int, float)): + return obj + if isinstance(obj, unicode): + return unicode(obj) + if isinstance(obj, str): + return str(obj) + if isinstance(obj, (mitogen.core.Context, + mitogen.core.Dead, + mitogen.core.CallError)): + return obj + + raise TypeError("Cannot serialize: %r: %r" % (type(obj), obj))