✨ Use Pydantic BaseSettings for config settings (#87)
* Use Pydantic BaseSettings for config settings * Update fastapi dep to >=0.47.0 and email_validator to email-validator * Fix deprecation warning for Pydantic >=1.0 * Properly support old-format comma separated strings for BACKEND_CORS_ORIGINS Co-authored-by: Sebastián Ramírez <tiangolo@gmail.com>
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
"secret_key": "changethis",
|
||||
"first_superuser": "admin@{{cookiecutter.domain_main}}",
|
||||
"first_superuser_password": "changethis",
|
||||
"backend_cors_origins": "http://localhost, http://localhost:4200, http://localhost:3000, http://localhost:8080, https://localhost, https://localhost:4200, https://localhost:3000, https://localhost:8080, http://dev.{{cookiecutter.domain_main}}, https://{{cookiecutter.domain_staging}}, https://{{cookiecutter.domain_main}}, http://local.dockertoolbox.tiangolo.com, http://localhost.tiangolo.com",
|
||||
"backend_cors_origins": "[\"http://localhost\", \"http://localhost:4200\", \"http://localhost:3000\", \"http://localhost:8080\", \"https://localhost\", \"https://localhost:4200\", \"https://localhost:3000\", \"https://localhost:8080\", \"http://dev.{{cookiecutter.domain_main}}\", \"https://{{cookiecutter.domain_staging}}\", \"https://{{cookiecutter.domain_main}}\", \"http://local.dockertoolbox.tiangolo.com\", \"http://localhost.tiangolo.com\"]",
|
||||
"smtp_port": "587",
|
||||
"smtp_host": "",
|
||||
"smtp_user": "",
|
||||
|
@@ -7,7 +7,7 @@ from sqlalchemy.orm import Session
|
||||
from app import crud
|
||||
from app.api.utils.db import get_db
|
||||
from app.api.utils.security import get_current_user
|
||||
from app.core import config
|
||||
from app.core.config import settings
|
||||
from app.core.jwt import create_access_token
|
||||
from app.core.security import get_password_hash
|
||||
from app.models.user import User as DBUser
|
||||
@@ -37,7 +37,7 @@ def login_access_token(
|
||||
raise HTTPException(status_code=400, detail="Incorrect email or password")
|
||||
elif not crud.user.is_active(user):
|
||||
raise HTTPException(status_code=400, detail="Inactive user")
|
||||
access_token_expires = timedelta(minutes=config.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
access_token_expires = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
return {
|
||||
"access_token": create_access_token(
|
||||
data={"user_id": user.id}, expires_delta=access_token_expires
|
||||
|
@@ -8,7 +8,7 @@ from sqlalchemy.orm import Session
|
||||
from app import crud
|
||||
from app.api.utils.db import get_db
|
||||
from app.api.utils.security import get_current_active_superuser, get_current_active_user
|
||||
from app.core import config
|
||||
from app.core.config import settings
|
||||
from app.models.user import User as DBUser
|
||||
from app.schemas.user import User, UserCreate, UserUpdate
|
||||
from app.utils import send_new_account_email
|
||||
@@ -47,7 +47,7 @@ def create_user(
|
||||
detail="The user with this username already exists in the system.",
|
||||
)
|
||||
user = crud.user.create(db, obj_in=user_in)
|
||||
if config.EMAILS_ENABLED and user_in.email:
|
||||
if settings.EMAILS_ENABLED and user_in.email:
|
||||
send_new_account_email(
|
||||
email_to=user_in.email, username=user_in.email, password=user_in.password
|
||||
)
|
||||
@@ -100,7 +100,7 @@ def create_user_open(
|
||||
"""
|
||||
Create new user without the need to be logged in.
|
||||
"""
|
||||
if not config.USERS_OPEN_REGISTRATION:
|
||||
if not settings.USERS_OPEN_REGISTRATION:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="Open user registration is forbidden on this server",
|
||||
|
@@ -7,19 +7,19 @@ from starlette.status import HTTP_403_FORBIDDEN
|
||||
|
||||
from app import crud
|
||||
from app.api.utils.db import get_db
|
||||
from app.core import config
|
||||
from app.core.config import settings
|
||||
from app.core.jwt import ALGORITHM
|
||||
from app.models.user import User
|
||||
from app.schemas.token import TokenPayload
|
||||
|
||||
reusable_oauth2 = OAuth2PasswordBearer(tokenUrl="/api/v1/login/access-token")
|
||||
reusable_oauth2 = OAuth2PasswordBearer(tokenUrl=f"{settings.API_V1_STR}/login/access-token")
|
||||
|
||||
|
||||
def get_current_user(
|
||||
db: Session = Depends(get_db), token: str = Security(reusable_oauth2)
|
||||
):
|
||||
try:
|
||||
payload = jwt.decode(token, config.SECRET_KEY, algorithms=[ALGORITHM])
|
||||
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[ALGORITHM])
|
||||
token_data = TokenPayload(**payload)
|
||||
except PyJWTError:
|
||||
raise HTTPException(
|
||||
|
@@ -1,55 +1,92 @@
|
||||
import os
|
||||
import secrets
|
||||
from typing import List
|
||||
|
||||
from pydantic import AnyHttpUrl, BaseSettings, EmailStr, HttpUrl, PostgresDsn, validator
|
||||
|
||||
|
||||
def getenv_boolean(var_name, default_value=False):
|
||||
result = default_value
|
||||
env_value = os.getenv(var_name)
|
||||
if env_value is not None:
|
||||
result = env_value.upper() in ("TRUE", "1")
|
||||
return result
|
||||
class Settings(BaseSettings):
|
||||
|
||||
API_V1_STR: str = "/api/v1"
|
||||
|
||||
API_V1_STR = "/api/v1"
|
||||
SECRET_KEY: str = secrets.token_urlsafe(32)
|
||||
|
||||
SECRET_KEY = os.getenvb(b"SECRET_KEY")
|
||||
if not SECRET_KEY:
|
||||
SECRET_KEY = os.urandom(32)
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8 # 60 minutes * 24 hours * 8 days = 8 days
|
||||
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 * 8 # 60 minutes * 24 hours * 8 days = 8 days
|
||||
SERVER_NAME: str
|
||||
SERVER_HOST: AnyHttpUrl
|
||||
# BACKEND_CORS_ORIGINS is a JSON-formatted list of origins
|
||||
# e.g: '["http://localhost", "http://localhost:4200", "http://localhost:3000", \
|
||||
# "http://localhost:8080", "http://local.dockertoolbox.tiangolo.com"]'
|
||||
BACKEND_CORS_ORIGINS: List[AnyHttpUrl] = []
|
||||
|
||||
SERVER_NAME = os.getenv("SERVER_NAME")
|
||||
SERVER_HOST = os.getenv("SERVER_HOST")
|
||||
BACKEND_CORS_ORIGINS = os.getenv(
|
||||
"BACKEND_CORS_ORIGINS"
|
||||
) # a string of origins separated by commas, e.g: "http://localhost, http://localhost:4200, http://localhost:3000, http://localhost:8080, http://local.dockertoolbox.tiangolo.com"
|
||||
PROJECT_NAME = os.getenv("PROJECT_NAME")
|
||||
SENTRY_DSN = os.getenv("SENTRY_DSN")
|
||||
@validator("BACKEND_CORS_ORIGINS", pre=True)
|
||||
def assemble_cors_origins(cls, v):
|
||||
if isinstance(v, str) and not v.startswith("["):
|
||||
return [i.strip() for i in v.split(",")]
|
||||
return v
|
||||
|
||||
POSTGRES_SERVER = os.getenv("POSTGRES_SERVER")
|
||||
POSTGRES_USER = os.getenv("POSTGRES_USER")
|
||||
POSTGRES_PASSWORD = os.getenv("POSTGRES_PASSWORD")
|
||||
POSTGRES_DB = os.getenv("POSTGRES_DB")
|
||||
SQLALCHEMY_DATABASE_URI = (
|
||||
f"postgresql://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{POSTGRES_SERVER}/{POSTGRES_DB}"
|
||||
)
|
||||
PROJECT_NAME: str
|
||||
SENTRY_DSN: HttpUrl = None
|
||||
|
||||
SMTP_TLS = getenv_boolean("SMTP_TLS", True)
|
||||
SMTP_PORT = None
|
||||
_SMTP_PORT = os.getenv("SMTP_PORT")
|
||||
if _SMTP_PORT is not None:
|
||||
SMTP_PORT = int(_SMTP_PORT)
|
||||
SMTP_HOST = os.getenv("SMTP_HOST")
|
||||
SMTP_USER = os.getenv("SMTP_USER")
|
||||
SMTP_PASSWORD = os.getenv("SMTP_PASSWORD")
|
||||
EMAILS_FROM_EMAIL = os.getenv("EMAILS_FROM_EMAIL")
|
||||
EMAILS_FROM_NAME = PROJECT_NAME
|
||||
EMAIL_RESET_TOKEN_EXPIRE_HOURS = 48
|
||||
EMAIL_TEMPLATES_DIR = "/app/app/email-templates/build"
|
||||
EMAILS_ENABLED = SMTP_HOST and SMTP_PORT and EMAILS_FROM_EMAIL
|
||||
@validator("SENTRY_DSN", pre=True)
|
||||
def sentry_dsn_can_be_blank(cls, v):
|
||||
if len(v) == 0:
|
||||
return None
|
||||
return v
|
||||
|
||||
FIRST_SUPERUSER = os.getenv("FIRST_SUPERUSER")
|
||||
FIRST_SUPERUSER_PASSWORD = os.getenv("FIRST_SUPERUSER_PASSWORD")
|
||||
POSTGRES_SERVER: str
|
||||
POSTGRES_USER: str
|
||||
POSTGRES_PASSWORD: str
|
||||
POSTGRES_DB: str
|
||||
SQLALCHEMY_DATABASE_URI: PostgresDsn = None
|
||||
|
||||
USERS_OPEN_REGISTRATION = getenv_boolean("USERS_OPEN_REGISTRATION")
|
||||
@validator("SQLALCHEMY_DATABASE_URI", pre=True)
|
||||
def assemble_db_connection(cls, v, values):
|
||||
if isinstance(v, str):
|
||||
return v
|
||||
return PostgresDsn.build(
|
||||
scheme="postgresql",
|
||||
user=values.get("POSTGRES_USER"),
|
||||
password=values.get("POSTGRES_PASSWORD"),
|
||||
host=values.get("POSTGRES_SERVER"),
|
||||
path=f"/{values.get('POSTGRES_DB') or ''}",
|
||||
)
|
||||
|
||||
EMAIL_TEST_USER = "test@example.com"
|
||||
SMTP_TLS: bool = True
|
||||
SMTP_PORT: int = None
|
||||
SMTP_HOST: str = None
|
||||
SMTP_USER: str = None
|
||||
SMTP_PASSWORD: str = None
|
||||
EMAILS_FROM_EMAIL: EmailStr = None
|
||||
EMAILS_FROM_NAME: str = None
|
||||
|
||||
@validator("EMAILS_FROM_NAME")
|
||||
def get_project_name(cls, v, values):
|
||||
if not v:
|
||||
return values["PROJECT_NAME"]
|
||||
return v
|
||||
|
||||
EMAIL_RESET_TOKEN_EXPIRE_HOURS: int = 48
|
||||
EMAIL_TEMPLATES_DIR: str = "/app/app/email-templates/build"
|
||||
EMAILS_ENABLED: bool = False
|
||||
|
||||
@validator("EMAILS_ENABLED", pre=True)
|
||||
def get_emails_enabled(cls, v, values):
|
||||
return bool(
|
||||
values.get("SMTP_HOST")
|
||||
and values.get("SMTP_PORT")
|
||||
and values.get("EMAILS_FROM_EMAIL")
|
||||
)
|
||||
|
||||
EMAIL_TEST_USER: EmailStr = "test@example.com"
|
||||
|
||||
FIRST_SUPERUSER: EmailStr
|
||||
FIRST_SUPERUSER_PASSWORD: str
|
||||
|
||||
USERS_OPEN_REGISTRATION: bool = False
|
||||
|
||||
class Config:
|
||||
case_sensitive = True
|
||||
|
||||
settings = Settings()
|
||||
|
@@ -2,7 +2,7 @@ from datetime import datetime, timedelta
|
||||
|
||||
import jwt
|
||||
|
||||
from app.core import config
|
||||
from app.core.config import settings
|
||||
|
||||
ALGORITHM = "HS256"
|
||||
access_token_jwt_subject = "access"
|
||||
@@ -15,5 +15,5 @@ def create_access_token(*, data: dict, expires_delta: timedelta = None):
|
||||
else:
|
||||
expire = datetime.utcnow() + timedelta(minutes=15)
|
||||
to_encode.update({"exp": expire, "sub": access_token_jwt_subject})
|
||||
encoded_jwt = jwt.encode(to_encode, config.SECRET_KEY, algorithm=ALGORITHM)
|
||||
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=ALGORITHM)
|
||||
return encoded_jwt
|
||||
|
@@ -41,7 +41,7 @@ class CRUDBase(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
|
||||
self, db_session: Session, *, db_obj: ModelType, obj_in: UpdateSchemaType
|
||||
) -> ModelType:
|
||||
obj_data = jsonable_encoder(db_obj)
|
||||
update_data = obj_in.dict(skip_defaults=True)
|
||||
update_data = obj_in.dict(exclude_unset=True)
|
||||
for field in obj_data:
|
||||
if field in update_data:
|
||||
setattr(db_obj, field, update_data[field])
|
||||
|
@@ -1,5 +1,5 @@
|
||||
from app import crud
|
||||
from app.core import config
|
||||
from app.core.config import settings
|
||||
from app.schemas.user import UserCreate
|
||||
|
||||
# make sure all SQL Alchemy models are imported before initializing DB
|
||||
@@ -14,11 +14,11 @@ def init_db(db_session):
|
||||
# the tables un-commenting the next line
|
||||
# Base.metadata.create_all(bind=engine)
|
||||
|
||||
user = crud.user.get_by_email(db_session, email=config.FIRST_SUPERUSER)
|
||||
user = crud.user.get_by_email(db_session, email=settings.FIRST_SUPERUSER)
|
||||
if not user:
|
||||
user_in = UserCreate(
|
||||
email=config.FIRST_SUPERUSER,
|
||||
password=config.FIRST_SUPERUSER_PASSWORD,
|
||||
email=settings.FIRST_SUPERUSER,
|
||||
password=settings.FIRST_SUPERUSER_PASSWORD,
|
||||
is_superuser=True,
|
||||
)
|
||||
user = crud.user.create(db_session, obj_in=user_in)
|
||||
|
@@ -1,9 +1,9 @@
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||
|
||||
from app.core import config
|
||||
from app.core.config import settings
|
||||
|
||||
engine = create_engine(config.SQLALCHEMY_DATABASE_URI, pool_pre_ping=True)
|
||||
engine = create_engine(settings.SQLALCHEMY_DATABASE_URI, pool_pre_ping=True)
|
||||
db_session = scoped_session(
|
||||
sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
)
|
||||
|
@@ -3,29 +3,22 @@ from starlette.middleware.cors import CORSMiddleware
|
||||
from starlette.requests import Request
|
||||
|
||||
from app.api.api_v1.api import api_router
|
||||
from app.core import config
|
||||
from app.core.config import settings
|
||||
from app.db.session import Session
|
||||
|
||||
app = FastAPI(title=config.PROJECT_NAME, openapi_url="/api/v1/openapi.json")
|
||||
|
||||
# CORS
|
||||
origins = []
|
||||
app = FastAPI(title=settings.PROJECT_NAME, openapi_url=f"{settings.API_V1_STR}/openapi.json")
|
||||
|
||||
# Set all CORS enabled origins
|
||||
if config.BACKEND_CORS_ORIGINS:
|
||||
origins_raw = config.BACKEND_CORS_ORIGINS.split(",")
|
||||
for origin in origins_raw:
|
||||
use_origin = origin.strip()
|
||||
origins.append(use_origin)
|
||||
if settings.BACKEND_CORS_ORIGINS:
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=origins,
|
||||
allow_origins=[str(origin) for origin in settings.BACKEND_CORS_ORIGINS],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
),
|
||||
|
||||
app.include_router(api_router, prefix=config.API_V1_STR)
|
||||
app.include_router(api_router, prefix=settings.API_V1_STR)
|
||||
|
||||
|
||||
@app.middleware("http")
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import requests
|
||||
|
||||
from app.core import config
|
||||
from app.core.config import settings
|
||||
from app.tests.utils.utils import get_server_api
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ def test_celery_worker_test(superuser_token_headers):
|
||||
server_api = get_server_api()
|
||||
data = {"msg": "test"}
|
||||
r = requests.post(
|
||||
f"{server_api}{config.API_V1_STR}/utils/test-celery/",
|
||||
f"{server_api}{settings.API_V1_STR}/utils/test-celery/",
|
||||
json=data,
|
||||
headers=superuser_token_headers,
|
||||
)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import requests
|
||||
|
||||
from app.core import config
|
||||
from app.core.config import settings
|
||||
from app.tests.utils.item import create_random_item
|
||||
from app.tests.utils.utils import get_server_api
|
||||
from app.tests.utils.user import create_random_user
|
||||
@@ -10,7 +10,7 @@ def test_create_item(superuser_token_headers):
|
||||
server_api = get_server_api()
|
||||
data = {"title": "Foo", "description": "Fighters"}
|
||||
response = requests.post(
|
||||
f"{server_api}{config.API_V1_STR}/items/",
|
||||
f"{server_api}{settings.API_V1_STR}/items/",
|
||||
headers=superuser_token_headers,
|
||||
json=data,
|
||||
)
|
||||
@@ -26,7 +26,7 @@ def test_read_item(superuser_token_headers):
|
||||
item = create_random_item()
|
||||
server_api = get_server_api()
|
||||
response = requests.get(
|
||||
f"{server_api}{config.API_V1_STR}/items/{item.id}",
|
||||
f"{server_api}{settings.API_V1_STR}/items/{item.id}",
|
||||
headers=superuser_token_headers,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
@@ -1,17 +1,17 @@
|
||||
import requests
|
||||
|
||||
from app.core import config
|
||||
from app.core.config import settings
|
||||
from app.tests.utils.utils import get_server_api
|
||||
|
||||
|
||||
def test_get_access_token():
|
||||
server_api = get_server_api()
|
||||
login_data = {
|
||||
"username": config.FIRST_SUPERUSER,
|
||||
"password": config.FIRST_SUPERUSER_PASSWORD,
|
||||
"username": settings.FIRST_SUPERUSER,
|
||||
"password": settings.FIRST_SUPERUSER_PASSWORD,
|
||||
}
|
||||
r = requests.post(
|
||||
f"{server_api}{config.API_V1_STR}/login/access-token", data=login_data
|
||||
f"{server_api}{settings.API_V1_STR}/login/access-token", data=login_data
|
||||
)
|
||||
tokens = r.json()
|
||||
assert r.status_code == 200
|
||||
@@ -22,7 +22,7 @@ def test_get_access_token():
|
||||
def test_use_access_token(superuser_token_headers):
|
||||
server_api = get_server_api()
|
||||
r = requests.post(
|
||||
f"{server_api}{config.API_V1_STR}/login/test-token",
|
||||
f"{server_api}{settings.API_V1_STR}/login/test-token",
|
||||
headers=superuser_token_headers,
|
||||
)
|
||||
result = r.json()
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import requests
|
||||
|
||||
from app import crud
|
||||
from app.core import config
|
||||
from app.core.config import settings
|
||||
from app.db.session import db_session
|
||||
from app.schemas.user import UserCreate
|
||||
from app.tests.utils.utils import get_server_api, random_lower_string, random_email
|
||||
@@ -10,25 +10,25 @@ from app.tests.utils.utils import get_server_api, random_lower_string, random_em
|
||||
def test_get_users_superuser_me(superuser_token_headers):
|
||||
server_api = get_server_api()
|
||||
r = requests.get(
|
||||
f"{server_api}{config.API_V1_STR}/users/me", headers=superuser_token_headers
|
||||
f"{server_api}{settings.API_V1_STR}/users/me", headers=superuser_token_headers
|
||||
)
|
||||
current_user = r.json()
|
||||
assert current_user
|
||||
assert current_user["is_active"] is True
|
||||
assert current_user["is_superuser"]
|
||||
assert current_user["email"] == config.FIRST_SUPERUSER
|
||||
assert current_user["email"] == settings.FIRST_SUPERUSER
|
||||
|
||||
|
||||
def test_get_users_normal_user_me(normal_user_token_headers):
|
||||
server_api = get_server_api()
|
||||
r = requests.get(
|
||||
f"{server_api}{config.API_V1_STR}/users/me", headers=normal_user_token_headers
|
||||
f"{server_api}{settings.API_V1_STR}/users/me", headers=normal_user_token_headers
|
||||
)
|
||||
current_user = r.json()
|
||||
assert current_user
|
||||
assert current_user["is_active"] is True
|
||||
assert current_user["is_superuser"] is False
|
||||
assert current_user["email"] == config.EMAIL_TEST_USER
|
||||
assert current_user["email"] == settings.EMAIL_TEST_USER
|
||||
|
||||
|
||||
def test_create_user_new_email(superuser_token_headers):
|
||||
@@ -37,7 +37,7 @@ def test_create_user_new_email(superuser_token_headers):
|
||||
password = random_lower_string()
|
||||
data = {"email": username, "password": password}
|
||||
r = requests.post(
|
||||
f"{server_api}{config.API_V1_STR}/users/",
|
||||
f"{server_api}{settings.API_V1_STR}/users/",
|
||||
headers=superuser_token_headers,
|
||||
json=data,
|
||||
)
|
||||
@@ -55,7 +55,7 @@ def test_get_existing_user(superuser_token_headers):
|
||||
user = crud.user.create(db_session, obj_in=user_in)
|
||||
user_id = user.id
|
||||
r = requests.get(
|
||||
f"{server_api}{config.API_V1_STR}/users/{user_id}",
|
||||
f"{server_api}{settings.API_V1_STR}/users/{user_id}",
|
||||
headers=superuser_token_headers,
|
||||
)
|
||||
assert 200 <= r.status_code < 300
|
||||
@@ -73,7 +73,7 @@ def test_create_user_existing_username(superuser_token_headers):
|
||||
crud.user.create(db_session, obj_in=user_in)
|
||||
data = {"email": username, "password": password}
|
||||
r = requests.post(
|
||||
f"{server_api}{config.API_V1_STR}/users/",
|
||||
f"{server_api}{settings.API_V1_STR}/users/",
|
||||
headers=superuser_token_headers,
|
||||
json=data,
|
||||
)
|
||||
@@ -88,7 +88,7 @@ def test_create_user_by_normal_user(normal_user_token_headers):
|
||||
password = random_lower_string()
|
||||
data = {"email": username, "password": password}
|
||||
r = requests.post(
|
||||
f"{server_api}{config.API_V1_STR}/users/",
|
||||
f"{server_api}{settings.API_V1_STR}/users/",
|
||||
headers=normal_user_token_headers,
|
||||
json=data,
|
||||
)
|
||||
@@ -108,7 +108,7 @@ def test_retrieve_users(superuser_token_headers):
|
||||
crud.user.create(db_session, obj_in=user_in2)
|
||||
|
||||
r = requests.get(
|
||||
f"{server_api}{config.API_V1_STR}/users/", headers=superuser_token_headers
|
||||
f"{server_api}{settings.API_V1_STR}/users/", headers=superuser_token_headers
|
||||
)
|
||||
all_users = r.json()
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from app.core import config
|
||||
from app.core.config import settings
|
||||
from app.tests.utils.utils import get_server_api, get_superuser_token_headers
|
||||
from app.tests.utils.user import authentication_token_from_email
|
||||
|
||||
@@ -17,4 +17,4 @@ def superuser_token_headers():
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def normal_user_token_headers():
|
||||
return authentication_token_from_email(config.EMAIL_TEST_USER)
|
||||
return authentication_token_from_email(settings.EMAIL_TEST_USER)
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import requests
|
||||
|
||||
from app import crud
|
||||
from app.core import config
|
||||
from app.core.config import settings
|
||||
from app.db.session import db_session
|
||||
from app.schemas.user import UserCreate, UserUpdate
|
||||
from app.tests.utils.utils import get_server_api, random_lower_string, random_email
|
||||
@@ -10,7 +10,7 @@ from app.tests.utils.utils import get_server_api, random_lower_string, random_em
|
||||
def user_authentication_headers(server_api, email, password):
|
||||
data = {"username": email, "password": password}
|
||||
|
||||
r = requests.post(f"{server_api}{config.API_V1_STR}/login/access-token", data=data)
|
||||
r = requests.post(f"{server_api}{settings.API_V1_STR}/login/access-token", data=data)
|
||||
response = r.json()
|
||||
auth_token = response["access_token"]
|
||||
headers = {"Authorization": f"Bearer {auth_token}"}
|
||||
|
@@ -2,7 +2,8 @@ import random
|
||||
import string
|
||||
|
||||
import requests
|
||||
from app.core import config
|
||||
|
||||
from app.core.config import settings
|
||||
|
||||
|
||||
def random_lower_string():
|
||||
@@ -14,18 +15,18 @@ def random_email():
|
||||
|
||||
|
||||
def get_server_api():
|
||||
server_name = f"http://{config.SERVER_NAME}"
|
||||
server_name = f"http://{settings.SERVER_NAME}"
|
||||
return server_name
|
||||
|
||||
|
||||
def get_superuser_token_headers():
|
||||
server_api = get_server_api()
|
||||
login_data = {
|
||||
"username": config.FIRST_SUPERUSER,
|
||||
"password": config.FIRST_SUPERUSER_PASSWORD,
|
||||
"username": settings.FIRST_SUPERUSER,
|
||||
"password": settings.FIRST_SUPERUSER_PASSWORD,
|
||||
}
|
||||
r = requests.post(
|
||||
f"{server_api}{config.API_V1_STR}/login/access-token", data=login_data
|
||||
f"{server_api}{settings.API_V1_STR}/login/access-token", data=login_data
|
||||
)
|
||||
tokens = r.json()
|
||||
a_token = tokens["access_token"]
|
||||
|
@@ -8,79 +8,79 @@ import jwt
|
||||
from emails.template import JinjaTemplate
|
||||
from jwt.exceptions import InvalidTokenError
|
||||
|
||||
from app.core import config
|
||||
from app.core.config import settings
|
||||
|
||||
password_reset_jwt_subject = "preset"
|
||||
|
||||
|
||||
def send_email(email_to: str, subject_template="", html_template="", environment={}):
|
||||
assert config.EMAILS_ENABLED, "no provided configuration for email variables"
|
||||
assert settings.EMAILS_ENABLED, "no provided configuration for email variables"
|
||||
message = emails.Message(
|
||||
subject=JinjaTemplate(subject_template),
|
||||
html=JinjaTemplate(html_template),
|
||||
mail_from=(config.EMAILS_FROM_NAME, config.EMAILS_FROM_EMAIL),
|
||||
mail_from=(settings.EMAILS_FROM_NAME, settings.EMAILS_FROM_EMAIL),
|
||||
)
|
||||
smtp_options = {"host": config.SMTP_HOST, "port": config.SMTP_PORT}
|
||||
if config.SMTP_TLS:
|
||||
smtp_options = {"host": settings.SMTP_HOST, "port": settings.SMTP_PORT}
|
||||
if settings.SMTP_TLS:
|
||||
smtp_options["tls"] = True
|
||||
if config.SMTP_USER:
|
||||
smtp_options["user"] = config.SMTP_USER
|
||||
if config.SMTP_PASSWORD:
|
||||
smtp_options["password"] = config.SMTP_PASSWORD
|
||||
if settings.SMTP_USER:
|
||||
smtp_options["user"] = settings.SMTP_USER
|
||||
if settings.SMTP_PASSWORD:
|
||||
smtp_options["password"] = settings.SMTP_PASSWORD
|
||||
response = message.send(to=email_to, render=environment, smtp=smtp_options)
|
||||
logging.info(f"send email result: {response}")
|
||||
|
||||
|
||||
def send_test_email(email_to: str):
|
||||
project_name = config.PROJECT_NAME
|
||||
project_name = settings.PROJECT_NAME
|
||||
subject = f"{project_name} - Test email"
|
||||
with open(Path(config.EMAIL_TEMPLATES_DIR) / "test_email.html") as f:
|
||||
with open(Path(settings.EMAIL_TEMPLATES_DIR) / "test_email.html") as f:
|
||||
template_str = f.read()
|
||||
send_email(
|
||||
email_to=email_to,
|
||||
subject_template=subject,
|
||||
html_template=template_str,
|
||||
environment={"project_name": config.PROJECT_NAME, "email": email_to},
|
||||
environment={"project_name": settings.PROJECT_NAME, "email": email_to},
|
||||
)
|
||||
|
||||
|
||||
def send_reset_password_email(email_to: str, email: str, token: str):
|
||||
project_name = config.PROJECT_NAME
|
||||
project_name = settings.PROJECT_NAME
|
||||
subject = f"{project_name} - Password recovery for user {email}"
|
||||
with open(Path(config.EMAIL_TEMPLATES_DIR) / "reset_password.html") as f:
|
||||
with open(Path(settings.EMAIL_TEMPLATES_DIR) / "reset_password.html") as f:
|
||||
template_str = f.read()
|
||||
if hasattr(token, "decode"):
|
||||
use_token = token.decode()
|
||||
else:
|
||||
use_token = token
|
||||
server_host = config.SERVER_HOST
|
||||
server_host = settings.SERVER_HOST
|
||||
link = f"{server_host}/reset-password?token={use_token}"
|
||||
send_email(
|
||||
email_to=email_to,
|
||||
subject_template=subject,
|
||||
html_template=template_str,
|
||||
environment={
|
||||
"project_name": config.PROJECT_NAME,
|
||||
"project_name": settings.PROJECT_NAME,
|
||||
"username": email,
|
||||
"email": email_to,
|
||||
"valid_hours": config.EMAIL_RESET_TOKEN_EXPIRE_HOURS,
|
||||
"valid_hours": settings.EMAIL_RESET_TOKEN_EXPIRE_HOURS,
|
||||
"link": link,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def send_new_account_email(email_to: str, username: str, password: str):
|
||||
project_name = config.PROJECT_NAME
|
||||
project_name = settings.PROJECT_NAME
|
||||
subject = f"{project_name} - New account for user {username}"
|
||||
with open(Path(config.EMAIL_TEMPLATES_DIR) / "new_account.html") as f:
|
||||
with open(Path(settings.EMAIL_TEMPLATES_DIR) / "new_account.html") as f:
|
||||
template_str = f.read()
|
||||
link = config.SERVER_HOST
|
||||
link = settings.SERVER_HOST
|
||||
send_email(
|
||||
email_to=email_to,
|
||||
subject_template=subject,
|
||||
html_template=template_str,
|
||||
environment={
|
||||
"project_name": config.PROJECT_NAME,
|
||||
"project_name": settings.PROJECT_NAME,
|
||||
"username": username,
|
||||
"password": password,
|
||||
"email": email_to,
|
||||
@@ -90,13 +90,13 @@ def send_new_account_email(email_to: str, username: str, password: str):
|
||||
|
||||
|
||||
def generate_password_reset_token(email):
|
||||
delta = timedelta(hours=config.EMAIL_RESET_TOKEN_EXPIRE_HOURS)
|
||||
delta = timedelta(hours=settings.EMAIL_RESET_TOKEN_EXPIRE_HOURS)
|
||||
now = datetime.utcnow()
|
||||
expires = now + delta
|
||||
exp = expires.timestamp()
|
||||
encoded_jwt = jwt.encode(
|
||||
{"exp": exp, "nbf": now, "sub": password_reset_jwt_subject, "email": email},
|
||||
config.SECRET_KEY,
|
||||
settings.SECRET_KEY,
|
||||
algorithm="HS256",
|
||||
)
|
||||
return encoded_jwt
|
||||
@@ -104,7 +104,7 @@ def generate_password_reset_token(email):
|
||||
|
||||
def verify_password_reset_token(token) -> Optional[str]:
|
||||
try:
|
||||
decoded_token = jwt.decode(token, config.SECRET_KEY, algorithms=["HS256"])
|
||||
decoded_token = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"])
|
||||
assert decoded_token["sub"] == password_reset_jwt_subject
|
||||
return decoded_token["email"]
|
||||
except InvalidTokenError:
|
||||
|
@@ -1,9 +1,9 @@
|
||||
from raven import Client
|
||||
|
||||
from app.core import config
|
||||
from app.core.config import settings
|
||||
from app.core.celery_app import celery_app
|
||||
|
||||
client_sentry = Client(config.SENTRY_DSN)
|
||||
client_sentry = Client(settings.SENTRY_DSN)
|
||||
|
||||
|
||||
@celery_app.task(acks_late=True)
|
||||
|
@@ -1,6 +1,6 @@
|
||||
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.7
|
||||
|
||||
RUN pip install celery~=4.3 passlib[bcrypt] tenacity requests emails "fastapi>=0.47.0" "uvicorn>=0.11.1" gunicorn pyjwt python-multipart email_validator jinja2 psycopg2-binary alembic SQLAlchemy email_validator
|
||||
RUN pip install celery~=4.3 passlib[bcrypt] tenacity requests emails "fastapi>=0.47.0" "uvicorn>=0.11.1" gunicorn pyjwt python-multipart email-validator jinja2 psycopg2-binary alembic SQLAlchemy
|
||||
|
||||
# For development, Jupyter remote kernel, Hydrogen
|
||||
# Using inside the container:
|
||||
|
@@ -1,6 +1,6 @@
|
||||
FROM python:3.7
|
||||
|
||||
RUN pip install raven celery~=4.3 passlib[bcrypt] tenacity requests "fastapi>=0.16.0" emails pyjwt email_validator jinja2 psycopg2-binary alembic SQLAlchemy email_validator
|
||||
RUN pip install raven celery~=4.3 passlib[bcrypt] tenacity requests "fastapi>=0.47.0" emails pyjwt email-validator jinja2 psycopg2-binary alembic SQLAlchemy
|
||||
|
||||
# For development, Jupyter remote kernel, Hydrogen
|
||||
# Using inside the container:
|
||||
|
@@ -1,6 +1,6 @@
|
||||
FROM python:3.7
|
||||
|
||||
RUN pip install requests pytest tenacity passlib[bcrypt] "fastapi>=0.16.0" psycopg2-binary SQLAlchemy email_validator
|
||||
RUN pip install requests pytest tenacity passlib[bcrypt] "fastapi>=0.47.0" email-validator psycopg2-binary SQLAlchemy
|
||||
|
||||
# For development, Jupyter remote kernel, Hydrogen
|
||||
# Using inside the container:
|
||||
|
@@ -12,3 +12,4 @@ services:
|
||||
backend-tests:
|
||||
environment:
|
||||
- JUPYTER=jupyter lab --ip=0.0.0.0 --allow-root --NotebookApp.custom_display_url=http://127.0.0.1:8888
|
||||
- SERVER_HOST=http://${DOMAIN}
|
||||
|
@@ -10,6 +10,7 @@ services:
|
||||
- env-postgres.env
|
||||
environment:
|
||||
- SERVER_NAME=backend
|
||||
- SERVER_HOST=http://${DOMAIN}
|
||||
backend:
|
||||
environment:
|
||||
# Don't send emails during testing
|
||||
|
Reference in New Issue
Block a user