fastapi-svelte-template/backend/todo/schemas/users.py

119 lines
3.6 KiB
Python

"""This module declareds the pydantic ORM representation for users."""
from datetime import datetime
from abc import ABC
from typing import Optional
from re import compile
from pydantic import BaseModel, validator
from todo.schemas.common import is_valid_id
def is_valid_email_format(input_str: str) -> bool:
"""Checks whether the input string is a valid email address format.
Uses a regular expression to perform the check.
"""
if not isinstance(input_str, str):
raise TypeError(f"Expected a string but got {type(input_str)}.")
regex = compile(r'^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$')
if regex.fullmatch(input_str):
return True
else:
return False
class AbstractUserValidator(BaseModel, ABC):
"""Base class for user validators shared with child classes."""
@validator('email', check_fields=False)
def assert_email_is_valid(cls, email):
if email is not None:
if not is_valid_email_format(email):
raise ValueError("Invalid email address format.")
return email
@validator('first_name', check_fields=False)
def assert_first_name_is_valid(cls, first_name):
if first_name is not None:
if not len(first_name):
raise ValueError("First Name must not be empty.")
return first_name
@validator('last_name', check_fields=False)
def assert_last_name_is_valid(cls, last_name):
if last_name is not None:
if not len(last_name):
raise ValueError("Last Name must not be empty.")
return last_name
class AbstractUser(AbstractUserValidator, ABC):
"""Base class for user attributes shared with child classes."""
email: str
first_name: str
last_name: str
class UserCreate(AbstractUser):
"""Schema for user creation."""
password: str
password_confirmation: str
@validator('password_confirmation')
def assert_passwords_match(cls, password_confirmation, values):
if not password_confirmation == values['password']:
raise ValueError("Passwords do not match.")
if len(password_confirmation) < 1:
# TODO use more robust password rules
raise ValueError("Password must not be empty.")
return password_confirmation
class UserUpdate(AbstractUserValidator):
"""Schema for user info updates.
All fields here are optional, but passwords must match if at least one was
provided.
"""
email: Optional[str]
first_name: Optional[str]
last_name: Optional[str]
password: Optional[str] = None
password_confirmation: Optional[str] = None
@validator('password_confirmation')
def assert_passwords_match_or_are_both_none(cls, password_confirmation, values):
password = values.get('password')
if None not in [password, password_confirmation]:
if not password == password_confirmation:
raise ValueError("Passwords do not match.")
if len(password_confirmation) < 1:
# TODO use more robust password rules
raise ValueError("Password must not be empty.")
return password_confirmation
class User(AbstractUser):
"""Schema for user info displaying."""
id: int
created: datetime
updated: datetime
@validator('id')
def assert_id_is_valid(cls, id):
if not is_valid_id(id):
raise ValueError("ID is invalid.")
return id
class Config:
orm_mode = True