What should I use to inject data into multiple view responses before JSON rendering?

17 views
Skip to first unread message

Pablo Díaz Ogni

unread,
Apr 10, 2024, 4:44:27 PMApr 10
to pylons-discuss
Hello guys! I'm building an API using pyramid + pyramid_openapi3 package to use openapi3 features such as request validation, the web interface, and have well documented the endpoints. I'm having trouble understanding the different ways I can use views, view_derivers, tweens, decorators, etc.

Let's say I have multiple views that return its own data as a dictionary/list and I want to wrap the response with more info before rendering to JSON:
{request: <request_metadata>, response: <the actual data returned by the view>}

I first tried with tweens, but that's post renderization so I have to deserialize and serialize the json. I also tried view_derivers and I was facing the same issue. I also saw that you can match a view with a Context such as an exception, but not sure if there is a way to use that Context in other way such as "all the View objects that have this specific attribute"

I also tried a decorator with wrapper(context, request), but i switched from functions with @view_config to use View classes with get() method instead, so I should change the approach of the decorator

I'm new to pyramid and it has its own way of plugging views so I would like to know what is the recommended way of doing this?

Jonathan Vanasco

unread,
Apr 11, 2024, 3:55:56 PMApr 11
to pylons-discuss
I don't know what is recommended, but have done similar things before.

One way I've done something similar is to use a custom renderer in the `@view_config`.

Another way I've accomplished this is to set a request attribute early on - either in a tween or in the view class instantiation or even a request attribute, and having my return value reflect this.

To illustrate the second example, in this project each view is configured to handle 2 different API endpoints - one that is human HTML, and another that is programmatic API for JSON.  If the route is accessed for JSON, the view returns a slightly different payload.

1- This code adds a "Request.wants_json" attribute:


2- This view returns a different payload, depending upon the wants_json attribute.


This was a quick, lazy and functional way to proceed.  If I had to do this again, I would probably use a single payload with a custom JSON renderer, and have a function in the custom JSON renderer to only render specific values from the payload (which would either be attached to the request, or listed in a namespace in the payload).

For your description though, I would probably just write a custom JSON renderer.

For example, here is a custom jsonp and gif renderer:

def jsonp_renderer_factory(info):
    def _render(value, system):
        value = py2json(value)
        request = system.get("request")
        if request is None:
            return value
        # JSONP is GET only, unless you do some weird shit that doesn't work reliably
        callback = request.GET.get("callback", None)
        if callback is None:
            ct_json = "application/json"
        else:
            ct_json = "application/javascript"
            value = "%s(%s)" % (callback, value)
        response = request.response
        if response.content_type == response.default_content_type:
            response.content_type = ct_json
        return value

    return _render

def gif_renderer_factory(info):
    def _render(value, system):
        request = system.get("request")
        if request is not None:
            response = request.response
            ct = response.content_type
            if ct == response.default_content_type:
                response.content_type = "image/gif"
        return value

    return _render

    config.add_renderer("jsonp", jsonp_renderer_factory)
    config.add_renderer("gif", gif_renderer_factory)



Pablo Díaz Ogni

unread,
Apr 16, 2024, 11:10:20 AMApr 16
to pylons-discuss
Hello, after a whole week trying different approaches I finally went with using BeforeEvent subscriber + a view deriver to add specific option on `request.environ` about which ones should enhance the response:

def my_view_deriver(view, info):
    def wrapper(context, request):
        if 'enhance_response' in info.options:
            request.environ['enhance_response'] = True
        return view(context, request)
    return wrapper

config.add_view_deriver(my_view_deriver)

@subscriber(BeforeEvent)
def enhance_response(event):
    request = event.get('request')
    if request.environ.get('enhance_response'):
        event.rendering_val = {
             'extra_key': 'something',
             'former_response': event.rendering_val,
        }
   # no need to return anything here

@view_config(..., enhance_response=True)
def my_view(context, request):
    return {'response': 'whatever'}


And that's it!
Reply all
Reply to author
Forward
0 new messages