How to transmit custom data with an exception? - python-3.x

I am validating json request data for my service. There are many fields, each with their own validators. My plan is to turn the request into a frozen dataclass, if all validations are successful. A validation can fail for reasons that require additional data to explain the cause. If the request is invalid, I want to report this data back to the client so that the user knows why the request was unsuccessful.
Example: The request has a field with an array of fruit.
request = {
'fruit': [{'type':'apple', 'price':5}, {'type': 'banana', 'price': 7}]
}
To validate the fruit field I use a function validate_fruit(fruit: list) which checks the types and values. If there is a fruit with price > 5 I say the request is invalid. I send a response with an error return code and want to specify which fruit is too expensive.
Example: Here, return code 12 means "there are fruit that are too expensive". Error data should give details.
response = {
'return_code': 12
'error_data': ['banana']
}
I would like to use exceptions to implement this. So validate_fruit can raise an Exception with a dict that specifies the return code and additional error data.
I am thinking about
def validate_fruit(fruit: list):
failures = [elem['type'] for elem in fruit if elem['price'] > 5]
if failures:
raise ValueError(data={'error_data': failures, 'return_code': 12})
try:
validate_fruit(fruit)
except Exception as error:
if error.return_code == 12:
...
Has anyone had the same idea? How do you do this?

you can use something like:
try:
raise ValueError({'error_data': ['banana'], 'return_code': 12})
except Exception as ex:
if ex.args[0]["return_code"] == 12:
print("error 12")

Related

Ksql python library reading response of query error

I'm trying to read from ksql in python with this script:
from ksql import KSQLAPI
client = KSQLAPI('http://0.0.0.0:8088',)
columns = ['id INT','name VARCHAR']
client.create_stream(table_name='students', columns_type= columns, topic='students')
query = client.query("SELECT name FROM students WHERE id = 1 ")
for student in query:
print(student)
As a response from the library, I was expecting a sequence of objects, as the documentation says, printed by the generator. Instead, it returns me a string representing pieces of an array of objects, this:
[{"header":{"queryId":"transient_STUDENTS_5788262560238090205","schema":"`NAME` STRING"}},
{"row":{"columns":["Alex"]}},
]
It then throws a RuntimeError and a StopIteration
RuntimeError: generator raised StopIteration
So I handled the generator like this:
query = client.query("SELECT name FROM students WHERE id = 1 ")
next(query)
for student in query:
if student.strip() == ']': break
student = student.strip()[:-1]
student = json.loads(student)
print(student)
The question is, is there another way to run the query with the library and get another type of response? If not, how is the correct way to handle this generator response?

How to return two different error messages when querying the same model in Django

Take a look at the following:
def mutate(self, info, first_id, second_id):
try:
first = Model.objects.get(pk=first_id)
second = Model.objects.get(pk=second_id)
except Model.DoesNotExist:
return Exception('Object does not exist.')
else:
...
How can I return a custom error message depending on which of the ids actually does not exist? It's be nice to have something like:
{first_id} does not exist
I can't have two different except blocks because it's the same Model. What to do?
You can simply split up your query's in two statements:
def mutate(self, info, first_id, second_id):
try:
first = Model.objects.get(pk=first_id)
except Model.DoesNotExist:
raise Exception('Your first id {} Does not exist'.format(first_id))
try:
second = Model.objects.get(pk=second_id)
except Model.DoesNotExist:
raise Exception('Your second id {} Does not exist'.format(second_id))
...
PS: you need to raise exceptions. Not return them.

Python IMAP4 append fails silently

When appending a message to a folder that doesn't exist, there is no error thrown. It's hard to imagine that this is intended, what am I doing wrong?
mailbox = imaplib.IMAP4_SSL(host="foo")
mailbox.login("foo", "bar")
try:
mailbox.append("DOES_NOT_EXIST", '', imaplib.Time2Internaldate(time.time()), str(mail).encode("utf-8"))
except:
# Expecting to fail here, but it doesn't
# Message doesn't show up in any other folder either (expectedly)
As rightfully stated in the comments, this is actually intended behaviour indeed and documented.
Each command returns a tuple: (type, [data, ...]) where type is usually 'OK' or 'NO', and data is either the text from the command response, or mandated results from the command.
One way to catch the error therefore is:
status, data = mailbox.append("DOES_NOT_EXIST", '', imaplib.Time2Internaldate(time.time()), str(mail).encode("utf-8"))
if status == "NO":
# Catch it here

Python json.loads() failing to parse json string

I have a web service written in Python 3.4 that uses the Falcon framework. One particular method accepts a post of json values. My code:
try:
raw_json = req.stream.read()
except Exception as ex:
raise falcon.HTTPError(falcon.HTTP_400, 'Error', ex.message)
try:
result_json = json.loads(raw_json.decode('utf-8'))
except ValueError:
raise falcon.HTTPError(falcon.HTTP_400,
'Malformed JSON', 'Could not decode the request body. The JSON was incorrect.')
clientIp = result_json['c']
wpIp = result_json['w']
lan = result_json['l']
table = int(result_json['t'])
This code was working fine 9 months ago but currently throws the error: "list indices must be integers or slices, not str." I think it likely broke after a Python or Falcon package update.
The ouput of raw_json.decode('utf-8') looks ok, returning [{"w": "10.191.0.2", "c": "10.191.0.3", "l": "255.255.255.0", "t": "4"}]. I think json.loads() is the root of my problem. len(result_json) is returning 1 where I would expect 4. Is there an additional parameter needed for json.loads() to help it parse correctly? Or am I missing something else entirely?
Thanks,
Greg (Python noob)
The returned result [{"w": "10.191.0.2", "c": "10.191.0.3", "l": "255.255.255.0", "t": "4"}] is a json array, which is parsed into a python list. Therefore
result_json['c']
produces the the mentioned error. Maybe there was a change in the API and it now returns an array where it previously returned a json object.
This should work:
clientIp = result_json[0]['c']
...

404 when page number is too high

If this filter REST query results in no values it will return an HTTP 200 with empty results:
http://server/path/entities?field=value&page=1
This one will return an HTTP 404 instead
http://server/path/entities?field=value&page=2
Obviously, there is no second page of results. Can I configure django-rest to return an empty HTTP 200, rather than an HTTP 404 in this scenario?
The GUI allows the user to page forward, then change the filter criteria, which can request the second URL and trigger a HTTP 404 and a user error.
I can ask the GUI team to treat a 404 as an empty result set, but I would rather that this just return an empty HTTP 200 from the server.
You can create a custom pagination class that intercept NotFound exception raise in paginate_queryset() and return empty list instead
something like this
def paginate_queryset(self, queryset, request, view=None):
"""Checking NotFound exception"""
try:
return super(EmptyPagination, self).paginate_queryset(queryset, request, view=view)
except NotFound: # intercept NotFound exception
return list()
def get_paginated_response(self, data):
"""Avoid case when self does not have page properties for empty list"""
if hasattr(self, 'page') and self.page is not None:
return super(EmptyPagination, self).get_paginated_response(data)
else:
return Response(OrderedDict([
('count', None),
('next', None),
('previous', None),
('results', data)
]))
and in config file
just tell DRF to use this custom pagination class
'DEFAULT_PAGINATION_CLASS': 'apps.commons.serializers.EmptyPagination',
This is not (easily) possible, because the 404 is triggered as the result of a NotFound exception being raised, which will break out from the pagination logic. You could special case the NotFound exception in a custom exception handler, but you will be guessing based on the detail string. This isn't the best idea, as the message can change if
The message is changed in the DRF core translations
Your application is using translated strings
Which means that your application will suddenly return back to raising a 404 at some point in the future.
You're better off just having your GUI team treat a 404 as an empty result, or have them reset the page number when the filtering changes.

Resources