text: from __future__ import absolute_import, print_function
  
  __all__ = ('Attribute', 'Event', 'Map')
  
  import six
  
  from collections import Mapping
  from django.utils import timezone
  
  
  class Attribute(object):
      def __init__(self, name, type=six.text_type, required=True):
          self.name = name
          self.type = type
          self.required = required
  
      def extract(self, value):
          if value is None:
              return value
          return self.type(value)
  
  
  class Map(Attribute):
      def __init__(self, name, attributes=None, required=True):
          self.name = name
          self.required = required
          self.attributes = attributes
  
      def extract(self, value):
          """
          If passed a non dictionary we assume we can pull attributes from it.
  
          This will hard error in some situations if you're passing a bad type
          (like an int).
          """
          if value is None:
              return value
  
          if not isinstance(value, Mapping):
              new_value = {}
              for attr in self.attributes:
                  new_value[attr.name] = attr.extract(
                      getattr(value, attr.name, None)
                  )
              items = new_value
          else:
              # ensure we dont mutate the original
              # we dont need to deepcopy as if it recurses into another Map it
              # will once again copy itself
              items = value.copy()
  
          data = {}
          for attr in self.attributes:
              nv = items.pop(attr.name, None)
              if attr.required and nv is None:
                  raise ValueError(u'{} is required (cannot be None)'.format(
                      attr.name,
                  ))
  
              data[attr.name] = attr.extract(nv)
  
          if items:
              raise ValueError(u'Unknown attributes: {}'.format(
                  ', '.join(map(six.text_type, six.iterkeys(items))),
              ))
  
          return data
  
  
  class Event(object):
      __slots__ = ['attributes', 'data', 'datetime', 'type']
  
      type = None
  
      attributes = ()
  
      def __init__(self, type=None, datetime=None, **items):
          self.datetime = datetime or timezone.now()
          if type is not None:
              self.type = type
  
          if self.type is None:
              raise ValueError('Event is missing type')
  
          data = {}
          for attr in self.attributes:
              nv = items.pop(attr.name, None)
              if attr.required and nv is None:
                  raise ValueError(u'{} is required (cannot be None)'.format(
                      attr.name,
                  ))
              data[attr.name] = attr.extract(nv)
  
          if items:
              raise ValueError(u'Unknown attributes: {}'.format(
                  ', '.join(six.iterkeys(items)),
              ))
  
          self.data = data
  
      def serialize(self):
          return dict({
              'timestamp': int(self.datetime.isoformat('%s')),
              'type': self.type,
          }, **self.data)
  
      @classmethod
      def from_instance(cls, instance, **kwargs):
          values = {}
          for attr in cls.attributes:
              values[attr.name] = (
                  kwargs.get(attr.name) or
                  getattr(instance, attr.name, None)
              )
          return cls(**values)
  