__init__
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
"""Validation and unit conversion for command-line resource options."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import re
|
||||
from decimal import Decimal, InvalidOperation
|
||||
|
||||
from .errors import ValidationError
|
||||
|
||||
|
||||
_RAM_PATTERN = re.compile(r"\s*(\d+(?:\.\d+)?)\s*([BKMG]i?B?)?\s*", re.IGNORECASE)
|
||||
_USERNAME_PATTERN = re.compile(r"[a-z_][a-z0-9_-]{0,31}\$?")
|
||||
_MIB_FACTORS = {
|
||||
"b": Decimal(1) / Decimal(1024 * 1024),
|
||||
"k": Decimal(1) / Decimal(1024),
|
||||
"kb": Decimal(1) / Decimal(1024),
|
||||
"ki": Decimal(1) / Decimal(1024),
|
||||
"kib": Decimal(1) / Decimal(1024),
|
||||
"m": Decimal(1),
|
||||
"mb": Decimal(1),
|
||||
"mi": Decimal(1),
|
||||
"mib": Decimal(1),
|
||||
"g": Decimal(1024),
|
||||
"gb": Decimal(1024),
|
||||
"gi": Decimal(1024),
|
||||
"gib": Decimal(1024),
|
||||
}
|
||||
|
||||
|
||||
def parse_ram(value: str) -> int:
|
||||
"""Parse a MiB-default RAM value, including B/K/M/G suffixes."""
|
||||
|
||||
match = _RAM_PATTERN.fullmatch(value)
|
||||
if not match:
|
||||
raise ValidationError(f"invalid RAM value: {value}")
|
||||
|
||||
try:
|
||||
amount = Decimal(match.group(1))
|
||||
except InvalidOperation as error:
|
||||
raise ValidationError(f"invalid RAM value: {value}") from error
|
||||
|
||||
unit = (match.group(2) or "MiB").lower()
|
||||
mib = int(amount * _MIB_FACTORS[unit])
|
||||
if mib < 128:
|
||||
raise ValidationError("RAM must be at least 128 MiB")
|
||||
return mib
|
||||
|
||||
|
||||
def parse_cpu(value: str) -> float:
|
||||
"""Parse a positive finite CPU capacity request."""
|
||||
|
||||
try:
|
||||
cpu = float(value)
|
||||
except ValueError as error:
|
||||
raise ValidationError(f"invalid CPU value: {value}") from error
|
||||
|
||||
if not math.isfinite(cpu) or cpu <= 0:
|
||||
raise ValidationError("CPU must be a finite value greater than 0")
|
||||
return cpu
|
||||
|
||||
|
||||
def parse_username(value: str) -> str:
|
||||
"""Validate a conventional Linux account name."""
|
||||
|
||||
if not _USERNAME_PATTERN.fullmatch(value):
|
||||
raise ValidationError(
|
||||
"username must start with a lowercase letter or underscore and contain "
|
||||
"at most 32 lowercase letters, digits, underscores, or hyphens"
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
def parse_password(value: str) -> str:
|
||||
"""Validate a guest password without including it in error messages."""
|
||||
|
||||
if not value:
|
||||
raise ValidationError("password must not be empty")
|
||||
if len(value) > 128:
|
||||
raise ValidationError("password must be at most 128 characters")
|
||||
if any(ord(character) < 32 or ord(character) == 127 for character in value):
|
||||
raise ValidationError("password must not contain control characters")
|
||||
return value
|
||||
Reference in New Issue
Block a user