From d0aea4078876cddfc0933ed363100753468f9042 Mon Sep 17 00:00:00 2001 From: mprahl Date: Apr 25 2019 12:12:49 +0000 Subject: Support the stream_version_lte filter in the API --- diff --git a/README.rst b/README.rst index bbaaf8a..0ee73a0 100644 --- a/README.rst +++ b/README.rst @@ -532,6 +532,8 @@ parameters include: parameter can be given multiple times, in which case or-ing will be used. - ``state_reason`` - ``stream`` +- ``stream_version_lte`` - less than or equal to the stream version. This is limited to + the major version. This value only applies to base modules. - ``submitted_after`` - Zulu ISO 8601 format e.g. ``submitted_after=2016-08-22T09:40:07Z`` - ``submitted_before`` - Zulu ISO 8601 format e.g. ``submitted_before=2016-08-23T09:40:07Z`` - ``version`` diff --git a/module_build_service/models.py b/module_build_service/models.py index 5d7b4ef..9cbc448 100644 --- a/module_build_service/models.py +++ b/module_build_service/models.py @@ -366,6 +366,25 @@ class ModuleBuild(MBSBase): .filter(ModuleBuild.context.like(context + '%')).all() @staticmethod + def _add_stream_version_lte_filter(session, query, stream_version): + """ + Adds a less than or equal to filter for stream versions based on x.y.z versioning. + + In essence, the filter does `XX0000 <= stream_version <= XXYYZZ` + + :param session: a SQLAlchemy session + :param query: a SQLAlchemy query to add the filtering to + :param int stream_version: the stream version to filter on + :return: the query with the added stream version filter + """ + # Compute the minimal stream_version. For example, for `stream_version` 281234, + # the minimal `stream_version` is 280000. + min_stream_version = (stream_version // 10000) * 10000 + return query\ + .filter(ModuleBuild.stream_version <= stream_version)\ + .filter(ModuleBuild.stream_version >= min_stream_version) + + @staticmethod def get_last_builds_in_stream_version_lte(session, name, stream_version): """ Returns the latest builds in "ready" state for given name:stream limited by @@ -378,16 +397,13 @@ class ModuleBuild(MBSBase): :param str name: Name of the module to search builds for. :param int stream_version: Maximum stream_version to search builds for. """ - # Compute the minimal stream_version - for example for `stream_version` 281234, - # the minimal `stream_version` is 280000. - min_stream_version = (stream_version // 10000) * 10000 - query = session.query(ModuleBuild)\ .filter(ModuleBuild.name == name)\ .filter(ModuleBuild.state == BUILD_STATES["ready"])\ - .filter(ModuleBuild.stream_version <= stream_version)\ - .filter(ModuleBuild.stream_version >= min_stream_version)\ .order_by(ModuleBuild.version.desc()) + + query = ModuleBuild._add_stream_version_lte_filter(session, query, stream_version) + builds = query.all() # In case there are multiple versions of single name:stream build, we want to return diff --git a/module_build_service/utils/views.py b/module_build_service/utils/views.py index 7c9ef33..676dd54 100644 --- a/module_build_service/utils/views.py +++ b/module_build_service/utils/views.py @@ -32,7 +32,7 @@ from sqlalchemy.sql.sqltypes import Boolean as sqlalchemy_boolean from sqlalchemy.orm import aliased import sqlalchemy -from module_build_service import models, api_version, conf +from module_build_service import models, api_version, conf, db from module_build_service.errors import ValidationError, NotFound from .general import scm_url_schemes @@ -206,11 +206,13 @@ def filter_module_builds(flask_request): :return: flask_sqlalchemy.Pagination """ search_query = dict() - special_columns = ['time_submitted', 'time_modified', 'time_completed', 'state'] - for key in request.args.keys(): + special_columns = set(( + 'time_submitted', 'time_modified', 'time_completed', 'state', 'stream_version_lte',)) + columns = models.ModuleBuild.__table__.columns.keys() + for key in set(request.args.keys()) - special_columns: # Only filter on valid database columns but skip columns that are treated specially or # ignored - if key not in special_columns and key in models.ModuleBuild.__table__.columns.keys(): + if key in columns: search_query[key] = flask_request.args[key] # Multiple states can be supplied => or-ing will take place @@ -281,6 +283,21 @@ def filter_module_builds(flask_request): elif context == 'before': query = query.filter(column <= item_datetime) + stream_version_lte = flask_request.args.get('stream_version_lte') + if stream_version_lte is not None: + invalid_error = ('An invalid value of stream_version_lte was provided. It must be an ' + 'integer greater than or equal to 10000.') + try: + stream_version_lte = int(stream_version_lte) + except (TypeError, ValueError): + raise ValidationError(invalid_error) + + if stream_version_lte < 10000: + raise ValidationError(invalid_error) + + query = models.ModuleBuild._add_stream_version_lte_filter( + db.session, query, stream_version_lte) + br_joined = False module_br_alias = None for item in ('base_module_br', 'name', 'stream', 'version', 'context', 'stream_version', diff --git a/tests/test_views/test_views.py b/tests/test_views/test_views.py index 76270b8..4ef66ed 100644 --- a/tests/test_views/test_views.py +++ b/tests/test_views/test_views.py @@ -652,6 +652,28 @@ class TestViews: 'provided for the \"modified_after\" parameter') assert data['status'] == 400 + @pytest.mark.parametrize('stream_version_lte', ('280000', '290000', '293000', 'invalid',)) + def test_query_builds_filter_stream_version_lte(self, stream_version_lte): + init_data(data_size=1, multiple_stream_versions=True) + url = ('/module-build-service/1/module-builds/?name=platform&verbose=true' + '&stream_version_lte={}'.format(stream_version_lte)) + rv = self.client.get(url) + data = json.loads(rv.data) + total = data.get('meta', {}).get('total') + if stream_version_lte == 'invalid': + assert data == { + 'error': 'Bad Request', + 'message': ('An invalid value of stream_version_lte was provided. It must be an ' + 'integer greater than or equal to 10000.'), + 'status': 400 + } + elif stream_version_lte == '280000': + assert total == 2 + elif stream_version_lte == '290000': + assert total == 1 + elif stream_version_lte == '293000': + assert total == 3 + def test_query_builds_order_by(self): build = db.session.query(module_build_service.models.ModuleBuild).filter_by(id=2).one() build.name = 'candy'