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.
Files changed (77) hide show
  1. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/PKG-INFO +1 -1
  2. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/pyproject.toml +1 -1
  3. fastapi_cli-0.0.30/src/fastapi_cli/__init__.py +1 -0
  4. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/src/fastapi_cli/cli.py +23 -30
  5. fastapi_cli-0.0.30/src/fastapi_cli/utils/cli.py +190 -0
  6. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/test_cli.py +11 -11
  7. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/test_utils_cli.py +2 -1
  8. fastapi_cli-0.0.29/src/fastapi_cli/__init__.py +0 -1
  9. fastapi_cli-0.0.29/src/fastapi_cli/utils/cli.py +0 -91
  10. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/LICENSE +0 -0
  11. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/README.md +0 -0
  12. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/scripts/add_latest_release_date.py +0 -0
  13. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/scripts/format.sh +0 -0
  14. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/scripts/lint.sh +0 -0
  15. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/scripts/prepare_release.py +0 -0
  16. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/scripts/test-cov-html.sh +0 -0
  17. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/scripts/test.sh +0 -0
  18. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/src/fastapi_cli/__main__.py +0 -0
  19. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/src/fastapi_cli/config.py +0 -0
  20. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/src/fastapi_cli/discover.py +0 -0
  21. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/src/fastapi_cli/exceptions.py +0 -0
  22. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/src/fastapi_cli/logging.py +0 -0
  23. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/src/fastapi_cli/py.typed +0 -0
  24. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/src/fastapi_cli/utils/__init__.py +0 -0
  25. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/__init__.py +0 -0
  26. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/broken_package/mod/__init__.py +0 -0
  27. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/broken_package/mod/app.py +0 -0
  28. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/broken_package/utils.py +0 -0
  29. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_api/api.py +0 -0
  30. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app/api.py +0 -0
  31. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app/app.py +0 -0
  32. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_api/app/__init__.py +0 -0
  33. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_api/app/api.py +0 -0
  34. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_app/app/__init__.py +0 -0
  35. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_app/app/api.py +0 -0
  36. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_app/app/app.py +0 -0
  37. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_main/app/__init__.py +0 -0
  38. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_main/app/api.py +0 -0
  39. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_main/app/app.py +0 -0
  40. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_main/app/main.py +0 -0
  41. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_non_default/app/__init__.py +0 -0
  42. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_app_dir_non_default/app/nondefault.py +0 -0
  43. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_main/api.py +0 -0
  44. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_main/app.py +0 -0
  45. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/default_main/main.py +0 -0
  46. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/default_files/non_default/nonstandard.py +0 -0
  47. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/nested_package/__init__.py +0 -0
  48. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/nested_package/package/__init__.py +0 -0
  49. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/nested_package/package/core/__init__.py +0 -0
  50. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/nested_package/package/core/utils.py +0 -0
  51. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/nested_package/package/mod/__init__.py +0 -0
  52. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/nested_package/package/mod/api.py +0 -0
  53. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/nested_package/package/mod/app.py +0 -0
  54. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/nested_package/package/mod/other.py +0 -0
  55. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/package/__init__.py +0 -0
  56. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/package/core/__init__.py +0 -0
  57. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/package/core/utils.py +0 -0
  58. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/package/mod/__init__.py +0 -0
  59. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/package/mod/api.py +0 -0
  60. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/package/mod/app.py +0 -0
  61. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/package/mod/other.py +0 -0
  62. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/pyproject_config/another_module.py +0 -0
  63. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/pyproject_config/my_module.py +0 -0
  64. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/pyproject_config/pyproject.toml +0 -0
  65. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/single_file_api.py +0 -0
  66. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/single_file_app.py +0 -0
  67. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/assets/single_file_other.py +0 -0
  68. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/conftest.py +0 -0
  69. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/test_cli_pyproject.py +0 -0
  70. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/test_discover.py +0 -0
  71. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/test_prepare_release.py +0 -0
  72. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/test_requirements.py +0 -0
  73. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/test_utils_default_dir.py +0 -0
  74. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/test_utils_default_file.py +0 -0
  75. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/test_utils_package.py +0 -0
  76. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/test_utils_single_file.py +0 -0
  77. {fastapi_cli-0.0.29 → fastapi_cli-0.0.30}/tests/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fastapi-cli
3
- Version: 0.0.29
3
+ Version: 0.0.30
4
4
  Summary: Run and manage FastAPI apps from the command line with FastAPI CLI. 🚀
5
5
  Author-Email: =?utf-8?q?Sebasti=C3=A1n_Ram=C3=ADrez?= <tiangolo@gmail.com>
6
6
  License-Expression: MIT
@@ -38,7 +38,7 @@ dependencies = [
38
38
  "rich-toolkit >= 0.14.8",
39
39
  "tomli >= 2.0.0; python_version < '3.11'",
40
40
  ]
41
- version = "0.0.29"
41
+ version = "0.0.30"
42
42
 
43
43
  [project.optional-dependencies]
44
44
  standard = [
@@ -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.print_title(f"Starting {server_type} server 🚀", tag="FastAPI")
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.print(f"Importing from {module_data.extra_sys_path}")
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, tag="module")
207
- toolkit.print_line()
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"[underline]from [bold]{module_data.module_import_str}[/bold] import [bold]{import_data.app_name}[/bold]"
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.print(
220
- f"Using import string: [blue]{import_string}[/]",
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.print_line()
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" • Import string: {mod_source_desc}")
225
+ toolkit.print(f"• Import string: {mod_source_desc}")
230
226
  else:
231
- toolkit.print(f" • Module: {mod_source_desc}")
232
- toolkit.print(f" • App name: {app_source_desc}")
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
- tag="tip",
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
- f"Server started at [link={url}]{url}[/]",
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
- tag="tip",
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 server 🚀" in result.output
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 server 🚀" in result.output
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 server 🚀" in result.output
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 server 🚀" in result.output
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 server 🚀" in result.output
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 server 🚀" in result.output
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 server 🚀" in result.output
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 server 🚀" in result.output
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 server 🚀" in result.output
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 server 🚀" in result.output
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'} server 🚀"
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
- assert "INFO" in formatted
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