Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add command for initializing FastAPI projects with example code. #41

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
261 changes: 260 additions & 1 deletion src/fastapi_cli/cli.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import os
from logging import getLogger
from pathlib import Path
from typing import Any, Union
from typing import Any, Dict, Union

import typer
from rich import print
Expand Down Expand Up @@ -31,6 +32,72 @@ def version_callback(value: bool) -> None:
raise typer.Exit()


def create_structure(structure: Dict[str, Any], base_path: str = "") -> None:
"""
Recursively creates a directory structure and files based on the provided dictionary.

This function traverses a nested dictionary structure and creates corresponding
directories and files. It can handle nested directories, empty files, and files with content.

Args:
structure (Dict[str, Any]): A dictionary representing the desired file/directory structure.
- Keys are names of directories or files.
- Values can be:
- dict: represents a subdirectory
- str: represents file content
- list: represents a directory with multiple files or subdirectories
base_path (str, optional): The base path where the structure should be created.
Defaults to the current directory.

Behavior:
- If a value is a dict, it creates a directory and recursively calls itself.
- If a value is a string, it creates a file with that content.
- If a value is a list, it creates a directory and processes each item in the list:
- String items become empty files.
- Dict items are treated as {filename: content} pairs.

Raises:
OSError: If there's an issue creating directories or files.
TypeError: If the structure contains unsupported types.

Example:
structure = {
"dir1": {
"file1.txt": "content",
"subdir": {
"file2.txt": "more content"
}
},
"dir2": [
"empty_file.txt",
{"config.json": '{"key": "value"}'}
]
}
create_structure(structure, "/path/to/base")

Note:
This function will overwrite existing files if they have the same name as items in the structure.
"""
for key, value in structure.items():
path = os.path.join(base_path, key)
if isinstance(value, dict):
os.makedirs(path, exist_ok=True)
create_structure(value, path)
elif isinstance(value, str):
with open(path, "w") as f:
f.write(value)
elif isinstance(value, list):
os.makedirs(path, exist_ok=True)
for item in value:
if isinstance(item, str):
with open(os.path.join(path, item), "w") as f:
f.write("")
elif isinstance(item, dict):
for file_name, content in item.items():
with open(os.path.join(path, file_name), "w") as f:
f.write(content)


@app.callback()
def callback(
version: Annotated[
Expand Down Expand Up @@ -273,5 +340,197 @@ def run(
)


@app.command()
def init(
name: str = typer.Option("fastapi_project", help="Name of the project"),
) -> Any:
"""Initialize a new FastAPI project with example code"""
project_structure = {
name: {
"app": {
"api": {
"v1": {
"endpoints": [
{
"items.py": """from fastapi import APIRouter, HTTPException
from typing import List, Dict

router = APIRouter()

items = {}

@router.get("/items/", response_model=List[Dict[str, Any]])
async def read_items():
return [{"id": k, **v} for k, v in items.items()]

@router.get("/items/{item_id}")
async def read_item(item_id: int):
if item_id not in items:
raise HTTPException(status_code=404, detail="Item not found")
return items[item_id]

@router.post("/items/")
async def create_item(item: Dict[str, Any]):
item_id = max(items.keys() or [0]) + 1
items[item_id] = item
return {"id": item_id, **item}
"""
},
"__init__.py",
],
},
"__init__.py": "",
},
"core": {
"config.py": """from pydantic import BaseSettings

class Settings(BaseSettings):
app_name: str = "FastAPI Project"
debug: bool = False

class Config:
env_file = ".env"

settings = Settings()
""",
"__init__.py": "",
},
"db": {
"base.py": """from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()
""",
"session.py": """from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"

engine = create_engine(
SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
""",
"__init__.py": "",
},
"models": [
{
"item.py": """from sqlalchemy import Column, Integer, String
from app.db.base import Base

class Item(Base):
__tablename__ = "items"

id = Column(Integer, primary_key=True, index=True)
title = Column(String, index=True)
description = Column(String, index=True)
"""
},
"__init__.py",
],
"schemas": [
{
"item.py": """from pydantic import BaseModel

class ItemBase(BaseModel):
title: str
description: str = None

class ItemCreate(ItemBase):
pass

class Item(ItemBase):
id: int

class Config:
orm_mode = True
"""
},
"__init__.py",
],
"main.py": """from fastapi import FastAPI
from app.api.v1.endpoints import items
from app.core.config import settings

app = FastAPI(title=settings.app_name, debug=settings.debug)

app.include_router(items.router, prefix="/api/v1")

@app.get("/")
async def root():
return {"message": "Welcome to FastAPI!"}
""",
"__init__.py": "",
},
"tests": [
{
"test_main.py": """from fastapi.testclient import TestClient
from app.main import app

client = TestClient(app)

def test_read_main():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {"message": "Welcome to FastAPI!"}
"""
},
"__init__.py",
],
"requirements.txt": """fastapi==0.68.0
uvicorn==0.15.0
sqlalchemy==1.4.23
pydantic==1.8.2
""",
"README.md": f"""# {name}

This is a FastAPI project generated using the FastAPI CLI tool.

## Getting Started

1. Install dependencies:
```
pip install -r requirements.txt
```

2. Run the server:
```
uvicorn app.main:app --reload
```

3. Open your browser and go to http://localhost:8000/docs to see the API documentation.

## Project Structure

- `app/`: Main application package
- `api/`: API endpoints
- `core/`: Core functionality (config, etc.)
- `db/`: Database-related code
- `models/`: SQLAlchemy models
- `schemas/`: Pydantic schemas
- `main.py`: Main FastAPI application
- `tests/`: Test files

## Running Tests

To run tests, use the following command:

```
pytest
```
""",
}
}

create_structure(project_structure)
typer.echo(f"FastAPI project '{name}' created successfully with example code!")


def main() -> None:
app()