27
27
from copy import deepcopy
28
28
from datetime import datetime
29
29
from enum import Enum
30
- from typing import Any , Callable , Dict , Optional , Union , List
30
+ from typing import Any , Callable , Dict , List , Optional , Tuple , Union
31
31
32
32
InputVal = Any
33
+ ConverterFunction = Callable [[Optional [InputVal ]], Optional [Any ]]
34
+ ConverterDefinition = Union [ConverterFunction , Tuple [ConverterFunction , "ConverterDefinition" ]]
33
35
34
36
35
37
def _to_ipaddress (value : Optional [str ]) -> Optional [Union [ipaddress .IPv4Address , ipaddress .IPv6Address ]]:
@@ -87,7 +89,7 @@ class DataType(Enum):
87
89
88
90
89
91
# Map data type identifier to converter function.
90
- _DEFAULT_CONVERTERS : Dict [DataType , Callable [[ Optional [ InputVal ]], Optional [ Any ]] ] = {
92
+ _DEFAULT_CONVERTERS : Dict [DataType , ConverterFunction ] = {
91
93
DataType .IP : _to_ipaddress ,
92
94
DataType .TIMESTAMP_WITH_TZ : _to_datetime ,
93
95
DataType .TIMESTAMP_WITHOUT_TZ : _to_datetime ,
@@ -97,37 +99,55 @@ class DataType(Enum):
97
99
class Converter :
98
100
def __init__ (
99
101
self ,
100
- mappings : Dict [DataType , Callable [[ Optional [ InputVal ]], Optional [ Any ] ]] = None ,
101
- default : Callable [[ Optional [ InputVal ]], Optional [ Any ]] = _to_default ,
102
+ mappings : Optional [ Dict [DataType , ConverterFunction ]] = None ,
103
+ default : ConverterFunction = _to_default ,
102
104
) -> None :
103
105
self ._mappings = mappings or {}
104
106
self ._default = default
105
107
106
108
@property
107
- def mappings (self ) -> Dict [DataType , Callable [[ Optional [ InputVal ]], Optional [ Any ]] ]:
109
+ def mappings (self ) -> Dict [DataType , ConverterFunction ]:
108
110
return self ._mappings
109
111
110
- def get (self , type_ : DataType ) -> Callable [[ Optional [ InputVal ]], Optional [ Any ]] :
112
+ def get (self , type_ : DataType ) -> ConverterFunction :
111
113
return self .mappings .get (type_ , self ._default )
112
114
113
- def set (self , type_ : DataType , converter : Callable [[ Optional [ InputVal ]], Optional [ Any ]] ) -> None :
115
+ def set (self , type_ : DataType , converter : ConverterFunction ) -> None :
114
116
self .mappings [type_ ] = converter
115
117
116
- def convert (self , type_ : Union [ DataType , int ] , value : Optional [Any ]) -> Optional [Any ]:
118
+ def convert (self , converter_definition : ConverterDefinition , value : Optional [Any ]) -> Optional [Any ]:
117
119
"""
118
- Convert a single row cell value with given data type. Invoked from `Cursor._convert_rows`.
120
+ Convert a single row cell value using given converter definition.
121
+ Also works recursively on nested values like `ARRAY` collections.
122
+ Invoked from `Cursor._convert_rows`.
119
123
"""
120
- if isinstance (type_ , List ):
121
- type_ , inner_type = type_
122
- if DataType (type_ ) is not DataType .ARRAY :
123
- raise ValueError (f"Data type { type_ } is not implemented as collection type" )
124
+ if isinstance (converter_definition , tuple ):
125
+ type_ , inner_type = converter_definition
124
126
if value is None :
125
127
result = self .convert (inner_type , None )
126
128
else :
127
129
result = [self .convert (inner_type , item ) for item in value ]
128
130
else :
129
- converter = self .get (DataType (type_ ))
130
- result = converter (value )
131
+ result = converter_definition (value )
132
+ return result
133
+
134
+ def col_type_to_converter (self , type_ : Union [int , List [int ]]) -> ConverterDefinition :
135
+ """
136
+ Resolve integer data type identifier to its corresponding converter function.
137
+ Also handles nested definitions with a *list* of data type identifiers on the
138
+ right hand side, describing the inner type of `ARRAY` values.
139
+
140
+ It is important to resolve the converter functions first, in order not to
141
+ hog the row loop with redundant lookups to the `mappings` dictionary.
142
+ """
143
+ result : ConverterDefinition
144
+ if isinstance (type_ , list ):
145
+ type_ , inner_type = type_
146
+ if DataType (type_ ) is not DataType .ARRAY :
147
+ raise ValueError (f"Data type { type_ } is not implemented as collection type" )
148
+ result = (self .get (DataType (type_ )), self .col_type_to_converter (inner_type ))
149
+ else :
150
+ result = self .get (DataType (type_ ))
131
151
return result
132
152
133
153
0 commit comments