Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
Signed-off-by: Alexander Ioannidis <a.ioannidis@cern.ch>
  • Loading branch information
slint committed Oct 6, 2017
1 parent fe6036e commit 32f2edc
Show file tree
Hide file tree
Showing 12 changed files with 481 additions and 8 deletions.
6 changes: 6 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@
'zenodo_xrootd = zenodo.modules.xrootd.ext:ZenodoXRootD',
'zenodo_jsonschemas = '
'zenodo.modules.jsonschemas.ext:ZenodoJSONSchemas',
'zenodo_webhooks = zenodo.modules.webhooks.ext:ZenodoWebhooks',
'flask_debugtoolbar = flask_debugtoolbar:DebugToolbarExtension',
],
'invenio_base.api_apps': [
Expand All @@ -204,6 +205,7 @@
'zenodo.modules.communities.ext:ZenodoCommunities',
'zenodo_deposit = zenodo.modules.deposit.ext:ZenodoDeposit',
'zenodo_records = zenodo.modules.records.ext:ZenodoRecords',
'zenodo_webhooks = zenodo.modules.webhooks.ext:ZenodoWebhooks',
'zenodo_xrootd = zenodo.modules.xrootd.ext:ZenodoXRootD',
],
'invenio_base.blueprints': [
Expand Down Expand Up @@ -232,6 +234,7 @@
'zenodo_records = zenodo.modules.records.tasks',
'zenodo_utils = zenodo.modules.utils.tasks',
'zenodo_sipstore = zenodo.modules.sipstore.tasks',
'zenodo_webhooks = zenodo.modules.webhooks.tasks',
],
'invenio_pidstore.minters': [
'zenodo_record_minter '
Expand Down Expand Up @@ -261,6 +264,9 @@
'records = zenodo.modules.records.mappings',
'deposits = zenodo.modules.deposit.mappings',
],
'invenio_webhooks.receivers': [
'ads = zenodo.modules.webhooks.receivers:ADSReceiver',
],
'dojson.contrib.to_marc21': [
'zenodo = zenodo.modules.records.serializers.to_marc21.rules',
],
Expand Down
3 changes: 3 additions & 0 deletions zenodo/modules/deposit/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@

ZENODO_MAX_FILE_SIZE = ZENODO_BUCKET_QUOTA_SIZE
"""Maximum file size accepted."""

ZENODO_RECORD_PUBLISH_WEBHOOKS_ENABLED = True
"""Publish webhooks on record publication."""
105 changes: 105 additions & 0 deletions zenodo/modules/deposit/events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# -*- coding: utf-8 -*-
#
# This file is part of Zenodo.
# Copyright (C) 2016 CERN.
#
# Zenodo 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 2 of the
# License, or (at your option) any later version.
#
# Zenodo 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 Zenodo; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307, USA.
#
# 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.

"""Webhook events for Zenodo Deposit."""

from dictdiffer import diff
from zenodo.modules.webhooks.api import Event
from zenodo.modules.webhooks.tasks import publish_event


def is_citation(rel_id):
return rel_id.get('relation') == 'cites'


def generate_record_publish_events(recid, record, depid, deposit):
"""Generate all relevant events for record publishing."""
first_publish = (deposit.get('_deposit', {}).get('pid', {})
.get('revision_id')) == 0
if first_publish:
# Send "record"
pass

# If this is not the first revision, detect diffs
if record.revision_id > 0:
# Let's examine "cites" only
old_revision = record.revisions[-1]
old_relids = list(filter(
is_citation, old_revision.get('related_identifiers', [])))
new_relids = list(filter(
is_citation, record.get('related_identifiers', [])))
relids_diff = list(diff(old_relids, new_relids))

# Sample data....
old_relids = [
{
"identifier": "10.5281/zenodo.33333",
"relation": "cites",
"scheme": "doi"
}
]
new_relids = [
{
"identifier": "10.5281/zenodo.33333",
"relation": "cites",
"scheme": "doi"
},
# New 'cites'
{
"identifier": "10.5281/zenodo.55555",
"relation": "cites",
"scheme": "doi"
},
# Not a new 'cites'
{
"identifier": "10.5281/zenodo.99999",
"relation": "isCitedBy",
"scheme": "doi"
}
]

new_cites = [
{
"identifier": "10.5281/zenodo.55555",
"relation": "cites",
"scheme": "doi"
},
]

subject_pid = {
"identifier": "10.5281/zenodo.11111",
"relation": "cites",
"scheme": "doi"
}

for pid in new_cites:
source = 'Zenodo.Curation'
event_type = 'record-relation'
payload = {
'action': 'added',
'relation_type': 'cites',
'subject_pid': subject_pid,
'object_pid': pid,
}
publish_event.delay(event_type, source, payload)
19 changes: 15 additions & 4 deletions zenodo/modules/deposit/receivers.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# This file is part of Zenodo.
# Copyright (C) 2016 CERN.
#
# Invenio is free software; you can redistribute it
# Zenodo 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 2 of the
# License, or (at your option) any later version.
#
# Invenio is distributed in the hope that it will be
# Zenodo 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 Invenio; if not, write to the
# along with Zenodo; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307, USA.
#
Expand All @@ -32,6 +32,7 @@
from zenodo.modules.deposit.tasks import datacite_register
from zenodo.modules.openaire.tasks import openaire_direct_index
from zenodo.modules.sipstore.tasks import archive_sip
from .events import generate_record_publish_events


def datacite_register_after_publish(sender, action=None, pid=None,
Expand Down Expand Up @@ -65,3 +66,13 @@ def sipstore_write_files_after_publish(sender, action=None, pid=None,
.first().sip
)
archive_sip.delay(str(sip.id))


def publish_relations_webhook_events(sender, action=None, pid=None,
deposit=None):
"""Publish webhook events for updated relations."""
if action == 'publish' and \
current_app.config['ZENODO_RECORD_PUBLISH_WEBHOOKS_ENABLED']:
recid, record = deposit.fetch_published()
generate_record_publish_events(
recid, record, depid=pid, deposit=deposit)
8 changes: 4 additions & 4 deletions zenodo/modules/deposit/tasks.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# -*- coding: utf-8 -*-
#
# This file is part of Invenio.
# This file is part of Zenodo.
# Copyright (C) 2016 CERN.
#
# Invenio is free software; you can redistribute it
# Zenodo 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 2 of the
# License, or (at your option) any later version.
#
# Invenio is distributed in the hope that it will be
# Zenodo 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 Invenio; if not, write to the
# along with Zenodo; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307, USA.
#
Expand Down
30 changes: 30 additions & 0 deletions zenodo/modules/webhooks/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
#
# This file is part of Zenodo.
# Copyright (C) 2017 CERN.
#
# Zenodo 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 2 of the
# License, or (at your option) any later version.
#
# Zenodo 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 Zenodo; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307, USA.
#
# 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.

"""Zenodo webhooks module."""

from .ext import ZenodoWebhooks
from .proxies import current_zenodo_webhooks

__all__ = ('ZenodoWebhooks', 'current_zenodo_webhooks',)
102 changes: 102 additions & 0 deletions zenodo/modules/webhooks/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# -*- coding: utf-8 -*-
#
# This file is part of Zenodo.
# Copyright (C) 2017 CERN.
#
# Zenodo 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 2 of the
# License, or (at your option) any later version.
#
# Zenodo 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 Zenodo; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307, USA.
#
# 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.

"""Zenodo Webhooks API."""

from __future__ import absolute_import, print_function, unicode_literals

import re
import time
import uuid

import requests
from flask import current_app

from .proxies import current_zenodo_webhooks


class Event:
"""Webhook event object."""

def __init__(self, event_type, source, payload, event_id=None,
timestamp=None):
"""Event initialization."""
self.event_id = event_id or uuid.uuid4()
self.timestamp = timestamp or time.time()
self.event_type = event_type
self.source = source
self.payload = payload

@classmethod
def match_subscribers(cls, event_type):
"""Return subscribers that match the event."""
matched_subscribers = []
for subscriber in current_zenodo_webhooks.subscribers:
# NOTE: Events are usually something like 'relations.citations', or
# 'relations.*'. The fact that there is a match-any-char-dot ('.'),
# won't be an issue.
try:
if re.match(subscriber['event'], event_type):
matched_subscribers.append(subscriber)
except re.error:
current_app.logger.warning(
'Invalid subscriber event regex: {}'.format(subscriber))
return matched_subscribers

@property
def request_body(self):
"""Return body for an event request."""
return {
'id': str(self.event_id),
'time': self.time,
'event_type': self.event_type,
'payload': self.payload,
'source': self.source,
}

@property
def request_headers(self):
"""Return headers for an event request."""
# NOTE: Inspired from GitHub
return {
'X-Zenodo-Event': self.event_type,
# TODO: Include HMAC signature of the body
# 'X-Hub-Signature': sign(body),
'X-Zenodo-Delivery': self.event_id,
}

def send(self, subscriber, sign=False):
"""Send event request to a subscriber."""
assert subscriber['content_type'] == 'application/json'
if sign:
# TODO: Include HMAC signature of the body using
# subscriber['secret']
pass
res = requests.post(
subscriber['url'],
json=self.request_body,
headers=self.request_headers)
if not res.ok:
raise Exception(
'Webhook delivery bad response {}'.format(res.text))
41 changes: 41 additions & 0 deletions zenodo/modules/webhooks/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
#
# This file is part of Zenodo.
# Copyright (C) 2017 CERN.
#
# Zenodo 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 2 of the
# License, or (at your option) any later version.
#
# Zenodo 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 Zenodo; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307, USA.
#
# 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.

"""Configuration for Zenodo webhooks."""

from __future__ import absolute_import, print_function, unicode_literals


ZENODO_WEBHOOKS_SUBSCRIBERS = [
{
'id': 'ads',
'event': 'relations.*',
# Maybe some header is required?
# eg. https://httpbin.org/response-headers?X-Hub-ID=something
'url': 'https://httpbin.org/status/202',
'content_type': 'application/json',
'secret': 'ads',
}
]
"""Statically loaded webhook subscribers."""
Loading

0 comments on commit 32f2edc

Please sign in to comment.