Repository: CartoDB/cartoframes
Pull request #1082 (2019-10-09 17:39:50)

Create Maps API key automagically in Kuviz
Solves: https://github.com/CartoDB/cartoframes/issues/731

- [x] refactoring
- [x] publish public
- [x] AuthAPIClient
- [x] publish private
- [x] update public and private
- [x] tests
- [x] docs & notebook


Patch:
diff --git a/cartoframes/data/clients/auth_api_client.py b/cartoframes/data/clients/auth_api_client.py
new file mode 100644
index 00000000..53efe97e
--- /dev/null
+++ b/cartoframes/data/clients/auth_api_client.py
@@ -0,0 +1,48 @@
+from __future__ import absolute_import
+
+from carto.api_keys import APIKeyManager
+
+from ...auth import get_default_credentials
+
+
+class AuthAPIClient(object):
+    """AuthAPIClient class is a client of the CARTO Auth API.
+    More info: https://carto.com/developers/auth-api/
+
+    Args:
+        credentials (:py:class:`Credentials <cartoframes.auth.Credentials>`, optional):
+              credentials of user account to send Dataset to. If not provided,
+              a default credentials (if set with :py:meth:`set_default_credentials
+              <cartoframes.auth.set_default_credentials>`) will attempted to be
+              used.
+    """
+    def __init__(self, credentials=None):
+        credentials = credentials or get_default_credentials()
+        self._api_key_manager = _get_api_key_manager(credentials)
+
+    def create_api_key(self, datasets, name, apis=['sql', 'maps'], permissions=['select']):
+        tables = []
+        for dataset in datasets:
+            table_names = dataset.get_table_names()
+            for table_name in table_names:
+                tables.append(_get_table_dict(dataset.schema, table_name, permissions))
+
+        api_key = self._api_key_manager.create(
+            name=name,
+            apis=apis,
+            tables=tables)
+
+        return api_key.token
+
+
+def _get_table_dict(schema, table, permissions):
+    return {
+        "schema": schema,
+        "name": table,
+        "permissions": permissions
+    }
+
+
+def _get_api_key_manager(credentials):
+    auth_client = credentials.get_api_key_auth_client()
+    return APIKeyManager(auth_client)
diff --git a/cartoframes/viz/kuviz.py b/cartoframes/viz/kuviz.py
index 9c2b77d0..24e6054e 100644
--- a/cartoframes/viz/kuviz.py
+++ b/cartoframes/viz/kuviz.py
@@ -1,19 +1,29 @@
 from copy import deepcopy
 from warnings import warn
+import time
 
 from carto.kuvizs import KuvizManager
+from carto.exceptions import CartoException
 
 from ..auth import get_default_credentials
 from .source import Source
 from ..utils.columns import normalize_name
+from ..data.clients.auth_api_client import AuthAPIClient
 
 from warnings import filterwarnings
 filterwarnings("ignore", category=FutureWarning, module="carto")
 
+DEFAULT_PUBLIC = 'default_public'
+
 
 class KuvizPublisher(object):
-    def __init__(self, layers):
-        self._layers = deepcopy(layers)
+    def __init__(self, credentials=None):
+        self.kuviz = None
+        self._maps_api_key = DEFAULT_PUBLIC
+
+        self._credentials = credentials or get_default_credentials()
+        self._auth_client = _create_auth_client(self._credentials)
+        self._layers = []
 
     @staticmethod
     def all(credentials=None):
@@ -22,48 +32,72 @@ class KuvizPublisher(object):
         kuvizs = km.all()
         return [kuviz_to_dict(kuviz) for kuviz in kuvizs]
 
-    def set_credentials(self, credentials=None):
-        self._credentials = credentials or get_default_credentials()
-        self._auth_client = _create_auth_client(self._credentials)
-
-    def publish(self, html, name, password=None):
-        return _create_kuviz(html=html, name=name, auth_client=self._auth_client, password=password)
+    def get_layers(self):
+        return self._layers
 
-    def is_sync(self):
-        return all(layer.source.dataset.is_remote() for layer in self._layers)
+    def set_layers(self, layers, name, table_name=None):
+        table_name = table_name or '{}_{}_table'.format(name, int(time.time() * 1000))
 
-    def is_public(self):
-        return all(layer.source.dataset.is_public() for layer in self._layers)
+        self._sync_layers(layers, table_name)
+        self._manage_maps_api_key(name)
+        self._add_layers_credentials()
 
-    def get_layers(self, maps_api_key='default_public'):
+    def publish(self, html, name, password=None):
+        self.kuviz = _create_kuviz(html=html, name=name, auth_client=self._auth_client, password=password)
+        return kuviz_to_dict(self.kuviz)
+
+    def update(self, data, name, password):
+        if not self.kuviz:
+            raise CartoException('The map has not been published yet. Use the `publish` method instead.')
+
+        self.kuviz.data = data
+        self.kuviz.name = name
+        self.kuviz.password = password
+        self.kuviz.save()
+
+        return kuviz_to_dict(self.kuviz)
+
+    def delete(self):
+        if self.kuviz:
+            self.kuviz.delete()
+            warn("Publication '{n}' ({id}) deleted".format(n=self.kuviz.name, id=self.kuviz.id))
+            self.kuviz = None
+            return True
+        return False
+
+    def _sync_layers(self, layers, table_name=None):
+        for idx, layer in enumerate(layers):
+            if layer.source.dataset.is_local():
+                table_name = normalize_name("{name}_{idx}".format(name=table_name, idx=idx))
+                layer = self._sync_layer(layer, table_name)
+            else:
+                layer = deepcopy(layer)
+            self._layers.append(layer)
+
+    def _sync_layer(self, layer, table_name):
+        layer.source.dataset.upload(table_name=table_name, credentials=self._credentials)
+        layer.source = Source(table_name, credentials=self._credentials)
+
+        return layer
+
+    def _manage_maps_api_key(self, name):
+        non_public_datasets = [layer.source.dataset
+                               for layer in self._layers
+                               if not layer.source.dataset.is_public()]
+
+        if len(non_public_datasets) > 0:
+            api_key_name = '{}_{}_api_key'.format(name, int(time.time() * 1000))
+            auth_api_client = AuthAPIClient(self._credentials)
+            self._maps_api_key = auth_api_client.create_api_key(non_public_datasets, api_key_name, ['maps'])
+
+    def _add_layers_credentials(self):
         for layer in self._layers:
-            layer.source.dataset.credentials = self._credentials
-
             layer.credentials = {
-                'username': self._credentials.username,
-                'api_key': maps_api_key,
-                'base_url': self._credentials.base_url
+                'username': layer.source.dataset.credentials.username,
+                'api_key': self._maps_api_key,
+                'base_url': layer.source.dataset.credentials.base_url
             }
 
-        return self._layers
-
-    def sync_layers(self, table_name, credentials=None):
-        for idx, layer in enumerate(self._layers):
-            table_name = normalize_name("{name}_{idx}".format(name=table_name, idx=idx + 1))
-
-            dataset_credentials = credentials or layer.source.dataset.credentials or get_default_credentials()
-
-            self._sync_layer(layer, table_name, dataset_credentials)
-
-    def _sync_layer(self, layer, table_name, credentials):
-        if layer.source.dataset.is_local():
-            layer.source.dataset.upload(table_name=table_name, credentials=credentials)
-            layer.source = Source(table_name, credentials=credentials)
-            warn('Table `{}` created. In order to publish the map, you will need to create a new Regular API '
-                 'key with permissions to Maps API and the table `{}`. You can do it from your CARTO dashboard or '
-                 'using the Auth API. You can get more info at '
-                 'https://carto.com/developers/auth-api/guides/types-of-API-Keys/'.format(table_name, table_name))
-
 
 def _create_kuviz(html, name, auth_client, password=None):
     km = _get_kuviz_manager(auth_client)
diff --git a/cartoframes/viz/map.py b/cartoframes/viz/map.py
index 18d6a0ce..52d2a8e2 100644
--- a/cartoframes/viz/map.py
+++ b/cartoframes/viz/map.py
@@ -8,7 +8,7 @@ from carto.exceptions import CartoException
 
 from . import constants
 from .basemaps import Basemaps
-from .kuviz import KuvizPublisher, kuviz_to_dict
+from .kuviz import KuvizPublisher
 from .html.HTMLMap import HTMLMap
 from ..utils.utils import get_center
 
@@ -212,7 +212,8 @@ class Map(object):
 
         self._carto_vl_path = kwargs.get('_carto_vl_path', None)
         self._airship_path = kwargs.get('_airship_path', None)
-        self._publisher = self._get_publisher()
+
+        self._publisher = None
         self._kuviz = None
 
         self.camera = None
@@ -270,16 +271,20 @@ class Map(object):
             '_airship_path': self._airship_path
         }
 
-    def publish(self, name, maps_api_key='default_public', credentials=None, password=None):
+    def publish(self, name, table_name=None, credentials=None, password=None):
         """Publish the map visualization as a CARTO custom visualization (aka Kuviz).
 
         Args:
             name (str): The Kuviz name on CARTO
-            maps_api_key (str, optional): A Regular API key with permissions
-                to Maps API and datasets used by the map
+            table_name (str, optional): Desired table name for the dataset on CARTO.
+                It is required working with local data (we need to upload it to CARTO)
+                If name does not conform to SQL naming conventions, it will be
+                'normalized' (e.g., all lower case, adding `_` in place of spaces
+                and other special characters.
             credentials (:py:class:`Credentials <cartoframes.auth.Credentials>`, optional):
                 A Credentials instance. If not provided, the credentials will be automatically
-                obtained from the default credentials if available.
+                obtained from the default credentials if available. It is used to create the
+                publication and also to save local data (if exists) into your CARTO account
             password (str, optional): setting it your Kuviz will be protected by
                 password. When someone will try to show the Kuviz, the password
                 will be requested
@@ -296,65 +301,27 @@ class Map(object):
                 tmap.publish('Custom Map Title')
 
         """
-        if not self._publisher.is_sync():
-            raise CartoException('The map layers are not synchronized with CARTO. '
-                                 'Please, use the `sync_data` method before publishing the map')
+        self._publisher = _get_publisher(table_name, credentials)
+        self._publisher.set_layers(self.layers, name, table_name)
 
-        if maps_api_key == 'default_public':
-            self._validate_public_publication()
-
-        self._publisher.set_credentials(credentials)
-        html = self._get_publication_html(name, maps_api_key)
-        self._kuviz = self._publisher.publish(html, name, password)
-        return kuviz_to_dict(self._kuviz)
-
-    def sync_data(self, table_name, credentials=None):
-        """Synchronize datasets used by the map with CARTO.
-
-        Args:
-            table_name (str): Desired table name for the dataset on CARTO. If
-                name does not conform to SQL naming conventions, it will be
-                'normalized' (e.g., all lower case, adding `_` in place of spaces
-                and other special characters.
-            credentials (:py:class:`Credentials <cartoframes.auth.Credentials>`, optional):
-                A Credentials instance. If not provided, the credentials will be automatically
-                obtained from the default credentials if available.
-        """
-        if not self._publisher.is_sync():
-            self._publisher.sync_layers(table_name, credentials)
+        html = self._get_publication_html(name)
+        return self._publisher.publish(html, name, password)
 
     def delete_publication(self):
         """Delete the published map Kuviz."""
-        if self._kuviz:
-            self._kuviz.delete()
-            print("Publication '{n}' ({id}) deleted".format(n=self._kuviz.name, id=self._kuviz.id))
-            self._kuviz = None
+        return self._publisher.delete()
 
-    def update_publication(self, name, password, maps_api_key='default_public'):
+    def update_publication(self, name, password):
         """Update the published map Kuviz.
 
         Args:
             name (str): The Kuviz name on CARTO
             password (str): setting it your Kuviz will be protected by
                 password and using `None` the Kuviz will be public
-            maps_api_key (str, optional): A Regular API key with permissions
-                to Maps API and datasets used by the map
         """
-        if not self._kuviz:
-            raise CartoException('The map has not been published. Use the `publish` method.')
-
-        if not self._publisher.is_sync():
-            raise CartoException('The map layers are not synchronized with CARTO. '
-                                 'Please, use the `sync_data` method before publishing the map')
-
-        if maps_api_key == 'default_public':
-            self._validate_public_publication()
 
-        self._kuviz.data = self._get_publication_html(name, maps_api_key)
-        self._kuviz.name = name
-        self._kuviz.password = password
-        self._kuviz.save()
-        return kuviz_to_dict(self._kuviz)
+        html = self._get_publication_html(name)
+        return self._publisher.update(html, name, password)
 
     @staticmethod
     def all_publications(credentials=None):
@@ -367,10 +334,10 @@ class Map(object):
         """
         return KuvizPublisher.all(credentials)
 
-    def _get_publication_html(self, name, maps_api_key):
+    def _get_publication_html(self, name):
         html_map = HTMLMap('templates/viz/main.html.j2')
         html_map.set_content(
-            layers=_get_layer_defs(self._publisher.get_layers(maps_api_key)),
+            layers=_get_layer_defs(self._publisher.get_layers()),
             bounds=self.bounds,
             size=None,
             viewport=self.viewport,
@@ -391,15 +358,9 @@ class Map(object):
         if default_legend and not title:
             raise CartoException('The default legend needs a map title to be displayed')
 
-    def _get_publisher(self):
-        return KuvizPublisher(self.layers)
 
-    def _validate_public_publication(self):
-        if not self._publisher.is_public():
-            raise CartoException('The datasets used in your map are not public. '
-                                 'You need add new Regular API key with permissions to Maps API and the datasets. '
-                                 'You can do it from your CARTO dashboard or using the Auth API. You can get more '
-                                 'info at https://carto.com/developers/auth-api/guides/types-of-API-Keys/')
+def _get_publisher(self, credentials):
+    return KuvizPublisher(credentials)
 
 
 def _get_bounds(bounds, layers):
diff --git a/docs/developer-center/guides/10-Publishing.md b/docs/developer-center/guides/10-Publishing.md
index 29fcaafa..5b2b45b8 100644
--- a/docs/developer-center/guides/10-Publishing.md
+++ b/docs/developer-center/guides/10-Publishing.md
@@ -2,69 +2,71 @@
 
 This guide walks through the steps to publish a visualization and get a URL to share it. To do this, we'll need to use "CARTO custom visualizations", also known as **Kuviz**.
 
-Let's start by creating a default credentials.
+Let's start by creating a default credentials. You will need to use your `master API key` in the notebook in order to create a visualization, but it is not going to be used in your visualization and your `master API key` will not be shared:
+- The visualizations use `default public API key` when possible (if you use public datasets from your CARTO account).
+- If it is not possible, a `regular API key` is created with read only permissions of your data used in the map.
+
+You can get more info about API keys at https://carto.com/developers/auth-api/guides/types-of-API-Keys/
 
 ```py
-from cartoframes.auth import set_default_credentials
+from cartoframes.auth import Credentials, set_default_credentials
 
-set_default_credentials('your_carto_user')
+set_default_credentials(Credentials.from_file())
 ```
 
 ### Publish
 
-#### Case 1: using a synchronized and public table
+#### Case 1: public dataset from your CARTO account
 
-The 'publish' method uses 'default_public' by default. Therefore, I don't need to use my API Key in this case. Additionally, it's possible to publish a visualization with **password**.
+As you are using a public table from your account, the publish method uses default public API key.
 
 ```py
 from cartoframes.viz import Map, Layer
 
-tmap = Map(Layer('public_table_name')) # -> Set here a table name from your account
-tmap.publish('cf_publish_case_1')
-tmap.publish('cf_publish_case_1_password', password="1234")
+public_data_map = Map(Layer('public_table_name')) # -> Set here a public table name from your account
+public_data_map.publish('public_data_map')
 ```
 
-#### Case 2: using a synchronized and private table
-
-In this case it's mandatory to add the `maps_api_key` parameter in the publish method. You can get more info at [https://carto.com/developers/auth-api/guides/types-of-API-Keys/](https://carto.com/developers/auth-api/guides/types-of-API-Keys/). This is due to the `publish` method uses `default_public` by default, and the dataset is private.
+#### Case 2: private dataset from your CARTO account
 
-1. Go to https://your_carto_user.carto.com/your_apps URL
-2. Click on "New API Key"
-3. Give the API key a name
-4. Select the related dataset
-5. Save changes
-6. Use your new Maps API Key!
-
-![API Keys dashboard page](../../img/guides/publishing/publishing-1.png)
+In this case, a regular API key has been created with read only permissions of your private table.
 
 ```py
 from cartoframes.viz import Map, Layer
 
-tmap = Map(Layer('private_table_name')) # -> Set here a table name from your account
-tmap.publish('cf_publish_case_2', maps_api_key='your_maps_api_key')
+private_data_map = Map(Layer('private_table_name')) # -> Set here a private table name from your account
+private_data_map.publish('cf_publish_case_2')
 ```
 
-#### Case 3: using a non-synchronized and private table
-
-If you try to publish a non synchronized dataset, you will get an **error**:
+#### Case 3: local data
 
-> 'The map layers are not synchronized with CARTO. Please, use the `sync_data` before publishing the map'
-
-As the error message says, we'll need to make a previous step synchronizing the data. Once it's been synchronized, as your new table will be private, you will need to create a Maps API key **with permissions** for your new private table from your CARTO dashboard or Auth API.
-
-Finally, we will be ready to publish the visualization!
+In this case, a table has been created in your CARTO account and a `regular API key` with read only permissions of the table.
 
 ```py
 from cartoframes.viz import Map, Layer
 from cartoframes.data import Dataset
 
-ds = Dataset('private_table_name')
-ds.download()
+# getting a DataFrame from a table for the example
+# but you can try it with the DataFrame you wish
+ds = Dataset('rings') # -> Set here a table name from your account
+df = ds.download()
+
+# create the map with your DataFrame
+ds = Dataset(df)
+local_data_map = Map(Layer(ds))
 
-tmap = Map(Layer(ds))
+local_data_map.publish('local_data_map')
+```
+
+### Publish with password
 
-tmap.sync_data('private_table_name_sync')
-tmap.publish('cf_publish_case_2', maps_api_key='your_maps_api_key')
+In any of the previous cases, you could create the shared visualization with a password. You only need to add a password parameter in the publish method:
+
+```py
+from cartoframes.viz import Map, Layer
+
+tmap = Map(Layer('public_table_name'))
+public_data_map.publish('public_data_map_with_password', password="1234")
 ```
 
 ### Update
diff --git a/docs/guides/kuviz.rst b/docs/guides/kuviz.rst
index c45c598f..fb813bf1 100644
--- a/docs/guides/kuviz.rst
+++ b/docs/guides/kuviz.rst
@@ -5,33 +5,47 @@ Publishing and sharing a map
 
 In CARTOframes, you can publish a map visualization and then share it. When you publish the map visualization, you receive an URL where to see your visualization outside of the Notebook, so you can share it or use it where you want.
 
+You will need to use your `master API key` in the notebook in order to create a visualization, but it is not going to be used in your visualization and your `master API key` will not be shared:
+- The visualizations use `default public API key` when possible (if you use public datasets from your CARTO account).
+- If it is not possible, a `regular API key` is created with read only permissions of your data used in the map.
+
 
 Publishing a map
 ----------------
 
 .. code::
 
-    from cartoframes.viz import Map, Layer, basemaps
+    from cartoframes.viz import Map, Layer
 
     tmap = Map(Layer(PUBLIC_TABLE))
     tmap.publish('cf_publish_case_1')
 
 
-The 'publish' method uses 'default_public' API key by default. So, if you want to publish a map using private datasets, you should add a Regular API key with permissions to Maps API and the datasets.
+As you are using a public table from your account, the publish method uses default public API key.
 
 .. code::
 
-    from cartoframes.viz import Map, Layer, basemaps
+    from cartoframes.viz import Map, Layer
 
     tmap = Map(Layer(PRIVATE_TABLE))
-    tmap.publish('cf_publish_case_2', maps_api_key='YOUR MAPS API KEY')
+    tmap.publish('cf_publish_case_2')
+
+
+In this case, a regular API key has been created with read only permissions of your private table.
 
+.. code::
+    df = pd.DataFrame(...)
+    ds = Dataset(df)
+    local_data_map = Map(Layer(ds))
+    local_data_map.publish('local_data_map')
+
+In this case, a table has been created in your CARTO account and a `regular API key` with read only permissions of the table.
 
 Anyway, you can publish a map visualization protected by password:
 
 .. code::
 
-    from cartoframes.viz import Map, Layer, basemaps
+    from cartoframes.viz import Map, Layer
 
     tmap = Map(Layer(PUBLIC_TABLE))
     tmap.publish('cf_publish_case_1_password', password="1234")
@@ -40,15 +54,6 @@ Anyway, you can publish a map visualization protected by password:
 In every case, the `publish` method will return the `id`, `url`, `name` and `privacy` of the visualization.
 
 
-Synchrorize data
-----------------
-
-If the data used by your map is not synchronized with CARTO, you will need to use `sync_data` method before publishing:
-
-.. code::
-
-    tmap_with_local_data.sync_data(table_name)
-
 
 Updating a published visualization
 ----------------------------------
diff --git a/examples/05_publishing/01_publish_visualization_using_kuviz.ipynb b/examples/05_publishing/01_publish_visualization_using_kuviz.ipynb
index 3abf0687..098d2afb 100644
--- a/examples/05_publishing/01_publish_visualization_using_kuviz.ipynb
+++ b/examples/05_publishing/01_publish_visualization_using_kuviz.ipynb
@@ -6,16 +6,27 @@
    "source": [
     "# Publishing and sharing visualizations\n",
     "\n",
-    "We're going to get through how to publish a visualization by getting the final URL to share it. For that purpose, we'll need to use the \"CARTO custom visualizations\", as known as **Kuviz**.\n",
+    "We're going to get through how to publish a visualization by getting the final URL to share it. For that purpose, we'll need to use the \"CARTO custom visualizations\", also known as **Kuviz**.\n",
     "\n",
-    "Let's start by creating a default credentials."
+    "Let's start by creating a default credentials instance."
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "## Set the Credentials"
+    "# Set the Credentials"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "You will need to use your `master API key` in the notebook in order to create a visualization, but it is not going to be used in your visualization and your `master API key` will not be shared:\n",
+    "- The visualizations use `default public API key` when possible (if you use public datasets from your CARTO account). \n",
+    "- If it is not possible, a `regular API key` is created with read only permissions of your data used in the map. \n",
+    "\n",
+    "You can get more info about API keys at https://carto.com/developers/auth-api/guides/types-of-API-Keys/"
    ]
   },
   {
@@ -24,26 +35,23 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "from cartoframes.auth import set_default_credentials\n",
+    "from cartoframes.auth import Credentials, set_default_credentials\n",
     "\n",
-    "set_default_credentials(\n",
-    "    username='your_carto_user',\n",
-    "    api_key='your_api_key'\n",
-    ")"
+    "set_default_credentials(Credentials.from_file())"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "## Publish"
+    "# Publish"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "### Case 1: using a synchronized and public table"
+    "### Case 1: public dataset from your CARTO account"
    ]
   },
   {
@@ -54,7 +62,7 @@
    "source": [
     "from cartoframes.viz import Map, Layer\n",
     "\n",
-    "tmap = Map(Layer('public_table_name')) # -> Set here a table name from your account"
+    "public_data_map = Map(Layer('')) # -> Set here a public table name from your account"
    ]
   },
   {
@@ -63,14 +71,14 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "tmap.publish('cf_publish_case_1')"
+    "public_data_map.publish('public_data_map')"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "The 'publish' method uses 'default_public' by default. Therefore, I don't need to use my API Key in this case. Additionally, it's possible to publish a visualization with **password**."
+    "As you are using a public table from your account, the `publish` method uses `default public API key`."
    ]
   },
   {
@@ -79,14 +87,14 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "tmap.publish('cf_publish_case_1_password', password=\"1234\")"
+    "public_data_map.publish('public_data_map_with_password', password=\"1234\")"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "### Case 2: using a synchronized and private table"
+    "### Case 2: private dataset from your CARTO account"
    ]
   },
   {
@@ -95,16 +103,14 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "from cartoframes.viz import Map, Layer\n",
-    "\n",
-    "tmap = Map(Layer(PRIVATE_TABLE))"
+    "private_data_map = Map(Layer('')) # -> Set here a private table name from your account"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "In this case it's mandatory to add the `maps_api_key` parameter in the publish method. You can get more info at https://carto.com/developers/auth-api/guides/types-of-API-Keys/. This is due to the `publish` method uses `default_public` by default, and the dataset is private."
+    "In this case, a `regular API key` has been created with read only permissions of your private table"
    ]
   },
   {
@@ -113,14 +119,14 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "tmap.publish('cf_publish_case_2', maps_api_key='your_maps_api_key')"
+    "private_data_map.publish('private_data_map')"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "### Case 3: using a non-synchronized (or using local data)"
+    "### Case 3: local data"
    ]
   },
   {
@@ -132,40 +138,44 @@
     "from cartoframes.viz import Map, Layer\n",
     "from cartoframes.data import Dataset\n",
     "\n",
-    "ds = Dataset('private_table_name')\n",
+    "# getting a DataFrame from a table for the example\n",
+    "# but you can try it with the DataFrame you wish \n",
+    "ds = Dataset('rings') # -> Set here a table name from your account\n",
     "df = ds.download()\n",
     "\n",
-    "# do some changes in the DataFrame\n",
-    "# and recreate the Dataset\n",
-    "\n",
+    "# create the map with your DataFrame\n",
     "ds = Dataset(df)\n",
-    "\n",
-    "tmap = Map(Layer(ds))"
+    "local_data_map = Map(Layer(ds))"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "local_data_map.publish('local_data_map')"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "In this case, if you try to publish a non synchronized dataset, you will get an error:\n",
-    "\n",
-    "> 'The map layers are not synchronized with CARTO. Please, use the `sync_data` before publishing the map'"
+    "In this case, a table has been created in your CARTO account and a `regular API key` with read only permissions of the table"
    ]
   },
   {
-   "cell_type": "code",
-   "execution_count": null,
+   "cell_type": "markdown",
    "metadata": {},
-   "outputs": [],
    "source": [
-    "tmap.publish('cf_publish_case_3')"
+    "# Publish with password"
    ]
   },
   {
    "cell_type": "markdown",
    "metadata": {},
    "source": [
-    "As the error message says, we will need to make a previous step sychronizing the data. After that as your new table will be private, you will need to create a Maps API key with permissions for your new private table from your CARTO dashboard or Auth API. And finally, we will be ready to publish the visualization."
+    "In any of the previous cases, you could create the shared visualization with a password. You only need to add a `password` parameter in the publish method"
    ]
   },
   {
@@ -174,7 +184,9 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "tmap.sync_data('private_table_name_sync')"
+    "from cartoframes.viz import Map, Layer\n",
+    "\n",
+    "public_data_map = Map(Layer('')) # -> Set here a public table name from your account"
    ]
   },
   {
@@ -183,7 +195,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "tmap.publish('cf_publish_case_3', maps_api_key='your_maps_api_key')"
+    "public_data_map.publish('public_data_map_with_password', password=\"1234\")"
    ]
   },
   {
@@ -218,7 +230,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "tmap.update_publication('cf_publish_update_2', password=None)"
+    "tmap.update_publication('cf_publish_case_2_password', password=None)"
    ]
   },
   {
@@ -234,7 +246,7 @@
    "metadata": {},
    "outputs": [],
    "source": [
-    "tmap.update_publication('cf_publish_update_3', password=\"1234\")"
+    "tmap.update_publication('cf_publish_case_2_password', password=\"1234\")"
    ]
   },
   {
@@ -268,7 +280,7 @@
    "source": [
     "from cartoframes.viz import Map, Layer, basemaps\n",
     "\n",
-    "tmap = Map(Layer(PUBLIC_TABLE))\n",
+    "tmap = Map(Layer('')) # -> Set here a public table name from your account\n",
     "tmap.publish('cf_publish_delete_1')"
    ]
   },
@@ -300,6 +312,7 @@
   }
  ],
  "metadata": {
+  "file_extension": ".py",
   "kernelspec": {
    "display_name": "Python 3",
    "language": "python",
@@ -315,8 +328,13 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.6.7"
-  }
+   "version": "3.7.3"
+  },
+  "mimetype": "text/x-python",
+  "name": "python",
+  "npconvert_exporter": "python",
+  "pygments_lexer": "ipython3",
+  "version": 3
  },
  "nbformat": 4,
  "nbformat_minor": 2


Test patch:
diff --git a/test/data/client/test_auth_api_client.py b/test/data/client/test_auth_api_client.py
new file mode 100644
index 00000000..2260906f
--- /dev/null
+++ b/test/data/client/test_auth_api_client.py
@@ -0,0 +1,62 @@
+import unittest
+
+from cartoframes.data.clients.auth_api_client import AuthAPIClient
+from cartoframes.data import Dataset
+
+
+try:
+    from unittest.mock import patch
+except ImportError:
+    from mock import patch
+
+TOKEN_MOCK = '1234'
+
+
+class APIKeyMock():
+    def __init__(self, name):
+        self.name = name
+        self.token = TOKEN_MOCK
+        self.type = None
+        self.created_at = None
+        self.updated_at = None
+        self.grants = None
+
+
+class APIKeyManagerMock():
+    def create(self, name, **kwargs):
+        return APIKeyMock(name)
+
+
+class TestSQLClient(unittest.TestCase):
+    @patch('cartoframes.data.clients.auth_api_client._get_api_key_manager')
+    def test_instantiation(self, get_api_key_manager_mock):
+        get_api_key_manager_mock.return_value = APIKeyManagerMock()
+
+        auth_api_client = AuthAPIClient()
+
+        assert isinstance(auth_api_client, AuthAPIClient)
+        assert isinstance(auth_api_client._api_key_manager, APIKeyManagerMock)
+
+    @patch('cartoframes.data.clients.auth_api_client._get_api_key_manager')
+    def test_create_api_key(self, get_api_key_manager_mock):
+        get_api_key_manager_mock.return_value = APIKeyManagerMock()
+
+        dataset = Dataset('fake_table')
+        api_key_name = 'fake_name'
+
+        auth_api_client = AuthAPIClient()
+        token = auth_api_client.create_api_key([dataset], api_key_name)
+
+        assert token == TOKEN_MOCK
+
+    @patch('cartoframes.data.clients.auth_api_client._get_api_key_manager')
+    def test_create_api_key_several_datasets(self, get_api_key_manager_mock):
+        get_api_key_manager_mock.return_value = APIKeyManagerMock()
+
+        dataset = Dataset('fake_table')
+        api_key_name = 'fake_name'
+
+        auth_api_client = AuthAPIClient()
+        token = auth_api_client.create_api_key([dataset, dataset, dataset], api_key_name)
+
+        assert token == TOKEN_MOCK
diff --git a/test/data/dataset/test_dataset.py b/test/data/dataset/test_dataset.py
index 3db4ce34..af743202 100644
--- a/test/data/dataset/test_dataset.py
+++ b/test/data/dataset/test_dataset.py
@@ -23,7 +23,6 @@ from cartoframes.data.dataset.registry.table_dataset import TableDataset
 from cartoframes.data.dataset.registry.query_dataset import QueryDataset
 from cartoframes.data.dataset.registry.base_dataset import BaseDataset
 from cartoframes.lib import context
-from cartoframes.utils.columns import DataframeColumnsInfo
 
 try:
     from unittest.mock import Mock
@@ -724,6 +723,32 @@ class TestDatasetUnit(unittest.TestCase, _UserUrlLoader):
         result = dataset._create_table_query(dataframe_columns_info.columns)
         self.assertEqual(result, expected_result)
 
+    def test_create_table_query_with_several_geometry_columns_prioritize_the_geom(self):
+        df = pd.DataFrame([['POINT (0 0)', 'POINT (1 1)', 'POINT (2 2)']], columns=['geom', 'the_geom', 'geometry'])
+        dataframe_columns_info = DataframeColumnsInfo(df, None)
+        table_name = 'fake_table'
+        expected_result = 'CREATE TABLE {} (geom text, the_geom geometry(Point, 4326), geometry text)'.format(
+            table_name)
+
+        dataset = DataFrameDataset(df)
+        dataset.table_name = table_name
+        result = dataset._create_table_query(dataframe_columns_info.columns)
+        self.assertEqual(result, expected_result)
+
+    def test_create_table_query_with_several_geometry_columns_and_geodataframe_prioritize_geometry(self):
+        df = pd.DataFrame([['POINT (0 0)', 'POINT (1 1)', 'POINT (2 2)']], columns=['geom', 'the_geom', 'geometry'])
+        from shapely.wkt import loads
+        df['geometry'] = df['geometry'].apply(loads)
+        gdf = gpd.GeoDataFrame(df, geometry='geometry')
+        dataframe_columns_info = DataframeColumnsInfo(gdf, None)
+        table_name = 'fake_table'
+        expected_result = 'CREATE TABLE {} (geom text, the_geom geometry(Point, 4326))'.format(table_name)
+
+        dataset = DataFrameDataset(gdf)
+        dataset.table_name = table_name
+        result = dataset._create_table_query(dataframe_columns_info.columns)
+        self.assertEqual(result, expected_result)
+
     def test_dataset_upload_one_geometry_that_is_not_the_geom_uses_the_geom(self):
         table = 'fake_table'
         credentials = 'fake'
@@ -773,6 +798,25 @@ class TestDatasetUnit(unittest.TestCase, _UserUrlLoader):
         self.assertEqual(ds._strategy._context.query, expected_query)
         self.assertEqual(list(ds._strategy._context.response), expected_data)
 
+    def test_dataset_upload_with_several_geometry_columns_and_geodataframe_prioritize_geometry(self):
+        table = 'fake_table'
+        credentials = 'fake'
+        df = pd.DataFrame([['POINT (0 0)', 'POINT (1 1)', 'POINT (2 2)']], columns=['geom', 'the_geom', 'geometry'])
+        from shapely.wkt import loads
+        df['geometry'] = df['geometry'].apply(loads)
+        gdf = gpd.GeoDataFrame(df, geometry='geometry')
+        ds = Dataset(gdf)
+
+        BaseDataset.exists = Mock(return_value=False)
+
+        ds.upload(table_name=table, credentials=credentials)
+
+        expected_query = "COPY {}(geom,the_geom,cartodb_id) FROM stdin WITH (FORMAT csv, DELIMITER '|');".format(table)
+        expected_data = [b'POINT (0 0)|SRID=4326;POINT (2 2)|0\n']
+
+        self.assertEqual(ds._strategy._context.query, expected_query)
+        self.assertEqual(list(ds._strategy._context.response), expected_data)
+
     def test_dataset_upload_the_geom_webmercator_column_is_removed(self):
         table = 'fake_table'
         credentials = 'fake'
diff --git a/test/mocks/kuviz_mock.py b/test/mocks/kuviz_mock.py
index 5db564ce..69a0b4a0 100644
--- a/test/mocks/kuviz_mock.py
+++ b/test/mocks/kuviz_mock.py
@@ -1,8 +1,5 @@
 from carto.kuvizs import Kuviz
 
-from cartoframes.viz.kuviz import KuvizPublisher, kuviz_to_dict
-from .dataset_mock import DatasetMock
-
 PRIVACY_PUBLIC = 'public'
 PRIVACY_PASSWORD = 'password'
 
@@ -17,29 +14,8 @@ class CartoKuvizMock(Kuviz):
         else:
             self.privacy = PRIVACY_PUBLIC
 
-    def delete(self):
-        return True
-
     def save(self):
         return True
 
-
-def _create_kuviz(html, name, credentials=None, password=None):
-    return CartoKuvizMock(name=name, password=password)
-
-
-class KuvizPublisherMock(KuvizPublisher):
-    def publish(self, html, name, password=None):
-        return _create_kuviz(html=html, name=name, credentials=self._credentials, password=password)
-
-    def _sync_layer(self, layer, table_name, credentials):
-        layer.source.dataset = DatasetMock(table_name, credentials)
-
-    def is_public(self):
+    def delete(self):
         return True
-
-    @staticmethod
-    def all():
-        kuviz = CartoKuvizMock(name="test")
-        kuvizs = [kuviz, kuviz, kuviz]
-        return [kuviz_to_dict(kuviz) for kuviz in kuvizs]
diff --git a/test/viz/test_kuviz.py b/test/viz/test_kuviz.py
index cc0020cc..ec3230b2 100644
--- a/test/viz/test_kuviz.py
+++ b/test/viz/test_kuviz.py
@@ -2,44 +2,26 @@
 
 import unittest
 
+from carto.exceptions import CartoException
+
 from cartoframes.lib import context
 from cartoframes.viz import Map, Layer, Source
-from cartoframes.data import StrategiesRegistry
+from cartoframes.data import Dataset, StrategiesRegistry
 from cartoframes.auth import Credentials
+from cartoframes.data.clients.auth_api_client import AuthAPIClient
+from cartoframes.viz.kuviz import KuvizPublisher, DEFAULT_PUBLIC, kuviz_to_dict
 
-from ..mocks.kuviz_mock import KuvizPublisherMock, _create_kuviz, PRIVACY_PUBLIC, PRIVACY_PASSWORD
-from ..mocks.dataset_mock import DatasetMock
 from ..mocks.context_mock import ContextMock
+from ..mocks.kuviz_mock import CartoKuvizMock
 
 from .utils import build_geojson
 
+try:
+    from unittest.mock import patch
+except ImportError:
+    from mock import patch
 
-class TestKuviz(unittest.TestCase):
-    def setUp(self):
-        self.username = 'fake_username'
-        self.api_key = 'fake_api_key'
-        self.credentials = Credentials(username=self.username, api_key=self.api_key)
-
-        self.html = "<html><body><h1>Hi Kuviz yeee</h1></body></html>"
-
-    def tearDown(self):
-        StrategiesRegistry.instance = None
-
-    def test_kuviz_create(self):
-        name = 'test-name'
-        kuviz = _create_kuviz(credentials=self.credentials, html=self.html, name=name)
-        self.assertIsNotNone(kuviz.id)
-        self.assertIsNotNone(kuviz.url)
-        self.assertEqual(kuviz.name, name)
-        self.assertEqual(kuviz.privacy, PRIVACY_PUBLIC)
-
-    def test_kuviz_create_with_password(self):
-        name = 'test-name'
-        kuviz = _create_kuviz(credentials=self.credentials, html=self.html, name=name, password="1234")
-        self.assertIsNotNone(kuviz.id)
-        self.assertIsNotNone(kuviz.url)
-        self.assertEqual(kuviz.name, name)
-        self.assertEqual(kuviz.privacy, PRIVACY_PASSWORD)
+TOKEN_MOCK = '1234'
 
 
 class TestKuvizPublisher(unittest.TestCase):
@@ -63,7 +45,26 @@ class TestKuvizPublisher(unittest.TestCase):
         self.assertEqual(kuviz_dict['name'], name)
         self.assertEqual(kuviz_dict['privacy'], privacy)
 
-    def test_kuviz_publisher_create_local(self):
+    @patch('cartoframes.viz.kuviz._get_kuviz_manager')
+    @patch('cartoframes.viz.kuviz._create_auth_client')
+    def test_kuviz_publisher_instantiation(self, _create_auth_client_mock, _get_kuviz_manager_mock):
+        _create_auth_client_mock.return_value = None
+        _get_kuviz_manager_mock.return_value = None
+
+        kuviz_publisher = KuvizPublisher(None)
+
+        assert isinstance(kuviz_publisher, KuvizPublisher)
+        assert kuviz_publisher._maps_api_key == DEFAULT_PUBLIC
+        assert kuviz_publisher._layers == []
+
+    @patch.object(KuvizPublisher, '_sync_layer')
+    @patch('cartoframes.viz.kuviz._get_kuviz_manager')
+    @patch('cartoframes.viz.kuviz._create_auth_client')
+    def test_kuviz_publisher_set_layers(self, _create_auth_client_mock, _get_kuviz_manager_mock, _sync_layer):
+        _create_auth_client_mock.return_value = None
+        _get_kuviz_manager_mock.return_value = None
+        _sync_layer.return_value = Layer(Dataset('fake_table', self.credentials))
+
         source_1 = Source(build_geojson([-10, 0], [-10, 0]))
         source_2 = Source(build_geojson([0, 10], [10, 0]))
         layer_1 = Layer(source_1)
@@ -73,91 +74,199 @@ class TestKuvizPublisher(unittest.TestCase):
             layer_2
         ])
 
-        kp = KuvizPublisherMock(vmap.layers)
-        self.assertNotEqual(kp._layers, vmap.layers)
-        self.assertEqual(len(kp._layers), len(vmap.layers))
+        kuviz_publisher = KuvizPublisher(None)
+        kuviz_publisher.set_layers(vmap.layers, 'fake_name', 'fake_table_name')
 
-    def test_kuviz_publisher_has_layers_copy(self):
-        source_1 = Source(build_geojson([-10, 0], [-10, 0]))
-        layer_1 = Layer(source_1)
-        vmap = Map(layer_1)
-
-        kp = KuvizPublisherMock(vmap.layers)
-        self.assertEqual(len(kp._layers), len(vmap.layers))
+        self.assertNotEqual(kuviz_publisher._layers, vmap.layers)
+        self.assertEqual(len(kuviz_publisher._layers), len(vmap.layers))
 
-        kp._layers = []
-        self.assertNotEqual(len(kp._layers), len(vmap.layers))
+    @patch.object(KuvizPublisher, '_sync_layer')
+    @patch('cartoframes.viz.kuviz._get_kuviz_manager')
+    @patch('cartoframes.viz.kuviz._create_auth_client')
+    def test_kuviz_publisher_has_layers_copy(self, _create_auth_client_mock, _get_kuviz_manager_mock, _sync_layer):
+        _create_auth_client_mock.return_value = None
+        _get_kuviz_manager_mock.return_value = None
+        _sync_layer.return_value = Layer(Dataset('fake_table', self.credentials))
 
-    def test_kuviz_publisher_from_local_sync(self):
-        source_1 = Source(build_geojson([-10, 0], [-10, 0]))
+        source_1 = Source(Dataset('fake_table_2', self.credentials))
         layer_1 = Layer(source_1)
-        vmap = Map(layer_1)
+        vmap = Map([layer_1])
 
-        kp = KuvizPublisherMock(vmap.layers)
-        self.assertEqual(kp.is_sync(), False)
+        kuviz_publisher = KuvizPublisher(None)
+        kuviz_publisher.set_layers(vmap.layers, 'fake_name', 'fake_table_name')
 
-    def test_kuviz_publisher_create_remote(self):
-        dataset = DatasetMock('fake_table', credentials=self.credentials)
-        vmap = Map(Layer(Source(dataset)))
+        self.assertEqual(len(kuviz_publisher._layers), len(vmap.layers))
+        self.assertNotEqual(kuviz_publisher._layers, vmap.layers)
 
-        kp = KuvizPublisherMock(vmap.layers)
-        self.assertNotEqual(kp._layers, vmap.layers)
-        self.assertEqual(len(kp._layers), len(vmap.layers))
+        vmap.layers = []
+        self.assertNotEqual(len(kuviz_publisher._layers), len(vmap.layers))
+        self.assertGreater(len(kuviz_publisher._layers), 0)
 
-    def test_kuviz_publisher_create_remote_sync(self):
-        dataset = DatasetMock('fake_table', credentials=self.credentials)
-        vmap = Map(Layer(Source(dataset)))
+    @patch.object(KuvizPublisher, '_sync_layer')
+    @patch('cartoframes.viz.kuviz._get_kuviz_manager')
+    @patch('cartoframes.viz.kuviz._create_auth_client')
+    def test_kuviz_publisher_from_local_is_sync(self, _create_auth_client_mock, _get_kuviz_manager_mock, _sync_layer):
+        _create_auth_client_mock.return_value = None
+        _get_kuviz_manager_mock.return_value = None
+        _sync_layer.return_value = Layer(Dataset('fake_table', self.credentials))
 
-        kp = KuvizPublisherMock(vmap.layers)
-        self.assertEqual(kp.is_sync(), True)
+        source_1 = Source(build_geojson([-10, 0], [-10, 0]))
+        layer_1 = Layer(source_1)
+        vmap = Map(layer_1)
+
+        kuviz_publisher = KuvizPublisher(None)
+        kuviz_publisher.set_layers(vmap.layers, 'fake_name', 'fake_table_name')
 
-    def test_kuviz_publisher_unsync(self):
-        dataset = DatasetMock(build_geojson([-10, 0], [-10, 0]))
-        vmap = Map(Layer(Source(dataset)))
+        layers = kuviz_publisher.get_layers()
 
-        kp = KuvizPublisherMock(vmap.layers)
-        self.assertEqual(kp.is_sync(), False)
+        self.assertEqual(vmap.layers[0].source.dataset.is_local(), True)
+        self.assertEqual(layers[0].source.dataset.is_remote(), True)
 
-    def test_kuviz_publisher_sync_layers(self):
-        dataset = DatasetMock(build_geojson([-10, 0], [-10, 0]))
-        vmap = Map(Layer(Source(dataset)))
+    @patch.object(KuvizPublisher, '_sync_layer')
+    @patch('cartoframes.viz.kuviz._get_kuviz_manager')
+    @patch('cartoframes.viz.kuviz._create_auth_client')
+    def test_kuviz_publisher_use_defaul_public(self, _create_auth_client_mock, _get_kuviz_manager_mock, _sync_layer):
+        _create_auth_client_mock.return_value = None
+        _get_kuviz_manager_mock.return_value = None
+        _sync_layer.return_value = Layer(Dataset('fake_table', self.credentials))
 
-        kp = KuvizPublisherMock(vmap.layers)
-        kp.sync_layers(table_name='fake_table', credentials=self.credentials)
-        self.assertEqual(kp.is_sync(), True)
+        dataset = Dataset('fake_table', credentials=self.credentials)
+        vmap = Map(Layer(dataset))
 
-    def test_kuviz_publisher_get_layers_defaul_apikey(self):
-        dataset = DatasetMock('fake_table', credentials=self.credentials)
-        vmap = Map(Layer(Source(dataset)))
+        kuviz_publisher = KuvizPublisher(None)
+        kuviz_publisher.set_layers(vmap.layers, 'fake_name', 'fake_table_name')
 
-        kp = KuvizPublisherMock(vmap.layers)
-        kp.set_credentials(self.credentials)
-        layers = kp.get_layers()
+        layers = kuviz_publisher.get_layers()
 
         self.assertEqual(layers[0].source.dataset.credentials, self.credentials)
         self.assertEqual(
             layers[0].credentials,
             {'username': self.username,
-             'api_key': 'default_public',
+             'api_key': DEFAULT_PUBLIC,
              'base_url': 'https://{}.carto.com'.format(self.username)})
 
-    def test_kuviz_publisher_get_layers_with_api_key(self):
-        dataset = DatasetMock('fake_table', credentials=self.credentials)
-        vmap = Map(Layer(Source(dataset)))
+    @patch.object(Dataset, 'is_public')
+    @patch.object(AuthAPIClient, 'create_api_key')
+    @patch('cartoframes.data.clients.auth_api_client._get_api_key_manager')
+    @patch('cartoframes.viz.kuviz._create_auth_client')
+    def test_kuviz_publisher_create_new_apikey(self, _create_auth_client_mock, _get_api_key_manager,
+                                               create_api_key, is_public):
+        token = '1234'
+
+        _create_auth_client_mock.return_value = None
+        create_api_key.return_value = token
+        _get_api_key_manager.return_value = None
+        is_public.return_value = False
+
+        dataset = Dataset('fake_table', credentials=self.credentials)
+        layers = [Layer(dataset)]
+
+        kuviz_publisher = KuvizPublisher(None)
+        kuviz_publisher._layers = layers
+
+        assert kuviz_publisher._maps_api_key == DEFAULT_PUBLIC
+        kuviz_publisher._manage_maps_api_key('fake_name')
+        assert kuviz_publisher._maps_api_key == token
+
+    @patch('cartoframes.viz.kuviz._create_kuviz')
+    @patch.object(KuvizPublisher, '_sync_layer')
+    @patch('cartoframes.viz.kuviz._get_kuviz_manager')
+    @patch('cartoframes.viz.kuviz._create_auth_client')
+    def test_kuviz_publisher_publish(self, _create_auth_client_mock, _get_kuviz_manager_mock, _sync_layer,
+                                     _create_kuviz):
+        kuviz = CartoKuvizMock('fake_kuviz')
+
+        _create_auth_client_mock.return_value = None
+        _get_kuviz_manager_mock.return_value = None
+        _sync_layer.return_value = Layer(Dataset('fake_table', self.credentials))
+        _create_kuviz.return_value = kuviz
+
+        dataset = Dataset('fake_table', credentials=self.credentials)
+        vmap = Map(Layer(dataset))
+
+        html = 'fake_html'
+        kuviz_name = 'fake_name'
+
+        kuviz_publisher = KuvizPublisher(None)
+        kuviz_publisher.set_layers(vmap.layers, kuviz_name, 'fake_table_name')
+        result = kuviz_publisher.publish(html, kuviz_name)
+
+        assert kuviz_publisher.kuviz == kuviz
+        assert result == kuviz_to_dict(kuviz)
+
+    @patch.object(KuvizPublisher, '_sync_layer')
+    @patch('cartoframes.viz.kuviz._get_kuviz_manager')
+    @patch('cartoframes.viz.kuviz._create_auth_client')
+    def test_kuviz_publisher_update_fail(self, _create_auth_client_mock, _get_kuviz_manager_mock, _sync_layer):
+        _create_auth_client_mock.return_value = None
+        _get_kuviz_manager_mock.return_value = None
+        _sync_layer.return_value = Layer(Dataset('fake_table', self.credentials))
+
+        dataset = Dataset('fake_table', credentials=self.credentials)
+        vmap = Map(Layer(dataset))
+
+        html = 'fake_html'
+        kuviz_name = 'fake_name'
+
+        kuviz_publisher = KuvizPublisher(None)
+        kuviz_publisher.set_layers(vmap.layers, kuviz_name, 'fake_table_name')
+
+        with self.assertRaises(CartoException):
+            kuviz_publisher.update(html, kuviz_name, None)
+
+    @patch('cartoframes.viz.kuviz._create_kuviz')
+    @patch.object(KuvizPublisher, '_sync_layer')
+    @patch('cartoframes.viz.kuviz._get_kuviz_manager')
+    @patch('cartoframes.viz.kuviz._create_auth_client')
+    def test_kuviz_publisher_update(self, _create_auth_client_mock, _get_kuviz_manager_mock, _sync_layer,
+                                    _create_kuviz):
+        kuviz = CartoKuvizMock('fake_kuviz')
+
+        _create_auth_client_mock.return_value = None
+        _get_kuviz_manager_mock.return_value = None
+        _sync_layer.return_value = Layer(Dataset('fake_table', self.credentials))
+        _create_kuviz.return_value = kuviz
+
+        dataset = Dataset('fake_table', credentials=self.credentials)
+        vmap = Map(Layer(dataset))
+
+        html = 'fake_html'
+        kuviz_name = 'fake_name'
+
+        kuviz_publisher = KuvizPublisher(None)
+        kuviz_publisher.set_layers(vmap.layers, kuviz_name, 'fake_table_name')
+        result_publish = kuviz_publisher.publish(html, kuviz_name)
+
+        kuviz.name = 'fake_name_2'
+        result_update = kuviz_publisher.update(html, kuviz_name, None)
+
+        assert kuviz_publisher.kuviz == kuviz
+        assert result_update == kuviz_to_dict(kuviz)
+        assert result_publish != kuviz_to_dict(kuviz)
+
+    @patch('cartoframes.viz.kuviz._create_kuviz')
+    @patch.object(KuvizPublisher, '_sync_layer')
+    @patch('cartoframes.viz.kuviz._get_kuviz_manager')
+    @patch('cartoframes.viz.kuviz._create_auth_client')
+    def test_kuviz_publisher_delete(self, _create_auth_client_mock, _get_kuviz_manager_mock, _sync_layer,
+                                    _create_kuviz):
+        kuviz = CartoKuvizMock('fake_kuviz')
 
-        kp = KuvizPublisherMock(vmap.layers)
-        kp.set_credentials(self.credentials)
-        maps_api_key = '1234'
-        layers = kp.get_layers(maps_api_key=maps_api_key)
+        _create_auth_client_mock.return_value = None
+        _get_kuviz_manager_mock.return_value = None
+        _sync_layer.return_value = Layer(Dataset('fake_table', self.credentials))
+        _create_kuviz.return_value = kuviz
 
-        self.assertEqual(layers[0].source.dataset.credentials, self.credentials)
-        self.assertEqual(
-            layers[0].credentials,
-            {'username': self.username,
-             'api_key': maps_api_key,
-             'base_url': 'https://{}.carto.com'.format(self.username)})
+        dataset = Dataset('fake_table', credentials=self.credentials)
+        vmap = Map(Layer(dataset))
 
-    def test_kuviz_publisher_all(self):
-        kuviz_dicts = KuvizPublisherMock.all()
-        for kuviz_dict in kuviz_dicts:
-            self.assert_kuviz_dict(kuviz_dict, name="test", privacy=PRIVACY_PUBLIC)
+        html = 'fake_html'
+        kuviz_name = 'fake_name'
+
+        kuviz_publisher = KuvizPublisher(None)
+        kuviz_publisher.set_layers(vmap.layers, kuviz_name, 'fake_table_name')
+        kuviz_publisher.publish(html, kuviz_name)
+
+        kuviz_publisher.delete()
+
+        assert kuviz_publisher.kuviz is None
diff --git a/test/viz/test_map.py b/test/viz/test_map.py
index 7489fd23..3a74964a 100644
--- a/test/viz/test_map.py
+++ b/test/viz/test_map.py
@@ -4,15 +4,19 @@ from carto.exceptions import CartoException
 
 from cartoframes.lib import context
 from cartoframes.viz import Map, Layer, Source, constants
-from cartoframes.data import StrategiesRegistry
+from cartoframes.viz.kuviz import KuvizPublisher, kuviz_to_dict
+from cartoframes.data import Dataset, StrategiesRegistry
 from cartoframes.auth import Credentials
 
-from ..mocks.map_mock import MapMock
-from ..mocks.dataset_mock import DatasetMock
+from .utils import build_geojson
+
 from ..mocks.context_mock import ContextMock
-from ..mocks.kuviz_mock import KuvizPublisherMock, PRIVACY_PUBLIC, PRIVACY_PASSWORD
+from ..mocks.kuviz_mock import CartoKuvizMock, PRIVACY_PUBLIC, PRIVACY_PASSWORD
 
-from .utils import build_geojson
+try:
+    from unittest.mock import patch
+except ImportError:
+    from mock import patch
 
 
 class TestMap(unittest.TestCase):
@@ -178,6 +182,21 @@ class TestMapDevelopmentPath(unittest.TestCase):
         self.assertTrue(_airship_path + constants.AIRSHIP_ICONS_DEV in template)
 
 
+class KuvizPublisherMock(KuvizPublisher):
+    def __init__(self):
+        pass
+
+    def get_layers(self):
+        return []
+
+    def set_layers(self, _1, _2, _3):
+        pass
+
+    def publish(self, _1, name, password):
+        self.kuviz = CartoKuvizMock(name, password=password)
+        return kuviz_to_dict(self.kuviz)
+
+
 class TestMapPublication(unittest.TestCase):
     def setUp(self):
         self.username = 'fake_username'
@@ -206,17 +225,15 @@ class TestMapPublication(unittest.TestCase):
         self.original_create_context = context.create_context
         context.create_context = lambda c: self._context_mock
 
-        self.html = "<html><body><h1>Hi Kuviz yeee</h1></body></html>"
-
     def tearDown(self):
         context.create_context = self.original_create_context
         StrategiesRegistry.instance = None
 
-    def assert_kuviz(self, kuviz, name, privacy):
-        self.assertIsNotNone(kuviz.id)
-        self.assertIsNotNone(kuviz.url)
-        self.assertEqual(kuviz.name, name)
-        self.assertEqual(kuviz.privacy, privacy)
+    # def assert_kuviz(self, kuviz, name, privacy):
+    #     self.assertIsNotNone(kuviz.id)
+    #     self.assertIsNotNone(kuviz.url)
+    #     self.assertEqual(kuviz.name, name)
+    #     self.assertEqual(kuviz.privacy, privacy)
 
     def assert_kuviz_dict(self, kuviz_dict, name, privacy):
         self.assertIsNotNone(kuviz_dict['id'])
@@ -224,89 +241,65 @@ class TestMapPublication(unittest.TestCase):
         self.assertEqual(kuviz_dict['name'], name)
         self.assertEqual(kuviz_dict['privacy'], privacy)
 
-    def test_map_publish_remote(self):
-        dataset = DatasetMock('fake_table', credentials=self.credentials)
-        map = MapMock(Layer(Source(dataset)))
-
-        name = 'cf_publish'
-        kuviz_dict = map.publish(name, credentials=self.credentials)
-        self.assert_kuviz_dict(kuviz_dict, name, PRIVACY_PUBLIC)
-        self.assert_kuviz(map._kuviz, name, PRIVACY_PUBLIC)
-
-    def test_map_publish_unsync_fails(self):
-        dataset = DatasetMock(self.test_geojson)
-        map = MapMock(Layer(Source(dataset)))
-
-        msg = 'The map layers are not synchronized with CARTO. Please, use the `sync_data` before publishing the map'
-        with self.assertRaises(CartoException, msg=msg):
-            map.publish('test', credentials=self.credentials)
-
-    def test_map_publish_unsync_sync_data_and_publish(self):
-        dataset = DatasetMock(self.test_geojson)
-        map = MapMock(Layer(Source(dataset)))
+    @patch('cartoframes.viz.map._get_publisher')
+    def test_map_publish_remote(self, _get_publisher):
+        _get_publisher.return_value = KuvizPublisherMock()
 
-        map.sync_data(table_name='fake_table', credentials=self.credentials)
+        dataset = Dataset('fake_table', credentials=self.credentials)
+        vmap = Map(Layer(dataset))
 
         name = 'cf_publish'
-        kuviz_dict = map.publish(name, credentials=self.credentials)
+        kuviz_dict = vmap.publish(name)
         self.assert_kuviz_dict(kuviz_dict, name, PRIVACY_PUBLIC)
-        self.assert_kuviz(map._kuviz, name, PRIVACY_PUBLIC)
 
-    def test_map_publish_with_password(self):
-        dataset = DatasetMock('fake_table', credentials=self.credentials)
-        map = MapMock(Layer(Source(dataset)))
+    @patch('cartoframes.viz.map._get_publisher')
+    def test_map_publish_with_password(self, _get_publisher):
+        _get_publisher.return_value = KuvizPublisherMock()
+
+        dataset = Dataset('fake_table', credentials=self.credentials)
+        map = Map(Layer(Source(dataset)))
 
         name = 'cf_publish'
         kuviz_dict = map.publish(name, credentials=self.credentials, password="1234")
         self.assert_kuviz_dict(kuviz_dict, name, PRIVACY_PASSWORD)
-        self.assert_kuviz(map._kuviz, name, PRIVACY_PASSWORD)
 
-    def test_map_publish_deletion(self):
-        dataset = DatasetMock('fake_table', credentials=self.credentials)
-        map = MapMock(Layer(Source(dataset)))
+    @patch('cartoframes.viz.map._get_publisher')
+    def test_map_publish_deletion(self, _get_publisher):
+        _get_publisher.return_value = KuvizPublisherMock()
+
+        dataset = Dataset('fake_table', credentials=self.credentials)
+        map = Map(Layer(Source(dataset)))
 
         name = 'cf_publish'
         map.publish(name, credentials=self.credentials)
-        map.delete_publication()
-        self.assertIsNone(map._kuviz)
+        response = map.delete_publication()
+
+        self.assertTrue(response)
+
+    @patch('cartoframes.viz.map._get_publisher')
+    def test_map_publish_update_name(self, _get_publisher):
+        _get_publisher.return_value = KuvizPublisherMock()
 
-    def test_map_publish_update_name(self):
-        dataset = DatasetMock('fake_table', credentials=self.credentials)
-        map = MapMock(Layer(Source(dataset)))
+        dataset = Dataset('fake_table', credentials=self.credentials)
+        map = Map(Layer(Source(dataset)))
 
         name = 'cf_publish'
         map.publish(name, credentials=self.credentials)
+
         new_name = 'cf_update'
         kuviz_dict = map.update_publication(new_name, password=None)
+
         self.assert_kuviz_dict(kuviz_dict, new_name, PRIVACY_PUBLIC)
-        self.assert_kuviz(map._kuviz, new_name, PRIVACY_PUBLIC)
 
-    def test_map_publish_update_password(self):
-        dataset = DatasetMock('fake_table', credentials=self.credentials)
-        map = MapMock(Layer(Source(dataset)))
+    @patch('cartoframes.viz.map._get_publisher')
+    def test_map_publish_update_password(self, _get_publisher):
+        _get_publisher.return_value = KuvizPublisherMock()
+
+        dataset = Dataset('fake_table', credentials=self.credentials)
+        map = Map(Layer(Source(dataset)))
 
         name = 'cf_publish'
         map.publish(name, credentials=self.credentials)
         kuviz_dict = map.update_publication(name, password="1234")
-        self.assert_kuviz_dict(kuviz_dict, name, PRIVACY_PASSWORD)
-        self.assert_kuviz(map._kuviz, name, PRIVACY_PASSWORD)
 
-    def test_map_publish_private_ds_with_public_apikey_fails(self):
-        is_public = KuvizPublisherMock.is_public
-
-        def is_not_public(self):
-            return False
-
-        KuvizPublisherMock.is_public = is_not_public
-
-        dataset = DatasetMock('fake_table', credentials=self.credentials)
-        map = MapMock(Layer(Source(dataset)))
-
-        msg = ('The datasets used in your map are not public. '
-               'You need add new Regular API key with permissions to Maps API and the datasets. '
-               'You can do it from your CARTO dashboard or using the Auth API. You can get more '
-               'info at https://carto.com/developers/auth-api/guides/types-of-API-Keys/')
-        with self.assertRaises(CartoException, msg=msg):
-            map.publish('test', credentials=self.credentials)
-
-        KuvizPublisherMock.is_public = is_public
+        self.assert_kuviz_dict(kuviz_dict, name, PRIVACY_PASSWORD)