Skip to content
Open
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
4 changes: 3 additions & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = false

[*.{py,toml,lock}]
[{*.{py,toml,lock,md},Dockerfile}]
indent_size = 4

[*.py]
insert_final_newline = true

[*.md]
Expand Down
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ POSTGRES_DB='template'
# Api
DEBUG=False
API_PORT=8000
ORIGINS='http://localhost:3000,https://google.com'

# Jwt
TOKEN_TYPE='Bearer'
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ COPY . .

EXPOSE 8000

CMD [ "uv", "run", "--no-dev", "fastapi", "run", "--host", "0.0.0.0", "--port", "8000" ]
CMD [ "uv", "run", "--no-dev", "fastapi", "run", "--host", "0.0.0.0", "--port", "8000" ]
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Setup

1. Install dependencies.
```bash
uv sync
Expand All @@ -20,7 +21,9 @@
```

# Launch

### Docker

* Development mode (fast-refresh)
```bash
docker compose watch
Expand All @@ -32,6 +35,7 @@
```

### Pure python

* Development mode (fast-refresh)
```bash
uv run fastapi dev
Expand All @@ -40,4 +44,4 @@
* Production mode
```bash
uv run --no-dev fastapi run
```
```
14 changes: 3 additions & 11 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@


@asynccontextmanager
async def lifespan(app: FastAPI) -> AsyncGenerator[None, Any]: # noqa: ARG001
async def lifespan(_: FastAPI) -> AsyncGenerator[None, Any]:
await create_tables()
yield
await engine.dispose()


app = FastAPI(
title=f'Template {"dev" if settings.DEBUG else "prod"} API',
swagger_ui_parameters=swagger_ui_parameters,
debug=settings.DEBUG,
lifespan=lifespan,
Expand All @@ -39,18 +40,9 @@ async def lifespan(app: FastAPI) -> AsyncGenerator[None, Any]: # noqa: ARG001
app.include_router(checks_router)
app.include_router(users_router)

origins = [
'http://localhost',
'https://localhost',
'http://127.0.0.1',
'https://127.0.0.1',
'http://0.0.0.0',
'https://0.0.0.0',
]

app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_origins=settings.origins,
allow_credentials=True,
allow_methods=['*'],
allow_headers=['*'],
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ license-files = ["LICENSE.md"]
requires-python = ">=3.13"
dependencies = [
"asyncpg>=0.30.0",
"fastapi[standard]>=0.115.13",
"fastapi[standard]>=0.115.14",
"passlib[bcrypt]>=1.7.4",
"pydantic>=2.11.7",
"pydantic-settings>=2.10.1",
Expand All @@ -19,5 +19,5 @@ dependencies = [
[dependency-groups]
dev = [
"pre-commit>=4.2.0",
"ruff>=0.12.0",
"ruff>=0.12.1",
]
4 changes: 2 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ cryptography==44.0.2
distlib==0.3.9
dnspython==2.7.0
email-validator==2.2.0
fastapi==0.115.13
fastapi==0.115.14
fastapi-cli==0.0.7
filelock==3.18.0
greenlet==3.1.1
Expand Down Expand Up @@ -40,7 +40,7 @@ python-multipart==0.0.20
pyyaml==6.0.2
rich==13.9.4
rich-toolkit==0.13.2
ruff==0.12.0
ruff==0.12.1
shellingham==1.5.4
sniffio==1.3.1
sqlalchemy==2.0.41
Expand Down
7 changes: 4 additions & 3 deletions src/services/users.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from fastapi import HTTPException, status
from pydantic import EmailStr
from sqlalchemy.future import select
from sqlalchemy.sql import exists

Expand Down Expand Up @@ -40,9 +41,9 @@ async def create_user(data: AddUser) -> UserId:
return response

@staticmethod
async def validate_auth_user(email: str, password: str) -> UserInfo | None:
async def validate_auth_user(email: EmailStr, password: str) -> UserInfo | None:
async with async_session() as session:
query = select(UserModel).where(UserModel.email == email)
query = select(UserModel).where(UserModel.email == str(email))
result = await session.execute(query)
user = result.scalars().first()
if not user:
Expand All @@ -52,7 +53,7 @@ async def validate_auth_user(email: str, password: str) -> UserInfo | None:
return user

@staticmethod
async def auth_user_with_jwt(email: str, password: str) -> Token:
async def auth_user_with_jwt(email: EmailStr, password: str) -> Token:
user = await UsersService.validate_auth_user(email, password)
payload = {
'sub': str(user.id),
Expand Down
9 changes: 7 additions & 2 deletions src/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ class Settings(BaseSettings):
POSTGRES_DB: str

# Api
DEBUG: bool
API_PORT: int
DEBUG: bool = False
API_PORT: int = 8000
ORIGINS: str = '*'

# Jwt
TOKEN_TYPE: str
Expand All @@ -26,6 +27,10 @@ class Settings(BaseSettings):
JWT_PRIVATE: str = jwt_private_path.read_text()
JWT_PUBLIC: str = jwt_public_path.read_text()

@property
def origins(self) -> list[str]:
return self.ORIGINS.split(',')

@property
def db_url(self) -> str:
return f'postgresql+asyncpg://{self.POSTGRES_USER}:{self.POSTGRES_PASSWORD}@{self.POSTGRES_HOST}:{self.POSTGRES_PORT}/{self.POSTGRES_DB}'
Expand Down
52 changes: 26 additions & 26 deletions uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.