Skip to content

Commit 83582cb

Browse files
ParthS007pamfilos
authored andcommitted
schemas: add url map converter for version
Signed-off-by: Parth Shandilya <[email protected]>
1 parent c993609 commit 83582cb

File tree

5 files changed

+114
-25
lines changed

5 files changed

+114
-25
lines changed

cap/converter.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""URL Map Converter module."""
2+
3+
import re
4+
5+
from flask import abort
6+
from werkzeug.routing import BaseConverter
7+
8+
SCHEMA_VERSION_REGEXP = r'^\d+\.\d+\.\d+$'
9+
10+
11+
class SchemaVersionConverter(BaseConverter):
12+
"""Converter class for validating schema version number."""
13+
14+
def __init__(self, url_map):
15+
"""Initialise converter."""
16+
super().__init__(url_map)
17+
18+
def to_python(self, value):
19+
if not bool(re.match(SCHEMA_VERSION_REGEXP, value)):
20+
abort(404)
21+
return value
22+
23+
def to_url(self, value):
24+
return value

cap/factory.py

+8
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@
1818
from invenio_cache import BytecodeCache
1919
from jinja2 import ChoiceLoader, FileSystemLoader
2020

21+
from .converter import SchemaVersionConverter
22+
23+
CAP_URL_MAP_CONVERTER = {
24+
# Key should match the variable name in URL
25+
'schema_version': SchemaVersionConverter,
26+
}
27+
2128

2229
def config_loader(app, **kwargs_config):
2330
"""Add loading templates."""
@@ -55,6 +62,7 @@ def config_loader(app, **kwargs_config):
5562
blueprint_entry_points=['invenio_base.api_blueprints'],
5663
extension_entry_points=['invenio_base.api_apps'],
5764
converter_entry_points=['invenio_base.api_converters'],
65+
converters=CAP_URL_MAP_CONVERTER,
5866
wsgi_factory=wsgi_proxyfix(),
5967
instance_path=instance_path,
6068
app_class=app_class(),

cap/modules/schemas/utils.py

+8-2
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,14 @@
2525

2626
import re
2727
from functools import wraps
28+
2829
from flask import abort
29-
from .permissions import AdminSchemaPermission
30-
from .models import Schema
3130
from invenio_jsonschemas.errors import JSONSchemaNotFound
3231
from jsonpatch import JsonPatchConflict
3332

33+
from .models import Schema
34+
from .permissions import AdminSchemaPermission
35+
3436

3537
def is_later_version(version1, version2):
3638
matched1 = re.match(r"(\d+)\.(\d+)\.(\d+)", version1)
@@ -143,6 +145,7 @@ def get_default_mapping(name, version):
143145

144146
def get_schema(f):
145147
"""Decorator to check if schema exists by name and/or version."""
148+
146149
@wraps(f)
147150
def wrapper(name=None, version=None, schema=None, *args, **kwargs):
148151
if name:
@@ -154,14 +157,17 @@ def wrapper(name=None, version=None, schema=None, *args, **kwargs):
154157
except JSONSchemaNotFound:
155158
abort(404)
156159
return f(name=name, version=version, schema=schema, *args, **kwargs)
160+
157161
return wrapper
158162

159163

160164
def schema_admin_permission(f):
161165
"""Decorator to check if user has admin permission."""
166+
162167
@wraps(f)
163168
def wrapper(name=None, version=None, schema=None, *args, **kwargs):
164169
if not AdminSchemaPermission(schema).can():
165170
abort(403)
166171
return f(name=name, version=version, schema=schema, *args, **kwargs)
172+
167173
return wrapper

cap/modules/schemas/views.py

+27-23
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def get_all_versions(name=None):
8787
'/<string:name>/permissions', methods=['GET', 'POST', 'DELETE']
8888
)
8989
@blueprint.route(
90-
'/<string:name>/<string:version>/permissions',
90+
'/<string:name>/<schema_version:version>/permissions',
9191
methods=['GET', 'POST', 'DELETE'],
9292
)
9393
# @login_required
@@ -117,11 +117,9 @@ def permissions(name=None, version=None):
117117
return jsonify({}), 204
118118

119119

120+
@blueprint.route('/<string:name>/notifications', methods=['GET', 'PATCH'])
120121
@blueprint.route(
121-
'/<string:name>/notifications', methods=['GET', 'PATCH']
122-
)
123-
@blueprint.route(
124-
'/<string:name>/<string:version>/notifications',
122+
'/<string:name>/<schema_version:version>/notifications',
125123
methods=['GET', 'PATCH'],
126124
)
127125
@get_schema
@@ -130,30 +128,36 @@ def permissions(name=None, version=None):
130128
def notifications_config(name=None, version=None, schema=None, *args, **kwargs):
131129
"""CRUD operations for schema configuration."""
132130
serialized_config = schema.config_serialize()
133-
notifications_config = serialized_config.get("config", {}).get("notifications", {})
131+
notifications_config = serialized_config.get("config", {}).get(
132+
"notifications", {}
133+
)
134134
if request.method == "PATCH":
135135
try:
136136
data = request.get_json()
137-
patched_notifications_config = apply_patch(notifications_config, data)
138-
patched_object = {'config': {'notifications': patched_notifications_config}}
137+
patched_notifications_config = apply_patch(
138+
notifications_config, data
139+
)
140+
patched_object = {
141+
'config': {'notifications': patched_notifications_config}
142+
}
139143
schema.update(**patched_object)
140144
db.session.commit()
141145
return jsonify(schema.config_serialize()), 201
142146
except (
143-
JsonPatchException,
144-
JsonPatchConflict,
145-
JsonPointerException,
146-
TypeError,
147-
) as err:
148-
return (
149-
jsonify(
150-
{
151-
'message': 'Could not apply '
152-
'json-patch to object: {}'.format(err)
153-
}
154-
),
155-
400,
156-
)
147+
JsonPatchException,
148+
JsonPatchConflict,
149+
JsonPointerException,
150+
TypeError,
151+
) as err:
152+
return (
153+
jsonify(
154+
{
155+
'message': 'Could not apply '
156+
'json-patch to object: {}'.format(err)
157+
}
158+
),
159+
400,
160+
)
157161
except IntegrityError:
158162
return (
159163
jsonify(
@@ -401,7 +405,7 @@ def patch(self, name, version):
401405
],
402406
)
403407
blueprint.add_url_rule(
404-
'/<string:name>/<string:version>',
408+
'/<string:name>/<schema_version:version>',
405409
view_func=schema_view_func,
406410
methods=['GET', 'PUT', 'DELETE', 'PATCH'],
407411
)

tests/integration/schemas/test_schemas_views.py

+47
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,53 @@ def test_get_when_user_outside_of_experiment_returns_403(
9393
assert resp.status_code == 403
9494

9595

96+
def test_get_schema_version_converter_with_wrong_version(client, db, users, auth_headers_for_user, superuser):
97+
cms_user = users['cms_user']
98+
schema = Schema(
99+
name='cms-schema',
100+
version='1.2.3',
101+
fullname='CMS Schema 1.2.3',
102+
experiment='CMS',
103+
deposit_schema={'title': 'deposit_schema'},
104+
deposit_options={'title': 'deposit_options'},
105+
record_schema={'title': 'record_schema'},
106+
record_options={'title': 'record_options'},
107+
record_mapping={
108+
'mappings': {
109+
'properties': {
110+
'title': {
111+
'type': 'text'
112+
}
113+
}
114+
}
115+
},
116+
deposit_mapping={
117+
'mappings':
118+
{
119+
'properties': {
120+
'keyword': {
121+
'type': 'keyword'
122+
}
123+
}
124+
}
125+
},
126+
is_indexed=True,
127+
)
128+
129+
db.session.add(schema)
130+
db.session.commit()
131+
132+
resp = client.get('/jsonschemas/cms-schema/1.0.0', headers=auth_headers_for_user(cms_user))
133+
assert resp.status_code == 404
134+
135+
resp = client.get('/jsonschemas/cms-schema/noti', headers=auth_headers_for_user(cms_user))
136+
assert resp.status_code == 404
137+
138+
resp = client.get('/jsonschemas/cms-schema/notifications', headers=auth_headers_for_user(superuser))
139+
assert resp.status_code == 200
140+
assert resp.json == {}
141+
142+
96143
def test_get(client, db, users, auth_headers_for_user):
97144
cms_user = users['cms_user']
98145
schema = Schema(

0 commit comments

Comments
 (0)