diff --git a/cliquet/errors.py b/cliquet/errors.py index 1baee7ac..1bd819be 100644 --- a/cliquet/errors.py +++ b/cliquet/errors.py @@ -1,31 +1,33 @@ import six from pyramid import httpexceptions +from enum import Enum from cliquet.logs import logger -from cliquet.utils import Enum, json, reapply_cors, encode_header - - -ERRORS = Enum( - MISSING_AUTH_TOKEN=104, - INVALID_AUTH_TOKEN=105, - BADJSON=106, - INVALID_PARAMETERS=107, - MISSING_PARAMETERS=108, - INVALID_POSTED_DATA=109, - INVALID_RESOURCE_ID=110, - MISSING_RESOURCE=111, - MISSING_CONTENT_LENGTH=112, - REQUEST_TOO_LARGE=113, - MODIFIED_MEANWHILE=114, - METHOD_NOT_ALLOWED=115, - VERSION_NOT_AVAILABLE=116, - CLIENT_REACHED_CAPACITY=117, - FORBIDDEN=121, - CONSTRAINT_VIOLATED=122, - UNDEFINED=999, - BACKEND=201, - SERVICE_DEPRECATED=202 -) +from cliquet.utils import json, reapply_cors, encode_header + + +class ERRORS(Enum): + MISSING_AUTH_TOKEN = 104 + INVALID_AUTH_TOKEN = 105 + BADJSON = 106 + INVALID_PARAMETERS = 107 + MISSING_PARAMETERS = 108 + INVALID_POSTED_DATA = 109 + INVALID_RESOURCE_ID = 110 + MISSING_RESOURCE = 111 + MISSING_CONTENT_LENGTH = 112 + REQUEST_TOO_LARGE = 113 + MODIFIED_MEANWHILE = 114 + METHOD_NOT_ALLOWED = 115 + VERSION_NOT_AVAILABLE = 116 + CLIENT_REACHED_CAPACITY = 117 + FORBIDDEN = 121 + CONSTRAINT_VIOLATED = 122 + UNDEFINED = 999 + BACKEND = 201 + SERVICE_DEPRECATED = 202 + + """Predefined errors as specified by the protocol. +-------------+-------+------------------------------------------------+ @@ -88,6 +90,9 @@ def http_error(httpexception, errno=None, """ errno = errno or ERRORS.UNDEFINED + if isinstance(errno, Enum): + errno = errno.value + # Track error number for request summary logger.bind(errno=errno) @@ -144,7 +149,7 @@ def json_error_handler(errors): message = '%(location)s: %(description)s' % error response = http_error(httpexceptions.HTTPBadRequest(), - errno=ERRORS.INVALID_PARAMETERS, + errno=ERRORS.INVALID_PARAMETERS.value, error='Invalid parameters', message=message, details=errors) diff --git a/cliquet/events.py b/cliquet/events.py index 6fb141fd..9f022a11 100644 --- a/cliquet/events.py +++ b/cliquet/events.py @@ -2,15 +2,21 @@ import transaction from pyramid.events import NewRequest +from enum import Enum from cliquet.logs import logger -from cliquet.utils import strip_uri_prefix, Enum +from cliquet.utils import strip_uri_prefix -ACTIONS = Enum(CREATE='create', - DELETE='delete', - READ='read', - UPDATE='update') +class ACTIONS(Enum): + CREATE = 'create' + DELETE = 'delete' + READ = 'read' + UPDATE = 'update' + + @staticmethod + def from_string_list(elements): + return tuple(ACTIONS(el) for el in elements) class _ResourceEvent(object): @@ -115,7 +121,8 @@ def notify_resource_event(request, timestamp, data, action, old=None): resource_name = request.current_resource_name # Add to impacted records or create new event. - group_by = resource_name + action + group_by = resource_name + action.value + if group_by in events: if action == ACTIONS.READ: events[group_by].read_records.extend(impacted) diff --git a/cliquet/initialization.py b/cliquet/initialization.py index 88732e19..0c3fb70a 100644 --- a/cliquet/initialization.py +++ b/cliquet/initialization.py @@ -397,14 +397,14 @@ def on_new_response(event): class EventActionFilter(object): def __init__(self, actions, config): - self.actions = actions + self.actions = [action.value for action in actions] def phash(self): return 'for_actions = %s' % (','.join(self.actions)) def __call__(self, event): action = event.payload.get('action') - return not action or action in self.actions + return not action or action.value in self.actions class EventResourceFilter(object): @@ -446,7 +446,12 @@ def setup_listeners(config): key = 'listeners.%s' % name listener = statsd_client.timer(key)(listener.__call__) - actions = aslist(settings.get(prefix + 'actions', '')) or write_actions + actions = aslist(settings.get(prefix + 'actions', '')) + if len(actions) > 0: + actions = ACTIONS.from_string_list(actions) + else: + actions = write_actions + resource_names = aslist(settings.get(prefix + 'resources', '')) options = dict(for_actions=actions, for_resources=resource_names) diff --git a/cliquet/resource/__init__.py b/cliquet/resource/__init__.py index 28c12741..f4d6c4de 100644 --- a/cliquet/resource/__init__.py +++ b/cliquet/resource/__init__.py @@ -376,7 +376,8 @@ def put(self): existing = self._get_record_or_404(self.record_id) except HTTPNotFound: # Look if this record used to exist (for preconditions check). - filter_by_id = Filter(id_field, self.record_id, COMPARISON.EQ) + filter_by_id = Filter(id_field, self.record_id, + COMPARISON.EQ.value) tombstones, _ = self.model.get_records(filters=[filter_by_id], include_deleted=True) if len(tombstones) > 0: @@ -882,7 +883,7 @@ def _extract_filters(self, queryparams=None): raise_invalid(self.request, **error_details) if param == '_since': - operator = COMPARISON.GT + operator = COMPARISON.GT.value else: if param == '_to': message = ('_to is now deprecated, ' @@ -890,7 +891,7 @@ def _extract_filters(self, queryparams=None): url = ('http://cliquet.rtfd.org/en/2.4.0/api/resource' '.html#list-of-available-url-parameters') send_alert(self.request, message, url) - operator = COMPARISON.LT + operator = COMPARISON.LT.value filters.append( Filter(self.model.modified_field, value, operator) ) @@ -899,9 +900,9 @@ def _extract_filters(self, queryparams=None): m = re.match(r'^(min|max|not|lt|gt|in|exclude)_(\w+)$', param) if m: keyword, field = m.groups() - operator = getattr(COMPARISON, keyword.upper()) + operator = getattr(COMPARISON, keyword.upper()).value else: - operator, field = COMPARISON.EQ, param + operator, field = COMPARISON.EQ.value, param if not self.is_known_field(field): error_details = { @@ -911,7 +912,7 @@ def _extract_filters(self, queryparams=None): raise_invalid(self.request, **error_details) value = native_value(paramvalue) - if operator in (COMPARISON.IN, COMPARISON.EXCLUDE): + if operator in (COMPARISON.IN.value, COMPARISON.EXCLUDE.value): value = set([native_value(v) for v in paramvalue.split(',')]) filters.append(Filter(field, value, operator)) @@ -957,14 +958,17 @@ def _build_pagination_rules(self, sorting, last_record, rules=None): next_sorting = sorting[:-1] for field, _ in next_sorting: - rule.append(Filter(field, last_record.get(field), COMPARISON.EQ)) + rule.append(Filter(field, last_record.get(field), + COMPARISON.EQ.value)) field, direction = sorting[-1] if direction == -1: - rule.append(Filter(field, last_record.get(field), COMPARISON.LT)) + rule.append(Filter(field, last_record.get(field), + COMPARISON.LT.value)) else: - rule.append(Filter(field, last_record.get(field), COMPARISON.GT)) + rule.append(Filter(field, last_record.get(field), + COMPARISON.GT.value)) rules.append(rule) @@ -1077,7 +1081,8 @@ def _extract_filters(self, queryparams=None): ids = self.context.shared_ids if ids: - filter_by_id = Filter(self.model.id_field, ids, COMPARISON.IN) + filter_by_id = Filter(self.model.id_field, ids, + COMPARISON.IN.value) filters.insert(0, filter_by_id) return filters diff --git a/cliquet/storage/memory.py b/cliquet/storage/memory.py index 4010d200..6949a40c 100644 --- a/cliquet/storage/memory.py +++ b/cliquet/storage/memory.py @@ -91,14 +91,14 @@ def apply_filters(self, records, filters): """Filter the specified records, using basic iteration. """ operators = { - COMPARISON.LT: operator.lt, - COMPARISON.MAX: operator.le, - COMPARISON.EQ: operator.eq, - COMPARISON.NOT: operator.ne, - COMPARISON.MIN: operator.ge, - COMPARISON.GT: operator.gt, - COMPARISON.IN: operator.contains, - COMPARISON.EXCLUDE: lambda x, y: not operator.contains(x, y), + COMPARISON.LT.value: operator.lt, + COMPARISON.MAX.value: operator.le, + COMPARISON.EQ.value: operator.eq, + COMPARISON.NOT.value: operator.ne, + COMPARISON.MIN.value: operator.ge, + COMPARISON.GT.value: operator.gt, + COMPARISON.IN.value: operator.contains, + COMPARISON.EXCLUDE.value: lambda x, y: not operator.contains(x, y), } for record in records: @@ -106,7 +106,8 @@ def apply_filters(self, records, filters): for f in filters: left = record.get(f.field) right = f.value - if f.operator in (COMPARISON.IN, COMPARISON.EXCLUDE): + if f.operator in (COMPARISON.IN.value, + COMPARISON.EXCLUDE.value): right = left left = f.value matches = matches and operators[f.operator](left, right) @@ -330,11 +331,11 @@ def get_unicity_rules(collection_id, parent_id, record, unique_fields, if value is None: continue - filters = [Filter(field, value, COMPARISON.EQ)] + filters = [Filter(field, value, COMPARISON.EQ.value)] if not for_creation: object_id = record[id_field] - exclude = Filter(id_field, object_id, COMPARISON.NOT) + exclude = Filter(id_field, object_id, COMPARISON.NOT.value) filters.append(exclude) rules.append(filters) diff --git a/cliquet/storage/postgresql/__init__.py b/cliquet/storage/postgresql/__init__.py index df0e1a5c..6d29cdf9 100644 --- a/cliquet/storage/postgresql/__init__.py +++ b/cliquet/storage/postgresql/__init__.py @@ -566,10 +566,10 @@ def _format_conditions(self, filters, id_field, modified_field, :rtype: tuple """ operators = { - COMPARISON.EQ: '=', - COMPARISON.NOT: '<>', - COMPARISON.IN: 'IN', - COMPARISON.EXCLUDE: 'NOT IN', + COMPARISON.EQ.value: '=', + COMPARISON.NOT.value: '<>', + COMPARISON.IN.value: 'IN', + COMPARISON.EXCLUDE.value: 'NOT IN', } conditions = [] @@ -589,7 +589,8 @@ def _format_conditions(self, filters, id_field, modified_field, # If field is missing, we default to ''. sql_field = "coalesce(data->>:%s, '')" % field_holder - if filtr.operator not in (COMPARISON.IN, COMPARISON.EXCLUDE): + if filtr.operator not in (COMPARISON.IN.value, + COMPARISON.EXCLUDE.value): # For the IN operator, let psycopg escape the values list. # Otherwise JSON-ify the native value (e.g. True -> 'true') if not isinstance(filtr.value, six.string_types): @@ -703,7 +704,7 @@ def _check_unicity(self, conn, collection_id, parent_id, record, if value is None: continue sql, holders = self._format_conditions( - [Filter(field, value, COMPARISON.EQ)], + [Filter(field, value, COMPARISON.EQ.value)], id_field, modified_field, prefix=field) @@ -720,7 +721,7 @@ def _check_unicity(self, conn, collection_id, parent_id, record, if not for_creation: object_id = record[id_field] sql, holders = self._format_conditions( - [Filter(id_field, object_id, COMPARISON.NOT)], + [Filter(id_field, object_id, COMPARISON.NOT.value)], id_field, modified_field) safeholders['condition_record'] = sql diff --git a/cliquet/tests/resource/test_events.py b/cliquet/tests/resource/test_events.py index 33efcb33..4ad3c1fc 100644 --- a/cliquet/tests/resource/test_events.py +++ b/cliquet/tests/resource/test_events.py @@ -277,11 +277,11 @@ def test_impacted_records_are_merged(self): self.assertEqual(len(self.events), 3) create_event = self.events[0] - self.assertEqual(create_event.payload['action'], 'create') + self.assertEqual(create_event.payload['action'], ACTIONS.CREATE) self.assertEqual(len(create_event.impacted_records), 1) self.assertNotIn('old', create_event.impacted_records[0]) update_event = self.events[1] - self.assertEqual(update_event.payload['action'], 'update') + self.assertEqual(update_event.payload['action'], ACTIONS.UPDATE) impacted = update_event.impacted_records self.assertEqual(len(impacted), 2) self.assertEqual(impacted[0]['old']['name'], 'foo') @@ -289,7 +289,7 @@ def test_impacted_records_are_merged(self): self.assertEqual(impacted[1]['old']['name'], 'bar') self.assertEqual(impacted[1]['new']['name'], 'baz') delete_event = self.events[2] - self.assertEqual(delete_event.payload['action'], 'delete') + self.assertEqual(delete_event.payload['action'], ACTIONS.DELETE) self.assertEqual(len(delete_event.impacted_records), 1) self.assertNotIn('new', delete_event.impacted_records[0]) diff --git a/cliquet/tests/resource/test_filter.py b/cliquet/tests/resource/test_filter.py index dacbbf25..9880bdbb 100644 --- a/cliquet/tests/resource/test_filter.py +++ b/cliquet/tests/resource/test_filter.py @@ -70,7 +70,7 @@ def test_filter_errors_are_json_formatted(self): except httpexceptions.HTTPBadRequest as e: error = e self.assertEqual(error.json, { - 'errno': ERRORS.INVALID_PARAMETERS, + 'errno': ERRORS.INVALID_PARAMETERS.value, 'message': "querystring: Unknown filter field 'foo'", 'code': 400, 'error': 'Invalid parameters', diff --git a/cliquet/tests/resource/test_preconditions.py b/cliquet/tests/resource/test_preconditions.py index 573fb1f8..8735ec3c 100644 --- a/cliquet/tests/resource/test_preconditions.py +++ b/cliquet/tests/resource/test_preconditions.py @@ -80,7 +80,7 @@ def test_preconditions_errors_are_json_formatted(self): except httpexceptions.HTTPPreconditionFailed as e: error = e self.assertEqual(error.json, { - 'errno': ERRORS.MODIFIED_MEANWHILE, + 'errno': ERRORS.MODIFIED_MEANWHILE.value, 'details': {}, 'message': 'Resource was modified meanwhile', 'code': 412, diff --git a/cliquet/tests/resource/test_record.py b/cliquet/tests/resource/test_record.py index 04894be5..c6f056c4 100644 --- a/cliquet/tests/resource/test_record.py +++ b/cliquet/tests/resource/test_record.py @@ -295,7 +295,7 @@ def assertReadonlyError(self, field): except httpexceptions.HTTPBadRequest as e: error = e self.assertEqual(error.json, { - 'errno': ERRORS.INVALID_PARAMETERS, + 'errno': ERRORS.INVALID_PARAMETERS.value, 'message': 'Cannot modify {0}'.format(field), 'code': 400, 'error': 'Invalid parameters', diff --git a/cliquet/tests/resource/test_sort.py b/cliquet/tests/resource/test_sort.py index 78274184..378ffba8 100644 --- a/cliquet/tests/resource/test_sort.py +++ b/cliquet/tests/resource/test_sort.py @@ -42,7 +42,7 @@ def test_filter_errors_are_json_formatted(self): except httpexceptions.HTTPBadRequest as e: error = e self.assertEqual(error.json, { - 'errno': ERRORS.INVALID_PARAMETERS, + 'errno': ERRORS.INVALID_PARAMETERS.value, 'message': "querystring: Unknown sort field 'foo'", 'code': 400, 'error': 'Invalid parameters', diff --git a/cliquet/tests/resource/test_views.py b/cliquet/tests/resource/test_views.py index e24ae972..bcc38f7f 100644 --- a/cliquet/tests/resource/test_views.py +++ b/cliquet/tests/resource/test_views.py @@ -316,7 +316,7 @@ def test_invalid_record_returns_json_formatted_error(self): status=400) # XXX: weird resp.json['message'] self.assertDictEqual(resp.json, { - 'errno': ERRORS.INVALID_PARAMETERS, + 'errno': ERRORS.INVALID_PARAMETERS.value, 'message': "data.name in body: 42 is not a string: {'name': ''}", 'code': 400, 'error': 'Invalid parameters', @@ -443,7 +443,7 @@ def test_invalid_body_returns_json_formatted_error(self): error_msg = ("Invalid JSON request body: Expecting property name" " enclosed in double quotes: line 1 column 2 (char 1)") self.assertDictEqual(resp.json, { - 'errno': ERRORS.INVALID_PARAMETERS, + 'errno': ERRORS.INVALID_PARAMETERS.value, 'message': "body: %s" % error_msg, 'code': 400, 'error': 'Invalid parameters', @@ -544,7 +544,7 @@ def test_invalid_body_returns_json_formatted_error(self): headers=self.headers, status=400) self.assertDictEqual(resp.json, { - 'errno': ERRORS.INVALID_PARAMETERS, + 'errno': ERRORS.INVALID_PARAMETERS.value, 'message': 'permissions.read in body: "book" is not iterable', 'code': 400, 'error': 'Invalid parameters', diff --git a/cliquet/tests/support.py b/cliquet/tests/support.py index a4987bbe..3075e84e 100644 --- a/cliquet/tests/support.py +++ b/cliquet/tests/support.py @@ -14,6 +14,7 @@ from pyramid.url import parse_url_overrides from pyramid.security import IAuthorizationPolicy, Authenticated, Everyone from zope.interface import implementer +from enum import Enum from cliquet import DEFAULT_SETTINGS from cliquet.authorization import PRIVATE @@ -145,6 +146,13 @@ class FormattedErrorMixin(object): def assertFormattedError(self, response, code, errno, error, message=None, info=None): + # make sure we translate Enum instances to their values + if isinstance(error, Enum): + error = error.value + + if isinstance(errno, Enum): + errno = errno.value + self.assertEqual(response.headers['Content-Type'], 'application/json; charset=UTF-8') self.assertEqual(response.json['code'], code) diff --git a/cliquet/tests/test_deprecation.py b/cliquet/tests/test_deprecation.py index f8b4ced3..eed32040 100644 --- a/cliquet/tests/test_deprecation.py +++ b/cliquet/tests/test_deprecation.py @@ -48,7 +48,7 @@ def test_returns_410_if_eos_in_the_past(self): 'message': 'This service had been decommissioned' }) self.assertDictEqual(response.json, { - "errno": ERRORS.SERVICE_DEPRECATED, + "errno": ERRORS.SERVICE_DEPRECATED.value, "message": "The service you are trying to connect no longer " "exists at this location.", "code": 410, diff --git a/cliquet/tests/test_listeners.py b/cliquet/tests/test_listeners.py index efa30c31..b2ae3066 100644 --- a/cliquet/tests/test_listeners.py +++ b/cliquet/tests/test_listeners.py @@ -187,7 +187,7 @@ def test_redis_is_notified(self): # okay, we should have the first event in Redis last = self._redis.lpop('cliquet.events') last = json.loads(last.decode('utf8')) - self.assertEqual(last['action'], ACTIONS.CREATE) + self.assertEqual(last['action'], ACTIONS.CREATE.value) def test_notification_is_broken(self): with self.redis_listening(): diff --git a/cliquet/tests/test_storage.py b/cliquet/tests/test_storage.py index d9f0e825..2555cab9 100644 --- a/cliquet/tests/test_storage.py +++ b/cliquet/tests/test_storage.py @@ -420,7 +420,7 @@ def test_get_all_handle_sorting_on_id(self): def test_get_all_can_filter_with_list_of_values(self): for l in ['a', 'b', 'c']: self.create_record({'code': l}) - filters = [Filter('code', ['a', 'b'], utils.COMPARISON.IN)] + filters = [Filter('code', ['a', 'b'], utils.COMPARISON.IN.value)] records, _ = self.storage.get_all(filters=filters, **self.storage_kw) self.assertEqual(len(records), 2) @@ -429,7 +429,7 @@ def test_get_all_can_filter_with_list_of_values_on_id(self): record1 = self.create_record({'code': 'a'}) record2 = self.create_record({'code': 'b'}) filters = [Filter('id', [record1['id'], record2['id']], - utils.COMPARISON.IN)] + utils.COMPARISON.IN.value)] records, _ = self.storage.get_all(filters=filters, **self.storage_kw) self.assertEqual(len(records), 2) @@ -437,7 +437,7 @@ def test_get_all_can_filter_with_list_of_values_on_id(self): def test_get_all_can_filter_with_list_of_excluded_values(self): for l in ['a', 'b', 'c']: self.create_record({'code': l}) - filters = [Filter('code', ('a', 'b'), utils.COMPARISON.EXCLUDE)] + filters = [Filter('code', ('a', 'b'), utils.COMPARISON.EXCLUDE.value)] records, _ = self.storage.get_all(filters=filters, **self.storage_kw) self.assertEqual(len(records), 1) @@ -451,7 +451,7 @@ def test_get_all_handle_a_pagination_rules(self): records, total_records = self.storage.get_all( limit=5, pagination_rules=[ - [Filter('number', 1, utils.COMPARISON.GT)] + [Filter('number', 1, utils.COMPARISON.GT.value)] ], **self.storage_kw) self.assertEqual(total_records, 10) self.assertEqual(len(records), 3) @@ -464,8 +464,8 @@ def test_get_all_handle_all_pagination_rules(self): records, total_records = self.storage.get_all( limit=5, pagination_rules=[ - [Filter('number', 1, utils.COMPARISON.GT)], - [Filter('id', last_record['id'], utils.COMPARISON.EQ)], + [Filter('number', 1, utils.COMPARISON.GT.value)], + [Filter('id', last_record['id'], utils.COMPARISON.EQ.value)], ], **self.storage_kw) self.assertEqual(total_records, 10) self.assertEqual(len(records), 4) @@ -641,7 +641,7 @@ def _get_last_modified_filters(self): start = self.storage.collection_timestamp(**self.storage_kw) time.sleep(0.1) return [ - Filter(self.modified_field, start, utils.COMPARISON.GT) + Filter(self.modified_field, start, utils.COMPARISON.GT.value) ] def create_and_delete_record(self, record=None): @@ -772,7 +772,7 @@ def test_delete_all_deletes_records(self): def test_delete_all_can_delete_partially(self): self.create_record({'foo': 'po'}) self.create_record() - filters = [Filter('foo', 'bar', utils.COMPARISON.EQ)] + filters = [Filter('foo', 'bar', utils.COMPARISON.EQ.value)] self.storage.delete_all(filters=filters, **self.storage_kw) _, count = self.storage.get_all(**self.storage_kw) self.assertEqual(count, 1) @@ -893,7 +893,7 @@ def test_filtering_on_arbitrary_field_excludes_deleted_records(self): self.create_record({'status': 0}) self.create_and_delete_record({'status': 0}) - filters += [Filter('status', 0, utils.COMPARISON.EQ)] + filters += [Filter('status', 0, utils.COMPARISON.EQ.value)] records, count = self.storage.get_all(filters=filters, include_deleted=True, **self.storage_kw) @@ -905,7 +905,7 @@ def test_support_filtering_on_deleted_field(self): self.create_record() self.create_and_delete_record() - filters += [Filter('deleted', True, utils.COMPARISON.EQ)] + filters += [Filter('deleted', True, utils.COMPARISON.EQ.value)] records, count = self.storage.get_all(filters=filters, include_deleted=True, **self.storage_kw) @@ -918,7 +918,7 @@ def test_support_filtering_out_on_deleted_field(self): self.create_record() self.create_and_delete_record() - filters += [Filter('deleted', True, utils.COMPARISON.NOT)] + filters += [Filter('deleted', True, utils.COMPARISON.NOT.value)] records, count = self.storage.get_all(filters=filters, include_deleted=True, **self.storage_kw) @@ -931,7 +931,7 @@ def test_return_empty_set_if_filtering_on_deleted_false(self): self.create_record() self.create_and_delete_record() - filters += [Filter('deleted', False, utils.COMPARISON.EQ)] + filters += [Filter('deleted', False, utils.COMPARISON.EQ.value)] records, count = self.storage.get_all(filters=filters, include_deleted=True, **self.storage_kw) @@ -942,7 +942,7 @@ def test_return_empty_set_if_filtering_on_deleted_without_include(self): self.create_record() self.create_and_delete_record() - filters = [Filter('deleted', True, utils.COMPARISON.EQ)] + filters = [Filter('deleted', True, utils.COMPARISON.EQ.value)] records, count = self.storage.get_all(filters=filters, **self.storage_kw) self.assertEqual(len(records), 0) @@ -960,7 +960,8 @@ def test_pagination_rules_on_last_modified_apply_to_deleted_records(self): else: self.create_record() - pagination = [[Filter('last_modified', 314, utils.COMPARISON.GT)]] + pagination = [[Filter('last_modified', 314, + utils.COMPARISON.GT.value)]] sorting = [Sort('last_modified', 1)] records, count = self.storage.get_all(sorting=sorting, pagination_rules=pagination, diff --git a/cliquet/utils.py b/cliquet/utils.py index 5aa6205f..675ce315 100644 --- a/cliquet/utils.py +++ b/cliquet/utils.py @@ -9,6 +9,7 @@ from base64 import b64decode, b64encode from binascii import hexlify from six.moves.urllib import parse as urlparse +from enum import Enum # ujson is not installable with pypy try: # pragma: no cover @@ -149,20 +150,15 @@ def dict_subset(d, keys): return {k: d[k] for k in keys if k in d} -def Enum(**enums): - return type('Enum', (), enums) - - -COMPARISON = Enum( - LT='<', - MIN='>=', - MAX='<=', - NOT='!=', - EQ='==', - GT='>', - IN='in', - EXCLUDE='exclude', -) +class COMPARISON(Enum): + LT = '<' + MIN = '>=' + MAX = '<=' + NOT = '!=' + EQ = '==' + GT = '>' + IN = 'in' + EXCLUDE = 'exclude' def reapply_cors(request, response): diff --git a/requirements.txt b/requirements.txt index 7190b717..582024e9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,3 +28,4 @@ wheel==0.26.0 zope.deprecation==4.1.2 zope.interface==4.1.3 zope.sqlalchemy==0.7.6 +enum34==1.1.2 diff --git a/setup.py b/setup.py index 89e529df..8b41eade 100644 --- a/setup.py +++ b/setup.py @@ -27,6 +27,7 @@ 'requests', 'six', 'structlog', + 'enum34', ] if installed_with_pypy: