Skip to content

Commit 03f5bd7

Browse files
authored
feat: configure types of integers fields when initializing Point from dict structure (#538)
1 parent 2d1fb2c commit 03f5bd7

File tree

3 files changed

+103
-4
lines changed

3 files changed

+103
-4
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
### Features
44
1. [#536](https://github.com/influxdata/influxdb-client-python/pull/536): Query to `CSV` skip empty lines
5+
1. [#538](https://github.com/influxdata/influxdb-client-python/pull/538): Configure types of `integer` fields when initializing `Point` from `dict` structure
56

67
## 1.35.0 [2022-12-01]
78

influxdb_client/client/write/point.py

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,15 +99,41 @@ def from_dict(dictionary: dict, write_precision: WritePrecision = DEFAULT_WRITE_
9999
record_tag_keys=["location", "version"],
100100
record_field_keys=["pressure", "temperature"])
101101
102+
Int Types:
103+
The following example shows how to configure the types of integers fields.
104+
It is useful when you want to serialize integers always as ``float`` to avoid ``field type conflict``
105+
or use ``unsigned 64-bit integer`` as the type for serialization.
106+
107+
.. code-block:: python
108+
109+
# Use custom dictionary structure
110+
dict_structure = {
111+
"measurement": "h2o_feet",
112+
"tags": {"location": "coyote_creek"},
113+
"fields": {
114+
"water_level": 1.0,
115+
"some_counter": 108913123234
116+
},
117+
"time": 1
118+
}
119+
120+
point = Point.from_dict(dict_structure, field_types={"some_counter": "uint"})
121+
102122
:param dictionary: dictionary for serialize into data Point
103123
:param write_precision: sets the precision for the supplied time values
104124
:key record_measurement_key: key of dictionary with specified measurement
105125
:key record_measurement_name: static measurement name for data Point
106126
:key record_time_key: key of dictionary with specified timestamp
107127
:key record_tag_keys: list of dictionary keys to use as a tag
108128
:key record_field_keys: list of dictionary keys to use as a field
129+
:key field_types: optional dictionary to specify types of serialized fields. Currently, is supported customization for integer types.
130+
Possible integers types:
131+
- ``int`` - serialize integers as "**Signed 64-bit integers**" - ``9223372036854775807i`` (default behaviour)
132+
- ``uint`` - serialize integers as "**Unsigned 64-bit integers**" - ``9223372036854775807u``
133+
- ``float`` - serialize integers as "**IEEE-754 64-bit floating-point numbers**". Useful for unify number types in your pipeline to avoid field type conflict - ``9223372036854775807``
134+
The ``field_types`` can be also specified as part of incoming dictionary. For more info see an example above.
109135
:return: new data point
110-
"""
136+
""" # noqa: E501
111137
measurement_ = kwargs.get('record_measurement_name', None)
112138
if measurement_ is None:
113139
measurement_ = dictionary[kwargs.get('record_measurement_key', 'measurement')]
@@ -134,6 +160,19 @@ def from_dict(dictionary: dict, write_precision: WritePrecision = DEFAULT_WRITE_
134160
record_time_key = kwargs.get('record_time_key', 'time')
135161
if record_time_key in dictionary:
136162
point.time(dictionary[record_time_key], write_precision=write_precision)
163+
164+
_field_types = kwargs.get('field_types', {})
165+
if 'field_types' in dictionary:
166+
_field_types = dictionary['field_types']
167+
# Map API fields types to Line Protocol types postfix:
168+
# - int: 'i'
169+
# - uint: 'u'
170+
# - float: ''
171+
point._field_types = dict(map(
172+
lambda item: (item[0], 'i' if item[1] == 'int' else 'u' if item[1] == 'uint' else ''),
173+
_field_types.items()
174+
))
175+
137176
return point
138177

139178
def __init__(self, measurement_name):
@@ -143,6 +182,7 @@ def __init__(self, measurement_name):
143182
self._name = measurement_name
144183
self._time = None
145184
self._write_precision = DEFAULT_WRITE_PRECISION
185+
self._field_types = {}
146186

147187
def time(self, time, write_precision=DEFAULT_WRITE_PRECISION):
148188
"""
@@ -190,7 +230,7 @@ def to_line_protocol(self, precision=None):
190230
"""
191231
warnings.warn(message, SyntaxWarning)
192232
_tags = _append_tags(self._tags)
193-
_fields = _append_fields(self._fields)
233+
_fields = _append_fields(self._fields, self._field_types)
194234
if not _fields:
195235
return ""
196236
_time = _append_time(self._time, self._write_precision if precision is None else precision)
@@ -227,7 +267,7 @@ def _append_tags(tags):
227267
return f"{',' if _return else ''}{','.join(_return)} "
228268

229269

230-
def _append_fields(fields):
270+
def _append_fields(fields, field_types):
231271
_return = []
232272

233273
for field, value in sorted(fields.items()):
@@ -246,7 +286,8 @@ def _append_fields(fields):
246286
s = s[:-2]
247287
_return.append(f'{_escape_key(field)}={s}')
248288
elif (isinstance(value, int) or _np_is_subtype(value, 'int')) and not isinstance(value, bool):
249-
_return.append(f'{_escape_key(field)}={str(value)}i')
289+
_type = field_types.get(field, "i")
290+
_return.append(f'{_escape_key(field)}={str(value)}{_type}')
250291
elif isinstance(value, bool):
251292
_return.append(f'{_escape_key(field)}={str(value).lower()}')
252293
elif isinstance(value, str):

tests/test_point.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,6 +482,63 @@ def test_from_dictionary_tolerant_to_missing_tags_and_fields(self):
482482
record_field_keys=["pressure", "temperature"])
483483
self.assertEqual("sensor_pt859,location=warehouse_125 pressure=125i", point.to_line_protocol())
484484

485+
def test_from_dictionary_uint(self):
486+
dict_structure = {
487+
"measurement": "h2o_feet",
488+
"tags": {"location": "coyote_creek"},
489+
"fields": {
490+
"water_level": 1.0,
491+
"some_counter": 108913123234
492+
},
493+
"time": 1
494+
}
495+
point = Point.from_dict(dict_structure, field_types={"some_counter": "uint"})
496+
self.assertEqual("h2o_feet,location=coyote_creek some_counter=108913123234u,water_level=1 1",
497+
point.to_line_protocol())
498+
499+
def test_from_dictionary_int(self):
500+
dict_structure = {
501+
"measurement": "h2o_feet",
502+
"tags": {"location": "coyote_creek"},
503+
"fields": {
504+
"water_level": 1.0,
505+
"some_counter": 108913123234
506+
},
507+
"time": 1
508+
}
509+
point = Point.from_dict(dict_structure, field_types={"some_counter": "int"})
510+
self.assertEqual("h2o_feet,location=coyote_creek some_counter=108913123234i,water_level=1 1",
511+
point.to_line_protocol())
512+
513+
def test_from_dictionary_float(self):
514+
dict_structure = {
515+
"measurement": "h2o_feet",
516+
"tags": {"location": "coyote_creek"},
517+
"fields": {
518+
"water_level": 1.0,
519+
"some_counter": 108913123234
520+
},
521+
"time": 1
522+
}
523+
point = Point.from_dict(dict_structure, field_types={"some_counter": "float"})
524+
self.assertEqual("h2o_feet,location=coyote_creek some_counter=108913123234,water_level=1 1",
525+
point.to_line_protocol())
526+
527+
def test_from_dictionary_float_from_dict(self):
528+
dict_structure = {
529+
"measurement": "h2o_feet",
530+
"tags": {"location": "coyote_creek"},
531+
"fields": {
532+
"water_level": 1.0,
533+
"some_counter": 108913123234
534+
},
535+
"field_types": {"some_counter": "float"},
536+
"time": 1
537+
}
538+
point = Point.from_dict(dict_structure)
539+
self.assertEqual("h2o_feet,location=coyote_creek some_counter=108913123234,water_level=1 1",
540+
point.to_line_protocol())
541+
485542
def test_static_measurement_name(self):
486543
dictionary = {
487544
"name": "sensor_pt859",

0 commit comments

Comments
 (0)