fastapi-cli 0.0.29__tar.gz → 0.0.30__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.
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/PKG-INFO +1 -1
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/pyproject.toml +1 -1
- fastapi_cli-0.0.30/src/fastapi_cli/__init__.py +1 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/src/fastapi_cli/cli.py +23 -30
- fastapi_cli-0.0.30/src/fastapi_cli/utils/cli.py +190 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/test_cli.py +11 -11
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/test_utils_cli.py +2 -1
- fastapi_cli-0.0.29/src/fastapi_cli/__init__.py +0 -1
- fastapi_cli-0.0.29/src/fastapi_cli/utils/cli.py +0 -91
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/LICENSE +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/README.md +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/scripts/add_latest_release_date.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/scripts/format.sh +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/scripts/lint.sh +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/scripts/prepare_release.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/scripts/test-cov-html.sh +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/scripts/test.sh +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/src/fastapi_cli/__main__.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/src/fastapi_cli/config.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/src/fastapi_cli/discover.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/src/fastapi_cli/exceptions.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/src/fastapi_cli/logging.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/src/fastapi_cli/py.typed +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/src/fastapi_cli/utils/__init__.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/__init__.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/broken_package/mod/__init__.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/broken_package/mod/app.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/broken_package/utils.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_api/api.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app/api.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app/app.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_api/app/__init__.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_api/app/api.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_app/app/__init__.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_app/app/api.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_app/app/app.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_main/app/__init__.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_main/app/api.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_main/app/app.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_main/app/main.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_non_default/app/__init__.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_non_default/app/nondefault.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_main/api.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_main/app.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_main/main.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/non_default/nonstandard.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/nested_package/__init__.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/nested_package/package/__init__.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/nested_package/package/core/__init__.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/nested_package/package/core/utils.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/nested_package/package/mod/__init__.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/nested_package/package/mod/api.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/nested_package/package/mod/app.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/nested_package/package/mod/other.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/package/__init__.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/package/core/__init__.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/package/core/utils.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/package/mod/__init__.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/package/mod/api.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/package/mod/app.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/package/mod/other.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/pyproject_config/another_module.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/pyproject_config/my_module.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/pyproject_config/pyproject.toml +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/single_file_api.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/single_file_app.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/single_file_other.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/conftest.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/test_cli_pyproject.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/test_discover.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/test_prepare_release.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/test_requirements.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/test_utils_default_dir.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/test_utils_default_file.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/test_utils_package.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/test_utils_single_file.py +0 -0
- {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/utils.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.0.30"
|
|
@@ -146,11 +146,12 @@ def _run(
|
|
|
146
146
|
with get_rich_toolkit() as toolkit:
|
|
147
147
|
server_type = "development" if command == "dev" else "production"
|
|
148
148
|
|
|
149
|
-
toolkit.
|
|
150
|
-
toolkit.print_line()
|
|
149
|
+
toolkit.print(f"Starting FastAPI in {server_type} mode", emoji="⚡️")
|
|
151
150
|
|
|
151
|
+
toolkit.print_line()
|
|
152
152
|
toolkit.print(
|
|
153
|
-
"Searching for package file structure from directories with [blue]__init__.py[/blue] files"
|
|
153
|
+
"Searching for package file structure from directories with [blue]__init__.py[/blue] files",
|
|
154
|
+
emoji="🔎",
|
|
154
155
|
)
|
|
155
156
|
|
|
156
157
|
if entrypoint and (path or app):
|
|
@@ -158,7 +159,6 @@ def _run(
|
|
|
158
159
|
toolkit.print(
|
|
159
160
|
"[error]Cannot use --entrypoint together with path or --app arguments"
|
|
160
161
|
)
|
|
161
|
-
toolkit.print_line()
|
|
162
162
|
raise typer.Exit(code=1)
|
|
163
163
|
|
|
164
164
|
try:
|
|
@@ -172,8 +172,6 @@ def _run(
|
|
|
172
172
|
field = ".".join(str(loc) for loc in error["loc"])
|
|
173
173
|
toolkit.print(f" [red]•[/red] {field}: {error['msg']}")
|
|
174
174
|
|
|
175
|
-
toolkit.print_line()
|
|
176
|
-
|
|
177
175
|
raise typer.Exit(code=1) from None
|
|
178
176
|
|
|
179
177
|
try:
|
|
@@ -197,45 +195,43 @@ def _run(
|
|
|
197
195
|
module_data = import_data.module_data
|
|
198
196
|
import_string = import_data.import_string
|
|
199
197
|
|
|
200
|
-
toolkit.
|
|
198
|
+
toolkit.print_line()
|
|
199
|
+
toolkit.print(f"Importing from {module_data.extra_sys_path}", emoji="📂")
|
|
201
200
|
toolkit.print_line()
|
|
202
201
|
|
|
203
202
|
if module_data.module_paths:
|
|
204
203
|
root_tree = _get_module_tree(module_data.module_paths)
|
|
205
204
|
|
|
206
|
-
toolkit.print(root_tree
|
|
207
|
-
|
|
205
|
+
toolkit.print(root_tree)
|
|
206
|
+
|
|
207
|
+
toolkit.print_line()
|
|
208
208
|
|
|
209
209
|
toolkit.print(
|
|
210
210
|
"Importing the FastAPI app object from the module with the following code:",
|
|
211
|
-
tag="code",
|
|
212
211
|
)
|
|
213
212
|
toolkit.print_line()
|
|
214
213
|
toolkit.print(
|
|
215
|
-
f"[
|
|
214
|
+
f"[blue]from [bold]{module_data.module_import_str}[/bold] import [bold]{import_data.app_name}[/bold]",
|
|
216
215
|
)
|
|
217
|
-
toolkit.print_line()
|
|
218
216
|
|
|
219
|
-
toolkit.
|
|
220
|
-
|
|
221
|
-
tag="app",
|
|
222
|
-
)
|
|
217
|
+
toolkit.print_line()
|
|
218
|
+
toolkit.print(f"Using import string: [blue]{import_string}[/]", emoji="🐍")
|
|
223
219
|
|
|
220
|
+
toolkit.print_line()
|
|
224
221
|
mod_source_desc = SOURCE_DESCRIPTIONS[import_data.module_config_source]
|
|
225
222
|
app_source_desc = SOURCE_DESCRIPTIONS[import_data.app_name_config_source]
|
|
226
|
-
toolkit.
|
|
227
|
-
toolkit.print("Configuration sources:", tag="info")
|
|
223
|
+
toolkit.print("Configuration sources:", emoji="📋")
|
|
228
224
|
if mod_source_desc == app_source_desc:
|
|
229
|
-
toolkit.print(f"
|
|
225
|
+
toolkit.print(f"• Import string: {mod_source_desc}")
|
|
230
226
|
else:
|
|
231
|
-
toolkit.print(f"
|
|
232
|
-
toolkit.print(f"
|
|
227
|
+
toolkit.print(f"• Module: {mod_source_desc}")
|
|
228
|
+
toolkit.print(f"• App name: {app_source_desc}")
|
|
233
229
|
|
|
234
230
|
if import_data.module_config_source == "auto-discovery":
|
|
235
231
|
toolkit.print_line()
|
|
236
232
|
toolkit.print(
|
|
237
233
|
"You can configure an entrypoint in [blue]pyproject.toml[/] for this app with:",
|
|
238
|
-
|
|
234
|
+
emoji="💡",
|
|
239
235
|
)
|
|
240
236
|
toolkit.print_line()
|
|
241
237
|
toolkit.print(
|
|
@@ -246,24 +242,21 @@ def _run(
|
|
|
246
242
|
),
|
|
247
243
|
"toml",
|
|
248
244
|
theme="ansi_light",
|
|
249
|
-
)
|
|
245
|
+
),
|
|
250
246
|
)
|
|
251
247
|
|
|
252
248
|
url = public_url.rstrip("/") if public_url else f"http://{host}:{port}"
|
|
253
249
|
url_docs = f"{url}/docs"
|
|
254
250
|
|
|
255
251
|
toolkit.print_line()
|
|
256
|
-
toolkit.print(
|
|
257
|
-
|
|
258
|
-
f"Documentation at [link={url_docs}]{url_docs}[/]",
|
|
259
|
-
tag="server",
|
|
260
|
-
)
|
|
252
|
+
toolkit.print(f"Server started at [link={url}]{url}[/]", emoji="🌐")
|
|
253
|
+
toolkit.print(f"Documentation at [link={url_docs}]{url_docs}[/]")
|
|
261
254
|
|
|
262
255
|
if command == "dev":
|
|
263
256
|
toolkit.print_line()
|
|
264
257
|
toolkit.print(
|
|
265
258
|
"Running in development mode, for production use: [bold]fastapi run[/]",
|
|
266
|
-
|
|
259
|
+
emoji="💡",
|
|
267
260
|
)
|
|
268
261
|
|
|
269
262
|
if not uvicorn:
|
|
@@ -272,7 +265,7 @@ def _run(
|
|
|
272
265
|
) from None
|
|
273
266
|
|
|
274
267
|
toolkit.print_line()
|
|
275
|
-
toolkit.print("Logs:")
|
|
268
|
+
toolkit.print("Logs:", bullet=False)
|
|
276
269
|
toolkit.print_line()
|
|
277
270
|
|
|
278
271
|
extra_uvicorn_kwargs: dict[str, Any] = (
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import sys
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from rich._loop import loop_first
|
|
6
|
+
from rich.console import Console, ConsoleOptions, RenderableType, RenderResult
|
|
7
|
+
from rich.segment import Segment
|
|
8
|
+
from rich.text import Text
|
|
9
|
+
from rich_toolkit import RichToolkit
|
|
10
|
+
from rich_toolkit.element import Element
|
|
11
|
+
from rich_toolkit.styles import BaseStyle
|
|
12
|
+
from uvicorn.logging import DefaultFormatter
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def should_use_rich_logs() -> bool:
|
|
18
|
+
"""Return True when stdout is a TTY and rich logs should be used, False otherwise."""
|
|
19
|
+
return sys.stdout.isatty()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class IndentedBlock:
|
|
23
|
+
"""Indent a renderable, hanging a prefix (e.g. an emoji bullet) on the
|
|
24
|
+
first line and aligning wrapped/extra lines under the text."""
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
renderable: RenderableType,
|
|
29
|
+
*,
|
|
30
|
+
first_prefix: Text,
|
|
31
|
+
prefix: Text,
|
|
32
|
+
) -> None:
|
|
33
|
+
self.renderable = renderable
|
|
34
|
+
self.first_prefix = first_prefix
|
|
35
|
+
self.prefix = prefix
|
|
36
|
+
|
|
37
|
+
# Text renders its `end` ("\n" by default), which would break lines
|
|
38
|
+
self.first_prefix.end = ""
|
|
39
|
+
self.prefix.end = ""
|
|
40
|
+
|
|
41
|
+
def __rich_console__(
|
|
42
|
+
self, console: Console, options: ConsoleOptions
|
|
43
|
+
) -> RenderResult:
|
|
44
|
+
prefix_width = max(self.first_prefix.cell_len, self.prefix.cell_len)
|
|
45
|
+
lines = console.render_lines(
|
|
46
|
+
self.renderable,
|
|
47
|
+
options.update_width(options.max_width - prefix_width),
|
|
48
|
+
pad=False,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
new_line = Segment.line()
|
|
52
|
+
|
|
53
|
+
for first, line in loop_first(lines):
|
|
54
|
+
if any(segment.text.strip() for segment in line):
|
|
55
|
+
yield from console.render(
|
|
56
|
+
self.first_prefix if first else self.prefix, options
|
|
57
|
+
)
|
|
58
|
+
yield from line
|
|
59
|
+
|
|
60
|
+
yield new_line
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class FastAPIStyle(BaseStyle):
|
|
64
|
+
"""Uniform left indent with emoji bullets hanging to the left of the text.
|
|
65
|
+
|
|
66
|
+
``emoji=`` renders as a bullet in a fixed column; ``bullet=False`` drops
|
|
67
|
+
the bullet column and just indents. This is a small, purpose-built subset
|
|
68
|
+
of the richer style in fastapi-cloud-cli.
|
|
69
|
+
"""
|
|
70
|
+
|
|
71
|
+
content_padding = 1
|
|
72
|
+
emoji_column_width = 3
|
|
73
|
+
|
|
74
|
+
def render_element(
|
|
75
|
+
self,
|
|
76
|
+
element: Any,
|
|
77
|
+
is_active: bool = False,
|
|
78
|
+
done: bool = False,
|
|
79
|
+
parent: Element | None = None,
|
|
80
|
+
**kwargs: Any,
|
|
81
|
+
) -> RenderableType:
|
|
82
|
+
rendered = super().render_element(
|
|
83
|
+
element=element, is_active=is_active, done=done, parent=parent, **kwargs
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
emoji = kwargs.get("emoji", "")
|
|
87
|
+
|
|
88
|
+
if not emoji and not kwargs.get("bullet", True):
|
|
89
|
+
indent = Text(" " * (self.content_padding + 1))
|
|
90
|
+
return IndentedBlock(rendered, first_prefix=indent, prefix=indent)
|
|
91
|
+
|
|
92
|
+
return IndentedBlock(
|
|
93
|
+
rendered,
|
|
94
|
+
first_prefix=self._get_bullet_prefix(emoji),
|
|
95
|
+
prefix=Text(" " * (self.content_padding + self.emoji_column_width)),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
def _get_bullet_prefix(self, emoji: str) -> Text:
|
|
99
|
+
prefix = Text(" " * self.content_padding)
|
|
100
|
+
|
|
101
|
+
if emoji:
|
|
102
|
+
prefix.append_text(Text.from_markup(emoji))
|
|
103
|
+
|
|
104
|
+
prefix.pad_right(
|
|
105
|
+
self.content_padding + self.emoji_column_width - prefix.cell_len
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
return prefix
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
LOG_LEVEL_COLORS = {
|
|
112
|
+
"debug": "blue",
|
|
113
|
+
"info": "cyan",
|
|
114
|
+
"warning": "yellow",
|
|
115
|
+
"warn": "yellow",
|
|
116
|
+
"error": "red",
|
|
117
|
+
"critical": "magenta",
|
|
118
|
+
"fatal": "magenta",
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def _get_log_bullet(level: str) -> str:
|
|
123
|
+
"""Colored bar rendered in the emoji bullet column, matching the log
|
|
124
|
+
level, like the ``fastapi cloud logs`` output."""
|
|
125
|
+
color = LOG_LEVEL_COLORS.get(level.lower(), "dim")
|
|
126
|
+
|
|
127
|
+
return f"[{color}]▕[/{color}]"
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
class CustomFormatter(DefaultFormatter):
|
|
131
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
132
|
+
super().__init__(*args, **kwargs)
|
|
133
|
+
self.toolkit = get_rich_toolkit()
|
|
134
|
+
|
|
135
|
+
def formatMessage(self, record: logging.LogRecord) -> str:
|
|
136
|
+
message = record.getMessage()
|
|
137
|
+
result = self.toolkit.print_as_string(
|
|
138
|
+
message, emoji=_get_log_bullet(record.levelname)
|
|
139
|
+
)
|
|
140
|
+
# Prepend newline to fix alignment after ^C is printed by the terminal
|
|
141
|
+
if message == "Shutting down":
|
|
142
|
+
result = "\n" + result
|
|
143
|
+
return result
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def get_uvicorn_log_config() -> dict[str, Any]:
|
|
147
|
+
return {
|
|
148
|
+
"version": 1,
|
|
149
|
+
"disable_existing_loggers": False,
|
|
150
|
+
"formatters": {
|
|
151
|
+
"default": {
|
|
152
|
+
"()": CustomFormatter,
|
|
153
|
+
"fmt": "%(levelprefix)s %(message)s",
|
|
154
|
+
"use_colors": None,
|
|
155
|
+
},
|
|
156
|
+
"access": {
|
|
157
|
+
"()": CustomFormatter,
|
|
158
|
+
"fmt": "%(levelprefix)s %(client_addr)s - '%(request_line)s' %(status_code)s",
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
"handlers": {
|
|
162
|
+
"default": {
|
|
163
|
+
"formatter": "default",
|
|
164
|
+
"class": "logging.StreamHandler",
|
|
165
|
+
"stream": "ext://sys.stderr",
|
|
166
|
+
},
|
|
167
|
+
"access": {
|
|
168
|
+
"formatter": "access",
|
|
169
|
+
"class": "logging.StreamHandler",
|
|
170
|
+
"stream": "ext://sys.stdout",
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
"loggers": {
|
|
174
|
+
"uvicorn": {
|
|
175
|
+
"handlers": ["default"],
|
|
176
|
+
"level": "INFO",
|
|
177
|
+
"propagate": False,
|
|
178
|
+
},
|
|
179
|
+
"uvicorn.error": {"level": "INFO"},
|
|
180
|
+
"uvicorn.access": {
|
|
181
|
+
"handlers": ["access"],
|
|
182
|
+
"level": "INFO",
|
|
183
|
+
"propagate": False,
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def get_rich_toolkit() -> RichToolkit:
|
|
190
|
+
return RichToolkit(style=FastAPIStyle())
|
|
@@ -45,7 +45,7 @@ def test_dev() -> None:
|
|
|
45
45
|
assert "Module: path CLI argument" in result.output
|
|
46
46
|
assert "App name: auto-discovery" in result.output
|
|
47
47
|
assert "You can configure an entrypoint in pyproject.toml" not in result.output
|
|
48
|
-
assert "Starting development
|
|
48
|
+
assert "Starting FastAPI in development mode" in result.output
|
|
49
49
|
assert "Server started at http://127.0.0.1:8000" in result.output
|
|
50
50
|
assert "Documentation at http://127.0.0.1:8000/docs" in result.output
|
|
51
51
|
assert (
|
|
@@ -114,7 +114,7 @@ def test_dev_package() -> None:
|
|
|
114
114
|
assert "Module: path CLI argument" in result.output
|
|
115
115
|
assert "App name: auto-discovery" in result.output
|
|
116
116
|
assert "You can configure an entrypoint in pyproject.toml" not in result.output
|
|
117
|
-
assert "Starting development
|
|
117
|
+
assert "Starting FastAPI in development mode" in result.output
|
|
118
118
|
assert "Server started at http://127.0.0.1:8000" in result.output
|
|
119
119
|
assert "Documentation at http://127.0.0.1:8000/docs" in result.output
|
|
120
120
|
assert (
|
|
@@ -167,7 +167,7 @@ def test_dev_args() -> None:
|
|
|
167
167
|
assert "Module: path CLI argument" in result.output
|
|
168
168
|
assert "App name: --app CLI option" in result.output
|
|
169
169
|
assert "You can configure an entrypoint in pyproject.toml" not in result.output
|
|
170
|
-
assert "Starting development
|
|
170
|
+
assert "Starting FastAPI in development mode" in result.output
|
|
171
171
|
assert "Server started at http://192.168.0.2:8080" in result.output
|
|
172
172
|
assert "Documentation at http://192.168.0.2:8080/docs" in result.output
|
|
173
173
|
assert (
|
|
@@ -201,7 +201,7 @@ def test_dev_env_vars() -> None:
|
|
|
201
201
|
assert "Module: path CLI argument" in result.output
|
|
202
202
|
assert "App name: auto-discovery" in result.output
|
|
203
203
|
assert "You can configure an entrypoint in pyproject.toml" not in result.output
|
|
204
|
-
assert "Starting development
|
|
204
|
+
assert "Starting FastAPI in development mode" in result.output
|
|
205
205
|
assert "Server started at http://127.0.0.1:8111" in result.output
|
|
206
206
|
assert "Documentation at http://127.0.0.1:8111/docs" in result.output
|
|
207
207
|
assert (
|
|
@@ -242,7 +242,7 @@ def test_dev_env_vars_and_args() -> None:
|
|
|
242
242
|
assert "Module: path CLI argument" in result.output
|
|
243
243
|
assert "App name: auto-discovery" in result.output
|
|
244
244
|
assert "You can configure an entrypoint in pyproject.toml" not in result.output
|
|
245
|
-
assert "Starting development
|
|
245
|
+
assert "Starting FastAPI in development mode" in result.output
|
|
246
246
|
assert "Server started at http://127.0.0.1:8080" in result.output
|
|
247
247
|
assert "Documentation at http://127.0.0.1:8080/docs" in result.output
|
|
248
248
|
assert (
|
|
@@ -291,7 +291,7 @@ def test_run() -> None:
|
|
|
291
291
|
assert "Module: path CLI argument" in result.output
|
|
292
292
|
assert "App name: auto-discovery" in result.output
|
|
293
293
|
assert "You can configure an entrypoint in pyproject.toml" not in result.output
|
|
294
|
-
assert "Starting production
|
|
294
|
+
assert "Starting FastAPI in production mode" in result.output
|
|
295
295
|
assert "Server started at http://0.0.0.0:8000" in result.output
|
|
296
296
|
assert "Documentation at http://0.0.0.0:8000/docs" in result.output
|
|
297
297
|
|
|
@@ -318,7 +318,7 @@ def test_run_trust_proxy() -> None:
|
|
|
318
318
|
"log_config": get_uvicorn_log_config(),
|
|
319
319
|
}
|
|
320
320
|
assert "Using import string: single_file_app:app" in result.output
|
|
321
|
-
assert "Starting production
|
|
321
|
+
assert "Starting FastAPI in production mode" in result.output
|
|
322
322
|
assert "Server started at http://0.0.0.0:8000" in result.output
|
|
323
323
|
assert "Documentation at http://0.0.0.0:8000/docs" in result.output
|
|
324
324
|
assert (
|
|
@@ -369,7 +369,7 @@ def test_run_args() -> None:
|
|
|
369
369
|
assert "Module: path CLI argument" in result.output
|
|
370
370
|
assert "App name: --app CLI option" in result.output
|
|
371
371
|
assert "You can configure an entrypoint in pyproject.toml" not in result.output
|
|
372
|
-
assert "Starting production
|
|
372
|
+
assert "Starting FastAPI in production mode" in result.output
|
|
373
373
|
assert "Server started at http://192.168.0.2:8080" in result.output
|
|
374
374
|
assert "Documentation at http://192.168.0.2:8080/docs" in result.output
|
|
375
375
|
assert (
|
|
@@ -403,7 +403,7 @@ def test_run_env_vars() -> None:
|
|
|
403
403
|
assert "Module: path CLI argument" in result.output
|
|
404
404
|
assert "App name: auto-discovery" in result.output
|
|
405
405
|
assert "You can configure an entrypoint in pyproject.toml" not in result.output
|
|
406
|
-
assert "Starting production
|
|
406
|
+
assert "Starting FastAPI in production mode" in result.output
|
|
407
407
|
assert "Server started at http://0.0.0.0:8111" in result.output
|
|
408
408
|
assert "Documentation at http://0.0.0.0:8111/docs" in result.output
|
|
409
409
|
|
|
@@ -440,7 +440,7 @@ def test_run_env_vars_and_args() -> None:
|
|
|
440
440
|
assert "Module: path CLI argument" in result.output
|
|
441
441
|
assert "App name: auto-discovery" in result.output
|
|
442
442
|
assert "You can configure an entrypoint in pyproject.toml" not in result.output
|
|
443
|
-
assert "Starting production
|
|
443
|
+
assert "Starting FastAPI in production mode" in result.output
|
|
444
444
|
assert "Server started at http://0.0.0.0:8080" in result.output
|
|
445
445
|
assert "Documentation at http://0.0.0.0:8080/docs" in result.output
|
|
446
446
|
|
|
@@ -485,7 +485,7 @@ def test_public_url_env_var(command: str, public_url: str) -> None:
|
|
|
485
485
|
|
|
486
486
|
assert "Using import string: single_file_app:app" in result.output
|
|
487
487
|
assert (
|
|
488
|
-
f"Starting {'development' if command == 'dev' else 'production'}
|
|
488
|
+
f"Starting FastAPI in {'development' if command == 'dev' else 'production'} mode"
|
|
489
489
|
in result.output
|
|
490
490
|
)
|
|
491
491
|
expected_url_base = public_url.rstrip("/")
|
|
@@ -47,7 +47,8 @@ def test_custom_formatter() -> None:
|
|
|
47
47
|
|
|
48
48
|
formatted = formatter.formatMessage(record)
|
|
49
49
|
|
|
50
|
-
|
|
50
|
+
# the log level is shown as a colored bar bullet, like `fastapi cloud logs`
|
|
51
|
+
assert "▕" in formatted
|
|
51
52
|
assert "127.0.0.1" in formatted
|
|
52
53
|
assert "GET / HTTP/1.1" in formatted
|
|
53
54
|
assert "200" in formatted
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.0.29"
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import sys
|
|
3
|
-
from typing import Any
|
|
4
|
-
|
|
5
|
-
from rich_toolkit import RichToolkit, RichToolkitTheme
|
|
6
|
-
from rich_toolkit.styles import TaggedStyle
|
|
7
|
-
from uvicorn.logging import DefaultFormatter
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class CustomFormatter(DefaultFormatter):
|
|
11
|
-
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
12
|
-
super().__init__(*args, **kwargs)
|
|
13
|
-
self.toolkit = get_rich_toolkit()
|
|
14
|
-
|
|
15
|
-
def formatMessage(self, record: logging.LogRecord) -> str:
|
|
16
|
-
message = record.getMessage()
|
|
17
|
-
result = self.toolkit.print_as_string(message, tag=record.levelname)
|
|
18
|
-
# Prepend newline to fix alignment after ^C is printed by the terminal
|
|
19
|
-
if message == "Shutting down":
|
|
20
|
-
result = "\n" + result
|
|
21
|
-
return result
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def should_use_rich_logs() -> bool:
|
|
25
|
-
"""Return True when stdout is a TTY and rich logs should be used, False otherwise."""
|
|
26
|
-
return sys.stdout.isatty()
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def get_uvicorn_log_config() -> dict[str, Any]:
|
|
30
|
-
return {
|
|
31
|
-
"version": 1,
|
|
32
|
-
"disable_existing_loggers": False,
|
|
33
|
-
"formatters": {
|
|
34
|
-
"default": {
|
|
35
|
-
"()": CustomFormatter,
|
|
36
|
-
"fmt": "%(levelprefix)s %(message)s",
|
|
37
|
-
"use_colors": None,
|
|
38
|
-
},
|
|
39
|
-
"access": {
|
|
40
|
-
"()": CustomFormatter,
|
|
41
|
-
"fmt": "%(levelprefix)s %(client_addr)s - '%(request_line)s' %(status_code)s",
|
|
42
|
-
},
|
|
43
|
-
},
|
|
44
|
-
"handlers": {
|
|
45
|
-
"default": {
|
|
46
|
-
"formatter": "default",
|
|
47
|
-
"class": "logging.StreamHandler",
|
|
48
|
-
"stream": "ext://sys.stderr",
|
|
49
|
-
},
|
|
50
|
-
"access": {
|
|
51
|
-
"formatter": "access",
|
|
52
|
-
"class": "logging.StreamHandler",
|
|
53
|
-
"stream": "ext://sys.stdout",
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
"loggers": {
|
|
57
|
-
"uvicorn": {
|
|
58
|
-
"handlers": ["default"],
|
|
59
|
-
"level": "INFO",
|
|
60
|
-
"propagate": False,
|
|
61
|
-
},
|
|
62
|
-
"uvicorn.error": {"level": "INFO"},
|
|
63
|
-
"uvicorn.access": {
|
|
64
|
-
"handlers": ["access"],
|
|
65
|
-
"level": "INFO",
|
|
66
|
-
"propagate": False,
|
|
67
|
-
},
|
|
68
|
-
},
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
logger = logging.getLogger(__name__)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
def get_rich_toolkit() -> RichToolkit:
|
|
76
|
-
theme = RichToolkitTheme(
|
|
77
|
-
style=TaggedStyle(tag_width=11),
|
|
78
|
-
theme={
|
|
79
|
-
"tag.title": "white on #009485",
|
|
80
|
-
"tag": "white on #007166",
|
|
81
|
-
"placeholder": "grey85",
|
|
82
|
-
"text": "white",
|
|
83
|
-
"selected": "#007166",
|
|
84
|
-
"result": "grey85",
|
|
85
|
-
"progress": "on #007166",
|
|
86
|
-
"error": "red",
|
|
87
|
-
"log.info": "black on blue",
|
|
88
|
-
},
|
|
89
|
-
)
|
|
90
|
-
|
|
91
|
-
return RichToolkit(theme=theme)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_api/app/api.py
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_app/app/api.py
RENAMED
|
File without changes
|
{fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_app/app/app.py
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_main/app/api.py
RENAMED
|
File without changes
|
{fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_main/app/app.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/non_default/nonstandard.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/nested_package/package/core/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/nested_package/package/mod/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|