Repository: CartoDB/cartoframes
Pull request #1637 (2020-05-18 11:26:48)

Fix read null geoms from carto
Fixes https://github.com/CartoDB/support/issues/2525

Geom NULL values where not properly detected and converted, so PG '__null' values were not converted to None. This PR fixes the issue.


Patch:
diff --git a/cartoframes/utils/columns.py b/cartoframes/utils/columns.py
index 6d6df518..37e00cce 100644
--- a/cartoframes/utils/columns.py
+++ b/cartoframes/utils/columns.py
@@ -7,7 +7,6 @@ from unidecode import unidecode
 from .utils import dtypes2pg, pg2dtypes, PG_NULL
 
 BOOL_DBTYPES = ['bool', 'boolean']
-OBJECT_DBTYPES = ['text']
 INT_DBTYPES = ['int2', 'int4', 'int2', 'int', 'int8', 'smallint', 'integer', 'bigint']
 FLOAT_DBTYPES = ['float4', 'float8', 'real', 'double precision', 'numeric', 'decimal']
 DATETIME_DBTYPES = ['date', 'timestamp', 'timestampz']
@@ -185,27 +184,21 @@ def _is_unsupported(value):
 def obtain_converters(columns):
     converters = {}
 
-    for int_column_name in type_columns_names(columns, INT_DBTYPES):
-        converters[int_column_name] = _convert_int
-
-    for float_column_name in type_columns_names(columns, FLOAT_DBTYPES):
-        converters[float_column_name] = _convert_float
-
-    for bool_column_name in type_columns_names(columns, BOOL_DBTYPES):
-        converters[bool_column_name] = _convert_bool
-
-    for object_column_name in type_columns_names(columns, OBJECT_DBTYPES):
-        converters[object_column_name] = _convert_object
+    for column in columns:
+        if column.dbtype in INT_DBTYPES:
+            converters[column.name] = _convert_int
+        elif column.dbtype in FLOAT_DBTYPES:
+            converters[column.name] = _convert_float
+        elif column.dbtype in BOOL_DBTYPES:
+            converters[column.name] = _convert_bool
+        else:
+            converters[column.name] = _convert_generic
 
     return converters
 
 
 def date_columns_names(columns):
-    return type_columns_names(columns, DATETIME_DBTYPES)
-
-
-def type_columns_names(columns, dbtypes):
-    return [x.name for x in columns if x.dbtype in dbtypes]
+    return [x.name for x in columns if x.dbtype in DATETIME_DBTYPES]
 
 
 def _convert_int(x):
@@ -230,7 +223,7 @@ def _convert_bool(x):
     return bool(x)
 
 
-def _convert_object(x):
+def _convert_generic(x):
     if _is_none_null(x):
         return None
     return x
@@ -238,10 +231,3 @@ def _convert_object(x):
 
 def _is_none_null(x):
     return x is None or x == PG_NULL
-
-
-def _first_value(series):
-    series = series.loc[~series.isnull()]  # Remove null values
-    if len(series) > 0:
-        return series.iloc[0]
-    return None
diff --git a/cartoframes/utils/utils.py b/cartoframes/utils/utils.py
index 1506c500..772675f3 100644
--- a/cartoframes/utils/utils.py
+++ b/cartoframes/utils/utils.py
@@ -266,7 +266,6 @@ def get_geodataframe_data(data, encode_data=True):
         return data
 
 
-# Dup
 def _first_value(series):
     series = series.loc[~series.isnull()]  # Remove null values
     if len(series) > 0:


Test patch:
diff --git a/tests/unit/utils/test_columns.py b/tests/unit/utils/test_columns.py
index 879fa719..157c0232 100644
--- a/tests/unit/utils/test_columns.py
+++ b/tests/unit/utils/test_columns.py
@@ -6,7 +6,9 @@ from pandas import DataFrame
 from geopandas import GeoDataFrame
 
 from cartoframes.utils.geom_utils import set_geometry
-from cartoframes.utils.columns import ColumnInfo, get_dataframe_columns_info, normalize_names
+from cartoframes.utils.columns import ColumnInfo, get_dataframe_columns_info, normalize_names, \
+                                      obtain_converters, _convert_int, _convert_float, \
+                                      _convert_bool, _convert_generic
 
 
 class TestColumns(object):
@@ -113,3 +115,21 @@ class TestColumns(object):
             ColumnInfo('the_geom', 'the_geom', 'geometry(Geometry, 4326)', True),
             ColumnInfo('g-e-o-m-e-t-r-y', 'g_e_o_m_e_t_r_y', 'text', False)
         ]
+
+    def test_converters(self):
+        columns = [
+            ColumnInfo('cartodb_id', 'cartodb_id', 'integer', False),
+            ColumnInfo('the_geom', 'the_geom', 'geometry(Geometry, 4326)', True),
+            ColumnInfo('name', 'name', 'text', False),
+            ColumnInfo('flag', 'flag', 'boolean', False),
+            ColumnInfo('number', 'number', 'double precision', False)
+        ]
+
+        converters = obtain_converters(columns)
+
+        assert type(converters) == dict
+        assert converters['cartodb_id'] == _convert_int
+        assert converters['the_geom'] == _convert_generic
+        assert converters['name'] == _convert_generic
+        assert converters['flag'] == _convert_bool
+        assert converters['number'] == _convert_float