From d0e09d2ba567f23d91ac76d1844966bafb9b063a Mon Sep 17 00:00:00 2001 From: Joe Banks Date: Sun, 7 Jul 2024 02:29:26 +0100 Subject: Apply fixable lint settings with Ruff --- backend/models/__init__.py | 8 ++-- backend/models/discord_role.py | 14 +++--- backend/models/discord_user.py | 30 ++++++------- backend/models/form.py | 98 ++++++++++++++++++++--------------------- backend/models/form_response.py | 15 ++++--- backend/models/question.py | 33 +++++++------- 6 files changed, 99 insertions(+), 99 deletions(-) (limited to 'backend/models') 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": -- cgit v1.2.3