RESTify apycotlib
authorJulien Cristau <julien.cristau@logilab.fr>
Fri, 25 Apr 2014 15:56:11 +0200
changeset 1419 04b8e8af2e66
parent 1007 0f8e2403d4d4
child 1420 788f69fcc2e2
RESTify apycotlib
_apycotlib/atest.py
_apycotlib/narvalactions.py
_apycotlib/repositories.py
_apycotlib/writer.py
entities.py
hooks.py
views/rest.py
--- a/_apycotlib/atest.py	Thu May 02 12:40:58 2013 +0200
+++ b/_apycotlib/atest.py	Fri Apr 25 15:56:11 2014 +0200
@@ -57,12 +57,13 @@
             sys.path.insert(0, clean_path(path))
 
 
+from narvalbot.engine import options_dict
 
 class Test(object):
     """the single source unit test class"""
 
     def __init__(self, texec, writer):
-        options = texec.options_dict()
+        options = options_dict(texec['options'])
         # directory where the test environment will be built
         self.tmpdir = tempfile.mkdtemp(dir=options.get('test_dir'))
         # notify some subprocesses they're executed by apycot through an
@@ -70,16 +71,16 @@
         os.environ['APYCOT_ROOT'] = self.tmpdir
         # test config / tested project environment
         self.texec = texec
-        self.tconfig = texec.configuration
-        self.environment = texec.environment
+        self.tconfig = texec['configuration']
+        self.environment = texec['environment']
         # IWriter object
         self.writer = writer
         # local caches
         self._configs = {}
         self._repositories = {}
         # environment variables as a dictionary
-        self.environ = self.tconfig.apycot_process_environment()
-        self.environ.update(self.environment.apycot_process_environment())
+        self.environ = self.tconfig['apycot_process_environment']
+        self.environ.update(self.environment['apycot_process_environment'])
         self.environ.setdefault('LC_ALL', 'fr_FR.UTF-8') # XXX force utf-8
         self._substitute(self.environment, self.environ)
         # Track environment change to be able to restore it later.
@@ -100,7 +101,7 @@
 
     def _substitute(self, pe, configdict):
         substitute_dict(configdict,
-                        {'NAME': pe.name, 'TESTDIR': self.tmpdir,
+                        {'NAME': pe['name'], 'TESTDIR': self.tmpdir,
                          'SRCDIR': self.apycot_repository(pe).co_path})
 
     # resource accessors #######################################################
@@ -109,10 +110,12 @@
         if pe is None:
             pe = self.environment
         try:
-            return self._configs[pe.eid]
+            return self._configs[pe['eid']]
         except KeyError:
-            config = self.tconfig.apycot_configuration(pe)
-            self._configs[pe.eid] = config
+            config = self.writer._cnxh.http_post(self.tconfig['cwuri'],
+                                                 vid='apycot.get_configuration',
+                                                 environment=pe['eid'])[0]
+            self._configs[pe['eid']] = config
             self._substitute(pe, config)
             return config
 
@@ -120,22 +123,22 @@
         if pe is None:
             pe = self.environment
         try:
-            return self._repositories[pe.eid]
+            return self._repositories[pe['eid']]
         except KeyError:
             from apycotlib.repositories import get_repository
-            if not pe.repository:
-                raise Exception('Project environment %s has no repository' % pe.dc_title())
-            repdef = {'repository': pe.repository,
-                      'path': pe.vcs_path,
-                      'branch': self.texec.branch}
+            if not pe['repository']:
+                raise Exception('Project environment %s has no repository' % pe['title'])
+            repdef = {'repository': pe['repository'],
+                      'path': pe['vcs_path'],
+                      'branch': self.texec['branch']}
             # don't overwrite branch hardcoded on the environment: have to be
             # done here, not only in when starting plan (eg in entities.py)
             # since project env may not be the tested project env
-            pecfg = pe.apycot_configuration()
+            pecfg = pe['apycot_configuration']
             if pecfg.get('branch'):
                 repdef['branch'] = pecfg['branch']
             apyrep = get_repository(repdef)
-            self._repositories[pe.eid] = apyrep
+            self._repositories[pe['eid']] = apyrep
             return apyrep
 
     def project_path(self, subpath=False):
@@ -153,7 +156,7 @@
         # setup environment variables
         if self.environ:
             for key, val in self.environ.iteritems():
-                self.update_env(self.tconfig.name, key, val)
+                self.update_env(self.tconfig['name'], key, val)
 
     def clean(self):
         """clean the test environment"""
@@ -233,7 +236,7 @@
             Command(self.writer, movebranchcmd, shell=True,
                     cwd=self.tmpdir).run()
         self.writer.link_to_revision(pe, vcsrepo)
-        self.writer.refresh_log(True)
+        self.writer.refresh_log()
 
     def call_preprocessor(self, pptype, penv):
         cfg = self.apycot_config(penv)
@@ -263,7 +266,7 @@
 
     def run_checker(self, id, nonexecuted=False, **kwargs):
         """run all checks in the test environment"""
-        options = self.texec.options_dict()
+        options = options_dict(self.texec['options'])
         options.update(kwargs)
         self._substitute(self.environment, options)
         check_writer = self.writer.make_check_writer()
@@ -302,6 +305,5 @@
             check_writer.end(status)
             #globstatus = min(globstatus, status)
             self.writer.execution_info('%s [%s]', checker.id, status)
-            with self.writer._lock:
-                self.global_status = min(self.global_status, status)
+            self.global_status = min(self.global_status, status)
         return checker, status
--- a/_apycotlib/narvalactions.py	Thu May 02 12:40:58 2013 +0200
+++ b/_apycotlib/narvalactions.py	Fri Apr 25 15:56:11 2014 +0200
@@ -7,8 +7,8 @@
         self.plan = plan
 
     def __enter__(self):
-        w  = writer.TestDataWriter(self.plan.cnxh, self.plan.cwplan.eid)
-        test = atest.Test(self.plan.cwplan, w)
+        w  = writer.TestDataWriter(self.plan.cnxh, self.plan.plandata['cwuri'])
+        test = atest.Test(self.plan.plandata, w)
         test.setup()
         self.test = test
         return test
@@ -20,8 +20,7 @@
 
 
 def install_environment(test):
-    tconfig = test.tconfig
-    environment = test.environment
-    for dep in tconfig.dependencies(environment) + [environment]:
+    data = test.writer._cnxh.http_post(test.texec['cwuri'], vid='apycot.get_dependencies')
+    for dep in data[0]:
         test.checkout(dep)
         test.call_preprocessor('install', dep)
--- a/_apycotlib/repositories.py	Thu May 02 12:40:58 2013 +0200
+++ b/_apycotlib/repositories.py	Fri Apr 25 15:56:11 2014 +0200
@@ -22,7 +22,7 @@
     """factory method: return a repository implementation according to
     <attrs> (a dictionary)
     """
-    repo_type = attrs['repository'].type
+    repo_type = attrs['repository']['type']
     assert repo_type in SUPPORTED_REPO_TYPES, repo_type
     return get_registered('repository', repo_type)(attrs)
 
@@ -63,7 +63,7 @@
 
     def __repr__(self):
         """get a string synthetizing the location"""
-        myrepr = '%s:%s' % (self.id, self.repository.source_url or self.repository.path)
+        myrepr = '%s:%s' % (self.id, self.repository['source_url'] or self.repository['path'])
         if self.path:
             myrepr += '/' + self.path
         if self.branch:
@@ -111,7 +111,7 @@
     id = 'subversion'
 
     def _ref_repo(self):
-        return self.repository.source_url
+        return self.repository['source_url']
 
     def _co_path(self):
         """return the path where the project will be located in the test
@@ -148,15 +148,15 @@
     default_branch = "default"
 
     def _ref_repo(self):
-        if self.repository.local_cache:
+        if False and self.repository['local_cache']:
             cnx = self.repository._cw.cnx
             cacheroot = cnx.get_option_value('local-repo-cache-root')
             path = osp.join(cacheroot, self.repository.local_cache)
             if osp.exists(path):
                 return path
-        if self.repository.path and osp.exists(self.repository.path):
-            return self.repository.path
-        return self.repository.source_url
+        if self.repository['path'] and osp.exists(self.repository['path']):
+            return self.repository['path']
+        return self.repository['source_url']
 
     def _co_path(self):
         """return the path where the project will be located in the test
--- a/_apycotlib/writer.py	Thu May 02 12:40:58 2013 +0200
+++ b/_apycotlib/writer.py	Fri Apr 25 15:56:11 2014 +0200
@@ -104,13 +104,12 @@
     a CubicWeb instance (using the apycot cube)
     """
 
-    def __init__(self, cnxh, target_eid):
+    def __init__(self, cnxh, target_url):
         self._cnxh = cnxh
         # eid of the execution entity
-        self._eid = target_eid
+        self._url = target_url
         self._logs = []
         self._logs_sent = 0
-        self._lock = RLock()
 
     def start(self):
         pass
@@ -119,11 +118,7 @@
         pass
 
     def set_exec_status(self, status):
-        with self._lock:
-            self._cnxh.execute(
-                'SET X status %(status)s WHERE X eid %(x)s',
-                {'status': status, 'x': self._eid})
-            self._cnxh.commit()
+        self._cnxh.http_post(self._url, vid='set_attributes', status=status)
 
     def execution_info(self, *args, **kwargs):
         msg = self._msg_info(*args, **kwargs)[-1]
@@ -137,26 +132,21 @@
                                                xml_escape(msg))
         self._logs.append(encodedmsg)
 
-    def raw(self, name, value, type=None, commit=True):
+    def raw(self, name, value, type=None):
         """give some raw data"""
-        with self._lock:
-            self._cnxh.cw.create_entity(
-                'CheckResultInfo', label=self._unicode(name),
-                value=self._unicode(value), type=type and unicode(type),
-                for_check=self._cnxh.cw.entity_from_eid(self._eid))
-            if commit:
-                self._cnxh.commit()
+        self._cnxh.http_post(self._url, vid='create_subentity',
+                             __cwetype__='CheckResultInfo',
+                             __cwrel__='for_check',
+                             label=self._unicode(name),
+                             value=self._unicode(value),
+                             type=type and unicode(type))
 
-    def refresh_log(self, flush=True):
+    def refresh_log(self):
         log = self._logs
-        with self._lock:
-            if self._logs_sent < len(log):
-                self._cnxh.execute(
-                    'SET X log %(log)s WHERE X eid %(x)s',
-                    {'log': u'\n'.join(log), 'x': self._eid})
-                self._log_sent = len(log)
-            if flush:
-                self._cnxh.commit()
+        if self._logs_sent < len(log):
+            self._cnxh.http_post(self._url, vid='set_attributes',
+                                 log=u'\n'.join(log))
+            self._log_sent = len(log)
 
 
 class CheckDataWriter(BaseDataWriter):
@@ -164,31 +154,26 @@
 
     def start(self, checker):
         """Register the given checker as started"""
-        with self._lock:
-            crname = getattr(checker, 'id', checker) # may be the checked id
-            self._eid = self._cnxh.cw.create_entity(
-                'CheckResult', name=self._unicode(crname), status=u'processing',
-                starttime=datetime.now(),
-                during_execution=self._cnxh.cw.entity_from_eid(self._eid)).eid
-            if hasattr(checker, 'options'):
-                options = ['%s=%s' % (k, v) for k, v in checker.options.iteritems()
-                           if k in checker.options_def
-                           and v != checker.options_def[k].get('default')]
-                if options:
-                    self.info('\n'.join(options))
-                    self.refresh_log(flush=False)
-            self._cnxh.commit()
+        crname = getattr(checker, 'id', checker) # may be the checked id
+        data = self._cnxh.http_post(self._url, vid='create_subentity',
+                                    __cwetype__='CheckResult',
+                                    __cwrel__='during_execution',
+                                    name=self._unicode(crname), status=u'processing',
+                                    starttime=datetime.now())
+        self._url = data[0]['cwuri']
+        if hasattr(checker, 'options'):
+            options = ['%s=%s' % (k, v) for k, v in checker.options.iteritems()
+                       if k in checker.options_def
+                       and v != checker.options_def[k].get('default')]
+            if options:
+                self.info('\n'.join(options))
+                self.refresh_log()
 
     def end(self, status):
         """Register the given checker as closed with status <status>"""
-        with self._lock:
-            """end of the latest started check"""
-            self._cnxh.execute(
-                'SET X status %(status)s, X endtime %(endtime)s, X log %(log)s '
-                'WHERE X eid %(x)s',
-                {'status': self._unicode(status), 'endtime': datetime.now(),
-                 'log': u'\n'.join(self._logs), 'x': self._eid})
-            self._cnxh.commit()
+        self._cnxh.http_post(self._url, vid='set_attributes',
+                             status=self._unicode(status), endtime=datetime.now(),
+                             log=u'\n'.join(self._logs),)
 
 
 class TestDataWriter(BaseDataWriter):
@@ -197,52 +182,36 @@
     def make_check_writer(self):
         """Return a CheckDataWriter suitable to write checker log and result within this test"""
         self.refresh_log()
-        return CheckDataWriter(self._cnxh, self._eid)
+        return CheckDataWriter(self._cnxh, self._url)
 
     def link_to_revision(self, environment, vcsrepo):
         changeset = vcsrepo.changeset()
         if changeset is not None:
-            if not self._cnxh.execute(
-                'SET X using_revision REV '
-                'WHERE X eid %(x)s, REV changeset %(cs)s, '
-                'REV from_repository R, R eid %(r)s, '
-                'NOT X using_revision REV',
-                {'x': self._eid, 'cs': changeset,
-                 'r': environment.repository.eid}):
-                self.raw(repr(vcsrepo), changeset, 'revision')
+            self.raw(repr(vcsrepo), changeset, 'revision')
 
     def start(self):
         self.set_exec_status(u'set up')
 
     def end(self, status, archivedir=None):
         """mark the current test as closed (with status <status>) and archive if requested."""
-        with self._lock:
-            """end of the test execution"""
-            if self._logs_sent < len(self._logs):
-                self._cnxh.execute('SET X status %(status)s, X log %(log)s WHERE X eid %(x)s',
-                                   {'log': u'\n'.join(self._logs),
-                                    'status': self._unicode(status),
-                                    'x': self._eid})
-            else:
-                self._cnxh.execute('SET X status %(status)s WHERE X eid %(x)s',
-                                   {'status': self._unicode(status),
-                                    'x': self._eid})
-            self._cnxh.commit()
-            if archivedir:
-                archive = make_archive_name(self._cnxh.cwinstid, self._eid)
-                archivefpath = os.path.join(tempfile.gettempdir(), archive)
-                tarball = tarfile.open(archivefpath, ARCHIVE_MODE)
-                try:
-                    tarball.add(archivedir)
-                    tarball.close()
-                    self._cnxh.cw.create_entity(
-                        'File', data=Binary(open(archivefpath, 'rb').read()),
-                        data_format=u'application/x-bzip2',
-                        data_name=unicode(archive),
-                        reverse_log_file=self._cnxh.cw.entity_from_eid(self._eid))
-                except:
-                    self.error('while archiving execution directory', tb=True)
-                finally:
-                    os.unlink(archivefpath)
-                self._cnxh.commit()
+        self.refresh_log()
+        self._cnxh.http_post(self._url, vid='set_attributes',
+                            status = self._unicode(status))
+        if False and archivedir: # XXX this should be refactored! (mostly) useless as is
+            archive = make_archive_name(self._cnxh.cwinstid, self._url)
+            archivefpath = os.path.join(tempfile.gettempdir(), archive)
+            tarball = tarfile.open(archivefpath, ARCHIVE_MODE)
+            try:
+                tarball.add(archivedir)
+                tarball.close()
+                self._cnxh.http_post(self._url, vid='create_subentity',
+                                     __cwetype__='File',
+                                     __cwrel__='reverse_log_file',
+                                     data=Binary(open(archivefpath, 'rb').read()),
+                                     data_format=u'application/x-bzip2',
+                                     data_name=unicode(archive))
+            except:
+                self.error('while archiving execution directory', tb=True)
+            finally:
+                os.unlink(archivefpath)
 
--- a/entities.py	Thu May 02 12:40:58 2013 +0200
+++ b/entities.py	Fri Apr 25 15:56:11 2014 +0200
@@ -201,6 +201,13 @@
                         return tc
                     return
 
+    def __json_encode__(self):
+        data = super(ProjectEnvironment, self).__json_encode__()
+        data['apycot_process_environment'] = self.apycot_process_environment()
+        data['repository'] = self.repository
+        data['apycot_configuration'] = self.apycot_configuration()
+        return data
+
 
 # Test configuration ###########################################################
 
@@ -328,6 +335,11 @@
                              priority=priority)
         return texec
 
+    def __json_encode__(self):
+        data = super(TestConfig, self).__json_encode__()
+        data['apycot_process_environment'] = self.apycot_process_environment()
+        return data
+
 
 class TestExecution(Plan, ExecutionRSSMixin):
     __regid__ = 'TestExecution'
@@ -452,6 +464,11 @@
             if rev.repository.eid == repository.eid:
                 return rev
 
+    def __json_encode__(self):
+        data = super(TestExecution, self).__json_encode__()
+        data['environment'] = self.environment
+        data['configuration'] = self.configuration
+        return data
 
 class CheckResult(AnyEntity):
     __regid__ = 'CheckResult'
--- a/hooks.py	Thu May 02 12:40:58 2013 +0200
+++ b/hooks.py	Fri Apr 25 15:56:11 2014 +0200
@@ -193,6 +193,22 @@
         StartTestOp.get_instance(self._cw).add_data(revision)
 
 
+class AutolinkToRevision(hook.Hook):
+    __regid__ = 'apycot.auto_link_to_revision'
+    __select__ = hook.Hook.__select__ & hook.match_rtype('for_check')
+    events = ('after_add_relation',)
+
+    def __call__(self):
+        cri = self._cw.entity_from_eid(self.eidfrom)
+        if cri.type != 'revision':
+            return
+        cr = self.eidto
+        if self._cw.execute(
+            'SET X using_revision R WHERE X eid %(cr)s, R changeset %(cs)s, NOT X using_revision R',
+            {'cr': cr, 'cs': cri.value}):
+            cri.cw_delete()
+
+
 # notifications ################################################################
 
 class ExecStatusChangeView(notifviews.NotificationView):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/views/rest.py	Fri Apr 25 15:56:11 2014 +0200
@@ -0,0 +1,47 @@
+# copyright 2010-2013 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/>.
+
+__docformat__ = "restructuredtext en"
+_ = unicode
+
+from cubicweb.web.views import json
+from cubicweb.predicates import is_instance, match_form_params
+
+
+class GetDependencies(json.JsonEntityView):
+    __regid__ = 'apycot.get_dependencies'
+    __select__ = is_instance('TestExecution')
+
+    def call(self):
+        data = []
+        for entity in self.cw_rset.entities():
+            tconfig = entity.configuration
+            env = entity.environment
+            data.append(tconfig.dependencies(env) + [env])
+        self.wdata(data)
+
+class GetConfiguration(json.JsonEntityView):
+    __regid__ = 'apycot.get_configuration'
+    __select__ = is_instance('TestConfig') & match_form_params('environment')
+
+    def call(self):
+        data = []
+        env = self._cw.entity_from_eid(self._cw.form['environment'])
+        for tconfig in self.cw_rset.entities():
+            data.append(tconfig.apycot_configuration(env))
+        self.wdata(data)
+
+