async def get(self, *args, **kwargs): if self.get_argument('full', 'false') == 'true': query_args = self._get_query_kwargs(kwargs) project, facts, links, urls = await asyncio.gather( self.postgres_execute( self.GET_FULL_SQL, query_args, 'get-{}'.format(self.NAME)), self.postgres_execute( self.GET_FACTS_SQL, query_args, 'get-project-facts'), self.postgres_execute( self.GET_LINKS_SQL, query_args, 'get-project-links'), self.postgres_execute( self.GET_URLS_SQL, query_args, 'get-project-urls')) if not project.row_count or not project.row: raise problemdetails.Problem( status_code=404, title='Item not found') output = project.row output.update({ 'facts': facts.rows, 'links': links.rows, 'urls': {row['environment']: row['url'] for row in urls.rows} }) self.send_response(output) else: await self._get(kwargs)
def get(self): sentinel = object() # safely detect missing query params status = int(self.get_query_argument('status', default='200')) raise_error = self.get_query_argument('raise_error', default=sentinel) if raise_error is not sentinel: query_args = [ arg for arg in self.request.query_arguments.keys() if arg not in ('raise_error', 'status') ] kwargs = {} for name in query_args: kwargs[name] = self.get_query_argument(name) if kwargs[name].lower() == 'none': kwargs[name] = None elif kwargs[name].startswith(('{', '[')): kwargs[name] = json.loads(kwargs[name]) raise problemdetails.Problem(status_code=status, **kwargs) else: kwargs = {} for name in ('detail', 'instance', 'title', 'type'): value = self.get_query_argument(name, sentinel) if value is not sentinel: if value.lower() == 'none': value = None kwargs[name] = value if status not in httputil.responses: kwargs['reason'] = 'Abnormal Status' self.send_error(status, **kwargs)
async def _post(self, kwargs) -> None: values = self.get_request_body() # Handle compound keys for child object CRUD if isinstance(self.ID_KEY, list): for key in self.ID_KEY: if key not in values and key in kwargs: values[key] = kwargs[key] elif self.ID_KEY not in values and self.ID_KEY in kwargs: values[self.ID_KEY] = kwargs[self.ID_KEY] # Set defaults of None for all fields in insert for name in self.FIELDS: if name not in values: values[name] = self.DEFAULTS.get(name) values['username'] = self._current_user.username result = await self.postgres_execute( self.POST_SQL, values, 'post-{}'.format(self.NAME)) if not result.row_count: self.logger.debug('No rows returned') raise problemdetails.Problem( status_code=500, title='Failed to create record') # Return the record as if it were a GET await self._get(self._get_query_kwargs(result.row))
async def _patch(self, kwargs): patch_value = self.get_request_body() result = await self.postgres_execute( self.GET_SQL, self._get_query_kwargs(kwargs), 'get-{}'.format(self.NAME)) if not result.row_count: raise problemdetails.Problem( status_code=404, title='Item not found') original = dict(result.row) for key in {'created_at', 'created_by', 'last_modified_at', 'last_modified_by'}: del original[key] for key, value in original.items(): if isinstance(value, uuid.UUID): original[key] = str(value) # Apply the patch to the current value patch = jsonpatch.JsonPatch(patch_value) updated = patch.apply(original) # Bail early if there are no changes if not {k: original[k] for k in original if k in updated and original[k] != updated[k]}: self._add_self_link(self.request.path) self._add_link_header() return self.set_status(304) if isinstance(self.ID_KEY, list): for key in self.ID_KEY: updated['current_{}'.format(key)] = kwargs[key] else: updated['current_{}'.format(self.ID_KEY)] = kwargs[self.ID_KEY] updated['username'] = self._current_user.username result = await self.postgres_execute( self.PATCH_SQL, updated, 'patch-{}'.format(self.NAME)) if not result.row_count: raise problemdetails.Problem( status_code=500, title='Failed to update record') # Send the new record as a response await self._get(self._get_query_kwargs(updated))
async def _delete(self, kwargs): result = await self.postgres_execute( self.DELETE_SQL, self._get_query_kwargs(kwargs), 'delete-{}'.format(self.NAME)) if not result.row_count: raise problemdetails.Problem( status_code=404, title='Item not found') self.set_status(204, reason='Item Deleted')
async def get(self, *args, **kwargs): if self.GET_SQL is None: self.logger.debug('GET_SQL not defined') raise problemdetails.Problem( status_code=405, title='Not Implemented') if self._respond_with_html: return self.render('index.html') await self._get(kwargs)
def get(self): age = self._extract_age() color = self._extract_color() if self.errors: raise problemdetails.Problem( **{ 'status_code': 400, 'type': 'https://example.net/validation-error', 'invalid-params': self.errors, }) self.write({'age': age, 'color': color})
def wrapped(self, *args, **kwargs): """Inner-wrapping of the decorator that performs the logic""" if not self._current_user or \ not self._current_user.has_permission(permission): if self._respond_with_html: return self.render('index.html') LOGGER.info('%r does not have the "%s" permission', self._current_user, permission) raise problemdetails.Problem( status_code=403, title='Unauthorized') return f(self, *args, **kwargs)
async def _get(self, kwargs): result = await self.postgres_execute( self.GET_SQL, self._get_query_kwargs(kwargs), 'get-{}'.format(self.NAME)) if not result.row_count or not result.row: raise problemdetails.Problem( status_code=404, title='Item not found') for key, value in result.row.items(): if isinstance(value, uuid.UUID): result.row[key] = str(value) self.send_response(result.row)
def _postgres_connection_check(self): """Ensures Postgres is connected, exiting the request in error if not :raises: problemdetails.Problem :raises: web.HTTPError """ if not self.application.postgres_is_connected: if problemdetails: raise problemdetails.Problem(status_code=503, title='Database Connection Error') raise web.HTTPError(503, reason='Database Connection Error')
async def prepare(self) -> None: await super().prepare() try: self.application.validate_request(self.request) except DeserializeError as err: self.logger.warning('Request failed to deserialize: %s', err) raise problemdetails.Problem( status_code=400, title='Bad Request', detail=str(err)) except InvalidSecurity as err: self.logger.debug('Invalid OpenAPI spec security: %s', err) raise problemdetails.Problem( status_code=500, title='OpenAPI security error', detail=str(err)) except OperationNotFound as err: raise problemdetails.Problem( status_code=405, title='Method Not Allowed', detail=str(err)) except InvalidContentType as err: raise problemdetails.Problem( status_code=415, title='Unsupported Media Type', detail=str(err)) except PathNotFound as err: self.logger.error('OpenAPI Spec Error: %s', err) raise problemdetails.Problem( status_code=500, title='OpenAPI Spec Error', detail=str(err)) except ValidateError as err: self.logger.warning('Request failed to validate: %s', err) raise problemdetails.Problem( status_code=400, title='Bad Request', detail='The request did not validate', errors=[str(e).split('\n')[0] for e in err.schema_errors])
def get(self, record_id): record = self.settings['database'].fetch(record_id) if record is None: raise problemdetails.Problem( status_code=404, type='/errors#error-customer-does-not-exist', title='Customer {0} does not exist'.format(record_id), instance=self.reverse_url('retrieve', record_id), ) self.set_header('Content-Type', 'application/json') self.set_status(200) self.write(self.settings['json-encoder'].encode(record))
def verify_funds(self, account, price): balance = self.get_balance(account) if price > balance: raise problemdetails.Problem( status_code=403, title='You do not have enough credit.', detail=(f'Your current balance is {balance}, but that ' f'costs {price}'), instance=self.reverse_url('account-handler', account), balance=balance, accounts=self.lookup_accounts(account), type='https://example.com/probs/out-of-credit', )
def post(self): content_type, params = cgi.parse_header( self.request.headers.get('Content-Type', 'application/json')) if content_type != 'application/json': raise problemdetails.Problem( status_code=415, title='Content type is not understood', detail='Cannot decode {0}, try application/json'.format( content_type), ) try: body = json.loads(self.request.body.decode('utf-8')) except (TypeError, ValueError, UnicodeDecodeError) as error: raise problemdetails.Problem( status_code=400, title='Failed to decode request', detail=str(error), failure=error, ) openapi = self.settings['openapi'] validator = jsonschema.Draft7Validator( schema=openapi['components']['schemas']['CustomerDetails']) all_errors = list(validator.iter_errors(body)) most_important = jsonschema.exceptions.best_match(all_errors) if most_important is not None: raise problemdetails.Problem( status_code=422, type='/errors#jsonschema-failure', title='Failed to process request', detail=most_important.message, failure=all_errors, ) record_id = self.settings['database'].insert(body) self.redirect(self.reverse_url('retrieve', record_id), status=303)
def get(self): logger = logging.getLogger('HttpBinHandler') url = 'http://httpbin.org/status/{0}'.format( self.get_query_argument('status', '500')) logger.info('retrieving %s', url) client = httpclient.AsyncHTTPClient() try: response = yield client.fetch(url) self.add_header('Content-Type', 'application/json') self.write(response.body) except httpclient.HTTPError as error: raise problemdetails.Problem(status_code=error.code, httpbin_headers=dict( error.response.headers.items()))
def on_postgres_error(self, metric_name: str, exc: Exception) -> typing.Optional[Exception]: """Invoked when an error occurs when executing a query If `tornado-problem-details` is available, :exc:`problemdetails.Problem` will be raised instead of :exc:`tornado.web.HTTPError`. Override for different error handling behaviors. Return an exception if you would like for it to be raised, or swallow it here. """ LOGGER.error('%s in %s for %s (%s)', exc.__class__.__name__, self.__class__.__name__, metric_name, str(exc).split('\n')[0]) if isinstance(exc, ConnectionException): if problemdetails: raise problemdetails.Problem(status_code=503, title='Database Connection Error') raise web.HTTPError(503, reason='Database Connection Error') elif isinstance(exc, asyncio.TimeoutError): if problemdetails: raise problemdetails.Problem(status_code=500, title='Query Timeout') raise web.HTTPError(500, reason='Query Timeout') elif isinstance(exc, errors.ForeignKeyViolation): if problemdetails: raise problemdetails.Problem(status_code=409, title='Foreign Key Violation') raise web.HTTPError(409, reason='Foreign Key Violation') elif isinstance(exc, errors.UniqueViolation): if problemdetails: raise problemdetails.Problem(status_code=409, title='Unique Violation') raise web.HTTPError(409, reason='Unique Violation') elif isinstance(exc, psycopg2.OperationalError): if problemdetails: raise problemdetails.Problem(status_code=503, title='Database Error') raise web.HTTPError(503, reason='Database Error') elif isinstance(exc, psycopg2.Error): if problemdetails: raise problemdetails.Problem(status_code=500, title='Database Error') raise web.HTTPError(500, reason='Database Error') return exc
async def delete(self, *args, **kwargs): if self.DELETE_SQL is None: self.logger.debug('DELETE_SQL not defined') raise problemdetails.Problem( status_code=405, title='Not Implemented') await self._delete(kwargs)
async def post(self, *args, **kwargs): if self.POST_SQL is None: self.logger.debug('POST_SQL not defined') raise problemdetails.Problem( status_code=405, title='Not Implemented') await self._post(kwargs)