def put(resource, **lookup): """ Perform a document replacement. Updates are first validated against the resource schema. If validation passes, the document is repalced and an OK status update is returned. If validation fails a set of validation issues is returned. :param resource: the name of the resource to which the document belongs. :param **lookup: document lookup query. .. versionchanged:: 0.3 Support for media fields. When IF_MATCH is disabled, no etag is included in the payload. Support for new validation format introduced with Cerberus v0.5. .. versionchanged:: 0.2 Use the new STATUS setting. Use the new ISSUES setting. Raise pre_<method> event. explictly resolve default values instead of letting them be resolved by common.parse. This avoids a validation error when a read-only field also has a default value. .. versionchanged:: 0.1.1 auth.request_auth_value is now used to store the auth_field value. Item-identifier wrapper stripped from both request and response payload. .. versionadded:: 0.1.0 """ resource_def = app.config['DOMAIN'][resource] schema = resource_def['schema'] validator = app.validator(schema, resource) payload = payload_() original = get_document(resource, **lookup) if not original: # not found abort(404) last_modified = None etag = None issues = {} object_id = original[config.ID_FIELD] response = {} try: document = parse(payload, resource) validation = validator.validate_replace(document, object_id) if validation: last_modified = datetime.utcnow().replace(microsecond=0) document[config.LAST_UPDATED] = last_modified document[config.DATE_CREATED] = original[config.DATE_CREATED] # ID_FIELD not in document means it is not being automatically # handled (it has been set to a field which exists in the resource # schema. if config.ID_FIELD not in document: document[config.ID_FIELD] = object_id resolve_user_restricted_access(document, resource) resolve_default_values(document, resource) resolve_media_files(document, resource, original) # notify callbacks getattr(app, "on_insert")(resource, [document]) getattr(app, "on_insert_%s" % resource)([document]) app.data.replace(resource, object_id, document) response[config.ID_FIELD] = document.get(config.ID_FIELD, object_id) response[config.LAST_UPDATED] = last_modified # metadata if config.IF_MATCH: etag = response[config.ETAG] = document_etag(document) if resource_def['hateoas']: response[config.LINKS] = { 'self': document_link(resource, response[config.ID_FIELD]) } else: issues = validator.errors except ValidationError as e: # TODO should probably log the error and abort 400 instead (when we # got logging) issues['validator exception'] = str(e) except exceptions.InternalServerError as e: raise e except Exception as e: # consider all other exceptions as Bad Requests abort(400, description=debug_error_message('An exception occurred: %s' % e)) if len(issues): response[config.ISSUES] = issues response[config.STATUS] = config.STATUS_ERR else: response[config.STATUS] = config.STATUS_OK return response, last_modified, etag, 200
def post(resource, payl=None): """ Adds one or more documents to a resource. Each document is validated against the domain schema. If validation passes the document is inserted and ID_FIELD, LAST_UPDATED and DATE_CREATED along with a link to the document are returned. If validation fails, a list of validation issues is returned. :param resource: name of the resource involved. :param payl: alternative payload. When calling post() from your own code you can provide an alternative payload This can be useful, for example, when you have a callback function hooked to a certain endpoint, and want to perform additional post() calls from there. Please be advised that in order to successfully use this option, a request context must be available. See https://github.com/nicolaiarocci/eve/issues/74 for a discussion, and a typical use case. .. versionchanged:: 0.3 Return 201 if at least one document has been successfully inserted. Fix #231 auth field not set if resource level authentication is set. Support for media fields. When IF_MATCH is disabled, no etag is included in the payload. Support for new validation format introduced with Cerberus v0.5. .. versionchanged:: 0.2 Use the new STATUS setting. Use the new ISSUES setting. Raise 'on_pre_<method>' event. Explictly resolve default values instead of letting them be resolved by common.parse. This avoids a validation error when a read-only field also has a default value. .. versionchanged:: 0.1.1 auth.request_auth_value is now used to store the auth_field value. .. versionchanged:: 0.1.0 More robust handling of auth_field. Support for optional HATEOAS. .. versionchanged: 0.0.9 Event hooks renamed to be more robuts and consistent: 'on_posting' renamed to 'on_insert'. You can now pass a pre-defined custom payload to the funcion. .. versionchanged:: 0.0.9 Storing self.app.auth.userid in auth_field when 'user-restricted resource access' is enabled. .. versionchanged: 0.0.7 Support for Rate-Limiting. Support for 'extra_response_fields'. 'on_posting' and 'on_posting_<resource>' events are raised before the documents are inserted into the database. This allows callback functions to arbitrarily edit/update the documents being stored. .. versionchanged:: 0.0.6 Support for bulk inserts. Please note: validation constraints are checked against the database, and not between the payload documents themselves. This causes an interesting corner case: in the event of a multiple documents payload where two or more documents carry the same value for a field where the 'unique' constraint is set, the payload will validate successfully, as there are no duplicates in the database (yet). If this is an issue, the client can always send the documents once at a time for insertion, or validate locally before submitting the payload to the API. .. versionchanged:: 0.0.5 Support for 'application/json' Content-Type . Support for 'user-restricted resource access'. .. versionchanged:: 0.0.4 Added the ``requires_auth`` decorator. .. versionchanged:: 0.0.3 JSON links. Superflous ``response`` container removed. """ date_utc = datetime.utcnow().replace(microsecond=0) resource_def = app.config['DOMAIN'][resource] schema = resource_def['schema'] validator = app.validator(schema, resource) documents = [] issues = [] # validation, and additional fields if payl is None: payl = payload() if isinstance(payl, dict): payl = [payl] for value in payl: document = [] doc_issues = {} try: document = parse(value, resource) validation = validator.validate(document) if validation: # validation is successful document[config.LAST_UPDATED] = \ document[config.DATE_CREATED] = date_utc resolve_user_restricted_access(document, resource) resolve_default_values(document, resource) resolve_media_files(document, resource) else: # validation errors added to list of document issues doc_issues = validator.errors except ValidationError as e: doc_issues['validation exception'] = str(e) except Exception as e: # most likely a problem with the incoming payload, report back to # the client as if it was a validation issue doc_issues['exception'] = str(e) issues.append(doc_issues) if len(doc_issues) == 0: documents.append(document) if len(documents): # notify callbacks getattr(app, "on_insert")(resource, documents) getattr(app, "on_insert_%s" % resource)(documents) # bulk insert ids = app.data.insert(resource, documents) # request was received and accepted; at least one document passed # validation and was accepted for insertion. return_code = 201 else: # request was received and accepted; no document passed validation # though. return_code = 200 # build response payload response = [] for doc_issues in issues: item = {} if len(doc_issues): item[config.STATUS] = config.STATUS_ERR item[config.ISSUES] = doc_issues else: item[config.STATUS] = config.STATUS_OK document = documents.pop(0) item[config.LAST_UPDATED] = document[config.LAST_UPDATED] # either return the custom ID_FIELD or the id returned by # data.insert(). item[config.ID_FIELD] = document.get(config.ID_FIELD, ids.pop(0)) if config.IF_MATCH: item[config.ETAG] = document_etag(document) if resource_def['hateoas']: item[config.LINKS] = \ {'self': document_link(resource, item[config.ID_FIELD])} # add any additional field that might be needed allowed_fields = [x for x in resource_def['extra_response_fields'] if x in document.keys()] for field in allowed_fields: item[field] = document[field] response.append(item) if len(response) == 1: response = response.pop(0) return response, None, None, return_code
def put(resource, **lookup): """ Perform a document replacement. Updates are first validated against the resource schema. If validation passes, the document is repalced and an OK status update is returned. If validation fails a set of validation issues is returned. :param resource: the name of the resource to which the document belongs. :param **lookup: document lookup query. .. versionchanged:: 0.3 Support for media fields. When IF_MATCH is disabled, no etag is included in the payload. Support for new validation format introduced with Cerberus v0.5. .. versionchanged:: 0.2 Use the new STATUS setting. Use the new ISSUES setting. Raise pre_<method> event. explictly resolve default values instead of letting them be resolved by common.parse. This avoids a validation error when a read-only field also has a default value. .. versionchanged:: 0.1.1 auth.request_auth_value is now used to store the auth_field value. Item-identifier wrapper stripped from both request and response payload. .. versionadded:: 0.1.0 """ resource_def = app.config['DOMAIN'][resource] schema = resource_def['schema'] validator = app.validator(schema, resource) payload = payload_() original = get_document(resource, **lookup) if not original: # not found abort(404) last_modified = None etag = None issues = {} object_id = original[config.ID_FIELD] response = {} try: document = parse(payload, resource) validation = validator.validate_replace(document, object_id) if validation: last_modified = datetime.utcnow().replace(microsecond=0) document[config.ID_FIELD] = object_id document[config.LAST_UPDATED] = last_modified # TODO what do we need here: the original creation date or the # PUT date? Going for the former seems reasonable. document[config.DATE_CREATED] = original[config.DATE_CREATED] resolve_user_restricted_access(document, resource) resolve_default_values(document, resource) resolve_media_files(document, resource, original) # notify callbacks getattr(app, "on_insert")(resource, [document]) getattr(app, "on_insert_%s" % resource)([document]) app.data.replace(resource, object_id, document) response[config.ID_FIELD] = object_id response[config.LAST_UPDATED] = last_modified # metadata if config.IF_MATCH: etag = response[config.ETAG] = document_etag(document) if resource_def['hateoas']: response[config.LINKS] = {'self': document_link(resource, object_id)} else: issues = validator.errors except ValidationError as e: # TODO should probably log the error and abort 400 instead (when we # got logging) issues['validator exception'] = str(e) except exceptions.InternalServerError as e: raise e except Exception as e: # consider all other exceptions as Bad Requests abort(400, description=debug_error_message( 'An exception occurred: %s' % e )) if len(issues): response[config.ISSUES] = issues response[config.STATUS] = config.STATUS_ERR else: response[config.STATUS] = config.STATUS_OK return response, last_modified, etag, 200
def post(resource, payl=None): """ Adds one or more documents to a resource. Each document is validated against the domain schema. If validation passes the document is inserted and ID_FIELD, LAST_UPDATED and DATE_CREATED along with a link to the document are returned. If validation fails, a list of validation issues is returned. :param resource: name of the resource involved. :param payl: alternative payload. When calling post() from your own code you can provide an alternative payload This can be useful, for example, when you have a callback function hooked to a certain endpoint, and want to perform additional post() calls from there. Please be advised that in order to successfully use this option, a request context must be available. See https://github.com/nicolaiarocci/eve/issues/74 for a discussion, and a typical use case. .. versionchanged:: 0.3 Return 201 if at least one document has been successfully inserted. Fix #231 auth field not set if resource level authentication is set. Support for media fields. When IF_MATCH is disabled, no etag is included in the payload. Support for new validation format introduced with Cerberus v0.5. .. versionchanged:: 0.2 Use the new STATUS setting. Use the new ISSUES setting. Raise 'on_pre_<method>' event. Explictly resolve default values instead of letting them be resolved by common.parse. This avoids a validation error when a read-only field also has a default value. .. versionchanged:: 0.1.1 auth.request_auth_value is now used to store the auth_field value. .. versionchanged:: 0.1.0 More robust handling of auth_field. Support for optional HATEOAS. .. versionchanged: 0.0.9 Event hooks renamed to be more robuts and consistent: 'on_posting' renamed to 'on_insert'. You can now pass a pre-defined custom payload to the funcion. .. versionchanged:: 0.0.9 Storing self.app.auth.userid in auth_field when 'user-restricted resource access' is enabled. .. versionchanged: 0.0.7 Support for Rate-Limiting. Support for 'extra_response_fields'. 'on_posting' and 'on_posting_<resource>' events are raised before the documents are inserted into the database. This allows callback functions to arbitrarily edit/update the documents being stored. .. versionchanged:: 0.0.6 Support for bulk inserts. Please note: validation constraints are checked against the database, and not between the payload documents themselves. This causes an interesting corner case: in the event of a multiple documents payload where two or more documents carry the same value for a field where the 'unique' constraint is set, the payload will validate successfully, as there are no duplicates in the database (yet). If this is an issue, the client can always send the documents once at a time for insertion, or validate locally before submitting the payload to the API. .. versionchanged:: 0.0.5 Support for 'application/json' Content-Type . Support for 'user-restricted resource access'. .. versionchanged:: 0.0.4 Added the ``requires_auth`` decorator. .. versionchanged:: 0.0.3 JSON links. Superflous ``response`` container removed. """ date_utc = datetime.utcnow().replace(microsecond=0) resource_def = app.config['DOMAIN'][resource] schema = resource_def['schema'] validator = app.validator(schema, resource) documents = [] issues = [] # validation, and additional fields if payl is None: payl = payload() if isinstance(payl, dict): payl = [payl] for value in payl: document = [] doc_issues = {} try: document = parse(value, resource) validation = validator.validate(document) if validation: # validation is successful document[config.LAST_UPDATED] = \ document[config.DATE_CREATED] = date_utc resolve_user_restricted_access(document, resource) resolve_default_values(document, resource) resolve_media_files(document, resource) else: # validation errors added to list of document issues doc_issues = validator.errors except ValidationError as e: doc_issues['validation exception'] = str(e) except Exception as e: # most likely a problem with the incoming payload, report back to # the client as if it was a validation issue doc_issues['exception'] = str(e) issues.append(doc_issues) if len(doc_issues) == 0: documents.append(document) if len(documents): # notify callbacks getattr(app, "on_insert")(resource, documents) getattr(app, "on_insert_%s" % resource)(documents) # bulk insert ids = app.data.insert(resource, documents) # request was received and accepted; at least one document passed # validation and was accepted for insertion. return_code = 201 else: # request was received and accepted; no document passed validation # though. return_code = 200 # build response payload response = [] for doc_issues in issues: item = {} if len(doc_issues): item[config.STATUS] = config.STATUS_ERR item[config.ISSUES] = doc_issues else: item[config.STATUS] = config.STATUS_OK document = documents.pop(0) item[config.LAST_UPDATED] = document[config.LAST_UPDATED] # either return the custom ID_FIELD or the id returned by # data.insert(). item[config.ID_FIELD] = document.get(config.ID_FIELD, ids.pop(0)) if config.IF_MATCH: item[config.ETAG] = document_etag(document) if resource_def['hateoas']: item[config.LINKS] = \ {'self': document_link(resource, item[config.ID_FIELD])} # add any additional field that might be needed allowed_fields = [ x for x in resource_def['extra_response_fields'] if x in document.keys() ] for field in allowed_fields: item[field] = document[field] response.append(item) if len(response) == 1: response = response.pop(0) return response, None, None, return_code