From 9135de3e9628c3e911347ab8718ec515cb9d6533 Mon Sep 17 00:00:00 2001 From: HBilal Khan Yousafzai Date: Wed, 1 Oct 2025 10:06:55 +0500 Subject: [PATCH 01/20] ci workflow added --- .github/workflows/ci.yaml | 54 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 .github/workflows/ci.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..ff53b3c --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,54 @@ +name: FastAPI CI with uv + +on: + push: + branches: + - main + - dev + - staging + +jobs: + test-and-run: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v5 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install uv + uses: astral-sh/setup-uv@v6 + with: + version: "0.8.22" + + - name: Create virtual environment and sync dependencies + run: | + uv venv .venv + uv sync + + - name: Run pre-commit hooks with uv + run: uv run pre-commit run --all-files + + - name: Run ruff linter check with uv + run: uv run ruff check ./app + + - name: Start FastAPI app with uvicorn in background + run: | + uv run uvicorn app.main:app --host 127.0.0.1 --port 8001 & + sleep 10 # wait for server startup + + - name: Health check API response + run: | + RESPONSE=$(curl -s http://127.0.0.1:8001/api/health) + EXPECTED='{"data":{"status":"ok","message":"API is running"},"is_success":true,"error":null,"meta_data":{"version":"1.0.0","timestamp":"2024-07-24T10:00:00Z"}}' + if [ "$RESPONSE" != "$EXPECTED" ]; then + echo "Health check response does not match expected" + echo "Response: $RESPONSE" + exit 1 + else + echo "Health check passed." + fi From a2d18adc361eca2ebdb4c1f9ce4de2e51b0f0bea Mon Sep 17 00:00:00 2001 From: HBilal Khan Yousafzai Date: Wed, 1 Oct 2025 10:49:25 +0500 Subject: [PATCH 02/20] chore: added workflow started on push and pull_request --- .github/workflows/ci.yaml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ff53b3c..22fa612 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -4,9 +4,10 @@ on: push: branches: - main - - dev + pull_request: + branches: + - main - staging - jobs: test-and-run: runs-on: ubuntu-latest From 2b7a3f435dea343a64a82f33b4bf5228b581c3e1 Mon Sep 17 00:00:00 2001 From: HBilal Khan Yousafzai Date: Wed, 1 Oct 2025 10:57:12 +0500 Subject: [PATCH 03/20] fixed ci given error --- .github/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 22fa612..108b6f2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -11,7 +11,7 @@ on: jobs: test-and-run: runs-on: ubuntu-latest - + steps: - name: Checkout code uses: actions/checkout@v5 From 9a294cd36de53ef36d108543781837f401c0ce65 Mon Sep 17 00:00:00 2001 From: HBilal Khan Yousafzai Date: Wed, 1 Oct 2025 11:04:05 +0500 Subject: [PATCH 04/20] health check fixed --- .github/workflows/ci.yaml | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 108b6f2..7d8e88a 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -42,14 +42,11 @@ jobs: uv run uvicorn app.main:app --host 127.0.0.1 --port 8001 & sleep 10 # wait for server startup - - name: Health check API response + - name: FastAPI boot smoke test run: | - RESPONSE=$(curl -s http://127.0.0.1:8001/api/health) - EXPECTED='{"data":{"status":"ok","message":"API is running"},"is_success":true,"error":null,"meta_data":{"version":"1.0.0","timestamp":"2024-07-24T10:00:00Z"}}' - if [ "$RESPONSE" != "$EXPECTED" ]; then - echo "Health check response does not match expected" - echo "Response: $RESPONSE" - exit 1 - else - echo "Health check passed." - fi + uvicorn main:app --host 0.0.0.0 --port 8001 > uvicorn.log 2>&1 & + for i in {1..20}; do + curl -fSsf http://127.0.0.1:8001/api/health && break || sleep 1 + done + cat uvicorn.log + pkill -f "uvicorn main:app" From 2fe11ec1d1117179b8acb2c16b7b34ddef8af872 Mon Sep 17 00:00:00 2001 From: HBilal Khan Yousafzai Date: Wed, 1 Oct 2025 11:10:28 +0500 Subject: [PATCH 05/20] fix: postgres service added to ci pipeline to fix database issue --- .github/workflows/ci.yaml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7d8e88a..6b8c98f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -11,7 +11,22 @@ on: jobs: test-and-run: runs-on: ubuntu-latest - + services: + postgres: + image: postgres:15 + env: + POSTGRES_USER: hbky + POSTGRES_PASSWORD: my_secure_pass + POSTGRES_DB: ci_db + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + env: + DATABASE_URL: postgresql://hbky:my_secure_pass@localhost:5432/ci_db steps: - name: Checkout code uses: actions/checkout@v5 @@ -40,7 +55,7 @@ jobs: - name: Start FastAPI app with uvicorn in background run: | uv run uvicorn app.main:app --host 127.0.0.1 --port 8001 & - sleep 10 # wait for server startup + sleep 20 # wait for server startup - name: FastAPI boot smoke test run: | From e9dc5955e396f3586ca81d12493af4544372bb44 Mon Sep 17 00:00:00 2001 From: HBilal Khan Yousafzai Date: Wed, 1 Oct 2025 11:15:27 +0500 Subject: [PATCH 06/20] fix: ci pipeline envs updated --- .github/workflows/ci.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6b8c98f..ef7775d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -27,6 +27,11 @@ jobs: --health-retries 5 env: DATABASE_URL: postgresql://hbky:my_secure_pass@localhost:5432/ci_db + DATABASE_NAME: ci_db + DATABASE_USER: hbky + DATABASE_PASSWORD: my_secure_pass + DATABASE_HOST: localhost + DATABASE_PORT: 5432 steps: - name: Checkout code uses: actions/checkout@v5 From c88ec103c942cda7f07a2928ac379b7b86b0cf86 Mon Sep 17 00:00:00 2001 From: HBilal Khan Yousafzai Date: Wed, 1 Oct 2025 11:18:40 +0500 Subject: [PATCH 07/20] fix: ci pipeline smoke command fixed --- .github/workflows/ci.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index ef7775d..333330c 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -64,9 +64,9 @@ jobs: - name: FastAPI boot smoke test run: | - uvicorn main:app --host 0.0.0.0 --port 8001 > uvicorn.log 2>&1 & + uvicorn app.main:app --host 0.0.0.0 --port 8001 > uvicorn.log 2>&1 & for i in {1..20}; do curl -fSsf http://127.0.0.1:8001/api/health && break || sleep 1 done cat uvicorn.log - pkill -f "uvicorn main:app" + pkill -f "uvicorn app.main:app" From 38175fb3d752c0c890e09b0e4bae6a22e78c475b Mon Sep 17 00:00:00 2001 From: HBilal Khan Yousafzai Date: Wed, 15 Oct 2025 15:48:05 +0500 Subject: [PATCH 08/20] feat: Add strict Ruff linting and formatting with Python 3.12 target; enable comprehensive MyPy type checking --- pyproject.toml | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 6322c40..1833c0e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,6 +57,45 @@ dev = [ "ruff>=0.12.9", ] + [tool.ruff] +target-version = "py312" line-length = 120 -select = ["E", "F", "W", "C", "B"] +exclude = [ + ".git", + ".venv", + "__pycache__", + "build", + "dist", + ".mypy_cache", + ".pytest_cache", + ".ruff_cache", + "app/alembic" +] + +[tool.ruff.format] +quote-style = "double" +indent-style = "space" +skip-magic-trailing-comma = false +line-ending = "auto" + +[tool.ruff.lint] +# Select rule groups or codes appropriate for your project scope +select = ["E", "F", "A", "ASYNC", "B", "I"] +# Optionally ignore rules you don't care about +ignore = [] +# Enable fixing of all fixable problems when running with --fix +fixable = ["ALL"] +unfixable = [] +# Regex to allow underscore-prefixed variables as unused without warning +dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" + + +[tool.mypy] +python_version = "3.12" +strict = true +warn_unused_ignores = true +disallow_untyped_defs = true +disallow_untyped_calls = true +disallow_incomplete_defs = true +warn_return_any = true From 976037de2cab0d14cd7f81adab708805766b0c34 Mon Sep 17 00:00:00 2001 From: HBilal Khan Yousafzai Date: Wed, 15 Oct 2025 16:12:49 +0500 Subject: [PATCH 09/20] feat: pre-commit hook created --- misc/pre-commit | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100755 misc/pre-commit diff --git a/misc/pre-commit b/misc/pre-commit new file mode 100755 index 0000000..86b1fbd --- /dev/null +++ b/misc/pre-commit @@ -0,0 +1,38 @@ +#!/bin/bash + +# Get list of staged Python files +files=$(git diff --cached --name-only --diff-filter=ACM | grep "\\.py$") + +echo "Modified Python files: $files" + +# If no Python files are modified, exit successfully +if [ -z "$files" ]; then + echo "No Python files modified. Skipping pre-commit hooks." + exit 0 +fi + +# Run ruff on the modified files with --output-format=full to capture warnings +echo "šŸ” Running ruff checks..." +# Capture both stdout and stderr to catch warnings +ruff_output=$(uv run ruff check $files --output-format=full 2>&1) +ruff_exit_code=$? + +# Check if there are any warnings or errors +if [[ $ruff_output == *"warning:"* ]] || [ $ruff_exit_code -ne 0 ]; then + echo "āŒ Ruff found issues that need to be fixed:" + echo "$ruff_output" + echo "\nPlease fix the above issues before committing." + exit 1 +fi + +# Capture the exit code of the pre-commit command +pre_commit_exit_code=$? + +# If pre-commit found issues, exit with its exit code +if [ $pre_commit_exit_code -ne 0 ]; then + echo "āŒ Pre-commit hooks found issues that need to be fixed" + exit $pre_commit_exit_code +fi + +echo "āœ… All checks passed" +exit 0 \ No newline at end of file From 63a709c2825151a50ff1407671651c0bca3de9af Mon Sep 17 00:00:00 2001 From: HBilal Khan Yousafzai Date: Wed, 15 Oct 2025 16:13:01 +0500 Subject: [PATCH 10/20] feat: setup.py created --- setup.py | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100755 setup.py diff --git a/setup.py b/setup.py new file mode 100755 index 0000000..2ead1cc --- /dev/null +++ b/setup.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +import os +import shutil +import sys +from pathlib import Path + +def setup_env(): + example_env = Path(".example.env") + env_file = Path(".env") + + if not example_env.exists(): + print("āŒ .example.env not found. Please create it first.") + return False + + if not env_file.exists(): + print("šŸ”„ Creating .env from .example.env...") + shutil.copy(example_env, env_file) + print("āœ… Created .env file") + else: + print("ā„¹ļø .env file already exists, skipping creation") + return True + +def setup_git_hooks(): + hooks_dir = Path(".git/hooks") + pre_commit_src = Path("misc/pre-commit") + + if not pre_commit_src.exists(): + print(f"āŒ Pre-commit hook not found at {pre_commit_src}") + return False + + if not hooks_dir.exists(): + print("ā„¹ļø Initializing git repository...") + os.system("git init") + + print("šŸ”§ Setting up git hooks...") + pre_commit_dest = hooks_dir / "pre-commit" + + # Copy the pre-commit hook + shutil.copy(pre_commit_src, pre_commit_dest) + + # Make it executable + pre_commit_dest.chmod(0o755) + print("āœ… Git hooks set up successfully") + return True + +def main(): + print("šŸš€ Starting project setup...") + + if not setup_env(): + sys.exit(1) + + if not setup_git_hooks(): + print("āš ļø Git hooks setup failed, but continuing...") + + print("\nšŸŽ‰ Setup completed successfully!") + print("Please review the .env file and update any necessary values.") + +if __name__ == "__main__": + main() \ No newline at end of file From 2465d4d79cd1bfab686baa490a1ce6f42703ab97 Mon Sep 17 00:00:00 2001 From: HBilal Khan Yousafzai Date: Wed, 15 Oct 2025 16:17:20 +0500 Subject: [PATCH 11/20] chore: setup.py added to readme --- README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b52b551..80081dc 100644 --- a/README.md +++ b/README.md @@ -31,11 +31,22 @@ This is a template project for a FastAPI application with a PostgreSQL database, ## Basic Configuration +### Setup +Setup Automatically +```python +./setup.py +``` +OR Manually + 1. **Environment Variables**: This project uses a `.env` file for local development configuration. If it doesn't exist, run this command to create: ```python - cp .env.example .env + cp .example.env .env + ``` + + ```python + cp misc/pre-commit .git/hooks/pre-commit ``` Ensure you set the following environment variables in your `.env` file: From 1db02c44ebbf22ed6eef6aca549e9b883f48f7d7 Mon Sep 17 00:00:00 2001 From: HBilal Khan Yousafzai Date: Wed, 15 Oct 2025 16:18:01 +0500 Subject: [PATCH 12/20] chore: ci workflow removed --- .github/workflows/ci.yaml | 72 --------------------------------------- 1 file changed, 72 deletions(-) delete mode 100644 .github/workflows/ci.yaml diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml deleted file mode 100644 index 333330c..0000000 --- a/.github/workflows/ci.yaml +++ /dev/null @@ -1,72 +0,0 @@ -name: FastAPI CI with uv - -on: - push: - branches: - - main - pull_request: - branches: - - main - - staging -jobs: - test-and-run: - runs-on: ubuntu-latest - services: - postgres: - image: postgres:15 - env: - POSTGRES_USER: hbky - POSTGRES_PASSWORD: my_secure_pass - POSTGRES_DB: ci_db - ports: - - 5432:5432 - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - env: - DATABASE_URL: postgresql://hbky:my_secure_pass@localhost:5432/ci_db - DATABASE_NAME: ci_db - DATABASE_USER: hbky - DATABASE_PASSWORD: my_secure_pass - DATABASE_HOST: localhost - DATABASE_PORT: 5432 - steps: - - name: Checkout code - uses: actions/checkout@v5 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - - name: Install uv - uses: astral-sh/setup-uv@v6 - with: - version: "0.8.22" - - - name: Create virtual environment and sync dependencies - run: | - uv venv .venv - uv sync - - - name: Run pre-commit hooks with uv - run: uv run pre-commit run --all-files - - - name: Run ruff linter check with uv - run: uv run ruff check ./app - - - name: Start FastAPI app with uvicorn in background - run: | - uv run uvicorn app.main:app --host 127.0.0.1 --port 8001 & - sleep 20 # wait for server startup - - - name: FastAPI boot smoke test - run: | - uvicorn app.main:app --host 0.0.0.0 --port 8001 > uvicorn.log 2>&1 & - for i in {1..20}; do - curl -fSsf http://127.0.0.1:8001/api/health && break || sleep 1 - done - cat uvicorn.log - pkill -f "uvicorn app.main:app" From 012890380293dd3e68ddfdece65c61af6111efd5 Mon Sep 17 00:00:00 2001 From: HBilal Khan Yousafzai Date: Tue, 21 Oct 2025 15:53:02 +0500 Subject: [PATCH 13/20] Add .editorconfig for consistent coding styles --- .editorconfig | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..17dc750 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,52 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# Top-most EditorConfig file +root = true + +# Unix-style newlines with a newline ending every file +[*] +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +charset = utf-8 +indent_style = space +indent_size = 4 + +# Python files +[*.py] +indent_size = 4 +max_line_length = 88 +quote_type = "double" + +# YAML files +[*.{yaml,yml}] +indent_style = space +indent_size = 2 + +# JSON files +[*.json] +indent_style = space +indent_size = 2 + +# TOML files +[*.toml] +indent_style = space +indent_size = 2 + +# Markdown files +[*.md] +trim_trailing_whitespace = false + +# Makefiles +[Makefile] +indent_style = tab + +# Dockerfiles +[Dockerfile] +indent_style = space +indent_size = 4 + +# Shell scripts +[*.sh] +indent_style = space +indent_size = 2 From a6224ce8d8f7a5829ed5312a1dbf2c8d3af5576b Mon Sep 17 00:00:00 2001 From: HBilal Khan Yousafzai Date: Tue, 21 Oct 2025 15:54:16 +0500 Subject: [PATCH 14/20] Add VSCode settings for Python development --- .vscode/settings.json | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..67b8b28 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,27 @@ +{ + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.ruff": "explicit", + "source.organizeImports.ruff": "explicit" + }, + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.rulers": [88], + "python.analysis.typeCheckingMode": "basic", + "python.linting.enabled": true, + "python.linting.ruffEnabled": true, + "python.linting.pylintEnabled": false, + "python.formatting.provider": "none", + "python.analysis.autoImportCompletions": true, + "python.analysis.autoSearchPaths": true, + "python.analysis.diagnosticMode": "workspace", + "python.analysis.inlayHints.variableTypes": true, + "python.analysis.inlayHints.functionReturnTypes": true, + "[python]": { + "editor.defaultFormatter": "charliermarsh.ruff", + "editor.formatOnSave": true, + "editor.codeActionsOnSave": { + "source.fixAll.ruff": "explicit", + "source.organizeImports.ruff": "explicit" + } + } +} From 3597e00f5a08c5e5bb2836df79e196686f9e3b35 Mon Sep 17 00:00:00 2001 From: HBilal Khan Yousafzai Date: Tue, 21 Oct 2025 15:58:31 +0500 Subject: [PATCH 15/20] Update README with clone and remote repo instructions Added instructions for cloning the repository and changing the remote URL. --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 518e75d..68ffc5c 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,17 @@ This is a template project for a FastAPI application with a PostgreSQL database, ## Basic Configuration +### Clone +clone the repo +```bash +git clone https://github.com/Texagon-Dev/fastapi-postgres-template your-project-name +``` + +### Change Remote Repo to your's one +```bash +git remote set-url origin +``` + ### Setup Setup Automatically ```python From 095a78a4270cfccf82e3f36777da9c1b24e7a748 Mon Sep 17 00:00:00 2001 From: HBilal Khan Yousafzai Date: Tue, 21 Oct 2025 16:02:39 +0500 Subject: [PATCH 16/20] Update Python version and add project metadata --- pyproject.toml | 84 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 67 insertions(+), 17 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 1833c0e..588246a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,10 @@ name = "FastAPI-Postgres-Template" version = "0.1.0" description = "Add your description here" readme = "README.md" -requires-python = ">=3.11" +authors = [{ name = "HBilal Khan", email = "hbilal@texagon.io" }, { name = "M Talha", email = "mtalha@texagon.io" }] +license = { text = "Proprietary" } +urls = { Homepage = "https://example.com/saas-backend", Repository = "https://github.com/Texagon-Dev/fastapi-hexagonal-ddd-postgres-template", Issues = "https://github.com/Texagon-Dev/fastapi-hexagonal-ddd-postgres-template/issues" } +requires-python = ">=3.12" dependencies = [ "aiocache[redis]>=0.12.3", "alembic==1.16.1", @@ -57,10 +60,21 @@ dev = [ "ruff>=0.12.9", ] +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" +[tool.hatch.build.targets.wheel] +packages = ["app"] + +# ------------------------- +# Ruff (lint + format) +# ------------------------- [tool.ruff] -target-version = "py312" line-length = 120 +target-version = "py311" +# Keep extended project-specific ignores AND a broad exclude list +extend-exclude = ["migrations", "alembic"] exclude = [ ".git", ".venv", @@ -69,8 +83,7 @@ exclude = [ "dist", ".mypy_cache", ".pytest_cache", - ".ruff_cache", - "app/alembic" + ".ruff_cache" ] [tool.ruff.format] @@ -80,22 +93,59 @@ skip-magic-trailing-comma = false line-ending = "auto" [tool.ruff.lint] -# Select rule groups or codes appropriate for your project scope -select = ["E", "F", "A", "ASYNC", "B", "I"] -# Optionally ignore rules you don't care about -ignore = [] -# Enable fixing of all fixable problems when running with --fix +select = [ + "E", # pycodestyle errors + "F", # pyflakes + "I", # isort (import sorting) + "B", # bugbear + "UP", # pyupgrade + "SIM", # simplify +] +ignore = [ + "E501", # line too long (formatter handles) + "E203", # whitespace before ':' + "B008", # FastAPI Depends used in defaults + "B904", # raise in except without from +] +# From A → enable sweeping autofix and smarter dummy variables fixable = ["ALL"] -unfixable = [] -# Regex to allow underscore-prefixed variables as unused without warning dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" +[tool.ruff.lint.per-file-ignores] +"__init__.py" = ["F401"] +"alembic/env.py" = ["E402", "F401"] +"app/main.py" = ["E402"] +"app/services/prompting/suggestion_prompt.py" = ["F541"] +# ------------------------- +# MyPy +# ------------------------- [tool.mypy] -python_version = "3.12" -strict = true -warn_unused_ignores = true -disallow_untyped_defs = true -disallow_untyped_calls = true -disallow_incomplete_defs = true +python_version = "3.11" warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = false +disallow_incomplete_defs = false +check_untyped_defs = true +no_implicit_optional = true +warn_redundant_casts = true +warn_unused_ignores = true +warn_no_return = true +strict_equality = true +ignore_missing_imports = true + +[[tool.mypy.overrides]] +module = "app.migrations.*" +ignore_errors = true + +[[tool.mypy.overrides]] +module = "alembic.*" +ignore_errors = true + +# ------------------------- +# Bandit (security) +# ------------------------- +[tool.bandit] +exclude_dirs = ["tests", "migrations", "alembic"] +skips = ["B101", "B601"] + From 2dacd22a9d81b3902f372a4b0baf81b61f34df20 Mon Sep 17 00:00:00 2001 From: HBilal Khan Yousafzai Date: Wed, 22 Oct 2025 12:07:09 +0500 Subject: [PATCH 17/20] chore(pre-commit): add Ruff, YAML/TOML linters checks --- .pre-commit-config.yaml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 202dfd4..d7714a3 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,6 +11,20 @@ repos: exclude: ^app/alembic/ - id: check-added-large-files exclude: ^app/alembic/ + - id: check-toml + exclude: ^app/alembic/ + - id: check-yaml + exclude: ^app/alembic/ + types_or: [yaml, yml] + + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.14.1 + hooks: + - id: ruff + args: [--fix, --show-fixes] + exclude: ^app/alembic/ + - id: ruff-format + exclude: ^app/alembic/ - repo: https://github.com/myint/autoflake rev: v2.3.1 From 660713edfe2815cbd1d271bf6557d006a7eeacc0 Mon Sep 17 00:00:00 2001 From: HBilal Khan Yousafzai Date: Wed, 22 Oct 2025 12:10:45 +0500 Subject: [PATCH 18/20] chore: make mypy more stricker --- pyproject.toml | 31 +++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 588246a..4f3c46c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,19 +71,27 @@ packages = ["app"] # Ruff (lint + format) # ------------------------- [tool.ruff] -line-length = 120 +line-length = 120 # Matches Black's default target-version = "py311" -# Keep extended project-specific ignores AND a broad exclude list -extend-exclude = ["migrations", "alembic"] +# Exclude non-source directories and common build artifacts exclude = [ ".git", ".venv", "__pycache__", + "**/__pycache__", "build", "dist", ".mypy_cache", ".pytest_cache", - ".ruff_cache" + ".ruff_cache", + "migrations", + "alembic", + "**/migrations", + "**/alembic", + "**/tests", + "**/test_*.py", + "**/*_test.py", + "**/conftest.py" ] [tool.ruff.format] @@ -122,16 +130,19 @@ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$" # ------------------------- [tool.mypy] python_version = "3.11" +# Enable strict mode +strict = true +# Keep some additional strictness flags for clarity +disallow_any_generics = true +disallow_subclassing_any = true +disallow_untyped_decorators = true warn_return_any = true -warn_unused_configs = true -disallow_untyped_defs = false -disallow_incomplete_defs = false -check_untyped_defs = true -no_implicit_optional = true +warn_unreachable = true warn_redundant_casts = true warn_unused_ignores = true warn_no_return = true -strict_equality = true +no_implicit_optional = true +check_untyped_defs = true ignore_missing_imports = true [[tool.mypy.overrides]] From 17e3cf4ee8330018388ee74581f30fc5427c9460 Mon Sep 17 00:00:00 2001 From: HBilal Khan Yousafzai Date: Wed, 22 Oct 2025 12:18:15 +0500 Subject: [PATCH 19/20] chore: dev setup script added --- scripts/dev_setup.py | 91 ++++++++++++++++++++++++++++++++++++++++++++ setup.py | 59 ---------------------------- 2 files changed, 91 insertions(+), 59 deletions(-) create mode 100644 scripts/dev_setup.py delete mode 100755 setup.py diff --git a/scripts/dev_setup.py b/scripts/dev_setup.py new file mode 100644 index 0000000..6d65ec5 --- /dev/null +++ b/scripts/dev_setup.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +import os +import shutil +import sys +import subprocess +from pathlib import Path + +def run_command(cmd: str, cwd: str = None) -> bool: + """Run a shell command and return True if successful.""" + print(f"\n$ {cmd}") + try: + subprocess.run(cmd, shell=True, check=True, cwd=cwd) + return True + except subprocess.CalledProcessError as e: + print(f"āŒ Command failed with exit code {e.returncode}") + return False + +def setup_env() -> bool: + """Set up the environment file.""" + example_env = Path(".example.env") + env_file = Path(".env") + + if not example_env.exists(): + print("āŒ .example.env not found. Please create it first.") + return False + + if not env_file.exists(): + print("šŸ”„ Creating .env from .example.env...") + shutil.copy(example_env, env_file) + print("āœ… Created .env file") + else: + print("ā„¹ļø .env file already exists, skipping creation") + return True + +def setup_git_hooks() -> bool: + """Set up git hooks.""" + hooks_dir = Path(".git/hooks") + pre_commit_src = Path("misc/pre-commit") + + if not pre_commit_src.exists(): + print(f"āŒ Pre-commit hook not found at {pre_commit_src}") + return False + + if not hooks_dir.exists(): + print("ā„¹ļø Initializing git repository...") + if not run_command("git init"): + return False + + print("šŸ”§ Setting up git hooks...") + pre_commit_dest = hooks_dir / "pre-commit" + + # Copy the pre-commit hook + shutil.copy(pre_commit_src, pre_commit_dest) + + # Make it executable (works on Unix-like systems) + try: + pre_commit_dest.chmod(0o755) + except Exception as e: + print(f"āš ļø Could not set executable permissions: {e}") + print("āš ļø On Windows, you may need to run this as Administrator") + + # Install pre-commit hooks + if not run_command("pre-commit install"): + print("āš ļø Failed to install pre-commit hooks") + return False + + print("āœ… Git hooks set up successfully") + return True + +def main(): + print("\nšŸš€ Starting project setup...") + + # Create scripts directory if it doesn't exist + scripts_dir = Path("scripts") + scripts_dir.mkdir(exist_ok=True) + + if not setup_env(): + sys.exit(1) + + if not setup_git_hooks(): + print("āš ļø Git hooks setup had issues, but continuing...") + + print("\nšŸŽ‰ Setup completed successfully!") + print("Next steps:") + print("1. Review and update the .env file with your configuration") + print("2. Run 'uv sync' to install dependencies") + print("3. Run 'uv run alembic upgrade head' to set up the database") + print("4. Run 'uv run uvicorn app.main:app --reload' to start the development server") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100755 index 2ead1cc..0000000 --- a/setup.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python3 -import os -import shutil -import sys -from pathlib import Path - -def setup_env(): - example_env = Path(".example.env") - env_file = Path(".env") - - if not example_env.exists(): - print("āŒ .example.env not found. Please create it first.") - return False - - if not env_file.exists(): - print("šŸ”„ Creating .env from .example.env...") - shutil.copy(example_env, env_file) - print("āœ… Created .env file") - else: - print("ā„¹ļø .env file already exists, skipping creation") - return True - -def setup_git_hooks(): - hooks_dir = Path(".git/hooks") - pre_commit_src = Path("misc/pre-commit") - - if not pre_commit_src.exists(): - print(f"āŒ Pre-commit hook not found at {pre_commit_src}") - return False - - if not hooks_dir.exists(): - print("ā„¹ļø Initializing git repository...") - os.system("git init") - - print("šŸ”§ Setting up git hooks...") - pre_commit_dest = hooks_dir / "pre-commit" - - # Copy the pre-commit hook - shutil.copy(pre_commit_src, pre_commit_dest) - - # Make it executable - pre_commit_dest.chmod(0o755) - print("āœ… Git hooks set up successfully") - return True - -def main(): - print("šŸš€ Starting project setup...") - - if not setup_env(): - sys.exit(1) - - if not setup_git_hooks(): - print("āš ļø Git hooks setup failed, but continuing...") - - print("\nšŸŽ‰ Setup completed successfully!") - print("Please review the .env file and update any necessary values.") - -if __name__ == "__main__": - main() \ No newline at end of file From 6eef28e32324807f67e16938763e86dda2713166 Mon Sep 17 00:00:00 2001 From: HBilal Khan Yousafzai Date: Wed, 22 Oct 2025 12:39:49 +0500 Subject: [PATCH 20/20] docs: reorganize README with clear setup options and improve Windows compatibility --- README.md | 207 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 106 insertions(+), 101 deletions(-) diff --git a/README.md b/README.md index 68ffc5c..881096b 100644 --- a/README.md +++ b/README.md @@ -17,131 +17,136 @@ This is a template project for a FastAPI application with a PostgreSQL database, - Password reset functionality - **Environment-based configuration**: Different settings for development, staging, and production -## Prerequisites +## Getting Started -- **Local Development**: - - Python 3.11 or higher - - PostgreSQL installed locally or accessible - - pip or another Python package manager +### Prerequisites -- **Docker Deployment**: - - Docker and Docker Compose installed - - A code editor (e.g., VS Code) - - A terminal or command prompt +- **Python 3.11+** and pip/uv +- **PostgreSQL** (local or remote) +- **Git** for version control +- **Docker & Docker Compose** (for containerized development) +- **Terminal/Command Prompt** (PowerShell recommended for Windows) -## Basic Configuration +### Quick Start -### Clone -clone the repo -```bash -git clone https://github.com/Texagon-Dev/fastapi-postgres-template your-project-name -``` - -### Change Remote Repo to your's one -```bash -git remote set-url origin -``` - -### Setup -Setup Automatically -```python -./setup.py -``` -OR Manually +**Clone the repository**: + ```bash + git clone https://github.com/Texagon-Dev/fastapi-postgres-template your-project-name + cd your-project-name + git remote set-url origin + ``` -1. **Environment Variables**: - This project uses a `.env` file for local development configuration. If it doesn't exist, run this command to create: +### Development Setup - ```python - cp .example.env .env - ``` - - ```python - cp misc/pre-commit .git/hooks/pre-commit - ``` +### Option 1: Automated Setup (Recommended) +We provide a setup script to help you get started quickly. Choose the appropriate command for your operating system: - Ensure you set the following environment variables in your `.env` file: +#### Mac/Linux: +```bash +# Make the script executable +chmod +x scripts/dev_setup.py -```python -# Core settings -FRONTEND_URL='http://localhost:3000' -SECRET_KEY='your_32_char_strong_secret_key_here' -DEBUG=True -ENVIRONMENT='development' # Options: development, staging, production - -# Database settings -POSTGRES_USER="your_username" -POSTGRES_PASSWORD="your_password" -POSTGRES_DB_NAME="fastapi_db" - -# Authentication -ALGORITHM='HS256' -ACCESS_TOKEN_EXPIRE_MINUTES=30 # Short-lived access tokens for security -REFRESH_TOKEN_EXPIRE_DAYS=7 # Longer-lived refresh tokens +# Run the setup script +./scripts/dev_setup.py ``` -## Local Installation and Setup +#### Windows (Command Prompt): +```cmd +# Run the setup script directly with Python +python scripts/dev_setup.py +``` -#### (Recommended uv) +#### Windows (PowerShell): +```powershell +# Run the setup script directly with Python +python .\scripts\dev_setup.py +``` -1. **Install dependencies directly with uv**: +### Option 2: Manual Setup +**The setup script will**: +- Create a `.env` file from the example +- Set up git hooks +- Install pre-commit hooks + + 2. **Set up environment**: + - Copy the example environment file: + ```bash + # Linux/macOS + cp .example.env .env + + # Windows (Command Prompt) + copy .example.env .env + + # Windows (PowerShell) + Copy-Item -Path .example.env -Destination .env + ``` + - Update the `.env` file with your configuration (see Configuration section below) + + ### Configuration + + - Edit the `.env` file with your settings: + + ```env + # Core settings + FRONTEND_URL='http://localhost:3000' + SECRET_KEY='your_32_char_strong_secret_key_here' + DEBUG=True + ENVIRONMENT='development' # Options: development, staging, production + + # Database settings + POSTGRES_USER="your_username" + POSTGRES_PASSWORD="your_password" + POSTGRES_DB_NAME="fastapi_db" + + # Authentication + ALGORITHM='HS256' + ACCESS_TOKEN_EXPIRE_MINUTES=30 # Short-lived access tokens for security + REFRESH_TOKEN_EXPIRE_DAYS=7 # Longer-lived refresh tokens + ``` + 3. **Install dependencies** (using uv - recommended): ```bash - # uv will make .venv automatically + # Install dependencies and create virtual environment uv sync + + # Activate virtual environment + # Linux/macOS: + source .venv/bin/activate + # Windows (Command Prompt): + .venv\Scripts\activate + # Windows (PowerShell): + .\.venv\Scripts\Activate.ps1 ``` -2. **Install new dependencies with uv**: + 4. **Set up the database**: ```bash - uv add dependency_name - ``` -3. **Run server**: - - ```bash - uv run uvicorn app.main:app --reload - ``` - OR if you have make available - ``` - make start + # Run migrations + uv run alembic upgrade head + + # Or using make (if available) + make alembic-upgrade ``` -#### (Not recommended) - -1. **Create a virtual environment**: - + 5. **Set up pre-commit hooks**: ```bash - # Using standard venv - python -m venv venv - source venv/bin/activate # On Windows: venv\Scripts\activate - - # OR using uv (faster) - uv venv - source .venv/bin/activate # On Windows: .venv\Scripts\activate + # Linux/macOS/Windows (Git Bash) + cp misc/pre-commit .git/hooks/pre-commit + chmod +x .git/hooks/pre-commit + + # Windows (Command Prompt) + copy /Y misc\pre-commit .git\hooks\pre-commit ``` -2. **Install dependencies**: - - ```bash - # Using pip - pip install -r requirements.txt - - # OR using uv (faster) - uv pip install -r requirements.txt - ``` - - -#### Set up the database: - - Create your PostgreSQL database and run migrations: + 6. **Start the development server**: + ```bash + # Using uvicorn directly + uv run uvicorn app.main:app --reload + + # Or using make (if available) + make start + ``` + The API will be available at `http://localhost:8000` and interactive docs at `http://localhost:8000/docs` - ```bash - # Make sure you've set the correct DATABASE_URL in your .env file - alembic upgrade head - ``` - OR if you have make available - ``` - make alembic-upgrade - ``` ### Database Migrations Guide