# AUTOGENERATED FROM: babilim/core/annotations.ipynb

# Cell: 0
"""doc
# babilim.core.annotations

> A collection of helpful annotations.

This code is under the MIT License and requires the abc package.
"""

# Cell: 1
# MIT License
#
# Copyright (c) 2019 Michael Fuerst
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

from abc import ABC, abstractmethod


# Cell: 2
class _ClassDecorator(ABC):
    def __get__(self, obj, objtype):
        """
        A class decorator is a base class that is used for all annotations that should be usable with python classes.
        Regular annotations will not work with classes.

        This is a helper class that can be used when writing annotations.
        """
        import functools
        return functools.partial(self.__call__, obj)

    @abstractmethod
    def __call__(self, *args, **kwargs):
        pass


# Cell: 3
class RunOnlyOnce(_ClassDecorator):
    def __init__(self, f):
        """
        A decorator that ensures a function in an object gets only called exactly once.

        :param f: The function that should be wrapped.
        """
        self.f = f
        self.called = {}

    def __call__(self, *args, **kwargs):
        if args[0] not in self.called:
            self.called[args[0]] = True
            self.f(*args, **kwargs)


# Cell: 4
"""doc
The run only once annotation is fundamental for the build function pattern, whereas it allows to write a function which is only called once, no matter how often it gets called. This behaviour is very usefull for creating variables on the GPU only once in the build and not on every run of the neural network.

> Important: This is for use with the build function in a module. Ensuring it only gets called once and does not eat memory on the gpu.

Using this in an example function which prints the parameter only yields on printout, even though the function gets called multiple times.
"""

# Cell: 5
"""doc
# Jupyter Notebook Helpers
"""

# Cell: 6
def extend_class(clazz, function_name):
    """
    Extend a class by the function decorated with this decorator.
    
    :param clazz: The class that should be decorated.
    :param function_name: The name that the function in the class should have. (Can be different than unbound name of the class.)
    """
    def _wrapper(func):
        setattr(clazz, function_name, func)
        return func
    return _wrapper


# Cell: 7
"""doc
This annotation can be used for developing code in a jupyter notebook. It allows you to define a class in separate cells, like the following example. This gives you the exploratory capabilities of jupyter while developing a class that can be later exported (using nbdev) and used in production.
"""