Source code for inspirehep.modules.orcid.cache
# -*- coding: utf-8 -*-
#
# This file is part of INSPIRE.
# Copyright (C) 2018 CERN.
#
# INSPIRE is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# INSPIRE is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with INSPIRE. If not, see <http://www.gnu.org/licenses/>.
#
# In applying this license, CERN does not waive the privileges and immunities
# granted to it by virtue of its status as an Intergovernmental Organization
# or submit itself to any jurisdiction.
from __future__ import absolute_import, division, print_function
import hashlib
from StringIO import StringIO
import flask
from flask import current_app as app
from redis import StrictRedis
from time_execution import time_execution
from .converter import OrcidConverter
CACHE_PREFIX = None
[docs]class OrcidCache(object):
def __init__(self, orcid, recid):
"""
Orcid cached data.
Args:
orcid (string): orcid identifier.
"""
self.orcid = orcid
self.recid = recid
self._cached_hash_value = None
self._new_hash_value = None
@property
def redis(self):
redis = getattr(flask.g, 'redis_client', None)
if redis is None:
url = app.config.get('CACHE_REDIS_URL')
redis = StrictRedis.from_url(url)
flask.g.redis_client = redis
return redis
@property
def _key(self):
"""Return the string '`CACHE_PREFIX`:orcidcache:`orcid_value`:`recid`'"""
prefix = ''
if CACHE_PREFIX:
prefix = '{}:'.format(CACHE_PREFIX)
return '{}orcidcache:{}:{}'.format(prefix, self.orcid, self.recid)
@time_execution
[docs] def write_work_putcode(self, putcode, inspire_record=None):
"""
Write the putcode and the hash for the given (orcid, recid).
Args:
putcode (string): the putcode used to push the record to ORCID.
inspire_record (InspireRecord): InspireRecord instance. If provided,
the hash for the record content is re-computed.
Raises:
ValueError: when the putcode is empty.
"""
if not putcode:
raise ValueError('Empty putcode not allowed')
data = {'putcode': putcode}
if inspire_record:
if not self._new_hash_value:
self._new_hash_value = _OrcidHasher(inspire_record).compute_hash()
data['hash'] = self._new_hash_value
self.redis.hmset(self._key, data)
@time_execution
[docs] def read_work_putcode(self):
"""Read the putcode for the given (orcid, recid)."""
value = self.redis.hgetall(self._key)
self._cached_hash_value = value.get('hash')
return value.get('putcode')
@time_execution
[docs] def delete_work_putcode(self):
"""Delete the putcode for the given (orcid, recid)."""
return self.redis.delete(self._key)
@time_execution
[docs] def has_work_content_changed(self, inspire_record):
"""
True if the work content has changed compared to the cached version.
Args:
inspire_record (InspireRecord): InspireRecord instance. If provided,
the hash for the record content is re-computed.
"""
if not self._cached_hash_value:
self.read_work_putcode()
if not self._new_hash_value:
self._new_hash_value = _OrcidHasher(inspire_record).compute_hash()
return self._cached_hash_value != self._new_hash_value
class _OrcidHasher(object):
def __init__(self, inspire_record):
self.inspire_record = inspire_record
def compute_hash(self):
"""Generate hash for an ORCID-serialised HEP record.
Return:
string: hash of the record
"""
orcid_record = OrcidConverter(
self.inspire_record,
app.config['LEGACY_RECORD_URL_PATTERN']
)
xml = orcid_record.get_xml() # lxml.etree._Element
return self._hash_xml_element(xml)
@classmethod
def _hash_xml_element(cls, element):
"""Compute a hash for XML element comparison.
Args:
element (lxml.etree._Element): the XML node.
Return:
string: hash
"""
canonical_string = cls._canonicalize_xml_element(element)
hash_value = hashlib.sha1(canonical_string)
return 'sha1:' + hash_value.hexdigest()
@staticmethod
def _canonicalize_xml_element(element):
"""Return a string with a canonical representation of the element.
Args:
element (lxml.etree._Element): the XML node
Return:
string: canonical representation
"""
element_tree = element.getroottree()
output_stream = StringIO()
element_tree.write_c14n(
output_stream,
with_comments=False,
exclusive=True,
)
return output_stream.getvalue()