[hooks,sobjects] Move synchronization code from hooks/operations to services
authorDenis Laxalde <denis.laxalde@logilab.fr>
Thu, 22 Jan 2015 16:51:35 +0100
changeset 68 dd318b930f1d
parent 67 64966fde037f
child 70 5ab8c18d8056
[hooks,sobjects] Move synchronization code from hooks/operations to services This will allow the synchronization to be triggered outside hooks (e.g. by hand from a ui action). Closes #4869938.
hooks.py
sobjects.py
--- a/hooks.py	Thu Jan 22 18:07:51 2015 +0100
+++ b/hooks.py	Thu Jan 22 16:51:35 2015 +0100
@@ -16,68 +16,20 @@
 
 """cubicweb-ckanpublish specific hooks and operations"""
 
-from requests.exceptions import RequestException
-
-from cubicweb import ValidationError, role
+from cubicweb import role
 from cubicweb.predicates import adaptable, score_entity, PartialPredicateMixIn
 from cubicweb.server import hook
 
-from cubes.ckanpublish.utils import (ckan_post, CKANPostError,
-                                     ckan_instance_configured)
-
-
-def _ckan_action(config, eid, action, **kwargs):
-    """Run `ckan_post` and eventually raise ValidationError."""
-    try:
-        return ckan_post(config, action, **kwargs)
-    except (CKANPostError, RequestException) as exc:
-        raise ValidationError(eid, {'ckan_dataset_id': unicode(exc)})
-
-
-def create_dataset(config, eid, data):
-    """Create a CKAN dataset and set `ckan_dataset_id` attribute or
-    respective entity. Return the dataset id.
-    """
-    res = _ckan_action(config, eid, 'package_create', data=data)
-    return res['id']
-
+from cubes.ckanpublish.sobjects import (delete_dataset,
+                                        delete_dataset_resource)
+from cubes.ckanpublish.utils import ckan_instance_configured
 
-def update_dataset(config, eid, datasetid, udata):
-    """Update an existing CKAN dataset"""
-    data = _ckan_action(config, eid, 'package_show', data={'id': datasetid})
-    data.update(udata)
-    _ckan_action(config, eid, 'package_update', data=data)
-
-
-def delete_dataset(config, eid, datasetid):
-    """Delete a CKAN dataset"""
-    _ckan_action(config, eid, 'package_delete', data={'id': datasetid})
-
-
-def create_dataset_resource(config, eid, datasetid, metadata, data):
-    """Add a resource to an existing CKAN dataset"""
-    metadata['package_id'] = datasetid
-    res = _ckan_action(config, eid, 'resource_create', data=metadata,
-                       files=[('upload', data)])
-    return res['id']
-
-
-def update_dataset_resource(config, eid, resourceid, metadata, data):
-    """Update an existing CKAN resource."""
-    metadata['id'] = resourceid
-    _ckan_action(config, eid, 'resource_update', data=metadata,
-                 files=[('upload', data)])
-
-
-def delete_dataset_resource(config, eid, resourceid):
-    """Delete a CKAN resource"""
-    _ckan_action(config, eid, 'resource_delete', data={'id': resourceid})
 
 
 class DeleteCKANDataSetHook(hook.Hook):
     """Delete CKAN dataset upon deletion of the corresponding entity"""
     __regid__ = 'ckanpublish.delete-ckan-dataset'
-    __select__ = (hook.Hook.__select__ & ckan_instance_configured &
+    __select__ = (hook.Hook.__select__ & ckan_instance_configured() &
                   adaptable('ICKANDataset') &
                   score_entity(lambda x: x.ckan_dataset_id))
     events = ('before_delete_entity', )
@@ -90,7 +42,7 @@
 class AddOrUpdateCKANDataSetHook(hook.Hook):
     """Add or update a CKAN dataset upon addition or update of an entity"""
     __regid__ = 'ckanpublish.add-update-ckan-dataset'
-    __select__ = (hook.Hook.__select__ & ckan_instance_configured &
+    __select__ = (hook.Hook.__select__ & ckan_instance_configured() &
                   adaptable('ICKANDataset'))
     events = ('after_add_entity', 'after_update_entity', )
     category = 'ckanpublish.dataset'
@@ -104,32 +56,18 @@
 
     def precommit_event(self):
         for eid in self.get_data():
-            entity = self.cnx.entity_from_eid(eid)
-            datasetid = entity.ckan_dataset_id
-            config = self.cnx.vreg.config
             if self.cnx.deleted_in_transaction(eid):
-                delete_dataset(config, eid, datasetid)
+                datasetid = self.cnx.entity_from_eid(eid).ckan_dataset_id
+                delete_dataset(self.cnx.vreg.config, eid, datasetid)
                 self.info('deleted CKAN dataset %s', datasetid)
             else:
-                cpublish = entity.cw_adapt_to('ICKANDataset')
-                data = cpublish.ckan_data()
-                if datasetid is not None:
-                    update_dataset(config, eid, datasetid, data)
-                    self.info('updated %s fields in CKAN dataset %s',
-                              data.keys(), datasetid)
-                else:
-                    datasetid = create_dataset(config, eid, data)
-                    with self.cnx.allow_all_hooks_but('ckanpublish.dataset'):
-                        self.cnx.execute(
-                            'SET X ckan_dataset_id %(dsid)s WHERE X eid %(eid)s',
-                            {'eid': eid, 'dsid': datasetid})
-                    self.info('created CKAN dataset %s', datasetid)
+                self.cnx.call_service('ckanpublish.sync_dataset', eid=eid)
 
 
 class DeleteCKANResourceHook(hook.Hook):
     """Delete CKAN resource upon deletion of the corresponding entity"""
     __regid__ = 'ckanpublish.delete-ckan-resource'
-    __select__ = (hook.Hook.__select__ & ckan_instance_configured &
+    __select__ = (hook.Hook.__select__ & ckan_instance_configured() &
                   adaptable('ICKANResource') &
                   score_entity(lambda x: x.ckan_resource_id))
     category = 'ckanpublish.resource'
@@ -161,7 +99,7 @@
     Actual implementations should at least fill the `rtype` attribute.
     """
     __regid__ = 'ckanpublish.link-ckan-resource-to-ckan-dataset'
-    __select__ = (hook.Hook.__select__ & ckan_instance_configured &
+    __select__ = (hook.Hook.__select__ & ckan_instance_configured() &
                   partial_match_rtype())
     __abstract__ = True
     category = 'ckanpublish.resource'
@@ -179,7 +117,7 @@
 class UpdateCKANResourceHook(hook.Hook):
     """Update a CKAN resource upon update of an resource-like entity"""
     __regid__ = 'ckanpublish.update-ckan-resource'
-    __select__ = (hook.Hook.__select__ & ckan_instance_configured &
+    __select__ = (hook.Hook.__select__ & ckan_instance_configured() &
                   adaptable('ICKANResource'))
     category = 'ckanpublish.resource'
     events = ('after_update_entity', )
@@ -193,31 +131,9 @@
 
     def precommit_event(self):
         for eid in self.get_data():
-            entity = self.cnx.entity_from_eid(eid)
-            resourceid = entity.ckan_resource_id
-            iresource = entity.cw_adapt_to('ICKANResource')
-            config = self.cnx.vreg.config
+            resourceid = self.cnx.entity_from_eid(eid).ckan_resource_id
             if self.cnx.deleted_in_transaction(eid) and resourceid is not None:
-                delete_dataset_resource(config, eid, resourceid)
+                delete_dataset_resource(self.cnx.vreg.config, eid, resourceid)
                 self.info('deleted resource %s', resourceid)
             else:
-                metadata = iresource.ckan_metadata()
-                data = iresource.read()
-                if resourceid is None:
-                    dataset = iresource.dataset
-                    assert dataset, 'no dataset for resource #%d' % eid
-                    if not dataset.ckan_dataset_id:
-                        self.error('skipping resource #%d as its dataset %#d is '
-                                   'not in the CKAN instance', eid, dataset.eid)
-                        continue
-                    resourceid = create_dataset_resource(
-                        config, eid, dataset.ckan_dataset_id, metadata, data)
-                    with self.cnx.allow_all_hooks_but('ckanpublish.resource'):
-                        self.cnx.execute(
-                            'SET X ckan_resource_id %(rid)s WHERE X eid %(eid)s',
-                            {'eid': eid, 'rid': resourceid})
-                    self.info('added resource %s', resourceid)
-                else:
-                    update_dataset_resource(
-                        config, eid, resourceid, metadata, data)
-                    self.info('updated resource %s', resourceid)
+                self.cnx.call_service('ckanpublish.sync_resource', eid=eid)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sobjects.py	Thu Jan 22 16:51:35 2015 +0100
@@ -0,0 +1,135 @@
+# copyright 2015 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
+# contact http://www.logilab.fr -- mailto:contact@logilab.fr
+#
+# This program is free software: you can redistribute it and/or modify it under
+# the terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation, either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# This program 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 Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License along
+# with this program. If not, see <http://www.gnu.org/licenses/>.
+
+"""cubicweb-ckanpublish server objects"""
+
+from requests.exceptions import RequestException
+
+from cubicweb import ValidationError
+from cubicweb.server import Service
+from cubicweb.predicates import adaptable
+
+from cubes.ckanpublish.utils import (ckan_instance_configured, ckan_post,
+                                     CKANPostError)
+
+
+def _ckan_action(config, eid, action, **kwargs):
+    """Run `ckan_post` and eventually raise ValidationError."""
+    try:
+        return ckan_post(config, action, **kwargs)
+    except (CKANPostError, RequestException) as exc:
+        raise ValidationError(eid, {'ckan_dataset_id': unicode(exc)})
+
+
+def create_dataset(config, eid, data):
+    """Create a CKAN dataset and set `ckan_dataset_id` attribute or
+    respective entity. Return the dataset id.
+    """
+    res = _ckan_action(config, eid, 'package_create', data=data)
+    return res['id']
+
+
+def update_dataset(config, eid, datasetid, udata):
+    """Update an existing CKAN dataset"""
+    data = _ckan_action(config, eid, 'package_show', data={'id': datasetid})
+    data.update(udata)
+    _ckan_action(config, eid, 'package_update', data=data)
+
+
+def delete_dataset(config, eid, datasetid):
+    """Delete a CKAN dataset"""
+    _ckan_action(config, eid, 'package_delete', data={'id': datasetid})
+
+
+def create_dataset_resource(config, eid, datasetid, metadata, data):
+    """Add a resource to an existing CKAN dataset"""
+    metadata['package_id'] = datasetid
+    res = _ckan_action(config, eid, 'resource_create', data=metadata,
+                       files=[('upload', data)])
+    return res['id']
+
+
+def update_dataset_resource(config, eid, resourceid, metadata, data):
+    """Update an existing CKAN resource."""
+    metadata['id'] = resourceid
+    _ckan_action(config, eid, 'resource_update', data=metadata,
+                 files=[('upload', data)])
+
+
+def delete_dataset_resource(config, eid, resourceid):
+    """Delete a CKAN resource"""
+    _ckan_action(config, eid, 'resource_delete', data={'id': resourceid})
+
+
+class SyncCKANDataset(Service):
+    """Service for synchronization of a "dataset-like" entity to a CKAN
+    instance.
+    """
+    __regid__ = 'ckanpublish.sync_dataset'
+    __select__ = ckan_instance_configured()
+
+    def call(self, eid, **kwargs):
+        """Create or update a CKAN dataset using dataset-like entity eid"""
+        entity = self._cw.entity_from_eid(eid)
+        datasetid = entity.ckan_dataset_id
+        data = entity.cw_adapt_to('ICKANDataset').ckan_data()
+        config = self._cw.vreg.config
+        if datasetid is not None:
+            update_dataset(config, eid, datasetid, data)
+            self.info('updated %s fields in CKAN dataset %s',
+                      data.keys(), datasetid)
+        else:
+            datasetid = create_dataset(config, eid, data)
+            with self._cw.allow_all_hooks_but('ckanpublish.dataset'):
+                self._cw.execute(
+                    'SET X ckan_dataset_id %(dsid)s WHERE X eid %(eid)s',
+                    {'eid': eid, 'dsid': datasetid})
+            self.info('created CKAN dataset %s', datasetid)
+
+
+class SyncCKANResource(Service):
+    """Service for synchronization of a "resource-like" entity to a CKAN
+    instance.
+    """
+    __regid__ = 'ckanpublish.sync_resource'
+    __select__ = ckan_instance_configured()
+
+    def call(self, eid, **kwargs):
+        """Create or update a CKAN resource using resource-like entity eid"""
+        entity = self._cw.entity_from_eid(eid)
+        resourceid = entity.ckan_resource_id
+        iresource = entity.cw_adapt_to('ICKANResource')
+        config = self._cw.vreg.config
+        metadata = iresource.ckan_metadata()
+        data = iresource.read()
+        if resourceid is None:
+            dataset = iresource.dataset
+            assert dataset, 'no dataset for resource #%d' % eid
+            if not dataset.ckan_dataset_id:
+                self.error('skipping resource #%d as its dataset %#d is '
+                           'not in the CKAN instance', eid, dataset.eid)
+                return
+            resourceid = create_dataset_resource(
+                config, eid, dataset.ckan_dataset_id, metadata, data)
+            with self._cw.allow_all_hooks_but('ckanpublish.resource'):
+                self._cw.execute(
+                    'SET X ckan_resource_id %(rid)s WHERE X eid %(eid)s',
+                    {'eid': eid, 'rid': resourceid})
+            self.info('added resource %s', resourceid)
+        else:
+            update_dataset_resource(
+                config, eid, resourceid, metadata, data)
+            self.info('updated resource %s', resourceid)