[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

[Git][snapshot-team/snapshot][master] 9 commits: Add support for optional filters on queries with SnapshotModel._modular_query()



Title: GitLab

Baptiste Beauplat pushed to branch master at snapshot / snapshot

Commits:

  • e376760b
    by Baptiste Beauplat at 2024-07-15T21:35:52+02:00
    Add support for optional filters on queries with SnapshotModel._modular_query()
    
  • 6819e8de
    by Baptiste Beauplat at 2024-07-15T21:35:52+02:00
    Add mirrorruns_get_mirrorrun() method to fetch run and archive from db
    
  • b4224d48
    by Baptiste Beauplat at 2024-07-15T21:35:52+02:00
    Add /mr/timestamp/ route to serve a list of snapshot timestamp (Closes: #969603)
    
    Can be optionally filtered using the query_parameters archive, since and
    before.
    
  • 22362291
    by Baptiste Beauplat at 2024-07-15T21:35:52+02:00
    Document new /mr/timestamp API endpoint
    
  • bcb368e2
    by Baptiste Beauplat at 2024-07-15T21:35:52+02:00
    Add date fixture to test to parse string timestamp into datetime
    
  • 7263528a
    by Baptiste Beauplat at 2024-07-15T21:35:52+02:00
    Add get_runs() method to testing dataset to retrieve a list of runs and archives
    
  • 8b1c1351
    by Baptiste Beauplat at 2024-07-15T21:35:52+02:00
    Add new unit test to cover /mr/timestamp endpoint
    
  • be8b5ed3
    by Baptiste Beauplat at 2024-07-15T21:35:52+02:00
    Add a new archive to testing dataset to cover a missing branch in _index_query_results()
    
  • 85f83e88
    by Baptiste Beauplat at 2024-07-25T20:31:03+02:00
    Merge branch 'feat/timestamp-endpoint'
    

8 changed files:

Changes:

  • API
    ... ... @@ -152,10 +152,19 @@ note: different source packages can build binaries with the same binary package
    152 152
             ]
    
    153 153
     }
    
    154 154
     
    
    155
    -
    
    156
    -
    
    157
    -
    
    158
    -
    
    155
    +URL: /mr/timestamp
    
    156
    +Options: after=date  get timestamp after specified date
    
    157
    + before=date         get timestamp before specified date
    
    158
    + archive=name        get timestamp for this archive only
    
    159
    +http status codes: 200 500
    
    160
    +summary: list all timestamp of snapshot runs, indexed by archive
    
    161
    +{   "_comment": "yadayadayda",
    
    162
    +    "result":
    
    163
    +        {   "debian": ["20230103T151722Z", "20230105T152319Z"],
    
    164
    +            "debian-ports": ["20230104T151406Z"],
    
    165
    +            ...
    
    166
    +        }
    
    167
    +}
    
    159 168
     
    
    160 169
     URL: /file/<hash>
    
    161 170
     http status codes: 200 500 403 404 451 304
    

  • web/app/snapshot/models/snapshot.py
    ... ... @@ -134,6 +134,43 @@ class SnapshotModel:
    134 134
     
    
    135 135
             return result
    
    136 136
     
    
    137
    +    def mirrorruns_get_mirrorrun(self, archive, after, before):
    
    138
    +        head = """
    
    139
    +            SELECT run, name as archive
    
    140
    +              FROM mirrorrun JOIN archive
    
    141
    +                USING (archive_id)
    
    142
    +        """
    
    143
    +        tail = 'ORDER BY run'
    
    144
    +        filters = {
    
    145
    +            'archive': 'archive.name = %(archive)s',
    
    146
    +            'after': 'run >= %(after)s',
    
    147
    +            'before': 'run <= %(before)s',
    
    148
    +        }
    
    149
    +
    
    150
    +        return self._modular_query(head, filters, tail, archive=archive,
    
    151
    +                                   after=after, before=before)
    
    152
    +
    
    153
    +    def _modular_query(self, head, filters, tail, **kwargs):
    
    154
    +        condition_keyword = 'WHERE'
    
    155
    +        condition = ''
    
    156
    +
    
    157
    +        for key, value in list(kwargs.items()):
    
    158
    +            if value is None:
    
    159
    +                kwargs.pop(key)
    
    160
    +                continue
    
    161
    +
    
    162
    +            condition += f'{condition_keyword} {filters[key]}'
    
    163
    +
    
    164
    +            if condition_keyword == 'WHERE':
    
    165
    +                condition_keyword = ' AND'
    
    166
    +
    
    167
    +        query = f'{head} {condition} {tail}'
    
    168
    +
    
    169
    +        with DBInstance(self.pool) as db:
    
    170
    +            rows = db.query(query, kwargs)
    
    171
    +
    
    172
    +        return rows
    
    173
    +
    
    137 174
         # @beaker_cache(expire=600, cache_response=False, type='memory',
    
    138 175
         #               key='archive')
    
    139 176
         # def mirrorruns_get_etag(self, archive):
    

  • web/app/snapshot/settings/common.py
    ... ... @@ -41,6 +41,7 @@ CACHE_TIMEOUT_DEFAULT = 600
    41 41
     CACHE_TIMEOUT_MR_INDEX = CACHE_TIMEOUT_DEFAULT
    
    42 42
     CACHE_TIMEOUT_MR_PACKAGE = CACHE_TIMEOUT_DEFAULT
    
    43 43
     CACHE_TIMEOUT_MR_VERSION = CACHE_TIMEOUT_DEFAULT
    
    44
    +CACHE_TIMEOUT_MR_TIMESTAMP = CACHE_TIMEOUT_DEFAULT
    
    44 45
     
    
    45 46
     CACHE_TIMEOUT_REMOVAL = CACHE_TIMEOUT_DEFAULT
    
    46 47
     CACHE_TIMEOUT_REMOVAL_ONE = CACHE_TIMEOUT_DEFAULT
    

  • web/app/snapshot/views/mr.py
    1 1
     # snapshot.debian.org - web frontend
    
    2 2
     #
    
    3 3
     # Copyright (c) 2009, 2010, 2015 Peter Palfrader
    
    4
    -# Copyright (c) 2021 Baptiste Beauplat <lyknode@cilg.org>
    
    4
    +# Copyright (c) 2021, 2023 Baptiste Beauplat <lyknode@cilg.org>
    
    5 5
     #
    
    6 6
     # Permission is hereby granted, free of charge, to any person obtaining a copy
    
    7 7
     # of this software and associated documentation files (the "Software"), to deal
    
    ... ... @@ -54,6 +54,29 @@ def _get_fileinfo_for_mr(hashes):
    54 54
         return result
    
    55 55
     
    
    56 56
     
    
    57
    +@router.route("/timestamp/")
    
    58
    +@cache()
    
    59
    +def mr_timestamp():
    
    60
    +    mr_timestamp.cache_timeout = current_app.config[
    
    61
    +        'CACHE_TIMEOUT_MR_TIMESTAMP'
    
    62
    +    ]
    
    63
    +    after = request.args.get('after')
    
    64
    +    before = request.args.get('before')
    
    65
    +    archive = request.args.get('archive')
    
    66
    +    timestamps = {}
    
    67
    +
    
    68
    +    runs = get_snapshot_model().mirrorruns_get_mirrorrun(archive, after,
    
    69
    +                                                         before)
    
    70
    +
    
    71
    +    for timestamp, archive in runs:
    
    72
    +        timestamps.setdefault(archive, []).append(rfc3339_timestamp(timestamp))
    
    73
    +
    
    74
    +    return jsonify({
    
    75
    +        '_comment': "foo",
    
    76
    +        'result': timestamps,
    
    77
    +    })
    
    78
    +
    
    79
    +
    
    57 80
     @router.route("/package/")
    
    58 81
     @cache()
    
    59 82
     def mr_index():
    

  • web/app/tests/conftest.py
    ... ... @@ -24,6 +24,7 @@
    24 24
     from os.path import realpath, abspath, join
    
    25 25
     from sys import path
    
    26 26
     from logging import getLogger
    
    27
    +from datetime import datetime
    
    27 28
     
    
    28 29
     from testing.postgresql import PostgresqlFactory
    
    29 30
     from pytest import fixture
    
    ... ... @@ -130,6 +131,17 @@ def client(app):
    130 131
         return app.test_client()
    
    131 132
     
    
    132 133
     
    
    134
    +@fixture
    
    135
    +def date():
    
    136
    +    def _parse(data):
    
    137
    +        if ':' in data:
    
    138
    +            return datetime.strptime(data, '%Y-%m-%dT%H:%M:%SZ')
    
    139
    +        else:
    
    140
    +            return datetime.strptime(data, '%Y%m%dT%H%M%SZ')
    
    141
    +
    
    142
    +    return _parse
    
    143
    +
    
    144
    +
    
    133 145
     @fixture
    
    134 146
     def snapshot():
    
    135 147
         return Snapshot
    

  • web/app/tests/controllers/dataset.py
    ... ... @@ -73,3 +73,12 @@ class DatasetController():
    73 73
                                 versions.add(version)
    
    74 74
     
    
    75 75
             return sorted(versions)
    
    76
    +
    
    77
    +    def get_runs(self):
    
    78
    +        runs = {}
    
    79
    +
    
    80
    +        for run, archives in self.dataset.items():
    
    81
    +            for archive in archives.keys():
    
    82
    +                runs.setdefault(archive, []).append(run)
    
    83
    +
    
    84
    +        return runs

  • web/app/tests/data/dataset.py
    ... ... @@ -41,6 +41,11 @@ SNAPSHOT_DATASET = {
    41 41
             },
    
    42 42
         },
    
    43 43
         '2020-08-11T18:54:38Z': {
    
    44
    +        'debian-ports': {
    
    45
    +            'unstable': {
    
    46
    +                'zsh': '1.10',
    
    47
    +            },
    
    48
    +        },
    
    44 49
             'debian': {
    
    45 50
                 'unstable': {
    
    46 51
                     'htop': '1.42',
    

  • web/app/tests/unit/views/test_mr.py
    1 1
     # snapshot.debian.org - web frontend
    
    2 2
     # https://salsa.debian.org/snapshot-team/snapshot
    
    3 3
     #
    
    4
    -# Copyright (c) 2021 Baptiste Beauplat <lyknode@cilg.org>
    
    4
    +# Copyright (c) 2021, 2023 Baptiste Beauplat <lyknode@cilg.org>
    
    5 5
     #
    
    6 6
     # Permission is hereby granted, free of charge, to any person obtaining a copy
    
    7 7
     # of this software and associated documentation files (the "Software"), to deal
    
    ... ... @@ -42,6 +42,56 @@ def test_mr_index(client, snapshot_db):
    42 42
         assert data == expected
    
    43 43
     
    
    44 44
     
    
    45
    +@mark.parametrize('archive,after,before', (
    
    46
    +    (False, False, False),
    
    47
    +    (False, True, False),
    
    48
    +    (False, False, True),
    
    49
    +    (False, True, True),
    
    50
    +    (True, True, True),
    
    51
    +))
    
    52
    +def test_mr_timestamp(client, archive, after, before, dataset, date):
    
    53
    +    expected = dataset.get_runs()
    
    54
    +    after_time = '2020-08-11T00:00:00Z'
    
    55
    +    before_time = '2020-08-12T00:00:00Z'
    
    56
    +    archive_name = 'debian-ports'
    
    57
    +    query_string = {}
    
    58
    +
    
    59
    +    if after:
    
    60
    +        query_string['after'] = after_time
    
    61
    +
    
    62
    +    if before:
    
    63
    +        query_string['before'] = before_time
    
    64
    +
    
    65
    +    if archive:
    
    66
    +        query_string['archive'] = archive_name
    
    67
    +
    
    68
    +    for key in list(expected.keys()):
    
    69
    +        if archive and archive_name != key:
    
    70
    +            expected.pop(key)
    
    71
    +            continue
    
    72
    +
    
    73
    +        expected[key].sort()
    
    74
    +        expected[key] = list(map(lambda x: x.replace('-', '').replace(':', ''),
    
    75
    +                                 expected[key]))
    
    76
    +
    
    77
    +        for run in list(expected[key]):
    
    78
    +            if after and date(run) < date(after_time):
    
    79
    +                expected[key].remove(run)
    
    80
    +                continue
    
    81
    +
    
    82
    +            if before and date(run) > date(before_time):
    
    83
    +                expected[key].remove(run)
    
    84
    +                continue
    
    85
    +
    
    86
    +    response = client.get('/mr/timestamp/', query_string=query_string)
    
    87
    +    data = loads(response.data.decode())
    
    88
    +
    
    89
    +    assert data == {
    
    90
    +        '_comment': 'foo',
    
    91
    +        'result': expected
    
    92
    +    }
    
    93
    +
    
    94
    +
    
    45 95
     @mark.parametrize('package,status', (
    
    46 96
         ('zsh', 200,),
    
    47 97
         ('this-package-does-not-exists', 404,),
    


  • Reply to: