0001-base-add-api_version-mechanism-70425.patch
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 |
- |