fastapi-auth-starter 0.1.3__py3-none-any.whl

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 (44) hide show
  1. fastapi_auth_starter/__init__.py +7 -0
  2. fastapi_auth_starter/cli.py +326 -0
  3. fastapi_auth_starter-0.1.3.data/data/README.md +247 -0
  4. fastapi_auth_starter-0.1.3.data/data/alembic/README +1 -0
  5. fastapi_auth_starter-0.1.3.data/data/alembic/env.py +100 -0
  6. fastapi_auth_starter-0.1.3.data/data/alembic/script.py.mako +28 -0
  7. fastapi_auth_starter-0.1.3.data/data/alembic/versions/279c472f4fd8_add_user_table.py +42 -0
  8. fastapi_auth_starter-0.1.3.data/data/alembic/versions/5f062b3648fa_change_user_id_from_uuid_to_string_for_.py +38 -0
  9. fastapi_auth_starter-0.1.3.data/data/alembic/versions/8d275132562b_create_tasks_table.py +44 -0
  10. fastapi_auth_starter-0.1.3.data/data/alembic.ini +150 -0
  11. fastapi_auth_starter-0.1.3.data/data/app/__init__.py +5 -0
  12. fastapi_auth_starter-0.1.3.data/data/app/api/__init__.py +4 -0
  13. fastapi_auth_starter-0.1.3.data/data/app/api/v1/__init__.py +4 -0
  14. fastapi_auth_starter-0.1.3.data/data/app/api/v1/api.py +21 -0
  15. fastapi_auth_starter-0.1.3.data/data/app/api/v1/routes/__init__.py +4 -0
  16. fastapi_auth_starter-0.1.3.data/data/app/api/v1/routes/auth.py +513 -0
  17. fastapi_auth_starter-0.1.3.data/data/app/api/v1/routes/health.py +50 -0
  18. fastapi_auth_starter-0.1.3.data/data/app/api/v1/routes/task.py +182 -0
  19. fastapi_auth_starter-0.1.3.data/data/app/api/v1/routes/user.py +144 -0
  20. fastapi_auth_starter-0.1.3.data/data/app/api/v1/schemas/__init__.py +8 -0
  21. fastapi_auth_starter-0.1.3.data/data/app/api/v1/schemas/auth.py +198 -0
  22. fastapi_auth_starter-0.1.3.data/data/app/api/v1/schemas/task.py +61 -0
  23. fastapi_auth_starter-0.1.3.data/data/app/api/v1/schemas/user.py +96 -0
  24. fastapi_auth_starter-0.1.3.data/data/app/core/__init__.py +4 -0
  25. fastapi_auth_starter-0.1.3.data/data/app/core/config.py +107 -0
  26. fastapi_auth_starter-0.1.3.data/data/app/core/database.py +106 -0
  27. fastapi_auth_starter-0.1.3.data/data/app/core/dependencies.py +148 -0
  28. fastapi_auth_starter-0.1.3.data/data/app/core/exceptions.py +7 -0
  29. fastapi_auth_starter-0.1.3.data/data/app/db/__init__.py +4 -0
  30. fastapi_auth_starter-0.1.3.data/data/app/main.py +91 -0
  31. fastapi_auth_starter-0.1.3.data/data/app/models/__init__.py +14 -0
  32. fastapi_auth_starter-0.1.3.data/data/app/models/task.py +56 -0
  33. fastapi_auth_starter-0.1.3.data/data/app/models/user.py +45 -0
  34. fastapi_auth_starter-0.1.3.data/data/app/services/__init__.py +8 -0
  35. fastapi_auth_starter-0.1.3.data/data/app/services/auth.py +405 -0
  36. fastapi_auth_starter-0.1.3.data/data/app/services/task.py +165 -0
  37. fastapi_auth_starter-0.1.3.data/data/app/services/user.py +108 -0
  38. fastapi_auth_starter-0.1.3.data/data/pyproject.toml +77 -0
  39. fastapi_auth_starter-0.1.3.data/data/runtime.txt +2 -0
  40. fastapi_auth_starter-0.1.3.data/data/vercel.json +19 -0
  41. fastapi_auth_starter-0.1.3.dist-info/METADATA +283 -0
  42. fastapi_auth_starter-0.1.3.dist-info/RECORD +44 -0
  43. fastapi_auth_starter-0.1.3.dist-info/WHEEL +4 -0
  44. fastapi_auth_starter-0.1.3.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,7 @@
1
+ """
2
+ FastAPI Auth Starter Package
3
+ A reusable FastAPI starter template with authentication
4
+ """
5
+
6
+ __version__ = "0.1.3"
7
+
@@ -0,0 +1,326 @@
1
+ """
2
+ CLI tool for scaffolding new FastAPI projects from this starter template
3
+ Reference: https://docs.python.org/3/library/argparse.html
4
+ """
5
+ import argparse
6
+ import shutil
7
+ import sys
8
+ from pathlib import Path
9
+
10
+
11
+ def get_template_files() -> list[Path]:
12
+ """
13
+ Get list of template files to copy when scaffolding a new project.
14
+
15
+ Returns paths relative to the package root that should be included.
16
+ """
17
+ # Files and directories to include in the template
18
+ # These are relative to the project root
19
+ # Note: fastapi_auth_starter/ is excluded - it's only for the package itself
20
+ template_files = [
21
+ "app",
22
+ "alembic",
23
+ "alembic.ini",
24
+ "pyproject.toml",
25
+ "README.md",
26
+ "runtime.txt",
27
+ "vercel.json",
28
+ ".gitignore", # Include .gitignore if it exists
29
+ ]
30
+
31
+ # Convert to Path objects
32
+ return [Path(f) for f in template_files]
33
+
34
+
35
+ def copy_template_files(source_dir: Path, dest_dir: Path, project_name: str) -> None:
36
+ """
37
+ Copy template files from source to destination, customizing as needed.
38
+
39
+ Args:
40
+ source_dir: Source directory containing template files
41
+ dest_dir: Destination directory for new project
42
+ project_name: Name of the new project (for customization)
43
+ """
44
+ template_files = get_template_files()
45
+
46
+ # Create destination directory
47
+ dest_dir.mkdir(parents=True, exist_ok=True)
48
+
49
+ # Copy each file/directory
50
+ for item in template_files:
51
+ source_path = source_dir / item
52
+ dest_path = dest_dir / item
53
+
54
+ if not source_path.exists():
55
+ # Skip silently for optional files like .gitignore
56
+ if item != ".gitignore":
57
+ print(f"Warning: Template file {item} not found, skipping...")
58
+ continue
59
+
60
+ # Explicitly exclude the package directory
61
+ if item == "fastapi_auth_starter":
62
+ continue
63
+
64
+ if source_path.is_dir():
65
+ # Copy directory recursively, excluding __pycache__ and .pyc files
66
+ shutil.copytree(
67
+ source_path,
68
+ dest_path,
69
+ dirs_exist_ok=True,
70
+ ignore=shutil.ignore_patterns("__pycache__", "*.pyc", "*.pyo")
71
+ )
72
+ else:
73
+ # Copy file
74
+ shutil.copy2(source_path, dest_path)
75
+
76
+ # Create a clean pyproject.toml for standalone application
77
+ pyproject_path = dest_dir / "pyproject.toml"
78
+ if pyproject_path.exists():
79
+ # Read original to extract dependencies
80
+ original_content = pyproject_path.read_text()
81
+
82
+ # Extract dependencies from original
83
+ import re
84
+ deps_match = re.search(r'dependencies = \[(.*?)\]', original_content, re.DOTALL)
85
+ dev_deps_match = re.search(r'\[project\.optional-dependencies\]\s+dev = \[(.*?)\]', original_content, re.DOTALL)
86
+
87
+ dependencies = deps_match.group(1).strip() if deps_match else ""
88
+ dev_dependencies = dev_deps_match.group(1).strip() if dev_deps_match else ""
89
+
90
+ # Sanitize project name
91
+ sanitized_name = project_name.lower().replace(" ", "_").replace("-", "_")
92
+
93
+ # Create clean pyproject.toml for standalone application
94
+ clean_content = f"""[project]
95
+ name = "{sanitized_name}"
96
+ version = "0.1.0"
97
+ description = "FastAPI application"
98
+ readme = "README.md"
99
+ requires-python = ">=3.12"
100
+ dependencies = [
101
+ {dependencies}
102
+ ]
103
+
104
+ # Development dependencies
105
+ [project.optional-dependencies]
106
+ dev = [
107
+ {dev_dependencies}
108
+ ]
109
+ """
110
+ pyproject_path.write_text(clean_content)
111
+
112
+ print(f"✓ Created new FastAPI project: {dest_dir}")
113
+
114
+
115
+ def find_package_root() -> Path:
116
+ """
117
+ Find the root directory containing template files.
118
+
119
+ When installed as a package, template files are in the package's shared-data.
120
+ In development mode, they're in the project root.
121
+ """
122
+ try:
123
+ # First, try development mode (current file's parent's parent)
124
+ package_file = Path(__file__).resolve()
125
+ dev_root = package_file.parent.parent
126
+
127
+ # Check if we're in development mode
128
+ if (dev_root / "app").exists() and (dev_root / "alembic").exists():
129
+ return dev_root
130
+
131
+ # Debug: Print where we're looking
132
+ print(f"Debug: Package file location: {package_file}", file=sys.stderr)
133
+ print(f"Debug: Package directory: {package_file.parent}", file=sys.stderr)
134
+ print(f"Debug: Package parent (site-packages): {package_file.parent.parent}", file=sys.stderr)
135
+
136
+ # If installed as a package, template files are in shared-data
137
+ # Hatchling places shared-data files in the package's installation directory
138
+ # We need to find where the package is installed and look for shared-data
139
+ try:
140
+ import site
141
+ import sysconfig
142
+
143
+ # Get all possible site-packages locations
144
+ site_dirs = site.getsitepackages()
145
+ if hasattr(site, 'getsitepackages'):
146
+ # Also check user site-packages
147
+ try:
148
+ user_site = site.getusersitepackages()
149
+ if user_site:
150
+ site_dirs.append(user_site)
151
+ except AttributeError:
152
+ pass
153
+
154
+ # Also check where this package is actually installed
155
+ # The package is at fastapi_auth_starter/__init__.py, so parent is fastapi_auth_starter/
156
+ package_location = Path(__file__).parent
157
+ package_parent = package_location.parent # This is site-packages or similar
158
+
159
+ print(f"Debug: Checking package_parent: {package_parent}", file=sys.stderr)
160
+ print(f"Debug: app exists? {(package_parent / 'app').exists()}", file=sys.stderr)
161
+ print(f"Debug: alembic exists? {(package_parent / 'alembic').exists()}", file=sys.stderr)
162
+
163
+ # List what's actually in site-packages for debugging
164
+ if package_parent.exists():
165
+ print(f"Debug: Contents of {package_parent}:", file=sys.stderr)
166
+ try:
167
+ for item in sorted(package_parent.iterdir())[:30]: # First 30 items
168
+ item_type = "DIR" if item.is_dir() else "FILE"
169
+ print(f"Debug: {item_type}: {item.name}", file=sys.stderr)
170
+ # If we find app or alembic, note it
171
+ if item.name in ["app", "alembic"]:
172
+ print(f"Debug: *** FOUND {item.name} at {item} ***", file=sys.stderr)
173
+ except Exception as e:
174
+ print(f"Debug: Error listing directory: {e}", file=sys.stderr)
175
+
176
+ # Try to find app/alembic by searching recursively (limited depth)
177
+ if package_parent.exists():
178
+ print(f"Debug: Searching for app/ and alembic/ directories...", file=sys.stderr)
179
+ for item in package_parent.iterdir():
180
+ if item.is_dir() and item.name in ["app", "alembic"]:
181
+ # Found one, check if the other exists nearby
182
+ other_name = "alembic" if item.name == "app" else "app"
183
+ other_path = item.parent / other_name
184
+ if other_path.exists():
185
+ print(f"Debug: *** FOUND BOTH at {item.parent} ***", file=sys.stderr)
186
+ return item.parent
187
+
188
+ # Hatchling shared-data places files at the site-packages level
189
+ # So if package is at site-packages/fastapi_auth_starter/
190
+ # Shared data is at site-packages/app/, site-packages/alembic/, etc.
191
+ if package_parent.exists():
192
+ # Check if shared-data is at the site-packages level
193
+ if (package_parent / "app").exists() and (package_parent / "alembic").exists():
194
+ print(f"Debug: Found template files at site-packages level: {package_parent}", file=sys.stderr)
195
+ return package_parent
196
+
197
+ # Also check parent of site-packages (unlikely but possible)
198
+ if package_parent.parent.exists():
199
+ if (package_parent.parent / "app").exists() and (package_parent.parent / "alembic").exists():
200
+ print(f"Debug: Found template files at parent level: {package_parent.parent}", file=sys.stderr)
201
+ return package_parent.parent
202
+
203
+ # Check standard site-packages locations
204
+ for site_dir in site_dirs:
205
+ site_path = Path(site_dir)
206
+
207
+ # Check if shared-data is in site-packages root
208
+ if (site_path / "app").exists() and (site_path / "alembic").exists():
209
+ return site_path
210
+
211
+ # Check in package directory
212
+ package_dir = site_path / "fastapi_auth_starter"
213
+ if package_dir.exists():
214
+ # Check parent (shared-data might be at site-packages level)
215
+ if (site_path / "app").exists():
216
+ return site_path
217
+ # Or in package directory itself
218
+ if (package_dir / "app").exists():
219
+ return package_dir
220
+
221
+ # Check parent of package (hatchling shared-data location)
222
+ package_parent = package_dir.parent
223
+ if (package_parent / "app").exists():
224
+ return package_parent
225
+
226
+ except Exception as e:
227
+ # Log error but continue to fallback
228
+ print(f"Warning: Error checking site-packages: {e}", file=sys.stderr)
229
+
230
+ # Fallback: use development root
231
+ # This handles editable installs and other scenarios
232
+ return dev_root
233
+
234
+ except Exception as e:
235
+ print(f"Error finding package root: {e}", file=sys.stderr)
236
+ # Final fallback: use current file's parent's parent
237
+ return Path(__file__).resolve().parent.parent
238
+
239
+
240
+ def init_project(project_name: str, target_dir: Path | None = None) -> None:
241
+ """
242
+ Initialize a new FastAPI project from this starter template.
243
+
244
+ Args:
245
+ project_name: Name of the new project
246
+ target_dir: Target directory (defaults to current directory/project_name)
247
+ """
248
+ if target_dir is None:
249
+ target_dir = Path.cwd() / project_name
250
+
251
+ # Check if target directory already exists
252
+ if target_dir.exists():
253
+ print(f"Error: Directory {target_dir} already exists!")
254
+ sys.exit(1)
255
+
256
+ # Find the template source directory
257
+ source_dir = find_package_root()
258
+
259
+ if not source_dir.exists():
260
+ print(f"Error: Could not find template source directory!")
261
+ sys.exit(1)
262
+
263
+ print(f"Creating new FastAPI project '{project_name}' from template...")
264
+ print(f"Source: {source_dir}")
265
+ print(f"Destination: {target_dir}")
266
+
267
+ # Debug: Verify source directory has required files
268
+ if not (source_dir / "app").exists():
269
+ print(f"Warning: 'app' directory not found at {source_dir / 'app'}", file=sys.stderr)
270
+ if not (source_dir / "alembic").exists():
271
+ print(f"Warning: 'alembic' directory not found at {source_dir / 'alembic'}", file=sys.stderr)
272
+
273
+ # Copy template files
274
+ copy_template_files(source_dir, target_dir, project_name)
275
+
276
+ print("\n✓ Project created successfully!")
277
+ print(f"\nNext steps:")
278
+ print(f" 1. cd {target_dir}")
279
+ print(f" 2. Create a .env file with your configuration")
280
+ print(f" 3. Run: uv sync")
281
+ print(f" 4. Run: uv run alembic upgrade head")
282
+ print(f" 5. Run: uv run uvicorn app.main:app --reload")
283
+
284
+
285
+ def main() -> None:
286
+ """
287
+ Main CLI entry point.
288
+ Handles command-line arguments and dispatches to appropriate functions.
289
+ """
290
+ parser = argparse.ArgumentParser(
291
+ description="FastAPI Auth Starter - Scaffold new FastAPI projects",
292
+ formatter_class=argparse.RawDescriptionHelpFormatter,
293
+ epilog="""
294
+ Examples:
295
+ fastapi-auth-starter init my-api
296
+ fastapi-auth-starter init my-api --dir /path/to/projects
297
+ """
298
+ )
299
+
300
+ subparsers = parser.add_subparsers(dest="command", help="Available commands")
301
+
302
+ # Init command
303
+ init_parser = subparsers.add_parser("init", help="Initialize a new FastAPI project")
304
+ init_parser.add_argument(
305
+ "project_name",
306
+ help="Name of the new project"
307
+ )
308
+ init_parser.add_argument(
309
+ "--dir",
310
+ type=Path,
311
+ help="Target directory (defaults to current directory/project_name)"
312
+ )
313
+
314
+ args = parser.parse_args()
315
+
316
+ if args.command == "init":
317
+ target_dir = args.dir / args.project_name if args.dir else None
318
+ init_project(args.project_name, target_dir)
319
+ else:
320
+ parser.print_help()
321
+ sys.exit(1)
322
+
323
+
324
+ if __name__ == "__main__":
325
+ main()
326
+
@@ -0,0 +1,247 @@
1
+ # FastAPI Auth Starter
2
+
3
+ A clean architecture FastAPI project starter with PostgreSQL and Alembic migrations.
4
+
5
+ ## Project Structure
6
+
7
+ ```
8
+ fastapi_auth_starter/
9
+ ├── app/
10
+ │ ├── __init__.py
11
+ │ ├── main.py # FastAPI application entry point
12
+ │ ├── api/
13
+ │ │ └── v1/
14
+ │ │ ├── api.py # API router aggregation
15
+ │ │ └── routes/
16
+ │ │ └── health.py # Health check endpoint
17
+ │ ├── core/
18
+ │ │ ├── config.py # Application configuration
19
+ │ │ └── database.py # Database connection and session management
20
+ │ ├── models/ # SQLAlchemy database models
21
+ │ ├── services/ # Business logic services
22
+ │ └── db/ # Database utilities
23
+ ├── alembic/ # Database migration scripts
24
+ ├── alembic.ini # Alembic configuration
25
+ ├── pyproject.toml # Project dependencies (uv)
26
+ └── README.md
27
+ ```
28
+
29
+ ## Features
30
+
31
+ - ✅ Clean architecture with separation of concerns
32
+ - ✅ FastAPI with async SQLAlchemy
33
+ - ✅ PostgreSQL database support
34
+ - ✅ Alembic for database migrations
35
+ - ✅ Health check endpoint
36
+ - ✅ Dependency injection with FastAPI
37
+ - ✅ Environment-based configuration
38
+
39
+ ## Prerequisites
40
+
41
+ - Python 3.12+ (3.13 for local dev, 3.12 for Vercel deployment)
42
+ - [uv](https://github.com/astral-sh/uv) package manager
43
+ - PostgreSQL database (local or remote)
44
+
45
+ ## Setup
46
+
47
+ ### 1. Install Dependencies
48
+
49
+ Dependencies are managed with `uv`. They are automatically installed when you run commands with `uv run`.
50
+
51
+ ### 2. Configure Environment Variables
52
+
53
+ **Important:** Copy the example environment file and configure your settings:
54
+
55
+ ```bash
56
+ cp .env.example .env
57
+ ```
58
+
59
+ Then edit `.env` with your configuration:
60
+
61
+ ```bash
62
+ # Database Configuration
63
+ DATABASE_URL=postgresql+asyncpg://user:password@localhost:5432/fastapi_auth
64
+
65
+ # API Configuration (optional - defaults are fine)
66
+ API_V1_PREFIX=/api/v1
67
+ PROJECT_NAME=FastAPI Auth Starter
68
+ VERSION=0.1.0
69
+ ```
70
+
71
+ **Note:**
72
+ - The `.env` file is gitignored and should not be committed
73
+ - The application uses `asyncpg` for async operations, but Alembic uses `psycopg2` for migrations (sync driver)
74
+ - All sensitive configuration should be in `.env` file, not hardcoded
75
+
76
+ ### 3. Initialize Database
77
+
78
+ First, ensure PostgreSQL is running and create the database:
79
+
80
+ ```bash
81
+ createdb fastapi_auth
82
+ ```
83
+
84
+ Or using PostgreSQL client:
85
+
86
+ ```sql
87
+ CREATE DATABASE fastapi_auth;
88
+ ```
89
+
90
+ ### 4. Run Migrations
91
+
92
+ ```bash
93
+ # Create initial migration (if needed)
94
+ uv run alembic revision --autogenerate -m "Initial migration"
95
+
96
+ # Apply migrations
97
+ uv run alembic upgrade head
98
+ ```
99
+
100
+ ## Running the Application
101
+
102
+ ### Development Server
103
+
104
+ ```bash
105
+ uv run uvicorn app.main:app --reload
106
+ ```
107
+
108
+ The API will be available at:
109
+ - API: http://localhost:8000
110
+ - Docs: http://localhost:8000/docs
111
+ - ReDoc: http://localhost:8000/redoc
112
+
113
+ ### Health Check
114
+
115
+ Test the health endpoint:
116
+
117
+ ```bash
118
+ curl http://localhost:8000/api/v1/health
119
+ ```
120
+
121
+ Expected response:
122
+ ```json
123
+ {
124
+ "status": "healthy",
125
+ "message": "Service is running"
126
+ }
127
+ ```
128
+
129
+ ## Development
130
+
131
+ ### Adding New Routes
132
+
133
+ 1. Create a new route file in `app/api/v1/routes/`
134
+ 2. Import and include the router in `app/api/v1/api.py`
135
+
136
+ Example:
137
+ ```python
138
+ # app/api/v1/routes/users.py
139
+ from fastapi import APIRouter
140
+
141
+ router = APIRouter(prefix="/users", tags=["users"])
142
+
143
+ @router.get("/")
144
+ async def get_users():
145
+ return {"users": []}
146
+ ```
147
+
148
+ Then add to `app/api/v1/api.py`:
149
+ ```python
150
+ from app.api.v1.routes import users
151
+ api_router.include_router(users.router)
152
+ ```
153
+
154
+ ### Adding Database Models
155
+
156
+ 1. Create model in `app/models/`
157
+ 2. Import in `app/models/__init__.py`
158
+ 3. Import in `alembic/env.py` (for autogenerate)
159
+
160
+ Example:
161
+ ```python
162
+ # app/models/user.py
163
+ from sqlalchemy import Column, Integer, String
164
+ from app.core.database import Base
165
+
166
+ class User(Base):
167
+ __tablename__ = "users"
168
+
169
+ id = Column(Integer, primary_key=True)
170
+ email = Column(String, unique=True, index=True)
171
+ ```
172
+
173
+ ### Creating Migrations
174
+
175
+ ```bash
176
+ # Auto-generate migration from model changes
177
+ uv run alembic revision --autogenerate -m "Description of changes"
178
+
179
+ # Create empty migration
180
+ uv run alembic revision -m "Description of changes"
181
+ ```
182
+
183
+ ### Applying Migrations
184
+
185
+ ```bash
186
+ # Apply all pending migrations
187
+ uv run alembic upgrade head
188
+
189
+ # Rollback one migration
190
+ uv run alembic downgrade -1
191
+
192
+ # Rollback to specific revision
193
+ uv run alembic downgrade <revision>
194
+ ```
195
+
196
+ ## Architecture
197
+
198
+ ### Layers
199
+
200
+ - **API Layer** (`app/api/`): Route handlers, request/response models
201
+ - **Service Layer** (`app/services/`): Business logic, domain operations
202
+ - **Model Layer** (`app/models/`): SQLAlchemy database models
203
+ - **Core Layer** (`app/core/`): Configuration, database setup, utilities
204
+
205
+ ### Dependency Injection
206
+
207
+ FastAPI's dependency injection is used throughout:
208
+ - Database sessions via `get_db()` dependency
209
+ - Configuration via `settings` object
210
+ - Custom dependencies in `app/core/dependencies.py` (create as needed)
211
+
212
+ ## Deployment
213
+
214
+ ### Vercel Deployment
215
+
216
+ 1. **Install Dev Dependencies Locally:**
217
+ ```bash
218
+ uv sync # Installs all dependencies including dev
219
+ ```
220
+
221
+ 2. **Set Environment Variables in Vercel:**
222
+ - Go to your project settings in Vercel
223
+ - Add `DATABASE_URL` environment variable with your PostgreSQL connection string
224
+ - Format: `postgresql+asyncpg://user:password@host:port/database`
225
+
226
+ 3. **Deploy:**
227
+ ```bash
228
+ vercel --prod
229
+ ```
230
+
231
+ **Note:**
232
+ - Runtime dependencies don't include `psycopg2-binary` or `alembic` (only needed for local migrations)
233
+ - Python 3.12 is used (Vercel doesn't support 3.13 yet)
234
+ - Make sure to run migrations on your database before deploying
235
+
236
+ ## References
237
+
238
+ - [FastAPI Documentation](https://fastapi.tiangolo.com/)
239
+ - [SQLAlchemy Async Documentation](https://docs.sqlalchemy.org/en/20/orm/extensions/asyncio.html)
240
+ - [Alembic Documentation](https://alembic.sqlalchemy.org/)
241
+ - [uv Documentation](https://github.com/astral-sh/uv)
242
+ - [Vercel Python Documentation](https://vercel.com/docs/functions/serverless-functions/runtimes/python)
243
+
244
+ ## License
245
+
246
+ MIT
247
+
@@ -0,0 +1 @@
1
+ Generic single-database configuration.
@@ -0,0 +1,100 @@
1
+ """
2
+ Alembic environment configuration
3
+ Handles database migrations with SQLAlchemy
4
+ Reference: https://alembic.sqlalchemy.org/en/latest/tutorial.html
5
+ """
6
+ from logging.config import fileConfig
7
+
8
+ from sqlalchemy import engine_from_config, pool
9
+
10
+ from alembic import context
11
+
12
+ # Import application settings and Base
13
+ from app.core.config import settings
14
+ from app.core.database import Base
15
+
16
+ # Import all models here so Alembic can detect them for autogenerate
17
+ # As models are added, import them here
18
+ from app.models import Task, User
19
+ # This is the Alembic Config object, which provides
20
+ # access to the values within the .ini file in use.
21
+ config = context.config
22
+
23
+ # Interpret the config file for Python logging.
24
+ # This line sets up loggers basically.
25
+ if config.config_file_name is not None:
26
+ fileConfig(config.config_file_name)
27
+
28
+ # Override sqlalchemy.url from settings if not set in alembic.ini
29
+ # Convert asyncpg URL to psycopg2 URL for Alembic (Alembic uses sync drivers)
30
+ # Reference: https://docs.sqlalchemy.org/en/20/core/engines.html#database-urls
31
+ if not config.get_main_option("sqlalchemy.url"):
32
+ # Convert asyncpg URL to psycopg2 URL (sync driver for migrations)
33
+ db_url = settings.DATABASE_URL.replace("postgresql+asyncpg://", "postgresql+psycopg2://")
34
+ # Escape % for ConfigParser (double % to prevent interpolation)
35
+ db_url = db_url.replace("%", "%%")
36
+ config.set_main_option("sqlalchemy.url", db_url)
37
+
38
+ # Set target_metadata for autogenerate support
39
+ # This tells Alembic what models exist in the application
40
+ target_metadata = Base.metadata
41
+
42
+
43
+ def run_migrations_offline() -> None:
44
+ """
45
+ Run migrations in 'offline' mode.
46
+
47
+ This configures the context with just a URL
48
+ and not an Engine, though an Engine is acceptable
49
+ here as well. By skipping the Engine creation
50
+ we don't even need a DBAPI to be available.
51
+
52
+ Calls to context.execute() here emit the given string to the
53
+ script output.
54
+ """
55
+ url = config.get_main_option("sqlalchemy.url")
56
+ context.configure(
57
+ url=url,
58
+ target_metadata=target_metadata,
59
+ literal_binds=True,
60
+ dialect_opts={"paramstyle": "named"},
61
+ )
62
+
63
+ with context.begin_transaction():
64
+ context.run_migrations()
65
+
66
+
67
+ def run_migrations_online() -> None:
68
+ """
69
+ Run migrations in 'online' mode.
70
+
71
+ In this scenario we need to create an Engine
72
+ and associate a connection with the context.
73
+
74
+ Reference: https://alembic.sqlalchemy.org/en/latest/tutorial.html#run-a-migration
75
+ """
76
+ # Create sync engine from configuration
77
+ # Alembic uses sync SQLAlchemy operations
78
+ # We use psycopg2 (sync) for migrations, asyncpg (async) for the app
79
+ connectable = engine_from_config(
80
+ config.get_section(config.config_ini_section, {}),
81
+ prefix="sqlalchemy.",
82
+ poolclass=pool.NullPool, # Don't pool connections for migrations
83
+ )
84
+
85
+ with connectable.connect() as connection:
86
+ context.configure(
87
+ connection=connection,
88
+ target_metadata=target_metadata,
89
+ compare_type=True, # Compare column types when autogenerating
90
+ compare_server_default=True, # Compare server defaults
91
+ )
92
+
93
+ with context.begin_transaction():
94
+ context.run_migrations()
95
+
96
+
97
+ if context.is_offline_mode():
98
+ run_migrations_offline()
99
+ else:
100
+ run_migrations_online()