<commit_before>from werkzeug import generate_password_hash, check_password_hash
import totalimpact.dao as dao
from totalimpact.providers.provider import ProviderFactory
import time, uuid, json, hashlib, inspect, re, copy

import threading
from pprint import pprint

# Master lock to ensure that only a single thread can write
# to the DB at one time to avoid document conflicts
db_write_lock = threading.Lock()

def todict(obj, classkey=None):
    """ Convert an object to a diff representation
        Recipe from http://stackoverflow.com/questions/1036409/recursively-convert-python-object-graph-to-dictionary
    """
    if isinstance(obj, dict):
        for k in obj.keys():
            obj[k] = todict(obj[k], classkey)
        return obj
    elif hasattr(obj, "__iter__"):
        return [todict(v, classkey) for v in obj]
    elif hasattr(obj, "__dict__"):
        data = dict([(key, todict(value, classkey))
            for key, value in obj.__dict__.iteritems()
            if not callable(value) and not key.startswith('_')])
        if classkey is not None and hasattr(obj, "__class__"):
            data[classkey] = obj.__class__.__name__
        return data
    else:
        return obj

class Saveable(object):

    def __init__(self, dao, id=None):
        self.dao = dao

        if id is None:
            self.id = uuid.uuid4().hex
        else:
            self.id = id

    def as_dict(self, obj=None, classkey=None):
        ''' Recursively convert this object's members into a dictionary structure for
            serialisation to the database.
        '''
        dict_repr = todict(self)
        # We don't want to store the dao object
        del dict_repr["dao"]
        return dict_repr

    def _update_dict(self, input, my_dict=None):
        '''Use another dict to recursively add list or dict items not in this.

        This object's value wins all conflicts, but new values in lists and
        dictionaries are added. Used to update from the db before saving.
        I think this might should go in the dao...not sure.

        returns - dict
        '''
        if my_dict is None:
            my_dict = self.as_dict()

        if input is None:
            return my_dict

        for k, v in input.iteritems():
            try:
                #get the dict here...whenever

                new_my_dict = my_dict.setdefault(k, {})
                self._update_dict(v, new_my_dict)

            except AttributeError:
                if not my_dict[k]:
                    my_dict[k] = v

        return my_dict

    def save(self):
        """ Save the object to the database, handling merging of data
            should the object have already been updated elsewhere """
        # Blocking call to acquire a lock for this section
        db_write_lock.acquire(True)
        new_dict = self.dao.get(self.id)
        dict_to_save = self._update_dict(new_dict)
        res = self.dao.save(dict_to_save)
        db_write_lock.release()
        return res

    def delete(self):
        self.dao.delete(self.id)
        return True

class ItemFactory():
    #TODO this should subclass a SaveableFactory

    @classmethod
    def get(cls, dao, id, metric_names):
        now = time.time()
        item_doc = dao.get(id)
        item = Item(dao, id=id)

        if item_doc is None:
            raise LookupError

        item.last_requested = now

        # first, just copy everything from the item_doc the DB gave us
        for k in item_doc:
            if k not in ["_id", "_rev"]:
                setattr(item, k, item_doc[k])

        # the aliases property needs to be an Aliases obj, not a dict.
        item.aliases = Aliases(seed=item_doc['aliases'])

        # make the Metric objects. We have to make keys for each metric in the config
        # so that Providers will know which metrics to update later on.
        # Then we fill these Metric objects's dictionaries with the metricSnaps
        # from the db.

        # first combine all the keys we have. Don't want to overwrite anything.
        try:
            metric_names = set(metric_names + item_doc["metrics"].keys() )
        except KeyError:
            pass

        item.metrics = {}
        for name in metric_names:
            try:
                item.metrics[name] = item_doc["metrics"][name]
            except KeyError: #this metric ain't in the item_doc from the db
                item.metrics[name] = {'values': {} }
        return item

    @classmethod
    def make(cls, dao, metric_names):
        now = time.time()
        item = Item(dao=dao)

        # make all the top-level stuff
        item.aliases = Aliases()
        item.metrics = {}
        item.biblio = {}
        item.last_modified = now
        item.created = now

        # make the metrics objects. We have to make all the ones in the config
        # so that Providers will know which ones to update later on.
        for name in metric_names:
            item.metrics[name] = {'values': {} }

        return item

class Item(Saveable):
    """{
        "id": "<uuid4>",
        "aliases": "<Aliases object>",
        "metrics": {
            "plos:html_views":{
                "ignore":False,
                "static_meta": "<static_meta dict from provider>",
                "values":
                    12: "1234556789.2",
                    13: "1234567999.9"
            },
            "plos:pdf_views: {...},
            "dryad:total_downloads: {...},
            ...
        },
        "biblio": "<Biblio object>",
        "created": 23112412414.234,
        "last_modified": 12414214.234,
        "last_requested": 124141245.234
    }
    """
    pass

class Error(Saveable):
    """{
        "error_type": "http_timeout",
        "message": "Error opening file",
        "provider": "github_provider",
        "id": "uuid4-goes-here",
        "stack_trace": "Python Stacktrace"
    }"""
    pass

class CollectionFactory():

    @staticmethod
    def make(dao, id=None, collection_dict=None):

        if id is not None and collection_dict is not None:
            raise TypeError("you can load from the db or from a dict, but not both")

        now = time.time()
        collection = Collection(dao=dao)

        if id is None and collection_dict is None:
            collection.id = uuid.uuid4().hex
            collection.created = now
            collection.last_modified = now
        else: # load an extant item
            if collection_dict is None:
                collection_dict = dao.get(id)

            if collection_dict is None:
                raise LookupError

            for k in collection_dict:
                setattr(collection, k, collection_dict[k])

        return collection

class Collection(Saveable):
    """
    {
        "id": "uuid-goes-here",
        "collection_name": "My Collection",
        "owner": "abcdef",
        "created": 1328569452.406,
        "last_modified": 1328569492.406,
        "item_tiids": ["abcd3", "abcd4"]
    }
    """

    def item_ids(self):
        if not hasattr(self, "item_tiids"):
            self.item_tiids = []
        return self.item_tiids

    def add_item(self, new_item_id):
        if not hasattr(self, "item_tiids"):
            self.item_tiids = []
        if new_item_id not in self.item_tiids:
            self.item_tiids.append(new_item_id)

    def add_items(self, new_item_ids):
        if not hasattr(self, "item_tiids"):
            self.item_tiids = []
        for item_id in new_item_ids:
            self.add_item(item_id)

    def remove_item(self, item_id):
        if not hasattr(self, "item_tiids"):
            self.item_tiids = []
        if item_id in self.item_tiids:
            self.item_tiids.remove(item_id)

class Biblio(object):
    """
    {
        "title": "An extension of de Finetti's theorem",
        "journal": "Advances in Applied Probability",
        "author": [
            "Pitman, J"
        ],
        "collection": "pitnoid",
        "volume": "10",
        "id": "p78",
        "year": "1978",
        "pages": "268 to 270"
    }
    """
    #TODO remove the "data" property, bringing this into parallel with teh
    # other stuff in this module.

    def __init__(self, seed=None):
        self.data = seed if seed is not None else ""

    def __str__(self):
        return str(self.data)

    def __repr__(self):
        return str(self.data)

    def as_dict(self):
        # renamed for consistancy with Items(); TODO cleanup old one
        return self.data

class Aliases(object):
    """
    {
        "title":["Why Most Published Research Findings Are False"],
        "url":["http:\/\/www.plosmedicine.org\/article\/info:doi\/10.1371\/journal.pmed.0020124"],
        "doi": ["10.1371\/journal.pmed.0020124"],
        "created": 12387239847.234,
        "last_modified": 1328569492.406
        ...
    }

    note we're not keeping the TIID in here any more. it needs to be on the the
    item, since that's what it describes. having it in two places == bad.
    """

    not_aliases = ["created", "last_modified"]

    def __init__(self, seed=None):
        self.created = time.time() # will get overwritten if need be
        try:
            for k in seed:
                setattr(self, k, seed[k])
        except TypeError:
            pass

    def add_alias(self, namespace, id):
        try:
            attr = getattr(self, namespace)
            attr.append(id)
        except AttributeError:
            setattr(self, namespace, [id])
        self.last_modified = time.time()

    #FIXME: this should take namespace and id, not a list of them
    def add_unique(self, alias_list):
        for ns, id in alias_list:
            try:
                if id not in getattr(self, ns):
                    self.add_alias(ns, id)
            except:
                    self.add_alias(ns, id)
        self.last_modified = time.time()

    def get_aliases_list(self, namespace_list=None):
        '''
        gets list of this object's aliases in each given namespace

        returns a list of (namespace, id) tuples
        '''
        # if this is a get on everything, just summon up the items
        if namespace_list is None:
            return [x for x in self.__dict__ if x not in self.not_aliases]

        # if the caller doesn't pass us a list, but just a single value, wrap it
        # up for them
        if not hasattr(namespace_list, "append"):
            namespace_list = [namespace_list]

        # otherwise, get for the specific namespaces
        ret = []
        for namespace in namespace_list:
            try:
                ids = getattr(self, namespace)

                # crazy hack TODO fix lists/strings flying about
                if not hasattr(ids, "append"):
                    ids = [ids]
                ret += [(namespace, id) for id in ids]
            except AttributeError:
                # this alias doesn't have that namespace...no worries, move on.
                pass

        return ret

    # FIXME I don't think we need this any more?
    def as_dict(self):
        return self.__dict__

<commit_msg>Change catch-all exception to using a default value for getattr (safer)<commit_after>from werkzeug import generate_password_hash, check_password_hash
import totalimpact.dao as dao
from totalimpact.providers.provider import ProviderFactory
import time, uuid, json, hashlib, inspect, re, copy

import threading
from pprint import pprint

# Master lock to ensure that only a single thread can write
# to the DB at one time to avoid document conflicts
db_write_lock = threading.Lock()

def todict(obj, classkey=None):
    """ Convert an object to a diff representation
        Recipe from http://stackoverflow.com/questions/1036409/recursively-convert-python-object-graph-to-dictionary
    """
    if isinstance(obj, dict):
        for k in obj.keys():
            obj[k] = todict(obj[k], classkey)
        return obj
    elif hasattr(obj, "__iter__"):
        return [todict(v, classkey) for v in obj]
    elif hasattr(obj, "__dict__"):
        data = dict([(key, todict(value, classkey))
            for key, value in obj.__dict__.iteritems()
            if not callable(value) and not key.startswith('_')])
        if classkey is not None and hasattr(obj, "__class__"):
            data[classkey] = obj.__class__.__name__
        return data
    else:
        return obj

class Saveable(object):

    def __init__(self, dao, id=None):
        self.dao = dao

        if id is None:
            self.id = uuid.uuid4().hex
        else:
            self.id = id

    def as_dict(self, obj=None, classkey=None):
        ''' Recursively convert this object's members into a dictionary structure for
            serialisation to the database.
        '''
        dict_repr = todict(self)
        # We don't want to store the dao object
        del dict_repr["dao"]
        return dict_repr

    def _update_dict(self, input, my_dict=None):
        '''Use another dict to recursively add list or dict items not in this.

        This object's value wins all conflicts, but new values in lists and
        dictionaries are added. Used to update from the db before saving.
        I think this might should go in the dao...not sure.

        returns - dict
        '''
        if my_dict is None:
            my_dict = self.as_dict()

        if input is None:
            return my_dict

        for k, v in input.iteritems():
            try:
                #get the dict here...whenever

                new_my_dict = my_dict.setdefault(k, {})
                self._update_dict(v, new_my_dict)

            except AttributeError:
                if not my_dict[k]:
                    my_dict[k] = v

        return my_dict

    def save(self):
        """ Save the object to the database, handling merging of data
            should the object have already been updated elsewhere """
        # Blocking call to acquire a lock for this section
        db_write_lock.acquire(True)
        new_dict = self.dao.get(self.id)
        dict_to_save = self._update_dict(new_dict)
        res = self.dao.save(dict_to_save)
        db_write_lock.release()
        return res

    def delete(self):
        self.dao.delete(self.id)
        return True

class ItemFactory():
    #TODO this should subclass a SaveableFactory

    @classmethod
    def get(cls, dao, id, metric_names):
        now = time.time()
        item_doc = dao.get(id)
        item = Item(dao, id=id)

        if item_doc is None:
            raise LookupError

        item.last_requested = now

        # first, just copy everything from the item_doc the DB gave us
        for k in item_doc:
            if k not in ["_id", "_rev"]:
                setattr(item, k, item_doc[k])

        # the aliases property needs to be an Aliases obj, not a dict.
        item.aliases = Aliases(seed=item_doc['aliases'])

        # make the Metric objects. We have to make keys for each metric in the config
        # so that Providers will know which metrics to update later on.
        # Then we fill these Metric objects's dictionaries with the metricSnaps
        # from the db.

        # first combine all the keys we have. Don't want to overwrite anything.
        try:
            metric_names = set(metric_names + item_doc["metrics"].keys() )
        except KeyError:
            pass

        item.metrics = {}
        for name in metric_names:
            try:
                item.metrics[name] = item_doc["metrics"][name]
            except KeyError: #this metric ain't in the item_doc from the db
                item.metrics[name] = {'values': {} }
        return item

    @classmethod
    def make(cls, dao, metric_names):
        now = time.time()
        item = Item(dao=dao)

        # make all the top-level stuff
        item.aliases = Aliases()
        item.metrics = {}
        item.biblio = {}
        item.last_modified = now
        item.created = now

        # make the metrics objects. We have to make all the ones in the config
        # so that Providers will know which ones to update later on.
        for name in metric_names:
            item.metrics[name] = {'values': {} }

        return item

class Item(Saveable):
    """{
        "id": "<uuid4>",
        "aliases": "<Aliases object>",
        "metrics": {
            "plos:html_views":{
                "ignore":False,
                "static_meta": "<static_meta dict from provider>",
                "values":
                    12: "1234556789.2",
                    13: "1234567999.9"
            },
            "plos:pdf_views: {...},
            "dryad:total_downloads: {...},
            ...
        },
        "biblio": "<Biblio object>",
        "created": 23112412414.234,
        "last_modified": 12414214.234,
        "last_requested": 124141245.234
    }
    """
    pass

class Error(Saveable):
    """{
        "error_type": "http_timeout",
        "message": "Error opening file",
        "provider": "github_provider",
        "id": "uuid4-goes-here",
        "stack_trace": "Python Stacktrace"
    }"""
    pass

class CollectionFactory():

    @staticmethod
    def make(dao, id=None, collection_dict=None):

        if id is not None and collection_dict is not None:
            raise TypeError("you can load from the db or from a dict, but not both")

        now = time.time()
        collection = Collection(dao=dao)

        if id is None and collection_dict is None:
            collection.id = uuid.uuid4().hex
            collection.created = now
            collection.last_modified = now
        else: # load an extant item
            if collection_dict is None:
                collection_dict = dao.get(id)

            if collection_dict is None:
                raise LookupError

            for k in collection_dict:
                setattr(collection, k, collection_dict[k])

        return collection

class Collection(Saveable):
    """
    {
        "id": "uuid-goes-here",
        "collection_name": "My Collection",
        "owner": "abcdef",
        "created": 1328569452.406,
        "last_modified": 1328569492.406,
        "item_tiids": ["abcd3", "abcd4"]
    }
    """

    def item_ids(self):
        if not hasattr(self, "item_tiids"):
            self.item_tiids = []
        return self.item_tiids

    def add_item(self, new_item_id):
        if not hasattr(self, "item_tiids"):
            self.item_tiids = []
        if new_item_id not in self.item_tiids:
            self.item_tiids.append(new_item_id)

    def add_items(self, new_item_ids):
        if not hasattr(self, "item_tiids"):
            self.item_tiids = []
        for item_id in new_item_ids:
            self.add_item(item_id)

    def remove_item(self, item_id):
        if not hasattr(self, "item_tiids"):
            self.item_tiids = []
        if item_id in self.item_tiids:
            self.item_tiids.remove(item_id)

class Biblio(object):
    """
    {
        "title": "An extension of de Finetti's theorem",
        "journal": "Advances in Applied Probability",
        "author": [
            "Pitman, J"
        ],
        "collection": "pitnoid",
        "volume": "10",
        "id": "p78",
        "year": "1978",
        "pages": "268 to 270"
    }
    """
    #TODO remove the "data" property, bringing this into parallel with teh
    # other stuff in this module.

    def __init__(self, seed=None):
        self.data = seed if seed is not None else ""

    def __str__(self):
        return str(self.data)

    def __repr__(self):
        return str(self.data)

    def as_dict(self):
        # renamed for consistancy with Items(); TODO cleanup old one
        return self.data

class Aliases(object):
    """
    {
        "title":["Why Most Published Research Findings Are False"],
        "url":["http:\/\/www.plosmedicine.org\/article\/info:doi\/10.1371\/journal.pmed.0020124"],
        "doi": ["10.1371\/journal.pmed.0020124"],
        "created": 12387239847.234,
        "last_modified": 1328569492.406
        ...
    }

    note we're not keeping the TIID in here any more. it needs to be on the the
    item, since that's what it describes. having it in two places == bad.
    """

    not_aliases = ["created", "last_modified"]

    def __init__(self, seed=None):
        self.created = time.time() # will get overwritten if need be
        try:
            for k in seed:
                setattr(self, k, seed[k])
        except TypeError:
            pass

    def add_alias(self, namespace, id):
        try:
            attr = getattr(self, namespace)
            attr.append(id)
        except AttributeError:
            setattr(self, namespace, [id])
        self.last_modified = time.time()

    #FIXME: this should take namespace and id, not a list of them
    def add_unique(self, alias_list):
        for ns, id in alias_list:
            if id not in getattr(self, ns, []):
                self.add_alias(ns, id)
        self.last_modified = time.time()

    def get_aliases_list(self, namespace_list=None):
        '''
        gets list of this object's aliases in each given namespace

        returns a list of (namespace, id) tuples
        '''
        # if this is a get on everything, just summon up the items
        if namespace_list is None:
            return [x for x in self.__dict__ if x not in self.not_aliases]

        # if the caller doesn't pass us a list, but just a single value, wrap it
        # up for them
        if not hasattr(namespace_list, "append"):
            namespace_list = [namespace_list]

        # otherwise, get for the specific namespaces
        ret = []
        for namespace in namespace_list:
            try:
                ids = getattr(self, namespace)

                # crazy hack TODO fix lists/strings flying about
                if not hasattr(ids, "append"):
                    ids = [ids]
                ret += [(namespace, id) for id in ids]
            except AttributeError:
                # this alias doesn't have that namespace...no worries, move on.
                pass

        return ret

    # FIXME I don't think we need this any more?
    def as_dict(self):
        return self.__dict__

<|endoftext|>


<commit_before>
from traffic import flags
from traffic import utils
from traffic import rootwrap
from traffic.db import base
import os

class API(base.Base):

    def set_execute(self, execute):
        self._execute = execute

    def create(self, context, instance_id, band, mac, prio=1):
        cmdlist = ["ifconfig | grep ", mac, " | awk '{print $1}'"]
        eht = os.popen("".join(cmdlist))
        virnt = eht.read().rstrip()
        cmds = ['tc qdisc add dev ', virnt, 'handle ffff: ingress']
        cmdfil = ['tc filter add dev ', virnt, 'parent ffff: protocol ip prio 50 u32 match ip src 0.0.0.0/0 police rate ']
        cmdfil.append(band)
        cmdfil.append('mbit burst 10k drop flowid :1')
        self.db.tqdisc_create(context,
                              {'instanceid': instance_id,
                               'classid': '',
                               'prio': prio,
                               'band': band})
        os.system(''.join(cmds))
        os.system(''.join(cmdfil))

    def create_bk(self, context, instance_id, band, mac, prio=1):
        classid = self.db.get_classid(context)
        if not classid:
            classid = '10:1'
            cmds = 'tc class add dev eth0 parent 10: classid 10:1 htb rate 1000Mbit ceil 1000Mbit'
            os.system(cmds)
        else:
            classid = classid[0]
        new_id = int(classid.split(':')[1]) + 1
        new_class_id = '10:' + str(new_id)
        bands = band + 'Mbit'
        cmd = ['tc class add dev eth0 parent 10:1 classid ', new_class_id, ' htb rate ', bands, ' prio ', str(prio)]
        self.db.tqdisc_create(context,
                              {'instanceid': instance_id,
                               'classid': new_class_id,
                               'prio': prio,
                               'band': bands})
#        self._execute('tc class add dev eth0 parent 10:1 classid', new_class_id, 'htb rate', bands, 'prio', prio)
        os.system(''.join(cmd))
        return new_class_id

    def get(self, context, id):
        result = self.db.tqdisc_get(context, id)
        return result

    def get_all(self, context):
        result = self.db.tqdisc_get_all(context)
        return result

    def get_by_instance_id(self, context, instance_id):
        result = self.db.tqdisc_get_by_instance_id(context, instance_id)
        return result

    def get_host(self, context, classid):
        return self.db.tqdisc_get_host(context, classid)

    def get_by_ip(self, context, ip):
        result = self.db.tqdisc_get_by_ip(context, ip)
        return result

    def delete(self, context, classid):
        cmd = ['tc class del dev eth0 classid ', classid]
        self._execute(cmd)
        self.db.tqdisc_delete(context, classid)<commit_msg>change the mac<commit_after>
from traffic import flags
from traffic import utils
from traffic import rootwrap
from traffic.db import base
import os

class API(base.Base):

    def set_execute(self, execute):
        self._execute = execute

    def create(self, context, instance_id, band, mac, prio=1):
        mac = mac[3:]
        cmdlist = ["ifconfig | grep ", mac, " | awk \"{print $1}\""]
        eht = os.popen("".join(cmdlist))
        virnt = eht.read().rstrip()
        cmds = ['tc qdisc add dev ', virnt, 'handle ffff: ingress']
        cmdfil = ['tc filter add dev ', virnt, 'parent ffff: protocol ip prio 50 u32 match ip src 0.0.0.0/0 police rate ']
        cmdfil.append(band)
        cmdfil.append('mbit burst 10k drop flowid :1')
        self.db.tqdisc_create(context,
                              {'instanceid': instance_id,
                               'classid': '',
                               'prio': prio,
                               'band': band})
        os.system(''.join(cmds))
        os.system(''.join(cmdfil))

    def create_bk(self, context, instance_id, band, mac, prio=1):
        classid = self.db.get_classid(context)
        if not classid:
            classid = '10:1'
            cmds = 'tc class add dev eth0 parent 10: classid 10:1 htb rate 1000Mbit ceil 1000Mbit'
            os.system(cmds)
        else:
            classid = classid[0]
        new_id = int(classid.split(':')[1]) + 1
        new_class_id = '10:' + str(new_id)
        bands = band + 'Mbit'
        cmd = ['tc class add dev eth0 parent 10:1 classid ', new_class_id, ' htb rate ', bands, ' prio ', str(prio)]
        self.db.tqdisc_create(context,
                              {'instanceid': instance_id,
                               'classid': new_class_id,
                               'prio': prio,
                               'band': bands})
#        self._execute('tc class add dev eth0 parent 10:1 classid', new_class_id, 'htb rate', bands, 'prio', prio)
        os.system(''.join(cmd))
        return new_class_id

    def get(self, context, id):
        result = self.db.tqdisc_get(context, id)
        return result

    def get_all(self, context):
        result = self.db.tqdisc_get_all(context)
        return result

    def get_by_instance_id(self, context, instance_id):
        result = self.db.tqdisc_get_by_instance_id(context, instance_id)
        return result

    def get_host(self, context, classid):
        return self.db.tqdisc_get_host(context, classid)

    def get_by_ip(self, context, ip):
        result = self.db.tqdisc_get_by_ip(context, ip)
        return result

    def delete(self, context, classid):
        cmd = ['tc class del dev eth0 classid ', classid]
        self._execute(cmd)
        self.db.tqdisc_delete(context, classid)<|endoftext|>


<commit_before># coding: utf-8

"""
    trakerr Client API

    Get your application events and errors to trakerr via the *trakerr API*.

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
"""

import platform
import pprint
import sys
from datetime import datetime

from six import *
# might want to clean up imports?
from trakerr_client import ApiClient, Configuration
from trakerr_client.apis import EventsApi
from trakerr_client.models import *

from event_trace_builder import EventTraceBuilder
from trakerr_utils import TrakerrUtils

class TrakerrClient(object):
    """
    An object which controls creating and sending AppEvents.
    """

    # Class variables and properties
    # event_api

    # api_key
    # context_app_version
    # context_env_name
    # context_env_version
    # context_env_hostname
    # context_appos
    # context_appos_version
    # context_appbrowser
    # context_appbrowser_version
    # context_datacenter
    # context_datacenter_region

    EPOCH_CONSTANT = datetime(1970, 1, 1)

    def __init__(self, api_key, context_app_version="1.0", context_deployment_stage="development"):
        """
        Initializes the TrakerrClient class and default values for it's properties.
        :param context_env_name: The string name of the enviroment the code is running on.
        :param context_deployment_stage: The string version of the enviroment
         the code is running on.
        """

        if (not isinstance(api_key, string_types)
                or (not isinstance(context_app_version, string_types)
                    and context_app_version is not None)
                or (not isinstance(context_deployment_stage, string_types)
                    and context_deployment_stage is not None)):
            raise TypeError("Arguments are expected strings")

        # pep8 linters wants you to init the private variable (which is good practice in python)
        # before the public property getters and setters. Hence the
        # preappending self._(privatevariable).
        seq = " "
        self._api_key = self.api_key = api_key
        self._context_app_version = self.context_app_version = context_app_version
        self._context_deployment_stage = self.context_deployment_stage = context_deployment_stage
        self._context_env_language = self.context_env_language = "Python"
        self._context_env_name = self.context_env_name = platform.python_implementation()
        self._context_env_version = self.context_env_version = platform.python_version()
        # Join is supposed to be faster than + operator
        self._context_env_hostname = self.context_env_hostname = platform.node()
        self._context_appos = self.context_appos = seq.join(
            (platform.system(), platform.release()))
        self._context_appos_version = self.context_appos_version = platform.version()
        self._context_appbrowser = self.context_appbrowser = None  # find default
        self._context_appbrowser_version = self.context_appbrowser_version = None  # find default
        self._context_datacenter = self.context_datacenter = None
        self._context_datacenter_region = self.context_datacenter_region = None

        self._events_api = EventsApi(ApiClient(Configuration().host))
        # Should get the default url. Also try Configuration().host

    def create_new_app_event(self, log_level="error", classification="Issue", event_type="unknown",
                             event_message="unknown", exc_info=False):
        """
        Creates a new AppEvent instance.
        :param log_level: Strng representation on the level of the Error.
         Can be 'debug','info','warning','error', 'fatal', defaults to 'error'.
        :param classification: Optional extra string descriptor to clarify the error.
        :param event_type: String representation of the type of error.
        :param event_message: String message of the error.
        :param exc_info: The exc_info tuple to parse the stacktrace from.
         Pass None to generate one from the current error stack;
         pass false to skip parsing the stacktrace.
        :return: AppEvent instance with exc_info parsed depending on the above flags.
        """
        try:
            if exc_info is True or exc_info is None:
                exc_info = sys.exc_info()
                if not any(exc_info):
                    exc_info = False

            if exc_info is not False:
                if not TrakerrUtils.is_exc_info_tuple(exc_info):
                    raise TypeError(
                        "exc_info is expected an exc_info info tuple or False.")
                errtype, value = exc_info[:2]
                if event_type is None or event_type == "unknown":
                    event_type = TrakerrUtils.format_error_name(errtype)
                if event_message is None or event_message == "unknown":
                    event_message = str(value)

            if (not isinstance(log_level, string_types)
                    or (not isinstance(classification, string_types)
                        and classification is not None)
                    or (not isinstance(event_type, string_types)
                        and event_type is not None)
                    or (not isinstance(event_message, string_types)
                        and event_message is not None)):
                # Do the type check before you creat a new event, hence why we
                # can't merge the two if not false statements.
                raise TypeError("Arguments are expected strings or None.")

            #Accepted value string parsing.
            #Leave the appevent incase someone wants to fill it out themselves as an error.
            #But we just want to default to error here.
            log_level = log_level.lower()
            allowed_values = ["debug", "info", "warning", "error", "fatal"]
            if log_level not in allowed_values:
                log_level = "error"

            excevent = AppEvent(self.api_key, log_level,
                                classification, event_type, event_message)

            if exc_info is not False:
                excevent.event_stacktrace = EventTraceBuilder.get_event_traces(
                    exc_info)
        finally:
            del exc_info

        return excevent

    def send_event(self, app_event):
        """
        Sends the given AppEvent instance to trakerr.
        :param app_event: AppEvent instance to send to trakerr.
        """
        if not isinstance(app_event, AppEvent):
            raise TypeError("Argument is expected of class AppEvent.")

        self.fill_defaults(app_event)
        self._events_api.events_post(app_event)

    def send_event_async(self, app_event, call_back=None):
        """
        Asyncronously sends the given AppEvent instance to trakerr.
        :param app_event: AppEvent instance to send to trakerr.
        :param call_back: Callback method for the async call.
        defaults to module level async_callback.
        """

        if not isinstance(app_event, AppEvent):
            raise TypeError("Argument is expected of class AppEvent.")

        if call_back is None:
            call_back = async_callback

        self.fill_defaults(app_event)
        self._events_api.events_post_with_http_info(
            app_event, callback=call_back)

    def log(self, arg_dict, log_level="error", classification="issue", exc_info=True):
        """
        Creates an AppEvent and sends it with the default values to all fields.
        Allows the caller to pass in user and session as added information to log,
        to file the error under.
        :param arg_dict: Dictionary with any of these key value pairs assigned to a string:
        errname, errmessage, user, session. You can leave any pair out that you don't need.
        To construct with pure default values,pass in an empty dictionary.
        If you are getting the Stacktrace errname and message will be filled with
        the values from Stacktrace. Otherwise, both errname and errmessage will be unknown.
        :param log_level: String representation on the level of the Error.
        Can be 'debug', 'info', 'warning', 'error', 'fatal', defaults to 'error'.
        :param classification: Optional extra string descriptor to clarify the error.
        (IE: log_level is fatal and classification may be 'hard lock' or 'Network error')
        :param exc_info: exc_info tuple to parse.
        Default None to generate a exc_info tuple from the current stacktrace.
        Pass False to not generate an exc_info tuple.
        """
        excevent = self.create_new_app_event(log_level,
                                             classification, arg_dict.get(
                                                 'errname'),
                                             arg_dict.get('errmessage'), exc_info)
        excevent.event_user = arg_dict.get('user')
        excevent.event_session = arg_dict.get('session')
        self.send_event_async(excevent)

    def fill_defaults(self, app_event):
        """
        Checks the given app event, and if it each event field is not filled out in a specific case,
        fill out the the event with the instance defaults.
        :param app_event:  The app event to fill parameters out.
        :return: The fully filled out AppEvent object,
        while also filling out the instance passed in.
        """

        if not isinstance(app_event, AppEvent):
            raise TypeError("Argument is expected of class AppEvent.")

        if app_event.api_key is None:
            app_event.api_key = self.api_key

        if app_event.context_app_version is None:
            app_event.context_app_version = self.context_app_version
        if app_event.deployment_stage is None:
            app_event.deployment_stage = self.context_deployment_stage

        if app_event.context_env_language is None:
            app_event.context_env_language = self.context_env_language
        if app_event.context_env_name is None:
            app_event.context_env_name = self.context_env_name
        if app_event.context_env_version is None:
            app_event.context_env_version = self.context_env_version
        if app_event.context_env_hostname is None:
            app_event.context_env_hostname = self.context_env_hostname

        if app_event.context_app_os is None:
            app_event.context_app_os = self.context_appos
        if app_event.context_app_os_version is None:
            app_event.context_app_os_version = self.context_appos_version

        if app_event.context_app_browser is None:
            app_event.context_app_browser = self.context_appbrowser
        if app_event.context_app_browser_version is None:
            app_event.context_app_browser_version = self.context_appbrowser_version

        if app_event.context_data_center is None:
            app_event.context_data_center = self.context_datacenter
        if app_event.context_data_center_region is None:
            app_event.context_data_center_region = self.context_datacenter_region

        tdo = datetime.utcnow() - self.EPOCH_CONSTANT  # timedelta object
        if app_event.event_time is None:
            app_event.event_time = int(tdo.total_seconds() * 1000)

    # getters and setters
    @property
    def api_key(self):
        """api_key property"""
        return self._api_key

    @api_key.setter
    def api_key(self, value):
        self._api_key = value

    @api_key.deleter
    def api_key(self):
        del self._api_key

    @property
    def context_app_version(self):
        """context_app_version property"""
        return self._context_app_version

    @context_app_version.setter
    def context_app_version(self, value):
        self._context_app_version = value

    @context_app_version.deleter
    def context_app_version(self):
        del self._context_app_version

    @property
    def context_env_name(self):
        """context_env_name property"""
        return self._context_env_name

    @context_env_name.setter
    def context_env_name(self, value):
        self._context_env_name = value

    @context_env_name.deleter
    def context_env_name(self):
        del self._context_env_name

    @property
    def context_env_version(self):
        """context_env_version property"""
        return self._context_env_version

    @context_env_version.setter
    def context_env_version(self, value):
        self._context_env_version = value

    @context_env_version.deleter
    def context_env_version(self):
        del self._context_env_hostname

    @property
    def context_env_hostname(self):
        """context_env_hostname property"""
        return self._context_env_hostname

    @context_env_hostname.setter
    def context_env_hostname(self, value):
        self._context_env_hostname = value

    @context_env_hostname.deleter
    def context_env_hostname(self):
        del self._context_env_hostname

    @property
    def context_appos(self):
        """context_appos property"""
        return self._context_appos

    @context_appos.setter
    def context_appos(self, value):
        self._context_appos = value

    @context_appos.deleter
    def context_appos(self):
        del self._context_appos

    @property
    def context_appos_version(self):
        """context_appos_version property"""
        return self._context_appos_version

    @context_appos_version.setter
    def context_appos_version(self, value):
        self._context_appos_version = value

    @context_appos_version.deleter
    def context_appos_version(self):
        del self._context_appos_version

    @property
    def context_appbrowser(self):
        """context_app_browser property"""
        return self._context_appbrowser

    @context_appbrowser.setter
    def context_appbrowser(self, value):
        self._context_appbrowser = value

    @context_appbrowser.deleter
    def context_appbrowser(self):
        del self._context_appbrowser

    @property
    def context_appbrowser_version(self):
        """context_app_browser_version property"""
        return self._context_appbrowser_version

    @context_appbrowser_version.setter
    def context_appbrowser_version(self, value):
        self._context_appbrowser_version = value

    @context_appbrowser_version.deleter
    def context_appbrowser_version(self):
        del self._context_appbrowser_version

    @property
    def context_datacenter(self):
        """context_datacenter property"""
        return self._context_datacenter

    @context_datacenter.setter
    def context_datacenter(self, value):
        self._context_datacenter = value

    @context_datacenter.deleter
    def context_datacenter(self):
        del self._context_datacenter

    @property
    def context_datacenter_region(self):
        """context_data_center_region property"""
        return self._context_datacenter_region

    @context_datacenter_region.setter
    def context_datacenter_region(self, value):
        self._context_datacenter_region = value

    @context_datacenter_region.deleter
    def context_datacenter_region(self):
        del self._context_datacenter_region

    @property
    def context_deployment_stage(self):
        """context_deployment_stage property"""
        return self._context_deployment_stage

    @context_deployment_stage.setter
    def context_deployment_stage(self, value):
        self._context_deployment_stage = value

    @context_deployment_stage.deleter
    def context_deployment_stage(self):
        del self._context_deployment_stage

    @property
    def context_env_language(self):
        """context_deployment_stage property"""
        return self._context_env_language

    @context_env_language.setter
    def context_env_language(self, value):
        self._context_env_language = value

    @context_env_language.deleter
    def context_env_language(self):
        del self._context_env_language

def async_callback(response):
    """
    Callback method for the send_event_async function. Currently outputs nothing.
    :param response: message returned after the async call is completed.
    """

    #pprint.pprint(str(response), sys.stderr)
<commit_msg>Update trakerr_io.py<commit_after># coding: utf-8

"""
    trakerr Client API

    Get your application events and errors to trakerr via the *trakerr API*.

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
"""

import platform
import pprint
import sys
from datetime import datetime

from six import *
# might want to clean up imports?
from trakerr_client import ApiClient, Configuration
from trakerr_client.apis import EventsApi
from trakerr_client.models import *

from event_trace_builder import EventTraceBuilder
from trakerr_utils import TrakerrUtils

class TrakerrClient(object):
    """
    An object which controls creating and sending AppEvents.
    """

    # Class variables and properties
    # event_api

    # api_key
    # context_app_version
    # context_env_name
    # context_env_version
    # context_env_hostname
    # context_appos
    # context_appos_version
    # context_appbrowser
    # context_appbrowser_version
    # context_datacenter
    # context_datacenter_region

    EPOCH_CONSTANT = datetime(1970, 1, 1)

    def __init__(self, api_key, context_app_version="1.0", context_deployment_stage="development"):
        """
        Initializes the TrakerrClient class and default values for it's properties.
        :param context_env_name: The string name of the enviroment the code is running on.
        :param context_deployment_stage: The string version of the enviroment
         the code is running on.
        """

        if (not isinstance(api_key, string_types)
                or (not isinstance(context_app_version, string_types)
                    and context_app_version is not None)
                or (not isinstance(context_deployment_stage, string_types)
                    and context_deployment_stage is not None)):
            raise TypeError("Arguments are expected strings")

        # pep8 linters wants you to init the private variable (which is good practice in python)
        # before the public property getters and setters. Hence the
        # preappending self._(privatevariable).
        seq = " "
        self._api_key = self.api_key = api_key
        self._context_app_version = self.context_app_version = context_app_version
        self._context_deployment_stage = self.context_deployment_stage = context_deployment_stage
        self._context_env_language = self.context_env_language = "Python"
        self._context_env_name = self.context_env_name = platform.python_implementation()
        self._context_env_version = self.context_env_version = platform.python_version()
        self._context_env_hostname = self.context_env_hostname = platform.node()
        # Join is supposed to be faster than + operator
        self._context_appos = self.context_appos = seq.join(
            (platform.system(), platform.release()))
        self._context_appos_version = self.context_appos_version = platform.version()
        self._context_appbrowser = self.context_appbrowser = None  # find default
        self._context_appbrowser_version = self.context_appbrowser_version = None  # find default
        self._context_datacenter = self.context_datacenter = None
        self._context_datacenter_region = self.context_datacenter_region = None

        self._events_api = EventsApi(ApiClient(Configuration().host))
        # Should get the default url. Also try Configuration().host

    def create_new_app_event(self, log_level="error", classification="Issue", event_type="unknown",
                             event_message="unknown", exc_info=False):
        """
        Creates a new AppEvent instance.
        :param log_level: Strng representation on the level of the Error.
         Can be 'debug','info','warning','error', 'fatal', defaults to 'error'.
        :param classification: Optional extra string descriptor to clarify the error.
        :param event_type: String representation of the type of error.
        :param event_message: String message of the error.
        :param exc_info: The exc_info tuple to parse the stacktrace from.
         Pass None to generate one from the current error stack;
         pass false to skip parsing the stacktrace.
        :return: AppEvent instance with exc_info parsed depending on the above flags.
        """
        try:
            if exc_info is True or exc_info is None:
                exc_info = sys.exc_info()
                if not any(exc_info):
                    exc_info = False

            if exc_info is not False:
                if not TrakerrUtils.is_exc_info_tuple(exc_info):
                    raise TypeError(
                        "exc_info is expected an exc_info info tuple or False.")
                errtype, value = exc_info[:2]
                if event_type is None or event_type == "unknown":
                    event_type = TrakerrUtils.format_error_name(errtype)
                if event_message is None or event_message == "unknown":
                    event_message = str(value)

            if (not isinstance(log_level, string_types)
                    or (not isinstance(classification, string_types)
                        and classification is not None)
                    or (not isinstance(event_type, string_types)
                        and event_type is not None)
                    or (not isinstance(event_message, string_types)
                        and event_message is not None)):
                # Do the type check before you creat a new event, hence why we
                # can't merge the two if not false statements.
                raise TypeError("Arguments are expected strings or None.")

            #Accepted value string parsing.
            #Leave the appevent incase someone wants to fill it out themselves as an error.
            #But we just want to default to error here.
            log_level = log_level.lower()
            allowed_values = ["debug", "info", "warning", "error", "fatal"]
            if log_level not in allowed_values:
                log_level = "error"

            excevent = AppEvent(self.api_key, log_level,
                                classification, event_type, event_message)

            if exc_info is not False:
                excevent.event_stacktrace = EventTraceBuilder.get_event_traces(
                    exc_info)
        finally:
            del exc_info

        return excevent

    def send_event(self, app_event):
        """
        Sends the given AppEvent instance to trakerr.
        :param app_event: AppEvent instance to send to trakerr.
        """
        if not isinstance(app_event, AppEvent):
            raise TypeError("Argument is expected of class AppEvent.")

        self.fill_defaults(app_event)
        self._events_api.events_post(app_event)

    def send_event_async(self, app_event, call_back=None):
        """
        Asyncronously sends the given AppEvent instance to trakerr.
        :param app_event: AppEvent instance to send to trakerr.
        :param call_back: Callback method for the async call.
        defaults to module level async_callback.
        """

        if not isinstance(app_event, AppEvent):
            raise TypeError("Argument is expected of class AppEvent.")

        if call_back is None:
            call_back = async_callback

        self.fill_defaults(app_event)
        self._events_api.events_post_with_http_info(
            app_event, callback=call_back)

    def log(self, arg_dict, log_level="error", classification="issue", exc_info=True):
        """
        Creates an AppEvent and sends it with the default values to all fields.
        Allows the caller to pass in user and session as added information to log,
        to file the error under.
        :param arg_dict: Dictionary with any of these key value pairs assigned to a string:
        errname, errmessage, user, session. You can leave any pair out that you don't need.
        To construct with pure default values,pass in an empty dictionary.
        If you are getting the Stacktrace errname and message will be filled with
        the values from Stacktrace. Otherwise, both errname and errmessage will be unknown.
        :param log_level: String representation on the level of the Error.
        Can be 'debug', 'info', 'warning', 'error', 'fatal', defaults to 'error'.
        :param classification: Optional extra string descriptor to clarify the error.
        (IE: log_level is fatal and classification may be 'hard lock' or 'Network error')
        :param exc_info: exc_info tuple to parse.
        Default None to generate a exc_info tuple from the current stacktrace.
        Pass False to not generate an exc_info tuple.
        """
        excevent = self.create_new_app_event(log_level,
                                             classification, arg_dict.get(
                                                 'errname'),
                                             arg_dict.get('errmessage'), exc_info)
        excevent.event_user = arg_dict.get('user')
        excevent.event_session = arg_dict.get('session')
        self.send_event_async(excevent)

    def fill_defaults(self, app_event):
        """
        Checks the given app event, and if it each event field is not filled out in a specific case,
        fill out the the event with the instance defaults.
        :param app_event:  The app event to fill parameters out.
        :return: The fully filled out AppEvent object,
        while also filling out the instance passed in.
        """

        if not isinstance(app_event, AppEvent):
            raise TypeError("Argument is expected of class AppEvent.")

        if app_event.api_key is None:
            app_event.api_key = self.api_key

        if app_event.context_app_version is None:
            app_event.context_app_version = self.context_app_version
        if app_event.deployment_stage is None:
            app_event.deployment_stage = self.context_deployment_stage

        if app_event.context_env_language is None:
            app_event.context_env_language = self.context_env_language
        if app_event.context_env_name is None:
            app_event.context_env_name = self.context_env_name
        if app_event.context_env_version is None:
            app_event.context_env_version = self.context_env_version
        if app_event.context_env_hostname is None:
            app_event.context_env_hostname = self.context_env_hostname

        if app_event.context_app_os is None:
            app_event.context_app_os = self.context_appos
        if app_event.context_app_os_version is None:
            app_event.context_app_os_version = self.context_appos_version

        if app_event.context_app_browser is None:
            app_event.context_app_browser = self.context_appbrowser
        if app_event.context_app_browser_version is None:
            app_event.context_app_browser_version = self.context_appbrowser_version

        if app_event.context_data_center is None:
            app_event.context_data_center = self.context_datacenter
        if app_event.context_data_center_region is None:
            app_event.context_data_center_region = self.context_datacenter_region

        tdo = datetime.utcnow() - self.EPOCH_CONSTANT  # timedelta object
        if app_event.event_time is None:
            app_event.event_time = int(tdo.total_seconds() * 1000)

    # getters and setters
    @property
    def api_key(self):
        """api_key property"""
        return self._api_key

    @api_key.setter
    def api_key(self, value):
        self._api_key = value

    @api_key.deleter
    def api_key(self):
        del self._api_key

    @property
    def context_app_version(self):
        """context_app_version property"""
        return self._context_app_version

    @context_app_version.setter
    def context_app_version(self, value):
        self._context_app_version = value

    @context_app_version.deleter
    def context_app_version(self):
        del self._context_app_version

    @property
    def context_env_name(self):
        """context_env_name property"""
        return self._context_env_name

    @context_env_name.setter
    def context_env_name(self, value):
        self._context_env_name = value

    @context_env_name.deleter
    def context_env_name(self):
        del self._context_env_name

    @property
    def context_env_version(self):
        """context_env_version property"""
        return self._context_env_version

    @context_env_version.setter
    def context_env_version(self, value):
        self._context_env_version = value

    @context_env_version.deleter
    def context_env_version(self):
        del self._context_env_hostname

    @property
    def context_env_hostname(self):
        """context_env_hostname property"""
        return self._context_env_hostname

    @context_env_hostname.setter
    def context_env_hostname(self, value):
        self._context_env_hostname = value

    @context_env_hostname.deleter
    def context_env_hostname(self):
        del self._context_env_hostname

    @property
    def context_appos(self):
        """context_appos property"""
        return self._context_appos

    @context_appos.setter
    def context_appos(self, value):
        self._context_appos = value

    @context_appos.deleter
    def context_appos(self):
        del self._context_appos

    @property
    def context_appos_version(self):
        """context_appos_version property"""
        return self._context_appos_version

    @context_appos_version.setter
    def context_appos_version(self, value):
        self._context_appos_version = value

    @context_appos_version.deleter
    def context_appos_version(self):
        del self._context_appos_version

    @property
    def context_appbrowser(self):
        """context_app_browser property"""
        return self._context_appbrowser

    @context_appbrowser.setter
    def context_appbrowser(self, value):
        self._context_appbrowser = value

    @context_appbrowser.deleter
    def context_appbrowser(self):
        del self._context_appbrowser

    @property
    def context_appbrowser_version(self):
        """context_app_browser_version property"""
        return self._context_appbrowser_version

    @context_appbrowser_version.setter
    def context_appbrowser_version(self, value):
        self._context_appbrowser_version = value

    @context_appbrowser_version.deleter
    def context_appbrowser_version(self):
        del self._context_appbrowser_version

    @property
    def context_datacenter(self):
        """context_datacenter property"""
        return self._context_datacenter

    @context_datacenter.setter
    def context_datacenter(self, value):
        self._context_datacenter = value

    @context_datacenter.deleter
    def context_datacenter(self):
        del self._context_datacenter

    @property
    def context_datacenter_region(self):
        """context_data_center_region property"""
        return self._context_datacenter_region

    @context_datacenter_region.setter
    def context_datacenter_region(self, value):
        self._context_datacenter_region = value

    @context_datacenter_region.deleter
    def context_datacenter_region(self):
        del self._context_datacenter_region

    @property
    def context_deployment_stage(self):
        """context_deployment_stage property"""
        return self._context_deployment_stage

    @context_deployment_stage.setter
    def context_deployment_stage(self, value):
        self._context_deployment_stage = value

    @context_deployment_stage.deleter
    def context_deployment_stage(self):
        del self._context_deployment_stage

    @property
    def context_env_language(self):
        """context_deployment_stage property"""
        return self._context_env_language

    @context_env_language.setter
    def context_env_language(self, value):
        self._context_env_language = value

    @context_env_language.deleter
    def context_env_language(self):
        del self._context_env_language

def async_callback(response):
    """
    Callback method for the send_event_async function. Currently outputs nothing.
    :param response: message returned after the async call is completed.
    """

    #pprint.pprint(str(response), sys.stderr)
<|endoftext|>


<commit_before>from trakt.core.helpers import from_iso8601
from trakt.objects.core.helpers import update_attributes

class List(object):
    def __init__(self, client, keys):
        self._client = client

        self.keys = keys

        # Class attributes
        self.name = None
        self.description = None
        self.likes = None

        self.allow_comments = None
        self.display_numbers = None

        self.liked_at = None
        self.updated_at = None

        self.comment_count = None
        self.item_count = None

    @property
    def id(self):
        if self.pk is None:
            return None

        _, sid = self.pk

        return sid

    @property
    def pk(self):
        if not self.keys:
            return None

        return self.keys[0]

    def _update(self, info=None):
        if not info:
            return

        if 'liked_at' in info:
            self.liked_at = from_iso8601(info.get('liked_at'))

        if 'updated_at' in info:
            self.updated_at = from_iso8601(info.get('updated_at'))

        update_attributes(self, info, [
            'name',
            'description',
            'likes',

            'allow_comments',
            'display_numbers',

            'comment_count',
            'item_count'
        ])

    def __repr__(self):
        _, sid = self.pk

        return '<List %r (%s)>' % (self.name, sid)

    def __str__(self):
        return self.__repr__()

class CustomList(List):
    def __init__(self, client, keys, username=None):
        super(CustomList, self).__init__(client, keys)

        self.username = username

        self.privacy = None

    def _update(self, info=None):
        if not info:
            return

        super(CustomList, self)._update(info)

        update_attributes(self, info, [
            'privacy'
        ])

        # Update with user details
        user = info.get('user', {})

        if user.get('username'):
            self.username = user['username']

    @classmethod
    def _construct(cls, client, keys, info, **kwargs):
        if not info:
            return None

        l = cls(client, keys, **kwargs)
        l._update(info)
        return l

    def items(self, **kwargs):
        return self._client['users/*/lists/*'].items(self.username, self.id, **kwargs)

    #
    # Owner actions
    #

    def add(self, items, **kwargs):
        return self._client['users/*/lists/*'].add(self.username, self.id, items, **kwargs)

    def delete(self, **kwargs):
        return self._client['users/*/lists/*'].delete(self.username, self.id, **kwargs)

    def update(self, **kwargs):
        item = self._client['users/*/lists/*'].update(self.username, self.id, return_type='data', **kwargs)

        if not item:
            return False

        self._update(item)
        return True

    def remove(self, items, **kwargs):
        return self._client['users/*/lists/*'].remove(self.username, self.id, items, **kwargs)

    #
    # Actions
    #

    def like(self, **kwargs):
        return self._client['users/*/lists/*'].like(self.username, self.id, **kwargs)

    def unlike(self, **kwargs):
        return self._client['users/*/lists/*'].unlike(self.username, self.id, **kwargs)
<commit_msg>Fixed an issue serializing `List` objects<commit_after>from trakt.core.helpers import from_iso8601
from trakt.objects.core.helpers import update_attributes

class List(object):
    def __init__(self, client, keys):
        self._client = client

        self.keys = keys

        # Class attributes
        self.name = None
        self.description = None
        self.likes = None

        self.allow_comments = None
        self.display_numbers = None

        self.liked_at = None
        self.updated_at = None

        self.comment_count = None
        self.item_count = None

    @property
    def id(self):
        if self.pk is None:
            return None

        _, sid = self.pk

        return sid

    @property
    def pk(self):
        if not self.keys:
            return None

        return self.keys[0]

    def _update(self, info=None):
        if not info:
            return

        if 'liked_at' in info:
            self.liked_at = from_iso8601(info.get('liked_at'))

        if 'updated_at' in info:
            self.updated_at = from_iso8601(info.get('updated_at'))

        update_attributes(self, info, [
            'name',
            'description',
            'likes',

            'allow_comments',
            'display_numbers',

            'comment_count',
            'item_count'
        ])

    def __getstate__(self):
        state = self.__dict__

        if hasattr(self, '_client'):
            del state['_client']

        return state

    def __repr__(self):
        _, sid = self.pk

        return '<List %r (%s)>' % (self.name, sid)

    def __str__(self):
        return self.__repr__()

class CustomList(List):
    def __init__(self, client, keys, username=None):
        super(CustomList, self).__init__(client, keys)

        self.username = username

        self.privacy = None

    def _update(self, info=None):
        if not info:
            return

        super(CustomList, self)._update(info)

        update_attributes(self, info, [
            'privacy'
        ])

        # Update with user details
        user = info.get('user', {})

        if user.get('username'):
            self.username = user['username']

    @classmethod
    def _construct(cls, client, keys, info, **kwargs):
        if not info:
            return None

        l = cls(client, keys, **kwargs)
        l._update(info)
        return l

    def items(self, **kwargs):
        return self._client['users/*/lists/*'].items(self.username, self.id, **kwargs)

    #
    # Owner actions
    #

    def add(self, items, **kwargs):
        return self._client['users/*/lists/*'].add(self.username, self.id, items, **kwargs)

    def delete(self, **kwargs):
        return self._client['users/*/lists/*'].delete(self.username, self.id, **kwargs)

    def update(self, **kwargs):
        item = self._client['users/*/lists/*'].update(self.username, self.id, return_type='data', **kwargs)

        if not item:
            return False

        self._update(item)
        return True

    def remove(self, items, **kwargs):
        return self._client['users/*/lists/*'].remove(self.username, self.id, items, **kwargs)

    #
    # Actions
    #

    def like(self, **kwargs):
        return self._client['users/*/lists/*'].like(self.username, self.id, **kwargs)

    def unlike(self, **kwargs):
        return self._client['users/*/lists/*'].unlike(self.username, self.id, **kwargs)
<|endoftext|>


<commit_before>import config
import generic
import datetime
from connections import connector
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from addressbook.models import savedAddress

current_section = 'transactions'

@login_required
def index(request, selected_provider_id=False, page=1):
    '''
    handler for the transactions list
    '''

    if selected_provider_id is False:
        selected_provider_id = connector.config.keys()[0]
    else:
        selected_provider_id = int(selected_provider_id)

    page = int(page)
    items_per_page = 10
    page_title = "Transactions"

    hide_moves = request.user.setting.get('hide_moves')

    # get addressbook
    addressBookAddresses = savedAddress.objects.filter(status__gt=1)
    saved_addresses = {}
    for saved_address in addressBookAddresses:
        saved_addresses[saved_address.address] = saved_address.name

    # get transactions
    transactions_list = connector.listtransactionsbyaccount('*', selected_provider_id, items_per_page, (items_per_page*(page-1)))
    # sort transactions
    transactions_list = sorted(transactions_list, key=lambda k: k.get('timereceived',0), reverse=True)

    # remove moves if there is a user setting for it
    transactions = []
    if hide_moves:
        for transaction in transactions_list:
            if transaction['category'] != "move":
                transactions.append(transaction)
    else:
        transactions = transactions_list

    for transaction in transactions:
        if transaction['category'] == "move" and hide_moves:
            continue

        transaction['currency_symbol'] = generic.getCurrencySymbol(transaction['currency'].lower())

        if transaction.get('confirmations', False) is not False:
            if transaction['confirmations'] <= config.MainConfig['globals']['confirmation_limit']:
                transaction['status_icon'] = 'glyphicon-time'
                transaction['status_color'] = '#AAA';
                transaction['tooltip'] = transaction['confirmations']
            else:
                transaction['status_icon'] = 'glyphicon-ok-circle'
                transaction['status_color'] = '#1C9E3F';
                transaction['tooltip'] = transaction['confirmations']

        if transaction['category'] == 'receive':
            transaction['source_address'] = transaction.get('details', {}).get('sender_address', '(no sender address)')
            transaction['addressbook_name'] = saved_addresses.get(transaction['source_address'], False)
            transaction['icon'] = 'glyphicon-circle-arrow-down'
            if not len(transaction['account']):
                transaction['account'] = "(default account)"
                default_account = connector.getdefaultaccount(transaction['provider_id'])
                transaction['destination_address'] = default_account['addresses'][0]
            else:
                transaction['destination_address'] = transaction['address']

        elif transaction['category'] == 'send':
            transaction['source_addresses'] = connector.getaddressesbyaccount(transaction['account'], transaction['provider_id'])
            transaction['destination_address'] = transaction['address']
            transaction['icon'] = 'glyphicon-circle-arrow-up'
            transaction['addressbook_name'] = saved_addresses.get(transaction['address'], False)
            if not len(transaction['account']):
                transaction['account'] = "(default account)"
                default_account = connector.getdefaultaccount(transaction['provider_id'])
                transaction['destination_address'] = default_account['addresses'][0]
            else:
                transaction['destination_address'] = transaction['address']

        elif transaction['category'] == 'move':
            transaction['source_addresses'] = connector.getaddressesbyaccount(transaction['account'], transaction['provider_id'])
            transaction['destination_addresses'] = connector.getaddressesbyaccount(transaction['otheraccount'], transaction['provider_id'])
            transaction['icon'] = 'glyphicon-circle-arrow-right'
            if not len(transaction['account']):
                transaction['account'] = "(default account)"
                default_account = connector.getdefaultaccount(transaction['provider_id'])
                transaction['destination_address'] = default_account['addresses'][0]

    sender_address_tooltip_text = "This address has been calculated using the Input Script Signature. You should verify before using it."

    providers = {}
    for provider_id in connector.config:
        providers[provider_id] = connector.config[provider_id]['name']

    context = {
               'globals': config.MainConfig['globals'],
               'system_errors': connector.errors,
               'system_alerts': connector.alerts,
               'request': request,
               'breadcrumbs': generic.buildBreadcrumbs(current_section, '', connector.config[selected_provider_id]['name']),
               'page_title': page_title,
               'page_sections': generic.getSiteSections(current_section),
               'transactions': transactions,
               'show_pager': True,
               'next_page': (page+1),
               'prev_page': max(1, page-1),
               'current_page': page,
               'saved_addresses': saved_addresses,
               'sender_address_tooltip_text': sender_address_tooltip_text,
               'providers': providers,
               'selected_provider_id': selected_provider_id,
               }
    return render(request, 'transactions/index.html', context)

@login_required
def transactionDetails(request, txid, provider_id):
    provider_id = int(provider_id)
    transaction = connector.gettransaction(txid, provider_id)

    transaction['timereceived_pretty'] = generic.twitterizeDate(transaction.get('timereceived', 'never'))
    transaction['time_pretty'] = generic.twitterizeDate(transaction.get('time', 'never'))
    transaction['timereceived_human'] = datetime.datetime.fromtimestamp(transaction.get('timereceived', 0))
    transaction['time_human'] = datetime.datetime.fromtimestamp(transaction.get('time', 0))
    transaction['blocktime_human'] = datetime.datetime.fromtimestamp(transaction.get('blocktime', 0))
    transaction['blocktime_pretty'] = generic.twitterizeDate(transaction.get('blocktime', 'never'))
    transaction['currency'] = connector.config[provider_id]['currency']
    transaction['currency_symbol'] = generic.getCurrencySymbol(transaction['currency'])

    if transaction.get('fee', False):
        transaction['fee'] = generic.longNumber(transaction['fee'])
    else:
        transaction['fee'] = ""

    account_addresses = []
    if transaction['details'][0]['category'] == 'receive':
        if not len(transaction['details'][0]['account']):
            # get the default account for provider_id
            account = connector.getdefaultaccount(provider_id)
        else:
            account = connector.getaccountdetailsbyaddress(transaction['details'][0]['address'])
    elif transaction['details'][0]['category'] == 'send':
        account_addresses = connector.getaddressesbyaccount(transaction['details'][0]['account'], provider_id)
        account = connector.getaccountdetailsbyaddress(account_addresses[0])

    if not len(transaction['details'][0]['account']):
        transaction['details'][0]['account'] = '(default account)'
    #print transaction

    page_title = "Transaction details for %s" % txid
    context = {
           'globals': config.MainConfig['globals'],
           'system_errors': connector.errors,
           'system_alerts': connector.alerts,
           'request': request,
           'breadcrumbs': generic.buildBreadcrumbs(current_section, '', 'Details of %s' % txid),
           'page_title': page_title,
           'page_sections': generic.getSiteSections(current_section),
           'transaction': transaction,
           'account': account,
           'conf_limit': config.MainConfig['globals']['confirmation_limit'],
           }

    if request.method == 'GET':
        return render(request, 'transactions/details.html', context)
    else:
        # maybe convert to PDF ?
        return render(request, 'transactions/details.html', context)<commit_msg>fix issue with icon<commit_after>import config
import generic
import datetime
from connections import connector
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from addressbook.models import savedAddress

current_section = 'transactions'

@login_required
def index(request, selected_provider_id=False, page=1):
    '''
    handler for the transactions list
    '''

    if selected_provider_id is False:
        selected_provider_id = connector.config.keys()[0]
    else:
        selected_provider_id = int(selected_provider_id)

    page = int(page)
    items_per_page = 10
    page_title = "Transactions"

    hide_moves = request.user.setting.get('hide_moves')

    # get addressbook
    addressBookAddresses = savedAddress.objects.filter(status__gt=1)
    saved_addresses = {}
    for saved_address in addressBookAddresses:
        saved_addresses[saved_address.address] = saved_address.name

    # get transactions
    transactions_list = connector.listtransactionsbyaccount('*', selected_provider_id, items_per_page, (items_per_page*(page-1)))
    # sort transactions
    transactions_list = sorted(transactions_list, key=lambda k: k.get('timereceived',0), reverse=True)

    # remove moves if there is a user setting for it
    transactions = []
    if hide_moves:
        for transaction in transactions_list:
            if transaction['category'] != "move":
                transactions.append(transaction)
    else:
        transactions = transactions_list

    for transaction in transactions:
        if transaction['category'] == "move" and hide_moves:
            continue

        transaction['currency_symbol'] = generic.getCurrencySymbol(transaction['currency'].lower())

        if transaction.get('category', False) in ['receive','send']:
            if transaction['confirmations'] <= config.MainConfig['globals']['confirmation_limit']:
                transaction['status_icon'] = 'glyphicon-time'
                transaction['status_color'] = '#AAA';
                transaction['tooltip'] = transaction['confirmations']
            else:
                transaction['status_icon'] = 'glyphicon-ok-circle'
                transaction['status_color'] = '#1C9E3F';
                transaction['tooltip'] = transaction['confirmations']

        if transaction['category'] == 'receive':
            transaction['source_address'] = transaction.get('details', {}).get('sender_address', '(no sender address)')
            transaction['addressbook_name'] = saved_addresses.get(transaction['source_address'], False)
            transaction['icon'] = 'glyphicon-circle-arrow-down'
            if not len(transaction['account']):
                transaction['account'] = "(default account)"
                default_account = connector.getdefaultaccount(transaction['provider_id'])
                transaction['destination_address'] = default_account['addresses'][0]
            else:
                transaction['destination_address'] = transaction['address']

        elif transaction['category'] == 'send':
            transaction['source_addresses'] = connector.getaddressesbyaccount(transaction['account'], transaction['provider_id'])
            transaction['destination_address'] = transaction['address']
            transaction['icon'] = 'glyphicon-circle-arrow-up'
            transaction['addressbook_name'] = saved_addresses.get(transaction['address'], False)
            if not len(transaction['account']):
                transaction['account'] = "(default account)"
                default_account = connector.getdefaultaccount(transaction['provider_id'])
                transaction['destination_address'] = default_account['addresses'][0]
            else:
                transaction['destination_address'] = transaction['address']

        elif transaction['category'] == 'move':
            transaction['source_addresses'] = connector.getaddressesbyaccount(transaction['account'], transaction['provider_id'])
            transaction['destination_addresses'] = connector.getaddressesbyaccount(transaction['otheraccount'], transaction['provider_id'])
            transaction['icon'] = 'glyphicon-circle-arrow-right'
            if not len(transaction['account']):
                transaction['account'] = "(default account)"
                default_account = connector.getdefaultaccount(transaction['provider_id'])
                transaction['destination_address'] = default_account['addresses'][0]

    sender_address_tooltip_text = "This address has been calculated using the Input Script Signature. You should verify before using it."

    providers = {}
    for provider_id in connector.config:
        providers[provider_id] = connector.config[provider_id]['name']

    context = {
               'globals': config.MainConfig['globals'],
               'system_errors': connector.errors,
               'system_alerts': connector.alerts,
               'request': request,
               'breadcrumbs': generic.buildBreadcrumbs(current_section, '', connector.config[selected_provider_id]['name']),
               'page_title': page_title,
               'page_sections': generic.getSiteSections(current_section),
               'transactions': transactions,
               'show_pager': True,
               'next_page': (page+1),
               'prev_page': max(1, page-1),
               'current_page': page,
               'saved_addresses': saved_addresses,
               'sender_address_tooltip_text': sender_address_tooltip_text,
               'providers': providers,
               'selected_provider_id': selected_provider_id,
               }
    return render(request, 'transactions/index.html', context)

@login_required
def transactionDetails(request, txid, provider_id):
    provider_id = int(provider_id)
    transaction = connector.gettransaction(txid, provider_id)

    transaction['timereceived_pretty'] = generic.twitterizeDate(transaction.get('timereceived', 'never'))
    transaction['time_pretty'] = generic.twitterizeDate(transaction.get('time', 'never'))
    transaction['timereceived_human'] = datetime.datetime.fromtimestamp(transaction.get('timereceived', 0))
    transaction['time_human'] = datetime.datetime.fromtimestamp(transaction.get('time', 0))
    transaction['blocktime_human'] = datetime.datetime.fromtimestamp(transaction.get('blocktime', 0))
    transaction['blocktime_pretty'] = generic.twitterizeDate(transaction.get('blocktime', 'never'))
    transaction['currency'] = connector.config[provider_id]['currency']
    transaction['currency_symbol'] = generic.getCurrencySymbol(transaction['currency'])

    if transaction.get('fee', False):
        transaction['fee'] = generic.longNumber(transaction['fee'])
    else:
        transaction['fee'] = ""

    account_addresses = []
    if transaction['details'][0]['category'] == 'receive':
        if not len(transaction['details'][0]['account']):
            # get the default account for provider_id
            account = connector.getdefaultaccount(provider_id)
        else:
            account = connector.getaccountdetailsbyaddress(transaction['details'][0]['address'])
    elif transaction['details'][0]['category'] == 'send':
        account_addresses = connector.getaddressesbyaccount(transaction['details'][0]['account'], provider_id)
        account = connector.getaccountdetailsbyaddress(account_addresses[0])

    if not len(transaction['details'][0]['account']):
        transaction['details'][0]['account'] = '(default account)'
    #print transaction

    page_title = "Transaction details for %s" % txid
    context = {
           'globals': config.MainConfig['globals'],
           'system_errors': connector.errors,
           'system_alerts': connector.alerts,
           'request': request,
           'breadcrumbs': generic.buildBreadcrumbs(current_section, '', 'Details of %s' % txid),
           'page_title': page_title,
           'page_sections': generic.getSiteSections(current_section),
           'transaction': transaction,
           'account': account,
           'conf_limit': config.MainConfig['globals']['confirmation_limit'],
           }

    if request.method == 'GET':
        return render(request, 'transactions/details.html', context)
    else:
        # maybe convert to PDF ?
        return render(request, 'transactions/details.html', context)<|endoftext|>


<commit_before>import sendgrid
import logging
import requests
import os
from CouncilTag import settings
from sendgrid.helpers.mail import Email, Content, Mail, Attachment
import base64
import pytz
from datetime import datetime, timedelta
import googlemaps
log = logging.Logger(__name__)

def verify_recaptcha(token):
    r = requests.post('https://www.google.com/recaptcha/api/siteverify', data={
                      'secret': os.environ["RECAPTCHAKEY"], 'response': token})
    response = r.json()
    return response['success']

def isCommentAllowed(timestamp, cutoff_days_offset, cutoff_hours, cutoff_minutes):
    gmaps = googlemaps.Client(key=os.environ.get("GOOGLETZAPIKEY"))
    tz_obj = gmaps.timezone(location="34.0195,-118.4912", timestamp=timestamp)
    tz_offset = abs(tz_obj["dstOffset"] + tz_obj["rawOffset"])
    dt = datetime.utcfromtimestamp(timestamp)
    dt = dt + timedelta(days=cutoff_days_offset)
    dt = dt.replace(hour=cutoff_hours, minute=cutoff_minutes)
    dt = dt + timedelta(minutes = tz_offset)
    now = datetime.now()
    if (now > dt):
      return False
    return True

def send_mail(mail_message):
    APIKEY = os.environ["SENDGRIDKEY"]
    sg = sendgrid.SendGridAPIClient(apikey=APIKEY)
    from_email = Email("do-not-reply@engage.town")
    if type(mail_message["user"]) is dict:
        to_email = Email(mail_message["user"]["email"])
    else:
        to_email = Email(mail_message["user"].email)
    subject = mail_message["subject"]
    content = Content('text/html', (mail_message["content"]))
    mail = Mail(from_email=from_email, subject=subject,
                to_email=to_email, content=content)

    if "attachment_file_path" in mail_message:
        with open(mail_message["attachment_file_path"], 'rb') as f:
            data = f.read()
            f.close()

        encode = base64.b64encode(data).decode()
        attachment = Attachment()
        attachment.content = encode
        attachment.type = mail_message["attachment_type"]
        attachment.filename = mail_message["attachment_file_name"]
        attachment.disposition = "attachment"
        mail.add_attachment(attachment)

    response = sg.client.mail.send.post(request_body=mail.get())
    if response.status_code == 200 or response.status_code == 202:
        return True
    else:
        log.error("Could not send an email from {} to {} about {}".format(from_email,
                                                                          to_email, subject))
        log.error(response.body)
        log.error(response.status_code)
        return False

def send_message(message_record):
    sg = sendgrid.SendGridAPIClient(apikey=settings.SENDGRID_API_KEY)
    from_email = Email(message_record["user"].email)
    to_email = Email(settings.COUNCIL_CLERK_EMAIL)
    subject = "Comment on {}".format(message_record.agenda_item.title)
    content = Content('text/html', message_record.content)
    mail = Mail(from_email=from_email, subject=subject,
                to_email=to_email, content=content)
    response = sg.client.mail.send.post(request_body=mail.get())
    if response.status_code == 200 or response.status_code == 202:
        return True
    else:
        log.error("Could not send an email from {} to {} about {}".format(from_email,
                                                                          to_email, subject))
        log.error(response.body)
        log.error(response.status_code)
        return False
<commit_msg>fix the last remaining test error. there are still failures to fix, however.<commit_after>import sendgrid
import logging
import requests
import os
from CouncilTag import settings
from sendgrid.helpers.mail import Email, Content, Mail, Attachment
import base64
import pytz
from datetime import datetime, timedelta
import googlemaps
log = logging.Logger(__name__)

def verify_recaptcha(token):
    r = requests.post('https://www.google.com/recaptcha/api/siteverify', data={
                      'secret': os.environ["RECAPTCHAKEY"], 'response': token})
    response = r.json()
    return response['success']

def isCommentAllowed(timestamp, cutoff_days_offset, cutoff_hours, cutoff_minutes):
    gmaps = googlemaps.Client(key=os.environ.get("GOOGLETZAPIKEY"))
    tz_obj = gmaps.timezone(location="34.0195,-118.4912", timestamp=timestamp)
    tz_offset = abs(tz_obj["dstOffset"] + tz_obj["rawOffset"])
    dt = datetime.utcfromtimestamp(timestamp)
    dt = dt + timedelta(days=cutoff_days_offset)
    dt = dt.replace(hour=cutoff_hours, minute=cutoff_minutes)
    dt = dt + timedelta(minutes = tz_offset)
    now = datetime.now()
    if (now > dt):
      return False
    return True

def send_mail(mail_message):
    APIKEY = os.environ["SENDGRIDKEY"]
    sg = sendgrid.SendGridAPIClient(apikey=APIKEY)
    from_email = Email("do-not-reply@engage.town")
    if type(mail_message["user"]) is dict:
        to_email = Email(mail_message["user"]["email"])
    else:
        to_email = Email(mail_message["user"].email)
    subject = mail_message["subject"]
    content = Content('text/html', (mail_message["content"]))
    mail = Mail(from_email=from_email, subject=subject,
                to_email=to_email, content=content)

    if "attachment_file_path" in mail_message:
        with open(mail_message["attachment_file_path"], 'rb') as f:
            data = f.read()
            f.close()

        encode = base64.b64encode(data).decode()
        attachment = Attachment()
        attachment.content = encode
        attachment.type = mail_message["attachment_type"]
        attachment.filename = mail_message["attachment_file_name"]
        attachment.disposition = "attachment"
        mail.add_attachment(attachment)

    response = sg.client.mail.send.post(request_body=mail.get())
    if response.status_code == 200 or response.status_code == 202:
        return True
    else:
        log.error("Could not send an email from {} to {} about {}".format(from_email,
                                                                          to_email, subject))
        log.error(response.body)
        log.error(response.status_code)
        return False

def send_message(message_record):
    sg = sendgrid.SendGridAPIClient(apikey=settings.SENDGRID_API_KEY)
    from_email = Email(message_record.user.email)
    to_email = Email(settings.COUNCIL_CLERK_EMAIL)
    subject = "Comment on {}".format(message_record.agenda_item.title)
    content = Content('text/html', message_record.content)
    mail = Mail(from_email=from_email, subject=subject,
                to_email=to_email, content=content)
    response = sg.client.mail.send.post(request_body=mail.get())
    if response.status_code == 200 or response.status_code == 202:
        return True
    else:
        log.error("Could not send an email from {} to {} about {}".format(from_email,
                                                                          to_email, subject))
        log.error(response.body)
        log.error(response.status_code)
        return False
<|endoftext|>