Skip to content

move SA stubs to dev, add few typings, a bit better python code style… #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Nov 24, 2021
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
64 changes: 64 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: tests

on:
push:
pull_request:

jobs:
build:
runs-on: ubuntu-20.04
strategy:
matrix:
python-version: [3.7, 3.8, 3.9]

services:
postgres:
image: postgres
env:
POSTGRES_DB: test
POSTGRES_USER: test
POSTGRES_PASSWORD: test
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
ports:
- 30000:5432
steps:
- uses: actions/checkout@v2

- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v2
with:
path: .venv
key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('**/poetry.lock') }}
- name: Install dependencies and actiavte virtualenv
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: |
python -m venv .venv
source .venv/bin/activate
pip install -r {{cookiecutter.project_name}}/requirements-dev.txt
pip install cookiecutter
- name: Lint with flake8
run: |
source .venv/bin/activate
# stop the build if there are Python syntax errors or undefined names
cd \{\{cookiecutter.project_name\}\}/
flake8 app --count --exit-zero --statistics
- name: Test new cookiecuttered project is passing pytest test
run: |
source .venv/bin/activate
python tests/create_test_project.py
export TEST_DATABASE_HOSTNAME=localhost
export TEST_DATABASE_USER=test
export TEST_DATABASE_PASSWORD=test
export TEST_DATABASE_PORT=30000
export TEST_DATABASE_DB=test

pytest generated_project_for_tests
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
<a href="https://github.com/rafsaf/minimal-fastapi-postgres-template/actions?query=workflow%3Atests" target="_blank">
<img src="https://github.com/rafsaf/minimal-fastapi-postgres-template/workflows/tests/badge.svg" alt="Test">
</a>

<a href="https://github.com/rafsaf/respo/blob/main/LICENSE" target="_blank">
<img src="https://img.shields.io/github/license/rafsaf/respo" alt="License">
</a>

## Minimal async FastAPI + postgresql template

![OpenAPIexample](./docs/OpenAPI_example.png)
Expand Down
Empty file added tests/__init__.py
Empty file.
23 changes: 23 additions & 0 deletions tests/create_test_project.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""
Creates template project in root folder with default values
"""

from pathlib import Path

from cookiecutter.main import cookiecutter

ROOT_FOLDER = Path(__file__).parent.parent


def main():
cookiecutter(
template=str(ROOT_FOLDER),
no_input=True,
extra_context={
"project_name": "generated_project_for_tests",
},
)


if __name__ == "__main__":
main()
6 changes: 0 additions & 6 deletions {{cookiecutter.project_name}}/app/api/deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,3 @@ async def get_current_user(
if not user:
raise HTTPException(status_code=404, detail="User not found")
return user


async def get_current_active_user(
current_user: User = Depends(get_current_user),
) -> User:
return current_user
10 changes: 4 additions & 6 deletions {{cookiecutter.project_name}}/app/api/endpoints/users.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from typing import Any

from fastapi import APIRouter, Depends
from sqlalchemy.ext.asyncio import AsyncSession

Expand All @@ -14,8 +12,8 @@
async def update_user_me(
user_update: schemas.UserUpdate,
session: AsyncSession = Depends(deps.get_session),
current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
current_user: models.User = Depends(deps.get_current_user),
):
"""
Update current user.
"""
Expand All @@ -35,8 +33,8 @@ async def update_user_me(

@router.get("/me", response_model=schemas.User)
async def read_user_me(
current_user: models.User = Depends(deps.get_current_active_user),
) -> Any:
current_user: models.User = Depends(deps.get_current_user),
):
"""
Get current user.
"""
Expand Down
11 changes: 9 additions & 2 deletions {{cookiecutter.project_name}}/app/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,18 @@
"""

from pathlib import Path
from typing import Dict, List, Literal, Union
from typing import Dict, List, Union

import toml
from pydantic import AnyHttpUrl, AnyUrl, BaseSettings, EmailStr, validator

# Literal from typing_extensions for python 3.7 support, remove if not needed
try:
from typing import Literal
except ImportError:
from typing_extensions import Literal
#

PROJECT_DIR = Path(__file__).parent.parent.parent
pyproject_content = toml.load(f"{PROJECT_DIR}/pyproject.toml")["tool"]["poetry"]

Expand Down Expand Up @@ -64,7 +71,7 @@ class Settings(BaseSettings):

# VALIDATORS
@validator("BACKEND_CORS_ORIGINS")
def _assemble_cors_origins(cls, cors_origins):
def _assemble_cors_origins(cls, cors_origins: Union[str, List[AnyHttpUrl]]):
if isinstance(cors_origins, str):
return [item.strip() for item in cors_origins.split(",")]
return cors_origins
Expand Down
6 changes: 3 additions & 3 deletions {{cookiecutter.project_name}}/app/core/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"""

from datetime import datetime, timedelta
from typing import Any, Union
from typing import Any, Tuple, Union

from jose import jwt
from passlib.context import CryptContext
Expand All @@ -18,7 +18,7 @@
ALGORITHM = "HS256"


def create_access_token(subject: Union[str, Any]) -> tuple[str, datetime]:
def create_access_token(subject: Union[str, Any]) -> Tuple[str, datetime]:
now = datetime.utcnow()
expire = now + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)

Expand All @@ -31,7 +31,7 @@ def create_access_token(subject: Union[str, Any]) -> tuple[str, datetime]:
return encoded_jwt, expire


def create_refresh_token(subject: Union[str, Any]) -> tuple[str, datetime]:
def create_refresh_token(subject: Union[str, Any]) -> Tuple[str, datetime]:
now = datetime.utcnow()
expire = now + timedelta(minutes=settings.REFRESH_TOKEN_EXPIRE_MINUTES)

Expand Down
6 changes: 3 additions & 3 deletions {{cookiecutter.project_name}}/app/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
from app.core.config import settings

if settings.ENVIRONMENT == "PYTEST":
SQLALCHEMY_DATABASE_URI = settings.TEST_SQLALCHEMY_DATABASE_URI
sqlalchemy_database_uri = settings.TEST_SQLALCHEMY_DATABASE_URI
else:
SQLALCHEMY_DATABASE_URI = settings.DEFAULT_SQLALCHEMY_DATABASE_URI
sqlalchemy_database_uri = settings.DEFAULT_SQLALCHEMY_DATABASE_URI

async_engine = create_async_engine(SQLALCHEMY_DATABASE_URI, pool_pre_ping=True)
async_engine = create_async_engine(sqlalchemy_database_uri, pool_pre_ping=True)
async_session = sessionmaker(async_engine, expire_on_commit=False, class_=AsyncSession)
4 changes: 2 additions & 2 deletions {{cookiecutter.project_name}}/app/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
import string


def random_lower_string(length=32) -> str:
def random_lower_string(length: int = 32) -> str:
return "".join(random.choices(string.ascii_lowercase, k=length))


def random_email(length=10) -> str:
def random_email(length: int = 10) -> str:
return f"{random_lower_string(length)}@{random_lower_string(length)}.com"
Loading