'return' data from on_response/on_request event hooks


#1

Hey,

I have a get method as follows:

def get() -> str:
return “welcome”

And, I wanted to tweak the final get method response through on_response event hook something like below:

class SampleHook:
def on_response(self, ret: ReturnValue):
self.message = " To apistar"
ret = ret + self.message
return ret

The endpoint triggering get() method should return the response “welcome To apistar”

So basically on_response should be able to return the response either to get() or as a final response. Not sure if it is even possible to do that way.

This being just an example, a real project scenario could be something like I always want to perform data serialization at the end of every get request throughout all project endpoints. It would be nice to have the data serialization done under on_response event hook and return that serialized response (example: date/time serialization, removing keys with null/None values from JSON response,…etc). Likely on the similar basis on_request event hook could perform some pre-processing of data even before going to the core get method logic. All this would be possible only if we could ‘return’ something from event hooks.


#2

You can require r: Response and mutate it.

r.setValue(1)

#3

thanks for reply, but I didn’t understand what is require r:Response? where is setValue() defined?


#4

Just overwrite response.content and… That’s it. If str it will then render html otherwise json.


#5

I think we are going pretty strict on annotations here. Because it worked when I annotated the event hook. I’m not sure if I’m completely right, but according to my understanding overwriting response.content is just about not sufficient but also got to annotate the event hook.


#6

If you want a handler to Serialize a Type:
https://docs.apistar.com/api-guide/type-system/#serialization

If you want to change the response properties: override them with injected http.Response object

If you want to change ReturnValue, well, then:

A - ReturnValue it’s just a typing.TypeVar with no types associated (https://github.com/encode/apistar/blob/a58290d9876d2eb0f2028e6a08fdab7c4f8bd762/apistar/server/components.py#L49)
It has no methods, remember it’s just a TypeVar (https://docs.python.org/3/library/typing.html#typing.TypeVar)

In [2]: import typing

In [3]: return_value = typing.TypeVar("ReturnValue")

In [4]: return_value.setValue()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-4-1c02e9ca8e43> in <module>()
----> 1 return_value.setValue()

AttributeError: 'TypeVar' object has no attribute 'setValue'
In [5]: dir(return_value)
Out[5]:
['__bound__',
 '__call__',
 '__class__',
 '__constraints__',
 '__contravariant__',
 '__covariant__',
 '__delattr__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__instancecheck__',
 '__le__',
 '__lt__',
 '__module__',
 '__name__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__slots__',
 '__str__',
 '__subclasscheck__',
 '__subclasshook__',
 '__weakref__',
 '_eval_type',
 '_get_type_vars']

B - meaning it’s just useful for type check on injector to resolve the returned type (here: https://github.com/encode/apistar/blob/8b63e4a9b6dbe6a45c619e5ee9dc217cc81d2eda/apistar/server/injector.py#L41)

C - and returned values are serialized if they are of class Type(JSON), str (HTML, utf-8) or bytes (otherwise, JSON)

So if you wanna have control of return types, again:

  1. You create a Type and returns it in the handler signature: def handler() -> MyReturnType:, the content of the response will be validated and serialized against it
  2. You, in the hook, changes response content, if you have defined a Type put a dict in there and it validates and serializes at the end of the flow.

#7

The respone: Response is the same everywhere it is injected. So if you change an attribute (eg response.content) then it will be disponbile everywhere, including hooks.