111 lines
3.4 KiB
Python
111 lines
3.4 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
|
||
|
|
||
|
|
||
|
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
|
||
|
|
||
|
class Config:
|
||
|
orm_mode = True
|