def on_post(self, req, resp, rid): """ Deserialize the file upload & save it to S3 File uploads are associated with a model of some kind. Ensure the associating model exists first & foremost. """ signals.pre_req.send(self.model) signals.pre_req_upload.send(self.model) props = req.deserialize(self.mimetypes) model = find(self.model, rid) signals.pre_upload.send(self.model, model=model) try: conn = s3_connect(self.key, self.secret) path = self._gen_s3_path(model, props) s3_url = s3_upload(self.acl, self.bucket, conn, props['content'], props['content-type'], path) except IOError: abort(ServiceUnavailable(**{ 'detail': 'The upload attempt failed unexpectedly', })) else: signals.post_upload.send(self.model, model=model, url=s3_url) resp.location = s3_url resp.status = falcon.HTTP_201 resp.serialize({'data': {'url': s3_url}}) signals.post_req.send(self.model) signals.post_req_upload.send(self.model)
def on_get(self, req, resp, rid, related): """ Find the related model & serialize it back If the parent resource of the related model doesn't exist then abort on a 404. """ signals.pre_req.send(self.model) signals.pre_req_find.send(self.model) if not hasattr(self.model, related): abort(InvalidURL(**{ 'detail': 'The "%s" resource does not have a related ' 'resource named "%s". This is an error, check ' 'your spelling & retry.' % (self.rtype, related) })) model = find(self.model, rid) try: model_related = getattr(model, related).load() except AttributeError: model_related = None if isinstance(model_related, list): props = to_rest_models(model_related, includes=req.includes) elif model: props = to_rest_model(model_related, includes=req.includes) else: props = model_related resp.serialize(props) signals.post_req.send(self.model) signals.post_req_find.send(self.model)
def process_request(self, req, resp): """ Process the request before routing it. We always enforce the use of SSL. """ if goldman.config.TLS_REQUIRED and req.protocol != 'https': abort(TLSRequired)
def process_request(self, req, resp): # pylint: disable=unused-argument """ Process the request before routing it. """ try: creds = self._get_creds(req) self.auth_creds(*creds) except (AuthRejected, AuthRequired, InvalidAuthSyntax) as exc: exc.headers = self._error_headers if not self.optional: abort(exc)
def process_request(self, req, resp): """ Process the request before routing it. """ key = req.env['REMOTE_PORT'] + req.env['REMOTE_ADDR'] val = self.cache.get(key, 0) if val == self.count: abort(exceptions.TooManyRequests(headers=self._error_headers)) else: self.cache[key] = val + 1
def find(model, rid): """ Find a model from the store by resource id """ validate_rid(model, rid) rid_field = model.rid_field model = goldman.sess.store.find(model.RTYPE, rid_field, rid) if not model: abort(exceptions.DocumentNotFound) return model
def fail(detail, link): """ Convenience method aborting on non-compliant request bodies Call this method with a string description of the reason for the error & a URL. The URL is typically a section in an online specification citing the proper way to construct the request body """ abort(exceptions.InvalidRequestBody(**{ 'detail': detail, 'links': link }))
def validate_rid(model, rid): """ Ensure the resource id is proper """ rid_field = getattr(model, model.rid_field) if isinstance(rid_field, IntType): try: int(rid) except (TypeError, ValueError): abort(exceptions.InvalidURL(**{ 'detail': 'The resource id {} in your request is not ' 'syntactically correct. Only numeric type ' 'resource id\'s are allowed'.format(rid) }))
def merge(self, data, clean=False, validate=False): """ Merge a dict with the model This is needed because schematics doesn't auto cast values when assigned. This method allows us to ensure incoming data & existing data on a model are always coerced properly. We create a temporary model instance with just the new data so all the features of schematics deserialization are still available. :param data: dict of potentially new different data to merge :param clean: set the dirty bit back to clean. This is useful when the merge is coming from the store where the data could have been mutated & the new merged in data is now the single source of truth. :param validate: run the schematics validate method :return: nothing.. it has mutation side effects """ try: model = self.__class__(data) except ConversionError as errors: abort(self.to_exceptions(errors.messages)) for key, val in model.to_native().items(): if key in data: setattr(self, key, val) if validate: try: self.validate() except ModelValidationError as errors: abort(self.to_exceptions(errors.messages)) if clean: self._original = self.to_native()
def handle_exc(exc): """ Given a database exception determine how to fail Attempt to lookup a known error & abort on a meaningful error. Otherwise issue a generic DatabaseUnavailable exception. :param exc: psycopg2 exception """ err = ERRORS_TABLE.get(exc.pgcode) if err: abort(exceptions.InvalidQueryParams(**{ 'detail': err, 'parameter': 'filter', })) abort(exceptions.DatabaseUnavailable)
def process_resource(self, req, resp, resource): """ Process the request after routing. Deserializer selection needs a resource to determine which deserializers are allowed. If a deserializer is required then it will be initialized & added to the request object for further processing. """ if req.content_required and resource: allowed = resource.deserializer_mimetypes if req.content_length in (None, 0): abort(EmptyRequestBody) elif req.content_type not in allowed: abort(ContentTypeUnsupported(allowed)) else: deserializer = self._get_deserializer(req.content_type) req.deserializer = deserializer(req, resp)
def _parse_top_level_content_type(self): """ Ensure a boundary is present in the Content-Type header This is the Content-Type header outside of any form-data & should simply be: Content-Type: multipart/form-data; boundary=<value>\r\n This is generated by the client obviously & should not occur within the uploaded payload. """ if not self.req.content_type_params.get('boundary'): abort(exceptions.InvalidRequestHeader(**{ 'detail': 'A boundary param is required in the Content-Type ' 'header & cannot be an empty string. The details ' 'of its grammar are further outlined in RFC 2046 ' '- section 5.1.1.', 'links': 'tools.ietf.org/html/rfc2388#section-4.1', }))
def deserialize(self, data=None): """ Invoke the deserializer If the payload is a collection (more than 1 records) then a list will be returned of normalized dict's. If the payload is a single item then the normalized dict will be returned (not a list) :return: list or dict """ data = [] if self.req.content_type_params.get('header') != 'present': abort(exceptions.InvalidRequestHeader(**{ 'detail': 'When using text/csv your Content-Type ' 'header MUST have a header=present parameter ' '& the payload MUST include a header of fields', 'links': 'tools.ietf.org/html/rfc4180#section-3' })) try: reader = csv.DictReader(self.req.stream) self._validate_field_headers(reader) for row in reader: Parser.run(row, reader) row = Normalizer.run(row, reader) row = super(Deserializer, self).deserialize(data) data.append(row) except csv.Error: abort(exceptions.InvalidRequestBody) return data