strconv - Data conversion from/to string

Overview

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 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 bases 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:

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

Function that converts typed value to its string representation.

alias of Callable[Any, str]

firebird.base.strconv.TConvertFromStr

Function that converts string representation of typed value to typed value.

alias of Callable[type, str, Any]

Globals

firebird.base.strconv.TRUE_STR: list[str] = ['yes', 'true', 'on', 'y', '1']

Valid string literals for True value.

firebird.base.strconv.FALSE_STR: list[str] = ['no', 'false', 'off', 'n', '0']

Valid string literals for False value.

Functions

firebird.base.strconv.convert_to_str(value: Any) str[source]

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:

value (Any) – 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.

Return type:

str

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)

firebird.base.strconv.convert_from_str(cls: type | str, value: str) Any[source]

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

Parameters:
  • cls (type | str) – The target type object or type name (simple or full) to convert to.

  • value (str) – The string value to be converted.

Return type:

Any

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).

Parameters:
Return type:

Any

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'

firebird.base.strconv.register_convertor(cls: type, *, to_str: ~collections.abc.Callable[[~typing.Any], str] = <function any2str>, from_str: ~collections.abc.Callable[[type, str], ~typing.Any] = <function str2any>) None[source]

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:
  • cls (type) – Class to register convertor for.

  • to_str (Callable[[Any], str]) – Optional function that converts an instance of cls to str. Defaults to any2str.

  • from_str (Callable[[type, str], Any]) – Optional function that converts str to value of cls data type. Defaults to str2any.

Return type:

None

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)

firebird.base.strconv.update_convertor(cls: type | str, *, to_str: Callable[[Any], str] | None = None, from_str: Callable[[type, str], Any] | None = None) None[source]

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

Parameters:
  • cls (type | str) – Class or class name whose convertor needs updating.

  • to_str (Callable[[Any], str] | None) – Optional new function that converts cls value to str.

  • from_str (Callable[[type, str], Any] | None) – 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.

Return type:

None

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

firebird.base.strconv.register_class(cls: type) None[source]

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.

Parameters:

cls (type) – Class to be registered.

Raises:

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

Return type:

None


firebird.base.strconv.has_convertor(cls: type | str) bool[source]

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

Parameters:

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”).

Return type:

bool

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

firebird.base.strconv.get_convertor(cls: type | str) Convertor[source]

“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:

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”).

Return type:

Convertor

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.

Parameters:

cls (type | str) –

Return type:

Convertor

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

firebird.base.strconv.any2str(value: Any) str[source]

Converts value to string using str(value).

This is the default to_str convertor function.

Parameters:

value (Any) – The value to convert.

Returns:

The string representation of the value.

Return type:

str


firebird.base.strconv.str2any(cls: type, value: str) Any[source]

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:
  • cls (type) – The target data type.

  • value (str) – The string representation to convert.

Returns:

An instance of cls created from the string value.

Return type:

Any

Dataclasses

class firebird.base.strconv.Convertor(cls: type, to_str: Callable[[Any], str], from_str: Callable[[type, str], Any])[source]

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:
  • cls (type) – The data type (class) this convertor handles.

  • to_str (Callable[[Any], str]) – The function converting an instance of cls to a string.

  • from_str (Callable[[type, str], Any]) – The function converting a string back to an instance of cls.

get_key() Hashable[source]

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

Return type:

Hashable

cls: type

The data type (class) this convertor handles.

from_str: Callable[[type, str], Any]

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

property full_name: str

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

property name: str

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

to_str: Callable[[Any], str]

The function converting an instance of cls to a string.