# Path: microcosm_pubsub/chain/context_decorators.py
# def get_from_context(context, func, assigned=DEFAULT_ASSIGNED):
#     """
#     Decorate a function - pass to the function the relevant arguments
#     from a context (dictionary) - based on the function arg names
# 
#     """
#     positional_args = get_positional_args(func)
# 
#     @wraps(func, assigned=assigned + WRAPPER_ASSIGNMENTS)
#     def decorate(*args, **kwargs):
#         try:
#             context_kwargs = {
#                 arg_name: (context[arg_name] if default is Signature.empty else context.get(arg_name, default))
#                 for arg_name, default in positional_args[len(args):]
#                 if arg_name not in kwargs
#             }
#         except KeyError as error:
#             raise ContextKeyNotFound(error, func)
# 
#         return func(*args, **kwargs, **context_kwargs)
#     return decorate
# 
# def save_to_context(context, func, assigned=DEFAULT_ASSIGNED):
#     """
#     Decorate a function - save to a context (dictionary) the function return value
#     if the function is marked by @extracts decorator
# 
#     """
#     extracts = getattr(func, EXTRACTS, None)
#     if not extracts:
#         return func
#     extracts_one_value = len(extracts) == 1
# 
#     @wraps(func, assigned=assigned + WRAPPER_ASSIGNMENTS)
#     def decorate(*args, **kwargs):
#         value = func(*args, **kwargs)
#         if extracts_one_value:
#             value = [value]
#         for index, name in enumerate(extracts):
#             context[name] = value[index]
#         return value
#     return decorate
# 
# def save_to_context_by_func_name(context, func, assigned=DEFAULT_ASSIGNED):
#     """
#     Decorate a function - save to a context (dictionary) the function return value
#     if the function is not signed by EXTRACTS and it's name starts with "extract_"
# 
#     """
#     if (
#         hasattr(func, EXTRACTS) or
#         not hasattr(func, "__name__") or
#         not func.__name__.startswith(EXTRACT_PREFIX)
#     ):
#         return func
#     name = func.__name__[len(EXTRACT_PREFIX):]
# 
#     @wraps(func, assigned=assigned + WRAPPER_ASSIGNMENTS)
#     def decorate(*args, **kwargs):
#         value = func(*args, **kwargs)
#         context[name] = value
#         return value
#     return decorate
# 
# def temporarily_replace_context_keys(context, func, assigned=DEFAULT_ASSIGNED):
#     """
#     Decorate a function - temporarily updates the context keys while running the function
#     Updates the context if the function is marked by @binds decorator.
# 
#     """
#     binds = getattr(func, BINDS, None)
#     if not binds:
#         return func
# 
#     @wraps(func, assigned=assigned + WRAPPER_ASSIGNMENTS)
#     def decorate(*args, **kwargs):
#         for old_key, new_key in binds.items():
#             if old_key not in context:
#                 raise KeyError(f"Variable '{old_key}'' not set")
#             if new_key in context:
#                 raise ValueError(f"Variable '{new_key}'' already set")
#         try:
#             for old_key, new_key in binds.items():
#                 context[new_key] = context.pop(old_key)
#             return func(*args, **kwargs)
#         finally:
#             for old_key, new_key in binds.items():
#                 context[old_key] = context.pop(new_key)
#     return decorate
# 
# Path: microcosm_pubsub/chain/decorators.py
# def binds(**binds):
#     """
#     On resolving this function with temporarily_replace_context_keys decorator -
#     temporarily updates the context keys.
# 
#     Note: The decorator just marks the function.
#     :param **binds: (old_context_key, new_context_key) - context keys to rename
# 
#     """
#     def decorate(func):
#         function = to_function(func)
#         setattr(function, BINDS, binds)
#         return function
#     return decorate
# 
# def extracts(*extract):
#     """
#     On resolving this function with get_from_context decorator -
#     updates the context based on the function return value.
# 
#     Note: The decorator just marks the function and don't change the return value.
#     :param *args: key/s of the return value
# 
#     """
#     def decorate(func):
#         function = to_function(func)
#         setattr(function, EXTRACTS, extract)
#         return function
#     return decorate
# 
# Path: microcosm_pubsub/chain/exceptions.py
# class ContextKeyNotFound(Exception):
#     def __init__(self, key_error: KeyError, func):
#         context_key, *_ = key_error.args
#         super().__init__(f"Failed to find context key `{context_key}` during evaluation of `{func}`")


from hamcrest import (
    assert_that,
    calling,
    equal_to,
    is_,
    raises,
)
from microcosm_pubsub.chain.context_decorators import (
    get_from_context,
    save_to_context,
    save_to_context_by_func_name,
    temporarily_replace_context_keys,
)
from microcosm_pubsub.chain.decorators import binds, extracts
from microcosm_pubsub.chain.exceptions import ContextKeyNotFound

    def test_get_from_context_function_and_methods(self):
        context = dict(arg=123)

        def function(arg):
            return arg

        class example:
            class_number = 200

            def __init__(self):
                self.instance_number = 100

            def method(self, arg):
                return self.instance_number + arg

            @staticmethod
            def static_method(arg):
                return arg

            @classmethod
            def class_method(cls, arg):
                return cls.class_number + arg

        wrapped_function = get_from_context(context, function)
        assert_that(wrapped_function(), is_(123))

        wrapped_method = get_from_context(context, example().method)
        assert_that(wrapped_method(), is_(223))


# Path: microcosm_pubsub/chain/context_decorators.py
# def get_from_context(context, func, assigned=DEFAULT_ASSIGNED):
#     """
#     Decorate a function - pass to the function the relevant arguments
#     from a context (dictionary) - based on the function arg names
# 
#     """
#     positional_args = get_positional_args(func)
# 
#     @wraps(func, assigned=assigned + WRAPPER_ASSIGNMENTS)
#     def decorate(*args, **kwargs):
#         try:
#             context_kwargs = {
#                 arg_name: (context[arg_name] if default is Signature.empty else context.get(arg_name, default))
#                 for arg_name, default in positional_args[len(args):]
#                 if arg_name not in kwargs
#             }
#         except KeyError as error:
#             raise ContextKeyNotFound(error, func)
# 
#         return func(*args, **kwargs, **context_kwargs)
#     return decorate
# 
# def save_to_context(context, func, assigned=DEFAULT_ASSIGNED):
#     """
#     Decorate a function - save to a context (dictionary) the function return value
#     if the function is marked by @extracts decorator
# 
#     """
#     extracts = getattr(func, EXTRACTS, None)
#     if not extracts:
#         return func
#     extracts_one_value = len(extracts) == 1
# 
#     @wraps(func, assigned=assigned + WRAPPER_ASSIGNMENTS)
#     def decorate(*args, **kwargs):
#         value = func(*args, **kwargs)
#         if extracts_one_value:
#             value = [value]
#         for index, name in enumerate(extracts):
#             context[name] = value[index]
#         return value
#     return decorate
# 
# def save_to_context_by_func_name(context, func, assigned=DEFAULT_ASSIGNED):
#     """
#     Decorate a function - save to a context (dictionary) the function return value
#     if the function is not signed by EXTRACTS and it's name starts with "extract_"
# 
#     """
#     if (
#         hasattr(func, EXTRACTS) or
#         not hasattr(func, "__name__") or
#         not func.__name__.startswith(EXTRACT_PREFIX)
#     ):
#         return func
#     name = func.__name__[len(EXTRACT_PREFIX):]
# 
#     @wraps(func, assigned=assigned + WRAPPER_ASSIGNMENTS)
#     def decorate(*args, **kwargs):
#         value = func(*args, **kwargs)
#         context[name] = value
#         return value
#     return decorate
# 
# def temporarily_replace_context_keys(context, func, assigned=DEFAULT_ASSIGNED):
#     """
#     Decorate a function - temporarily updates the context keys while running the function
#     Updates the context if the function is marked by @binds decorator.
# 
#     """
#     binds = getattr(func, BINDS, None)
#     if not binds:
#         return func
# 
#     @wraps(func, assigned=assigned + WRAPPER_ASSIGNMENTS)
#     def decorate(*args, **kwargs):
#         for old_key, new_key in binds.items():
#             if old_key not in context:
#                 raise KeyError(f"Variable '{old_key}'' not set")
#             if new_key in context:
#                 raise ValueError(f"Variable '{new_key}'' already set")
#         try:
#             for old_key, new_key in binds.items():
#                 context[new_key] = context.pop(old_key)
#             return func(*args, **kwargs)
#         finally:
#             for old_key, new_key in binds.items():
#                 context[old_key] = context.pop(new_key)
#     return decorate
# 
# Path: microcosm_pubsub/chain/decorators.py
# def binds(**binds):
#     """
#     On resolving this function with temporarily_replace_context_keys decorator -
#     temporarily updates the context keys.
# 
#     Note: The decorator just marks the function.
#     :param **binds: (old_context_key, new_context_key) - context keys to rename
# 
#     """
#     def decorate(func):
#         function = to_function(func)
#         setattr(function, BINDS, binds)
#         return function
#     return decorate
# 
# def extracts(*extract):
#     """
#     On resolving this function with get_from_context decorator -
#     updates the context based on the function return value.
# 
#     Note: The decorator just marks the function and don't change the return value.
#     :param *args: key/s of the return value
# 
#     """
#     def decorate(func):
#         function = to_function(func)
#         setattr(function, EXTRACTS, extract)
#         return function
#     return decorate
# 
# Path: microcosm_pubsub/chain/exceptions.py
# class ContextKeyNotFound(Exception):
#     def __init__(self, key_error: KeyError, func):
#         context_key, *_ = key_error.args
#         super().__init__(f"Failed to find context key `{context_key}` during evaluation of `{func}`")


# Path: microcosm_pubsub/tests/chain/test_decorators.py
from hamcrest import (
    assert_that,
    calling,
    equal_to,
    is_,
    raises,
)
from microcosm_pubsub.chain.context_decorators import (
    get_from_context,
    save_to_context,
    save_to_context_by_func_name,
    temporarily_replace_context_keys,
)
from microcosm_pubsub.chain.decorators import binds, extracts
from microcosm_pubsub.chain.exceptions import ContextKeyNotFound


    def test_get_from_context_function_and_methods(self):
        context = dict(arg=123)

        def function(arg):
            return arg

        class example:
            class_number = 200

            def __init__(self):
                self.instance_number = 100

            def method(self, arg):
                return self.instance_number + arg

            @staticmethod
            def static_method(arg):
                return arg

            @classmethod
            def class_method(cls, arg):
                return cls.class_number + arg

        wrapped_function = get_from_context(context, function)
        assert_that(wrapped_function(), is_(123))

        wrapped_method = get_from_context(context, example().method)
        assert_that(wrapped_method(), is_(223))


        wrapped_static_method = get_from_context(context, example().static_method)