Coverage for src/fastapi_new/new.py: 100%
104 statements
« prev ^ index » next coverage.py v7.11.2, created at 2026-11-04 19:57 +0000
« prev ^ index » next coverage.py v7.11.2, created at 2026-11-04 19:57 +0000
1import pathlib 2hbibjbkblbmbnbob
2import shutil 2hbibjbkblbmbnbob
3import subprocess 2hbibjbkblbmbnbob
4from dataclasses import dataclass 2hbibjbkblbmbnbob
5from typing import Annotated 2hbibjbkblbmbnbob
7import typer 2hbibjbkblbmbnbob
8from rich_toolkit import RichToolkit 2hbibjbkblbmbnbob
10from .utils.cli import get_rich_toolkit 2hbibjbkblbmbnbob
12TEMPLATE_CONTENT = """from fastapi import FastAPI 2hbibjbkblbmbnbob
13app = FastAPI()
15@app.get("/")
16def main():
17 return {"message": "Hello World"}
18"""
21@dataclass 2hbibjbkblbmbnbob
22class ProjectConfig: 2hbibjbkblbmbnbob
23 name: str 2hbibjbkblbmbnbob
24 path: pathlib.Path 2hbibjbkblbmbnbob
25 python: str | None = None 2hbibjbkblbmbnbob
28def _generate_readme(project_name: str) -> str: 2hbibjbkblbmbnbob
29 return f"""# {project_name} 1JaK4qriLMbN5stjOucP6vwkQRdS7xylTzeU8ABmVWfX9CDnYZg0!EFo1Gh2#HIp3
31A project created with FastAPI CLI.
33## Quick Start
35### Start the development server
37```bash
38uv run fastapi dev
39```
41Visit http://localhost:8000
43### Deploy to FastAPI Cloud
45Sign up and log in at https://fastapicloud.com, then deploy with:
47```bash
48uv run fastapi deploy
49```
51## Project Structure
53- `main.py` - Your FastAPI application
54- `pyproject.toml` - Project dependencies
56## Learn More
58- [FastAPI Documentation](https://fastapi.tiangolo.com)
59- [FastAPI Cloud](https://fastapicloud.com)
60"""
63def _exit_with_error(toolkit: RichToolkit, error_msg: str) -> None: 2hbibjbkblbmbnbob
64 toolkit.print(f"[bold red]Error:[/bold red] {error_msg}", tag="error") 2- [ 4 $ xb~ pb. ] 5 % ybabqb/ ^ 6 ' zbbbrb: _ 7 ( Abcbsb; ` 8 ) Bbdbtb= { 9 * Cbebub? | ! + Dbfbvb@ } # , Ebgbwb
65 raise typer.Exit(code=1) 2- [ 4 $ xb~ pb. ] 5 % ybabqb/ ^ 6 ' zbbbrb: _ 7 ( Abcbsb; ` 8 ) Bbdbtb= { 9 * Cbebub? | ! + Dbfbvb@ } # , Ebgbwb
68def _validate_python_version(python: str | None) -> str | None: 2hbibjbkblbmbnbob
69 """
70 Validate Python version is >= 3.10.
71 Returns error message if < 3.10, None otherwise.
72 Let uv handle malformed versions or versions it can't find.
73 """
74 if not python: 2J a K - [ 4 q r $ i ~ L M b N . ] 5 s t % j abO u c P / ^ 6 v w ' k bbQ R d S : _ 7 x y ( l cbT z e U ; ` 8 A B ) m dbV W f X = { 9 C D * n ebY Z g 0 ? | ! E F + o fb1 G h 2 @ } # H I , p gb3
75 return None 1JK-[4qrLMN.]5stOuP/^6vwQRS:_7xyTzU;`8ABVWX={9CDYZ0?|!EF1G2@}#HI3
77 try: 2a $ i ~ b % j abc ' k bbd ( l cbe ) m dbf * n ebg + o fbh , p gb
78 parts = python.split(".") 2a $ i ~ b % j abc ' k bbd ( l cbe ) m dbf * n ebg + o fbh , p gb
79 if len(parts) < 2: 2a $ i ~ b % j abc ' k bbd ( l cbe ) m dbf * n ebg + o fbh , p gb
80 return None # Let uv handle malformed version 1ijklmnop
81 major, minor = int(parts[0]), int(parts[1]) 2a $ ~ b % abc ' bbd ( cbe ) dbf * ebg + fbh , gb
83 if major < 3 or (major == 3 and minor < 10): 2a ~ b abc bbd cbe dbf ebg fbh gb
84 return f"Python {python} is not supported. FastAPI requires Python 3.10 or higher." 2~ abbbcbdbebfbgb
85 except (ValueError, IndexError): 1$%'()*+,
86 # Malformed version - let uv handle the error
87 pass 1$%'()*+,
89 return None 1a$b%c'd(e)f*g+h,
92def _setup(toolkit: RichToolkit, config: ProjectConfig) -> None: 2hbibjbkblbmbnbob
93 error = _validate_python_version(config.python) 2J a K - [ 4 q r $ i ~ L M b N . ] 5 s t % j abO u c P / ^ 6 v w ' k bbQ R d S : _ 7 x y ( l cbT z e U ; ` 8 A B ) m dbV W f X = { 9 C D * n ebY Z g 0 ? | ! E F + o fb1 G h 2 @ } # H I , p gb3
94 if error: 2J a K - [ 4 q r $ i ~ L M b N . ] 5 s t % j abO u c P / ^ 6 v w ' k bbQ R d S : _ 7 x y ( l cbT z e U ; ` 8 A B ) m dbV W f X = { 9 C D * n ebY Z g 0 ? | ! E F + o fb1 G h 2 @ } # H I , p gb3
95 _exit_with_error(toolkit, error) 2~ abbbcbdbebfbgb
97 msg = "Setting up environment with uv" 1JaK-[4qr$iLMbN.]5st%jOucP/^6vw'kQRdS:_7xy(lTzeU;`8AB)mVWfX={9CD*nYZg0?|!EF+o1Gh2@}#HI,p3
99 if config.python: 1JaK-[4qr$iLMbN.]5st%jOucP/^6vw'kQRdS:_7xy(lTzeU;`8AB)mVWfX={9CD*nYZg0?|!EF+o1Gh2@}#HI,p3
100 msg += f" (Python {config.python})" 1a$ib%jc'kd(le)mf*ng+oh,p
102 toolkit.print(msg, tag="env") 1JaK-[4qr$iLMbN.]5st%jOucP/^6vw'kQRdS:_7xy(lTzeU;`8AB)mVWfX={9CD*nYZg0?|!EF+o1Gh2@}#HI,p3
104 # If config.name is provided, create in subdirectory; otherwise init in current dir
105 # uv will infer the project name from the directory name
106 if config.path == pathlib.Path.cwd(): 1JaK-[4qr$iLMbN.]5st%jOucP/^6vw'kQRdS:_7xy(lTzeU;`8AB)mVWfX={9CD*nYZg0?|!EF+o1Gh2@}#HI,p3
107 init_cmd = ["uv", "init", "--bare"] 1qrstvwxyABCDEFHI
108 else:
109 init_cmd = ["uv", "init", "--bare", str(config.path)] 1JaK-[4$iLMbN.]5%jOucP/^6'kQRdS:_7(lTzeU;`8)mVWfX={9*nYZg0?|!+o1Gh2@}#,p3
111 if config.python: 1JaK-[4qr$iLMbN.]5st%jOucP/^6vw'kQRdS:_7xy(lTzeU;`8AB)mVWfX={9CD*nYZg0?|!EF+o1Gh2@}#HI,p3
112 init_cmd.extend(["--python", config.python]) 1a$ib%jc'kd(le)mf*ng+oh,p
114 try: 1JaK-[4qr$iLMbN.]5st%jOucP/^6vw'kQRdS:_7xy(lTzeU;`8AB)mVWfX={9CD*nYZg0?|!EF+o1Gh2@}#HI,p3
115 subprocess.run(init_cmd, check=True, capture_output=True) 1JaK-[4qr$iLMbN.]5st%jOucP/^6vw'kQRdS:_7xy(lTzeU;`8AB)mVWfX={9CD*nYZg0?|!EF+o1Gh2@}#HI,p3
116 except subprocess.CalledProcessError as e: 1[$]%^'_(`){*|+},
117 stderr = e.stderr.decode() if e.stderr else "No details available" 1[$]%^'_(`){*|+},
118 _exit_with_error(toolkit, f"Failed to initialize project with uv. {stderr}") 1[$]%^'_(`){*|+},
121def _install_dependencies(toolkit: RichToolkit, config: ProjectConfig) -> None: 2hbibjbkblbmbnbob
122 toolkit.print("Installing dependencies...", tag="deps") 1JaK-4qriLMbN.5stjOucP/6vwkQRdS:7xylTzeU;8ABmVWfX=9CDnYZg0?!EFo1Gh2@#HIp3
124 try: 1JaK-4qriLMbN.5stjOucP/6vwkQRdS:7xylTzeU;8ABmVWfX=9CDnYZg0?!EFo1Gh2@#HIp3
125 subprocess.run( 1JaK-4qriLMbN.5stjOucP/6vwkQRdS:7xylTzeU;8ABmVWfX=9CDnYZg0?!EFo1Gh2@#HIp3
126 ["uv", "add", "fastapi[standard]"],
127 check=True,
128 capture_output=True,
129 cwd=config.path,
130 )
131 except subprocess.CalledProcessError as e: 1-./:;=?@
132 stderr = e.stderr.decode() if e.stderr else "No details available" 1-./:;=?@
133 _exit_with_error(toolkit, f"Failed to install dependencies. {stderr}") 1-./:;=?@
136def _write_template_files(toolkit: RichToolkit, config: ProjectConfig) -> None: 2hbibjbkblbmbnbob
137 toolkit.print("Writing template files...", tag="template") 1JaK4qriLMbN5stjOucP6vwkQRdS7xylTzeU8ABmVWfX9CDnYZg0!EFo1Gh2#HIp3
138 readme_content = _generate_readme(config.name) 1JaK4qriLMbN5stjOucP6vwkQRdS7xylTzeU8ABmVWfX9CDnYZg0!EFo1Gh2#HIp3
140 try: 1JaK4qriLMbN5stjOucP6vwkQRdS7xylTzeU8ABmVWfX9CDnYZg0!EFo1Gh2#HIp3
141 (config.path / "main.py").write_text(TEMPLATE_CONTENT) 1JaK4qriLMbN5stjOucP6vwkQRdS7xylTzeU8ABmVWfX9CDnYZg0!EFo1Gh2#HIp3
142 (config.path / "README.md").write_text(readme_content) 1JaK4qriLMbN5stjOucP6vwkQRdS7xylTzeU8ABmVWfX9CDnYZg0!EFo1Gh2#HIp3
143 except Exception as e: 1456789!#
144 _exit_with_error(toolkit, f"Failed to write template files. {str(e)}") 1456789!#
147def new( 2hbibjbu kblbz mbnbobG
148 ctx: typer.Context,
149 project: Annotated[
150 str | None,
151 typer.Argument(
152 help="The name/path of the new FastAPI project. If not provided, initializes in the current directory.",
153 ),
154 ] = None,
155 python: Annotated[
156 str | None,
157 typer.Option(
158 "--python",
159 "-p",
160 help="Specify the Python version for the new project (e.g., 3.14). Must be 3.10 or higher.",
161 ),
162 ] = None,
163) -> None:
164 if project: 2J a K - [ 4 q r $ i xb~ pbL M b N . ] 5 s t % j ybabqbO u c P / ^ 6 v w ' k zbbbrbQ R d S : _ 7 x y ( l AbcbsbT z e U ; ` 8 A B ) m BbdbtbV W f X = { 9 C D * n CbebubY Z g 0 ? | ! E F + o Dbfbvb1 G h 2 @ } # H I , p Ebgbwb3
165 path = (pathlib.Path.cwd() / project).resolve() 2J a K - [ 4 r $ i xb~ pbL M b N . ] 5 t % j ybabqbO u c P / ^ 6 w ' k zbbbrbQ R d S : _ 7 y ( l AbcbsbT z e U ; ` 8 B ) m BbdbtbV W f X = { 9 D * n CbebubY Z g 0 ? | ! F + o Dbfbvb1 G h 2 @ } # I , p Ebgbwb3
166 else:
167 path = pathlib.Path.cwd() 1qsvxACEH
169 current_dir = path == pathlib.Path.cwd() 2J a K - [ 4 q r $ i xb~ pbL M b N . ] 5 s t % j ybabqbO u c P / ^ 6 v w ' k zbbbrbQ R d S : _ 7 x y ( l AbcbsbT z e U ; ` 8 A B ) m BbdbtbV W f X = { 9 C D * n CbebubY Z g 0 ? | ! E F + o Dbfbvb1 G h 2 @ } # H I , p Ebgbwb3
171 config = ProjectConfig( 2J a K - [ 4 q r $ i xb~ pbL M b N . ] 5 s t % j ybabqbO u c P / ^ 6 v w ' k zbbbrbQ R d S : _ 7 x y ( l AbcbsbT z e U ; ` 8 A B ) m BbdbtbV W f X = { 9 C D * n CbebubY Z g 0 ? | ! E F + o Dbfbvb1 G h 2 @ } # H I , p Ebgbwb3
172 name=path.name,
173 path=path,
174 python=python,
175 )
177 with get_rich_toolkit() as toolkit: 2J a K - [ 4 q r $ i xb~ pbL M b N . ] 5 s t % j ybabqbO u c P / ^ 6 v w ' k zbbbrbQ R d S : _ 7 x y ( l AbcbsbT z e U ; ` 8 A B ) m BbdbtbV W f X = { 9 C D * n CbebubY Z g 0 ? | ! E F + o Dbfbvb1 G h 2 @ } # H I , p Ebgbwb3
178 toolkit.print_title("Creating a new project 🚀", tag="FastAPI") 2J a K - [ 4 q r $ i xb~ pbL M b N . ] 5 s t % j ybabqbO u c P / ^ 6 v w ' k zbbbrbQ R d S : _ 7 x y ( l AbcbsbT z e U ; ` 8 A B ) m BbdbtbV W f X = { 9 C D * n CbebubY Z g 0 ? | ! E F + o Dbfbvb1 G h 2 @ } # H I , p Ebgbwb3
180 toolkit.print_line() 2J a K - [ 4 q r $ i xb~ pbL M b N . ] 5 s t % j ybabqbO u c P / ^ 6 v w ' k zbbbrbQ R d S : _ 7 x y ( l AbcbsbT z e U ; ` 8 A B ) m BbdbtbV W f X = { 9 C D * n CbebubY Z g 0 ? | ! E F + o Dbfbvb1 G h 2 @ } # H I , p Ebgbwb3
182 if current_dir: 2J a K - [ 4 q r $ i xb~ pbL M b N . ] 5 s t % j ybabqbO u c P / ^ 6 v w ' k zbbbrbQ R d S : _ 7 x y ( l AbcbsbT z e U ; ` 8 A B ) m BbdbtbV W f X = { 9 C D * n CbebubY Z g 0 ? | ! E F + o Dbfbvb1 G h 2 @ } # H I , p Ebgbwb3
183 toolkit.print( 1qrstvwxyABCDEFHI
184 f"[yellow]⚠️ Initializing in current directory: {config.path}[/yellow]",
185 tag="warning",
186 )
187 toolkit.print_line() 1qrstvwxyABCDEFHI
189 # Check if project directory already exists (only for new subdirectory)
190 if not current_dir and config.path.exists(): 2J a K - [ 4 q r $ i xb~ pbL M b N . ] 5 s t % j ybabqbO u c P / ^ 6 v w ' k zbbbrbQ R d S : _ 7 x y ( l AbcbsbT z e U ; ` 8 A B ) m BbdbtbV W f X = { 9 C D * n CbebubY Z g 0 ? | ! E F + o Dbfbvb1 G h 2 @ } # H I , p Ebgbwb3
191 _exit_with_error(toolkit, f"Directory '{config.name}' already exists.") 2xbybzbAbBbCbDbEb
193 if shutil.which("uv") is None: 2J a K - [ 4 q r $ i ~ pbL M b N . ] 5 s t % j abqbO u c P / ^ 6 v w ' k bbrbQ R d S : _ 7 x y ( l cbsbT z e U ; ` 8 A B ) m dbtbV W f X = { 9 C D * n ebubY Z g 0 ? | ! E F + o fbvb1 G h 2 @ } # H I , p gbwb3
194 _exit_with_error( 2pbqbrbsbtbubvbwb
195 toolkit,
196 "uv is required to create new projects. Install it from https://docs.astral.sh/uv/getting-started/installation/",
197 )
199 _setup(toolkit, config) 2J a K - [ 4 q r $ i ~ L M b N . ] 5 s t % j abO u c P / ^ 6 v w ' k bbQ R d S : _ 7 x y ( l cbT z e U ; ` 8 A B ) m dbV W f X = { 9 C D * n ebY Z g 0 ? | ! E F + o fb1 G h 2 @ } # H I , p gb3
201 toolkit.print_line() 1JaK-4qriLMbN.5stjOucP/6vwkQRdS:7xylTzeU;8ABmVWfX=9CDnYZg0?!EFo1Gh2@#HIp3
203 _install_dependencies(toolkit, config) 1JaK-4qriLMbN.5stjOucP/6vwkQRdS:7xylTzeU;8ABmVWfX=9CDnYZg0?!EFo1Gh2@#HIp3
205 toolkit.print_line() 1JaK4qriLMbN5stjOucP6vwkQRdS7xylTzeU8ABmVWfX9CDnYZg0!EFo1Gh2#HIp3
207 _write_template_files(toolkit, config) 1JaK4qriLMbN5stjOucP6vwkQRdS7xylTzeU8ABmVWfX9CDnYZg0!EFo1Gh2#HIp3
209 toolkit.print_line() 1JaKqriLMbNstjOucPvwkQRdSxylTzeUABmVWfXCDnYZg0EFo1Gh2HIp3
211 # Print success message
212 if not current_dir: 1JaKqriLMbNstjOucPvwkQRdSxylTzeUABmVWfXCDnYZg0EFo1Gh2HIp3
213 toolkit.print( 1JaKiLMbNjOucPkQRdSlTzeUmVWfXnYZg0o1Gh2p3
214 f"[bold green]✨ Success![/bold green] Created FastAPI project: [cyan]{config.name}[/cyan]",
215 tag="success",
216 )
218 toolkit.print_line() 1JaKiLMbNjOucPkQRdSlTzeUmVWfXnYZg0o1Gh2p3
220 toolkit.print("[bold]Next steps:[/bold]") 1JaKiLMbNjOucPkQRdSlTzeUmVWfXnYZg0o1Gh2p3
221 toolkit.print(f" [dim]$[/dim] cd {config.name}") 1JaKiLMbNjOucPkQRdSlTzeUmVWfXnYZg0o1Gh2p3
222 toolkit.print(" [dim]$[/dim] uv run fastapi dev") 1JaKiLMbNjOucPkQRdSlTzeUmVWfXnYZg0o1Gh2p3
223 else:
224 toolkit.print( 1qrstvwxyABCDEFHI
225 "[bold green]✨ Success![/bold green] Initialized FastAPI project in current directory",
226 tag="success",
227 )
229 toolkit.print_line() 1qrstvwxyABCDEFHI
231 toolkit.print("[bold]Next steps:[/bold]") 1qrstvwxyABCDEFHI
232 toolkit.print(" [dim]$[/dim] uv run fastapi dev") 1qrstvwxyABCDEFHI
234 toolkit.print_line() 1JaKqriLMbNstjOucPvwkQRdSxylTzeUABmVWfXCDnYZg0EFo1Gh2HIp3
236 toolkit.print("Visit [blue]http://localhost:8000[/blue]") 1JaKqriLMbNstjOucPvwkQRdSxylTzeUABmVWfXCDnYZg0EFo1Gh2HIp3
238 toolkit.print_line() 1JaKqriLMbNstjOucPvwkQRdSxylTzeUABmVWfXCDnYZg0EFo1Gh2HIp3
240 toolkit.print("[bold]Deploy to FastAPI Cloud:[/bold]") 1JaKqriLMbNstjOucPvwkQRdSxylTzeUABmVWfXCDnYZg0EFo1Gh2HIp3
241 toolkit.print(" [dim]$[/dim] uv run fastapi deploy") 1JaKqriLMbNstjOucPvwkQRdSxylTzeUABmVWfXCDnYZg0EFo1Gh2HIp3
243 toolkit.print_line() 1JaKqriLMbNstjOucPvwkQRdSxylTzeUABmVWfXCDnYZg0EFo1Gh2HIp3
245 toolkit.print( 1JaKqriLMbNstjOucPvwkQRdSxylTzeUABmVWfXCDnYZg0EFo1Gh2HIp3
246 "[dim]💡 Tip: Use 'uv run' to automatically use the project's environment[/dim]"
247 )