def update_list(update_params): final_value = getattr(self, attr, []) or [] final_value = copy.deepcopy(final_value) if update_params is None or update_params == '': if not final_value: return update_params = ['-' + val for val in final_value] if isinstance(update_params, dict): keys = list(update_params.keys()) else: keys = update_params positive, negative = split_keys(keys) if not (positive + negative): raise JHTTPBadRequest('Missing params') if positive: if unique: positive = [v for v in positive if v not in final_value] final_value += positive if negative: final_value = list(set(final_value) - set(negative)) setattr(self, attr, final_value) if save: self.save(request)
def validate(self, *arg, **kw): try: return super(BaseDocument, self).validate(*arg, **kw) except mongo.ValidationError as e: raise JHTTPBadRequest('Resource `%s`: %s' % (self.__class__.__name__, e), extra={'data': e})
def view_mapper_wrapper(context, request): matchdict = request.matchdict.copy() matchdict.pop('action', None) matchdict.pop('traverse', None) # instance of BaseView (or child of) view_obj = view(context, request) action = getattr(view_obj, action_name) request.action = action_name # Tunneled collection PATCH/PUT doesn't support query params tunneled = getattr(request, '_tunneled_get', False) if tunneled and action_name in ('update_many', ): view_obj._query_params = dictset() # we should not run "after_calls" here, so lets save them in # request as filters they will be ran in the renderer factory request.filters = view_obj._after_calls try: # run before_calls (validators) before running the action for call in view_obj._before_calls.get(action_name, []): call(request=request) except wrappers.ValidationError as e: log.error('validation error: %s', e) raise JHTTPBadRequest(e.args) except wrappers.ResourceNotFound as e: log.error('resource not found: %s', e) raise JHTTPNotFound() trigger_before_events(view_obj) return action(**matchdict)
def apply_fields(cls, query_set, _fields): """ Apply fields' restrictions to `query_set`. First, fields are split to fields that should only be included and fields that should be excluded. Then excluded fields are removed from included fields. """ fields_only, fields_exclude = process_fields(_fields) if not (fields_only or fields_exclude): return query_set try: fields_only = fields_only or cls.native_fields() fields_exclude = fields_exclude or [] if fields_exclude: # Remove fields_exclude from fields_only fields_only = [ f for f in fields_only if f not in fields_exclude ] if fields_only: # Add PK field fields_only.append(cls.pk_field()) fields_only = [ getattr(cls, f) for f in sorted(set(fields_only)) ] query_set = query_set.with_entities(*fields_only) except InvalidRequestError as e: raise JHTTPBadRequest('Bad _fields param: %s ' % e) return query_set
def check_fields_allowed(cls, fields): """ Check if `fields` are allowed to be used on this model. """ fields = [f.split('__')[0] for f in fields] fields_to_query = set(cls.fields_to_query()) if not set(fields).issubset(fields_to_query): not_allowed = set(fields) - fields_to_query raise JHTTPBadRequest("'%s' object does not have fields: %s" % (cls.__name__, ', '.join(not_allowed)))
def get_or_create(cls, **params): defaults = params.pop('defaults', {}) try: return cls.objects.get(**params), False except mongo.queryset.DoesNotExist: defaults.update(params) return cls(**defaults).save(), True except mongo.queryset.MultipleObjectsReturned: raise JHTTPBadRequest('Bad or Insufficient Params')
def check_fields_allowed(cls, fields): if issubclass(cls, mongo.DynamicDocument): # Dont check if its dynamic doc return fields = [f.split('__')[0] for f in fields] fields_to_query = set(cls.fields_to_query()) if not set(fields).issubset(fields_to_query): not_allowed = set(fields) - fields_to_query raise JHTTPBadRequest("'%s' object does not have fields: %s" % (cls.__name__, ', '.join(not_allowed)))
def _get_object(id_): if hasattr(id_, 'pk_field'): return id_ obj = model.get_item(**{pk_field: id_, '_raise_on_empty': False}) if setdefault: return obj or setdefault else: if not obj: raise JHTTPBadRequest('id2obj: Object %s not found' % id_) return obj
def create_account(cls, params): """ Create auth user instance with data from :params:. Used by both Token and Ticket-based auths to register a user ( called from views). """ user_params = dictset(params).subset(['username', 'email', 'password']) try: return cls.get_or_create(email=user_params['email'], defaults=user_params) except JHTTPBadRequest: raise JHTTPBadRequest('Failed to create account.')
def apply_fields(cls, query_set, _fields): fields_only, fields_exclude = process_fields(_fields) try: if fields_only: query_set = query_set.only(*fields_only) if fields_exclude: query_set = query_set.exclude(*fields_exclude) except mongo.InvalidQueryError as e: raise JHTTPBadRequest('Bad _fields param: %s ' % e) return query_set
def update_list(): pos_keys, neg_keys = split_keys(params.keys()) if not (pos_keys + neg_keys): raise JHTTPBadRequest('missing params') if pos_keys: if unique: self.update({'add_to_set__%s' % attr: pos_keys}) else: self.update({'push_all__%s' % attr: pos_keys}) if neg_keys: self.update({'pull_all__%s' % attr: neg_keys})
def get_or_create(cls, **params): defaults = params.pop('defaults', {}) _limit = params.pop('_limit', 1) query_set = cls.get_collection(_limit=_limit, **params) try: obj = query_set.one() return obj, False except NoResultFound: defaults.update(params) new_obj = cls(**defaults) new_obj.save() return new_obj, True except MultipleResultsFound: raise JHTTPBadRequest('Bad or Insufficient Params')
def register(self): """ Register a new user by POSTing all required data. User's `Authorization` header value is returned in `WWW-Authenticate` header. """ user, created = self.Model.create_account(self._json_params) if user.api_key is None: raise JHTTPBadRequest('Failed to generate ApiKey for user') if not created: raise JHTTPConflict('Looks like you already have an account.') self.request._user = user headers = remember(self.request, user.username) return JHTTPOk('Registered', headers=headers)
def update_list(update_params): final_value = getattr(self, attr) or [] final_value = copy.deepcopy(final_value) if update_params is None or update_params == '': if not final_value: return update_params = ['-' + val for val in final_value] if isinstance(update_params, dict): keys = list(update_params.keys()) else: keys = update_params positives, negatives = split_keys(keys) if not positives and not negatives: raise JHTTPBadRequest('Missing params') parameters = {} cls_name = self.__class__.__name__ location_dot_notation = "\"" + cls_name.lower() + "\"" + "." + attr sql_expression = location_dot_notation for index, negative in enumerate(negatives): index_str = str(index) if sql_expression == location_dot_notation: sql_expression = "array_remove(" + location_dot_notation + ", :negative_" + index_str + ")" else: sql_expression = "array_remove(" + sql_expression + ", :negative_" + index_str + ")" parameters["negative_" + index_str] = negative if len(positives) > 0: sql_expression = "array_cat(" + sql_expression + ", :added_items)" parameters["added_items"] = list( set(positives) - set(final_value)) expression = text(sql_expression).bindparams(**parameters) setattr(self, attr, expression) object_session(self).flush() if save: self.save()
def set_public_limits(view): public_max = int( view.request.registry.settings.get('public_max_limit', 100)) try: _limit = int(view._params.get('_limit', 20)) _page = int(view._params.get('_page', 0)) _start = int(view._params.get('_start', 0)) view.add_after_call('index', set_total(view.request, total=public_max), pos=0) except ValueError: from nefertari.json_httpexceptions import JHTTPBadRequest raise JHTTPBadRequest("Bad _limit/_page param") _start = _start or _page * _limit if _start + _limit > public_max: view._params['_limit'] = max((public_max - _start), 0)
def error_view(context, request): return JHTTPBadRequest(context.args[0])
def value_error_view(context, request): return JHTTPBadRequest("Bad or missing value '%s'" % context.args[0])
def key_error_view(context, request): return JHTTPBadRequest("Bad or missing param '%s'" % context.args[0])
def get_collection(cls, **params): """ params may include '_limit', '_page', '_sort', '_fields' returns paginated and sorted query set raises JHTTPBadRequest for bad values in params """ params.pop('__confirmation', False) __strict = params.pop('__strict', True) _sort = _split(params.pop('_sort', [])) _fields = _split(params.pop('_fields', [])) _limit = params.pop('_limit', None) _page = params.pop('_page', None) _start = params.pop('_start', None) _count = '_count' in params params.pop('_count', None) _explain = '_explain' in params params.pop('_explain', None) __raise_on_empty = params.pop('__raise_on_empty', False) query_set = cls.objects # Remove any __ legacy instructions from this point on params = dictset( filter(lambda item: not item[0].startswith('__'), params.items())) if __strict: _check_fields = [ f.strip('-+') for f in params.keys() + _fields + _sort ] cls.check_fields_allowed(_check_fields) else: params = cls.filter_fields(params) process_lists(params) process_bools(params) #if param is _all then remove it params.pop_by_values('_all') try: query_set = query_set(**params) _total = query_set.count() if _count: return _total if _limit is None: raise JHTTPBadRequest('Missing _limit') _start, _limit = process_limit(_start, _page, _limit) # Filtering by fields has to be the first thing to do on the query_set! query_set = cls.apply_fields(query_set, _fields) query_set = cls.apply_sort(query_set, _sort) query_set = query_set[_start:_start + _limit] if not query_set.count(): msg = "'%s(%s)' resource not found" % (cls.__name__, params) if __raise_on_empty: raise JHTTPNotFound(msg) else: log.debug(msg) except (mongo.ValidationError, mongo.InvalidQueryError) as e: raise JHTTPBadRequest(str(e), extra={'data': e}) if _explain: return query_set.explain() log.debug('get_collection.query_set: %s(%s)', cls.__name__, query_set._query) query_set._nefertari_meta = dict(total=_total, start=_start, fields=_fields) return query_set
def get_collection(cls, **params): """ Query collection and return results. Notes: * Before validating that only model fields are present in params, reserved params, query params and all params starting with double underscore are dropped. * Params which have value "_all" are dropped. * When ``_count`` param is used, objects count is returned before applying offset and limit. :param bool _strict: If True ``params`` are validated to contain only fields defined on model, exception is raised if invalid fields are present. When False - invalid fields are dropped. Defaults to ``True``. :param bool _item_request: Indicates whether it is a single item request or not. When True and DataError happens on DB request, JHTTPNotFound is raised. JHTTPBadRequest is raised when False. Defaults to ``False``. :param list _sort: Field names to sort results by. If field name is prefixed with "-" it is used for "descending" sorting. Otherwise "ascending" sorting is performed by that field. Defaults to an empty list in which case sorting is not performed. :param list _fields: Names of fields which should be included or excluded from results. Fields to excluded should be prefixed with "-". Defaults to an empty list in which case all fields are returned. :param int _limit: Number of results per page. Defaults to None in which case all results are returned. :param int _page: Number of page. In conjunction with ``_limit`` is used to calculate results offset. Defaults to None in which case it is ignored. Params ``_page`` and ``_start` are mutually exclusive. :param int _start: Results offset. If provided ``_limit`` and ``_page`` params are ignored when calculating offset. Defaults to None. Params ``_page`` and ``_start`` are mutually exclusive. If not offset-related params are provided, offset equals to 0. :param Query query_set: Existing queryset. If provided, all queries are applied to it instead of creating new queryset. Defaults to None. :param _count: When provided, only results number is returned as integer. :param _explain: When provided, query performed(SQL) is returned as a string instead of query results. :param bool _raise_on_empty: When True JHTTPNotFound is raised if query returned no results. Defaults to False in which case error is just logged and empty query results are returned. :returns: Query results as ``sqlalchemy.orm.query.Query`` instance. May be sorted, offset, limited. :returns: Dict of {'field_name': fieldval}, when ``_fields`` param is provided. :returns: Number of query results as an int when ``_count`` param is provided. :returns: String representing query ran when ``_explain`` param is provided. :raises JHTTPNotFound: When ``_raise_on_empty=True`` and no results found. :raises JHTTPNotFound: When ``_item_request=True`` and ``sqlalchemy.exc.DataError`` exception is raised during DB query. Latter exception is raised when querying DB with an identifier of a wrong type. E.g. when querying Int field with a string. :raises JHTTPBadRequest: When ``_item_request=False`` and ``sqlalchemy.exc.DataError`` exception is raised during DB query. :raises JHTTPBadRequest: When ``sqlalchemy.exc.InvalidRequestError`` or ``sqlalchemy.exc.IntegrityError`` errors happen during DB query. """ log.debug('Get collection: {}, {}'.format(cls.__name__, params)) params.pop('__confirmation', False) _strict = params.pop('_strict', True) _item_request = params.pop('_item_request', False) _sort = _split(params.pop('_sort', [])) _fields = _split(params.pop('_fields', [])) _limit = params.pop('_limit', None) _page = params.pop('_page', None) _start = params.pop('_start', None) query_set = params.pop('query_set', None) _count = '_count' in params params.pop('_count', None) _explain = '_explain' in params params.pop('_explain', None) _raise_on_empty = params.pop('_raise_on_empty', False) if query_set is None: query_set = Session().query(cls) # Remove any __ legacy instructions from this point on params = dictset({ key: val for key, val in params.items() if not key.startswith('__') }) iterables_exprs, params = cls._pop_iterables(params) params = drop_reserved_params(params) if _strict: _check_fields = [ f.strip('-+') for f in list(params.keys()) + _fields + _sort ] cls.check_fields_allowed(_check_fields) else: params = cls.filter_fields(params) process_lists(params) process_bools(params) # If param is _all then remove it params.pop_by_values('_all') try: query_set = query_set.filter_by(**params) # Apply filtering by iterable expressions for expr in iterables_exprs: query_set = query_set.from_self().filter(expr) _total = query_set.count() if _count: return _total # Filtering by fields has to be the first thing to do on # the query_set! query_set = cls.apply_fields(query_set, _fields) query_set = cls.apply_sort(query_set, _sort) if _limit is not None: _start, _limit = process_limit(_start, _page, _limit) query_set = query_set.offset(_start).limit(_limit) if not query_set.count(): msg = "'%s(%s)' resource not found" % (cls.__name__, params) if _raise_on_empty: raise JHTTPNotFound(msg) else: log.debug(msg) except DataError as ex: if _item_request: msg = "'{}({})' resource not found".format( cls.__name__, params) raise JHTTPNotFound(msg, explanation=ex.message) else: raise JHTTPBadRequest(str(ex), extra={'data': ex}) except (InvalidRequestError, ) as ex: raise JHTTPBadRequest(str(ex), extra={'data': ex}) query_sql = str(query_set).replace('\n', '') if _explain: return query_sql log.debug('get_collection.query_set: %s (%s)', cls.__name__, query_sql) if _fields: query_set = cls.add_field_names(query_set, _fields) query_set._nefertari_meta = dict(total=_total, start=_start, fields=_fields) return query_set