fastapi_chameleon 0.1.16__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,130 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ pip-wheel-metadata/
24
+ share/python-wheels/
25
+ *.egg-info/
26
+ .installed.cfg
27
+ *.egg
28
+ MANIFEST
29
+
30
+ # PyInstaller
31
+ # Usually these files are written by a python script from a template
32
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
33
+ *.manifest
34
+ *.spec
35
+
36
+ # Installer logs
37
+ pip-log.txt
38
+ pip-delete-this-directory.txt
39
+
40
+ # Unit test / coverage reports
41
+ htmlcov/
42
+ .tox/
43
+ .nox/
44
+ .coverage
45
+ .coverage.*
46
+ .cache
47
+ nosetests.xml
48
+ coverage.xml
49
+ *.cover
50
+ *.py,cover
51
+ .hypothesis/
52
+ .pytest_cache/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ target/
76
+
77
+ # Jupyter Notebook
78
+ .ipynb_checkpoints
79
+
80
+ # IPython
81
+ profile_default/
82
+ ipython_config.py
83
+
84
+ # pyenv
85
+ .python-version
86
+
87
+ # pipenv
88
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
89
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
90
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
91
+ # install all needed dependencies.
92
+ #Pipfile.lock
93
+
94
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow
95
+ __pypackages__/
96
+
97
+ # Celery stuff
98
+ celerybeat-schedule
99
+ celerybeat.pid
100
+
101
+ # SageMath parsed files
102
+ *.sage.py
103
+
104
+ # Environments
105
+ .env
106
+ .venv
107
+ env/
108
+ venv/
109
+ ENV/
110
+ env.bak/
111
+ venv.bak/
112
+
113
+ # Spyder project settings
114
+ .spyderproject
115
+ .spyproject
116
+
117
+ # Rope project settings
118
+ .ropeproject
119
+
120
+ # mkdocs documentation
121
+ /site
122
+
123
+ # mypy
124
+ .mypy_cache/
125
+ .dmypy.json
126
+ dmypy.json
127
+
128
+ # Pyre type checker
129
+ .pyre/
130
+ .idea
@@ -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,118 @@
1
+ Metadata-Version: 2.3
2
+ Name: fastapi_chameleon
3
+ Version: 0.1.16
4
+ Summary: Adds integration of the Chameleon template language to FastAPI.
5
+ Project-URL: Homepage, https://github.com/mikeckennedy/fastapi-chameleon
6
+ Author-email: Michael Kennedy <michael@talkpython.fm>
7
+ License: MIT
8
+ Keywords: Chameleon,FastAPI,integration,template
9
+ Classifier: Development Status :: 5 - Production/Stable
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python
12
+ Classifier: Programming Language :: Python :: 3.9
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Requires-Python: >=3.9
18
+ Requires-Dist: chameleon
19
+ Requires-Dist: fastapi
20
+ Description-Content-Type: text/markdown
21
+
22
+ # fastapi-chameleon
23
+
24
+ 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).
25
+
26
+ ## Installation
27
+
28
+ Simply `pip install fastapi_chameleon`.
29
+
30
+ ## Usage
31
+
32
+ This is easy to use. Just create a folder within your web app to hold the templates such as:
33
+
34
+ ```
35
+ ├── main.py
36
+ ├── views.py
37
+
38
+ ├── templates
39
+ │ ├── home
40
+ │ │ └── index.pt
41
+ │ └── shared
42
+ │ └── layout.pt
43
+
44
+ ```
45
+
46
+ In the app startup, tell the library about the folder you wish to use:
47
+
48
+ ```python
49
+ import os
50
+ from pathlib import Path
51
+ import fastapi_chameleon
52
+
53
+ dev_mode = True
54
+
55
+ BASE_DIR = Path(__file__).resolve().parent
56
+ template_folder = str(BASE_DIR / 'templates')
57
+ fastapi_chameleon.global_init(template_folder, auto_reload=dev_mode)
58
+ ```
59
+
60
+ Then just decorate the FastAPI view methods (works on sync and async methods):
61
+
62
+ ```python
63
+ @router.post('/')
64
+ @fastapi_chameleon.template('home/index.pt')
65
+ async def home_post(request: Request):
66
+ form = await request.form()
67
+ vm = PersonViewModel(**form)
68
+
69
+ return vm.dict() # {'first':'Michael', 'last':'Kennedy', ...}
70
+
71
+ ```
72
+
73
+ The view method should return a `dict` to be passed as variables/values to the template.
74
+
75
+ If a `fastapi.Response` is returned, the template is skipped and the response along with status_code and
76
+ other values is directly passed through. This is common for redirects and error responses not meant
77
+ for this page template.
78
+
79
+ ## Friendly 404s and errors
80
+
81
+ A common technique for user-friendly sites is to use a
82
+ [custom HTML page for 404 responses](http://www.instantshift.com/2019/10/16/user-friendly-404-pages/).
83
+ This is especially important in FastAPI because FastAPI returns a 404 response + JSON by default.
84
+ This library has support for friendly 404 pages using the `fastapi_chameleon.not_found()` function.
85
+
86
+ Here's an example:
87
+
88
+ ```python
89
+ @router.get('/catalog/item/{item_id}')
90
+ @fastapi_chameleon.template('catalog/item.pt')
91
+ async def item(item_id: int):
92
+ item = service.get_item_by_id(item_id)
93
+ if not item:
94
+ fastapi_chameleon.not_found()
95
+
96
+ return item.dict()
97
+ ```
98
+
99
+ This will render a 404 response with using the template file `templates/errors/404.pt`.
100
+ You can specify another template to use for the response, but it's not required.
101
+
102
+ If you need to return errors other than `Not Found` (status code `404`), you can use a more
103
+ generic function: `fastapi_chameleon.generic_error(template_file: str, status_code: int)`.
104
+ This function will allow you to return different status codes. It's generic, thus you'll have
105
+ to pass a path to your error template file as well as a status code you want the user to get
106
+ in response. For example:
107
+
108
+ ```python
109
+ @router.get('/catalog/item/{item_id}')
110
+ @fastapi_chameleon.template('catalog/item.pt')
111
+ async def item(item_id: int):
112
+ item = service.get_item_by_id(item_id)
113
+ if not item:
114
+ fastapi_chameleon.generic_error('errors/unauthorized.pt',
115
+ fastapi.status.HTTP_401_UNAUTHORIZED)
116
+
117
+ return item.dict()
118
+ ```
@@ -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,39 @@
1
+ import asyncio
2
+ from pathlib import Path
3
+
4
+ import fastapi
5
+ import uvicorn
6
+
7
+ import fastapi_chameleon
8
+
9
+ app = fastapi.FastAPI()
10
+
11
+
12
+ @app.get("/")
13
+ @fastapi_chameleon.template('index.pt')
14
+ def hello_world():
15
+ return {'message': "Let's go Chameleon and FastAPI!"}
16
+
17
+
18
+ @app.get('/async')
19
+ @fastapi_chameleon.template('async.pt')
20
+ async def async_world():
21
+ await asyncio.sleep(.01)
22
+ return {'message': "Let's go async Chameleon and FastAPI!"}
23
+
24
+
25
+ def add_chameleon():
26
+ dev_mode = True
27
+
28
+ BASE_DIR = Path(__file__).resolve().parent
29
+ template_folder = (BASE_DIR / 'templates').as_posix()
30
+ fastapi_chameleon.global_init(template_folder, auto_reload=dev_mode)
31
+
32
+
33
+ def main():
34
+ add_chameleon()
35
+ uvicorn.run(app, host='127.0.0.1', port=8000)
36
+
37
+
38
+ if __name__ == '__main__':
39
+ main()
@@ -0,0 +1,8 @@
1
+ body {
2
+ padding: 20px;
3
+ background-color: #fafafa;
4
+ }
5
+
6
+ h1, p {
7
+ text-align: center;
8
+ }
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Title</title>
6
+ <link rel="stylesheet" href="/static/site.css">
7
+ </head>
8
+ <body>
9
+ <h1>Hello async world</h1>
10
+ <p>Your async message is <strong>${message}</strong></p>
11
+ </body>
12
+ </html>
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <title>Title</title>
6
+ <link rel="stylesheet" href="/static/site.css">
7
+ </head>
8
+ <body>
9
+ <h1>Hello world</h1>
10
+ <p>Your message is <strong>${message}</strong>
11
+ <a href="/async">See the async example</a> too.</p>
12
+ </body>
13
+ </html>
@@ -0,0 +1,11 @@
1
+ """fastapi-chameleon - Adds integration of the Chameleon template language to FastAPI."""
2
+
3
+ __version__ = '0.1.16'
4
+ __author__ = 'Michael Kennedy <michael@talkpython.fm>'
5
+ __all__ = ['template', 'global_init', 'not_found', 'response', 'generic_error', ]
6
+
7
+ from .engine import generic_error
8
+ from .engine import global_init
9
+ from .engine import not_found
10
+ from .engine import response
11
+ from .engine import template
@@ -0,0 +1,146 @@
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 (
10
+ FastAPIChameleonException,
11
+ FastAPIChameleonGenericException,
12
+ FastAPIChameleonNotFoundException,
13
+ )
14
+
15
+ __templates: Optional[PageTemplateLoader] = None
16
+ template_path: Optional[str] = None
17
+
18
+
19
+ def global_init(template_folder: str, auto_reload=False, cache_init=True):
20
+ global __templates, template_path
21
+
22
+ if __templates and cache_init:
23
+ return
24
+
25
+ if not template_folder:
26
+ msg = 'The template_folder must be specified.'
27
+ raise FastAPIChameleonException(msg)
28
+
29
+ if not os.path.isdir(template_folder):
30
+ msg = f"The specified template folder must be a folder, it's not: {template_folder}"
31
+ raise FastAPIChameleonException(msg)
32
+
33
+ template_path = template_folder
34
+ __templates = PageTemplateLoader(template_folder, auto_reload=auto_reload)
35
+
36
+
37
+ def clear():
38
+ global __templates, template_path
39
+ __templates = None
40
+ template_path = None
41
+
42
+
43
+ def render(template_file: str, **template_data: dict) -> str:
44
+ if not __templates:
45
+ raise FastAPIChameleonException('You must call global_init() before rendering templates.')
46
+
47
+ page: PageTemplate = __templates[template_file]
48
+ return page.render(encoding='utf-8', **template_data)
49
+
50
+
51
+ def response(template_file: str, mimetype='text/html', status_code=200, **template_data) -> fastapi.Response:
52
+ html = render(template_file, **template_data)
53
+ return fastapi.Response(content=html, media_type=mimetype, status_code=status_code)
54
+
55
+
56
+ def template(template_file: Optional[Union[Callable, str]] = None, mimetype: str = 'text/html'):
57
+ """
58
+ Decorate a FastAPI view method to render an HTML response.
59
+
60
+ :param str template_file: Optional, the Chameleon template file (path relative to template folder, *.pt).
61
+ :param str mimetype: The mimetype response (defaults to text/html).
62
+ :return: Decorator to be consumed by FastAPI
63
+ """
64
+
65
+ wrapped_function = None
66
+ if callable(template_file):
67
+ wrapped_function = template_file
68
+ template_file = None
69
+
70
+ def response_inner(f):
71
+ nonlocal template_file
72
+ global template_path
73
+
74
+ if not template_path:
75
+ template_path = 'templates'
76
+ # raise FastAPIChameleonException("Cannot continue: fastapi_chameleon.global_init() has not been called.")
77
+
78
+ if not template_file:
79
+ # Use the default naming scheme: template_folder/module_name/function_name.pt
80
+ module = f.__module__
81
+ if '.' in module:
82
+ module = module.split('.')[-1]
83
+ view = f.__name__
84
+ template_file = f'{module}/{view}.html'
85
+
86
+ if not os.path.exists(os.path.join(template_path, template_file)):
87
+ template_file = f'{module}/{view}.pt'
88
+
89
+ @wraps(f)
90
+ def sync_view_method(*args, **kwargs):
91
+ try:
92
+ response_val = f(*args, **kwargs)
93
+ return __render_response(template_file, response_val, mimetype)
94
+ except FastAPIChameleonNotFoundException as nfe:
95
+ return __render_response(nfe.template_file, {}, 'text/html', 404)
96
+ except FastAPIChameleonGenericException as nfe:
97
+ template_data = nfe.template_data if nfe.template_data is not None else {}
98
+ return __render_response(nfe.template_file, template_data, 'text/html', nfe.status_code)
99
+
100
+ @wraps(f)
101
+ async def async_view_method(*args, **kwargs):
102
+ try:
103
+ response_val = await f(*args, **kwargs)
104
+ return __render_response(template_file, response_val, mimetype)
105
+ except FastAPIChameleonNotFoundException as nfe:
106
+ return __render_response(nfe.template_file, {}, 'text/html', 404)
107
+ except FastAPIChameleonGenericException as nfe:
108
+ template_data = nfe.template_data if nfe.template_data is not None else {}
109
+ return __render_response(nfe.template_file, template_data, 'text/html', nfe.status_code)
110
+
111
+ if inspect.iscoroutinefunction(f):
112
+ return async_view_method
113
+ else:
114
+ return sync_view_method
115
+
116
+ return response_inner(wrapped_function) if wrapped_function else response_inner
117
+
118
+
119
+ def __render_response(template_file, response_val, mimetype, status_code: int = 200) -> fastapi.Response:
120
+ # source skip: assign-if-exp
121
+ if isinstance(response_val, fastapi.Response):
122
+ return response_val
123
+
124
+ if template_file and not isinstance(response_val, dict):
125
+ msg = f'Invalid return type {type(response_val)}, we expected a dict or fastapi.Response as the return value.'
126
+ raise FastAPIChameleonException(msg)
127
+
128
+ model = response_val
129
+
130
+ html = render(template_file, **model)
131
+ return fastapi.Response(content=html, media_type=mimetype, status_code=status_code)
132
+
133
+
134
+ def not_found(four04template_file: str = 'errors/404.pt'):
135
+ msg = 'The URL resulted in a 404 response.'
136
+
137
+ if four04template_file and four04template_file.strip():
138
+ raise FastAPIChameleonNotFoundException(msg, four04template_file)
139
+ else:
140
+ raise FastAPIChameleonNotFoundException(msg)
141
+
142
+
143
+ def generic_error(template_file: str, status_code: int, template_data: Optional[dict] = None):
144
+ msg = 'The URL resulted in an error.'
145
+
146
+ raise FastAPIChameleonGenericException(template_file, status_code, msg, template_data=template_data)
@@ -0,0 +1,24 @@
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, template_data: Optional[dict] = 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
24
+ self.template_data: Optional[dict] = template_data
@@ -0,0 +1,55 @@
1
+ [project]
2
+ name = "fastapi_chameleon"
3
+ version = "0.1.16"
4
+ description = "Adds integration of the Chameleon template language to FastAPI."
5
+ readme = { file = "README.md", content-type = "text/markdown" }
6
+ requires-python = ">=3.9"
7
+ license = { text = "MIT" }
8
+ authors = [
9
+ { name = "Michael Kennedy", email = "michael@talkpython.fm" }
10
+ ]
11
+ keywords = ["FastAPI", "Chameleon", "template", "integration"]
12
+ classifiers = [
13
+ "Development Status :: 5 - Production/Stable",
14
+ "License :: OSI Approved :: MIT License",
15
+ "Programming Language :: Python",
16
+ "Programming Language :: Python :: 3.9",
17
+ "Programming Language :: Python :: 3.10",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Programming Language :: Python :: 3.13"
21
+ ]
22
+ dependencies = [
23
+ "fastapi",
24
+ "chameleon"
25
+ ]
26
+
27
+ [project.urls]
28
+ "Homepage" = "https://github.com/mikeckennedy/fastapi-chameleon"
29
+
30
+ [tool.setuptools]
31
+ packages = ["fastapi_chameleon"]
32
+
33
+ [build-system]
34
+ requires = ["hatchling>=1.21.0", "hatch-vcs>=0.3.0"]
35
+ build-backend = "hatchling.build"
36
+
37
+
38
+ [tool.hatch.build.targets.sdist]
39
+ exclude = [
40
+ "/.github",
41
+ "/tests",
42
+ "/example_app",
43
+ "settings.json",
44
+ ]
45
+
46
+ [tool.hatch.build.targets.wheel]
47
+ packages = ["fastapi_chameleon"]
48
+ exclude = [
49
+ "/.github",
50
+ "/tests",
51
+ "/example",
52
+ "/example_client",
53
+ "settings.json",
54
+ "ruff.toml",
55
+ ]
@@ -0,0 +1,8 @@
1
+ -r requirements.txt
2
+
3
+ pytest
4
+ pytest-clarity
5
+ twine
6
+ hatchling
7
+ hatch-vcs>=0.3.0
8
+ uvicorn
@@ -0,0 +1,2 @@
1
+ fastapi
2
+ Chameleon
@@ -0,0 +1,42 @@
1
+ # [ruff]
2
+ line-length = 120
3
+ format.quote-style = "single"
4
+
5
+ # Enable Pyflakes `E` and `F` codes by default.
6
+ select = ["E", "F"]
7
+ ignore = []
8
+
9
+ # Exclude a variety of commonly ignored directories.
10
+ exclude = [
11
+ ".bzr",
12
+ ".direnv",
13
+ ".eggs",
14
+ ".git",
15
+ ".hg",
16
+ ".mypy_cache",
17
+ ".nox",
18
+ ".pants.d",
19
+ ".ruff_cache",
20
+ ".svn",
21
+ ".tox",
22
+ "__pypackages__",
23
+ "_build",
24
+ "buck-out",
25
+ "build",
26
+ "dist",
27
+ "node_modules",
28
+ ".env",
29
+ ".venv",
30
+ "venv",
31
+ ]
32
+ per-file-ignores = {}
33
+
34
+ # Allow unused variables when underscore-prefixed.
35
+ # dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
36
+
37
+ # Assume Python 3.13.
38
+ target-version = "py313"
39
+
40
+ #[tool.ruff.mccabe]
41
+ ## Unlike Flake8, default to a complexity level of 10.
42
+ mccabe.max-complexity = 10
@@ -0,0 +1,6 @@
1
+ [tox]
2
+ envlist = py39,py310,py311,py312,py313
3
+
4
+ [testenv]
5
+ commands = pytest fastapi-chameleon
6
+ deps = pytest