#9 Add REST API to query composes.
Merged 6 years ago by jkaluza. Opened 6 years ago by jkaluza.
jkaluza/odcs rest-api  into  master

file added
+71
@@ -0,0 +1,71 @@ 

+ # -*- coding: utf-8 -*-

+ # Copyright (c) 2017  Red Hat, Inc.

+ #

+ # Permission is hereby granted, free of charge, to any person obtaining a copy

+ # of this software and associated documentation files (the "Software"), to deal

+ # in the Software without restriction, including without limitation the rights

+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell

+ # copies of the Software, and to permit persons to whom the Software is

+ # furnished to do so, subject to the following conditions:

+ #

+ # The above copyright notice and this permission notice shall be included in all

+ # copies or substantial portions of the Software.

+ #

+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE

+ # SOFTWARE.

+ 

+ from flask import request, url_for

+ from odcs.models import Compose

+ 

+ 

+ def pagination_metadata(p_query):

+     """

+     Returns a dictionary containing metadata about the paginated query. This must be run as part of a Flask request.

+     :param p_query: flask_sqlalchemy.Pagination object

+     :return: a dictionary containing metadata about the paginated query

+     """

+ 

+     pagination_data = {

+         'page': p_query.page,

+         'per_page': p_query.per_page,

+         'total': p_query.total,

+         'pages': p_query.pages,

+         'first': url_for(request.endpoint, page=1, per_page=p_query.per_page, _external=True),

+         'last': url_for(request.endpoint, page=p_query.pages, per_page=p_query.per_page, _external=True)

+     }

+ 

+     if p_query.has_prev:

+         pagination_data['prev'] = url_for(request.endpoint, page=p_query.prev_num,

+                                           per_page=p_query.per_page, _external=True)

+     if p_query.has_next:

+         pagination_data['next'] = url_for(request.endpoint, page=p_query.next_num,

+                                           per_page=p_query.per_page, _external=True)

+ 

+     return pagination_data

+ 

+ 

+ def filter_composes(flask_request):

+     """

+     Returns a flask_sqlalchemy.Pagination object based on the request parameters

+     :param request: Flask request object

+     :return: flask_sqlalchemy.Pagination

+     """

+     search_query = dict()

+ 

+     for key in ['owner', 'source_type', 'source', 'state']:

+         if flask_request.args.get(key, None):

+             search_query[key] = flask_request.args[key]

+ 

+     query = Compose.query

+ 

+     if search_query:

+         query = query.filter_by(**search_query)

+ 

+     page = flask_request.args.get('page', 1, type=int)

+     per_page = flask_request.args.get('per_page', 10, type=int)

+     return query.paginate(page, per_page, False)

file modified
+24 -2
@@ -29,6 +29,7 @@ 

  from odcs import app, db, log, conf

  from odcs.models import Compose, COMPOSE_RESULTS

  from odcs.pungi import PungiSourceType

+ from odcs.api_utils import pagination_metadata, filter_composes

  

  

  api_v1 = {
@@ -39,6 +40,12 @@ 

              'methods': ['GET'],

          }

      },

+     'compose': {

+         'url': '/odcs/1/composes/<int:id>',

+         'options': {

+             'methods': ['GET'],

+         }

+     },

      'composes_post': {

          'url': '/odcs/1/composes/',

          'options': {
@@ -50,7 +57,22 @@ 

  

  class ODCSAPI(MethodView):

      def get(self, id):

-         return "Done", 200

+         if id is None:

+             p_query = filter_composes(request)

+ 

+             json_data = {

+                 'meta': pagination_metadata(p_query)

+             }

+             json_data['items'] = [item.json() for item in p_query.items]

+ 

+             return jsonify(json_data), 200

+ 

+         else:

+             compose = Compose.query.filter_by(id=id).first()

+             if compose:

+                 return jsonify(compose.json()), 200

+             else:

+                 raise ValueError('No such compose found.')

  

      def post(self):

          owner = "Unknown"  # TODO
@@ -107,7 +129,7 @@ 

  

  

  def register_api_v1():

-     """ Registers version 1 of MBS API. """

+     """ Registers version 1 of ODCS API. """

      module_view = ODCSAPI.as_view('composes')

      for key, val in api_v1.items():

          app.add_url_rule(val['url'],

file modified
+56 -13
@@ -22,11 +22,10 @@ 

  

  import unittest

  import json

- import time

  

- from mock import patch

  from odcs import db, app

- from odcs.models import Compose, COMPOSE_STATES

+ from odcs.models import Compose, COMPOSE_STATES, COMPOSE_RESULTS

+ from odcs.pungi import PungiSourceType

  

  

  class TestViews(unittest.TestCase):
@@ -39,19 +38,22 @@ 

          db.create_all()

          db.session.commit()

  

+         self.c1 = Compose.create(

+             db.session, "unknown", PungiSourceType.MODULE, "testmodule-master",

+             COMPOSE_RESULTS["repository"], 60)

+         self.c2 = Compose.create(

+             db.session, "me", PungiSourceType.KOJI_TAG, "f26",

+             COMPOSE_RESULTS["repository"], 60)

+         db.session.add(self.c1)

+         db.session.add(self.c2)

+         db.session.commit()

+ 

      def tearDown(self):

          db.session.remove()

          db.drop_all()

          db.session.commit()

  

-     @patch("odcs.utils.execute_cmd")

-     def test_submit_build(self, execute_cmd):

-         def mocked_execute_cmd():

-             time.sleep(1)

-             return 0

- 

-         execute_cmd = mocked_execute_cmd # NOQA

- 

+     def test_submit_build(self):

          rv = self.client.post('/odcs/1/composes/', data=json.dumps(

              {'source_type': 'module', 'source': 'testmodule-master'}))

          data = json.loads(rv.data.decode('utf8'))
@@ -59,11 +61,52 @@ 

          expected_json = {'source_type': 2, 'state': 0, 'time_done': None,

                           'state_name': 'wait', 'source': u'testmodule-master',

                           'owner': u'Unknown',

-                          'result_repo': 'http://localhost/odcs/latest-odcs-1-1/compose/Temporary',

-                          'time_submitted': data["time_submitted"], 'id': 1,

+                          'result_repo': 'http://localhost/odcs/latest-odcs-%d-1/compose/Temporary' % data['id'],

+                          'time_submitted': data["time_submitted"], 'id': data['id'],

                           'time_removed': None}

          self.assertEqual(data, expected_json)

  

          db.session.expire_all()

          c = db.session.query(Compose).filter(Compose.id == 1).one()

          self.assertEqual(c.state, COMPOSE_STATES["wait"])

+ 

+     def test_query_compose(self):

+         resp = self.client.get('/odcs/1/composes/1')

+         data = json.loads(resp.data.decode('utf8'))

+         self.assertEqual(data['id'], 1)

+         self.assertEqual(data['source'], "testmodule-master")

+ 

+     def test_query_composes(self):

+         resp = self.client.get('/odcs/1/composes/')

+         evs = json.loads(resp.data.decode('utf8'))['items']

+         self.assertEqual(len(evs), 2)

+ 

+     def test_query_compose_owner(self):

+         resp = self.client.get('/odcs/1/composes/?owner=me')

+         evs = json.loads(resp.data.decode('utf8'))['items']

+         self.assertEqual(len(evs), 1)

+         self.assertEqual(evs[0]['source'], 'f26')

+ 

+     def test_query_compose_state_done(self):

+         resp = self.client.get(

+             '/odcs/1/composes/?state=%d' % COMPOSE_STATES["done"])

+         evs = json.loads(resp.data.decode('utf8'))['items']

+         self.assertEqual(len(evs), 0)

+ 

+     def test_query_compose_state_wait(self):

+         resp = self.client.get(

+             '/odcs/1/composes/?state=%d' % COMPOSE_STATES["wait"])

+         evs = json.loads(resp.data.decode('utf8'))['items']

+         self.assertEqual(len(evs), 2)

+ 

+     def test_query_compose_source_type(self):

+         resp = self.client.get(

+             '/odcs/1/composes/?source_type=%d' % PungiSourceType.MODULE)

+         evs = json.loads(resp.data.decode('utf8'))['items']

+         self.assertEqual(len(evs), 1)

+ 

+     def test_query_compose_source(self):

+         resp = self.client.get(

+             '/odcs/1/composes/?source=f26')

+         evs = json.loads(resp.data.decode('utf8'))['items']

+         self.assertEqual(len(evs), 1)

no initial comment

rebased

6 years ago

Pull-Request has been merged by jkaluza

6 years ago