# Path: chopsticks/serialise_main.py
# def serialise_func(f, seen=()):
#     """Serialise a function defined in __main__ to be called remotely."""
#     source = inspect.getsource(f)
# 
#     # We compile the source we get rather than inspecting the code object of
#     # f - this captures named loaded in executing the default argument
#     # expressions
#     code = compile(source, '<main>', 'exec')
#     names = trace_globals(code)
# 
#     imported_names = {}
#     variables = {}
# 
#     fglobals = f.func_globals if PY2 else f.__globals__
#     for name in names:
#         try:
#             v = fglobals[name]
#         except KeyError:
#             # Perhaps not need, or perhaps the function will crash
#             continue
#         if isinstance(v, types.ModuleType):
#             imported_names[name] = v.__name__
#         elif isinstance(v, types.FunctionType) and v.__module__ == '__main__':
#             if v in seen:
#                 continue
#             else:
#                 subdeps = serialise_func(v, seen=seen + (f, v,))
#                 vsource, _, _, vnames, vvars = subdeps
#                 source += '\n\n' + vsource
#                 imported_names.update(vnames)
#                 variables.update(vvars)
#         else:
#             variables[name] = v
# 
#     # We can't tell what submodules are used within f, but we can
#     # calculate a list of submodules that have been imported, so let's send
#     # that list.
#     imports = set(imported_names.values())
#     prefixes = tuple(mod + '.' for mod in imported_names.values())
#     imports.update(mod for mod in sys.modules if mod.startswith(prefixes))
# 
#     return source, f.__name__, imports, imported_names, variables
# 
# def deserialise_func(source, f_name, imports, imported_names, variables):
#     """Deserialise a function serialised with serialise_func."""
#     ns = chopmain.__dict__
#     sys.modules['__chopmain__'] = chopmain
# 
#     for mod in imports:
#         __import__(mod)
#     ns.update(variables)
#     for name, modname in imported_names.items():
#         ns[name] = sys.modules[modname]
#     code = compile(source, '<__chopmain__>', 'exec')
#     exec_(code, ns, ns)
#     return ns[f_name]


import sys
import xml.etree
from unittest import TestCase
from chopsticks.serialise_main import serialise_func, deserialise_func
CONST = 5

# This constant should not be serialised, because it is unused
UNUSED_CONST = 7


# Create an alias for the xml module
xml_processing = xml


def bar():
    xml_processing  # Just reference this import to ensure it is serialised
    return list(range(CONST))


def foo():
    """Test function to be serialised, calls bar()."""
    return bar()[-2:]


# Trigger the serialisation behaviour
foo.__module__ = bar.__module__ = '__main__'



class SerialisationTest(TestCase):
    """We can serialise a function as source."""

    def setUp(self):
        (self.source, self.name, self.imports,

# Path: chopsticks/serialise_main.py
# def serialise_func(f, seen=()):
#     """Serialise a function defined in __main__ to be called remotely."""
#     source = inspect.getsource(f)
# 
#     # We compile the source we get rather than inspecting the code object of
#     # f - this captures named loaded in executing the default argument
#     # expressions
#     code = compile(source, '<main>', 'exec')
#     names = trace_globals(code)
# 
#     imported_names = {}
#     variables = {}
# 
#     fglobals = f.func_globals if PY2 else f.__globals__
#     for name in names:
#         try:
#             v = fglobals[name]
#         except KeyError:
#             # Perhaps not need, or perhaps the function will crash
#             continue
#         if isinstance(v, types.ModuleType):
#             imported_names[name] = v.__name__
#         elif isinstance(v, types.FunctionType) and v.__module__ == '__main__':
#             if v in seen:
#                 continue
#             else:
#                 subdeps = serialise_func(v, seen=seen + (f, v,))
#                 vsource, _, _, vnames, vvars = subdeps
#                 source += '\n\n' + vsource
#                 imported_names.update(vnames)
#                 variables.update(vvars)
#         else:
#             variables[name] = v
# 
#     # We can't tell what submodules are used within f, but we can
#     # calculate a list of submodules that have been imported, so let's send
#     # that list.
#     imports = set(imported_names.values())
#     prefixes = tuple(mod + '.' for mod in imported_names.values())
#     imports.update(mod for mod in sys.modules if mod.startswith(prefixes))
# 
#     return source, f.__name__, imports, imported_names, variables
# 
# def deserialise_func(source, f_name, imports, imported_names, variables):
#     """Deserialise a function serialised with serialise_func."""
#     ns = chopmain.__dict__
#     sys.modules['__chopmain__'] = chopmain
# 
#     for mod in imports:
#         __import__(mod)
#     ns.update(variables)
#     for name, modname in imported_names.items():
#         ns[name] = sys.modules[modname]
#     code = compile(source, '<__chopmain__>', 'exec')
#     exec_(code, ns, ns)
#     return ns[f_name]


# Path: tests/test_serisalisation.py
import sys
import xml.etree
from unittest import TestCase
from chopsticks.serialise_main import serialise_func, deserialise_func

CONST = 5

# This constant should not be serialised, because it is unused
UNUSED_CONST = 7


# Create an alias for the xml module
xml_processing = xml


def bar():
    xml_processing  # Just reference this import to ensure it is serialised
    return list(range(CONST))


def foo():
    """Test function to be serialised, calls bar()."""
    return bar()[-2:]


# Trigger the serialisation behaviour
foo.__module__ = bar.__module__ = '__main__'



class SerialisationTest(TestCase):
    """We can serialise a function as source."""

    def setUp(self):
        (self.source, self.name, self.imports,

         self.imported_names, self.variables) = serialise_func(foo)