Postpone a functions execution until after some time has elapsed.

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
"""Various utility functions used by this plugin"""

from threading import Timer

import datetime  # only needed for example usage...
from datetime import timedelta  # only needed for example usage...
import time  # only needed for example usage...


def debounce(wait):
    """Postpone a functions execution until after some time has elapsed

    :type wait: int
    :param wait: The amount of Seconds to wait before the next call can execute.
    """

    def decorator(fun):
        def debounced(*args, **kwargs):
            def call_it():
                fun(*args, **kwargs)

            try:
                debounced.t.cancel()
            except AttributeError:
                pass

            debounced.t = Timer(wait, call_it)
            debounced.t.start()

        return debounced

    return decorator


#
# Example Usage
#

class Person:
    TITLES = ('Dr', 'Mr', 'Mrs', 'Ms')

    def __init__(self, name, surname, birthdate):
        self.name = name
        self.surname = surname
        self.birthdate = birthdate

        self.age = None
        self.age_last_calculated = None

    def fullname(self):
        return '{0} {1}'.format(self.name, self.surname)

    @debounce(1)
    def calculate_age(self):
        today = datetime.date.today()
        age = today.year - self.birthdate.year
        self.age = age
        self.age_last_calculated = today


#
# Only the last printed output (1.9) should have values other than 'None'

my_age_as_days = 365 * 35  # approximate
my_age = datetime.datetime.now() - timedelta(days=my_age_as_days)
person = Person('Jon', 'LaBelle', my_age)

# call the debounced function (should output 'None'):
person.calculate_age()
print('calculated_age: {0}'.format(person.age))  # >> None
print('age_last_calculated: {0}'.format(person.age_last_calculated))  # >> None

# sleep a bit... then call the debounced function again (should output 'None'):
time.sleep(0.1)
person.calculate_age()
print('calculated_age: {0}'.format(person.age))  # >> None
print('age_last_calculated: {0}'.format(person.age_last_calculated))  # >> None

# sleep a bit more... then call the debounced function again (should output 'None'):
time.sleep(0.4)
person.calculate_age()
print('calculated_age: {0}'.format(person.age))  # >> None
print('age_last_calculated: {0}'.format(person.age_last_calculated))  # >> None

# sleep until the wait time thresh-hold has been satisified (1-second)...
# then call the debounced function again (should output values for Age and Last Calculated):
time.sleep(1.9)
person.calculate_age()
print('calculated_age: {0}'.format(person.age))  # >> 23
print('age_last_calculated: {0}'.format(person.age_last_calculated))  # 2018-05-23