fastapi-cli 0.0.9__tar.gz → 0.0.11__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 (72) hide show
  1. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/PKG-INFO +1 -1
  2. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/pyproject.toml +1 -1
  3. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/requirements-tests.txt +1 -1
  4. fastapi_cli-0.0.11/src/fastapi_cli/__init__.py +1 -0
  5. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/src/fastapi_cli/cli.py +44 -5
  6. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/src/fastapi_cli/discover.py +23 -0
  7. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/test_cli.py +73 -0
  8. fastapi_cli-0.0.11/tests/test_discover.py +58 -0
  9. fastapi_cli-0.0.9/src/fastapi_cli/__init__.py +0 -1
  10. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/LICENSE +0 -0
  11. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/README.md +0 -0
  12. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/pdm_build.py +0 -0
  13. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/requirements-github-actions.txt +0 -0
  14. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/requirements.txt +0 -0
  15. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/scripts/format.sh +0 -0
  16. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/scripts/lint.sh +0 -0
  17. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/scripts/test-cov-html.sh +0 -0
  18. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/scripts/test.sh +0 -0
  19. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/src/fastapi_cli/__main__.py +0 -0
  20. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/src/fastapi_cli/exceptions.py +0 -0
  21. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/src/fastapi_cli/logging.py +0 -0
  22. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/src/fastapi_cli/py.typed +0 -0
  23. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/src/fastapi_cli/utils/__init__.py +0 -0
  24. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/src/fastapi_cli/utils/cli.py +0 -0
  25. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/__init__.py +0 -0
  26. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/broken_package/mod/__init__.py +0 -0
  27. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/broken_package/mod/app.py +0 -0
  28. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/broken_package/utils.py +0 -0
  29. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/default_files/default_api/api.py +0 -0
  30. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/default_files/default_app/api.py +0 -0
  31. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/default_files/default_app/app.py +0 -0
  32. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/default_files/default_app_dir_api/app/__init__.py +0 -0
  33. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/default_files/default_app_dir_api/app/api.py +0 -0
  34. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/default_files/default_app_dir_app/app/__init__.py +0 -0
  35. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/default_files/default_app_dir_app/app/api.py +0 -0
  36. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/default_files/default_app_dir_app/app/app.py +0 -0
  37. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/default_files/default_app_dir_main/app/__init__.py +0 -0
  38. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/default_files/default_app_dir_main/app/api.py +0 -0
  39. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/default_files/default_app_dir_main/app/app.py +0 -0
  40. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/default_files/default_app_dir_main/app/main.py +0 -0
  41. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/default_files/default_app_dir_non_default/app/__init__.py +0 -0
  42. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/default_files/default_app_dir_non_default/app/nondefault.py +0 -0
  43. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/default_files/default_main/api.py +0 -0
  44. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/default_files/default_main/app.py +0 -0
  45. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/default_files/default_main/main.py +0 -0
  46. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/default_files/non_default/nonstandard.py +0 -0
  47. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/nested_package/__init__.py +0 -0
  48. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/nested_package/package/__init__.py +0 -0
  49. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/nested_package/package/core/__init__.py +0 -0
  50. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/nested_package/package/core/utils.py +0 -0
  51. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/nested_package/package/mod/__init__.py +0 -0
  52. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/nested_package/package/mod/api.py +0 -0
  53. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/nested_package/package/mod/app.py +0 -0
  54. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/nested_package/package/mod/other.py +0 -0
  55. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/package/__init__.py +0 -0
  56. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/package/core/__init__.py +0 -0
  57. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/package/core/utils.py +0 -0
  58. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/package/mod/__init__.py +0 -0
  59. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/package/mod/api.py +0 -0
  60. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/package/mod/app.py +0 -0
  61. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/package/mod/other.py +0 -0
  62. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/single_file_api.py +0 -0
  63. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/single_file_app.py +0 -0
  64. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/assets/single_file_other.py +0 -0
  65. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/conftest.py +0 -0
  66. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/test_requirements.py +0 -0
  67. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/test_utils_cli.py +0 -0
  68. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/test_utils_default_dir.py +0 -0
  69. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/test_utils_default_file.py +0 -0
  70. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/test_utils_package.py +0 -0
  71. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/test_utils_single_file.py +0 -0
  72. {fastapi_cli-0.0.9 → fastapi_cli-0.0.11}/tests/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fastapi-cli
3
- Version: 0.0.9
3
+ Version: 0.0.11
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: MIT
@@ -35,7 +35,7 @@ dependencies = [
35
35
  "uvicorn[standard] >= 0.15.0",
36
36
  "rich-toolkit >= 0.14.8",
37
37
  ]
38
- version = "0.0.9"
38
+ version = "0.0.11"
39
39
 
40
40
  [project.license]
41
41
  text = "MIT"
@@ -3,7 +3,7 @@
3
3
  pytest >=4.4.0,<9.0.0
4
4
  coverage[toml] >=6.2,<8.0
5
5
  mypy ==1.14.0
6
- ruff ==0.11.2
6
+ ruff ==0.12.12
7
7
  # Needed explicitly by fastapi-cli-slim
8
8
  fastapi-slim
9
9
  uvicorn
@@ -0,0 +1 @@
1
+ __version__ = "0.0.11"
@@ -7,7 +7,7 @@ from rich import print
7
7
  from rich.tree import Tree
8
8
  from typing_extensions import Annotated
9
9
 
10
- from fastapi_cli.discover import get_import_data
10
+ from fastapi_cli.discover import get_import_data, get_import_data_from_import_string
11
11
  from fastapi_cli.exceptions import FastAPICLIException
12
12
 
13
13
  from . import __version__
@@ -95,7 +95,9 @@ def _run(
95
95
  root_path: str = "",
96
96
  command: str,
97
97
  app: Union[str, None] = None,
98
+ entrypoint: Union[str, None] = None,
98
99
  proxy_headers: bool = False,
100
+ forwarded_allow_ips: Union[str, None] = None,
99
101
  ) -> None:
100
102
  with get_rich_toolkit() as toolkit:
101
103
  server_type = "development" if command == "dev" else "production"
@@ -108,7 +110,10 @@ def _run(
108
110
  )
109
111
 
110
112
  try:
111
- import_data = get_import_data(path=path, app_name=app)
113
+ if entrypoint:
114
+ import_data = get_import_data_from_import_string(entrypoint)
115
+ else:
116
+ import_data = get_import_data(path=path, app_name=app)
112
117
  except FastAPICLIException as e:
113
118
  toolkit.print_line()
114
119
  toolkit.print(f"[error]{e}")
@@ -123,10 +128,11 @@ def _run(
123
128
  toolkit.print(f"Importing from {module_data.extra_sys_path}")
124
129
  toolkit.print_line()
125
130
 
126
- root_tree = _get_module_tree(module_data.module_paths)
131
+ if module_data.module_paths:
132
+ root_tree = _get_module_tree(module_data.module_paths)
127
133
 
128
- toolkit.print(root_tree, tag="module")
129
- toolkit.print_line()
134
+ toolkit.print(root_tree, tag="module")
135
+ toolkit.print_line()
130
136
 
131
137
  toolkit.print(
132
138
  "Importing the FastAPI app object from the module with the following code:",
@@ -177,6 +183,7 @@ def _run(
177
183
  workers=workers,
178
184
  root_path=root_path,
179
185
  proxy_headers=proxy_headers,
186
+ forwarded_allow_ips=forwarded_allow_ips,
180
187
  log_config=get_uvicorn_log_config(),
181
188
  )
182
189
 
@@ -220,12 +227,26 @@ def dev(
220
227
  help="The name of the variable that contains the [bold]FastAPI[/bold] app in the imported module or package. If not provided, it is detected automatically."
221
228
  ),
222
229
  ] = None,
230
+ entrypoint: Annotated[
231
+ Union[str, None],
232
+ typer.Option(
233
+ "--entrypoint",
234
+ "-e",
235
+ help="The FastAPI app import string in the format 'some.importable_module:app_name'.",
236
+ ),
237
+ ] = None,
223
238
  proxy_headers: Annotated[
224
239
  bool,
225
240
  typer.Option(
226
241
  help="Enable/Disable X-Forwarded-Proto, X-Forwarded-For, X-Forwarded-Port to populate remote address info."
227
242
  ),
228
243
  ] = True,
244
+ forwarded_allow_ips: Annotated[
245
+ Union[str, None],
246
+ typer.Option(
247
+ help="Comma separated list of IP Addresses to trust with proxy headers. The literal '*' means trust everything."
248
+ ),
249
+ ] = None,
229
250
  ) -> Any:
230
251
  """
231
252
  Run a [bold]FastAPI[/bold] app in [yellow]development[/yellow] mode. 🧪
@@ -259,8 +280,10 @@ def dev(
259
280
  reload=reload,
260
281
  root_path=root_path,
261
282
  app=app,
283
+ entrypoint=entrypoint,
262
284
  command="dev",
263
285
  proxy_headers=proxy_headers,
286
+ forwarded_allow_ips=forwarded_allow_ips,
264
287
  )
265
288
 
266
289
 
@@ -309,12 +332,26 @@ def run(
309
332
  help="The name of the variable that contains the [bold]FastAPI[/bold] app in the imported module or package. If not provided, it is detected automatically."
310
333
  ),
311
334
  ] = None,
335
+ entrypoint: Annotated[
336
+ Union[str, None],
337
+ typer.Option(
338
+ "--entrypoint",
339
+ "-e",
340
+ help="The FastAPI app import string in the format 'some.importable_module:app_name'.",
341
+ ),
342
+ ] = None,
312
343
  proxy_headers: Annotated[
313
344
  bool,
314
345
  typer.Option(
315
346
  help="Enable/Disable X-Forwarded-Proto, X-Forwarded-For, X-Forwarded-Port to populate remote address info."
316
347
  ),
317
348
  ] = True,
349
+ forwarded_allow_ips: Annotated[
350
+ Union[str, None],
351
+ typer.Option(
352
+ help="Comma separated list of IP Addresses to trust with proxy headers. The literal '*' means trust everything."
353
+ ),
354
+ ] = None,
318
355
  ) -> Any:
319
356
  """
320
357
  Run a [bold]FastAPI[/bold] app in [green]production[/green] mode. 🚀
@@ -349,8 +386,10 @@ def run(
349
386
  workers=workers,
350
387
  root_path=root_path,
351
388
  app=app,
389
+ entrypoint=entrypoint,
352
390
  command="run",
353
391
  proxy_headers=proxy_headers,
392
+ forwarded_allow_ips=forwarded_allow_ips,
354
393
  )
355
394
 
356
395
 
@@ -130,3 +130,26 @@ def get_import_data(
130
130
  return ImportData(
131
131
  app_name=use_app_name, module_data=mod_data, import_string=import_string
132
132
  )
133
+
134
+
135
+ def get_import_data_from_import_string(import_string: str) -> ImportData:
136
+ module_str, _, app_name = import_string.partition(":")
137
+
138
+ if not module_str or not app_name:
139
+ raise FastAPICLIException(
140
+ "Import string must be in the format module.submodule:app_name"
141
+ )
142
+
143
+ here = Path(".").resolve()
144
+
145
+ sys.path.insert(0, str(here))
146
+
147
+ return ImportData(
148
+ app_name=app_name,
149
+ module_data=ModuleData(
150
+ module_import_str=module_str,
151
+ extra_sys_path=here,
152
+ module_paths=[],
153
+ ),
154
+ import_string=import_string,
155
+ )
@@ -30,6 +30,7 @@ def test_dev() -> None:
30
30
  "workers": None,
31
31
  "root_path": "",
32
32
  "proxy_headers": True,
33
+ "forwarded_allow_ips": None,
33
34
  "log_config": get_uvicorn_log_config(),
34
35
  }
35
36
  assert "Using import string: single_file_app:app" in result.output
@@ -59,6 +60,7 @@ def test_dev_package() -> None:
59
60
  "workers": None,
60
61
  "root_path": "",
61
62
  "proxy_headers": True,
63
+ "forwarded_allow_ips": None,
62
64
  "log_config": get_uvicorn_log_config(),
63
65
  }
64
66
  assert "Using import string: nested_package.package:app" in result.output
@@ -107,6 +109,7 @@ def test_dev_args() -> None:
107
109
  "workers": None,
108
110
  "root_path": "/api",
109
111
  "proxy_headers": False,
112
+ "forwarded_allow_ips": None,
110
113
  "log_config": get_uvicorn_log_config(),
111
114
  }
112
115
  assert "Using import string: single_file_app:api" in result.output
@@ -134,6 +137,33 @@ def test_run() -> None:
134
137
  "workers": None,
135
138
  "root_path": "",
136
139
  "proxy_headers": True,
140
+ "forwarded_allow_ips": None,
141
+ "log_config": get_uvicorn_log_config(),
142
+ }
143
+ assert "Using import string: single_file_app:app" in result.output
144
+ assert "Starting production server 🚀" in result.output
145
+ assert "Server started at http://0.0.0.0:8000" in result.output
146
+ assert "Documentation at http://0.0.0.0:8000/docs" in result.output
147
+
148
+
149
+ def test_run_trust_proxy() -> None:
150
+ with changing_dir(assets_path):
151
+ with patch.object(uvicorn, "run") as mock_run:
152
+ result = runner.invoke(
153
+ app, ["run", "single_file_app.py", "--forwarded-allow-ips", "*"]
154
+ )
155
+ assert result.exit_code == 0, result.output
156
+ assert mock_run.called
157
+ assert mock_run.call_args
158
+ assert mock_run.call_args.kwargs == {
159
+ "app": "single_file_app:app",
160
+ "host": "0.0.0.0",
161
+ "port": 8000,
162
+ "reload": False,
163
+ "workers": None,
164
+ "root_path": "",
165
+ "proxy_headers": True,
166
+ "forwarded_allow_ips": "*",
137
167
  "log_config": get_uvicorn_log_config(),
138
168
  }
139
169
  assert "Using import string: single_file_app:app" in result.output
@@ -179,6 +209,7 @@ def test_run_args() -> None:
179
209
  "workers": 2,
180
210
  "root_path": "/api",
181
211
  "proxy_headers": False,
212
+ "forwarded_allow_ips": None,
182
213
  "log_config": get_uvicorn_log_config(),
183
214
  }
184
215
 
@@ -254,6 +285,48 @@ def test_version() -> None:
254
285
  assert "FastAPI CLI version:" in result.output
255
286
 
256
287
 
288
+ def test_dev_with_import_string() -> None:
289
+ with changing_dir(assets_path):
290
+ with patch.object(uvicorn, "run") as mock_run:
291
+ result = runner.invoke(app, ["dev", "--entrypoint", "single_file_app:api"])
292
+ assert result.exit_code == 0, result.output
293
+ assert mock_run.called
294
+ assert mock_run.call_args
295
+ assert mock_run.call_args.kwargs == {
296
+ "app": "single_file_app:api",
297
+ "forwarded_allow_ips": None,
298
+ "host": "127.0.0.1",
299
+ "port": 8000,
300
+ "reload": True,
301
+ "workers": None,
302
+ "root_path": "",
303
+ "proxy_headers": True,
304
+ "log_config": get_uvicorn_log_config(),
305
+ }
306
+ assert "Using import string: single_file_app:api" in result.output
307
+
308
+
309
+ def test_run_with_import_string() -> None:
310
+ with changing_dir(assets_path):
311
+ with patch.object(uvicorn, "run") as mock_run:
312
+ result = runner.invoke(app, ["run", "--entrypoint", "single_file_app:app"])
313
+ assert result.exit_code == 0, result.output
314
+ assert mock_run.called
315
+ assert mock_run.call_args
316
+ assert mock_run.call_args.kwargs == {
317
+ "app": "single_file_app:app",
318
+ "forwarded_allow_ips": None,
319
+ "host": "0.0.0.0",
320
+ "port": 8000,
321
+ "reload": False,
322
+ "workers": None,
323
+ "root_path": "",
324
+ "proxy_headers": True,
325
+ "log_config": get_uvicorn_log_config(),
326
+ }
327
+ assert "Using import string: single_file_app:app" in result.output
328
+
329
+
257
330
  def test_script() -> None:
258
331
  result = subprocess.run(
259
332
  [sys.executable, "-m", "coverage", "run", "-m", "fastapi_cli", "--help"],
@@ -0,0 +1,58 @@
1
+ from pathlib import Path
2
+
3
+ import pytest
4
+
5
+ from fastapi_cli.discover import (
6
+ ImportData,
7
+ get_import_data_from_import_string,
8
+ )
9
+ from fastapi_cli.exceptions import FastAPICLIException
10
+
11
+ assets_path = Path(__file__).parent / "assets"
12
+
13
+
14
+ def test_get_import_data_from_import_string_valid() -> None:
15
+ result = get_import_data_from_import_string("module.submodule:app")
16
+
17
+ assert isinstance(result, ImportData)
18
+ assert result.app_name == "app"
19
+ assert result.import_string == "module.submodule:app"
20
+ assert result.module_data.module_import_str == "module.submodule"
21
+ assert result.module_data.extra_sys_path == Path(".").resolve()
22
+ assert result.module_data.module_paths == []
23
+
24
+
25
+ def test_get_import_data_from_import_string_missing_colon() -> None:
26
+ with pytest.raises(FastAPICLIException) as exc_info:
27
+ get_import_data_from_import_string("module.submodule")
28
+
29
+ assert "Import string must be in the format module.submodule:app_name" in str(
30
+ exc_info.value
31
+ )
32
+
33
+
34
+ def test_get_import_data_from_import_string_missing_app() -> None:
35
+ with pytest.raises(FastAPICLIException) as exc_info:
36
+ get_import_data_from_import_string("module.submodule:")
37
+
38
+ assert "Import string must be in the format module.submodule:app_name" in str(
39
+ exc_info.value
40
+ )
41
+
42
+
43
+ def test_get_import_data_from_import_string_missing_module() -> None:
44
+ with pytest.raises(FastAPICLIException) as exc_info:
45
+ get_import_data_from_import_string(":app")
46
+
47
+ assert "Import string must be in the format module.submodule:app_name" in str(
48
+ exc_info.value
49
+ )
50
+
51
+
52
+ def test_get_import_data_from_import_string_empty() -> None:
53
+ with pytest.raises(FastAPICLIException) as exc_info:
54
+ get_import_data_from_import_string("")
55
+
56
+ assert "Import string must be in the format module.submodule:app_name" in str(
57
+ exc_info.value
58
+ )
@@ -1 +0,0 @@
1
- __version__ = "0.0.9"
File without changes
File without changes
File without changes
File without changes