How can I modify request body before it's accessed by the api handler and response body before it's returned by the handler?
Is it possible to implement a middleware or view hooks to change the request body and response body as needed?
Additional context
I am working on implementing API versioning with FastAPI using Stripe's approach. As I understand, it requires modifying the request body and response body depending on requested version of the API. I've been experimenting with different approaches but unable to find a clean way to do it. I've tried implementing it as a middleware but it doesn't seem like the right place. So my question is, are there any hooks that I can rely on to modify the incoming request and modify the response. Thanks!
@tiangolo has made it really easy to work with the middleware - it's the way I'd handle this if I was doing it the way you are - of course using the mounts - it's really easy to handle /api/v1/... and /api/v2/... in the same application:
Thank you for your input @wshayes. What I am trying to achieve is slightely different but your suggestion definitely pointed me in the right direction.
Thanks for your help @wshayes !
@nav you might also want to look at APIRouters, with them you can add an API prefix to a whole set of routes/path operations: https://fastapi.tiangolo.com/tutorial/bigger-applications/#include-an-apirouter-with-a-prefix-tags-and-responses
Thanks for the info @tiangolo but I am trying to avoid having to maintain multiple routes (versioning via routes i.e., /v1, /v2, etc). What I am trying to achieve is the ability to modify body of the request before it's processed by the route handler and response after it has been processed by the same handler.
So far I haven't had luck in finding an appropriate way to do it. It may be that the answer lies in Starlette since the request and response objects are handled by it. Additionally, request object is a child class of collections.abc.Mapping which is immutable so changing the request body might require a layer of abstraction.
Hmm, by what you describe here in this last comment, it seems that you just need a middleware, as @wshayes was suggesting: https://fastapi.tiangolo.com/tutorial/middleware/
Thanks @tiangolo, it does seem like middleware is the way to go but now the challenge is modifying starlette.requests.Request and starlette.responses.StreamingResponse which is what request and response objects are. There seem to be enough documentation on reading the body in both cases but not so much regarding modifying the body.
Cool @nav ! Thanks for reporting back and closing the issue.
Just as a hint, it might be easier (and I think it's what Tom intended in Starlette) to extract the content that you need, modify it however you need, and create a new Request. And the same for the response, create a new StreamingResponse. Although modifying a StreamingResponse while you are streaming content to it seems a complex task, but that might be what you need.
@tiangolo
Do you have any examples of how to create new Request base on original Request ?
I need to modify original request and add values to header and pass on to path handlers.
Thank you very much
Thanks @euri10 for pointing me at this issue (maybe the issue label can be charged to include 'API versioning'?)
@nav! Great that someone else is looking at Stripe as an API model to follow, besides me.
Are you looking to use Stripe's approach exactly? ie:
A) header based (ie API release date) version override along with
B) defaulting to the account's set API version release date
I could see that requiring a middleware to:
1) load the account/user where the set API version is stored, to retrieve the default version
2) detect the API version header
Then it gets hazy; how are you planning to proxy the requests to the appropriate API version of the endpoint? The million dollar question.
Reference: Stripe's article on versioning: https://stripe.com/en-ca/blog/api-versioning
@tiangolo, as another approach:
Would it be possible to create a proxy API object that simply discovered the API version from (account/user or header) that THEN called the correctly versioned API object?
Ideally, it would allow both proxy and real API objects to be decorated with 'dependencies', etc - this allows dependencies to change between versions?
It seems like Stripe's approach _avoids_ having major version changes at all, so do you really need to dispatch it to a different endpoint at all?
Some readers might have already noticed that the Stripe API also defines major versions using a prefixed path (like /v1/charges). Although we reserve the right to make use of this at some point, it’s not likely to change for some time.
It seems like just using middleware to apply the backwards incompatible changes for that major version only is fine; if you then implement a major change you can roll that out with the APIRouter and start a new series of fixes in the middleware on that route that apply to that major version. API consumers would need to explicitly upgrade to the new version anyway, but with this approach they'd need to change the _path_ to get a new major version, while they could use the _header_ to upgrade minor versions.
I'm hopeful I can go indefinitely without incrementing a major version change :D
Edit:
Hmmm, although yeah, I don't see how to appropriately incorporate that into the type information and therefore the API documentation.
Hey @elyobo! Thanks for the reply.
Stripe probably wouldn't introduce a new API /v2/ unless it was vastly different - became graphql-based, for example. If you look at their release history, they've already made semi-significant changes on /v1/. For the purpose of discussion here, we can probably ignore the fact that /v1/ exists in their API URL.
The header-based 'minor version' as you call it is what I'd like to achieve.
This feels like it ought to be solvable within FastAPI. I'm not sure how middleware can be applied here other than to detect the API version header and attach it to the request state?
Surely the selection of what 'model/path operation version' needs to take place after at least some of the 'path operation dependencies' have run (eg, user/token/api key validation, particularly if their account is locked to a particular API version by default, and no version header is found).
Minimizing route/path operation/model changes, and allowing users to lock their 'account' to a version, but easily upgrade via a dashboard when they're ready.
how to appropriately incorporate that into the type information and therefore the API documentation.
This is true - makes things more difficult.
As I interpret it the middleware would operate by
All of this seems pretty straightforward... but it's the incorporation of this in to the typing and the auto doc generation that I find difficult to think about.
@elyobo, thanks for laying out your thoughts.
I imagine that would work, but I just thought that in the context of fastapi, dependencies not middleware were to be used for authorization.
Of course, we're not prevented from doing it as you suggest, just guided to.
Thanks again for the details.
You are quite right in that regard, I'm new to fastapi and didn't state
that part correctly. The auth part while be a dependency of the versioning
middleware, so that the user (and version) is known before applying the
required changes to request and response.
On Sat, 24 Aug 2019, 01:06 James Addison, notifications@github.com wrote:
@elyobo https://github.com/elyobo, thanks for laying out your thoughts.
I imagine that would work, but I just thought that in the context of
fastapi, dependencies not middleware were to be used for authorization.Of course, we're not prevented from doing it as you suggest, just guided
to.Thanks again for the details.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/tiangolo/fastapi/issues/200?email_source=notifications&email_token=AADDR7QCIZQ4V67BAF3VRVLQF74I5A5CNFSM4HK4IMTKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD5APLWY#issuecomment-524350939,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AADDR7VB73NMJBFWWX3CQLTQF74I5ANCNFSM4HK4IMTA
.
@tiangolo do you have any thoughts here? Supporting different request and response formats internally seems simple, but having this appropriately handled by the typing seems challenging. The only way I see would be to define very broad input and output types with a lot of Optional values such that it covers all possible permutations.
I think you could create a “versioning middleware” that checks the endpoint, looks up a set of (typed) transformations based on the endpoint and version, and applies the transformation. But you’d still be responsible for setting the body content on the new request.
My impression is that starlette is not super well-suited to this middleware-based approach to API versioning, if only because there isn’t an easy way to access and modify the request/response contents in middleware.
If there were a way to use a version header (or similar) to route a request, I think that could make it easy to achieve a versioned api without different paths by just writing multiple endpoints that delegate to the newer versions.
I’m not sure if this would be hard / possible to implement given the existing functionality, but it would be nice!
I looked into this a little more, and it looks like all you need to do to achieve non-path-based routing is to override starlette.routing.BaseRoute.matches (you can look at starlette.routing.Route.matches for how it is done by validating the path). Currently, the endpoint decorators are all hard-coded against fastapi.routing.APIRoute, so you'd have to monkeypatch APIRoute.matches (unless you wanted to override all the decorator methods, which wouldn't be unreasonable, just would involve a lot of copy-pasted typed arguments).
Edit: I believe you can now set a default route class, so you shouldn't have to monkeypatch APIRoute any more.
If you were willing to monkeypatch the APIRoute.matches call to achieve version-based routing, you might take the following steps:
You'd add a versioning middleware that would somehow read the version (specific to your app) and store it in the ASGI scope.
You could store the appropriate version for an endpoint callable directly on the callable by means of a version decorator, something like:
F = TypeVar("F", bound=Callable[..., Any])
def version(major: int, minor: int, patch: int) -> Callable[[F], F]:
def decorator(func: F) -> F:
func.__api_version__ = (major, minor, patch)
return func
(See below for usage.)
You would have the modified APIRoute.matches call read the version added to the callable i.e., endpoint.__api_version__, and validate the version value against what had been stored in the scope by the version middleware.
You would then declare your versioned delegating endpoints via:
@router.get("/")
@version(0, 2, 0)
def f(x: int, y: int) -> Tuple[int, int]:
return (x, y)
@router.get("/")
@version(0, 1, 0)
def f_pre_tuple(x: int) -> int:
return f(x, 0)[0]
(Note: it would be important to place the endpoints in descending version order so that the most recent valid version was used.)
If I understand correctly, this would be more or less equivalent to the Stripe approach to API versioning.
(If anyone is interested in more detail, please comment, and I'd be happy to flesh out the versioning middleware and/or routing validation logic a little more.)
A feature request appropriate for fastapi that would make this a lot more natural would be a way to specify the route class for the router. In particular, this would make it much easier to override the matching logic for your application without needing to resort to monkeypatching or reimplementing 100 decorators.
It was only a three-line change so I went ahead and created a pull request (#468), but I think it probably merits some discussion first.
I don't know whether the above approach would be well-handled by docs generation out of the box, but if it wasn't handled the way you want, you could probably override the relevant openapi generation calls in a subclass of fastapi.FastAPI to do what you want (whether that was generating the docs for the current version, for a specific other version (e.g., read off a version header), or otherwise).
Cool @nav ! Thanks for reporting back and closing the issue.
Just as a hint, it might be easier (and I think it's what Tom intended in Starlette) to extract the content that you need, modify it however you need, and create a new
Request. And the same for the response, create a newStreamingResponse. Although modifying aStreamingResponsewhile you are streaming content to it seems a complex task, but that might be what you need.
Now i trying to get Response body. But have problem about body_iterator. If i use that one. It's will be conflict between length of body and content-length in Headers.
Did you have any example for New Request then modify a StreamingResponse?.
Most helpful comment
I looked into this a little more, and it looks like all you need to do to achieve non-path-based routing is to override
starlette.routing.BaseRoute.matches(you can look atstarlette.routing.Route.matchesfor how it is done by validating the path). Currently, the endpoint decorators are all hard-coded againstfastapi.routing.APIRoute, so you'd have to monkeypatchAPIRoute.matches(unless you wanted to override all the decorator methods, which wouldn't be unreasonable, just would involve a lot of copy-pasted typed arguments).Edit: I believe you can now set a default route class, so you shouldn't have to monkeypatch
APIRouteany more.If you were willing to monkeypatch the
APIRoute.matchescall to achieve version-based routing, you might take the following steps:You'd add a versioning middleware that would somehow read the version (specific to your app) and store it in the ASGI scope.
You could store the appropriate version for an endpoint callable directly on the callable by means of a version decorator, something like:
(See below for usage.)
You would have the modified
APIRoute.matchescall read the version added to the callable i.e.,endpoint.__api_version__, and validate the version value against what had been stored in the scope by the version middleware.You would then declare your versioned delegating endpoints via:
(Note: it would be important to place the endpoints in descending version order so that the most recent valid version was used.)
If I understand correctly, this would be more or less equivalent to the Stripe approach to API versioning.
(If anyone is interested in more detail, please comment, and I'd be happy to flesh out the versioning middleware and/or routing validation logic a little more.)
A feature request appropriate for fastapi that would make this a lot more natural would be a way to specify the route class for the router. In particular, this would make it much easier to override the matching logic for your application without needing to resort to monkeypatching or reimplementing 100 decorators.
It was only a three-line change so I went ahead and created a pull request (#468), but I think it probably merits some discussion first.
I don't know whether the above approach would be well-handled by docs generation out of the box, but if it wasn't handled the way you want, you could probably override the relevant openapi generation calls in a subclass of
fastapi.FastAPIto do what you want (whether that was generating the docs for the current version, for a specific other version (e.g., read off a version header), or otherwise).