Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use of output formatter seems to affect required parameters #156

Closed
horvathcom opened this issue Feb 1, 2016 · 11 comments
Closed

Use of output formatter seems to affect required parameters #156

horvathcom opened this issue Feb 1, 2016 · 11 comments
Labels

Comments

@horvathcom
Copy link

I don't know why the presence of an output formatter would affect making sure required parameters are present...


'''
Both of these have a single parameter named "data".
If you call the one without the output formatter, it correctly notices the missing parameter.
If you call the one with the output formatter, it raises a server error.

'''
import hug

@hug.get('/without_output_formatter')
def with_output_formatter(data):
    ''' 
    If called without the required parameter, gives you ...
HTTP/1.0 400 Bad Request
Date: Mon, 01 Feb 2016 04:09:15 GMT
Server: WSGIServer/0.2 CPython/3.4.3+
content-length: 55
content-type: application/json

{
    "errors": {
        "data": "Required parameter not supplied"
    }
}

    '''
    return 'Without Output Formatter'

@hug.get('/with_output_formatter',output=hug.output_format.svg_xml_image)
def without_output_formatter(data):
    ''' 
    If called without the required parameter, gives you ...
HTTP/1.0 500 Internal Server Error
Content-Length: 59
Content-Type: text/plain
Date: Mon, 01 Feb 2016 04:09:09 GMT
Server: WSGIServer/0.2 CPython/3.4.3+

A server error occurred.  Please contact the administrator.


    ''' 
    return 'With Output Formatter'


@timothycrosley
Copy link
Collaborator

Hi @horvathcom,

The reason for the error is because your specifying you want to output the data in an svg_xml_image format, then the data your passing to that format is:

{
    "errors": {
        "data": "Required parameter not supplied"
    }
}

which the SVG output format can not handle by default, thus the confusion. To show why this makes sense, lets say your output_format was html, and the fields represented a form, clearly you would want the response still to be returned in an HTML output format, even though the function returns a dictionary of errors. Here is how to get around that with the current version of hug (1.9.9):

Change the output format on error:

@hug.format.content_type('image/svg+xml')
def svg_on_success(content, response):
    if isinstance(content, dict) and 'errors' in content:
        response.content_type = 'application/json'
        return hug.output_format.json(content)
    return hug.output_format.svg_xml_image(content)


@hug.get('/with_output_formatter', output=svg_on_success)
def with_output_formatter(data):
   pass

In hug 2.0.0 you will be able to do the following -

1 Transform errors to an SVG compatible format:

    def errors_to_svg(errors):
        return 'error.svg'

    @hug.get('/with_output_formatter', on_invalid=errors)
    def with_output_formatter(data):
        pass

2 Change the output format just for the case an error occurs

    @hug.get(output_invalid=hug.output.format.json, output=hug.output_format.svg_xml_image)
    def with_output_formatter(data):
       pass

3 Use smart redirects

    @hug.get(output=hug.output_format.svg_xml_image)
    def output_image(data):
        return data.to_svg()


    @hug.get():
    def endpoint_for_users(data):
       return output_image # at this point we know data is present

@timothycrosley
Copy link
Collaborator

Let me know if this makes sense and if you have any input into a better direction to use for this feature. Thanks!

~Timothy

@timothycrosley
Copy link
Collaborator

Also: FYI if your using develop branch - Options #1, and #3 are already implemented in the and I've opened an issue for #2 #157

@horvathcom
Copy link
Author

Option 3 works for me. As for the right solution, in the spirit of being as simple as possible but no simpler, I would have expected the error to be handled without being specified... in other words, where you have this above ...

output_invalid=hug.output.format.json

... I would have only expected an output_invalid parameter if it did something different then the default. Said another way, the output formatter would only have come into affect if there were no exceptions raised.

Having said that, I am so new to hug, I am not sure I have a sense of what it should be.

What I do know is that this is a very cool piece of work. We have lots of APIs that we are using RAML to define... if this thing had RAML for the documentation output, it would be unbelievable! I love the self-documentation feature, but we also provide RAML to an API gateway.

@timothycrosley
Copy link
Collaborator

Hi @horvathcom,

I agree with your sentiment and I'll be updating hug in the 2.0.0 release so that your initial API would have worked as you expected - you are correct that it should according to hug's defined mission statement. I've added an issue to support RAML documentation here: #163

@horvathcom
Copy link
Author

Oops, option 3 didn't work for me. It still sends back content-type: application/json when there aren't errors.

@horvathcom
Copy link
Author

This is the closest I can get...
`
''' This program creates an SVG QR code with the data passed as a parameter. '''
import qrcode
import qrcode.image.svg
import io
import hug

@hug.format.content_type('image/svg+xml')
def output_image(data):
'''The data parameter is the text to be encoded into the QR code'''
qr = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_H)
qr.add_data(data)
qr.make()
img = qr.make_image(image_factory=qrcode.image.svg.SvgImage)
svg_bytes = io.BytesIO()
img.save(svg_bytes)
svg_bytes.flush()
svg_bytes.seek(0)
return svg_bytes

@hug.get('/qrcode')
def get_qrcode_endpoint_for_users(data):
'''This is workaround to make sure the presence of the 'data' parameter is returned as JSON if not present. '''
return output_image(data)

`

@timothycrosley
Copy link
Collaborator

Hi @horvathcom,

option #2 only works on the development branch that is forming hug 2.0.0

For hug 1.9.9, the option listed above the enumerated list is the correct way to go:

''' This program creates an SVG QR code with the data passed as a parameter. '''
import qrcode
import qrcode.image.svg
import io
import hug


@hug.format.content_type('image/svg+xml')
def output_image(data, response):
    '''The data parameter is the text to be encoded into the QR code'''
    if isinstance(content, dict) and 'errors' in content:
        response.content_type = 'application/json'
        return hug.output_format.json(content)

    return hug.output_format.svg_xml_image(data)


@hug.get('/qrcode', output=output_image)
def get_qrcode_endpoint_for_users(data):
    '''Returns a qr code image'''
    qr = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_H)
    qr.add_data(data)
    qr.make()
    return qr.make_image(image_factory=qrcode.image.svg.SvgImage)

you can then reuse your output image formatter as needed.

Hope this is helpful.

Thanks!

~Timothy

@horvathcom
Copy link
Author

Sorry, I really appreciate the time you are taking, but it isn't making sense to me still....

Is "if isinstance(content, dict) and 'errors' in content:" meant to be a hint to me for what to do in an exception handler? Or is content a variable with magic meaning like response and request.

The qrcode modules all seem to want to force you to write to a file or io stream. It isn't clear to me how that translates into output formatters.

I was thinking of just doing this one in bottle since it is the only one I have with non-JSON content, but I can't figure out basic stuff for error handling.

I am wondering if I need some basic falcon knowledge?

@timothycrosley
Copy link
Collaborator

Hi @horvathcom,

No worries! I'm always glad to help.

Here is the total code sample that should work unedited to do what you want to accomplish, assuming you have the qrcode library installed:

''' This program creates an SVG QR code with the data passed as a parameter. '''
import qrcode
import qrcode.image.svg
import io
import hug


@hug.format.content_type('image/svg+xml')
def qr_image(data, response):
    '''The data parameter is the text to be encoded into the QR code'''
    if isinstance(data, dict) and 'errors' in data:
        response.content_type = 'application/json'
        return hug.output_format.json(data)

    svg_bytes = io.BytesIO()
    data.save(svg_bytes)
    svg_bytes.flush()
    svg_bytes.seek(0)
    return svg_bytes


@hug.get('/qrcode', output=qr_image)
def get_qrcode_endpoint_for_users(data):
    '''Returns a qr code image'''
    qr = qrcode.QRCode(error_correction=qrcode.constants.ERROR_CORRECT_H)
    qr.add_data(data)
    qr.make()
    return qr.make_image(image_factory=qrcode.image.svg.SvgImage)

Again, in this example you can reuse the qr_image handler anywhere you need to return a qr image. For your first question, I was rushing to provide an answer and didn't test my code, no magic involved there just was mislabeled the variable content instead of data as was passed in. Hopefully this helps :)

~Timothy

@timothycrosley
Copy link
Collaborator

And to answer your second question, output handlers sole job is essentially to turn the data returned by your function (get_qrcode_endpoint_for_user) into a bytes object and or stream as these are the objects that the wsgi spec behind all Python Web Frameworks allows for. In addition to this it has a secondary goal of attaching a content type to the response:

so a hug output formatting function always looks like

@hug.content_type(default_http_content_type_for_format):
def output_formatter(data_as_python_object):
    return code_to_transform_data_as_python_object_into_bytes_or_stream

I hope this makes sense.

~Timothy

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants