async def request_wrapper(request: web.Request, **kwargs) -> Union[Callable, web.Response]: errors = {} if path_schema: try: path = path_schema.load(dict(request.match_info)) request["path"] = path except ValidationError as e: errors["path"] = e.messages if query_schema: try: query = query_schema.load(dict(request.query)) request["query"] = query except ValidationError as e: errors["query"] = e.messages if body_schema: try: body = body_schema.load(await request.json()) request["body"] = body except JSONDecodeError as e: raise web.HTTPUnsupportedMediaType() except ValidationError as e: errors["body"] = e.messages if errors: validation_error_schema = HTTPValidationErrorSchema() data = validation_error_schema.dump({"detail": errors}) raise web.HTTPUnprocessableEntity( text=json.dumps(data), content_type="application/json") return await handler(request, **kwargs)
def _try_client_response(self, headers, parsed): """ Sends a response back to the client based on Accept header Defaults to JSON """ media_msg = 'The requested media type is unsupported' mime_type = None sub_type = None try: accept_types = headers['Accept'] mime_type, sub_type, _, _ = parse_mimetype(accept_types) except KeyError: pass if mime_type == 'application' and sub_type == 'octet-stream': return web.Response( content_type='application/octet-stream', body=parsed.SerializeToString() ) if ((mime_type in ['application', '*'] or mime_type is None) and (sub_type in ['json', '*'] or sub_type is None)): return web.Response( content_type='application/json', text=MessageToJson(parsed) ) raise web.HTTPUnsupportedMediaType(reason=media_msg)
async def http_error_handler(req: Request, handler: Callable) -> Response: """Middleware for handling exceptions received from the API methods. :param req: A request instance :param handler: A request handler :raises: Reformatted HTTP Exceptions :returns: Successful requests unaffected """ try: response = await handler(req) return response except web.HTTPError as error: details = _json_exception(error.status, error, req.url) LOG.error(details) c_type = "application/problem+json" if error.status == 400: raise web.HTTPBadRequest(text=details, content_type=c_type) elif error.status == 401: raise web.HTTPUnauthorized( headers={"WWW-Authenticate": 'OAuth realm="/", charset="UTF-8"'}, text=details, content_type=c_type ) elif error.status == 403: raise web.HTTPForbidden(text=details, content_type=c_type) elif error.status == 404: raise web.HTTPNotFound(text=details, content_type=c_type) elif error.status == 415: raise web.HTTPUnsupportedMediaType(text=details, content_type=c_type) elif error.status == 422: raise web.HTTPUnprocessableEntity(text=details, content_type=c_type) else: raise web.HTTPServerError()
async def wrapper(request): # Check media type if request.content_type != 'application/json': raise web.HTTPUnsupportedMediaType( text=( 'Invalid Content-Type "{}". ' 'Only "application/json" is supported.' ).format(request.content_type) ) # Parse JSON request body = await request.text() try: payload = loads(body) except ValueError: log.error('Invalid JSON payload:\n{}'.format(body)) raise web.HTTPBadRequest( text='Invalid JSON payload' ) # Log request and responses log.info('Request:\n{}'.format(pformat(payload))) response = await handler(request, payload) log.info('Response:\n{}'.format(pformat(response))) # Convert dictionaries to JSON responses if isinstance(response, dict): return web.json_response(response) return response
def _try_client_response(headers, parsed): """ Used by pre-spec /state and /batches routes Should be removed when routes are updated to spec """ media_msg = 'The requested media type is unsupported' mime_type = None sub_type = None try: accept_types = headers['Accept'] mime_type, sub_type, _, _ = parse_mimetype(accept_types) except KeyError: pass if mime_type == 'application' and sub_type == 'octet-stream': return web.Response( content_type='application/octet-stream', body=parsed.SerializeToString() ) if ((mime_type in ['application', '*'] or mime_type is None) and (sub_type in ['json', '*'] or sub_type is None)): return web.Response( content_type='application/json', text=MessageToJson(parsed) ) raise web.HTTPUnsupportedMediaType(reason=media_msg)
def abort(code): if code == 400: return web.HTTPBadRequest() elif code == 401: return web.HTTPUnauthorized() elif code == 402: return web.HTTPPaymentRequired() elif code == 403: return web.HTTPForbidden() elif code == 404: return web.HTTPNotFound() elif code == 405: return web.HTTPMethodNotAllowed() elif code == 406: return web.HTTPNotAcceptable() elif code == 407: return web.HTTPProxyAuthenticationRequired() elif code == 408: return web.HTTPRequestTimeout() elif code == 409: return web.HTTPConflict() elif code == 410: return web.HTTPGone() elif code == 411: return web.HTTPLengthRequired() elif code == 412: return web.HTTPPreconditionFailed() elif code == 413: return web.HTTPRequestEntityTooLarge() elif code == 414: return web.HTTPRequestURITooLong() elif code == 415: return web.HTTPUnsupportedMediaType() elif code == 416: return web.HTTPRequestRangeNotSatisfiable() elif code == 417: return web.HTTPExpectationFailed() elif code == 421: return web.HTTPMisdirectedRequest() elif code == 422: return web.HTTPUnprocessableEntity() elif code == 424: return web.HTTPFailedDependency() elif code == 426: return web.HTTPUpgradeRequired() elif code == 428: return web.HTTPPreconditionRequired() elif code == 429: return web.HTTPTooManyRequests() elif code == 431: return web.HTTPRequestHeaderFieldsTooLarge() elif code == 451: return web.HTTPUnavailableForLegalReasons() else: return web.HTTPBadRequest()
async def create_request(self, request): params = await request.post() wallet = self.daemon.wallet if 'amount_sat' not in params or not params['amount_sat'].isdigit(): raise web.HTTPUnsupportedMediaType() amount = int(params['amount_sat']) message = params['message'] or "donation" payment_hash = await wallet.lnworker._add_invoice_coro(amount, message, 3600) key = payment_hash.hex() raise web.HTTPFound(self.root + '/pay?id=' + key)
async def put(self): if_match = self.request.headers.get('if-match', '') if_none_match = self.request.headers.get('if-none-match', '') if if_match == '' and if_none_match == '': raise web.HTTPPreconditionRequired() assert_preconditions(self.request, await self.etag()) if not re.match(r'application/(?:hal\+)?json(?:$|;)', self.request.content_type): raise web.HTTPUnsupportedMediaType() try: request_body_json = json_loads(await self.request.text()) except: raise web.HTTPBadRequest() # self.request.app['swagger'].validate_definition('Account', request_body_json) existing_roles = set( self.request.app['config']['authz_admin']['roles'].keys()) try: roles = request_body_json['_links']['role'] assert isinstance(roles, list) except: raise web.HTTPBadRequest( text="No '#/_links/role' array in request.") from None new_roles = set() try: for link_object in roles: role = web.URL(link_object['href']).name assert role in existing_roles new_roles.add(role) except: raise web.HTTPBadRequest( text= "Not all roles are valid HALJSON link objects to an existing role." ) from None if await self.data() is None: try: log_id = await database.create_account(self.request, self['account'], new_roles) except database.PreconditionFailed: raise web.HTTPPreconditionFailed() from None status = 201 headers = { 'Location': self.rel_url.raw_path, 'ETag': etag_from_int(log_id) } else: try: log_id = await database.update_account(self.request, self, new_roles) except database.PreconditionFailed: raise web.HTTPPreconditionFailed() from None status = 204 headers = {'ETag': etag_from_int(log_id)} return web.Response(status=status, headers=headers)
async def handle_post(request): if request.content_type != 'application/json': raise web.HTTPUnsupportedMediaType( text='Media type needs to be application/json') body_json = await request.json() resp_json = { key: value for key, value in body_json.items() if key.lower().startswith('a') } return web.Response(text=json.dumps(resp_json), content_type='application/json')
async def create_request(self, request): raise NotImplementedError() # FIXME code here is broken params = await request.post() wallet = self.wallet if 'amount_sat' not in params or not params['amount_sat'].isdigit(): raise web.HTTPUnsupportedMediaType() amount = int(params['amount_sat']) message = params['message'] or "donation" payment_hash = wallet.lnworker.add_request(amount_sat=amount, message=message, expiry=3600) key = payment_hash.hex() raise web.HTTPFound(self.root + '/pay?id=' + key)
async def _format_data_to_update_and_add_to_db(self, schema_type: str, accession_id: str, data: str) -> str: """Raise not implemented. Patch update for XML not supported :param schema_type: Schema type of the object to replace. :param accession_id: Identifier of object to replace. :param data: Original xml content :raises: HTTPUnsupportedMediaType """ reason = "XML patching is not possible." raise web.HTTPUnsupportedMediaType(reason=reason)
async def handler(request: Request) -> Response: post = await request.post() if 'file' not in post: return web.HTTPBadRequest(text='No file in request') if not post['file'].content_type.startswith('image/'): return web.HTTPUnsupportedMediaType() filename = post['file'].filename file_stream = post['file'].file image = file_stream.read() with open(test_data_folder / filename, 'rb') as fp: image_original = fp.read() if len(image) == len(image_original) and image == image_original: return web.HTTPOk() return web.HTTPBadRequest(text='Original and received images do not match')
async def login_post(request): try: data = await request.json() except JSONDecodeError: raise web.HTTPUnsupportedMediaType(text="Payload could not be parsed as JSON") if not check_required_keys(data, "user_name", "user_pass"): raise web.HTTPUnprocessableEntity(text="Payload missing required parameter(s)") user_name, user_pass = data["user_name"], data["user_pass"] user_dict = request.app["users"] if user_name not in user_dict or user_pass != user_dict[user_name]: raise web.HTTPUnauthorized(text="Invalid user name and/or password") return web.json_response({"token": "abcdefghijklmnopqrstuvwxyz"})
def unpublish_post(self, request): """De-register a service in the service directory :param request: incoming HTTP request :type request: aiohttp.Request :returns: A HTTP response :rtype: aiohttp.web.Response """ content_handlers = { 'application/json': services.Service.from_json, 'application/xml': services.Service.from_xml, } try: handler = content_handlers[request.content_type] except KeyError: self.log.info('Unhandled Content-Type: %s', request.content_type) raise web.HTTPUnsupportedMediaType( reason='Unhandled Content-Type: %s' % request.content_type) try: text = yield from request.text() self.log.debug('Service text: %s', text) service = handler(text) except ValueError: # bad input raise web.HTTPBadRequest(reason='Invalid data, expected service') name = service.name if not name: # bad input raise web.HTTPBadRequest(reason='Missing service name') try: self._directory.unpublish(name=name) except self._directory.DoesNotExist: self.log.info('Service %s is not published', name) raise web.HTTPBadRequest(reason='Service %s is not published' % (name, )) self.log.info('Unpublish %s OK', name) payload = 'Unpublish OK' code = web.HTTPOk.status_code return web.Response(body=payload.encode('utf-8'), status=code, content_type='text/plain', charset='utf-8')
async def register_post(request): try: data = await request.json() except JSONDecodeError: raise web.HTTPUnsupportedMediaType(text="Payload is not JSON") if not check_required_keys(data, "user_name", "user_pass"): raise web.HTTPUnprocessableEntity(text="Payload missing required parameter(s)") user_name, user_pass = data["user_name"], data["user_pass"] user_dict = request.app["users"] if user_name in user_dict: raise web.HTTPUnprocessableEntity(text="User name already registered") user_dict[user_name] = user_pass raise web.HTTPCreated(text="User created")
async def Event(request: web.Request) -> EventType: user_agent = request.headers.get('User-Agent', None) if not user_agent.startswith('GitHub-Hookshot/'): raise web.HTTPNotAcceptable(reason='User agent looks incorrect') content_type = request.headers.get('Content-Type', None) if not content_type == 'application/json': raise web.HTTPUnsupportedMediaType(reason='Not a JSON payload') payload = await request.json() event_type = request.headers.get('X-GitHub-Event', None) key = 'issue' if event_type == 'issues' else event_type return EventType( app=request.app, key=key, type=event_type, guid=request.headers.get('X-GitHub-Delivery', None), signature=request.headers.get('X-Hub-Signature', None), user_agent=user_agent, content_type=content_type, payload=payload, action=payload.get('action', None), )
async def patch_object(self, req: Request) -> Response: """Update metadata object in database. We do not support patch for XML. :param req: PATCH request :raises: HTTPUnauthorized if object is in published folder :returns: JSON response containing accessionId for submitted object """ schema_type = req.match_info["schema"] accession_id = req.match_info["accessionId"] self._check_schema_exists(schema_type) collection = f"draft-{schema_type}" if req.path.startswith("/drafts") else schema_type db_client = req.app["db_client"] operator: Union[Operator, XMLOperator] if req.content_type == "multipart/form-data": reason = "XML patching is not possible." raise web.HTTPUnsupportedMediaType(reason=reason) else: content = await self._get_data(req) operator = Operator(db_client) await operator.check_exists(collection, accession_id) await self._handle_check_ownedby_user(req, collection, accession_id) folder_op = FolderOperator(db_client) exists, _, published = await folder_op.check_object_in_folder(collection, accession_id) if exists: if published: reason = "Published objects cannot be updated." LOG.error(reason) raise web.HTTPUnauthorized(reason=reason) accession_id = await operator.update_metadata_object(collection, accession_id, content) body = json.dumps({"accessionId": accession_id}) LOG.info(f"PATCH object with accession ID {accession_id} in schema {collection} was successful.") return web.Response(body=body, status=200, content_type="application/json")
async def put_object(self, req: Request) -> Response: """Replace metadata object in database. For JSON request we don't allow replacing in the DB. :param req: PUT request :raises: HTTPUnsupportedMediaType if JSON replace is attempted :returns: JSON response containing accessionId for submitted object """ schema_type = req.match_info["schema"] accession_id = req.match_info["accessionId"] self._check_schema_exists(schema_type) collection = f"draft-{schema_type}" if req.path.startswith("/drafts") else schema_type db_client = req.app["db_client"] content: Union[Dict, str] operator: Union[Operator, XMLOperator] if req.content_type == "multipart/form-data": files = await _extract_xml_upload(req, extract_one=True) content, _ = files[0] operator = XMLOperator(db_client) else: content = await self._get_data(req) if not req.path.startswith("/drafts"): reason = "Replacing objects only allowed for XML." LOG.error(reason) raise web.HTTPUnsupportedMediaType(reason=reason) operator = Operator(db_client) await operator.check_exists(collection, accession_id) await self._handle_check_ownedby_user(req, collection, accession_id) accession_id = await operator.replace_metadata_object(collection, accession_id, content) body = json.dumps({"accessionId": accession_id}) LOG.info(f"PUT object with accession ID {accession_id} in schema {collection} was successful.") return web.Response(body=body, status=200, content_type="application/json")
async def handle_request(self, request: web.Request) -> web.Response: request_time = int(time() * 1000) try: # Check if the Content-Type and Accept headers both are presented # and contain 'application/json' if request.headers["Content-Type"] != "application/json": raise web.HTTPUnsupportedMediaType( reason="Invalid Content-Type") if request.headers["Accept"] != "application/json": raise web.HTTPNotAcceptable(reason="Invalid Accept header") except KeyError as exp: reason = "{} header is required".format(exc_message(exp)) raise web.HTTPNotAcceptable(reason=reason) try: request_data = await request.json() except json.JSONDecodeError: exp = JsonRpcError(Errors.PARSE_ERROR) self.log_request(request, request_time, "invalid", exp) return exp.http_response() try: if isinstance(request_data, list): response_data = await self.process_batch_rpc(request_data) self.log_request(request, request_time) return web.json_response(response_data) response_data = await self.process_single_rpc(request_data) self.log_request(request, request_time, request_data["method"]) return web.json_response(response_data) except JsonRpcError as exp: exp.set_context(request_data) self.log_request(request, request_time, request_data.get("method", "unknown/invalid"), exp) return exp.http_response()
async def _rpc_handle( self, http_request: web.Request, dispatcher: pjrpc.server.AsyncDispatcher) -> web.Response: """ Handles JSON-RPC request. :param http_request: :py:class:`aiohttp.web.Response` :returns: :py:class:`aiohttp.web.Request` """ if http_request.content_type not in pjrpc.common.REQUEST_CONTENT_TYPES: raise web.HTTPUnsupportedMediaType() try: request_text = await http_request.text() except UnicodeDecodeError as e: raise web.HTTPBadRequest() from e response_text = await dispatcher.dispatch(request_text, context=http_request) if response_text is None: return web.Response() else: return web.json_response(text=response_text)
def route(request): if request.content_type != "multipart/form-data": return web.HTTPBadRequest() query_parameters = request.rel_url.query if "token" in query_parameters: room = toolbox.token_verify(query_parameters["token"]) if room is None: return web.HTTPBadRequest() elif not re.search(r'^\d{6}$', room): return web.HTTPBadRequest() else: return web.HTTPBadRequest() try: reader = yield from request.multipart() except: return web.HTTPBadRequest() data = yield from reader.next() photo_dir = os.path.join(request.app["photo_dir"], room) if os.path.exists(photo_dir) == 0: os.mkdir(photo_dir) temp_path = '' size = 0 suffix = '' hash_calc = hashlib.md5() while True: try: chunk = yield from data.read_chunk() # 8192 bytes by default except: return web.HTTPBadRequest() if not chunk: break if size == 0: if len(chunk) < 4: return web.HTTPUnsupportedMediaType( reason="unsupported file type") # top_bytes = chunk[0:4].hex().upper() top_bytes = ''.join('{:02x}'.format(x) for x in chunk[0:4]).upper() if top_bytes[0:6] == 'FFD8FF': suffix = "jpg" elif top_bytes[0:8] == '89504E47': suffix = "png" elif top_bytes[0:8] == '47494638': suffix = "gif" else: return web.HTTPUnsupportedMediaType( reason="unsupported file type") while True: temp_name = str(int(time.time())) + str(random.randint( 0, 9999)).zfill(4) temp_path = os.path.join(photo_dir, temp_name) if not os.path.exists(temp_path): file = open(temp_path, 'wb') break size = size + len(chunk) file.write(chunk) hash_calc.update(chunk) if size / 1048576 > 3: # size limit 3MB file.close() os.remove(temp_path) return web.HTTPRequestEntityTooLarge(reason="file size overflow") file.close() hash_value = hash_calc.hexdigest() formal_name = hash_value + "." + suffix formal_path = os.path.join(photo_dir, formal_name) if os.path.exists(formal_path) != 0: os.remove(temp_path) else: os.rename(temp_path, formal_path) return web.Response(text=formal_name, # headers={'Access-Control-Allow-Origin':'*'} )
async def post(self) -> web.Response: """Validate route function.""" cache = self.request.app["cache"] logging.debug( f"Got following content-type-headers: {self.request.headers[hdrs.CONTENT_TYPE]}." ) if "multipart/" not in self.request.headers[hdrs.CONTENT_TYPE].lower(): raise web.HTTPUnsupportedMediaType( reason= f"multipart/* content type expected, got {hdrs.CONTENT_TYPE}.") # Iterate through each part of MultipartReader data_graph_url = None data_graph = None shapes_graph = None shapes_graph_url = None ontology_graph = None ontology_graph_url = None config = None data_graph_matrix = dict() shapes_graph_matrix = dict() async for part in (await self.request.multipart()): logging.debug(f"part.name {part.name}.") if Part(part.name) is Part.CONFIG: # Get config: config_json = await part.json() logging.debug(f"Got config: {config_json}.") if config_json: config = _create_config(config_json) pass # Data graph, url: if Part(part.name) is Part.DATA_GRAPH_URL: # Get data graph from url: data_graph_url = (await part.read()).decode() logging.debug( f"Got reference to data graph with url: {data_graph_url}.") data_graph_matrix[part.name] = data_graph_url pass # Data graph, file: if Part(part.name) is Part.DATA_GRAPH_FILE: # Process any files you uploaded logging.debug( f"Got input data graph with filename: {part.filename}.") try: data_graph = (await part.read()).decode() except ValueError: raise web.HTTPBadRequest( reason="Data graph file is not readable.") from None # logging.debug(f"Content of {part.filename}:\n{data_graph}.") if part.filename: data_graph_matrix[part.name] = part.filename pass # Shapes graph, url: if Part(part.name) is Part.SHAPES_GRAPH_URL: # Get shapes graph from url: shapes_graph_url = (await part.read()).decode() logging.debug( f"Got reference to shapes graph with url: {shapes_graph_url}." ) shapes_graph_matrix[part.name] = shapes_graph_url pass # Shapes graph, file: if Part(part.name) is Part.SHAPES_GRAPH_FILE: # Process any files you uploaded logging.debug( f"Got input shapes graph with filename: {part.filename}.") try: shapes_graph = (await part.read()).decode() except ValueError: raise web.HTTPBadRequest( reason="Shapes graph file is not readable.") from None # logging.debug(f"Content of {part.filename}:\n{shapes_graph}.") if part.filename: shapes_graph_matrix[part.name] = part.filename pass # Ontology graph, url: if Part(part.name) is Part.ONTOLOGY_GRAPH_URL: # Get ontology graph from url: ontology_graph_url = (await part.read()).decode() logging.debug( f"Got reference to ontology graph with url: {ontology_graph_url}." ) pass # Ontology graph, file: if Part(part.name) is Part.ONTOLOGY_GRAPH_FILE: # Process any files you uploaded logging.debug( f"Got input ontology graph with filename: {part.filename}." ) try: ontology_graph = (await part.read()).decode() except ValueError: raise web.HTTPBadRequest( reason="Ontology graph file is not readable." ) from None # check if we got any input: # validate data-graph input: if len(data_graph_matrix) == 0: raise web.HTTPBadRequest(reason="No data graph in input.") elif len(data_graph_matrix) > 1: logging.debug(f"Ambigious user input: {data_graph_matrix}.") raise web.HTTPBadRequest(reason="Multiple data graphs in input.") # validate shape-graph input: if len(shapes_graph_matrix) == 0: raise web.HTTPBadRequest(reason="No shapes graph in input.") elif len(shapes_graph_matrix) > 1: logging.debug(f"Ambigious user input: {shapes_graph_matrix}.") raise web.HTTPBadRequest(reason="Multiple shapes graphs in input.") # We have got data, now validate: try: # instantiate validator service: service = await ValidatorService.create( cache=cache, data_graph_url=data_graph_url, data_graph=data_graph, shapes_graph_url=shapes_graph_url, shapes_graph=shapes_graph, ontology_graph_url=ontology_graph_url, ontology_graph=ontology_graph, config=config, ) except FetchError as e: logging.debug(traceback.format_exc()) raise web.HTTPBadRequest(reason=str(e)) from None except SyntaxError as e: logging.debug(traceback.format_exc()) raise web.HTTPBadRequest(reason=str(e)) from None # validate: ( conforms, data_graph, ontology_graph, results_graph, ) = await service.validate(cache=cache) # Try to content-negotiate: logging.debug( f"Got following accept-headers: {self.request.headers[hdrs.ACCEPT]}." ) content_type = "text/turtle" # default if "*/*" in self.request.headers[hdrs.ACCEPT]: pass # use default elif self.request.headers[ hdrs.ACCEPT]: # we try to serialize according to accept-header content_type = self.request.headers[hdrs.ACCEPT] response_graph = Graph() response_graph += results_graph response_graph += data_graph if config and config.include_expanded_triples is True: response_graph += ontology_graph try: return web.Response( body=response_graph.serialize(format=content_type), content_type=content_type, ) except PluginException: # rdflib raises PluginException, in this context imples 406 logging.debug(traceback.format_exc()) raise web.HTTPNotAcceptable() from None # 406
async def get_post_data(cls, request): """extract input JSON from POST request""" if request.headers.get(hdrs.CONTENT_TYPE, None) == 'application/json': return await request.json() raise web.HTTPUnsupportedMediaType( reason="Application/json media type is needed")
async def service_submission(request: web.Request): reader = MultipartReader.from_response(request) data = None filedata = None # Read multipart email while True: part = await reader.next() # pylint: disable=not-callable if part is None: break if part.headers[hdrs.CONTENT_TYPE] == "application/json": data = await part.json() continue if part.headers[hdrs.CONTENT_TYPE] == "application/zip": filedata = await part.read(decode=True) # Validate max file size maxsize = 10 * 1024 * 1024 # 10MB actualsize = len(filedata) if actualsize > maxsize: raise web.HTTPRequestEntityTooLarge(maxsize, actualsize) filename = part.filename continue raise web.HTTPUnsupportedMediaType( reason=f"One part had an unexpected type: {part.headers[hdrs.CONTENT_TYPE]}" ) # data (dict) and file (bytearray) have the necessary information to compose the email support_email_address = request.app[APP_CONFIG_KEY]["smtp"]["sender"] is_real_usage = any( env in os.environ.get("SWARM_STACK_NAME", "") for env in ("production", "staging") ) db = get_storage(request.app) user = await db.get_user({"id": request[RQT_USERID_KEY]}) user_email = user.get("email") if not is_real_usage: support_email_address = user_email try: # NOTE: temporarily internal import to avoid render_and_send_mail to be interpreted as handler # TODO: Move outside when get_handlers_from_namespace is fixed from .login.utils import render_and_send_mail attachments = [("metadata.json", json.dumps(data, indent=4))] if filedata: attachments.append((filename, filedata)) # send email await render_and_send_mail( request, to=support_email_address, template=common_themed(EMAIL_TEMPLATE_NAME), context={ "user": user_email, "data": json2html.convert( json=json.dumps(data), table_attributes='class="pure-table"' ), "subject": "TEST: " * (not is_real_usage) + "New service submission", }, attachments=attachments, ) except Exception as exc: log.exception("Error while sending the 'new service submission' mail.") raise web.HTTPServiceUnavailable() from exc raise web.HTTPNoContent(content_type="application/json")