Projet

Général

Profil

0001-base-add-api_version-mechanism-70425.patch

Emmanuel Cazenave, 19 octobre 2022 17:34

Télécharger (7,46 ko)

Voir les différences:

Subject: [PATCH] base: add api_version mechanism (#70425)

 debian/control                 |  1 +
 passerelle/base/models.py      |  6 +++++
 passerelle/utils/api.py        |  2 ++
 passerelle/utils/validation.py |  9 +++++++
 passerelle/views.py            |  4 +++
 setup.py                       |  1 +
 tests/test_generic_endpoint.py | 47 ++++++++++++++++++++++++++++++++++
 7 files changed, 70 insertions(+)
debian/control
24 24
         python3-jsonschema,
25 25
         python3-levenshtein,
26 26
         python3-lxml,
27
         python3-packaging,
27 28
         python3-pdfrw,
28 29
         python3-pil,
29 30
         python3-pycryptodome,
passerelle/base/models.py
40 40
from passerelle.utils.api import endpoint
41 41
from passerelle.utils.jsonresponse import APIError
42 42
from passerelle.utils.sftp import SFTP, SFTPField
43
from passerelle.utils.validation import version_match
43 44

  
44 45
KEYTYPE_CHOICES = (
45 46
    ('API', _('API Key')),
......
145 146
    manager_view_template_name = None
146 147
    manager_form_base_class = GenericConnectorForm
147 148
    hide_description_fields = []
149
    api_version = None
148 150

  
149 151
    # permission descriptions
150 152
    _can_access_description = _('Access is limited to the following API users:')
......
284 286
                if endpoint_name == 'up' and hasattr(self.check_status, 'not_implemented'):
285 287
                    # hide automatic up endpoint if check_status method is not implemented
286 288
                    continue
289
                if self.api_version is not None and method.endpoint_info.api_version is not None:
290
                    if not version_match(self.api_version, method.endpoint_info.api_version):
291
                        continue
292

  
287 293
                for http_method in method.endpoint_info.methods:
288 294
                    # duplicate information to give each method its own entry
289 295
                    endpoint_info = copy.copy(method.endpoint_info)
passerelle/utils/api.py
59 59
        datasource=False,
60 60
        # helper to define the POST json schema
61 61
        post_json_schema=None,
62
        api_version=None,
62 63
    ):
63 64
        self.perm = perm
64 65
        self.methods = methods or ['get']
......
103 104
        if json_schema_response:
104 105
            self.response_schemas['application/json'] = json_schema_response
105 106
        self.datasource = datasource
107
        self.api_version = api_version
106 108

  
107 109
    def __call__(self, func):
108 110
        func.endpoint_info = self
passerelle/utils/validation.py
14 14
# You should have received a copy of the GNU Affero General Public License
15 15
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 16

  
17
from packaging.specifiers import SpecifierSet
18
from packaging.version import Version
19

  
17 20

  
18 21
def is_number(string):
19 22
    if hasattr(string, 'isdecimal'):
20 23
        return string.isdecimal() and [ord(c) < 256 for c in string]
21 24
    else:  # str PY2
22 25
        return string.isdigit()
26

  
27

  
28
def version_match(api_version, api_version_spec):
29
    api_version = Version(api_version)
30
    api_version_spec = SpecifierSet(api_version_spec)
31
    return api_version in api_version_spec
passerelle/views.py
56 56
from passerelle.utils.json import unflatten
57 57
from passerelle.utils.jsonresponse import APIError, JSONEncoder
58 58
from passerelle.utils.paginator import InfinitePaginator
59
from passerelle.utils.validation import version_match
59 60

  
60 61
from .forms import ResourceLogSearchForm
61 62
from .utils import is_authorized, to_json
......
429 430
                continue
430 431
            if not method.endpoint_info.name == kwargs.get('endpoint'):
431 432
                continue
433
            if self.connector.api_version is not None and method.endpoint_info.api_version is not None:
434
                if not version_match(self.connector.api_version, method.endpoint_info.api_version):
435
                    continue
432 436
            if method.endpoint_info.pattern:
433 437
                pattern = re_path(method.endpoint_info.pattern, method)
434 438
                match = pattern.resolve(kwargs.get('rest') or '')
setup.py
165 165
        'pytz',
166 166
        'vobject',
167 167
        'Levenshtein',
168
        'packaging',
168 169
        'python-ldap',
169 170
        'pyOpenSSL',
170 171
        'roman',
tests/test_generic_endpoint.py
349 349
class FakeJSONConnector:
350 350
    slug = 'connector-json'
351 351
    log_level = 'DEBUG'
352
    api_version = None
352 353

  
353 354
    FOO_SCHEMA = {
354 355
        'properties': {
......
448 449
class FakeConnectorDatasource:
449 450
    slug = 'connector-datasource'
450 451
    log_level = 'DEBUG'
452
    api_version = None
451 453

  
452 454
    payload = {
453 455
        'data': [
......
1051 1053
    with patch_init, patch_object:
1052 1054
        response = app.get(url)
1053 1055
        assert len(response.pyquery('#description')) == 1
1056

  
1057

  
1058
class FakeAPIVersionConnector(DummyConnectorBase):
1059
    def get_connector_slug(self):
1060
        return 'connector-api-version'
1061

  
1062
    @endpoint(api_version='>2')
1063
    # pylint: disable=disallowed-name
1064
    def foo(self, request):
1065
        return {'data': 'foo'}
1066

  
1067

  
1068
def test_endpoint_decorator_api_version(db, app):
1069
    connector = FakeAPIVersionConnector()
1070
    connector.id = 33
1071

  
1072
    patch_init = mock.patch('passerelle.views.GenericConnectorMixin.init_stuff')
1073
    patch_object = mock.patch('passerelle.views.GenericEndpointView.get_object', return_value=connector)
1074

  
1075
    url_foo = reverse(
1076
        'generic-endpoint',
1077
        kwargs={
1078
            'connector': 'connector-api-version',
1079
            'slug': 'connector-api-version',
1080
            'endpoint': 'foo',
1081
        },
1082
    )
1083
    connector.api_version = '3'
1084
    with patch_init, patch_object:
1085
        resp = app.get(url_foo)
1086
    assert resp.json['err'] == 0
1087
    assert resp.json['data'] == 'foo'
1088

  
1089
    connector.api_version = '2'
1090
    with patch_init, patch_object:
1091
        resp = app.get(url_foo, status=404)
1092

  
1093

  
1094
def test_endpoints_infos_api_version(db, app):
1095
    connector = FakeAPIVersionConnector()
1096
    assert len(connector.get_endpoints_infos()) == 1
1097
    connector.api_version = '3'
1098
    assert len(connector.get_endpoints_infos()) == 1
1099
    connector.api_version = '2'
1100
    assert len(connector.get_endpoints_infos()) == 0
1054
-