-
Notifications
You must be signed in to change notification settings - Fork 16
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
Add FastAPI Support #2
Conversation
Added tests and README update. Had a tough time with the tests. Not entirely sure if it was an encoding thing with the way FastAPI uses the the Jinja Environment, or if it's an OS thing. By removing whitespace/newlines, I was able to have my assertions pass. Wrote comments to explain what was being done. |
First of all, thanks for the PR @tataraba! Having support for FlastAPI is a great contribution. My main concern is that using a decorator instead of a function you cannot have the same route/view return two different responses. I don't know how frequently users would do that, but they would have more control if it was just a function or some callable. If it was a simple function, the templates = Jinja2Templates(directory="templates")
@app.get("/items/{id}", response_class=HTMLResponse)
async def read_item(request: Request, id: str):
return render_block(templates, "item.html", "content", {"request": request, "id": id}) This would enable the pattern: templates = Jinja2Templates(directory="templates")
@app.get("/items/{id}", response_class=HTMLResponse)
async def read_item(request: Request, id: str):
if request.headers.get("HX-Request"):
return render_block(templates, "item.html", "content", {"request": request, "id": id})
return templates.TemplateResponse("item.html", {"request": request, "id": id}) I can see how having to always pass the templates = Jinja2Templates(directory="templates")
blocks = Jinja2Blocks(templates) # We need to do this only once, just after instantiating Jinja2Templates
@app.get("/items/{id}", response_class=HTMLResponse)
async def read_item(request: Request, id: str):
return blocks.BlockResponse("item.html", "content", {"request": request, "id": id}) Or replace the usage of templates = Jinja2Blocks(directory="templates") # This might inherit and extend Jinja2Templates
@app.get("/items/{id}", response_class=HTMLResponse)
async def read_item(request: Request, id: str):
if request.headers.get("HX-Request"):
return templates.BlockResponse("item.html", "content", {"request": request, "id": id})
return templates.TemplateResponse("item.html", {"request": request, "id": id}) After writing it down, this last example might be the one I like more, as it seems to follow closer the FastAPI API and users might find it more familiar. What are your thoughts? |
I noticed that you made templates = Jinja2Blocks(directory="templates") # This might inherit and extend Jinja2Templates
@app.get("/items/{id}", response_class=HTMLResponse)
async def read_item(request: Request, id: str):
if request.headers.get("HX-Request"):
return templates.TemplateResponse("item.html", "content", {"request": request, "id": id}, block_name="content")
return templates.TemplateResponse("item.html", {"request": request, "id": id}) |
Ooh, I really like the idea of In that sense, I think I'll give a crack at that last example you posted! Thanks a lot for the feedback. I'll get back to the code when I have a little spare time ⌚ (hopefully soon!) |
Ah well, decided to give it a crack before going to bed, but it seems to be working. I got the following block to work as intended, which is to say, it renders a block of Jinja called templates = Jinja2Blocks(directory="src/templates")
@app.get("/", response_class=HTMLResponse)
async def get_page(request: Request):
return templates.TemplateResponse(
"index.html", # Name of template file
context={"request": request, "magic_number": 42}, # The "template context"
block_name="content"
) If the code is written like this instead, it sends the whole template. templates = Jinja2Blocks(directory="src/templates")
@app.get("/", response_class=HTMLResponse)
async def get_page(request: Request):
return templates.TemplateResponse(
"index.html", # Name of template file
context={"request": request, "magic_number": 42}, # The "template context"
) This also allows for your last example that checks the header for an I'll add some tests tomorrow, but hopefully this is a step in the right direction! ✨ |
Awesome, let me know once it's ready for review! |
I've been using it locally with a project and it works like a charm, at least for me! I've updated the tests and the README as well. Should be ready for you! |
I added FastAPI support (under
fastapi.py
). I generally tried to follow the same pattern as flask/quart, but have a slightly different approach.With FastAPI, you have to declare a
Jinja2Templates
object, which is a subclass of the Jinja Environment class. This is declared outside of the route/view. And then, in order to render the template, you have to use aJinja2Templates
method. This is what it looks like ordinarily:I wanted to preserve this approach as much as possible, but allow the possibility to
render_block
--thus sending only the applicable block content.My approach was to use a decorator to define the
block_name
. Everything else stays pretty much the same from the FastAPI perspective.The end result/usage ends up looking kind of like this:
The result is the same as
render_block
in the other applications (Flask/Quart).the
render_block
decorator takes theJinja2Templates
object as the first (positional) parameter. This is needed in order to extract the JinjaEnvironment
object. Thetemplate_name
, as well as the necessaryrequest
k,v pair, are both extracted from the FastAPITemplateResponse
method (from within the decorator). Then, the response is rebuilt with thejinja2_fragments.render_block
method.Last thing:
This is my first contribution to a library, so I apologize if anything is a little clunky. I've yet to add tests on my end, but I was able to confirm that it was working as expected on one of my own apps.