aboutsummaryrefslogtreecommitdiffstats
path: root/backend/models
diff options
context:
space:
mode:
Diffstat (limited to 'backend/models')
-rw-r--r--backend/models/__init__.py8
-rw-r--r--backend/models/discord_role.py14
-rw-r--r--backend/models/discord_user.py30
-rw-r--r--backend/models/form.py98
-rw-r--r--backend/models/form_response.py15
-rw-r--r--backend/models/question.py33
6 files changed, 99 insertions, 99 deletions
diff --git a/backend/models/__init__.py b/backend/models/__init__.py
index a9f76e0..336e28b 100644
--- a/backend/models/__init__.py
+++ b/backend/models/__init__.py
@@ -7,13 +7,13 @@ from .question import CodeQuestion, Question
__all__ = [
"AntiSpam",
+ "CodeQuestion",
+ "DiscordMember",
"DiscordRole",
"DiscordUser",
- "DiscordMember",
"Form",
+ "FormList",
"FormResponse",
- "CodeQuestion",
"Question",
- "FormList",
- "ResponseList"
+ "ResponseList",
]
diff --git a/backend/models/discord_role.py b/backend/models/discord_role.py
index ada35ef..195f557 100644
--- a/backend/models/discord_role.py
+++ b/backend/models/discord_role.py
@@ -1,13 +1,11 @@
-import typing
-
from pydantic import BaseModel
class RoleTags(BaseModel):
"""Meta information about a discord role."""
- bot_id: typing.Optional[str]
- integration_id: typing.Optional[str]
+ bot_id: str | None
+ integration_id: str | None
premium_subscriber: bool
def __init__(self, **data) -> None:
@@ -20,7 +18,7 @@ class RoleTags(BaseModel):
We manually parse the raw data to determine if the field exists, and give it a useful
bool value.
"""
- data["premium_subscriber"] = "premium_subscriber" in data.keys()
+ data["premium_subscriber"] = "premium_subscriber" in data
super().__init__(**data)
@@ -31,10 +29,10 @@ class DiscordRole(BaseModel):
name: str
color: int
hoist: bool
- icon: typing.Optional[str]
- unicode_emoji: typing.Optional[str]
+ icon: str | None
+ unicode_emoji: str | None
position: int
permissions: str
managed: bool
mentionable: bool
- tags: typing.Optional[RoleTags]
+ tags: RoleTags | None
diff --git a/backend/models/discord_user.py b/backend/models/discord_user.py
index 0eca15b..be10672 100644
--- a/backend/models/discord_user.py
+++ b/backend/models/discord_user.py
@@ -11,15 +11,15 @@ class _User(BaseModel):
username: str
id: str
discriminator: str
- avatar: t.Optional[str]
- bot: t.Optional[bool]
- system: t.Optional[bool]
- locale: t.Optional[str]
- verified: t.Optional[bool]
- email: t.Optional[str]
- flags: t.Optional[int]
- premium_type: t.Optional[int]
- public_flags: t.Optional[int]
+ avatar: str | None
+ bot: bool | None
+ system: bool | None
+ locale: str | None
+ verified: bool | None
+ email: str | None
+ flags: int | None
+ premium_type: int | None
+ public_flags: int | None
class DiscordUser(_User):
@@ -33,16 +33,16 @@ class DiscordMember(BaseModel):
"""A discord guild member."""
user: _User
- nick: t.Optional[str]
- avatar: t.Optional[str]
+ nick: str | None
+ avatar: str | None
roles: list[str]
joined_at: datetime.datetime
- premium_since: t.Optional[datetime.datetime]
+ premium_since: datetime.datetime | None
deaf: bool
mute: bool
- pending: t.Optional[bool]
- permissions: t.Optional[str]
- communication_disabled_until: t.Optional[datetime.datetime]
+ pending: bool | None
+ permissions: str | None
+ communication_disabled_until: datetime.datetime | None
def dict(self, *args, **kwargs) -> dict[str, t.Any]:
"""Convert the model to a python dict, and encode timestamps in a serializable format."""
diff --git a/backend/models/form.py b/backend/models/form.py
index 10c8bfd..3db267e 100644
--- a/backend/models/form.py
+++ b/backend/models/form.py
@@ -5,6 +5,7 @@ from pydantic import BaseModel, Field, constr, root_validator, validator
from pydantic.error_wrappers import ErrorWrapper, ValidationError
from backend.constants import DISCORD_GUILD, FormFeatures, WebHook
+
from .question import Question
PUBLIC_FIELDS = [
@@ -14,20 +15,22 @@ PUBLIC_FIELDS = [
"name",
"description",
"submitted_text",
- "discord_role"
+ "discord_role",
]
class _WebHook(BaseModel):
"""Schema model of discord webhooks."""
+
url: str
- message: t.Optional[str]
+ message: str | None
@validator("url")
def validate_url(cls, url: str) -> str:
"""Validates URL parameter."""
if "discord.com/api/webhooks/" not in url:
- raise ValueError("URL must be a discord webhook.")
+ msg = "URL must be a discord webhook."
+ raise ValueError(msg)
return url
@@ -40,56 +43,55 @@ class Form(BaseModel):
questions: list[Question]
name: str
description: str
- submitted_text: t.Optional[str] = None
+ submitted_text: str | None = None
webhook: _WebHook = None
- discord_role: t.Optional[str]
- response_readers: t.Optional[list[str]]
- editors: t.Optional[list[str]]
+ discord_role: str | None
+ response_readers: list[str] | None
+ editors: list[str] | None
class Config:
allow_population_by_field_name = True
@validator("features")
- def validate_features(cls, value: list[str]) -> t.Optional[list[str]]:
+ def validate_features(cls, value: list[str]) -> list[str]:
"""Validates is all features in allowed list."""
# Uppercase everything to avoid mixed case in DB
value = [v.upper() for v in value]
allowed_values = [v.value for v in FormFeatures.__members__.values()]
if any(v not in allowed_values for v in value):
- raise ValueError("Form features list contains one or more invalid values.")
+ msg = "Form features list contains one or more invalid values."
+ raise ValueError(msg)
if FormFeatures.REQUIRES_LOGIN.value not in value:
if FormFeatures.COLLECT_EMAIL.value in value:
- raise ValueError(
- "COLLECT_EMAIL feature require REQUIRES_LOGIN feature."
- )
+ msg = "COLLECT_EMAIL feature require REQUIRES_LOGIN feature."
+ raise ValueError(msg)
if FormFeatures.ASSIGN_ROLE.value in value:
- raise ValueError("ASSIGN_ROLE feature require REQUIRES_LOGIN feature.")
+ msg = "ASSIGN_ROLE feature require REQUIRES_LOGIN feature."
+ raise ValueError(msg)
return value
@validator("response_readers", "editors")
- def validate_role_scoping(cls, value: t.Optional[list[str]]) -> t.Optional[list[str]]:
+ def validate_role_scoping(cls, value: list[str] | None) -> list[str]:
"""Ensure special role based permissions aren't granted to the @everyone role."""
- if value and str(DISCORD_GUILD) in value:
- raise ValueError("You can not add the everyone role as an access scope.")
+ if value and DISCORD_GUILD in value:
+ msg = "You can not add the everyone role as an access scope."
+ raise ValueError(msg)
return value
@root_validator
- def validate_role(cls, values: dict[str, t.Any]) -> t.Optional[dict[str, t.Any]]:
+ def validate_role(cls, values: dict[str, t.Any]) -> dict[str, t.Any]:
"""Validates does Discord role provided when flag provided."""
- if (
- FormFeatures.ASSIGN_ROLE.value in values.get("features", [])
- and not values.get("discord_role")
- ):
- raise ValueError(
- "discord_role field is required when ASSIGN_ROLE flag is provided."
- )
+ is_role_assigner = FormFeatures.ASSIGN_ROLE.value in values.get("features", [])
+ if is_role_assigner and not values.get("discord_role"):
+ msg = "discord_role field is required when ASSIGN_ROLE flag is provided."
+ raise ValueError(msg)
return values
- def dict(self, admin: bool = True, **kwargs) -> dict[str, t.Any]:
+ def dict(self, admin: bool = True, **kwargs) -> dict[str, t.Any]: # noqa: FBT001, FBT002
"""Wrapper for original function to exclude private data for public access."""
data = super().dict(**kwargs)
@@ -97,10 +99,7 @@ class Form(BaseModel):
if not admin:
for field in PUBLIC_FIELDS:
- if field == "id" and kwargs.get("by_alias"):
- fetch_field = "_id"
- else:
- fetch_field = field
+ fetch_field = "_id" if field == "id" and kwargs.get("by_alias") else field
returned_data[field] = data[fetch_field]
else:
@@ -110,17 +109,20 @@ class Form(BaseModel):
class FormList(BaseModel):
- __root__: t.List[Form]
+ __root__: list[Form]
-async def validate_hook_url(url: str) -> t.Optional[ValidationError]:
+async def validate_hook_url(url: str) -> ValidationError | None:
"""Validator for discord webhook urls."""
- async def validate() -> t.Optional[str]:
+
+ async def validate() -> str | None:
if not isinstance(url, str):
- raise ValueError("Webhook URL must be a string.")
+ msg = "Webhook URL must be a string."
+ raise TypeError(msg)
if "discord.com/api/webhooks/" not in url:
- raise ValueError("URL must be a discord webhook.")
+ msg = "URL must be a discord webhook."
+ raise ValueError(msg)
try:
async with httpx.AsyncClient() as client:
@@ -129,36 +131,32 @@ async def validate_hook_url(url: str) -> t.Optional[ValidationError]:
except httpx.RequestError as error:
# Catch exceptions in request format
- raise ValueError(
- f"Encountered error while trying to connect to url: `{error}`"
- )
+ msg = f"Encountered error while trying to connect to url: `{error}`"
+ raise ValueError(msg)
except httpx.HTTPStatusError as error:
# Catch exceptions in response
status = error.response.status_code
if status == 401:
- raise ValueError(
- "Could not authenticate with target. Please check the webhook url."
- )
- elif status == 404:
- raise ValueError(
- "Target could not find webhook url. Please check the webhook url."
- )
- else:
- raise ValueError(
- f"Unknown error ({status}) while connecting to target: {error}"
- )
+ msg = "Could not authenticate with target. Please check the webhook url."
+ raise ValueError(msg)
+ if status == 404:
+ msg = "Target could not find webhook url. Please check the webhook url."
+ raise ValueError(msg)
+
+ msg = f"Unknown error ({status}) while connecting to target: {error}"
+ raise ValueError(msg)
return url
# Validate, and return errors, if any
try:
await validate()
- except Exception as e:
+ except Exception as e: # noqa: BLE001
loc = (
WebHook.__name__.lower(),
- WebHook.URL.value
+ WebHook.URL.value,
)
return ValidationError([ErrorWrapper(e, loc=loc)], _WebHook)
diff --git a/backend/models/form_response.py b/backend/models/form_response.py
index 933f5e4..3c8297b 100644
--- a/backend/models/form_response.py
+++ b/backend/models/form_response.py
@@ -11,19 +11,20 @@ class FormResponse(BaseModel):
"""Schema model for form response."""
id: str = Field(alias="_id")
- user: t.Optional[DiscordUser]
- antispam: t.Optional[AntiSpam]
+ user: DiscordUser | None
+ antispam: AntiSpam | None
response: dict[str, t.Any]
form_id: str
timestamp: str
@validator("timestamp", pre=True)
- def set_timestamp(cls, iso_string: t.Optional[str]) -> t.Optional[str]:
+ def set_timestamp(cls, iso_string: str | None) -> str:
if iso_string is None:
- return datetime.datetime.now(tz=datetime.timezone.utc).isoformat()
+ return datetime.datetime.now(tz=datetime.UTC).isoformat()
- elif not isinstance(iso_string, str):
- raise ValueError("Submission timestamp must be a string.")
+ if not isinstance(iso_string, str):
+ msg = "Submission timestamp must be a string."
+ raise TypeError(msg)
# Convert to datetime and back to ensure string is valid
return datetime.datetime.fromisoformat(iso_string).isoformat()
@@ -33,4 +34,4 @@ class FormResponse(BaseModel):
class ResponseList(BaseModel):
- __root__: t.List[FormResponse]
+ __root__: list[FormResponse]
diff --git a/backend/models/question.py b/backend/models/question.py
index 201aa51..a13ce93 100644
--- a/backend/models/question.py
+++ b/backend/models/question.py
@@ -4,11 +4,12 @@ from pydantic import BaseModel, Field, root_validator, validator
from backend.constants import QUESTION_TYPES, REQUIRED_QUESTION_TYPE_DATA
-_TESTS_TYPE = t.Union[t.Dict[str, str], int]
+_TESTS_TYPE = dict[str, str] | int
class Unittests(BaseModel):
"""Schema model for unittest suites in code questions."""
+
allow_failure: bool = False
tests: _TESTS_TYPE
@@ -16,17 +17,19 @@ class Unittests(BaseModel):
def validate_tests(cls, value: _TESTS_TYPE) -> _TESTS_TYPE:
"""Confirm that at least one test exists in a test suite."""
if isinstance(value, dict):
- keys = len(value.keys()) - (1 if "setUp" in value.keys() else 0)
+ keys = len(value.keys()) - (1 if "setUp" in value else 0)
if keys == 0:
- raise ValueError("Must have at least one test in a test suite.")
+ msg = "Must have at least one test in a test suite."
+ raise ValueError(msg)
return value
class CodeQuestion(BaseModel):
"""Schema model for questions of type `code`."""
+
language: str
- unittests: t.Optional[Unittests]
+ unittests: Unittests | None
class Question(BaseModel):
@@ -42,22 +45,20 @@ class Question(BaseModel):
allow_population_by_field_name = True
@validator("type", pre=True)
- def validate_question_type(cls, value: str) -> t.Optional[str]:
+ def validate_question_type(cls, value: str) -> str:
"""Checks if question type in currently allowed types list."""
value = value.lower()
if value not in QUESTION_TYPES:
- raise ValueError(
- f"{value} is not valid question type. "
- f"Allowed question types: {QUESTION_TYPES}."
- )
+ msg = f"{value} is not valid question type. Allowed question types: {QUESTION_TYPES}."
+ raise ValueError(msg)
return value
@root_validator
def validate_question_data(
- cls,
- value: dict[str, t.Any]
- ) -> t.Optional[dict[str, t.Any]]:
+ cls,
+ value: dict[str, t.Any],
+ ) -> dict[str, t.Any]:
"""Check does required data exists for question type and remove other data."""
# When question type don't need data, don't add anything to keep DB clean.
if value.get("type") not in REQUIRED_QUESTION_TYPE_DATA:
@@ -65,13 +66,15 @@ class Question(BaseModel):
for key, data_type in REQUIRED_QUESTION_TYPE_DATA[value["type"]].items():
if key not in value.get("data", {}):
- raise ValueError(f"Required question data key '{key}' not provided.")
+ msg = f"Required question data key '{key}' not provided."
+ raise ValueError(msg)
if not isinstance(value["data"][key], data_type):
- raise ValueError(
+ msg = (
f"Question data key '{key}' expects {data_type.__name__}, "
- f"got {type(value['data'][key]).__name__} instead."
+ f"got {type(value["data"][key]).__name__} instead."
)
+ raise TypeError(msg)
# Validate unittest options
if value.get("type").lower() == "code":