def low(self, fun, low, print_event=True, full_return=False):
        '''
        Execute a function from low data
        Low data includes:
            required:
                - fun: the name of the function to run
            optional:
                - arg: a list of args to pass to fun
                - kwarg: kwargs for fun
                - __user__: user who is running the command
                - __jid__: jid to run under
                - __tag__: tag to run under
        '''
        # fire the mminion loading (if not already done) here
        # this is not to clutter the output with the module loading
        # if we have a high debug level.
        self.mminion  # pylint: disable=W0104
        jid = low.get('__jid__', salt.utils.jid.gen_jid(self.opts))
        tag = low.get('__tag__', salt.utils.event.tagify(jid, prefix=self.tag_prefix))

        data = {'fun': '{0}.{1}'.format(self.client, fun),
                'jid': jid,
                'user': low.get('__user__', 'UNKNOWN'),
               }

        event = salt.utils.event.get_event(
                'master',
                self.opts['sock_dir'],
                self.opts['transport'],
                opts=self.opts,
                listen=False)

        if print_event:
            print_func = self.print_async_event \
                if hasattr(self, 'print_async_event') \
                else None
        else:
            # Suppress printing of return event (this keeps us from printing
            # runner/wheel output during orchestration).
            print_func = None

        namespaced_event = salt.utils.event.NamespacedEvent(
            event,
            tag,
            print_func=print_func
        )

        # TODO: test that they exist
        # TODO: Other things to inject??
        func_globals = {'__jid__': jid,
                        '__user__': data['user'],
                        '__tag__': tag,
                        # weak ref to avoid the Exception in interpreter
                        # teardown of event
                        '__jid_event__': weakref.proxy(namespaced_event),
                        }

        try:
            self_functions = pycopy.copy(self.functions)
            salt.utils.lazy.verify_fun(self_functions, fun)

            # Inject some useful globals to *all* the function's global
            # namespace only once per module-- not per func
            completed_funcs = []

            for mod_name in six.iterkeys(self_functions):
                if '.' not in mod_name:
                    continue
                mod, _ = mod_name.split('.', 1)
                if mod in completed_funcs:
                    continue
                completed_funcs.append(mod)
                for global_key, value in six.iteritems(func_globals):
                    self.functions[mod_name].__globals__[global_key] = value

            # There are some discrepancies of what a "low" structure is in the
            # publisher world it is a dict including stuff such as jid, fun,
            # arg (a list of args, with kwargs packed in). Historically this
            # particular one has had no "arg" and just has had all the kwargs
            # packed into the top level object. The plan is to move away from
            # that since the caller knows what is an arg vs a kwarg, but while
            # we make the transition we will load "kwargs" using format_call if
            # there are no kwargs in the low object passed in.

            if 'arg' in low and 'kwarg' in low:
                args = low['arg']
                kwargs = low['kwarg']
            else:
                f_call = salt.utils.args.format_call(
                    self.functions[fun],
                    low,
                    expected_extra_kws=CLIENT_INTERNAL_KEYWORDS
                )
                args = f_call.get('args', ())
                kwargs = f_call.get('kwargs', {})

            # Update the event data with loaded args and kwargs
            data['fun_args'] = list(args) + ([kwargs] if kwargs else [])
            func_globals['__jid_event__'].fire_event(data, 'new')

            # Track the job locally so we know what is running on the master
            serial = salt.payload.Serial(self.opts)
            jid_proc_file = os.path.join(*[self.opts['cachedir'], 'proc', jid])
            data['pid'] = os.getpid()
            with salt.utils.files.fopen(jid_proc_file, 'w+b') as fp_:
                fp_.write(serial.dumps(data))
            del data['pid']

            # Initialize a context for executing the method.
            with tornado.stack_context.StackContext(self.functions.context_dict.clone):
                func = self.functions[fun]
                try:
                    data['return'] = func(*args, **kwargs)
                except TypeError as exc:
                    data['return'] = salt.utils.text.cli_info('Error: {exc}\nUsage:\n{doc}'.format(
                        exc=exc, doc=func.__doc__), 'Passed invalid arguments')
                except Exception as exc:
                    data['return'] = salt.utils.text.cli_info(six.text_type(exc), 'General error occurred')
                try:
                    data['success'] = self.context.get('retcode', 0) == 0
                except AttributeError:
                    # Assume a True result if no context attribute
                    data['success'] = True
                if isinstance(data['return'], dict) and 'data' in data['return']:
                    # some functions can return boolean values
                    data['success'] = salt.utils.state.check_result(data['return']['data'])
        except (Exception, SystemExit) as ex:
            if isinstance(ex, salt.exceptions.NotImplemented):
                data['return'] = six.text_type(ex)
            else:
                data['return'] = 'Exception occurred in {client} {fun}: {tb}'.format(
                    client=self.client, fun=fun, tb=traceback.format_exc())
            data['success'] = False
        finally:
            # Job has finished or issue found, so let's clean up after ourselves
            try:
                os.remove(jid_proc_file)
            except OSError as err:
                log.error("Error attempting to remove master job tracker: %s", err)

        if self.store_job:
            try:
                salt.utils.job.store_job(
                    self.opts,
                    {
                        'id': self.opts['id'],
                        'tgt': self.opts['id'],
                        'jid': data['jid'],
                        'return': data,
                    },
                    event=None,
                    mminion=self.mminion,
                    )
            except salt.exceptions.SaltCacheError:
                log.error('Could not store job cache info. '
                          'Job details for this run may be unavailable.')

        # Outputters _can_ mutate data so write to the job cache first!
        namespaced_event.fire_event(data, 'ret')

        # if we fired an event, make sure to delete the event object.
        # This will ensure that we call destroy, which will do the 0MQ linger
        log.info('Runner completed: %s', data['jid'])
        del event
        del namespaced_event
        return data if full_return else data['return']