Source code for ferrosoft.strparse
"""
Tolerant string-to-type parsers for processing external and user-supplied data.
All parsers in this module are intentionally lenient:
* The ``parse_*_noexcept`` functions return ``None`` instead of raising on
invalid input, making them safe to use with data from CSV files, query
parameters, or any other source where the value may be absent or malformed.
* :func:`parse_boolean` treats every unrecognised string as ``True``, mirroring
the JavaScript truthiness model and the convention used by HTML checkboxes
(where a submitted field value always means "checked").
The :data:`_UNTRUTHY_VALUES` list deliberately covers representations from
multiple languages and data formats (Python's ``"none"``, JavaScript's
``"null"`` and ``"undefined"``, SQL's implicit empty string, numeric zeros)
so that the same parser can be used across heterogeneous data sources.
"""
import uuid
from decimal import Decimal
from uuid import UUID
#: String values that :func:`parse_boolean` treats as ``False``.
#: Comparison is case-insensitive (inputs are lower-cased before lookup).
#: The set mirrors JavaScript's falsy strings plus common null/zero
#: representations from Python, SQL, and JSON.
_UNTRUTHY_VALUES = [
"",
"false",
"0",
"-0",
"0n",
"null",
"undefined",
"none",
"nan",
]
[docs]
def get_untruthy_values() -> list[str]:
"""Return the list of string values that :func:`parse_boolean` treats as ``False``.
Callers can use this to display or validate the set of accepted falsy
representations without hard-coding the list themselves.
Returns:
A list of lowercase strings considered untruthy by :func:`parse_boolean`.
"""
return _UNTRUTHY_VALUES
[docs]
def parse_boolean(value: str) -> bool:
"""Parse a string as a boolean, treating all unrecognised values as ``True``.
Converts ``value`` to lowercase and checks it against
:data:`_UNTRUTHY_VALUES`. Any string not in that list — including
``"yes"``, ``"on"``, ``"1"``, ``"true"``, or any arbitrary non-empty
string — is considered ``True``.
This permissive convention is intentional: it matches the behaviour of
HTML form checkboxes (where the presence of any submitted value means
"enabled") and JavaScript's general truthiness rules.
Args:
value: The raw string to interpret as a boolean.
Returns:
``False`` if ``value.lower()`` is in :data:`_UNTRUTHY_VALUES`,
``True`` otherwise.
Example:
::
parse_boolean("true") # True
parse_boolean("yes") # True
parse_boolean("1") # True
parse_boolean("false") # False
parse_boolean("0") # False
parse_boolean("none") # False
parse_boolean("") # False
"""
return value.lower() not in _UNTRUTHY_VALUES
[docs]
def parse_decimal_noexcept(value: str) -> Decimal | None:
"""Parse a string as a :class:`~decimal.Decimal`, returning ``None`` on failure.
Args:
value: The raw string to parse.
Returns:
A :class:`~decimal.Decimal` on success, or ``None`` if ``value``
cannot be converted (e.g. it is empty, contains letters, or is
otherwise not a valid decimal literal).
"""
try:
return Decimal(value)
except ValueError:
return None
[docs]
def parse_int_noexcept(value: str | None) -> int | None:
"""Parse a string as an integer, returning ``None`` on failure or absent input.
Args:
value: The raw string to parse, or ``None``.
Returns:
An ``int`` on success, or ``None`` if ``value`` is ``None`` or cannot
be converted to an integer.
"""
if value is None:
return None
try:
return int(value)
except ValueError:
return None
[docs]
def parse_uuid_noexcept(value: str | None) -> UUID | None:
"""Parse a string as a :class:`~uuid.UUID`, returning ``None`` on failure or absent input.
Accepts any UUID format recognised by :class:`uuid.UUID` (with or without
hyphens, upper or lower case).
Args:
value: The raw string to parse, or ``None``.
Returns:
A :class:`~uuid.UUID` on success, or ``None`` if ``value`` is ``None``
or not a valid UUID representation.
"""
if value is None:
return None
try:
return UUID(value)
except ValueError:
return None