Skip to content

feat: configure types of integers fields when initializing Point from dict structure #538

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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

## 1.35.0 [2022-12-01]

Expand Down
49 changes: 45 additions & 4 deletions influxdb_client/client/write/point.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,15 +99,41 @@ def from_dict(dictionary: dict, write_precision: WritePrecision = DEFAULT_WRITE_
record_tag_keys=["location", "version"],
record_field_keys=["pressure", "temperature"])

Int Types:
The following example shows how to configure the types of integers fields.
It is useful when you want to serialize integers always as ``float`` to avoid ``field type conflict``
or use ``unsigned 64-bit integer`` as the type for serialization.

.. code-block:: python

# Use custom dictionary structure
dict_structure = {
"measurement": "h2o_feet",
"tags": {"location": "coyote_creek"},
"fields": {
"water_level": 1.0,
"some_counter": 108913123234
},
"time": 1
}

point = Point.from_dict(dict_structure, field_types={"some_counter": "uint"})

:param dictionary: dictionary for serialize into data Point
:param write_precision: sets the precision for the supplied time values
:key record_measurement_key: key of dictionary with specified measurement
:key record_measurement_name: static measurement name for data Point
:key record_time_key: key of dictionary with specified timestamp
:key record_tag_keys: list of dictionary keys to use as a tag
:key record_field_keys: list of dictionary keys to use as a field
:key field_types: optional dictionary to specify types of serialized fields. Currently, is supported customization for integer types.
Possible integers types:
- ``int`` - serialize integers as "**Signed 64-bit integers**" - ``9223372036854775807i`` (default behaviour)
- ``uint`` - serialize integers as "**Unsigned 64-bit integers**" - ``9223372036854775807u``
- ``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``
The ``field_types`` can be also specified as part of incoming dictionary. For more info see an example above.
:return: new data point
"""
""" # noqa: E501
measurement_ = kwargs.get('record_measurement_name', None)
if measurement_ is None:
measurement_ = dictionary[kwargs.get('record_measurement_key', 'measurement')]
Expand All @@ -134,6 +160,19 @@ def from_dict(dictionary: dict, write_precision: WritePrecision = DEFAULT_WRITE_
record_time_key = kwargs.get('record_time_key', 'time')
if record_time_key in dictionary:
point.time(dictionary[record_time_key], write_precision=write_precision)

_field_types = kwargs.get('field_types', {})
if 'field_types' in dictionary:
_field_types = dictionary['field_types']
# Map API fields types to Line Protocol types postfix:
# - int: 'i'
# - uint: 'u'
# - float: ''
point._field_types = dict(map(
lambda item: (item[0], 'i' if item[1] == 'int' else 'u' if item[1] == 'uint' else ''),
_field_types.items()
))

return point

def __init__(self, measurement_name):
Expand All @@ -143,6 +182,7 @@ def __init__(self, measurement_name):
self._name = measurement_name
self._time = None
self._write_precision = DEFAULT_WRITE_PRECISION
self._field_types = {}

def time(self, time, write_precision=DEFAULT_WRITE_PRECISION):
"""
Expand Down Expand Up @@ -190,7 +230,7 @@ def to_line_protocol(self, precision=None):
"""
warnings.warn(message, SyntaxWarning)
_tags = _append_tags(self._tags)
_fields = _append_fields(self._fields)
_fields = _append_fields(self._fields, self._field_types)
if not _fields:
return ""
_time = _append_time(self._time, self._write_precision if precision is None else precision)
Expand Down Expand Up @@ -227,7 +267,7 @@ def _append_tags(tags):
return f"{',' if _return else ''}{','.join(_return)} "


def _append_fields(fields):
def _append_fields(fields, field_types):
_return = []

for field, value in sorted(fields.items()):
Expand All @@ -246,7 +286,8 @@ def _append_fields(fields):
s = s[:-2]
_return.append(f'{_escape_key(field)}={s}')
elif (isinstance(value, int) or _np_is_subtype(value, 'int')) and not isinstance(value, bool):
_return.append(f'{_escape_key(field)}={str(value)}i')
_type = field_types.get(field, "i")
_return.append(f'{_escape_key(field)}={str(value)}{_type}')
elif isinstance(value, bool):
_return.append(f'{_escape_key(field)}={str(value).lower()}')
elif isinstance(value, str):
Expand Down
57 changes: 57 additions & 0 deletions tests/test_point.py
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,63 @@ def test_from_dictionary_tolerant_to_missing_tags_and_fields(self):
record_field_keys=["pressure", "temperature"])
self.assertEqual("sensor_pt859,location=warehouse_125 pressure=125i", point.to_line_protocol())

def test_from_dictionary_uint(self):
dict_structure = {
"measurement": "h2o_feet",
"tags": {"location": "coyote_creek"},
"fields": {
"water_level": 1.0,
"some_counter": 108913123234
},
"time": 1
}
point = Point.from_dict(dict_structure, field_types={"some_counter": "uint"})
self.assertEqual("h2o_feet,location=coyote_creek some_counter=108913123234u,water_level=1 1",
point.to_line_protocol())

def test_from_dictionary_int(self):
dict_structure = {
"measurement": "h2o_feet",
"tags": {"location": "coyote_creek"},
"fields": {
"water_level": 1.0,
"some_counter": 108913123234
},
"time": 1
}
point = Point.from_dict(dict_structure, field_types={"some_counter": "int"})
self.assertEqual("h2o_feet,location=coyote_creek some_counter=108913123234i,water_level=1 1",
point.to_line_protocol())

def test_from_dictionary_float(self):
dict_structure = {
"measurement": "h2o_feet",
"tags": {"location": "coyote_creek"},
"fields": {
"water_level": 1.0,
"some_counter": 108913123234
},
"time": 1
}
point = Point.from_dict(dict_structure, field_types={"some_counter": "float"})
self.assertEqual("h2o_feet,location=coyote_creek some_counter=108913123234,water_level=1 1",
point.to_line_protocol())

def test_from_dictionary_float_from_dict(self):
dict_structure = {
"measurement": "h2o_feet",
"tags": {"location": "coyote_creek"},
"fields": {
"water_level": 1.0,
"some_counter": 108913123234
},
"field_types": {"some_counter": "float"},
"time": 1
}
point = Point.from_dict(dict_structure)
self.assertEqual("h2o_feet,location=coyote_creek some_counter=108913123234,water_level=1 1",
point.to_line_protocol())

def test_static_measurement_name(self):
dictionary = {
"name": "sensor_pt859",
Expand Down