def get(self): search = request.args.get('search') resp = OrgBookService.search(search) if resp.status_code != requests.codes.ok: raise BadGateway(f'OrgBook API responded with {resp.status_code}: {resp.reason}') try: results = json.loads(resp.text)['results'] except: raise BadGateway('OrgBook API responded with unexpected data.') return results
def run(self, payload: dict[str, Any]) -> dict[str, Any]: try: return self.client.invoke(**self.LAMBDA_CONFIG, Payload=json.dumps(payload).encode()) except Exception: raise BadGateway( "Lambda is not accessible right now. Please try again.")
def get_delivery_estimate(self, product, units, buyer): """Returns a shipping cost estimation for an order from shared server. If the order can't be delivered returns cost -1""" cost = -1 total = product.price * units url = os.environ['SHARED_SERVER_URI'] + '/deliveries/estimate' header = {'Authorization': 'Bearer ' + self.auth_token} payload = { "ammount": total, "distance": product.location.distance_to(buyer.location), "user": { "email": buyer.email, "points": buyer.points, "deliveries": buyer.metadata.purchases } } response = requests.post(url, headers=header, json=payload) if response.status_code == 401: raise ExpiredTokenError() if response.status_code == 201: delivery_estimate = response.json() if delivery_estimate['isAbleToDeliver']: cost = delivery_estimate['cost'] else: raise BadGateway() return cost
def lambda_proxy(action): """Invoke Lambda function through API Gateway to start/stop EC2 server instance action: (str) Only valid arguments are 'start_instance' and 'stop_instance'. """ if action != 'start_instance' and action != 'stop_instance': raise ValueError('Invalid argument for ec2_handler: %s' % action) # Input to the Lambda function payload = {'action': action} headers = {'Content-Type': 'application/json'} response = requests.post( # API Gateway endpoint 'https://mvht1yr1c8.execute-api.us-east-2.amazonaws.com/prod/{proxy+}', data=json.dumps(payload), headers=headers) # API Gateway max timeout is very close to EC2 instance startup time. if action == 'start_instance' and response.status_code == 504: # Try again return lambda_proxy(action) # Instance state == 'stopping' if response.status_code == 502: raise BadGateway('Server is shutting down. Try again in a minute.') return response.json()['public_ip_address']
def send_invite_emails(pending_users): '''Send an invite email to every pending user in the list''' user_manager = current_app.user_manager db_adapter = user_manager.db_adapter logger = g.request_logger if hasattr(g, 'request_logger') else LOG for user in pending_users: logger.info('Sending email invite to: \'%s\'', user.username) # generate invite token token = user_manager.generate_token(int(user.id)) invite_link = url_for('user.register', token=token, _external=True) email_msg = current_app.email_renderer.create_invitation_message( current_user, user, invite_link) try: # send invite email current_app.notification_service.send_email(email_msg) except NotificationError: error = 'Failed to send invitation email to: \'%s\'' % user.username logger.error('Failed to send invitation email to: \'%s\'', user.username) raise BadGateway(error) # store token to db db_adapter.update_object(user, reset_password_token=token) db_adapter.commit()
def post(self, party_guid): party = Party.find_by_party_guid(party_guid) if party is None: raise NotFound('Party not found.') if PartyOrgBookEntity.find_by_party_guid(party_guid) is not None: raise BadRequest( 'This party is already associated with an OrgBook entity.') data = PartyOrgBookEntityListResource.parser.parse_args() credential_id = data.get('credential_id') if PartyOrgBookEntity.find_by_credential_id(credential_id) is not None: raise BadRequest( 'An OrgBook entity with the provided credential ID already exists.' ) resp = OrgBookService.get_credential(credential_id) if resp.status_code != requests.codes.ok: raise BadGateway( f'OrgBook API responded with {resp.status_code}: {resp.reason}' ) try: credential = json.loads(resp.text) registration_id = credential['topic']['source_id'] registration_status = not (credential['inactive']) registration_date = credential['effective_date'] name_id = credential['names'][0]['id'] name_text = credential['names'][0]['text'] except: raise BadGateway('OrgBook API responded with unexpected data.') party_orgbook_entity = PartyOrgBookEntity.create( registration_id, registration_status, registration_date, name_id, name_text, credential_id, party_guid) if not party_orgbook_entity: raise InternalServerError( 'Failed to create the Party OrgBook Entity.') party_orgbook_entity.save() party.party_name = name_text party.save() return party_orgbook_entity, 201
def get(self, credential_id): resp = OrgBookService.get_credential(credential_id) if resp.status_code != requests.codes.ok: raise BadGateway(f'OrgBook API responded with {resp.status_code}: {resp.reason}') credential = json.loads(resp.text) return credential
def get(self): search = request.args.get('search') resp = OrgBookService.search(search) if resp.status_code != requests.codes.ok: message = f'OrgBook API responded with {resp.status_code}: {resp.reason}' current_app.logger.error( f'SearchResource.get: {message}\nresp.text:\n{resp.text}') raise BadGateway(message) try: results = json.loads(resp.text)['results'] except: message = 'OrgBook API responded with unexpected data.' current_app.logger.error( f'SearchResource.get: {message}\nresp.text:\n{resp.text}') raise BadGateway(message) return results
def retrieve_yaml_remote_rule(url): """ Retrieve a remote rule file content from the git web UI. """ response = requests_session.request('HEAD', url) if response.status_code == 404: log.debug(f'Server returned 404 for {url}.') return None if response.status_code != 200: raise BadGateway('Error occurred while retrieving a remote rule file from the repo.') # remote rule file found... response = requests_session.request('GET', url) response.raise_for_status() return response.content
def create_payment(self, order): """Sends a new payment order to shared server""" url = os.environ['SHARED_SERVER_URI'] + '/payments' header = {'Authorization': 'Bearer ' + self.auth_token} payload = { "transaction_id": order.tracking_number, "currency": "Pesos", "value": order.total, "paymentMethod": PaymentInfoSchema().dump(order.payment_info) } response = requests.post(url, headers=header, json=payload) if response.status_code == 401: raise ExpiredTokenError() if response.status_code != 201: raise BadGateway()
def get_payment_methods(self): """Returns available payment methods from shared server""" url = os.environ['SHARED_SERVER_URI'] + '/payments/methods' header = {'Authorization': 'Bearer ' + self.auth_token} response = requests.get(url, headers=header) if response.status_code == 401: raise ExpiredTokenError() if response.status_code == 200: payment_methods = [] payment_methods_dict = response.json() for payment_method in payment_methods_dict: payment_methods.append( PaymentMethodSchema().load(payment_method)) else: raise BadGateway() return payment_methods
def get(self, credential_id): # Get the credential data for this credential ID resp = OrgBookService.get_credential(credential_id) if resp.status_code != requests.codes.ok: message = f'OrgBook API (get_credential) responded with {resp.status_code}: {resp.reason}' current_app.logger.error( f'CredentialResource get_credential: {message}\nresp.text:\n{resp.text}' ) raise BadGateway(message) # Get the topic ID from the credential data credential_data = json.loads(resp.text) topic_id = credential_data['topic']['id'] # Get the business number data using the topic ID resp = OrgBookService.get_business_number(topic_id) if resp.status_code != requests.codes.ok: message = f'OrgBook API (get_business_number) responded with {resp.status_code}: {resp.reason}' current_app.logger.warning( f'CredentialResource get_business_number: {message}\nresp.text:\n{resp.text}' ) # Get the business number from the business number data business_number = None try: business_number_data = json.loads(resp.text) results = business_number_data['results'] for result in results: for attribute in result['attributes']: if attribute['type'] == 'business_number': business_number = attribute['value'] break if business_number: break except: pass # Set the business number (if it was found) in the credential data if business_number is None: message = f'OrgBook API (get_business_number) did not contain the business number' current_app.logger.warning( f'CredentialResource get_business_number: {message}') credential_data['business_number'] = business_number return credential_data
def get_weather(): # Auth try: user = check_api_key() except Unauthorized: raise # Check throttling if not check_ratelimit(user.api_key): raise TooManyRequests('Maximum of %s request per hour' % config.LIMIT_REQ_PER_HOUR) q = request.args.get('q') if not q: raise BadRequest('q parameter required') # We're only supporting ?q=cityname[,countryname] # So we'll just remove anything that's not (alphanum or comma), # complain if more than one comma # and pass the ?q straight through to OWM (Open Weather Map) q = re.sub('[^\w,]', '', q) if q.count(',') > 1: raise BadRequest( 'q parameter should be of format cityname[,countryname]') # Don't talk to OWM if we're running in dev mode if config.DEV_MODE: return 'cloudy with a chance of meatballs' # Request to OWM url = util.construct_owm_req_uri(q) res = requests.get(url) # Grab the first weather item's description # (according to doco looks like we only get more than one if specifying geo region) # If the format isn't exactly as expected, or other problem talking to OWM # (e.g. bad API key) just log this and return 502 bad gateway try: j = res.json() description = j['weather'][0]['description'] except: # TODO log the invalid req/res from OWM for troubleshooting, raise an alert etc # Error returned to client is intentionally vague raise BadGateway('Problem talking to weather service') return description
def send_reset_password(email): logger = g.request_logger if hasattr(g, 'request_logger') else LOG with Transaction() as transaction: user_manager = current_app.user_manager user = transaction.find_one_by_fields( User, case_sensitive=True, search_fields={'username': email}) if not user: logger.warning('User does not exist with email: \'%s\'', user.username) raise ItemNotFound('user', {'username': email}) # Generate reset password token token = user_manager.generate_token(int(user.get_id())) reset_password_link = url_for('user.reset_password', token=token, _external=True) # Create reset password email message email_message = current_app.email_renderer.create_password_reset_message( current_user, user, reset_password_link) logger.info('Sending reset-password email to: \'%s\'', user.username) try: # Send password reset email current_app.notification_service.send_email(email_message) except NotificationError: error = 'Failed to send reset-password email to: \'%s\'' % user.username logger.error('Failed to send reset-password email to: \'%s\'', user.username) raise BadGateway(error) # Store token to db user.reset_password_token = token user = transaction.add_or_update(user, flush=True) # Send forgot_password signal to flask_user to trigger any hooks # pylint: disable=W0212 user_forgot_password.send(current_app._get_current_object(), user=user) logger.info( 'Successfully sent reset password email for user with email: \'%s\'', user.username, )
def get_profile(user, retries=10): tries = 0 status = 0 session_id = keyring.get_password("instarss", "user") while status != 200 and tries < retries: if tries: time.sleep(1) r = requests.get('https://www.instagram.com/' + user + '/', cookies={'sessionid': session_id}, allow_redirects=False) status = r.status_code if r.status_code != 200: print(user, status, file=sys.stderr) tries += 1 if status != 200: sys.stderr.flush() raise BadGateway(description=status) return r.content
def _requests_fn(fn, url, parse_json=False, **kwargs): """Wraps requests.* and adds raise_for_status() and User-Agent.""" kwargs.setdefault('headers', {}).update(HEADERS) resp = fn(url, gateway=True, **kwargs) logging.info(f'Got {resp.status_code} headers: {resp.headers}') type = content_type(resp) if (type and type != 'text/html' and (type.startswith('text/') or type.endswith('+json') or type.endswith('/json'))): logging.info(resp.text) if parse_json: try: return resp.json() except ValueError: msg = "Couldn't parse response as JSON" logging.info(msg, exc_info=True) raise BadGateway(msg) return resp
def share_by_email(self, **kwargs): shared_image_name = 'share_analysis.png' logger = g.request_logger if hasattr(g, 'request_logger') else LOG attachments = [("attachment", (attachment['filename'], attachment['content'])) for attachment in kwargs['attachments']] base64_image_str = kwargs.get('image_url') if base64_image_str: base64_image_str = base64_image_str.replace( 'data:image/png;base64,', '') attachments.append(( "inline", ( shared_image_name, base64.decodebytes(base64_image_str.encode('utf-8')), ), )) else: shared_image_name = '' for recipient in kwargs['recipients']: msg = current_app.email_renderer.create_share_analysis_email( subject=kwargs['subject'], recipient=recipient, reply_to=kwargs['sender'], body=kwargs['message'], attachments=attachments, query_url=kwargs['query_url'], image=shared_image_name, ) try: current_app.notification_service.send_email(msg) except NotificationError: error = 'Failed to send share analysis email to: \'%s\'' % recipient logger.error(error) raise BadGateway(error) message = (SHARE_ANALYSIS_PREVIEW_MESSAGE.format( recipient=','.join(kwargs['recipients'])) if kwargs.get( 'is_preview', False) else SHARE_ANALYSIS_MESSAGE) return {'message': message}
def update_payment_status(self, order): """Updates the order payment status from shared server""" url = os.environ['SHARED_SERVER_URI'] + '/payments/id/' + str( order.tracking_number) header = {'Authorization': 'Bearer ' + self.auth_token} response = requests.get(url, headers=header) payment_status = { 'PENDIENTE': 'PAGO PENDIENTE DE PROCESO', 'CONFIRMADO': 'PAGO ACEPTADO', 'CANCELADO': 'PAGO RECHAZADO' } if response.status_code == 401: raise ExpiredTokenError() if response.status_code == 200: payment_list = response.json() if payment_list: payment_last_update = payment_list[-1] status = payment_last_update['status'] order.last_status_update = payment_last_update['updateat'] order.status = payment_status[status] else: raise BadGateway()
def update_tracking_status(self, order): """Returns the shipping order status from shared server""" url = os.environ['SHARED_SERVER_URI'] + '/tracking/' + str( order.tracking_number) header = {'Authorization': 'Bearer ' + self.auth_token} response = requests.get(url, headers=header) tracking_status = { 'PENDIENTE': 'PENDIENTE DE ENVIO', 'EN TRANSITO': 'ENVIO EN PROGRESO', 'CANCELADO': 'ENVIO CANCELADO', 'ENTREGADO': 'ENVIO REALIZADO' } if response.status_code == 401: raise ExpiredTokenError() if response.status_code == 200: tracking_list = response.json() if tracking_list: tracking_last_update = tracking_list[-1] status = tracking_last_update['status'] order.last_status_update = tracking_last_update['updateat'] order.status = tracking_status[status] else: raise BadGateway()
def retrieve_scm_from_koji_build(nvr, build, koji_url): if not build: raise NotFound('Failed to find Koji build for "{}" at "{}"'.format(nvr, koji_url)) source = None try: source = build['extra']['source']['original_url'] except (TypeError, KeyError, AttributeError): pass finally: if not source: source = build.get('source') if not source: raise NoSourceException( 'Failed to retrieve SCM URL from Koji build "{}" at "{}" ' '(expected SCM URL in "source" attribute)'.format(nvr, koji_url) ) url = urlparse(source) path_components = url.path.rsplit('/', 2) if len(path_components) < 3: namespace = "" else: namespace = path_components[-2] rev = url.fragment if not rev: raise BadGateway( 'Failed to parse SCM URL "{}" from Koji build "{}" at "{}" ' '(missing URL fragment with SCM revision information)'.format(source, nvr, koji_url) ) pkg_name = url.path.split('/')[-1] pkg_name = re.sub(r'\.git$', '', pkg_name) return namespace, pkg_name, rev
def _error(resp): msg = "Couldn't fetch %s as ActivityStreams 2" % url logging.warning(msg) err = BadGateway(msg) err.requests_response = resp raise err
def application(environ, start_response): headers = list(EnvironHeaders(environ).items()) headers[:] = [(k, v) for k, v in headers if not is_hop_by_hop_header(k) and k.lower() not in ( "content-length", "host")] headers.append(("Connection", "close")) if opts["host"] == "<auto>": headers.append(("Host", target.ascii_host)) elif opts["host"] is None: headers.append(("Host", environ["HTTP_HOST"])) else: headers.append(("Host", opts["host"])) headers.extend(opts["headers"].items()) remote_path = path if opts["remove_prefix"]: remote_path = "%s/%s" % ( target.path.rstrip("/"), remote_path[len(prefix):].lstrip("/"), ) content_length = environ.get("CONTENT_LENGTH") chunked = False if content_length not in ("", None): headers.append(("Content-Length", content_length)) elif content_length is not None: headers.append(("Transfer-Encoding", "chunked")) chunked = True try: if target.scheme == "http": con = client.HTTPConnection(target.ascii_host, target.port or 80, timeout=self.timeout) elif target.scheme == "https": con = client.HTTPSConnection( target.ascii_host, target.port or 443, timeout=self.timeout, context=opts["ssl_context"], ) else: raise RuntimeError( "Target scheme must be 'http' or 'https', got '{}'.". format(target.scheme)) con.connect() remote_url = url_quote(remote_path) querystring = environ["QUERY_STRING"] if querystring: remote_url = remote_url + "?" + querystring con.putrequest(environ["REQUEST_METHOD"], remote_url, skip_host=True) for k, v in headers: if k.lower() == "connection": v = "close" con.putheader(k, v) con.endheaders() stream = get_input_stream(environ) while 1: data = stream.read(self.chunk_size) if not data: break if chunked: con.send(b"%x\r\n%s\r\n" % (len(data), data)) else: con.send(data) resp = con.getresponse() except socket.error: from werkzeug.exceptions import BadGateway return BadGateway()(environ, start_response) start_response( "%d %s" % (resp.status, resp.reason), [(k.title(), v) for k, v in resp.getheaders() if not is_hop_by_hop_header(k)], ) def read(): while 1: try: data = resp.read(self.chunk_size) except socket.error: break if not data: break yield data return read()
def post(self): if request.headers.get('Tus-Resumable') is None: raise BadRequest( 'Received file upload for unsupported file transfer protocol') # Validate the file size file_size = request.headers.get('Upload-Length') if not file_size: raise BadRequest('Received file upload of unspecified size') file_size = int(file_size) max_file_size = Config.MAX_CONTENT_LENGTH if file_size > max_file_size: raise RequestEntityTooLarge( f'The maximum file upload size is {max_file_size/1024/1024}MB.' ) # Validate the file name and file type data = self.parser.parse_args() filename = data.get('filename') or request.headers.get('Filename') if not filename: raise BadRequest('File name cannot be empty') if filename.endswith(FORBIDDEN_FILETYPES): raise BadRequest('File type is forbidden') # Create the path string for this file document_guid = str(uuid.uuid4()) base_folder = Config.UPLOADED_DOCUMENT_DEST folder = data.get('folder') or request.headers.get('Folder') folder = os.path.join(base_folder, folder) file_path = os.path.join(folder, document_guid) pretty_folder = data.get('pretty_folder') or request.headers.get( 'Pretty-Folder') pretty_path = os.path.join(base_folder, pretty_folder, filename) # If the object store is enabled, send the post request through to TUSD to the object store object_store_path = None if Config.OBJECT_STORE_ENABLED: resp = requests.post(url=Config.TUSD_URL, headers={ key: value for (key, value) in request.headers if key != 'Host' }, data=request.data) if resp.status_code != requests.codes.created: message = f'Cannot upload file. Object store responded with {resp.status_code} ({resp.reason}): {resp._content}' current_app.logger.error( f'POST resp.request:\n{resp.request.__dict__}') current_app.logger.error(f'POST resp:\n{resp.__dict__}') current_app.logger.error(message) raise BadGateway(message) object_store_upload_resource = urlparse( resp.headers['Location']).path.split('/')[-1] object_store_path = Config.S3_PREFIX + object_store_upload_resource.split( '+')[0] cache.set(OBJECT_STORE_UPLOAD_RESOURCE(document_guid), object_store_upload_resource, TIMEOUT_24_HOURS) cache.set(OBJECT_STORE_PATH(document_guid), object_store_path, TIMEOUT_24_HOURS) # Else, create an empty file at this path in the file system else: try: if not os.path.exists(folder): os.makedirs(folder) with open(file_path, 'wb') as f: f.seek(file_size - 1) f.write(b'\0') except IOError as e: current_app.logger.error(e) raise InternalServerError('Unable to create file') # Cache data to be used in future PATCH requests cache.set(FILE_UPLOAD_SIZE(document_guid), file_size, TIMEOUT_24_HOURS) cache.set(FILE_UPLOAD_OFFSET(document_guid), 0, TIMEOUT_24_HOURS) cache.set(FILE_UPLOAD_PATH(document_guid), file_path, TIMEOUT_24_HOURS) # Create document record document = Document(document_guid=document_guid, full_storage_path=file_path, upload_started_date=datetime.utcnow(), file_display_name=filename, path_display_name=pretty_path, object_store_path=object_store_path) document.save() # Create and send response response = make_response(jsonify(document_manager_guid=document_guid), 201) response.headers['Tus-Resumable'] = TUS_API_VERSION response.headers['Tus-Version'] = TUS_API_SUPPORTED_VERSIONS response.headers[ 'Location'] = f'{Config.DOCUMENT_MANAGER_URL}/documents/{document_guid}' response.headers['Upload-Offset'] = 0 response.headers[ 'Access-Control-Expose-Headers'] = 'Tus-Resumable,Tus-Version,Location,Upload-Offset,Content-Type' response.autocorrect_location_header = False return response
def application(environ, start_response): headers = list(EnvironHeaders(environ).items()) headers[:] = [(k, v) for k, v in headers if not is_hop_by_hop_header(k) and k.lower() not in ( 'content-length', 'host')] headers.append(('Connection', 'close')) if opts['host'] == '<auto>': headers.append(('Host', target.ascii_host)) elif opts['host'] is None: headers.append(('Host', environ['HTTP_HOST'])) else: headers.append(('Host', opts['host'])) headers.extend(opts['headers'].items()) remote_path = path if opts['remove_prefix']: remote_path = '%s/%s' % (target.path.rstrip('/'), remote_path[len(prefix):].lstrip('/')) content_length = environ.get('CONTENT_LENGTH') chunked = False if content_length not in ('', None): headers.append(('Content-Length', content_length)) elif content_length is not None: headers.append(('Transfer-Encoding', 'chunked')) chunked = True try: if target.scheme == 'http': con = httplib.HTTPConnection(target.ascii_host, target.port or 80, timeout=self.timeout) elif target.scheme == 'https': con = httplib.HTTPSConnection(target.ascii_host, target.port or 443, timeout=self.timeout, context=opts['ssl_context']) con.connect() con.putrequest(environ['REQUEST_METHOD'], url_quote(remote_path), skip_host=True) for k, v in headers: if k.lower() == 'connection': v = 'close' con.putheader(k, v) con.endheaders() stream = get_input_stream(environ) while 1: data = stream.read(self.chunk_size) if not data: break if chunked: con.send(b'%x\r\n%s\r\n' % (len(data), data)) else: con.send(data) resp = con.getresponse() except socket.error: from werkzeug.exceptions import BadGateway return BadGateway()(environ, start_response) start_response( '%d %s' % (resp.status, resp.reason), [(k.title(), v) for k, v in resp.getheaders() if not is_hop_by_hop_header(k)]) def read(): while 1: try: data = resp.read(self.chunk_size) except socket.error: break if not data: break yield data return read()
), ( RequestTimeout("Request has timed out"), ( { "error": { "title": "Request has timed out", "status": 408, "details": "()", } }, 408, ), ), ( BadGateway("This is a Bad Gateway"), ( { "error": { "title": "This is a Bad Gateway", "status": 502, "details": "()", } }, 502, ), ), ], ) def test_error_handler(app, error, expected): resp = generic_error_handler(error)
def patch(self, document_guid): # Get and validate the file path (not required if object store is enabled) file_path = cache.get(FILE_UPLOAD_PATH(document_guid)) if not Config.OBJECT_STORE_ENABLED and ( file_path is None or not os.path.lexists(file_path)): raise NotFound('File does not exist') # Get and validate the upload offset request_offset = int(request.headers.get('Upload-Offset', 0)) file_offset = cache.get(FILE_UPLOAD_OFFSET(document_guid)) if request_offset != file_offset: raise Conflict( 'Upload offset in request does not match the file\'s upload offset' ) # Get and validate the content length and the expected new upload offset chunk_size = request.headers.get('Content-Length') if chunk_size is None: raise BadRequest('No Content-Length header in request') chunk_size = int(chunk_size) new_offset = file_offset + chunk_size file_size = cache.get(FILE_UPLOAD_SIZE(document_guid)) if new_offset > file_size: raise RequestEntityTooLarge( 'The uploaded chunk would put the file above its declared file size' ) # If the object store is enabled, send the patch request through to TUSD to the object store if Config.OBJECT_STORE_ENABLED: object_store_upload_resource = cache.get( OBJECT_STORE_UPLOAD_RESOURCE(document_guid)) url = f'{Config.TUSD_URL}{object_store_upload_resource}' headers = { key: value for (key, value) in request.headers if key != 'Host' } resp = requests.patch(url=url, headers=headers, data=request.data) if resp.status_code not in [ requests.codes.ok, requests.codes.no_content ]: message = f'Cannot upload file. Object store responded with {resp.status_code} ({resp.reason}): {resp._content}' current_app.logger.error( f'PATCH resp.request:\n{resp.request.__dict__}') current_app.logger.error(f'PATCH resp:\n{resp.__dict__}') current_app.logger.error(message) raise BadGateway(message) # Else, write the content to the file in the file system else: try: with open(file_path, 'r+b') as f: f.seek(file_offset) f.write(request.data) except IOError as e: current_app.logger.error(e) raise InternalServerError('Unable to write to file') # If the file upload is complete, set the upload completion date and delete cached data if new_offset == file_size: document = Document.find_by_document_guid(document_guid) document.upload_completed_date = datetime.utcnow() document.save() cache.delete(FILE_UPLOAD_SIZE(document_guid)) cache.delete(FILE_UPLOAD_OFFSET(document_guid)) cache.delete(FILE_UPLOAD_PATH(document_guid)) cache.delete(OBJECT_STORE_PATH(document_guid)) cache.delete(OBJECT_STORE_UPLOAD_RESOURCE(document_guid)) # Else, the file upload is still in progress, update its upload offset in the cache else: cache.set(FILE_UPLOAD_OFFSET(document_guid), new_offset, TIMEOUT_24_HOURS) response = make_response('', 204) response.headers['Tus-Resumable'] = TUS_API_VERSION response.headers['Tus-Version'] = TUS_API_SUPPORTED_VERSIONS response.headers['Upload-Offset'] = new_offset response.headers[ 'Access-Control-Expose-Headers'] = 'Tus-Resumable,Tus-Version,Upload-Offset' return response