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
17 changes: 17 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Normalize text files to LF on checkout/checkin
* text eol=lf

# Binary files — prevent line ending conversion
*.png binary
*.jpg binary
*.jpeg binary
*.ico binary

# Extra explicit rules for common text file types
*.py text eol=lf
*.toml text eol=lf
*.yaml text eol=lf
*.yml text eol=lf
*.json text eol=lf
*.md text eol=lf
*.lock text eol=lf
35 changes: 35 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: Lint

on:
pull_request:
branches: [master]

jobs:
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Python with uv
uses: astral-sh/setup-uv@v5
with:
python-version: "3.12"

- name: Install dependencies
run: uv sync

- name: ruff format check
run: uv run ruff format --check app/

- name: ruff lint
run: uv run ruff check app/

- name: ty type check
run: uv run ty check

- name: codespell
run: uv run python -m codespell_lib app/

- name: bandit security scan
run: uv run bandit -q -c pyproject.toml -r app/
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@

**/app.log
*.pyc
.ruff_cache/
47 changes: 47 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
default_install_hook_types:
- pre-commit

repos:
- repo: local
hooks:
- id: uv-lock-check
name: uv lock check
entry: uv lock --check
language: system
files: ^(pyproject\.toml|uv\.lock)$
pass_filenames: false

- id: ruff-format
name: ruff format
entry: uv run ruff format
language: system
types: [python]
files: ^app/

- id: ruff-check
name: ruff lint
entry: uv run ruff check --fix
language: system
types: [python]
files: ^app/

- id: ty-check
name: ty type checker
entry: uv run ty check
language: system
types: [python]
files: ^app/
pass_filenames: false

- id: codespell
name: codespell
entry: uv run python -m codespell_lib
language: system
types_or: [python, markdown, yaml, rst]

- id: bandit
name: bandit security scan
entry: uv run bandit -q -c pyproject.toml
language: system
types: [python]
files: ^app/
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.12
47 changes: 31 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,32 @@ Quick implementation of a database connector with both REST endpoints and GraphQ

---

## Requirements

| Tool | Version |
|--------|-----------------------------------------------|
| Python | >= 3.12 |
| uv | latest |
| just | latest (optional, for the `justfile` recipes) |

## Initialization

1. Clone the repository:
```bash
git clone https://github.com/Cybernetic-Ransomware/template_GraphQL_SQLAlchemy_FastAPI.git
```
2. Install Python >= 3.12.
3. Create a virtual environment:
2. Install [uv](https://docs.astral.sh/uv/getting-started/installation/).
3. Install dependencies (creates `.venv` and installs runtime + dev dependencies):
```bash
python -m venv .venv
uv sync
```
4. Activate the virtual environment:
- On Windows:
```bash
.venv\Scripts\activate
```
- On macOS and Linux:
```bash
source .venv/bin/activate
```
5. Install dependencies from `requirements.txt`:
4. Run the application:
```bash
pip install -r requirements.txt
uv run uvicorn app.main:app --reload --port 8080
```
6. Run the application:
or, with [just](https://github.com/casey/just):
```bash
uvicorn app.main:app --reload --port 8080
just run
```

---
Expand All @@ -42,6 +41,22 @@ Quick implementation of a database connector with both REST endpoints and GraphQ

---

## Development

Set up the pre-commit hooks once after cloning:
```bash
uv run pre-commit install
```

Common tasks (see `justfile` for the full list):
```bash
just format # ruff format
just lint # ruff format + lint, ty type-check, codespell, bandit
just commit # run pre-commit, then open Commitizen for a conventional commit
```

---

## Useful links and documentation

- SQLAlchemy: [https://docs.sqlalchemy.org/en/20/](https://docs.sqlalchemy.org/en/20/)
Expand Down
1 change: 1 addition & 0 deletions app/connector.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from sqlalchemy.future import select
from sqlalchemy.orm import Session

from app.models import Item
from app.schemas import ItemCreate

Expand Down
1 change: 0 additions & 1 deletion app/db/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker


DATABASE_URL = "sqlite:///./test.db"


Expand Down
6 changes: 2 additions & 4 deletions app/graphql/context.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
from fastapi import Depends

from sqlalchemy.orm import Session

from app.db.database import get_db


async def get_context(db: Session = Depends(get_db)):
return {
"db": db
}
return {"db": db}
9 changes: 3 additions & 6 deletions app/graphql/resolvers.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import asyncio
import strawberry

from typing import AsyncGenerator
from collections.abc import AsyncGenerator

import strawberry
from sqlalchemy.orm import Session
from strawberry.types import Info

from app.connector import create_item, read_items, read_item
from app.connector import create_item, read_item, read_items
from app.schemas import ItemCreate
from app.models import Item


@strawberry.type
Expand All @@ -19,7 +17,6 @@ class ItemType:

@strawberry.type
class Query:

@strawberry.field
def hello(self) -> str:
return "Hello World"
Expand Down
3 changes: 1 addition & 2 deletions app/graphql/schema.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import strawberry

from app.graphql.resolvers import Query, Mutation, Subscription

from app.graphql.resolvers import Mutation, Query, Subscription

schema = strawberry.Schema(query=Query, mutation=Mutation, subscription=Subscription)
12 changes: 6 additions & 6 deletions app/logger/conf_log.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@

def setup_logger():
logging.basicConfig(
filename='app.log', # Plik, do którego będą zapisywane logi
filemode='a', # Tryb otwierania pliku (a - append)
format='%(asctime)s - %(levelname)s - %(message)s', # Format logów
datefmt='%Y-%m-%d %H:%M:%S', # Format daty i czasu
level=logging.INFO # Poziom logowania
filename="app.log", # File to which logs will be written
filemode="a", # File open mode (a - append)
format="%(asctime)s - %(levelname)s - %(message)s", # Log format
datefmt="%Y-%m-%d %H:%M:%S", # Date and time format
level=logging.INFO, # Logging level
)

console_handler = logging.StreamHandler()
console_handler.setLevel(logging.ERROR)
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
formatter = logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
console_handler.setFormatter(formatter)

logger = logging.getLogger()
Expand Down
12 changes: 5 additions & 7 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
from fastapi import FastAPI, Depends, HTTPException
from fastapi import Depends, FastAPI, HTTPException
from sqlalchemy.orm import Session
from strawberry.fastapi import GraphQLRouter
from strawberry.subscriptions import GRAPHQL_TRANSPORT_WS_PROTOCOL, GRAPHQL_WS_PROTOCOL
from sqlalchemy.orm import Session


from app.connector import create_item, read_items, read_item
from app.db.database import engine, Base, get_db
from app.connector import create_item, read_item, read_items
from app.db.database import Base, engine, get_db
from app.graphql.context import get_context
from app.graphql.schema import schema
from app.logger.conf_log import setup_logger, get_logger
from app.logger.conf_log import get_logger, setup_logger
from app.schemas import ItemCreate, ItemResponse


setup_logger()
logger = get_logger(__name__)

Expand Down
1 change: 1 addition & 0 deletions app/models.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from sqlalchemy import Column, Integer, String

from app.db.database import Base


Expand Down
30 changes: 30 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Install/sync all dependencies (runtime + dev)
install:
uv sync

# Apply ruff formatting to app source
format:
uv run ruff format app/

# Run the full linting suite: format, lint, type-check, spell-check, security scan
lint:
uv run ruff format app/
uv run ruff check --fix app/
uv run ty check
uv run python -m codespell_lib app/
uv run bandit -q -c pyproject.toml -r app/

# Run the app locally with auto-reload
# Swagger UI: http://127.0.0.1:8080/docs
# GraphQL Playground: http://127.0.0.1:8080/graphql
run:
uv run uvicorn app.main:app --reload --port 8080

# Run pre-commit on staged files, then open Commitizen
commit:
uv run pre-commit run
uv run cz commit

# Bump version (auto-tags vX.Y.Z, updates pyproject.toml)
bump:
uv run cz bump
64 changes: 64 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
[project]
name = "template-graphql-sqlalchemy-fastapi"
version = "0.1.0"
description = "Template Project: GraphQL (Strawberry) + SQLAlchemy + FastAPI"
requires-python = ">=3.12"
dependencies = [
"fastapi[standard]>=0.138.2",
"pydantic>=2.13.4",
"sqlalchemy>=2.0.51",
"strawberry-graphql>=0.320.0",
]

[dependency-groups]
dev = [
"bandit>=1.9.4",
"codespell>=2.4.2",
"commitizen>=4.16.4",
"pre-commit>=4.6.0",
"ruff>=0.15.20",
"ty>=0.0.55",
]

[tool.uv]
package = false

[tool.ruff]
line-length = 124

[tool.ruff.lint]
select = ["E", "F", "UP", "B", "SIM", "I", "RUF"]
ignore = []

[tool.ruff.lint.flake8-bugbear]
extend-immutable-calls = [
"fastapi.Depends",
"fastapi.params.Depends",
"fastapi.Query",
"fastapi.params.Query",
]

[tool.ruff.format]
quote-style = "double"
indent-style = "space"
docstring-code-format = true
docstring-code-line-length = 124

[tool.ty.environment]
python-version = "3.12"

[tool.codespell]
skip = "uv.lock,./.git,./.github"
builtin = "clear"
quiet-level = 3

[tool.bandit]
targets = ["app"]

[tool.commitizen]
name = "cz_conventional_commits"
version = "0.1.0"
tag_format = "v$version"
version_files = [
"pyproject.toml:version",
]
Binary file removed requirements.txt
Binary file not shown.
Loading
Loading