Skip to content

Commit

Permalink
back_up: trying to connect to db with ORM
Browse files Browse the repository at this point in the history
  • Loading branch information
AtticusZeller committed Nov 8, 2024
1 parent 3e113f2 commit 55be9c7
Show file tree
Hide file tree
Showing 16 changed files with 701 additions and 322 deletions.
302 changes: 265 additions & 37 deletions .vscode/PythonImportHelper-v2-Completion.json

Large diffs are not rendered by default.

130 changes: 19 additions & 111 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,51 +32,21 @@ ___

___

### FastAPI&supabase

1. works of authorization all handled by supabase-py and fastapi **dependency** without any extra code
2. supabase-py crud integration with **pydantic** model validation

### Pytest

1. pytest integration with **pytest-cov**
2. pytest **fixtures** for fastapi client and supabase client
3. pytest **fixtures** for access_token and refresh_token
4. test for **CRUD** operations
5. test for **api** operations

### CI/CD

1. **codecov** for coverage report
2. **poetry** for dependency management and pytest integration
3. **pre-commit** for code quality
4. **latest_changes.yml** for auto update README.md
5. **Semantic Release** for auto release and changelog
6. **docker** for deployment

## How to use it

___
![](docs/assets/usage.gif)

1. create your github repo and config it
1. allow ci to access your repo
![img.png](docs/assets/img.png)
2. config ci_tokens
1. `CODECOV_TOKEN` for codecov in `.github/workflows/ci.yml` ,`semantic-release` is optional for auto release
2. `ATTICUS_PAT`should replace with your GitHub token for latest_changes.yml in `.github/workflows/latest_changes.yml`
3. `DOCKER_USERNAME` and `DOCKER_PASSWORD` for docker-image.yml in `.github/workflows/docker-image.yml`
4. replace `tags: atticuszhou/supafast:latest` with your docker repo in `.github/workflows/docker-image.yml`
3. config fastapi setting in `your_project\src\app\core\config.py`
4. config `pyproject.toml` with your project name and description,etc

2. cd your repo and install dependencies with [uv](https://github.com/astral-sh/uv), which is an extremely fast Python package and project manager, written in Rust.
### install python dependencies

cd your repo and install dependencies with [uv](https://github.com/astral-sh/uv), which is an extremely fast Python package and project manager, written in Rust.

```shell
uv sync
```

3. [start your supabase locally](https://supabase.com/docs/guides/local-development/cli/getting-started?queryGroups=platform&platform=linux&queryGroups=access-method&access-method=postgres)
### install supabase

1. [start your supabase locally](https://supabase.com/docs/guides/local-development/cli/getting-started?queryGroups=platform&platform=linux&queryGroups=access-method&access-method=postgres)

```bash
# brew in linux https://brew.sh/
Expand All @@ -85,34 +55,17 @@ supabase init
supabase start
```

4. set your supabase env
2. set your supabase env

```shell
export SUPABASE_URL=your_supabase_url
export SUPABASE_KEY=your_supabase_key
export SUPERUSER_EMAIL=your_superuser_email
export SUPERUSER_PASSWORD=your_superuser_password
```

5. config fastapi settings

```python
# src/app/core/config.py
class Settings(BaseSettings):
API_V1_STR: str = "/api/v1"
SUPABASE_URL: str = Field(default_factory=lambda: os.getenv("SUPABASE_URL"))
SUPABASE_KEY: str = Field(default_factory=lambda: os.getenv("SUPABASE_KEY"))
SUPERUSER_EMAIL: str = Field(default_factory=lambda: os.getenv("SUPERUSER_EMAIL"))
SUPERUSER_PASSWORD: str = Field(default=lambda: os.getenv("SUPERUSER_PASSWORD"))
# SERVER_NAME: str
SERVER_HOST: AnyHttpUrl = "https://localhost"
SERVER_PORT: int = 8000
BACKEND_CORS_ORIGINS: list[AnyHttpUrl] = []
PROJECT_NAME: str = "fastapi supabase template"
Config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True)
```

6. run server
### run

1. run server

```shell
uv run uvicorn app.main:app --reload
Expand All @@ -123,63 +76,18 @@ uv run uvicorn app.main:app --reload
___

- [x] FastAPI backend
- [x] **standard** structure
- [ ] Layered Architecture
for <a href="https://github.com/tiangolo/fastapi" class="external-link" target="_blank">**FastAPI**</a> project
- [ ] sqlmodel ORM supabase
- [ ] config for fastapi and postgresql connection for supabase

```text
── src
│ └── app
│ ├── api
│ │ ├── api_v1
│ │ │ ├── endpoints
│ │ │ │ ├── __init__.py
│ │ │ │ └── items.py
│ │ │ ├── __init__.py
│ │ │ └── api.py
│ │ ├── __init__.py
│ │ └── deps.py
│ ├── core
│ │ ├── __init__.py
│ │ ├── config.py
│ │ └── events.py
│ ├── crud
│ │ ├── __init__.py
│ │ ├── base.py
│ │ └── crud_item.py
│ ├── schemas
│ │ ├── __init__.py
│ │ ├── auth.py
│ │ ├── base.py
│ │ ├── item.py
│ │ └── msg.py
│ ├── services
│ │ └── __init__.py
│ ├── utils
│ │ └── __init__.py
│ ├── __init__.py
│ └── main.py
...
```

- [x] **auto-auth** by fastapi dependency with supabase-auth
- [x] **CRUD** operations pytest
- [x] **api** requests pytest
- [ ] **auto-auth** by fastapi dependency with supabase-auth
- [ ] **CRUD** operations pytest
- [ ] **api** requests pytest
- [ ] Supabase integration
- [x] crud supabase-postgresql
- [ ] crud supabase-postgresql
- [ ] websocket with supabase-realtime
- [ ] curd supabase-storage
- [ ] supafunc integration
- [x] deployment
- [x] Full **Docker** integration (Docker based).
- [ ] clone
- [ ] cookiecutter

## Release Notes 🥸

___

### Latest Changes

## License

This project is licensed under the terms of the MIT license.
- [ ] deployment
- [ ] Full **Docker** integration (Docker based).
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ dependencies = [
"python-multipart>=0.0.9",
"supabase>=2.7.4",
"fastapi>=0.112.2",
"sqlmodel>=0.0.22",
"psycopg>=3.2.3",
]

[dependency-groups]
Expand Down Expand Up @@ -73,7 +75,7 @@ exclude_lines = [
'\(Protocol\)=$',
'typing.assert_never',
'assert_never',
'if __name__ == .__main__.=',
'if __name__ == "__main__":',
]


Expand Down
Empty file.
2 changes: 1 addition & 1 deletion src/app/api/api_v1/api.py → src/app/api/main.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from fastapi import APIRouter

from app.api.api_v1.endpoints import items
from app.api.routes import items

api_router = APIRouter()
api_router.include_router(items.router, prefix="/items", tags=["items"])
File renamed without changes.
File renamed without changes.
35 changes: 35 additions & 0 deletions src/app/core/db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from sqlmodel import Session, select

from app.api.deps import engine, get_super_client
from app.core.config import settings
from app.models import User

# make sure all SQLModel models are imported (app.models) before initializing DB
# otherwise, SQLModel might fail to initialize relationships properly
# for more details: https://github.com/fastapi/full-stack-fastapi-template/issues/28


async def init_db(session: Session) -> None:
# Tables should be created with Alembic migrations
# But if you don't want to use migrations, create
# the tables un-commenting the next lines
from sqlmodel import SQLModel

# This works because the models are already imported and registered from app.models
SQLModel.metadata.create_all(engine)

user = session.exec(
select(User).where(User.email == settings.FIRST_SUPERUSER)
).first()

if not user:
super_client = await get_super_client()
response = await super_client.auth.sign_up(
{
"email": settings.FIRST_SUPERUSER,
"password": settings.FIRST_SUPERUSER_PASSWORD,
}
)
assert response.user.email == settings.FIRST_SUPERUSER
assert response.user.id is not None
assert response.session.access_token is not None
11 changes: 11 additions & 0 deletions src/app/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from sqlmodel import SQLModel

from .item import Item
from .user import User

__all__ = ["User", "Item", "Message"]


# Generic message
class Message(SQLModel):
message: str
39 changes: 39 additions & 0 deletions src/app/models/item.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import uuid

from sqlmodel import Field, SQLModel


# Shared properties
class ItemBase(SQLModel):
title: str = Field(min_length=1, max_length=255)
description: str | None = Field(default=None, max_length=255)


# Properties to receive on item creation
class ItemCreate(ItemBase):
pass


# Properties to receive on item update
class ItemUpdate(ItemBase):
title: str | None = Field(default=None, min_length=1, max_length=255) # type: ignore


# Database model, database table inferred from class name
class Item(ItemBase, table=True):
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
title: str = Field(max_length=255)
owner_id: uuid.UUID = Field(
foreign_key="user.id", nullable=False, ondelete="CASCADE"
)


# Properties to return via API, id is always required
class ItemPublic(ItemBase):
id: uuid.UUID
owner_id: uuid.UUID


class ItemsPublic(SQLModel):
data: list[ItemPublic]
count: int
13 changes: 13 additions & 0 deletions src/app/models/user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import uuid

from pydantic import EmailStr
from sqlmodel import Field, Relationship, SQLModel

from app.models import Item


class User(SQLModel, table=True):
# __table_args__ = {'extend_existing': True, 'autoload': True}
id: uuid.UUID = Field(default_factory=uuid.uuid4, primary_key=True)
email: EmailStr = Field(max_length=255)
items: list["Item"] = Relationship(back_populates="owner", cascade_delete=True)
Loading

0 comments on commit 55be9c7

Please sign in to comment.