Repository: CartoDB/cartoframes
Pull request #1585 (2020-03-10 20:00:32)

Allow using any column name in visualization
- Using `prop('name')` instead of `$name`
- Update unit tests
- Bonus: fix metrics check (improve speed of unit tests)


Patch:
diff --git a/cartoframes/utils/metrics.py b/cartoframes/utils/metrics.py
index 0a42e06c..e0295abe 100644
--- a/cartoframes/utils/metrics.py
+++ b/cartoframes/utils/metrics.py
@@ -88,10 +88,9 @@ def build_metrics_data(event_name, extra_metrics_data):
 
 @silent_fail
 def post_metrics(event_name, extra_metrics_data):
-    if get_metrics_enabled():
-        json_data = build_metrics_data(event_name, extra_metrics_data)
-        result = requests.post('https://carto.com/api/metrics', json=json_data, timeout=2)
-        log.debug('Metrics sent! {0} {1}'.format(result.status_code, json_data))
+    json_data = build_metrics_data(event_name, extra_metrics_data)
+    result = requests.post('https://carto.com/api/metrics', json=json_data, timeout=2)
+    log.debug('Metrics sent! {0} {1}'.format(result.status_code, json_data))
 
 
 def send_metrics(event_name):
@@ -100,8 +99,9 @@ def send_metrics(event_name):
         def wrapper_func(*args, **kwargs):
             result = func(*args, **kwargs)
 
-            extra_metrics_data = build_extra_metrics_data(func, *args, **kwargs)
-            post_metrics(event_name, extra_metrics_data)
+            if get_metrics_enabled():
+                extra_metrics_data = build_extra_metrics_data(func, *args, **kwargs)
+                post_metrics(event_name, extra_metrics_data)
 
             return result
         return wrapper_func
diff --git a/cartoframes/utils/utils.py b/cartoframes/utils/utils.py
index de4c3817..555abb8b 100644
--- a/cartoframes/utils/utils.py
+++ b/cartoframes/utils/utils.py
@@ -148,10 +148,6 @@ def gen_variable_name(value):
     return 'v' + get_hash(value)[:6]
 
 
-def gen_column_name(value, operation=False):
-    return value if operation else '$' + value
-
-
 def get_hash(text):
     h = hashlib.sha1()
     h.update(text.encode('utf-8'))
@@ -398,10 +394,10 @@ def create_hash(value):
 
 
 def extract_viz_columns(viz):
-    """Extract columns ($name) in viz"""
+    """Extract columns prop('name') in viz"""
     columns = []
     viz_nocomments = remove_comments(viz)
-    viz_columns = re.findall(r'\$([A-Za-z0-9_]+)', viz_nocomments)
+    viz_columns = re.findall(r'prop\([\'\"]([^\)]*)[\'\"]\)', viz_nocomments)
     if viz_columns is not None:
         columns += viz_columns
     return list(set(columns))
diff --git a/cartoframes/viz/popup.py b/cartoframes/viz/popup.py
index 07a5fa82..df0e3cfb 100644
--- a/cartoframes/viz/popup.py
+++ b/cartoframes/viz/popup.py
@@ -1,4 +1,4 @@
-from ..utils.utils import gen_variable_name, gen_column_name
+from ..utils.utils import gen_variable_name
 
 
 class Popup:
@@ -9,15 +9,15 @@ class Popup:
     Layer popup.
 
     """
-    def __init__(self, event=None, value=None, title=None, operation=False):
-        self._init_popup(event, value, title, operation)
+    def __init__(self, event=None, value=None, title=None):
+        self._init_popup(event, value, title)
 
-    def _init_popup(self, event=None, value=None, title=None, operation=False):
+    def _init_popup(self, event=None, value=None, title=None):
         if not isinstance(event, str) and not isinstance(value, str):
             raise ValueError('Wrong popup input')
 
         self._event = event
-        self._value = gen_column_name(value, operation)
+        self._value = value
         self._title = title if title else value
 
         self._interactivity = self._get_interactivity()
diff --git a/cartoframes/viz/popup_list.py b/cartoframes/viz/popup_list.py
index 8b399528..4033a62f 100644
--- a/cartoframes/viz/popup_list.py
+++ b/cartoframes/viz/popup_list.py
@@ -1,4 +1,5 @@
 from .popup import Popup
+from .styles.utils import prop
 
 
 class PopupList:
@@ -43,8 +44,7 @@ class PopupList:
                     popup_elements.append(
                         Popup('click',
                               value=popup.get('value'),
-                              title=popup.get('title'),
-                              operation=popup.get('operation', False))
+                              title=popup.get('title'))
                     )
 
         if hover_popup_elements is not None:
@@ -58,8 +58,7 @@ class PopupList:
                     popup_elements.append(
                         Popup('hover',
                               value=popup.get('value'),
-                              title=popup.get('title'),
-                              operation=popup.get('operation', False))
+                              title=popup.get('title'))
                     )
 
         return popup_elements
@@ -78,6 +77,6 @@ class PopupList:
 
         for popup in self._popups:
             if popup:
-                popups_variables[popup.variable.get('name')] = popup.variable.get('value')
+                popups_variables[popup.variable.get('name')] = prop(popup.variable.get('value'))
 
         return popups_variables
diff --git a/cartoframes/viz/popups/popup_element.py b/cartoframes/viz/popups/popup_element.py
index 9a4cfd79..bb7e39d6 100644
--- a/cartoframes/viz/popups/popup_element.py
+++ b/cartoframes/viz/popups/popup_element.py
@@ -1,11 +1,9 @@
-def popup_element(value, title=None, operation=None):
+def popup_element(value, title=None):
     """Helper function for quickly adding a popup element to a layer.
 
     Args:
         value (str): Column name to display the value for each feature.
         title (str, optional): Title for the given value. By default, it's the name of the value.
-        operation (str, optional): Cluster operation, defaults to 'count'. Other options
-          available are 'avg', 'min', 'max', and 'sum'.
 
     Example:
         >>> popup_element('column_name', title='Popup title')
@@ -13,6 +11,5 @@ def popup_element(value, title=None, operation=None):
     """
     return {
       'value': value,
-      'title': title,
-      'operation': operation
+      'title': title
     }
diff --git a/cartoframes/viz/styles/animation_style.py b/cartoframes/viz/styles/animation_style.py
index a61e2686..c8148ad6 100644
--- a/cartoframes/viz/styles/animation_style.py
+++ b/cartoframes/viz/styles/animation_style.py
@@ -1,4 +1,4 @@
-from .utils import get_value
+from .utils import get_value, prop
 from ..style import Style
 from ..widgets import time_series_widget
 from ..popups import popup_element
@@ -64,4 +64,4 @@ def animation_style(value, duration=20, fade_in=1, fade_out=1, color=None,
 
 
 def _animation_filter(value, duration, fade):
-    return 'animation(linear(${0}), {1}, fade{2})'.format(value, duration, fade)
+    return 'animation(linear({0}), {1}, fade{2})'.format(prop(value), duration, fade)
diff --git a/cartoframes/viz/styles/cluster_size_style.py b/cartoframes/viz/styles/cluster_size_style.py
index a4d0902b..b21d0424 100644
--- a/cartoframes/viz/styles/cluster_size_style.py
+++ b/cartoframes/viz/styles/cluster_size_style.py
@@ -1,4 +1,4 @@
-from .utils import get_value
+from .utils import get_value, prop
 from ..constants import CLUSTER_KEYS, CLUSTER_OPERATIONS
 from ..style import Style
 from ..legends import size_continuous_legend
@@ -52,8 +52,8 @@ def cluster_size_style(value, operation='count', resolution=32, color=None, opac
         value,
         default_legend=size_continuous_legend(title=value),
         default_widget=histogram_widget(value, title=value),
-        default_popup_hover=popup_element(cluster_operation, title=cluster_operation_title, operation=True),
-        default_popup_click=popup_element(cluster_operation, title=cluster_operation_title, operation=True)
+        default_popup_hover=popup_element(cluster_operation, title=cluster_operation_title),
+        default_popup_click=popup_element(cluster_operation, title=cluster_operation_title)
     )
 
 
@@ -80,7 +80,7 @@ def _get_cluster_operation(operation, value):
     _check_valid_operation(operation)
 
     if value is not None and operation != 'count':
-        return '{0}(${1})'.format(CLUSTER_OPERATIONS[operation], value)
+        return '{0}({1})'.format(CLUSTER_OPERATIONS[operation], prop(value))
 
     return '{0}()'.format(CLUSTER_OPERATIONS[operation])
 
diff --git a/cartoframes/viz/styles/color_bins_style.py b/cartoframes/viz/styles/color_bins_style.py
index 92cdc36b..7f469bf3 100644
--- a/cartoframes/viz/styles/color_bins_style.py
+++ b/cartoframes/viz/styles/color_bins_style.py
@@ -1,4 +1,4 @@
-from .utils import serialize_palette, get_value
+from .utils import serialize_palette, get_value, prop
 from ..style import Style
 from ..legends import color_bins_legend
 from ..widgets import histogram_widget
@@ -49,12 +49,12 @@ def color_bins_style(value, method='quantiles', bins=5, breaks=None, palette=Non
         default_palette = 'purpor'
         breaks = list(breaks)
 
-    animation_filter = 'animation(linear(${}), 20, fade(1,1))'.format(animate) if animate else '1'
+    animation_filter = 'animation(linear({}), 20, fade(1,1))'.format(prop(animate)) if animate else '1'
 
     data = {
         'point': {
-            'color': 'opacity(ramp({0}(${1}, {2}), {3}),{4})'.format(
-                func, value, breaks or bins,
+            'color': 'opacity(ramp({0}({1}, {2}), {3}),{4})'.format(
+                func, prop(value), breaks or bins,
                 serialize_palette(palette) or default_palette,
                 get_value(opacity, 1)),
             'width': get_value(size, 'width', 'point'),
@@ -63,16 +63,16 @@ def color_bins_style(value, method='quantiles', bins=5, breaks=None, palette=Non
             'filter': animation_filter
         },
         'line': {
-            'color': 'opacity(ramp({0}(${1}, {2}), {3}),{4})'.format(
-                func, value, breaks or bins,
+            'color': 'opacity(ramp({0}({1}, {2}), {3}),{4})'.format(
+                func, prop(value), breaks or bins,
                 serialize_palette(palette) or default_palette,
                 get_value(opacity, 1)),
             'width': get_value(size, 'width', 'line'),
             'filter': animation_filter
         },
         'polygon': {
-            'color': 'opacity(ramp({0}(${1}, {2}), {3}), {4})'.format(
-                func, value, breaks or bins,
+            'color': 'opacity(ramp({0}({1}, {2}), {3}), {4})'.format(
+                func, prop(value), breaks or bins,
                 serialize_palette(palette) or default_palette,
                 get_value(opacity, 0.9)),
             'strokeColor': get_value(stroke_color, 'strokeColor', 'polygon'),
diff --git a/cartoframes/viz/styles/color_category_style.py b/cartoframes/viz/styles/color_category_style.py
index 73883a40..a94c1606 100644
--- a/cartoframes/viz/styles/color_category_style.py
+++ b/cartoframes/viz/styles/color_category_style.py
@@ -1,4 +1,4 @@
-from .utils import serialize_palette, get_value
+from .utils import serialize_palette, get_value, prop
 from ..style import Style
 from ..legends import color_category_legend
 from ..widgets import category_widget
@@ -31,12 +31,12 @@ def color_category_style(value, top=11, cat=None, palette=None, size=None, opaci
     """
     func = 'buckets' if cat else 'top'
     default_palette = 'bold'
-    animation_filter = 'animation(linear(${}), 20, fade(1,1))'.format(animate) if animate else '1'
+    animation_filter = 'animation(linear({}), 20, fade(1,1))'.format(prop(animate)) if animate else '1'
 
     data = {
           'point': {
-              'color': 'opacity(ramp({0}(${1}, {2}), {3}),{4})'.format(
-                  func, value, cat or top,
+              'color': 'opacity(ramp({0}({1}, {2}), {3}),{4})'.format(
+                  func, prop(value), cat or top,
                   serialize_palette(palette) or default_palette,
                   get_value(opacity, 1)),
               'width': get_value(size, 'width', 'point'),
@@ -45,16 +45,16 @@ def color_category_style(value, top=11, cat=None, palette=None, size=None, opaci
               'filter': animation_filter
           },
           'line': {
-              'color': 'opacity(ramp({0}(${1}, {2}), {3}),{4})'.format(
-                  func, value, cat or top,
+              'color': 'opacity(ramp({0}({1}, {2}), {3}),{4})'.format(
+                  func, prop(value), cat or top,
                   serialize_palette(palette) or default_palette,
                   get_value(opacity, 1)),
               'width': get_value(size, 'width', 'line'),
               'filter': animation_filter
           },
           'polygon': {
-              'color': 'opacity(ramp({0}(${1}, {2}), {3}), {4})'.format(
-                  func, value, cat or top,
+              'color': 'opacity(ramp({0}({1}, {2}), {3}), {4})'.format(
+                  func, prop(value), cat or top,
                   serialize_palette(palette) or default_palette,
                   get_value(opacity, 0.9)
               ),
diff --git a/cartoframes/viz/styles/color_continuous_style.py b/cartoframes/viz/styles/color_continuous_style.py
index 056ff168..4201c6c9 100644
--- a/cartoframes/viz/styles/color_continuous_style.py
+++ b/cartoframes/viz/styles/color_continuous_style.py
@@ -1,4 +1,4 @@
-from .utils import serialize_palette, get_value
+from .utils import serialize_palette, get_value, prop
 from ..style import Style
 from ..legends import color_continuous_legend
 from ..widgets import histogram_widget
@@ -31,18 +31,18 @@ def color_continuous_style(value, size=None, range_min=None, range_max=None, pal
 
     """
     default_palette = 'bluyl'
-    animation_filter = 'animation(linear(${}), 20, fade(1,1))'.format(animate) if animate else '1'
+    animation_filter = 'animation(linear({}), 20, fade(1,1))'.format(prop(animate)) if animate else '1'
 
     if range_min is None:
-        range_min = 'globalMIN(${0})'.format(value)
+        range_min = 'globalMIN({0})'.format(prop(value))
 
     if range_max is None:
-        range_max = 'globalMAX(${0})'.format(value)
+        range_max = 'globalMAX({0})'.format(prop(value))
 
     data = {
         'point': {
-            'color': 'opacity(ramp(linear(${0}, {1}, {2}), {3}), {4})'.format(
-                value, range_min, range_max,
+            'color': 'opacity(ramp(linear({0}, {1}, {2}), {3}), {4})'.format(
+                prop(value), range_min, range_max,
                 serialize_palette(palette) or default_palette,
                 get_value(opacity, 1)),
             'width': get_value(size, 'width', 'point'),
@@ -51,16 +51,16 @@ def color_continuous_style(value, size=None, range_min=None, range_max=None, pal
             'filter': animation_filter
         },
         'line': {
-            'color': 'opacity(ramp(linear(${0}, {1}, {2}), {3}), {4})'.format(
-                value, range_min, range_max,
+            'color': 'opacity(ramp(linear({0}, {1}, {2}), {3}), {4})'.format(
+                prop(value), range_min, range_max,
                 serialize_palette(palette) or default_palette,
                 get_value(opacity, 1)),
             'width': get_value(size, 'width', 'line'),
             'filter': animation_filter
         },
         'polygon': {
-            'color': 'opacity(ramp(linear(${0}, {1}, {2}), {3}), {4})'.format(
-                value, range_min, range_max,
+            'color': 'opacity(ramp(linear({0}, {1}, {2}), {3}), {4})'.format(
+                prop(value), range_min, range_max,
                 serialize_palette(palette) or default_palette,
                 get_value(opacity, 0.9)),
             'strokeColor': get_value(stroke_color, 'strokeColor', 'polygon'),
diff --git a/cartoframes/viz/styles/size_bins_style.py b/cartoframes/viz/styles/size_bins_style.py
index cf556119..eca71030 100644
--- a/cartoframes/viz/styles/size_bins_style.py
+++ b/cartoframes/viz/styles/size_bins_style.py
@@ -1,4 +1,4 @@
-from .utils import get_value
+from .utils import get_value, prop
 from ..style import Style
 from ..legends import size_bins_legend
 from ..widgets import histogram_widget
@@ -44,15 +44,15 @@ def size_bins_style(value, method='quantiles', bins=5, breaks=None, size_range=N
         func = 'buckets'
         breaks = list(breaks)
 
-    animation_filter = 'animation(linear(${}), 20, fade(1,1))'.format(animate) if animate else '1'
+    animation_filter = 'animation(linear({}), 20, fade(1,1))'.format(prop(animate)) if animate else '1'
 
     data = {
         'point': {
             'color': 'opacity({0}, {1})'.format(
                 get_value(color, '#EE5D5A'),
                 get_value(opacity, 0.8)),
-            'width': 'ramp({0}(${1}, {2}), {3})'.format(
-                func, value, breaks or bins, size_range or [2, 14]),
+            'width': 'ramp({0}({1}, {2}), {3})'.format(
+                func, prop(value), breaks or bins, size_range or [2, 14]),
             'strokeColor': get_value(stroke_color, 'strokeColor', 'point'),
             'strokeWidth': get_value(stroke_width, 'strokeWidth', 'point'),
             'filter': animation_filter
@@ -61,8 +61,8 @@ def size_bins_style(value, method='quantiles', bins=5, breaks=None, size_range=N
             'color': 'opacity({0}, {1})'.format(
                 get_value(color, 'color', 'line'),
                 get_value(opacity, 0.8)),
-            'width': 'ramp({0}(${1}, {2}), {3})'.format(
-                func, value, breaks or bins, size_range or [1, 10]),
+            'width': 'ramp({0}({1}, {2}), {3})'.format(
+                func, prop(value), breaks or bins, size_range or [1, 10]),
             'filter': animation_filter
         }
     }
diff --git a/cartoframes/viz/styles/size_category_style.py b/cartoframes/viz/styles/size_category_style.py
index c277ca83..8d0c894b 100644
--- a/cartoframes/viz/styles/size_category_style.py
+++ b/cartoframes/viz/styles/size_category_style.py
@@ -1,4 +1,4 @@
-from .utils import get_value
+from .utils import get_value, prop
 from ..style import Style
 from ..legends import size_category_legend
 from ..widgets import category_widget
@@ -30,7 +30,7 @@ def size_category_style(value, top=5, cat=None, size_range=None, color=None, opa
 
     """
     func = 'buckets' if cat else 'top'
-    animation_filter = 'animation(linear(${}), 20, fade(1,1))'.format(animate) if animate else '1'
+    animation_filter = 'animation(linear({}), 20, fade(1,1))'.format(prop(animate)) if animate else '1'
     opacity = opacity if opacity else '0.8'
 
     data = {
@@ -38,8 +38,8 @@ def size_category_style(value, top=5, cat=None, size_range=None, color=None, opa
             'color': 'opacity({0}, {1})'.format(
                 get_value(color, '#F46D43'),
                 get_value(opacity, 1)),
-            'width': 'ramp({0}(${1}, {2}), {3})'.format(
-                func, value, cat or top, size_range or [2, 20]),
+            'width': 'ramp({0}({1}, {2}), {3})'.format(
+                func, prop(value), cat or top, size_range or [2, 20]),
             'strokeColor': get_value(stroke_color, 'strokeColor', 'point'),
             'strokeWidth': get_value(stroke_width, 'strokeWidth', 'point'),
             'filter': animation_filter
@@ -48,8 +48,8 @@ def size_category_style(value, top=5, cat=None, size_range=None, color=None, opa
             'color': 'opacity({0}, {1})'.format(
                 get_value(color, 'color', 'line'),
                 get_value(opacity, 1)),
-            'width': 'ramp({0}(${1}, {2}), {3})'.format(
-                func, value, cat or top, size_range or [1, 10]),
+            'width': 'ramp({0}({1}, {2}), {3})'.format(
+                func, prop(value), cat or top, size_range or [1, 10]),
             'filter': animation_filter
         }
     }
diff --git a/cartoframes/viz/styles/size_continuous_style.py b/cartoframes/viz/styles/size_continuous_style.py
index bd985abf..e5d5a618 100644
--- a/cartoframes/viz/styles/size_continuous_style.py
+++ b/cartoframes/viz/styles/size_continuous_style.py
@@ -1,4 +1,4 @@
-from .utils import get_value
+from .utils import get_value, prop
 from ..style import Style
 from ..legends import size_continuous_legend
 from ..widgets import histogram_widget
@@ -30,35 +30,35 @@ def size_continuous_style(value, size_range=None, range_min=None, range_max=None
         cartoframes.viz.style.Style
 
     """
-    animation_filter = 'animation(linear(${}), 20, fade(1,1))'.format(animate) if animate else '1'
+    animation_filter = 'animation(linear({}), 20, fade(1,1))'.format(prop(animate)) if animate else '1'
 
     if range_min is None:
-        range_min = 'globalMIN(${0})'.format(value)
+        range_min = 'globalMIN({0})'.format(prop(value))
 
     if range_max is None:
-        range_max = 'globalMAX(${0})'.format(value)
+        range_max = 'globalMAX({0})'.format(prop(value))
 
     data = {
         'point': {
-            '@size_value': 'ramp(linear(${0}, {1}, {2}), {3})'.format(
-                value, range_min, range_max, size_range or [2, 40]),
+            '@size_value': 'ramp(linear({0}, {1}, {2}), {3})'.format(
+                prop(value), range_min, range_max, size_range or [2, 40]),
             'color': 'opacity({0}, {1})'.format(
                 get_value(color, '#FFB927'),
                 get_value(opacity, 0.8)),
-            'width': 'ramp(linear(sqrt(${0}), sqrt({1}), sqrt({2})), {3})'.format(
-                value, range_min, range_max, size_range or [2, 40]),
+            'width': 'ramp(linear(sqrt({0}), sqrt({1}), sqrt({2})), {3})'.format(
+                prop(value), range_min, range_max, size_range or [2, 40]),
             'strokeColor': get_value(stroke_color, 'strokeColor', 'point'),
             'strokeWidth': get_value(stroke_width, 'strokeWidth', 'point'),
             'filter': animation_filter
         },
         'line': {
-            '@size_value': 'ramp(linear(${0}, {1}, {2}), {3})'.format(
-                value, range_min, range_max, size_range or [1, 10]),
+            '@size_value': 'ramp(linear({0}, {1}, {2}), {3})'.format(
+                prop(value), range_min, range_max, size_range or [1, 10]),
             'color': 'opacity({0}, {1})'.format(
                 get_value(color, 'color', 'line'),
                 get_value(opacity, 0.8)),
-            'width': 'ramp(linear(${0}, {1}, {2}), {3})'.format(
-                value, range_min, range_max, size_range or [1, 10]),
+            'width': 'ramp(linear({0}, {1}, {2}), {3})'.format(
+                prop(value), range_min, range_max, size_range or [1, 10]),
             'filter': animation_filter
         }
     }
diff --git a/cartoframes/viz/styles/utils.py b/cartoframes/viz/styles/utils.py
index a1514f1b..b637eb19 100644
--- a/cartoframes/viz/styles/utils.py
+++ b/cartoframes/viz/styles/utils.py
@@ -1,4 +1,3 @@
-
 from .. import defaults
 
 
@@ -14,3 +13,10 @@ def get_value(value, default, geom_type=None):
             return defaults.STYLE.get(geom_type, {}).get(default)
         return default
     return value
+
+
+def prop(value):
+    if '\'' in value:
+        return 'prop("{}")'.format(value)
+    else:
+        return "prop('{}')".format(value)
diff --git a/cartoframes/viz/widget_list.py b/cartoframes/viz/widget_list.py
index 33be0b7d..f28f8ca3 100644
--- a/cartoframes/viz/widget_list.py
+++ b/cartoframes/viz/widget_list.py
@@ -1,4 +1,5 @@
 from .widget import Widget
+from .styles.utils import prop
 
 
 class WidgetList:
@@ -41,6 +42,6 @@ class WidgetList:
         output = {}
         for widget in self._widgets:
             if widget._variable_name:
-                output[widget._variable_name] = '$' + widget._value if widget.has_bridge() else widget._value
+                output[widget._variable_name] = prop(widget._value) if widget.has_bridge() else widget._value
 
         return output
diff --git a/cartoframes/viz/widgets/formula_widget.py b/cartoframes/viz/widgets/formula_widget.py
index b543e1bf..f302875b 100644
--- a/cartoframes/viz/widgets/formula_widget.py
+++ b/cartoframes/viz/widgets/formula_widget.py
@@ -1,5 +1,6 @@
 from ..constants import FORMULA_OPERATIONS_GLOBAL, FORMULA_OPERATIONS_VIEWPORT
 from ..widget import Widget
+from ..styles.utils import prop
 
 
 def formula_widget(value, operation=None, title=None, description=None, footer=None, is_global=False):
@@ -50,9 +51,9 @@ def _get_value_expression(operation, value, is_global):
         return formula_operation + '()'
     elif operation in ['avg', 'max', 'min', 'sum']:
         formula_operation = _get_formula_operation(operation, is_global)
-        return formula_operation + '($' + value + ')'
+        return formula_operation + '(' + prop(value) + ')'
     else:
-        return '$' + value
+        return prop(value)
 
 
 def _get_formula_operation(operation, is_global):


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 4a034a9a..42b03939 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -28,4 +28,3 @@ def pytest_unconfigure(config):
     """
     called before test process is exited.
     """
-    setup_metrics(True)
diff --git a/tests/unit/utils/test_utils.py b/tests/unit/utils/test_utils.py
index e5017f7d..fc0ba618 100644
--- a/tests/unit/utils/test_utils.py
+++ b/tests/unit/utils/test_utils.py
@@ -211,7 +211,7 @@ class TestUtils(unittest.TestCase):
         assert encode_row(np.nan) == b'NaN'
 
     def test_extract_viz_columns(self):
-        viz = 'color: $hello + $A_0123'
+        viz = "color: prop('hello') + prop('A_0123')"
         assert 'hello' in extract_viz_columns(viz)
         assert 'A_0123' in extract_viz_columns(viz)
 
diff --git a/tests/unit/viz/popups/test_popup.py b/tests/unit/viz/popups/test_popup.py
index 4b1efaa2..f4fec59e 100644
--- a/tests/unit/viz/popups/test_popup.py
+++ b/tests/unit/viz/popups/test_popup.py
@@ -13,9 +13,9 @@ class TestPopup(object):
         popup_click_1 = Popup('click', 'pop')
         popup_hover_1 = Popup('hover', 'pop', 'Pop')
 
-        assert popup_click_1.value == '$pop'
+        assert popup_click_1.value == 'pop'
         assert popup_click_1.title == 'pop'
-        assert popup_hover_1.value == '$pop'
+        assert popup_hover_1.value == 'pop'
         assert popup_hover_1.title == 'Pop'
 
     def test_popup_interactivity(self):
@@ -27,7 +27,7 @@ class TestPopup(object):
         assert popup_click_1.interactivity == {
             'event': 'click',
             'attrs': {
-                'name': 'v559339',
+                'name': 'v4f197c',
                 'title': 'pop'
             }
         }
@@ -35,7 +35,7 @@ class TestPopup(object):
         assert popup_hover_1.interactivity == {
             'event': 'hover',
             'attrs': {
-                'name': 'v559339',
+                'name': 'v4f197c',
                 'title': 'Pop'
             }
         }
@@ -47,13 +47,13 @@ class TestPopup(object):
         popup_hover_1 = Popup('hover', 'pop', 'Pop')
 
         assert popup_click_1.variable == {
-            'name': 'v559339',
-            'value': '$pop'
+            'name': 'v4f197c',
+            'value': 'pop'
         }
 
         assert popup_hover_1.variable == {
-            'name': 'v559339',
-            'value': '$pop'
+            'name': 'v4f197c',
+            'value': 'pop'
         }
 
     def test_wrong_attribute(self):
diff --git a/tests/unit/viz/popups/test_popup_list.py b/tests/unit/viz/popups/test_popup_list.py
index d5c0e953..88c61c12 100644
--- a/tests/unit/viz/popups/test_popup_list.py
+++ b/tests/unit/viz/popups/test_popup_list.py
@@ -18,25 +18,25 @@ class TestPopupList(object):
           {
             'event': 'click',
             'attrs': {
-                'name': 'vbacecf',
+                'name': 'v72224b',
                 'title': 'value_2'
             }
           }, {
             'event': 'click',
             'attrs': {
-                'name': 'v1a58b5',
+                'name': 'vbc6799',
                 'title': 'value_1'
             }
           }, {
             'event': 'hover',
             'attrs': {
-                'name': 'v700930',
+                'name': 'vc266e3',
                 'title': 'value_3'
             }
           }, {
             'event': 'hover',
             'attrs': {
-                'name': 'v1a58b5',
+                'name': 'vbc6799',
                 'title': 'value_1'
             }
           }
@@ -44,8 +44,8 @@ class TestPopupList(object):
 
     def test_should_get_all_popup_variables(self):
         assert popup_list.get_variables() == {
-          'v1a58b5': '$value_1',
-          'vbacecf': '$value_2',
-          'v1a58b5': '$value_1',
-          'v700930': '$value_3'
+          'vbc6799': "prop('value_1')",
+          'v72224b': "prop('value_2')",
+          'vbc6799': "prop('value_1')",
+          'vc266e3': "prop('value_3')"
         }
diff --git a/tests/unit/viz/test_map.py b/tests/unit/viz/test_map.py
index ae594a8f..0beafb49 100644
--- a/tests/unit/viz/test_map.py
+++ b/tests/unit/viz/test_map.py
@@ -104,19 +104,19 @@ class TestMapLayer(object):
             {
                 'event': 'click',
                 'attrs': {
-                    'name': 'v8e0f74',
+                    'name': 'v6ae999',
                     'title': 'name'
                 }
             }, {
                 'event': 'click',
                 'attrs': {
-                    'name': 'v559339',
+                    'name': 'v4f197c',
                     'title': 'pop'
                 }
             }, {
                 'event': 'hover',
                 'attrs': {
-                    'name': 'v559339',
+                    'name': 'v4f197c',
                     'title': 'Pop'
                 }
             }
diff --git a/tests/unit/viz/test_widget_list.py b/tests/unit/viz/test_widget_list.py
index 423bf522..f3422c53 100644
--- a/tests/unit/viz/test_widget_list.py
+++ b/tests/unit/viz/test_widget_list.py
@@ -23,7 +23,7 @@ class TestWidgetList(object):
         widget_list = WidgetList([WIDGET_A])
 
         assert widget_list._widgets[0]._type == 'formula'
-        assert widget_list._widgets[0]._value == 'viewportSum($amount)'
+        assert widget_list._widgets[0]._value == "viewportSum(prop('amount'))"
         assert widget_list._widgets[0]._title == '[TITLE]'
         assert widget_list._widgets[0]._description == '[description]'
         assert widget_list._widgets[0]._footer == '[footer]'
@@ -35,7 +35,7 @@ class TestWidgetList(object):
         widget_list = WidgetList([WIDGET_A, WIDGET_B])
 
         assert widget_list._widgets[0]._type == 'formula'
-        assert widget_list._widgets[0]._value == 'viewportSum($amount)'
+        assert widget_list._widgets[0]._value == "viewportSum(prop('amount'))"
         assert widget_list._widgets[0]._title == '[TITLE]'
         assert widget_list._widgets[0]._description == '[description]'
         assert widget_list._widgets[0]._footer == '[footer]'
@@ -68,13 +68,13 @@ class TestWidgetList(object):
         assert widgets_info == [
             {
                 'type': 'formula',
-                'value': 'viewportSum($amount)',
+                'value': "viewportSum(prop('amount'))",
                 'title': '[TITLE]',
                 'prop': '',
                 'description': '[description]',
                 'footer': '[footer]',
                 'has_bridge': False,
-                'variable_name': 'vb6dbcf',
+                'variable_name': 'vdb8cc1',
                 'options': {
                     'readOnly': False,
                     'buckets': 20
diff --git a/tests/unit/viz/widgets/test_category_widget.py b/tests/unit/viz/widgets/test_category_widget.py
index 0b8981e3..eb2706da 100644
--- a/tests/unit/viz/widgets/test_category_widget.py
+++ b/tests/unit/viz/widgets/test_category_widget.py
@@ -8,9 +8,9 @@ class TestCategoryWidget(object):
 
     def test_factory(self):
         "should create a category widget"
-        widget = widgets.category_widget('$value', title='Category Widget')
+        widget = widgets.category_widget("prop('value')", title='Category Widget')
         widget_info = widget.get_info()
         assert widget_info.get('type') == 'category'
-        assert widget_info.get('value') == '$value'
+        assert widget_info.get('value') == "prop('value')"
         assert widget_info.get('title') == 'Category Widget'
         assert widget_info.get('options').get('readOnly') is False
diff --git a/tests/unit/viz/widgets/test_formula_widget.py b/tests/unit/viz/widgets/test_formula_widget.py
index f0af1c8a..bf58db8a 100644
--- a/tests/unit/viz/widgets/test_formula_widget.py
+++ b/tests/unit/viz/widgets/test_formula_widget.py
@@ -11,7 +11,7 @@ class TestFormulaWidget(object):
         widget = widgets.formula_widget('value', title='Formula Widget')
         widget_info = widget.get_info()
         assert widget_info.get('type') == 'formula'
-        assert widget_info.get('value') == '$value'
+        assert widget_info.get('value') == "prop('value')"
         assert widget_info.get('title') == 'Formula Widget'
 
     def test_count_formula_viewport(self):
@@ -30,10 +30,10 @@ class TestFormulaWidget(object):
         "should create a formula widget to get a viewport operation"
         widget = widgets.formula_widget('value', operation='avg')
         widget_info = widget.get_info()
-        assert widget_info.get('value') == 'viewportAvg($value)'
+        assert widget_info.get('value') == "viewportAvg(prop('value'))"
 
     def test_formula_global(self):
         "should create a formula widget to get a global operation"
         widget = widgets.formula_widget('value', operation='avg', is_global=True)
         widget_info = widget.get_info()
-        assert widget_info.get('value') == 'globalAvg($value)'
+        assert widget_info.get('value') == "globalAvg(prop('value'))"
diff --git a/tests/unit/viz/widgets/test_histogram_widget.py b/tests/unit/viz/widgets/test_histogram_widget.py
index 396d8137..90cc9470 100644
--- a/tests/unit/viz/widgets/test_histogram_widget.py
+++ b/tests/unit/viz/widgets/test_histogram_widget.py
@@ -8,9 +8,9 @@ class TestHistogramWidget(object):
 
     def test_factory(self):
         "should create a histogram widget"
-        widget = widgets.histogram_widget('$value', title='Histogram Widget')
+        widget = widgets.histogram_widget("prop('value')", title='Histogram Widget')
         widget_info = widget.get_info()
         assert widget_info.get('type') == 'histogram'
-        assert widget_info.get('value') == '$value'
+        assert widget_info.get('value') == "prop('value')"
         assert widget_info.get('title') == 'Histogram Widget'
         assert widget_info.get('options').get('readOnly') is False
diff --git a/tests/unit/viz/widgets/test_time_series_widget.py b/tests/unit/viz/widgets/test_time_series_widget.py
index a9347fcb..7022eb98 100644
--- a/tests/unit/viz/widgets/test_time_series_widget.py
+++ b/tests/unit/viz/widgets/test_time_series_widget.py
@@ -8,9 +8,9 @@ class TestTimeSeriesWidget(object):
 
     def test_factory(self):
         "should create a time series widget"
-        widget = widgets.time_series_widget('$value', title='Time Series Widget')
+        widget = widgets.time_series_widget("prop('value')", title='Time Series Widget')
         widget_info = widget.get_info()
         assert widget_info.get('type') == 'time-series'
-        assert widget_info.get('value') == '$value'
+        assert widget_info.get('value') == "prop('value')"
         assert widget_info.get('title') == 'Time Series Widget'
         assert widget_info.get('options').get('readOnly') is False