Skip to content

strconv - Data conversion from/to string

Overview

firebird.base.strconv

This module provides a centralized mechanism for converting various Python data types to and from their string representations. It allows registering custom conversion functions for specific types, making the conversion process extensible and decoupled from the types themselves.

Core features include:

  • Registration of type-specific string conversion functions.
  • Lookup of convertors based on type or type name (simple or full).
  • Helper functions (convert_to_str, convert_from_str) for easy conversion.
  • Built-in support for common types (str, int, float, bool, Decimal, UUID, Enum, etc.).
Example
from firebird.base.strconv import convert_to_str, convert_from_str
from decimal import Decimal

# Convert Decimal to string
s = convert_to_str(Decimal('123.45'))
print(s)  # Output: 123.45

# Convert string back to Decimal
d = convert_from_str(Decimal, '123.45')
print(d)  # Output: Decimal('123.45')

# Boolean conversion
b = convert_from_str(bool, 'yes')
print(b) # Output: True
s = convert_to_str(False)
print(s) # Output: no

While Python types typically support conversion to string via builtin str() function (and custom __str__ methods), there is no symetric operation that converts string created by str() back to typed value. This module provides support for such symetric conversion from/to string for any data type.

Note

Symetric string conversion is used by firebird.base.config module, notably by ListOption and DataclassOption. You can extend the range of data types supported by these options by registering convertors for required data types.

Architecture

Module maintains a global registry of convertors associated with a data type. These convertor functions may support conversion for multiple data types, and must have signatures:

# Conversion to string

def value2str(value: Any) -> str:
    ...

# Conversion from string

def str2value(cls: Type, value: str) -> Any:
    ...

Convertors should raise ValueError when conversion fails.

Convertors must be registered using register_convertor function, or reassigned using update_convertor.

There are two methods for use of registered convertors:

  1. By using functions convert_to_str and convert_from_str. These functions use convertor that is registered for particular data type. If there is no such convertor, they use first convertor registered for any base class. The lookup is performed in Method Resolution Order. This method is preferred when you will work with various data types.
  2. Using get_convertor function to obtain registry entry with convertors.

This method is preferred when you will work with single data type repeatedly.

Important

The convertor registry lookup could be done either for a class, or class name.

Lookup for a class is first performed for specified class. If there is no such entry, all base classes in Method Resolution Order are used for lookup, and the first entry found is returned.

Lookup for a class name could be performed only for a specified class. Because some types could be converted using convertors registered for their base class, such lookup will not find the required convertor entry. To circumvent this issue, it's necessary to register a class for name lookup using register_class function. The registry lookup uses this class registry to use the actual type instead type name.

Registered data types

Module registers convertor functions for next data types:

  • str
  • int
  • float
  • complex
  • bool. Uses TRUE_STR and FALSE_STR list for conversion from string (the case is NOT significant). Conversion to string always uses first item in these lists.
  • decimal.Decimal
  • uuid.UUID
  • MIME
  • ZMQAddress
  • enum.Enum and enum.Flag. Supports conversion for all descendants of Enum, IntEnum, Flag and IntFlag.

Tip

Functions any2str and str2any could be used to register conversion for data types that support symetric conversion via __str__() method and class constructor (__init__() must accept one positional argument that could be a string and all other arguments must be keyword arguments with default values).

Types for annotations

firebird.base.strconv.TConvertToStr: TypeAlias module-attribute

firebird.base.strconv.TConvertFromStr: TypeAlias module-attribute

Globals

firebird.base.strconv.TRUE_STR: list[str] module-attribute

Valid string literals for True value.

firebird.base.strconv.FALSE_STR: list[str] module-attribute

Valid string literals for False value.

Functions

firebird.base.strconv.convert_to_str(value: Any) -> str

Converts a value to its string representation using its registered convertor.

Looks up the convertor based on the value's class (value.__class__). If there is no direct convertor registered for the value's specific class, it searches the Method Resolution Order (MRO) for a convertor registered for a base class.

Parameters:

Name Type Description Default
value Any

The value to be converted to a string.

required

Raises:

Type Description
TypeError

If no convertor is found for the value's class or any of its base classes in the MRO.

Example
from decimal import Decimal
from uuid import uuid4
from firebird.base.strconv import convert_to_str, register_convertor

print(convert_to_str(123))           # Output: '123'
print(convert_to_str(Decimal('1.2'))) # Output: '1.2'
print(convert_to_str(True))          # Output: 'yes'
my_uuid = uuid4()
print(convert_to_str(my_uuid))       # Output: UUID string representation

class MyBase: pass
class MyDerived(MyBase): pass
register_convertor(MyBase, to_str=lambda v: "BaseStr")
instance = MyDerived()
print(convert_to_str(instance))      # Output: 'BaseStr' (uses MyBase convertor)
Source code in firebird/base/strconv.py
def convert_to_str(value: Any) -> str:
    """Converts a value to its string representation using its registered convertor.

    Looks up the convertor based on the value's class (`value.__class__`).
    If there is no direct convertor registered for the value's specific class,
    it searches the Method Resolution Order (MRO) for a convertor registered
    for a base class.

    Arguments:
        value: The value to be converted to a string.

    Raises:
        TypeError: If no convertor is found for the value's class or any of
                   its base classes in the MRO.

    Example:
        ```python
        from decimal import Decimal
        from uuid import uuid4
        from firebird.base.strconv import convert_to_str, register_convertor

        print(convert_to_str(123))           # Output: '123'
        print(convert_to_str(Decimal('1.2'))) # Output: '1.2'
        print(convert_to_str(True))          # Output: 'yes'
        my_uuid = uuid4()
        print(convert_to_str(my_uuid))       # Output: UUID string representation

        class MyBase: pass
        class MyDerived(MyBase): pass
        register_convertor(MyBase, to_str=lambda v: "BaseStr")
        instance = MyDerived()
        print(convert_to_str(instance))      # Output: 'BaseStr' (uses MyBase convertor)
        ```
    """
    return get_convertor(value.__class__).to_str(value)

firebird.base.strconv.convert_from_str(cls: type | str, value: str) -> Any

Converts a string representation back to a typed value using a registered convertor.

Parameters:

Name Type Description Default
cls type | str

The target type object or type name (simple or full) to convert to.

required
value str

The string value to be converted.

required
Note

When cls is a type name:

  1. If the class name is NOT registered via register_class(), MRO lookup for base class convertors is not possible if an exact name match isn't found.
  2. If a simple class name is provided and is ambiguous (multiple registered classes with the same name), the first match found is used. Use full names ('module.ClassName') for clarity in such cases.

Raises:

Type Description
TypeError

If no convertor is found for cls or any of its base classes (when MRO lookup is possible).

ValueError

Often raised by the underlying from_str function if the string value is not in the expected format for the target type (e.g., converting 'abc' to int).

Example
from decimal import Decimal
from uuid import UUID
from firebird.base.strconv import convert_from_str

num = convert_from_str(int, '123')        # Output: 123 (int)
dec = convert_from_str(Decimal, '1.2')    # Output: Decimal('1.2')
flag = convert_from_str(bool, 'off')      # Output: False (bool)
uid = convert_from_str(UUID, '...')       # Output: UUID object
# Using string name
dec_from_name = convert_from_str('Decimal', '3.14') # Output: Decimal('3.14')

try:
   convert_from_str(int, 'not-a-number')
except ValueError as e:
   print(e) # Example: invalid literal for int() with base 10: 'not-a-number'
Source code in firebird/base/strconv.py
def convert_from_str(cls: type | str, value: str) -> Any:
    """Converts a string representation back to a typed value using a registered convertor.

    Arguments:
        cls:   The target type object or type name (simple or full) to convert to.
        value: The string value to be converted.

    Note:
        When `cls` is a type name:

        1. If the class name is NOT registered via `register_class()`, MRO lookup for
           base class convertors is not possible if an exact name match isn't found.
        2. If a simple class name is provided and is ambiguous (multiple registered
           classes with the same name), the first match found is used. Use full names
           ('module.ClassName') for clarity in such cases.

    Raises:
        TypeError: If no convertor is found for `cls` or any of its base classes (when MRO lookup is possible).
        ValueError: Often raised by the underlying `from_str` function if the string
                    `value` is not in the expected format for the target type (e.g.,
                    converting 'abc' to int).

    Example:
        ```python
        from decimal import Decimal
        from uuid import UUID
        from firebird.base.strconv import convert_from_str

        num = convert_from_str(int, '123')        # Output: 123 (int)
        dec = convert_from_str(Decimal, '1.2')    # Output: Decimal('1.2')
        flag = convert_from_str(bool, 'off')      # Output: False (bool)
        uid = convert_from_str(UUID, '...')       # Output: UUID object
        # Using string name
        dec_from_name = convert_from_str('Decimal', '3.14') # Output: Decimal('3.14')

        try:
           convert_from_str(int, 'not-a-number')
        except ValueError as e:
           print(e) # Example: invalid literal for int() with base 10: 'not-a-number'
        ```
    """
    return get_convertor(cls).from_str(cls, value)

firebird.base.strconv.register_convertor(cls: type, *, to_str: TConvertToStr = any2str, from_str: TConvertFromStr = str2any) -> None

Registers convertor function(s) for a specific data type.

If to_str or from_str are not provided, default convertors (any2str, str2any) based on str() and cls() are used.

Parameters:

Name Type Description Default
cls type

Class to register convertor for.

required
to_str TConvertToStr

Optional function that converts an instance of cls to str. Defaults to any2str.

any2str
from_str TConvertFromStr

Optional function that converts str to value of cls data type. Defaults to str2any.

str2any
Example
from datetime import date
from firebird.base.strconv import register_convertor, convert_to_str, convert_from_str

# Register custom convertors for date
def date_to_iso(value: date) -> str:
    return value.isoformat()

def iso_to_date(cls: type, value: str) -> date:
    return cls.fromisoformat(value)

register_convertor(date, to_str=date_to_iso, from_str=iso_to_date)

d = date(2023, 10, 27)
s = convert_to_str(d) # Uses date_to_iso -> '2023-10-27'
d2 = convert_from_str(date, s) # Uses iso_to_date -> date(2023, 10, 27)
Source code in firebird/base/strconv.py
def register_convertor(cls: type, *, to_str: TConvertToStr=any2str,
                       from_str: TConvertFromStr=str2any) -> None:
    """Registers convertor function(s) for a specific data type.

    If `to_str` or `from_str` are not provided, default convertors (`any2str`,
    `str2any`) based on `str()` and `cls()` are used.

    Arguments:
        cls:      Class to register convertor for.
        to_str:   Optional function that converts an instance of `cls` to `str`.
                  Defaults to `any2str`.
        from_str: Optional function that converts `str` to value of `cls` data type.
                  Defaults to `str2any`.

    Example:
        ```python
        from datetime import date
        from firebird.base.strconv import register_convertor, convert_to_str, convert_from_str

        # Register custom convertors for date
        def date_to_iso(value: date) -> str:
            return value.isoformat()

        def iso_to_date(cls: type, value: str) -> date:
            return cls.fromisoformat(value)

        register_convertor(date, to_str=date_to_iso, from_str=iso_to_date)

        d = date(2023, 10, 27)
        s = convert_to_str(d) # Uses date_to_iso -> '2023-10-27'
        d2 = convert_from_str(date, s) # Uses iso_to_date -> date(2023, 10, 27)
        ```
    """
    _convertors.store(Convertor(cls, to_str, from_str))

firebird.base.strconv.update_convertor(cls: type | str, *, to_str: TConvertToStr | None = None, from_str: TConvertFromStr | None = None) -> None

Update the to_str and/or from_str functions for an existing convertor.

Parameters:

Name Type Description Default
cls type | str

Class or class name whose convertor needs updating.

required
to_str TConvertToStr | None

Optional new function that converts cls value to str.

None
from_str TConvertFromStr | None

Optional new function that converts str to value of cls data type.

None

Raises:

Type Description
TypeError

If the data type (or its name) has no registered convertor.

Example
from firebird.base.strconv import update_convertor, convert_to_str

# Assume BoolConvertor exists and uses 'yes'/'no'
# Change bool to output 'TRUE'/'FALSE'
update_convertor(bool, to_str=lambda v: 'TRUE' if v else 'FALSE')
print(convert_to_str(True)) # Output: TRUE
Source code in firebird/base/strconv.py
def update_convertor(cls: type | str, *,
                     to_str: TConvertToStr | None=None,
                     from_str: TConvertFromStr | None=None) -> None:
    """Update the `to_str` and/or `from_str` functions for an existing convertor.

    Arguments:
        cls:      Class or class name whose convertor needs updating.
        to_str:   Optional new function that converts `cls` value to `str`.
        from_str: Optional new function that converts `str` to value of `cls` data type.

    Raises:
        TypeError: If the data type (or its name) has no registered convertor.

    Example:
        ```python
        from firebird.base.strconv import update_convertor, convert_to_str

        # Assume BoolConvertor exists and uses 'yes'/'no'
        # Change bool to output 'TRUE'/'FALSE'
        update_convertor(bool, to_str=lambda v: 'TRUE' if v else 'FALSE')
        print(convert_to_str(True)) # Output: TRUE
        ```
    """
    conv: Convertor = get_convertor(cls)
    if to_str:
        conv.to_str = to_str
    if from_str:
        conv.from_str = from_str

firebird.base.strconv.register_class(cls: type) -> None

Registers a class name for lookup, primarily for string-based conversions.

This allows functions like has_convertor, get_convertor, and convert_from_str to find the correct convertor when given a simple class name (e.g., "MyClass") as a string, instead of the class object itself. Registration is particularly useful when:

  1. Performing lookups based on class names stored as strings.
  2. Resolving potential ambiguity if multiple classes with the same simple name exist in different modules (though using full names like 'module.MyClass' is generally safer in such cases).
  3. Enabling MRO (Method Resolution Order) lookup for base class convertors when the lookup starts with a string name.
See Also

has_convertor, get_convertor, convert_from_str

Parameters:

Name Type Description Default
cls type

Class to be registered.

required

Raises:

Type Description
TypeError

When the simple class name (cls.__name__) is already registered.

Source code in firebird/base/strconv.py
def register_class(cls: type) -> None:
    """Registers a class name for lookup, primarily for string-based conversions.

    This allows functions like `has_convertor`, `get_convertor`, and `convert_from_str`
    to find the correct convertor when given a simple class name (e.g., "MyClass")
    as a string, instead of the class object itself. Registration is particularly
    useful when:

    1. Performing lookups based on class names stored as strings.
    2. Resolving potential ambiguity if multiple classes with the same simple name
       exist in different modules (though using full names like 'module.MyClass'
       is generally safer in such cases).
    3. Enabling MRO (Method Resolution Order) lookup for base class convertors
       when the lookup starts with a string name.

    See Also:
        `has_convertor`, `get_convertor`, `convert_from_str`

    Arguments:
        cls: Class to be registered.

    Raises:
        TypeError: When the simple class name (`cls.__name__`) is already registered.
    """
    if cls.__name__ in _classes:
        raise TypeError(f"Class '{cls.__name__}' already registered as '{_classes[cls.__name__]!r}'")
    _classes[cls.__name__] = cls

firebird.base.strconv.has_convertor(cls: type | str) -> bool

Returns True if a convertor is registered for the class or its bases.

Parameters:

Name Type Description Default
cls type | str

Type object or type name. The name could be a simple class name (e.g., "MyClass") or a full name including the module (e.g., "my_module.MyClass").

required
Note

When cls is a name:

  1. If the class name is NOT registered via register_class(), it's not possible to perform MRO lookup for base class convertors. Only an exact match on the name (simple or full) will work.
  2. If a simple class name is provided and multiple classes of the same name but from different modules have registered convertors (or been registered via register_class), the lookup might be ambiguous. Using full names is recommended in such scenarios.
Example
from decimal import Decimal
from firebird.base.strconv import register_convertor, has_convertor, register_class

print(has_convertor(Decimal))  # Output: True (built-in)
print(has_convertor('Decimal')) # Output: True (built-in, simple name works)
print(has_convertor('decimal.Decimal')) # Output: True (full name)

class MyData: pass
class MySubData(MyData): pass

register_convertor(MyData)
register_class(MySubData) # Register subclass name

print(has_convertor(MySubData))   # Output: True (finds MyData via MRO)
print(has_convertor('MySubData')) # Output: True (finds MyData via MRO because name is registered)
print(has_convertor('NonExistent')) # Output: False
Source code in firebird/base/strconv.py
def has_convertor(cls: type | str) -> bool:
    """Returns True if a convertor is registered for the class or its bases.

    Arguments:
        cls: Type object or type name. The name could be a simple class name
             (e.g., "MyClass") or a full name including the module
             (e.g., "my_module.MyClass").

    Note:
        When `cls` is a name:

        1. If the class name is NOT registered via `register_class()`, it's not
           possible to perform MRO lookup for base class convertors. Only an exact
           match on the name (simple or full) will work.
        2. If a simple class name is provided and multiple classes of the same
           name but from different modules have registered convertors (or been
           registered via `register_class`), the lookup might be ambiguous. Using
           full names is recommended in such scenarios.

    Example:
        ```python
        from decimal import Decimal
        from firebird.base.strconv import register_convertor, has_convertor, register_class

        print(has_convertor(Decimal))  # Output: True (built-in)
        print(has_convertor('Decimal')) # Output: True (built-in, simple name works)
        print(has_convertor('decimal.Decimal')) # Output: True (full name)

        class MyData: pass
        class MySubData(MyData): pass

        register_convertor(MyData)
        register_class(MySubData) # Register subclass name

        print(has_convertor(MySubData))   # Output: True (finds MyData via MRO)
        print(has_convertor('MySubData')) # Output: True (finds MyData via MRO because name is registered)
        print(has_convertor('NonExistent')) # Output: False
        ```
    """
    return _get_convertor(cls) is not None

firebird.base.strconv.get_convertor(cls: type | str) -> Convertor

"Returns the Convertor object registered for a data type or its bases.

This function performs the lookup based on the type or type name, including MRO search for base classes if necessary and possible. It is used internally by convert_to_str and convert_from_str, but can be called directly if you need access to the Convertor instance itself, for example, for introspection or direct access to the to_str/from_str functions.

Parameters:

Name Type Description Default
cls type | str

Type object or type name. The name could be a simple class name (e.g., "MyClass") or a full name including the module (e.g., "my_module.MyClass").

required
Note

When cls is a name:

  1. If the class name is NOT registered via register_class(), MRO lookup for base class convertors is not possible if an exact name match isn't found.
  2. If a simple class name is provided and is ambiguous (multiple registered classes with the same name), the first match found is used. Use full names for clarity.

Raises:

Type Description
TypeError

If no convertor is found for cls or any of its base classes.

Example
from decimal import Decimal
from firebird.base.strconv import get_convertor

decimal_conv = get_convertor(Decimal)
print(decimal_conv.name) # Output: Decimal
print(decimal_conv.to_str(Decimal('9.87'))) # Output: 9.87

bool_conv = get_convertor('bool') # Lookup by name
print(bool_conv.from_str(bool, 'TRUE')) # Output: True
Source code in firebird/base/strconv.py
def get_convertor(cls: type | str) -> Convertor:
    """"Returns the Convertor object registered for a data type or its bases.

    This function performs the lookup based on the type or type name, including
    MRO search for base classes if necessary and possible. It is used internally
    by `convert_to_str` and `convert_from_str`, but can be called directly
    if you need access to the `Convertor` instance itself, for example,
    for introspection or direct access to the `to_str`/`from_str` functions.

    Arguments:
        cls: Type object or type name. The name could be a simple class name
             (e.g., "MyClass") or a full name including the module
             (e.g., "my_module.MyClass").

    Note:
        When `cls` is a name:

        1. If the class name is NOT registered via `register_class()`, MRO lookup for
           base class convertors is not possible if an exact name match isn't found.
        2. If a simple class name is provided and is ambiguous (multiple registered
           classes with the same name), the first match found is used. Use full names
           for clarity.

    Raises:
        TypeError: If no convertor is found for `cls` or any of its base classes.

    Example:
        ```python
        from decimal import Decimal
        from firebird.base.strconv import get_convertor

        decimal_conv = get_convertor(Decimal)
        print(decimal_conv.name) # Output: Decimal
        print(decimal_conv.to_str(Decimal('9.87'))) # Output: 9.87

        bool_conv = get_convertor('bool') # Lookup by name
        print(bool_conv.from_str(bool, 'TRUE')) # Output: True
        ```
    """
    if (conv := _get_convertor(cls)) is None:
        raise TypeError(f"Type '{cls.__name__ if isinstance(cls, type) else cls}' has no Convertor")
    return conv

firebird.base.strconv.any2str(value: Any) -> str

Converts value to string using str(value).

This is the default to_str convertor function.

Parameters:

Name Type Description Default
value Any

The value to convert.

required

Returns:

Type Description
str

The string representation of the value.

Source code in firebird/base/strconv.py
def any2str(value: Any) -> str:
    """Converts value to string using `str(value)`.

    This is the default `to_str` convertor function.

    Arguments:
       value: The value to convert.

    Returns:
       The string representation of the value.
    """
    return str(value)

firebird.base.strconv.str2any(cls: type, value: str) -> Any

Converts string to data type value using type(value).

This is the default from_str convertor function. It assumes the type's constructor can handle a single string argument.

Parameters:

Name Type Description Default
cls type

The target data type.

required
value str

The string representation to convert.

required

Returns:

Type Description
Any

An instance of cls created from the string value.

Source code in firebird/base/strconv.py
def str2any(cls: type, value: str) -> Any:
    """Converts string to data type value using `type(value)`.

    This is the default `from_str` convertor function. It assumes the
    type's constructor can handle a single string argument.

    Arguments:
      cls: The target data type.
      value: The string representation to convert.

    Returns:
      An instance of `cls` created from the string value.
    """
    return cls(value)

Dataclasses

firebird.base.strconv.Convertor dataclass

Bases: Distinct

Data convertor registry entry.

Holds the functions responsible for converting a specific data type to and from its string representation. Instances of this class are stored in the internal registry.

Parameters:

Name Type Description Default
cls type

The data type (class) this convertor handles.

required
to_str TConvertToStr

The function converting an instance of cls to a string.

required
from_str TConvertFromStr

The function converting a string back to an instance of cls.

required
Source code in firebird/base/strconv.py
@dataclass
class Convertor(Distinct):
    """Data convertor registry entry.

    Holds the functions responsible for converting a specific data type
    to and from its string representation. Instances of this class are
    stored in the internal registry.

    Arguments:
        cls: The data type (class) this convertor handles.
        to_str: The function converting an instance of `cls` to a string.
        from_str: The function converting a string back to an instance of `cls`.
    """
    cls: type
    "The data type (class) this convertor handles."
    to_str: TConvertToStr
    "The function converting an instance of `cls` to a string."
    from_str: TConvertFromStr
    "The function converting a string back to an instance of `cls`."
    def get_key(self) -> Hashable:
        """Returns instance key (the class itself), used by the `Registry`."""
        return self.cls
    @property
    def name(self) -> str:
        """Simple type name (e.g., [int][], [Decimal][decimal.Decimal])."""
        return self.cls.__name__
    @property
    def full_name(self) -> str:
        """Type name including source module (e.g., [decimal.Decimal][])."""
        return f'{self.cls.__module__}.{self.cls.__name__}'

cls: type instance-attribute

The data type (class) this convertor handles.

from_str: TConvertFromStr instance-attribute

The function converting a string back to an instance of cls.

full_name: str property

Type name including source module (e.g., decimal.Decimal).

name: str property

Simple type name (e.g., int, Decimal).

to_str: TConvertToStr instance-attribute

The function converting an instance of cls to a string.

get_key() -> Hashable

Returns instance key (the class itself), used by the Registry.

Source code in firebird/base/strconv.py
def get_key(self) -> Hashable:
    """Returns instance key (the class itself), used by the `Registry`."""
    return self.cls