<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

class Model(object):

    def __init__(self, dao, id=None, seed=None):
        self.dao = dao
        self.id = None #set it properly below

        if seed:
            for key in seed:
                setattr(self, key, seed[key])
        if id:
            # if the user passes in an id, override the seed id
            self.id = id
        if (self.id is None):
            # if no id in seed or in parameter then mint a new one
            self.id = uuid.uuid4().hex


    def load(self):
        doc = self.dao.get(self.id)
        if doc is None:
            raise(LookupError)
        
        for key in doc:
            setattr(self, key, doc[key])

        return doc

    def save(self):
        doc = self.as_dict()
        # couch wants the underscore...should be fixed in dao, not here.
        doc["_id"] = doc.pop("id")

        try:
            self.dao.update_item(doc, self.id)
        except LookupError:
            self.dao.create_item(doc, self.id)
        return doc

    def keys_from_docstring(self):
        json_doc = inspect.getdoc(self)
        return json.loads(json_doc).keys()

    def as_dict(self):
        doc = {}
        for key in self.keys_from_docstring():
            try:
                temp_val = getattr(self, key)
                try:
                    val = temp_val.as_dict()
                except AttributeError:
                    val = temp_val

                doc[key] = val
                
            except AttributeError:
                doc[key] = None
        return doc

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


# FIXME: no id on the item? this should appear in the alias object?
class Item(Model):
    """{
        "id": "uuid4-goes-here",
        "aliases": "aliases_object",
        "metrics": "metric_object",
        "biblio": "biblio_object",
        "created": 23112412414.234,
        "last_modified": 12414214.234,
        "last_requested": 124141245.234
    }
    """

    def load(self):
        doc = self.dao.get(self.id)
        if doc is None:
            raise(LookupError)
        
        for key in doc:
            setattr(self, key, doc[key])

        setattr(self, "aliases", Aliases(seed=doc["aliases"]))

        return doc
    
    def set_last_requested(self):
        self.last_requested = time.time()
        self.save()


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

        
class Collection(Model):    
    """
    {
        "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": [
            "<NAME>"
        ], 
        "collection": "pitnoid", 
        "volume": "10", 
        "id": "p78", 
        "year": "1978", 
        "pages": "268 to 270"
    }
    """
    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)


class Metrics(object):
    '''
    This is set up to only deal with *one* type of metric; plos:pdf_view and
    plos:html_views, for example, need different Metrics objects despite being
    from the same provider.
    '''
    def __init__(self, name):

        self.name = name
        self.ignore = False
        self.metric_snaps = {}
        self.latest_snap = None
        
    def add_metric_snap(self, metric_snap):
        '''Stores a MetricSnap object based on a key hashed from its "value" attr.

        When you get the snap back out, you're getting the object, not its attributes.
        Also, note that snaps with identical values get overwritten.
        '''

        hash = hashlib.md5(str(metric_snap.data["value"])).hexdigest()
        self.metric_snaps[hash] = metric_snap
        self.latest_snap = metric_snap
        self.last_modified = time.time()
        return hash
        
    def as_dict(self):
        return self.__dict__
      


# FIXME: should this have a created property?
# FIXME: should things like "can_use_commercially" be true/false rather than the - yes
# string "0" or "1", or are there other values that can go in there
# FIXME: add a validation routine
# FIXME: we need a nicer interface to get at the contents of the inner data object
class MetricSnap(object):
    """
    {
        "id": "Mendeley:readers",
        "value": 16,
        "created": 1233442897.234,
        "last_modified": 1328569492.406,
        "provenance_url": ["http:\/\/api.mendeley.com\/research\/public-chemical-compound-databases\/"],
        "static_meta": {
            "display_name": "readers"
            "provider": "Mendeley",
            "provider_url": "http:\/\/www.mendeley.com\/",
            "description": "Mendeley readers: the number of readers of the article",
            "icon": "http:\/\/www.mendeley.com\/favicon.ico",<commit_msg>Metric.__dict__ mostly works...few more tests to do.<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

class Model(object):

    def __init__(self, dao, id=None, seed=None):
        self.dao = dao
        self.id = None #set it properly below

        if seed:
            for key in seed:
                setattr(self, key, seed[key])
        if id:
            # if the user passes in an id, override the seed id
            self.id = id
        if (self.id is None):
            # if no id in seed or in parameter then mint a new one
            self.id = uuid.uuid4().hex


    def load(self):
        doc = self.dao.get(self.id)
        if doc is None:
            raise(LookupError)
        
        for key in doc:
            setattr(self, key, doc[key])

        return doc

    def save(self):
        doc = self.as_dict()
        # couch wants the underscore...should be fixed in dao, not here.
        doc["_id"] = doc.pop("id")

        try:
            self.dao.update_item(doc, self.id)
        except LookupError:
            self.dao.create_item(doc, self.id)
        return doc

    def keys_from_docstring(self):
        json_doc = inspect.getdoc(self)
        return json.loads(json_doc).keys()

    def as_dict(self):
        doc = {}
        for key in self.keys_from_docstring():
            try:
                temp_val = getattr(self, key)
                try:
                    val = temp_val.as_dict()
                except AttributeError:
                    val = temp_val

                doc[key] = val
                
            except AttributeError:
                doc[key] = None
        return doc

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


# FIXME: no id on the item? this should appear in the alias object?
class Item(Model):
    """{
        "id": "uuid4-goes-here",
        "aliases": "aliases_object",
        "metrics": "metric_object",
        "biblio": "biblio_object",
        "created": 23112412414.234,
        "last_modified": 12414214.234,
        "last_requested": 124141245.234
    }
    """

    def load(self):
        doc = self.dao.get(self.id)
        if doc is None:
            raise(LookupError)
        
        for key in doc:
            setattr(self, key, doc[key])

        setattr(self, "aliases", Aliases(seed=doc["aliases"]))

        return doc
    
    def set_last_requested(self):
        self.last_requested = time.time()
        self.save()


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

        
class Collection(Model):    
    """
    {
        "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": [
            "<NAME>"
        ], 
        "collection": "pitnoid", 
        "volume": "10", 
        "id": "p78", 
        "year": "1978", 
        "pages": "268 to 270"
    }
    """
    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)


class Metrics(object):
    '''
    This is set up to only deal with *one* type of metric; plos:pdf_view and
    plos:html_views, for example, need different Metrics objects despite being
    from the same provider.
    '''
    def __init__(self, name):

        self.name = name
        self.ignore = False
        self.metric_snaps = {}
        self.latest_snap = None
        
    def add_metric_snap(self, metric_snap):
        '''Stores a MetricSnap object based on a key hashed from its "value" attr.

        When you get the snap back out, you're getting the object, not its attributes.
        Also, note that snaps with identical values get overwritten.
        '''

        hash = hashlib.md5(str(metric_snap.data["value"])).hexdigest()
        self.metric_snaps[hash] = metric_snap
        self.latest_snap = metric_snap
        self.last_modified = time.time()
        return hash
        
    
      


# FIXME: should this have a created property?
# FIXME: should things like "can_use_commercially" be true/false rather than the - yes
# string "0" or "1", or are there other values that can go in there
# FIXME: add a validation routine
# FIXME: we need a nicer interface to get at the contents of the inner data object
class MetricSnap(object):
    """
    {
        "id": "Mendeley:readers",
        "value": 16,
        "created": 1233442897.234,
        "last_modified": 1328569492.406,
        "provenance_url": ["http:\/\/api.mendeley.com\/research\/public-chemical-compound-databases\/"],
        "static_meta": {
            "display_name": "readers"
            "provider": "Mendeley",
            "provider_url": "http:\/\/www.mendeley.com\/",
            "description": "Mendeley readers: the number of readers of the article",
            "icon": "http:\/\/www.mendeley.com\/favicon.ico",
{'fraction_non_alphanumeric': 0.11283891547049442, 'fraction_numerical': 0.01802232854864434, 'mean_word_length': 1.917868776174965, 'pattern_counts': {'":': 74, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 4, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3476136', 'n_tokens_mistral': 4069, 'n_tokens_neox': 3825, 'n_words': 1031}