Skip to content
Merged
Show file tree
Hide file tree
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
7 changes: 7 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ repos:
types: [python]
pass_filenames: false

- id: inspect-templates
name: inspect changed fastapi templates
entry: python scripts/inspect-changed-templates.py
language: system
pass_filenames: false
stages: [commit]

ci:
autofix_commit_msg: 🎨 [pre-commit.ci] Auto format from pre-commit.com hooks
autoupdate_commit_msg: ⬆ [pre-commit.ci] pre-commit autoupdate
9 changes: 8 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Changelog

## v.1.1.2 (2025-09-05)
## v1.1.3 (2025-09-13)

### Templates

- add `fastapi-single-module` template
- update `fastapi-psql-orm` template : fix dockerfile & docker-compose.yml scripts errors

## v1.1.2 (2025-09-05)

### Improvements

Expand Down
103 changes: 103 additions & 0 deletions scripts/inspect-changed-templates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
#!/usr/bin/env python3
# --------------------------------------------------------------------------
# Inspect only changed FastAPI templates (for pre-commit / local use)
# --------------------------------------------------------------------------
import os
import subprocess
import sys
from pathlib import Path
from typing import List, Set

# Ensure src is importable
PROJECT_ROOT = Path(__file__).parent.parent
sys.path.insert(0, str(PROJECT_ROOT / "src"))

from fastapi_fastkit.backend.inspector import inspect_fastapi_template

TEMPLATE_DIR = PROJECT_ROOT / "src" / "fastapi_fastkit" / "fastapi_project_template"


def get_changed_files() -> List[str]:
"""Return a list of changed files according to git (staged + unstaged)."""
# Include staged and unstaged changes compared to HEAD
cmd = [
"git",
"diff",
"--name-only",
"--cached",
]
staged = subprocess.run(cmd, capture_output=True, text=True, check=False)
cmd_unstaged = [
"git",
"diff",
"--name-only",
]
unstaged = subprocess.run(cmd_unstaged, capture_output=True, text=True, check=False)

files: Set[str] = set()
if staged.stdout:
files.update(
[line.strip() for line in staged.stdout.splitlines() if line.strip()]
)
if unstaged.stdout:
files.update(
[line.strip() for line in unstaged.stdout.splitlines() if line.strip()]
)
return sorted(files)


def extract_changed_templates(changed_files: List[str]) -> List[str]:
"""Map changed files to template directory names under TEMPLATE_DIR."""
templates: Set[str] = set()
template_root_str = str(TEMPLATE_DIR).rstrip("/")

for f in changed_files:
fp = (PROJECT_ROOT / f).resolve()
# Only consider files under template root
try:
if str(fp).startswith(template_root_str):
# template name is immediate child dir of TEMPLATE_DIR
# e.g., .../fastapi_project_template/<template>/...
rel = str(fp)[len(template_root_str) + 1 :]
parts = rel.split(os.sep)
if parts:
templates.add(parts[0])
except Exception:
continue

# Filter only directories that truly exist
return sorted([t for t in templates if (TEMPLATE_DIR / t).is_dir()])


def main() -> int:
changed_files = get_changed_files()
changed_templates = extract_changed_templates(changed_files)

if not changed_templates:
print("No template changes detected; skipping inspection.")
return 0

print(f"Detected changed templates: {', '.join(changed_templates)}")
any_failed = False

for template in changed_templates:
t_path = TEMPLATE_DIR / template
print(f"Inspecting changed template: {template}\n Path: {t_path}")
try:
result = inspect_fastapi_template(str(t_path))
if not result.get("is_valid", False):
any_failed = True
except Exception as e:
any_failed = True
print(f"Exception while inspecting {template}: {e}")

if any_failed:
print("Template inspection failed for at least one changed template.")
return 1

print("All changed templates passed inspection.")
return 0


if __name__ == "__main__":
sys.exit(main())
2 changes: 1 addition & 1 deletion src/fastapi_fastkit/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "1.1.2"
__version__ = "1.1.3"

import os

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,10 +190,12 @@ Once the server is running, visit:
The application exposes the following tools and endpoints via MCP:

### MCP-Specific Endpoints

- **mcp_hello**: Simple hello world endpoint (`/mcp-routes/hello`)
- **mcp_status**: MCP configuration status (`/mcp-routes/status`)

### API Endpoints via MCP

- **get_items**: List items with pagination
- **get_item**: Get specific item by ID
- **create_item**: Create new item (requires auth)
Expand Down Expand Up @@ -229,21 +231,19 @@ COPY scripts/ scripts/
CMD ["uvicorn", "src.main:app", "--host", "0.0.0.0", "--port", "8000"]
```

## Contributing

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Run tests and linting
5. Submit a pull request

## License

This project is licensed under the MIT License.

## Support

For support and questions:
- Create an issue in the repository
- Check the FastAPI documentation: https://fastapi.tiangolo.com/
- Check the FastAPI-MCP documentation: https://github.com/tadata-org/fastapi_mcp

# Project Origin

This project was created based on the template from the [FastAPI-fastkit](https://github.com/bnbong/FastAPI-fastkit) project.

The `FastAPI-fastkit` is an open-source project that helps Python and FastAPI beginners quickly set up a FastAPI-based application development environment in a framework-like structure.

### Template Information
- Template creator: [bnbong](mailto:bbbong9@gmail.com)
- FastAPI-fastkit project maintainer: [bnbong](mailto:bbbong9@gmail.com)
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,6 @@ FROM python:3.12-slim

WORKDIR /app

RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
&& rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
COPY setup.py .
COPY src/ src/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: '3'

services:
db:
image: postgres:16-alpine
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# FastAPI Single Module Template

This template provides a minimal FastAPI application that runs with a single `main.py` module.

## Structure
```
.
├── README.md
├── requirements.txt
├── pyproject.toml
├── setup.py
├── setup.cfg
├── src/
│ └── main.py
└── tests/
├── conftest.py
└── test_main.py
```

## Run
```bash
uvicorn src.main:app --reload
```

## Requirements
- Python >= 3.9
- FastAPI, Uvicorn

# Project Origin

This project was created based on the template from the [FastAPI-fastkit](https://github.com/bnbong/FastAPI-fastkit) project.

The `FastAPI-fastkit` is an open-source project that helps Python and FastAPI beginners quickly set up a FastAPI-based application development environment in a framework-like structure.

### Template Information
- Template creator: [bnbong](mailto:bbbong9@gmail.com)
- FastAPI-fastkit project maintainer: [bnbong](mailto:bbbong9@gmail.com)
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[project]
name = "<project_name>"
version = "0.1.0"
description = "<description>"
authors = [
{name = "<author>", email = "<author_email>"},
]
readme = "README.md"
license = "MIT"
requires-python = ">=3.9"
dependencies = [
"fastapi>=0.115.8",
"uvicorn[standard]>=0.34.0",
]

[dependency-groups]
dev = [
"pytest>=8.3.4",
"httpx>=0.28.1",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build.targets.wheel]
packages = ["src"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
fastapi==0.115.8
uvicorn[standard]==0.34.0
pydantic==2.10.6
pydantic-settings==2.7.1
pytest==8.3.4
httpx==0.28.1
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[tool:pytest]
pythonpath = src
testpaths = tests
python_files = test_*.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from setuptools import setup

install_requires: list[str] = [
"fastapi",
"uvicorn",
]

description = "[FastAPI-fastkit templated] <description>"

setup(
name="<project_name>",
description=description,
author="<author>",
author_email=f"<author_email>",
packages=["src"],
install_requires=install_requires,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# --------------------------------------------------------------------------
# Main server application module
# --------------------------------------------------------------------------
from fastapi import FastAPI

app = FastAPI(title="<project_name>")


@app.get("/")
async def root() -> dict[str, str]:
return {"message": "Hello from FastAPI Single Module!"}


@app.get("/health")
async def health() -> dict[str, str]:
return {"status": "healthy"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# --------------------------------------------------------------------------
# pytest runtime configuration module
# --------------------------------------------------------------------------
from collections.abc import Generator

import pytest
from fastapi.testclient import TestClient

from src.main import app


@pytest.fixture(scope="module")
def client() -> Generator[TestClient, None, None]:
with TestClient(app) as c:
yield c
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# --------------------------------------------------------------------------
# Main endpoint testcases
# --------------------------------------------------------------------------
from fastapi.testclient import TestClient


def test_root(client: TestClient) -> None:
res = client.get("/")
assert res.status_code == 200
assert res.json()["message"].startswith("Hello from FastAPI Single Module")


def test_health(client: TestClient) -> None:
res = client.get("/health")
assert res.status_code == 200
assert res.json() == {"status": "healthy"}
Loading