Giter Club home page Giter Club logo

fastapi-chameleon's Introduction

fastapi-chameleon

Adds integration of the Chameleon template language to FastAPI. If you are interested in Jinja instead, see the sister project: github.com/AGeekInside/fastapi-jinja.

Installation

Simply pip install fastapi_chameleon.

Usage

This is easy to use. Just create a folder within your web app to hold the templates such as:

├── main.py
├── views.py
│
├── templates
│   ├── home
│   │   └── index.pt
│   └── shared
│       └── layout.pt

In the app startup, tell the library about the folder you wish to use:

import os
from pathlib import Path
import fastapi_chameleon

dev_mode = True

BASE_DIR = Path(__file__).resolve().parent
template_folder = str(BASE_DIR / 'templates')
fastapi_chameleon.global_init(template_folder, auto_reload=dev_mode)

Then just decorate the FastAPI view methods (works on sync and async methods):

@router.post('/')
@fastapi_chameleon.template('home/index.pt')
async def home_post(request: Request):
    form = await request.form()
    vm = PersonViewModel(**form) 

    return vm.dict() # {'first':'Michael', 'last':'Kennedy', ...}

The view method should return a dict to be passed as variables/values to the template.

If a fastapi.Response is returned, the template is skipped and the response along with status_code and other values is directly passed through. This is common for redirects and error responses not meant for this page template.

Friendly 404s and errors

A common technique for user-friendly sites is to use a custom HTML page for 404 responses. This is especially important in FastAPI because FastAPI returns a 404 response + JSON by default. This library has support for friendly 404 pages using the fastapi_chameleon.not_found() function.

Here's an example:

@router.get('/catalog/item/{item_id}')
@fastapi_chameleon.template('catalog/item.pt')
async def item(item_id: int):
    item = service.get_item_by_id(item_id)
    if not item:
        fastapi_chameleon.not_found()
    
    return item.dict()

This will render a 404 response with using the template file templates/errors/404.pt. You can specify another template to use for the response, but it's not required.

If you need to return errors other than Not Found (status code 404), you can use a more generic function: fastapi_chameleon.generic_error(template_file: str, status_code: int). This function will allow you to return different status codes. It's generic, thus you'll have to pass a path to your error template file as well as a status code you want the user to get in response. For example:

@router.get('/catalog/item/{item_id}')
@fastapi_chameleon.template('catalog/item.pt')
async def item(item_id: int):
    item = service.get_item_by_id(item_id)
    if not item:
        fastapi_chameleon.generic_error('errors/unauthorized.pt',
                                        fastapi.status.HTTP_401_UNAUTHORIZED)

    return item.dict()

fastapi-chameleon's People

Contributors

fariddarabi avatar fferegrino avatar jmtaysom avatar jugmac00 avatar madeinoz67 avatar mikeckennedy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

fastapi-chameleon's Issues

Test on only Python 3.6+

With FastAPI requiring Python 3.6+ there isnt a need to test on Python 2.7 or the other older versions of Python. You could then remove the dev dependency on six.

render template inside view method

I'm trying to build a video collector similar to the one from your HTMX course. Mine uses FastAPI, Chameleon, Beanie. I'm having trouble rendering the search results due to the conditional partial render inside of the view method. I don't know how to render a template when it's inside the view method like that, only done it through the decorator on the view method function itself.

How would I implement something like this in fastapi-chameleon?

html = flask.render_template('videos/partials/search_results.html', videos=vm.videos)
return flask.make_response(html)

I have tried the following with from fastapi_chameleon import template imported

html = template(template_file="videos/partials/search_results.pt", videos=vm.videos)
return fastapi.responses.HTMLResponse(html)

I also tried getting creative and reading through your fastapi-chameleon source, so I tried

page: PageTemplate = "/usr/home/roller/Projects/coach-aaron/templates/videos/partials/search_results.pt"
return page.render(encoding='utf-8', videos=vm.videos)

That didn't work either.

At the moment I'm going to try following the FastAPI docs
If I have to for now I'll do just this one partial using Jinja if I can get it to do that.. we'll see. I appreciate any help or suggestions you may have on this. I'll definitely submit this webapp I'm working on to the Student Creation Showcase once it's ready enough and is actually doing interesting things.

Is it possible to add a custom header to the response?

How can I add a custom response header when using fastapi-chameleon?

If I use chameleon "manually", the response header is added as expected:

@router.get("/headers-1/", response_class=HTMLResponse)
async def get_headers_1(request: Request, response: Response):
    response.headers["HX-Refresh"] = "true"
    template = templates["hello.pt"]
    return template.render(name="Alice")

But when I use the template decorator, the "HX-Refresh" header is not included in the response:

@router.get("/headers-2/")
@template(template_file='hello.pt')
async def get_headers_2(request: Request, response: Response):
    response.headers["HX-Refresh"] = "true"
    return {'name': "Bob"}

My hello.py contains

<div>Hello, ${name}.</div>

Pip Install Fails with SetuptoolsDeprecationWarning

I'm running a virtual environment with the latest Python from Homebrew on macOS:

python --version
Python 3.9.2

I'm taking the FastAPI Web App course and I'm trying to install fastapi-chameleon from both this repo and my fork both error out:

 silversaucer> pip install git+https://github.com/mikeckennedy/fastapi-chameleon

Collecting git+https://github.com/mikeckennedy/fastapi-chameleon
  Cloning https://github.com/mikeckennedy/fastapi-chameleon to /private/var/folders/y8/p92hytfj04b7_92zv1ppn1s00000gn/T/pip-req-build-mp91okuf
  Running command git clone -q https://github.com/mikeckennedy/fastapi-chameleon /private/var/folders/y8/p92hytfj04b7_92zv1ppn1s00000gn/T/pip-req-build-mp91okuf
    ERROR: Command errored out with exit status 1:
     command: /Users/prcutler/workspace/silversaucer/.venvfastapi/bin/python -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'/private/var/folders/y8/p92hytfj04b7_92zv1ppn1s00000gn/T/pip-req-build-mp91okuf/setup.py'"'"'; __file__='"'"'/private/var/folders/y8/p92hytfj04b7_92zv1ppn1s00000gn/T/pip-req-build-mp91okuf/setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' egg_info --egg-base /private/var/folders/y8/p92hytfj04b7_92zv1ppn1s00000gn/T/pip-pip-egg-info-z72h9h4o
         cwd: /private/var/folders/y8/p92hytfj04b7_92zv1ppn1s00000gn/T/pip-req-build-mp91okuf/
    Complete output (7 lines):
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/Users/prcutler/workspace/silversaucer/.venvfastapi/lib/python3.9/site-packages/setuptools/__init__.py", line 14, in <module>
        from setuptools.dist import Distribution
      File "/Users/prcutler/workspace/silversaucer/.venvfastapi/lib/python3.9/site-packages/setuptools/dist.py", line 30, in <module>
        from . import SetuptoolsDeprecationWarning
    ImportError: cannot import name 'SetuptoolsDeprecationWarning' from partially initialized module 'setuptools' (most likely due to a circular import) (/Users/prcutler/workspace/silversaucer/.venvfastapi/lib/python3.9/site-packages/setuptools/__init__.py)
    ----------------------------------------
WARNING: Discarding git+https://github.com/mikeckennedy/fastapi-chameleon. Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.
ERROR: Command errored out with exit status 1: python setup.py egg_info Check the logs for full command output.

All my Python modules appear up to date:

Screen Shot 2021-03-10 at 10 38 14 AM

Is there something I'm missing or I should try? Is there any other info needed?

Thanks!

Call to global_init() required to run any test individually

Each specific rest requires global_init() to be ran before it can be run. The test_can_call_init_with_good_path() test does this, and that appears to enable all other tests to run.

def test_can_call_init_with_good_path():
    fc.global_init(folder, cache_init=False)

If you run various tests individually, they error out because global_init is not called. I am working on a fix for the fastapi-jinja library that uses a pytest fixture to make this work. I am basically creating a fixture that is like this:

@pytest.fixture
def call_global_init():
    here = os.path.dirname(__file__)
    folder = os.path.join(here, "templates")
    fc.global_init(folder, cache_init=False)

The fixture could then be added to any test that requires global init to be called. It should work, but I want to verify.

TypeError: expected str, bytes or os.PathLike object, not NoneType

From a student:

In ch4, when I try to run your main.py, I get this:

C:/Users/user/FS-FAPI/source/web-applications-with-fastapi-course/code/ch4-templates/main.py
Traceback (most recent call last):
  File "C:\Users\user\FS-FAPI\source\web-applications-with-fastapi-course\code\ch4-templates\main.py", line 7, in <module>
    from views import home
  File "C:\Users\user\FS-FAPI\source\web-applications-with-fastapi-course\code\ch4-templates\views\home.py", line 9, in <module>
    def index():
  File "C:\Users\user\FS-FAPI\venv\lib\site-packages\fastapi_chameleon\engine.py", line 72, in response_inner
    if not os.path.exists(os.path.join(template_path, template_file)):
  File "C:\Users\user\AppData\Local\Programs\Python\Python39\lib\ntpath.py", line 78, in join
    path = os.fspath(path)
TypeError: expected str, bytes or os.PathLike object, not NoneType

Process finished with exit code 1

Looks a timing issue between calling global init and "using" the decorator.

Error in engine.py, using python3.9 (posixpath.py) when template_path is None

Hello, got this error trying to run using ch5-viewmodels from TalkPython Course... template_path is None.

I believe the problem is that fastapi_chameleon.global_init() is called after from fastapi_chameleon import template in home.py

Exception has occurred: TypeError       (note: full exception trace is shown but execution is paused at: _run_module_as_main)
expected str, bytes or os.PathLike object, not NoneType
  File "/home/cristiana/anaconda3/envs/fastapi/lib/python3.9/posixpath.py", line 76, in join
    a = os.fspath(a)
  File "/home/cristiana/anaconda3/envs/fastapi/lib/python3.9/site-packages/fastapi_chameleon/engine.py", line 72, in response_inner
    if not os.path.exists(os.path.join(template_path, template_file)):
  File "/home/cristiana/AAA-wip/Cursos-Programming/Curso-TalkPython-FastAPI-FullWeb/web-applications-with-fastapi-cris/views/home.py", line 13, in <module>
    def index(request: Request):
  File "/home/cristiana/AAA-wip/Cursos-Programming/Curso-TalkPython-FastAPI-FullWeb/web-applications-with-fastapi-cris/main.py", line 7, in <module>
    from views import home
  File "/home/cristiana/anaconda3/envs/fastapi/lib/python3.9/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/home/cristiana/anaconda3/envs/fastapi/lib/python3.9/runpy.py", line 97, in _run_module_code
    _run_code(code, mod_globals, init_globals,
  File "/home/cristiana/anaconda3/envs/fastapi/lib/python3.9/runpy.py", line 268, in run_path
    return _run_module_code(code, init_globals, run_name,
  File "/home/cristiana/anaconda3/envs/fastapi/lib/python3.9/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/home/cristiana/anaconda3/envs/fastapi/lib/python3.9/runpy.py", line 197, in _run_module_as_main (Current frame)
    return _run_code(code, main_globals, None,

returning empty dict

In what situation do you want to return an empty dict in this case? Seems like you'd more likely want to try to coerce to a dict and fail over to an empty dict? Though maybe you are just trying to account for None, in which case maybe it should be an elif response_val is None?

try:
    model = dict(response_val)
except TypeError:
    model = {}

else:
model = {}

Releasing this to PiPy

Hi Michael; thanks for creating this. Is there a checklist you may have around to understand what would be needed to release this package into the wild? even if it is a beta version for the time being?

Also, would you need help with that? I am happy to help.

template folder not found

I have my templates folder within the same directory as my main.py file from which I am calling the templates folder.

The code in the main.py looks like this:

import fastapi
import uvicorn
import fastapi_chameleon
from fastapi_chameleon import template


app = fastapi.FastAPI()
fastapi_chameleon.global_init("templates")


@app.get("/")
@template(template_file="index.html")
def index():
    return {
        "user_name": "usernameExample"
    }


if __name__ == "__main__":
    uvicorn.run(app)
else:
    pass

I get an error message:

"c:/Users/username/OneDrive - Dynatrace/Pictures/pypi/application/venv/Scripts/python.exe" "c:/Users/username/OneDrive - Dynatrace/Pictures/pypi/application/main.py"
Traceback (most recent call last):
  File "c:\Users\username\OneDrive - Dynatrace\Pictures\pypi\application\main.py", line 8, in <module>
    fastapi_chameleon.global_init(".templates")
  File "C:\Users\username\OneDrive - Dynatrace\Pictures\pypi\application\venv\lib\site-packages\fastapi_chameleon\engine.py", line 27, in global_init
    raise FastAPIChameleonException(msg)
fastapi_chameleon.exceptions.FastAPIChameleonException: The specified template folder must be a folder, it's not: templates

On Windows

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.