# Path: narrative/models.py
# class Datum(models.Model):
#     # When was the datum created
#     timestamp = models.DateTimeField(auto_now_add=True, db_index=True)
#     expiration_time = models.DateTimeField(null=True, blank=True, default=None)
#     # Origin of this datum; ie, what piece of software created it
#     origin = models.CharField(max_length=64)
#     datum_name = models.CharField(max_length=64)
# 
#     # Additional information about the datum; this is very datum specific
#     # This approach, storing json in a textfield and having an
#     # explicit accessor and mutator, is very ugly.  Really this is because
#     # we are storing non-structured/sparse data in a relational format.
#     datum_note_json = models.TextField(null=True, blank=True, default=None)
# 
#     # An ID to tie particular datums together
#     thread_id = models.CharField(max_length=36, null=True, blank=True)
# 
#     log_level = models.IntegerField(choices=DatumLogLevel.types, default=DatumLogLevel.INFO)
# 
#     objects = DatumManager()
# 
#     def __init__(self, *args, **kwargs):
#         if 'ttl' in kwargs:
#             kwargs['expiration_time'] = self.get_utc_now() + kwargs.pop('ttl')
# 
#         super(Datum, self).__init__(*args, **kwargs)
# 
#     def __str__(self):
#         note_snippet = self.datum_note_json[:50] if self.datum_note_json else ''
#         return 'origin:{0} datum_name:{1} note:{2}'.format(
#             self.origin, self.datum_name, note_snippet
#         )
# 
#     def get_note(self):
#         if self.datum_note_json:
#             return json.loads(self.datum_note_json)
#         else:
#             return []
# 
#     def set_note(self, note):
#         self.datum_note_json = json.dumps(note)
# 
#     def get_utc_now(self):
#         return utc_tz.localize(datetime.datetime.utcnow())
# 
#     @property
#     def timestamp_with_tz(self):
#         return utc_tz.localize(self.timestamp)
# 
#     def save(self, *args, **kwargs):
#         if None == self.thread_id:
#             self.thread_id = str(uuid.uuid4())
# 
#         super(Datum, self).save(*args, **kwargs)
# 
# class DatumLogLevel(StatusType):
#     TRACE = 0
#     DEBUG = 1
#     INFO = 2
#     WARN = 3
#     ERROR = 4
# 
#     types = (
#         (TRACE, 'Trace'),
#         (ERROR, 'Error'),
#         (WARN, 'Warn'),
#         (INFO, 'Info'),
#         (DEBUG, 'Debug'),
#     )
# 
#     @classmethod
#     def status_by_name(cls, name):
#         status = find_tuple(cls.types, name.title(), 1)
#         return status[0] if status is not None else NarrativeConfig.objects.get_minimum_log_level()
# 
# def log_datum(*args, **kwargs):
#     """
#     Handle logging a datum.  It is better to use this method than to manually create datums,
#     because it provides a central place for controlling which datums should or should not be
#     created based the logging level set in the NarrativeConfig.
#     """
#     minimum_datum_log_level = NarrativeConfig.objects.filter()[0].minimum_datum_log_level
# 
#     note = None
#     datum = None
# 
#     if kwargs['log_level'] >= minimum_datum_log_level:
#         note = kwargs.pop('note') if 'note' in kwargs else None
# 
#         datum = Datum(*args, **kwargs)
# 
#         if note:
#             datum.set_note(note)
# 
#         datum.save()
# 
#     return datum


import datetime
from django.conf import settings
from narrative.models import Datum, DatumLogLevel, log_datum

class UptimeEventTypes:
    UP = 0
    DOWN = 1


heartbeat_details = {
    'origin': 'external_heartbeat',
    'datum_name': 'beat',
    'ttl': getattr(settings, 'NARRATIVE_HEARBEAT_TTL', None),
}


def create_heartbeat(get_utc_now=datetime.datetime.utcnow):
    """
    Create a datum recording the heartbeat, if sufficient time
    (as specified by NARRATIVE_HEARTBEAT_MIN_INTERVAL) has passed
    since the last heartbeat was created.
    """
    start_of_non_creation_window = get_utc_now() - settings.NARRATIVE_HEARTBEAT_MIN_INTERVAL

    if not Datum.objects.filter(
            origin=heartbeat_details['origin'],
            datum_name=heartbeat_details['datum_name'],
            timestamp__gte=start_of_non_creation_window).exists():
        # No recent heartbeat, so create a new one
        log_datum(
            origin=heartbeat_details['origin'],
            datum_name=heartbeat_details['datum_name'],
            ttl=heartbeat_details['ttl'],

# Path: narrative/models.py
# class Datum(models.Model):
#     # When was the datum created
#     timestamp = models.DateTimeField(auto_now_add=True, db_index=True)
#     expiration_time = models.DateTimeField(null=True, blank=True, default=None)
#     # Origin of this datum; ie, what piece of software created it
#     origin = models.CharField(max_length=64)
#     datum_name = models.CharField(max_length=64)
# 
#     # Additional information about the datum; this is very datum specific
#     # This approach, storing json in a textfield and having an
#     # explicit accessor and mutator, is very ugly.  Really this is because
#     # we are storing non-structured/sparse data in a relational format.
#     datum_note_json = models.TextField(null=True, blank=True, default=None)
# 
#     # An ID to tie particular datums together
#     thread_id = models.CharField(max_length=36, null=True, blank=True)
# 
#     log_level = models.IntegerField(choices=DatumLogLevel.types, default=DatumLogLevel.INFO)
# 
#     objects = DatumManager()
# 
#     def __init__(self, *args, **kwargs):
#         if 'ttl' in kwargs:
#             kwargs['expiration_time'] = self.get_utc_now() + kwargs.pop('ttl')
# 
#         super(Datum, self).__init__(*args, **kwargs)
# 
#     def __str__(self):
#         note_snippet = self.datum_note_json[:50] if self.datum_note_json else ''
#         return 'origin:{0} datum_name:{1} note:{2}'.format(
#             self.origin, self.datum_name, note_snippet
#         )
# 
#     def get_note(self):
#         if self.datum_note_json:
#             return json.loads(self.datum_note_json)
#         else:
#             return []
# 
#     def set_note(self, note):
#         self.datum_note_json = json.dumps(note)
# 
#     def get_utc_now(self):
#         return utc_tz.localize(datetime.datetime.utcnow())
# 
#     @property
#     def timestamp_with_tz(self):
#         return utc_tz.localize(self.timestamp)
# 
#     def save(self, *args, **kwargs):
#         if None == self.thread_id:
#             self.thread_id = str(uuid.uuid4())
# 
#         super(Datum, self).save(*args, **kwargs)
# 
# class DatumLogLevel(StatusType):
#     TRACE = 0
#     DEBUG = 1
#     INFO = 2
#     WARN = 3
#     ERROR = 4
# 
#     types = (
#         (TRACE, 'Trace'),
#         (ERROR, 'Error'),
#         (WARN, 'Warn'),
#         (INFO, 'Info'),
#         (DEBUG, 'Debug'),
#     )
# 
#     @classmethod
#     def status_by_name(cls, name):
#         status = find_tuple(cls.types, name.title(), 1)
#         return status[0] if status is not None else NarrativeConfig.objects.get_minimum_log_level()
# 
# def log_datum(*args, **kwargs):
#     """
#     Handle logging a datum.  It is better to use this method than to manually create datums,
#     because it provides a central place for controlling which datums should or should not be
#     created based the logging level set in the NarrativeConfig.
#     """
#     minimum_datum_log_level = NarrativeConfig.objects.filter()[0].minimum_datum_log_level
# 
#     note = None
#     datum = None
# 
#     if kwargs['log_level'] >= minimum_datum_log_level:
#         note = kwargs.pop('note') if 'note' in kwargs else None
# 
#         datum = Datum(*args, **kwargs)
# 
#         if note:
#             datum.set_note(note)
# 
#         datum.save()
# 
#     return datum


# Path: narrative/batteries/uptime.py
import datetime
from django.conf import settings
from narrative.models import Datum, DatumLogLevel, log_datum


class UptimeEventTypes:
    UP = 0
    DOWN = 1


heartbeat_details = {
    'origin': 'external_heartbeat',
    'datum_name': 'beat',
    'ttl': getattr(settings, 'NARRATIVE_HEARBEAT_TTL', None),
}


def create_heartbeat(get_utc_now=datetime.datetime.utcnow):
    """
    Create a datum recording the heartbeat, if sufficient time
    (as specified by NARRATIVE_HEARTBEAT_MIN_INTERVAL) has passed
    since the last heartbeat was created.
    """
    start_of_non_creation_window = get_utc_now() - settings.NARRATIVE_HEARTBEAT_MIN_INTERVAL

    if not Datum.objects.filter(
            origin=heartbeat_details['origin'],
            datum_name=heartbeat_details['datum_name'],
            timestamp__gte=start_of_non_creation_window).exists():
        # No recent heartbeat, so create a new one
        log_datum(
            origin=heartbeat_details['origin'],
            datum_name=heartbeat_details['datum_name'],
            ttl=heartbeat_details['ttl'],

            log_level=DatumLogLevel.INFO)