ref: typehints, enum instead of raw string, abc and other (#1)

Full code coverage with annotations, fixing errors in various linters: ruff, wps, etc. Fixing errors in type checkers: ty, mypy, pyright. Formatting and bringing code to a consistent style, applying best practices in various aspects.
This commit is contained in:
kolo
2025-10-08 13:37:31 +03:00
committed by GitHub
parent 22f1171192
commit 73303b1c08
45 changed files with 983 additions and 996 deletions
+11 -2
View File
@@ -1,3 +1,12 @@
__all__ = ["Command"]
__all__ = [
"Command",
"PossibleValues",
"PredefinedFlags",
"InputCommand",
"Flags",
"Flag"
]
from argenta.command.models import Command
from argenta.command.models import Command, InputCommand
from argenta.command.flag import defaults as PredefinedFlags
from argenta.command.flag import (Flag, Flags, PossibleValues)
+20 -13
View File
@@ -1,42 +1,49 @@
from argenta.command.flag.models import Flag, InputFlag
from abc import ABC, abstractmethod
from typing import override
class BaseInputCommandException(Exception):
class InputCommandException(ABC, Exception):
"""
Private. Base exception class for all exceptions raised when parse input command
"""
pass
@override
@abstractmethod
def __str__(self) -> str:
raise NotImplementedError
class UnprocessedInputFlagException(BaseInputCommandException):
class UnprocessedInputFlagException(InputCommandException):
"""
Private. Raised when an unprocessed input flag is detected
"""
def __str__(self):
@override
def __str__(self) -> str:
return "Unprocessed Input Flags"
class RepeatedInputFlagsException(BaseInputCommandException):
class RepeatedInputFlagsException(InputCommandException):
"""
Private. Raised when repeated input flags are detected
"""
def __init__(self, flag: Flag | InputFlag):
self.flag = flag
self.flag: Flag | InputFlag = flag
super().__init__()
def __str__(self):
@override
def __str__(self) -> str:
string_entity: str = self.flag.string_entity
return (
"Repeated Input Flags\n"
f"Duplicate flag was detected in the input: '{self.flag.get_string_entity()}'"
f"Duplicate flag was detected in the input: '{string_entity}'"
)
class EmptyInputCommandException(BaseInputCommandException):
class EmptyInputCommandException(InputCommandException):
"""
Private. Raised when an empty input command is detected
"""
def __str__(self):
@override
def __str__(self) -> str:
return "Input Command is empty"
+5 -11
View File
@@ -1,17 +1,11 @@
__all__ = [
"Flag",
"InputFlag",
"UndefinedInputFlags",
"ValidInputFlags",
"InvalidValueInputFlags",
"Flags", "PossibleValues"
"Flags",
"PossibleValues",
"ValidationStatus"
]
from argenta.command.flag.models import Flag, InputFlag, PossibleValues
from argenta.command.flag.flags.models import (
UndefinedInputFlags,
ValidInputFlags,
Flags,
InvalidValueInputFlags,
)
from argenta.command.flag.models import Flag, InputFlag, PossibleValues, ValidationStatus
from argenta.command.flag.flags.models import Flags
+19 -24
View File
@@ -1,32 +1,27 @@
from dataclasses import dataclass
from typing import Literal
from argenta.command.flag.models import Flag, PossibleValues
import re
import re
DEFAULT_PREFIX: Literal["-", "--", "---"] = "-"
@dataclass
class PredefinedFlags:
"""
Public. A dataclass with predefined flags and most frequently used flags for quick use
"""
HELP = Flag(name="help", possible_values=PossibleValues.NEITHER)
SHORT_HELP = Flag(name="H", prefix=DEFAULT_PREFIX, possible_values=PossibleValues.NEITHER)
HELP = Flag(name="help", possible_values=PossibleValues.DISABLE)
SHORT_HELP = Flag(name="H", prefix="-", possible_values=PossibleValues.DISABLE)
INFO = Flag(name="info", possible_values=PossibleValues.NEITHER) # noqa: WPS110
SHORT_INFO = Flag(name="I", prefix=DEFAULT_PREFIX, possible_values=PossibleValues.NEITHER)
INFO = Flag(name="info", possible_values=PossibleValues.DISABLE)
SHORT_INFO = Flag(name="I", prefix="-", possible_values=PossibleValues.DISABLE)
ALL = Flag(name="all", possible_values=PossibleValues.NEITHER)
SHORT_ALL = Flag(name="A", prefix=DEFAULT_PREFIX, possible_values=PossibleValues.NEITHER)
ALL = Flag(name="all", possible_values=PossibleValues.DISABLE)
SHORT_ALL = Flag(name="A", prefix="-", possible_values=PossibleValues.DISABLE)
HOST = Flag(
name="host", possible_values=re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
)
SHORT_HOST = Flag(
name="H",
prefix=DEFAULT_PREFIX,
possible_values=re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$"),
)
HOST = Flag(
name="host", possible_values=re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$")
)
SHORT_HOST = Flag(
name="H",
prefix="-",
possible_values=re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$"),
)
PORT = Flag(name="port", possible_values=re.compile(r"^\d{1,5}$"))
SHORT_PORT = Flag(name="P", prefix="-", possible_values=re.compile(r"^\d{1,5}$"))
PORT = Flag(name="port", possible_values=re.compile(r"^\d{1,5}$"))
SHORT_PORT = Flag(name="P", prefix=DEFAULT_PREFIX, possible_values=re.compile(r"^\d{1,5}$"))
+2 -8
View File
@@ -1,16 +1,10 @@
__all__ = [
"Flags",
"InputFlags",
"UndefinedInputFlags",
"InvalidValueInputFlags",
"ValidInputFlags",
"InputFlags"
]
from argenta.command.flag.flags.models import (
Flags,
InputFlags,
UndefinedInputFlags,
InvalidValueInputFlags,
ValidInputFlags,
InputFlags
)
+63 -47
View File
@@ -1,90 +1,106 @@
from argenta.command.flag.models import InputFlag, Flag
from typing import Generic, TypeVar
from typing import Generic, TypeVar, override
from collections.abc import Iterator
FlagType = TypeVar("FlagType")
class BaseFlags(Generic[FlagType]):
def __init__(self, *flags: FlagType):
def __init__(self, flags: list[FlagType] | None = None) -> None:
"""
Public. A model that combines the registered flags
:param flags: the flags that will be registered
:return: None
"""
self._flags = flags if flags else []
self.flags: list[FlagType] = flags if flags else []
def get_flags(self) -> list[FlagType]:
"""
Public. Returns a list of flags
:return: list of flags as list[FlagType]
"""
return self._flags
def add_flag(self, flag: FlagType):
def add_flag(self, flag: FlagType) -> None:
"""
Public. Adds a flag to the list of flags
:param flag: flag to add
:return: None
"""
self._flags.append(flag)
self.flags.append(flag)
def add_flags(self, flags: list[FlagType]):
def add_flags(self, flags: list[FlagType]) -> None:
"""
Public. Adds a list of flags to the list of flags
:param flags: list of flags to add
:return: None
"""
self._flags.extend(flags)
self.flags.extend(flags)
def get_flag(self, name: str) -> FlagType | None:
def __iter__(self) -> Iterator[FlagType]:
return iter(self.flags)
def __next__(self) -> FlagType:
return next(iter(self))
def __getitem__(self, flag_index: int) -> FlagType:
return self.flags[flag_index]
def __bool__(self) -> bool:
return bool(self.flags)
class Flags(BaseFlags[Flag]):
def get_flag_by_name(self, name: str) -> Flag | None:
"""
Public. Returns the flag entity by its name or None if not found
:param name: the name of the flag to get
:return: entity of the flag or None
"""
if name in [flag.get_name() for flag in self._flags]:
return list(filter(lambda flag: flag.get_name() == name, self._flags))[0]
else:
return None
return next((flag for flag in self.flags if flag.name == name), None)
@override
def __eq__(self, other: object) -> bool:
if not isinstance(other, Flags):
return NotImplemented
def __iter__(self):
return iter(self._flags)
if len(self.flags) != len(other.flags):
return False
def __next__(self):
return next(iter(self))
flag_pairs: zip[tuple[Flag, Flag]] = zip(self.flags, other.flags)
return all(s_flag == o_flag for s_flag, o_flag in flag_pairs)
def __getitem__(self, item):
return self._flags[item]
def __bool__(self):
return bool(self._flags)
def __eq__(self, other):
if len(self.get_flags()) != len(other.get_flags()):
def __contains__(self, flag_to_check: object) -> bool:
if isinstance(flag_to_check, Flag):
for flag in self.flags:
if flag == flag_to_check:
return True
return False
else:
for flag, other_flag in zip(self.get_flags(), other.get_flags()):
if not flag == other_flag:
return False
return True
class Flags(BaseFlags[Flag]):
pass
raise TypeError
class InputFlags(BaseFlags[InputFlag]):
pass
def get_flag_by_name(self, name: str) -> InputFlag | None:
"""
Public. Returns the flag entity by its name or None if not found
:param name: the name of the flag to get
:return: entity of the flag or None
"""
return next((flag for flag in self.flags if flag.name == name), None)
@override
def __eq__(self, other: object) -> bool:
if not isinstance(other, InputFlags):
raise NotImplementedError
if len(self.flags) != len(other.flags):
return False
class ValidInputFlags(InputFlags):
pass
paired_flags: zip[tuple[InputFlag, InputFlag]] = zip(self.flags, other.flags)
return all(my_flag == other_flag for my_flag, other_flag in paired_flags)
class UndefinedInputFlags(InputFlags):
pass
def __contains__(self, ingressable_item: object) -> bool:
if isinstance(ingressable_item, InputFlag):
for flag in self.flags:
if flag == ingressable_item:
return True
return False
else:
raise TypeError
class InvalidValueInputFlags(InputFlags):
pass
+79 -94
View File
@@ -1,57 +1,22 @@
from enum import Enum
from typing import Literal, Pattern
from re import Pattern
from typing import Literal, override
class PossibleValues(Enum):
DISABLE: Literal[False] = False
ALL: Literal[True] = True
def __eq__(self, other: bool) -> bool:
return self.value == other
NEITHER = 'NEITHER'
ALL = 'ALL'
class BaseFlag:
def __init__(self, name: str, prefix: Literal["-", "--", "---"] = "--") -> None:
"""
Private. Base class for flags
:param name: the name of the flag
:param prefix: the prefix of the flag
:return: None
"""
self._name = name
self._prefix = prefix
def get_string_entity(self) -> str:
"""
Public. Returns a string representation of the flag
:return: string representation of the flag as str
"""
string_entity: str = self._prefix + self._name
return string_entity
def get_name(self) -> str:
"""
Public. Returns the name of the flag
:return: the name of the flag as str
"""
return self._name
def get_prefix(self) -> str:
"""
Public. Returns the prefix of the flag
:return: the prefix of the flag as str
"""
return self._prefix
def __eq__(self, other) -> bool:
return self.get_string_entity() == other.get_string_entity()
class ValidationStatus(Enum):
VALID = 'VALID'
INVALID = 'INVALID'
UNDEFINED = 'UNDEFINED'
class Flag(BaseFlag):
class Flag:
def __init__(
self,
name: str,
self, name: str, *,
prefix: Literal["-", "--", "---"] = "--",
possible_values: list[str] | Pattern[str] | PossibleValues = PossibleValues.ALL,
) -> None:
@@ -62,45 +27,58 @@ class Flag(BaseFlag):
:param possible_values: The possible values of the flag, if False then the flag cannot have a value
:return: None
"""
super().__init__(name, prefix)
self.possible_values = possible_values
self.name: str = name
self.prefix: Literal["-", "--", "---"] = prefix
self.possible_values: list[str] | Pattern[str] | PossibleValues = possible_values
def validate_input_flag_value(self, input_flag_value: str | None):
def validate_input_flag_value(self, input_flag_value: str | None) -> bool:
"""
Private. Validates the input flag value
:param input_flag_value: The input flag value to validate
:return: whether the entered flag is valid as bool
"""
if self.possible_values == PossibleValues.DISABLE:
if input_flag_value is None:
return True
else:
return False
elif isinstance(self.possible_values, Pattern):
if isinstance(input_flag_value, str):
is_valid = bool(self.possible_values.match(input_flag_value))
if bool(is_valid):
return True
else:
return False
else:
return False
if self.possible_values == PossibleValues.NEITHER:
return input_flag_value is None
elif isinstance(self.possible_values, list):
if input_flag_value in self.possible_values:
return True
else:
return False
if isinstance(self.possible_values, Pattern):
return isinstance(input_flag_value, str) and bool(self.possible_values.match(input_flag_value))
if isinstance(self.possible_values, list):
return input_flag_value in self.possible_values
return True
@property
def string_entity(self) -> str:
"""
Public. Returns a string representation of the flag
:return: string representation of the flag as str
"""
string_entity: str = self.prefix + self.name
return string_entity
@override
def __str__(self) -> str:
return self.string_entity
@override
def __repr__(self) -> str:
return f'Flag<name={self.name}, prefix={self.prefix}>'
@override
def __eq__(self, other: object) -> bool:
if isinstance(other, Flag):
return self.string_entity == other.string_entity
else:
return True
raise NotImplementedError
class InputFlag(BaseFlag):
class InputFlag:
def __init__(
self,
name: str,
prefix: Literal["-", "--", "---"] = "--",
value: str | None = None,
self, name: str, *,
prefix: Literal['-', '--', '---'] = '--',
input_value: str | None,
status: ValidationStatus | None
):
"""
Public. The entity of the flag of the entered command
@@ -109,26 +87,33 @@ class InputFlag(BaseFlag):
:param value: the value of the input flag
:return: None
"""
super().__init__(name, prefix)
self._flag_value = value
self.name: str = name
self.prefix: Literal['-', '--', '---'] = prefix
self.input_value: str | None = input_value
self.status: ValidationStatus | None = status
@property
def string_entity(self) -> str:
"""
Public. Returns a string representation of the flag
:return: string representation of the flag as str
"""
string_entity: str = self.prefix + self.name
return string_entity
def get_value(self) -> str | None:
"""
Public. Returns the value of the flag
:return: the value of the flag as str
"""
return self._flag_value
@override
def __str__(self) -> str:
return f'{self.string_entity} {self.input_value}'
@override
def __repr__(self) -> str:
return f'InputFlag<name={self.name}, prefix={self.prefix}, value={self.input_value}, status={self.status}>'
def set_value(self, value):
"""
Private. Sets the value of the flag
:param value: the fag value to set
:return: None
"""
self._flag_value = value
def __eq__(self, other) -> bool:
return (
self.get_string_entity() == other.get_string_entity()
and self.get_value() == other.get_value()
)
@override
def __eq__(self, other: object) -> bool:
if isinstance(other, InputFlag):
return (
self.name == other.name
)
else:
raise NotImplementedError
+100 -144
View File
@@ -1,35 +1,28 @@
from argenta.command.flag.models import Flag, InputFlag
from argenta.command.flag.models import Flag, InputFlag, ValidationStatus
from argenta.command.flag.flags.models import InputFlags, Flags
from argenta.command.exceptions import (
UnprocessedInputFlagException,
RepeatedInputFlagsException,
EmptyInputCommandException,
)
from typing import cast, Literal
from typing import Never, Self, cast, Literal
class BaseCommand:
def __init__(self, trigger: str) -> None:
"""
Private. Base class for all commands
:param trigger: A string trigger, which, when entered by the user, indicates that the input corresponds to the command
"""
self._trigger = trigger
ParseFlagsResult = tuple[InputFlags, str | None, str | None]
ParseResult = tuple[str, InputFlags]
def get_trigger(self) -> str:
"""
Public. Returns the trigger of the command
:return: the trigger of the command as str
"""
return self._trigger
MIN_FLAG_PREFIX: str = "-"
DEFAULT_WITHOUT_FLAGS: Flags = Flags()
DEFAULT_WITHOUT_INPUT_FLAGS: InputFlags = InputFlags()
class Command(BaseCommand):
class Command:
def __init__(
self,
trigger: str,
trigger: str, *,
description: str | None = None,
flags: Flag | Flags | None = None,
flags: Flag | Flags = DEFAULT_WITHOUT_FLAGS,
aliases: list[str] | None = None,
):
"""
@@ -39,157 +32,120 @@ class Command(BaseCommand):
:param flags: processed commands
:param aliases: string synonyms for the main trigger
"""
super().__init__(trigger)
self._registered_flags: Flags = (
flags
if isinstance(flags, Flags)
else Flags(flags)
if isinstance(flags, Flag)
else Flags()
)
self._description = "Very useful command" if not description else description
self._aliases = aliases if isinstance(aliases, list) else []
def get_registered_flags(self) -> Flags:
"""
Private. Returns the registered flags
:return: the registered flags as Flags
"""
return self._registered_flags
def get_aliases(self) -> list[str] | list:
"""
Public. Returns the aliases of the command
:return: the aliases of the command as list[str] | list
"""
return self._aliases
self.registered_flags: Flags = flags if isinstance(flags, Flags) else Flags([flags])
self.trigger: str = trigger
self.description: str = description if description else "Command without description"
self.aliases: list[str] = aliases if aliases else []
def validate_input_flag(
self, flag: InputFlag
) -> Literal["Undefined", "Valid", "Invalid"]:
) -> ValidationStatus:
"""
Private. Validates the input flag
:param flag: input flag for validation
:return: is input flag valid as bool
"""
registered_flags: Flags | None = self.get_registered_flags()
if registered_flags:
if isinstance(registered_flags, Flag):
if registered_flags.get_string_entity() == flag.get_string_entity():
is_valid = registered_flags.validate_input_flag_value(
flag.get_value()
)
if is_valid:
return "Valid"
else:
return "Invalid"
registered_flags: Flags = self.registered_flags
for registered_flag in registered_flags:
if registered_flag.string_entity == flag.string_entity:
is_valid = registered_flag.validate_input_flag_value(flag.input_value)
if is_valid:
return ValidationStatus.VALID
else:
return "Undefined"
else:
for registered_flag in registered_flags:
if registered_flag.get_string_entity() == flag.get_string_entity():
is_valid = registered_flag.validate_input_flag_value(
flag.get_value()
)
if is_valid:
return "Valid"
else:
return "Invalid"
return "Undefined"
return "Undefined"
def get_description(self) -> str:
"""
Private. Returns the description of the command
:return: the description of the command as str
"""
return self._description
return ValidationStatus.INVALID
return ValidationStatus.UNDEFINED
class InputCommand(BaseCommand):
def __init__(self, trigger: str, input_flags: InputFlag | InputFlags | None = None):
class InputCommand:
def __init__(self, trigger: str, *,
input_flags: InputFlag | InputFlags = DEFAULT_WITHOUT_INPUT_FLAGS):
"""
Private. The model of the input command, after parsing
:param trigger:the trigger of the command
:param input_flags: the input flags
:return: None
"""
super().__init__(trigger)
self._input_flags: InputFlags = (
input_flags
if isinstance(input_flags, InputFlags)
else InputFlags(input_flags)
if isinstance(input_flags, InputFlag)
else InputFlags()
)
self.trigger: str = trigger
self.input_flags: InputFlags = input_flags if isinstance(input_flags, InputFlags) else InputFlags([input_flags])
def _set_input_flags(self, input_flags: InputFlags) -> None:
"""
Private. Sets the input flags
:param input_flags: the input flags to set
:return: None
"""
self._input_flags = input_flags
def get_input_flags(self) -> InputFlags:
"""
Private. Returns the input flags
:return: the input flags as InputFlags
"""
return self._input_flags
@staticmethod
def parse(raw_command: str) -> "InputCommand":
@classmethod
def parse(cls, raw_command: str) -> Self:
"""
Private. Parse the raw input command
:param raw_command: raw input command
:return: model of the input command, after parsing as InputCommand
"""
if not raw_command:
trigger, input_flags = CommandParser(raw_command).parse_raw_command()
return cls(trigger=trigger, input_flags=input_flags)
class CommandParser:
def __init__(self, raw_command: str) -> None:
self.raw_command: str = raw_command
self._parsed_input_flags: InputFlags = InputFlags()
def parse_raw_command(self) -> ParseResult:
if not self.raw_command:
raise EmptyInputCommandException()
list_of_tokens = raw_command.split()
command = list_of_tokens.pop(0)
input_flags, crnt_flag_name, crnt_flag_val = self._parse_flags(self.raw_command.split()[1:])
input_flags: InputFlags = InputFlags()
current_flag_name, current_flag_value = None, None
for k, _ in enumerate(list_of_tokens):
if _.startswith("-"):
if len(_) < 2 or len(_[: _.rfind("-")]) > 3:
raise UnprocessedInputFlagException()
current_flag_name = _
else:
if not current_flag_name or current_flag_value:
raise UnprocessedInputFlagException()
current_flag_value = _
if current_flag_name:
if not len(list_of_tokens) == k + 1:
if not list_of_tokens[k + 1].startswith("-"):
continue
input_flag = InputFlag(
name=current_flag_name[current_flag_name.rfind("-") + 1 :],
prefix=cast(
Literal["-", "--", "---"],
current_flag_name[: current_flag_name.rfind("-") + 1],
),
value=current_flag_value,
)
all_flags = [
flag.get_string_entity() for flag in input_flags.get_flags()
]
if input_flag.get_string_entity() not in all_flags:
input_flags.add_flag(input_flag)
else:
raise RepeatedInputFlagsException(input_flag)
current_flag_name, current_flag_value = None, None
if any([current_flag_name, current_flag_value]):
if any([crnt_flag_name, crnt_flag_val]):
raise UnprocessedInputFlagException()
else:
return InputCommand(trigger=command, input_flags=input_flags)
return (self.raw_command.split()[0], input_flags)
def _parse_flags(self, _tokens: list[str] | list[Never]) -> ParseFlagsResult:
crnt_flg_name, crnt_flg_val = None, None
for index, token in enumerate(_tokens):
crnt_flg_name, crnt_flg_val = _parse_single_token(token, crnt_flg_name, crnt_flg_val)
if not crnt_flg_name or self._is_next_token_value(index, _tokens):
continue
input_flag = InputFlag(
name=crnt_flg_name[crnt_flg_name.rfind(MIN_FLAG_PREFIX) + 1:],
prefix=cast(
Literal["-", "--", "---"],
crnt_flg_name[:crnt_flg_name.rfind(MIN_FLAG_PREFIX) + 1],
),
input_value=crnt_flg_val,
status=None
)
if input_flag in self._parsed_input_flags:
raise RepeatedInputFlagsException(input_flag)
self._parsed_input_flags.add_flag(input_flag)
crnt_flg_name, crnt_flg_val = None, None
return (self._parsed_input_flags, crnt_flg_name, crnt_flg_val)
def _is_next_token_value(self, current_index: int,
_tokens: list[str] | list[Never]) -> bool:
next_index = current_index + 1
if next_index >= len(_tokens):
return False
next_token = _tokens[next_index]
return not next_token.startswith(MIN_FLAG_PREFIX)
def _parse_single_token(
token: str,
crnt_flag_name: str | None,
crnt_flag_val: str | None
) -> tuple[str | None, str | None]:
if not token.startswith(MIN_FLAG_PREFIX):
if not crnt_flag_name or crnt_flag_val:
raise UnprocessedInputFlagException
return crnt_flag_name, token
prefix = token[:token.rfind(MIN_FLAG_PREFIX)]
if len(token) < 2 or len(prefix) > 2:
raise UnprocessedInputFlagException
new_flag_name = token
new_flag_value = None
return new_flag_name, new_flag_value