Skip to content

Commit e1a8bb9

Browse files
authored
Merge pull request #38 from puentesarrin/method_name_on_require
Add method name as argument for validating required fields with callable
2 parents 05a47db + 17d7e19 commit e1a8bb9

File tree

10 files changed

+62
-41
lines changed

10 files changed

+62
-41
lines changed

restea/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '0.3.7'
1+
__version__ = '0.3.9'

restea/adapters/base.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,17 @@ def prepare_response(self, content, status_code, content_type, headers):
4949
'''
5050
raise NotImplementedError
5151

52-
def get_original_request(self, *args, **kwargs):
52+
def split_request_and_arguments(self, *args, **kwargs):
5353
'''
54-
Returns the original request object.
54+
Hook to return the original request object and arguments.
5555
5656
This method receives all arguments that the `wrap_request` method
57-
receives and return the first argument as is commonly received.
57+
receives and return the first argument as the request object by
58+
default which is commonly received in that order.
59+
Override this method in your subclass wrapper if the behavior is
60+
different for your framework.
5861
'''
59-
return args[0]
62+
return args[0], args[1:], kwargs
6063

6164
def wrap_request(self, *args, **kwargs):
6265
'''
@@ -65,7 +68,9 @@ def wrap_request(self, *args, **kwargs):
6568
'''
6669
data_format, kwargs = self._get_format_name(kwargs)
6770
formatter = formats.get_formatter(data_format)
68-
original_request = self.get_original_request(*args, **kwargs)
71+
original_request, args, kwargs = self.split_request_and_arguments(
72+
*args, **kwargs
73+
)
6974

7075
if not self.request_wrapper_class:
7176
raise RuntimeError(
@@ -78,6 +83,10 @@ def wrap_request(self, *args, **kwargs):
7883
)
7984
response_tuple = resource.dispatch(*args, **kwargs)
8085

86+
if len(response_tuple) == 3:
87+
# For backward compatibility, it adds an empty dict as headers
88+
response_tuple += ({},)
89+
8190
return self.prepare_response(*response_tuple)
8291

8392

restea/adapters/djangowrap.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def prepare_response(self, content, status_code, content_type, headers):
6565
response[name] = value
6666
return response
6767

68-
def get_routes(self, path='', iden_format='(?P<iden>\w+)'):
68+
def get_routes(self, path='', iden_format=r'(?P<iden>\w+)'):
6969
'''
7070
Prepare routes for the given REST resource
7171

restea/adapters/flaskwrap.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ def app(self):
6262
'''
6363
return flask.current_app
6464

65-
def get_original_request(self, *args, **kwargs):
66-
return flask.request
65+
def split_request_and_arguments(self, *args, **kwargs):
66+
return flask.request, args, kwargs
6767

6868
def prepare_response(self, content, status_code, content_type, headers):
6969
response = flask.Response(

restea/adapters/wheezywebwrap.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ def prepare_response(self, content, status_code, content_type, headers):
7878
response.headers.append((name, value))
7979
return response
8080

81-
def get_routes(self, path='', iden_format='(?P<iden>\w+)'):
81+
def get_routes(self, path='', iden_format=r'(?P<iden>\w+)'):
8282
'''
8383
Prepare routes for the given REST resource
8484

restea/fields.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,15 @@ def field_names(self):
3939
'''
4040
return set(self.fields.keys())
4141

42-
def get_required_field_names(self, data):
42+
def get_required_field_names(self, method_name, data):
4343
'''
4444
Returns only required field names
4545
:returns: required field names (from self.fields)
4646
:rtype: set
4747
'''
4848
def is_required_field(field, data):
4949
if callable(field.required):
50-
return field.required(data)
50+
return field.required(method_name, data)
5151
else:
5252
return field.required
5353

@@ -56,9 +56,11 @@ def is_required_field(field, data):
5656
if is_required_field(field, data)
5757
)
5858

59-
def validate(self, data):
59+
def validate(self, method_name, data):
6060
'''
6161
Validates payload input
62+
:param method_name: name of the method
63+
:type method_name: str
6264
:param data: input playload data to be validated
6365
:type data: dict
6466
:raises restea.fields.FieldSet.Error: field validation failed
@@ -74,7 +76,10 @@ def validate(self, data):
7476
continue
7577
cleaned_data[name] = self.fields[name].validate(value)
7678

77-
for req_field in self.get_required_field_names(cleaned_data):
79+
required_field_names = self.get_required_field_names(
80+
method_name, cleaned_data
81+
)
82+
for req_field in required_field_names:
7883
if req_field not in cleaned_data:
7984
raise self.Error('Field "{}" is missing'.format(req_field))
8085

@@ -277,8 +282,10 @@ class Email(String):
277282
Email implements field validation for emails
278283
'''
279284
error_message = '"%s" is not a valid email'
280-
pattern = r'^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*' \
281-
'(\.[a-z]{2,16})$'
285+
pattern = (
286+
r'^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*'
287+
r'(\.[a-z]{2,16})$'
288+
)
282289

283290
def _validate_field(self, field_value):
284291
if not re.match(self.pattern, field_value, re.IGNORECASE):

restea/resource.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,12 @@ def _get_method(self, method_name):
171171
)
172172
return getattr(type(self), method_name)
173173

174-
def _get_payload(self):
174+
def _get_payload(self, method_name):
175175
'''
176176
Returns a validated and parsed payload data for request
177177
178+
:param method_name: name of the method
179+
:type method_name: str
178180
:raises restea.errors.BadRequestError: unparseable data
179181
:raises restea.errors.BadRequestError: payload is not mappable
180182
:raises restea.errors.BadRequestError: validation of fields not passed
@@ -197,7 +199,7 @@ def _get_payload(self):
197199
)
198200

199201
try:
200-
return self.fields.validate(payload_data)
202+
return self.fields.validate(method_name, payload_data)
201203
except fields.FieldSet.Error as e:
202204
raise errors.BadRequestError(str(e))
203205
except fields.FieldSet.ConfigurationError as e:
@@ -224,16 +226,13 @@ def process(self, *args, **kwargs):
224226
if not self._is_valid_formatter:
225227
raise errors.BadRequestError('Not recognizable format')
226228

227-
self.payload = self._get_payload()
228-
229-
self.prepare()
230-
231229
method_name = self._get_method_name(has_iden=bool(args or kwargs))
230+
self.payload = self._get_payload(method_name)
232231
method = self._get_method(method_name)
233232
method = self._apply_decorators(method)
234233

234+
self.prepare()
235235
response = method(self, *args, **kwargs)
236-
237236
response = self.finish(response)
238237

239238
try:
@@ -246,7 +245,8 @@ def dispatch(self, *args, **kwargs):
246245
Dispatches the request and handles exception to return data, status
247246
and content type
248247
249-
:returns: 3 element tuple: result, HTTP status code and content type
248+
:returns: 4-element tuple: result, HTTP status code, content type, and
249+
headers
250250
:rtype: tuple
251251
'''
252252
try:

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
setup(
88
name='restea',
99
packages=['restea', 'restea.adapters'],
10-
version='0.3.8',
10+
version='0.3.9',
1111
description='Simple RESTful server toolkit',
1212
long_description=readme_content,
1313
author='Walery Jadlowski',

tests/test_fields.py

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,30 +49,35 @@ def test_field_set_required_fields():
4949
fs, f1, f2 = create_field_set_helper()
5050
f1.required = True
5151
f2.required = False
52-
assert fs.get_required_field_names({}) == set(['field1'])
52+
assert fs.get_required_field_names('create', {}) == set(['field1'])
5353

5454

5555
def test_field_set_required_fields_callable():
5656
fs, f1, f2 = create_field_set_helper()
5757

58-
def foo(data):
58+
def foo(method_name, data):
5959
return data.get('field2') == 0
6060
f1.required = foo
6161
f2.required = False
62-
assert fs.get_required_field_names({'field2': 0}) == set(['field1'])
63-
assert fs.get_required_field_names({}) == set([])
62+
required_field_names = fs.get_required_field_names('create', {'field2': 0})
63+
assert required_field_names == set(['field1'])
64+
required_field_names = fs.get_required_field_names('create', {})
65+
assert required_field_names == set([])
6466

65-
f1.required = lambda data: data.get('field2') == 0
67+
f1.required = lambda method_name, data: data.get('field2') == 0
6668
f2.required = False
67-
assert fs.get_required_field_names({'field2': 0}) == set(['field1'])
68-
assert fs.get_required_field_names({}) == set([])
69+
required_field_names = fs.get_required_field_names('create', {'field2': 0})
70+
assert required_field_names == set(['field1'])
71+
required_field_names = fs.get_required_field_names('create', {})
72+
assert required_field_names == set([])
6973

7074

7175
def test_field_set_validate():
7276
fs, f1, f2 = create_field_set_helper()
7377
f1.validate.return_value = 1
7478
f2.validate.return_value = 2
75-
res = fs.validate({'field1': '1', 'field2': '2', 'field3': 'wrong!'})
79+
payload = {'field1': '1', 'field2': '2', 'field3': 'wrong!'}
80+
res = fs.validate('create', payload)
7681

7782
assert res == {'field1': 1, 'field2': 2}
7883
f1.validate.assert_called_with('1')
@@ -84,7 +89,7 @@ def test_feild_set_validate_requred_fields_missing():
8489
f1.requred = True
8590

8691
with pytest.raises(FieldSet.Error) as e:
87-
fs.validate({'field2': '2'})
92+
fs.validate('create', {'field2': '2'})
8893
assert 'Field "field1" is missing' in str(e)
8994

9095

tests/test_resource.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ def test_get_payload_should_pass_validation():
205205
resource.fields = mock.Mock()
206206
resource.fields.validate.return_value = expected_data
207207

208-
assert resource._get_payload() == expected_data
208+
assert resource._get_payload('edit') == expected_data
209209

210210

211211
def test_get_payload_unexpected_data():
@@ -215,7 +215,7 @@ def test_get_payload_unexpected_data():
215215
formatter_mock.unserialize.side_effect = formats.LoadError()
216216

217217
with pytest.raises(errors.BadRequestError) as e:
218-
resource._get_payload()
218+
resource._get_payload('edit')
219219
assert 'Fail to load the data' in str(e)
220220

221221

@@ -226,7 +226,7 @@ def test_get_payload_not_mapable_payload():
226226
formatter_mock.unserialize.return_value = ['item']
227227

228228
with pytest.raises(errors.BadRequestError) as e:
229-
resource._get_payload()
229+
resource._get_payload('edit')
230230
assert 'Data should be key -> value structure' in str(e)
231231

232232

@@ -243,7 +243,7 @@ def test_get_payload_field_validation_fails():
243243
)
244244

245245
with pytest.raises(errors.BadRequestError) as e:
246-
resource._get_payload()
246+
resource._get_payload('edit')
247247
assert field_error_message in str(e)
248248

249249

@@ -261,21 +261,21 @@ def test_get_payload_field_misconfigured_fields_fails():
261261
resource.fields.validate.side_effect = conf_error
262262

263263
with pytest.raises(errors.ServerError) as e:
264-
resource._get_payload()
264+
resource._get_payload('edit')
265265
assert configuration_error_message in str(e)
266266

267267

268268
def test_get_payload_field_validation_no_data_empty_payload():
269269
resource, _, _ = create_resource_helper(method='POST')
270-
assert {} == resource._get_payload()
270+
assert {} == resource._get_payload('create')
271271

272272

273273
def test_get_payload_validation_no_fields_case_empty_payload():
274274
resource, _, formatter_mock = create_resource_helper(
275275
method='PUT', data='data'
276276
)
277277
formatter_mock.unserialize.return_value = {'data': 'test'}
278-
assert {} == resource._get_payload()
278+
assert {} == resource._get_payload('edit')
279279

280280

281281
@patch.object(formats.JsonFormat, 'serialize')

0 commit comments

Comments
 (0)