fastapi_chameleon 0.1.14__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2020 Michael Kennedy
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.1
2
+ Name: fastapi_chameleon
3
+ Version: 0.1.14
4
+ Summary: Adds integration of the Chameleon template language to FastAPI.
5
+ Home-page: https://github.com/mikeckennedy/fastapi-chameleon
6
+ Author: Michael Kennedy
7
+ Author-email: michael@talkpython.fm
8
+ License: MIT
9
+ Classifier: Development Status :: 2 - Pre-Alpha
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Programming Language :: Python :: 3.6
13
+ Classifier: Programming Language :: Python :: 3.7
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+
20
+ # fastapi-chameleon
21
+
22
+ 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](https://github.com/AGeekInside/fastapi-jinja).
23
+
24
+ ## Installation
25
+
26
+ Simply `pip install fastapi_chameleon`.
27
+
28
+ ## Usage
29
+
30
+ This is easy to use. Just create a folder within your web app to hold the templates such as:
31
+
32
+ ```
33
+ ├── main.py
34
+ ├── views.py
35
+ │
36
+ ├── templates
37
+ │ ├── home
38
+ │ │ └── index.pt
39
+ │ └── shared
40
+ │ └── layout.pt
41
+
42
+ ```
43
+
44
+ In the app startup, tell the library about the folder you wish to use:
45
+
46
+ ```python
47
+ import os
48
+ from pathlib import Path
49
+ import fastapi_chameleon
50
+
51
+ dev_mode = True
52
+
53
+ BASE_DIR = Path(__file__).resolve().parent
54
+ template_folder = str(BASE_DIR / 'templates')
55
+ fastapi_chameleon.global_init(template_folder, auto_reload=dev_mode)
56
+ ```
57
+
58
+ Then just decorate the FastAPI view methods (works on sync and async methods):
59
+
60
+ ```python
61
+ @router.post('/')
62
+ @fastapi_chameleon.template('home/index.pt')
63
+ async def home_post(request: Request):
64
+ form = await request.form()
65
+ vm = PersonViewModel(**form)
66
+
67
+ return vm.dict() # {'first':'Michael', 'last':'Kennedy', ...}
68
+
69
+ ```
70
+
71
+ The view method should return a `dict` to be passed as variables/values to the template.
72
+
73
+ If a `fastapi.Response` is returned, the template is skipped and the response along with status_code and
74
+ other values is directly passed through. This is common for redirects and error responses not meant
75
+ for this page template.
76
+
77
+ ## Friendly 404s and errors
78
+
79
+ A common technique for user-friendly sites is to use a
80
+ [custom HTML page for 404 responses](http://www.instantshift.com/2019/10/16/user-friendly-404-pages/).
81
+ This is especially important in FastAPI because FastAPI returns a 404 response + JSON by default.
82
+ This library has support for friendly 404 pages using the `fastapi_chameleon.not_found()` function.
83
+
84
+ Here's an example:
85
+
86
+ ```python
87
+ @router.get('/catalog/item/{item_id}')
88
+ @fastapi_chameleon.template('catalog/item.pt')
89
+ async def item(item_id: int):
90
+ item = service.get_item_by_id(item_id)
91
+ if not item:
92
+ fastapi_chameleon.not_found()
93
+
94
+ return item.dict()
95
+ ```
96
+
97
+ This will render a 404 response with using the template file `templates/errors/404.pt`.
98
+ You can specify another template to use for the response, but it's not required.
99
+
100
+ If you need to return errors other than `Not Found` (status code `404`), you can use a more
101
+ generic function: `fastapi_chameleon.generic_error(template_file: str, status_code: int)`.
102
+ This function will allow you to return different status codes. It's generic, thus you'll have
103
+ to pass a path to your error template file as well as a status code you want the user to get
104
+ in response. For example:
105
+
106
+ ```python
107
+ @router.get('/catalog/item/{item_id}')
108
+ @fastapi_chameleon.template('catalog/item.pt')
109
+ async def item(item_id: int):
110
+ item = service.get_item_by_id(item_id)
111
+ if not item:
112
+ fastapi_chameleon.generic_error('errors/unauthorized.pt',
113
+ fastapi.status.HTTP_401_UNAUTHORIZED)
114
+
115
+ return item.dict()
116
+ ```
@@ -0,0 +1,97 @@
1
+ # fastapi-chameleon
2
+
3
+ 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](https://github.com/AGeekInside/fastapi-jinja).
4
+
5
+ ## Installation
6
+
7
+ Simply `pip install fastapi_chameleon`.
8
+
9
+ ## Usage
10
+
11
+ This is easy to use. Just create a folder within your web app to hold the templates such as:
12
+
13
+ ```
14
+ ├── main.py
15
+ ├── views.py
16
+ │
17
+ ├── templates
18
+ │ ├── home
19
+ │ │ └── index.pt
20
+ │ └── shared
21
+ │ └── layout.pt
22
+
23
+ ```
24
+
25
+ In the app startup, tell the library about the folder you wish to use:
26
+
27
+ ```python
28
+ import os
29
+ from pathlib import Path
30
+ import fastapi_chameleon
31
+
32
+ dev_mode = True
33
+
34
+ BASE_DIR = Path(__file__).resolve().parent
35
+ template_folder = str(BASE_DIR / 'templates')
36
+ fastapi_chameleon.global_init(template_folder, auto_reload=dev_mode)
37
+ ```
38
+
39
+ Then just decorate the FastAPI view methods (works on sync and async methods):
40
+
41
+ ```python
42
+ @router.post('/')
43
+ @fastapi_chameleon.template('home/index.pt')
44
+ async def home_post(request: Request):
45
+ form = await request.form()
46
+ vm = PersonViewModel(**form)
47
+
48
+ return vm.dict() # {'first':'Michael', 'last':'Kennedy', ...}
49
+
50
+ ```
51
+
52
+ The view method should return a `dict` to be passed as variables/values to the template.
53
+
54
+ If a `fastapi.Response` is returned, the template is skipped and the response along with status_code and
55
+ other values is directly passed through. This is common for redirects and error responses not meant
56
+ for this page template.
57
+
58
+ ## Friendly 404s and errors
59
+
60
+ A common technique for user-friendly sites is to use a
61
+ [custom HTML page for 404 responses](http://www.instantshift.com/2019/10/16/user-friendly-404-pages/).
62
+ This is especially important in FastAPI because FastAPI returns a 404 response + JSON by default.
63
+ This library has support for friendly 404 pages using the `fastapi_chameleon.not_found()` function.
64
+
65
+ Here's an example:
66
+
67
+ ```python
68
+ @router.get('/catalog/item/{item_id}')
69
+ @fastapi_chameleon.template('catalog/item.pt')
70
+ async def item(item_id: int):
71
+ item = service.get_item_by_id(item_id)
72
+ if not item:
73
+ fastapi_chameleon.not_found()
74
+
75
+ return item.dict()
76
+ ```
77
+
78
+ This will render a 404 response with using the template file `templates/errors/404.pt`.
79
+ You can specify another template to use for the response, but it's not required.
80
+
81
+ If you need to return errors other than `Not Found` (status code `404`), you can use a more
82
+ generic function: `fastapi_chameleon.generic_error(template_file: str, status_code: int)`.
83
+ This function will allow you to return different status codes. It's generic, thus you'll have
84
+ to pass a path to your error template file as well as a status code you want the user to get
85
+ in response. For example:
86
+
87
+ ```python
88
+ @router.get('/catalog/item/{item_id}')
89
+ @fastapi_chameleon.template('catalog/item.pt')
90
+ async def item(item_id: int):
91
+ item = service.get_item_by_id(item_id)
92
+ if not item:
93
+ fastapi_chameleon.generic_error('errors/unauthorized.pt',
94
+ fastapi.status.HTTP_401_UNAUTHORIZED)
95
+
96
+ return item.dict()
97
+ ```
@@ -0,0 +1,11 @@
1
+ """fastapi-chameleon - Adds integration of the Chameleon template language to FastAPI."""
2
+
3
+ __version__ = '0.1.14'
4
+ __author__ = 'Michael Kennedy <michael@talkpython.fm>'
5
+ __all__ = ['template', 'global_init', 'not_found', 'response', 'generic_error', ]
6
+
7
+ from .engine import global_init
8
+ from .engine import template
9
+ from .engine import response
10
+ from .engine import not_found
11
+ from .engine import generic_error
@@ -0,0 +1,141 @@
1
+ import inspect
2
+ import os
3
+ from functools import wraps
4
+ from typing import Optional, Union, Callable
5
+
6
+ import fastapi
7
+ from chameleon import PageTemplateLoader, PageTemplate
8
+
9
+ from fastapi_chameleon.exceptions import FastAPIChameleonException, FastAPIChameleonGenericException, \
10
+ FastAPIChameleonNotFoundException
11
+
12
+ __templates: Optional[PageTemplateLoader] = None
13
+ template_path: Optional[str] = None
14
+
15
+
16
+ def global_init(template_folder: str, auto_reload=False, cache_init=True):
17
+ global __templates, template_path
18
+
19
+ if __templates and cache_init:
20
+ return
21
+
22
+ if not template_folder:
23
+ msg = f'The template_folder must be specified.'
24
+ raise FastAPIChameleonException(msg)
25
+
26
+ if not os.path.isdir(template_folder):
27
+ msg = f"The specified template folder must be a folder, it's not: {template_folder}"
28
+ raise FastAPIChameleonException(msg)
29
+
30
+ template_path = template_folder
31
+ __templates = PageTemplateLoader(template_folder, auto_reload=auto_reload)
32
+
33
+
34
+ def clear():
35
+ global __templates, template_path
36
+ __templates = None
37
+ template_path = None
38
+
39
+
40
+ def render(template_file: str, **template_data: dict) -> str:
41
+ if not __templates:
42
+ raise FastAPIChameleonException("You must call global_init() before rendering templates.")
43
+
44
+ page: PageTemplate = __templates[template_file]
45
+ return page.render(encoding='utf-8', **template_data)
46
+
47
+
48
+ def response(template_file: str, mimetype='text/html', status_code=200, **template_data) -> fastapi.Response:
49
+ html = render(template_file, **template_data)
50
+ return fastapi.Response(content=html, media_type=mimetype, status_code=status_code)
51
+
52
+
53
+ def template(template_file: Optional[Union[Callable, str]] = None, mimetype: str = 'text/html'):
54
+ """
55
+ Decorate a FastAPI view method to render an HTML response.
56
+
57
+ :param str template_file: Optional, the Chameleon template file (path relative to template folder, *.pt).
58
+ :param str mimetype: The mimetype response (defaults to text/html).
59
+ :return: Decorator to be consumed by FastAPI
60
+ """
61
+
62
+ wrapped_function = None
63
+ if callable(template_file):
64
+ wrapped_function = template_file
65
+ template_file = None
66
+
67
+ def response_inner(f):
68
+ nonlocal template_file
69
+ global template_path
70
+
71
+ if not template_path:
72
+ template_path = 'templates'
73
+ # raise FastAPIChameleonException("Cannot continue: fastapi_chameleon.global_init() has not been called.")
74
+
75
+ if not template_file:
76
+ # Use the default naming scheme: template_folder/module_name/function_name.pt
77
+ module = f.__module__
78
+ if '.' in module:
79
+ module = module.split('.')[-1]
80
+ view = f.__name__
81
+ template_file = f'{module}/{view}.html'
82
+
83
+ if not os.path.exists(os.path.join(template_path, template_file)):
84
+ template_file = f'{module}/{view}.pt'
85
+
86
+ @wraps(f)
87
+ def sync_view_method(*args, **kwargs):
88
+ try:
89
+ response_val = f(*args, **kwargs)
90
+ return __render_response(template_file, response_val, mimetype)
91
+ except FastAPIChameleonNotFoundException as nfe:
92
+ return __render_response(nfe.template_file, {}, 'text/html', 404)
93
+ except FastAPIChameleonGenericException as nfe:
94
+ return __render_response(nfe.template_file, {}, 'text/html', nfe.status_code)
95
+
96
+ @wraps(f)
97
+ async def async_view_method(*args, **kwargs):
98
+ try:
99
+ response_val = await f(*args, **kwargs)
100
+ return __render_response(template_file, response_val, mimetype)
101
+ except FastAPIChameleonNotFoundException as nfe:
102
+ return __render_response(nfe.template_file, {}, 'text/html', 404)
103
+ except FastAPIChameleonGenericException as nfe:
104
+ return __render_response(nfe.template_file, {}, 'text/html', nfe.status_code)
105
+
106
+ if inspect.iscoroutinefunction(f):
107
+ return async_view_method
108
+ else:
109
+ return sync_view_method
110
+
111
+ return response_inner(wrapped_function) if wrapped_function else response_inner
112
+
113
+
114
+ def __render_response(template_file, response_val, mimetype, status_code: int = 200) -> fastapi.Response:
115
+ # source skip: assign-if-exp
116
+ if isinstance(response_val, fastapi.Response):
117
+ return response_val
118
+
119
+ if template_file and not isinstance(response_val, dict):
120
+ msg = f"Invalid return type {type(response_val)}, we expected a dict or fastapi.Response as the return value."
121
+ raise FastAPIChameleonException(msg)
122
+
123
+ model = response_val
124
+
125
+ html = render(template_file, **model)
126
+ return fastapi.Response(content=html, media_type=mimetype, status_code=status_code)
127
+
128
+
129
+ def not_found(four04template_file: str = 'errors/404.pt'):
130
+ msg = 'The URL resulted in a 404 response.'
131
+
132
+ if four04template_file and four04template_file.strip():
133
+ raise FastAPIChameleonNotFoundException(msg, four04template_file)
134
+ else:
135
+ raise FastAPIChameleonNotFoundException(msg)
136
+
137
+
138
+ def generic_error(template_file: str, status_code: int):
139
+ msg = 'The URL resulted in an error.'
140
+
141
+ raise FastAPIChameleonGenericException(template_file, status_code, msg)
@@ -0,0 +1,23 @@
1
+ from typing import Optional
2
+
3
+
4
+ class FastAPIChameleonException(Exception):
5
+ pass
6
+
7
+
8
+ class FastAPIChameleonNotFoundException(FastAPIChameleonException):
9
+ def __init__(self, message: Optional[str] = None, four04template_file: str = 'errors/404.pt'):
10
+ super().__init__(message)
11
+
12
+ self.template_file: str = four04template_file
13
+ self.message: Optional[str] = message
14
+
15
+
16
+ class FastAPIChameleonGenericException(FastAPIChameleonException):
17
+ def __init__(self, template_file: str, status_code: int,
18
+ message: Optional[str] = None):
19
+ super().__init__(message)
20
+
21
+ self.template_file: str = template_file
22
+ self.status_code: int = status_code
23
+ self.message: Optional[str] = message
@@ -0,0 +1,116 @@
1
+ Metadata-Version: 2.1
2
+ Name: fastapi-chameleon
3
+ Version: 0.1.14
4
+ Summary: Adds integration of the Chameleon template language to FastAPI.
5
+ Home-page: https://github.com/mikeckennedy/fastapi-chameleon
6
+ Author: Michael Kennedy
7
+ Author-email: michael@talkpython.fm
8
+ License: MIT
9
+ Classifier: Development Status :: 2 - Pre-Alpha
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Programming Language :: Python :: 3.6
13
+ Classifier: Programming Language :: Python :: 3.7
14
+ Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+
20
+ # fastapi-chameleon
21
+
22
+ 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](https://github.com/AGeekInside/fastapi-jinja).
23
+
24
+ ## Installation
25
+
26
+ Simply `pip install fastapi_chameleon`.
27
+
28
+ ## Usage
29
+
30
+ This is easy to use. Just create a folder within your web app to hold the templates such as:
31
+
32
+ ```
33
+ ├── main.py
34
+ ├── views.py
35
+ │
36
+ ├── templates
37
+ │ ├── home
38
+ │ │ └── index.pt
39
+ │ └── shared
40
+ │ └── layout.pt
41
+
42
+ ```
43
+
44
+ In the app startup, tell the library about the folder you wish to use:
45
+
46
+ ```python
47
+ import os
48
+ from pathlib import Path
49
+ import fastapi_chameleon
50
+
51
+ dev_mode = True
52
+
53
+ BASE_DIR = Path(__file__).resolve().parent
54
+ template_folder = str(BASE_DIR / 'templates')
55
+ fastapi_chameleon.global_init(template_folder, auto_reload=dev_mode)
56
+ ```
57
+
58
+ Then just decorate the FastAPI view methods (works on sync and async methods):
59
+
60
+ ```python
61
+ @router.post('/')
62
+ @fastapi_chameleon.template('home/index.pt')
63
+ async def home_post(request: Request):
64
+ form = await request.form()
65
+ vm = PersonViewModel(**form)
66
+
67
+ return vm.dict() # {'first':'Michael', 'last':'Kennedy', ...}
68
+
69
+ ```
70
+
71
+ The view method should return a `dict` to be passed as variables/values to the template.
72
+
73
+ If a `fastapi.Response` is returned, the template is skipped and the response along with status_code and
74
+ other values is directly passed through. This is common for redirects and error responses not meant
75
+ for this page template.
76
+
77
+ ## Friendly 404s and errors
78
+
79
+ A common technique for user-friendly sites is to use a
80
+ [custom HTML page for 404 responses](http://www.instantshift.com/2019/10/16/user-friendly-404-pages/).
81
+ This is especially important in FastAPI because FastAPI returns a 404 response + JSON by default.
82
+ This library has support for friendly 404 pages using the `fastapi_chameleon.not_found()` function.
83
+
84
+ Here's an example:
85
+
86
+ ```python
87
+ @router.get('/catalog/item/{item_id}')
88
+ @fastapi_chameleon.template('catalog/item.pt')
89
+ async def item(item_id: int):
90
+ item = service.get_item_by_id(item_id)
91
+ if not item:
92
+ fastapi_chameleon.not_found()
93
+
94
+ return item.dict()
95
+ ```
96
+
97
+ This will render a 404 response with using the template file `templates/errors/404.pt`.
98
+ You can specify another template to use for the response, but it's not required.
99
+
100
+ If you need to return errors other than `Not Found` (status code `404`), you can use a more
101
+ generic function: `fastapi_chameleon.generic_error(template_file: str, status_code: int)`.
102
+ This function will allow you to return different status codes. It's generic, thus you'll have
103
+ to pass a path to your error template file as well as a status code you want the user to get
104
+ in response. For example:
105
+
106
+ ```python
107
+ @router.get('/catalog/item/{item_id}')
108
+ @fastapi_chameleon.template('catalog/item.pt')
109
+ async def item(item_id: int):
110
+ item = service.get_item_by_id(item_id)
111
+ if not item:
112
+ fastapi_chameleon.generic_error('errors/unauthorized.pt',
113
+ fastapi.status.HTTP_401_UNAUTHORIZED)
114
+
115
+ return item.dict()
116
+ ```
@@ -0,0 +1,15 @@
1
+ LICENSE
2
+ README.md
3
+ setup.py
4
+ fastapi_chameleon/__init__.py
5
+ fastapi_chameleon/engine.py
6
+ fastapi_chameleon/exceptions.py
7
+ fastapi_chameleon.egg-info/PKG-INFO
8
+ fastapi_chameleon.egg-info/SOURCES.txt
9
+ fastapi_chameleon.egg-info/dependency_links.txt
10
+ fastapi_chameleon.egg-info/requires.txt
11
+ fastapi_chameleon.egg-info/top_level.txt
12
+ tests/test_generic_error.py
13
+ tests/test_init.py
14
+ tests/test_not_found.py
15
+ tests/test_render.py
@@ -0,0 +1,2 @@
1
+ fastapi
2
+ Chameleon
@@ -0,0 +1 @@
1
+ fastapi_chameleon
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,67 @@
1
+ import io
2
+ import os
3
+ import re
4
+
5
+ from setuptools import find_packages
6
+ from setuptools import setup
7
+
8
+
9
+ def read(filename):
10
+ filename = os.path.join(os.path.dirname(__file__), filename)
11
+ text_type = type(u"")
12
+ with io.open(filename, mode="r", encoding='utf-8') as fd:
13
+ return re.sub(text_type(r':[a-z]+:`~?(.*?)`'), text_type(r'``\1``'), fd.read())
14
+
15
+
16
+ def read_version():
17
+ filename = os.path.join(os.path.dirname(__file__), 'fastapi_chameleon', '__init__.py')
18
+ with open(filename, mode="r", encoding='utf-8') as fin:
19
+ for line in fin:
20
+ if line and line.strip() and line.startswith('__version__'):
21
+ return line.split('=')[1].strip().strip("'").strip('"')
22
+
23
+ return "0.0.0.0"
24
+
25
+
26
+ # requirements_txt = os.path.join(
27
+ # os.path.dirname(__file__),
28
+ # 'requirements.txt'
29
+ # )
30
+
31
+ # with open(requirements_txt, 'r', encoding='utf-8') as fin:
32
+ # requires = [
33
+ # line.strip()
34
+ # for line in fin
35
+ # if line and line.strip() and not line.strip().startswith('#')
36
+ # ]
37
+
38
+ requires = ["fastapi", "Chameleon"]
39
+
40
+ setup(
41
+ name="fastapi_chameleon",
42
+ version=read_version(),
43
+ url="https://github.com/mikeckennedy/fastapi-chameleon",
44
+ license='MIT',
45
+
46
+ author="Michael Kennedy",
47
+ author_email="michael@talkpython.fm",
48
+
49
+ description="Adds integration of the Chameleon template language to FastAPI.",
50
+ long_description=read("README.md"),
51
+ long_description_content_type="text/markdown",
52
+
53
+ packages=find_packages(exclude=('tests', 'example', 'readme_resources', 'build', 'dist',)),
54
+
55
+ install_requires=requires,
56
+
57
+ classifiers=[
58
+ 'Development Status :: 2 - Pre-Alpha',
59
+ 'License :: OSI Approved :: MIT License',
60
+ 'Programming Language :: Python',
61
+ 'Programming Language :: Python :: 3.6',
62
+ 'Programming Language :: Python :: 3.7',
63
+ 'Programming Language :: Python :: 3.8',
64
+ 'Programming Language :: Python :: 3.9',
65
+ 'Programming Language :: Python :: 3.10',
66
+ ],
67
+ )
@@ -0,0 +1,63 @@
1
+ import asyncio
2
+
3
+ import fastapi
4
+ import pytest
5
+
6
+ import fastapi_chameleon
7
+ import fastapi_chameleon as fc
8
+
9
+
10
+ # setup_global_template - needed as pytest mix-in.
11
+ # noinspection PyUnusedLocal
12
+ @pytest.mark.parametrize(
13
+ ("status_code", "template_file", "expected_h1_in_body"),
14
+ [
15
+ (fastapi.status.HTTP_400_BAD_REQUEST, "errors/404.pt", b'<h1>This is a pretty 404 page.</h1>'),
16
+ (fastapi.status.HTTP_401_UNAUTHORIZED, "errors/other_error_page.pt", b'<h1>Another pretty 404 page.</h1>'),
17
+ (fastapi.status.HTTP_403_FORBIDDEN, "errors/404.pt", b'<h1>This is a pretty 404 page.</h1>'),
18
+ (fastapi.status.HTTP_404_NOT_FOUND, "errors/other_error_page.pt", b'<h1>Another pretty 404 page.</h1>'),
19
+ (fastapi.status.HTTP_405_METHOD_NOT_ALLOWED, "errors/404.pt", b'<h1>This is a pretty 404 page.</h1>'),
20
+ (fastapi.status.HTTP_406_NOT_ACCEPTABLE, "errors/other_error_page.pt", b'<h1>Another pretty 404 page.</h1>'),
21
+ (fastapi.status.HTTP_407_PROXY_AUTHENTICATION_REQUIRED, "errors/404.pt",
22
+ b'<h1>This is a pretty 404 page.</h1>'),
23
+ (fastapi.status.HTTP_408_REQUEST_TIMEOUT, "errors/other_error_page.pt", b'<h1>Another pretty 404 page.</h1>'),
24
+ ],
25
+ )
26
+ def test_friendly_403_sync_method(setup_global_template, status_code, template_file, expected_h1_in_body):
27
+ @fc.template('home/index.pt')
28
+ def view_method(a, b, c):
29
+ fastapi_chameleon.generic_error(template_file, status_code)
30
+ return {'a': a, 'b': b, 'c': c}
31
+
32
+ resp = view_method(1, 2, 3)
33
+ assert isinstance(resp, fastapi.Response)
34
+ assert resp.status_code == status_code
35
+ assert expected_h1_in_body in resp.body
36
+
37
+
38
+ # setup_global_template - needed as pytest mix-in.
39
+ # noinspection PyUnusedLocal
40
+ @pytest.mark.parametrize(
41
+ ("status_code", "template_file", "expected_h1_in_body"),
42
+ [
43
+ (fastapi.status.HTTP_400_BAD_REQUEST, "errors/404.pt", b'<h1>This is a pretty 404 page.</h1>'),
44
+ (fastapi.status.HTTP_401_UNAUTHORIZED, "errors/other_error_page.pt", b'<h1>Another pretty 404 page.</h1>'),
45
+ (fastapi.status.HTTP_403_FORBIDDEN, "errors/404.pt", b'<h1>This is a pretty 404 page.</h1>'),
46
+ (fastapi.status.HTTP_404_NOT_FOUND, "errors/other_error_page.pt", b'<h1>Another pretty 404 page.</h1>'),
47
+ (fastapi.status.HTTP_405_METHOD_NOT_ALLOWED, "errors/404.pt", b'<h1>This is a pretty 404 page.</h1>'),
48
+ (fastapi.status.HTTP_406_NOT_ACCEPTABLE, "errors/other_error_page.pt", b'<h1>Another pretty 404 page.</h1>'),
49
+ (fastapi.status.HTTP_407_PROXY_AUTHENTICATION_REQUIRED, "errors/404.pt",
50
+ b'<h1>This is a pretty 404 page.</h1>'),
51
+ (fastapi.status.HTTP_408_REQUEST_TIMEOUT, "errors/other_error_page.pt", b'<h1>Another pretty 404 page.</h1>'),
52
+ ],
53
+ )
54
+ def test_friendly_403_async_method(setup_global_template, status_code, template_file, expected_h1_in_body):
55
+ @fc.template('home/index.pt')
56
+ async def view_method(a, b, c):
57
+ fastapi_chameleon.generic_error(template_file, status_code)
58
+ return {'a': a, 'b': b, 'c': c}
59
+
60
+ resp = asyncio.run(view_method(1, 2, 3))
61
+ assert isinstance(resp, fastapi.Response)
62
+ assert resp.status_code == status_code
63
+ assert expected_h1_in_body in resp.body
@@ -0,0 +1,28 @@
1
+ import pytest
2
+
3
+ import fastapi_chameleon as fc
4
+ from fastapi_chameleon.exceptions import FastAPIChameleonException
5
+
6
+
7
+ def test_cannot_decorate_with_missing_init():
8
+ fc.engine.clear()
9
+
10
+ with pytest.raises(FastAPIChameleonException):
11
+ @fc.template('home/index.pt')
12
+ def view_method(a, b, c):
13
+ return {"a": a, "b": b, "c": c}
14
+
15
+ view_method(1, 2, 3)
16
+
17
+
18
+ def test_can_call_init_with_good_path(test_templates_path):
19
+ fc.global_init(str(test_templates_path), cache_init=False)
20
+
21
+ # Clear paths so as to no affect future tests
22
+ fc.engine.clear()
23
+
24
+
25
+ def test_cannot_call_init_with_bad_path(test_templates_path):
26
+ bad_path = test_templates_path / "missing"
27
+ with pytest.raises(Exception):
28
+ fc.global_init(str(bad_path), cache_init=False)
@@ -0,0 +1,48 @@
1
+ import asyncio
2
+
3
+ import fastapi
4
+
5
+ import fastapi_chameleon
6
+ import fastapi_chameleon as fc
7
+
8
+
9
+ # setup_global_template - needed as pytest mix-in.
10
+ # noinspection PyUnusedLocal
11
+ def test_friendly_404_sync_method(setup_global_template):
12
+ @fc.template('home/index.pt')
13
+ def view_method(a, b, c):
14
+ fastapi_chameleon.not_found()
15
+ return {'a': a, 'b': b, 'c': c}
16
+
17
+ resp = view_method(1, 2, 3)
18
+ assert isinstance(resp, fastapi.Response)
19
+ assert resp.status_code == 404
20
+ assert b'<h1>This is a pretty 404 page.</h1>' in resp.body
21
+
22
+
23
+ # setup_global_template - needed as pytest mix-in.
24
+ # noinspection PyUnusedLocal
25
+ def test_friendly_404_custom_template_sync_method(setup_global_template):
26
+ @fc.template('home/index.pt')
27
+ def view_method(a, b, c):
28
+ fastapi_chameleon.not_found(four04template_file='errors/other_error_page.pt')
29
+ return {'a': a, 'b': b, 'c': c}
30
+
31
+ resp = view_method(1, 2, 3)
32
+ assert isinstance(resp, fastapi.Response)
33
+ assert resp.status_code == 404
34
+ assert b'<h1>Another pretty 404 page.</h1>' in resp.body
35
+
36
+
37
+ # setup_global_template - needed as pytest mix-in.
38
+ # noinspection PyUnusedLocal
39
+ def test_friendly_404_async_method(setup_global_template):
40
+ @fc.template('home/index.pt')
41
+ async def view_method(a, b, c):
42
+ fastapi_chameleon.not_found()
43
+ return {'a': a, 'b': b, 'c': c}
44
+
45
+ resp = asyncio.run(view_method(1, 2, 3))
46
+ assert isinstance(resp, fastapi.Response)
47
+ assert resp.status_code == 404
48
+ assert b'<h1>This is a pretty 404 page.</h1>' in resp.body
@@ -0,0 +1,102 @@
1
+ import asyncio
2
+
3
+ import fastapi
4
+ # noinspection PyPackageRequirements
5
+ import pytest
6
+
7
+ import fastapi_chameleon as fc
8
+
9
+
10
+ # setup_global_template - needed as pytest mix-in.
11
+ # noinspection PyUnusedLocal
12
+ def test_cannot_decorate_missing_template(setup_global_template):
13
+ with pytest.raises(ValueError):
14
+ @fc.template('home/missing.pt')
15
+ def view_method():
16
+ return {}
17
+
18
+ view_method()
19
+
20
+
21
+ # setup_global_template - needed as pytest mix-in.
22
+ # noinspection PyUnusedLocal
23
+ def test_requires_template_for_default_name(setup_global_template):
24
+ with pytest.raises(ValueError):
25
+ @fc.template(None)
26
+ def view_method():
27
+ return {}
28
+
29
+ view_method()
30
+
31
+
32
+ # setup_global_template - needed as pytest mix-in.
33
+ # noinspection PyUnusedLocal
34
+ def test_default_template_name_pt(setup_global_template):
35
+ @fc.template()
36
+ def index(a, b, c):
37
+ return {'a': a, 'b': b, 'c': c, 'world': 'WORLD'}
38
+
39
+ resp = index(1, 2, 3)
40
+ assert isinstance(resp, fastapi.Response)
41
+ assert resp.status_code == 200
42
+ html = resp.body.decode('utf-8')
43
+ assert '<h1>Hello default WORLD!</h1>' in html
44
+
45
+
46
+ # setup_global_template - needed as pytest mix-in.
47
+ # noinspection PyUnusedLocal
48
+ def test_default_template_name_no_parentheses(setup_global_template):
49
+ @fc.template
50
+ def index(a, b, c):
51
+ return {'a': a, 'b': b, 'c': c, 'world': 'WORLD'}
52
+
53
+ resp = index(1, 2, 3)
54
+ assert isinstance(resp, fastapi.Response)
55
+ assert resp.status_code == 200
56
+ html = resp.body.decode('utf-8')
57
+ assert '<h1>Hello default WORLD!</h1>' in html
58
+
59
+
60
+ def test_default_template_name_html(setup_global_template):
61
+ @fc.template()
62
+ def details(a, b, c):
63
+ return {'a': a, 'b': b, 'c': c, 'world': 'WORLD'}
64
+
65
+ resp = details(1, 2, 3)
66
+ assert isinstance(resp, fastapi.Response)
67
+ assert resp.status_code == 200
68
+ html = resp.body.decode('utf-8')
69
+ assert '<h1>Hello default WORLD!</h1>' in html
70
+
71
+
72
+ # setup_global_template - needed as pytest mix-in.
73
+ # noinspection PyUnusedLocal
74
+ def test_can_decorate_dict_sync_method(setup_global_template):
75
+ @fc.template('home/index.pt')
76
+ def view_method(a, b, c):
77
+ return {'a': a, 'b': b, 'c': c}
78
+
79
+ resp = view_method(1, 2, 3)
80
+ assert isinstance(resp, fastapi.Response)
81
+ assert resp.status_code == 200
82
+
83
+
84
+ def test_can_decorate_dict_async_method(setup_global_template):
85
+ @fc.template('home/index.pt')
86
+ async def view_method(a, b, c):
87
+ return {'a': a, 'b': b, 'c': c}
88
+
89
+ resp = asyncio.run(view_method(1, 2, 3))
90
+ assert isinstance(resp, fastapi.Response)
91
+ assert resp.status_code == 200
92
+
93
+
94
+ def test_direct_response_pass_through():
95
+ @fc.template('home/index.pt')
96
+ def view_method(a, b, c):
97
+ return fastapi.Response(content='abc', status_code=418)
98
+
99
+ resp = view_method(1, 2, 3)
100
+ assert isinstance(resp, fastapi.Response)
101
+ assert resp.status_code == 418
102
+ assert resp.body == b"abc"