Repository: CartoDB/cartoframes
Pull request #1391 (2019-12-18 18:04:30)

Review subscription messages
Closes https://github.com/CartoDB/cartoframes/issues/1155, https://github.com/CartoDB/cartoframes/issues/1331.

- Add check_do_enabled: Catalog().subscriptions(), dataset/geography.subscription_info(), dataset/geography.subscribe(), dataset/geography.download(), dataset/geography.get_all(credentials=creds), Enrichment()
- Add DO disabled message
- Change subscription message based on `estimated_delivery_days`
- Remove duplicated checks
- Add unit tests



Patch:
diff --git a/cartoframes/__init__.py b/cartoframes/__init__.py
index 620c5ea7..be32bb0d 100644
--- a/cartoframes/__init__.py
+++ b/cartoframes/__init__.py
@@ -7,7 +7,7 @@ from .io.carto import read_carto, to_carto, has_table, delete_table, rename_tabl
 
 
 # Check installed packages versions
-check_package('carto', '>=1.8.2')
+check_package('carto', '>=1.8.3')
 check_package('pandas', '>=0.23.0')
 check_package('geopandas', '>=0.6.0')
 
diff --git a/cartoframes/auth/credentials.py b/cartoframes/auth/credentials.py
index bdc0e3e0..96b14b6c 100644
--- a/cartoframes/auth/credentials.py
+++ b/cartoframes/auth/credentials.py
@@ -6,10 +6,10 @@ import appdirs
 
 from carto.auth import APIKeyAuthClient
 from carto.do_token import DoTokenManager
-from carto.exceptions import CartoException
 
 from .. import __version__
 from ..core.logger import log
+from ..utils.utils import check_do_enabled
 
 from urllib.parse import urlparse
 from warnings import filterwarnings
@@ -162,7 +162,6 @@ class Credentials(object):
                 credentials = Credentials(username='johnsmith', api_key='abcdefg')
                 credentials.save('path/to/credentials/file.json')
         """
-
         if config_file is None:
             config_file = _DEFAULT_PATH
 
@@ -196,7 +195,6 @@ class Credentials(object):
                 Credentials(username='johnsmith', api_key='abcdefg', base_url='https://johnsmith.carto.com/')
 
         """
-
         path_to_remove = config_file or _DEFAULT_PATH
 
         try:
@@ -205,15 +203,11 @@ class Credentials(object):
         except OSError:
             log.warning('No credential file found at {}.'.format(path_to_remove))
 
+    @check_do_enabled
     def get_do_credentials(self):
         """Returns the Data Observatory v2 credentials"""
-
         do_token_manager = DoTokenManager(self.get_api_key_auth_client())
-        do_credentials = do_token_manager.get()
-        if not do_credentials:
-            raise CartoException('Authentication error: do you have permissions to access Data Observatory v2?')
-
-        return do_credentials
+        return do_token_manager.get()
 
     def get_api_key_auth_client(self):
         if not self._api_key_auth_client:
diff --git a/cartoframes/data/clients/bigquery_client.py b/cartoframes/data/clients/bigquery_client.py
index f6b653a8..404dd798 100644
--- a/cartoframes/data/clients/bigquery_client.py
+++ b/cartoframes/data/clients/bigquery_client.py
@@ -6,8 +6,8 @@ import pandas as pd
 from google.auth.exceptions import RefreshError
 from google.cloud import bigquery, storage, bigquery_storage_v1beta1 as bigquery_storage
 from google.oauth2.credentials import Credentials as GoogleCredentials
-
 from carto.exceptions import CartoException
+
 from ...auth import get_default_credentials
 from ...core.logger import log
 from ...utils.utils import timelogger
diff --git a/cartoframes/data/observatory/catalog/dataset.py b/cartoframes/data/observatory/catalog/dataset.py
index 83c4a6e0..dd81d08e 100644
--- a/cartoframes/data/observatory/catalog/dataset.py
+++ b/cartoframes/data/observatory/catalog/dataset.py
@@ -3,8 +3,6 @@ import geopandas as gpd
 
 from shapely import wkt
 
-from carto.exceptions import CartoException
-
 from .entity import CatalogEntity
 from .repository.dataset_repo import get_dataset_repo
 from .repository.geography_repo import get_geography_repo
@@ -16,7 +14,7 @@ from . import subscription_info
 from . import subscriptions
 from . import utils
 from ....core.logger import log
-from ....utils.utils import get_credentials, check_credentials
+from ....utils.utils import get_credentials, check_credentials, check_do_enabled
 
 DATASET_TYPE = 'dataset'
 
@@ -342,6 +340,7 @@ class Dataset(CatalogEntity):
         return dataset_describe(self.variables)
 
     @classmethod
+    @check_do_enabled
     def get_all(cls, filters=None, credentials=None):
         """Get all the Dataset instances that comply with the indicated filters (or all of them if no filters
         are passed). If credentials are given, only the datasets granted for those credentials are returned.
@@ -366,33 +365,6 @@ class Dataset(CatalogEntity):
 
         return cls._entity_repo.get_all(filters, credentials)
 
-    def download(self, file_path, credentials=None):
-        """Download dataset data as a local file. You need Data Observatory enabled in your CARTO
-        account, please contact us at support@carto.com for more information.
-
-        For premium datasets (those with `is_public_data` set to False), you need a subscription to the dataset.
-        Check the subscription guides for more information.
-
-        Args:
-            file_path (str): the file path where save the dataset (CSV).
-            credentials (:py:class:`Credentials <cartoframes.auth.Credentials>`, optional):
-                credentials of CARTO user account. If not provided,
-                a default credentials (if set with :py:meth:`set_default_credentials
-                <cartoframes.auth.set_default_credentials>`) will be used.
-
-        Returns:
-            os.path with the local file path with the file downloaded
-
-        :raises CartoException: If you have not a valid license for the dataset being downloaded.
-        :raises ValueError: If the credentials argument is not valid.
-        """
-        _credentials = get_credentials(credentials)
-
-        if not self._is_subscribed(_credentials):
-            raise CartoException('You are not subscribed to this Dataset yet. Please, use the subscribe method first.')
-
-        self._download(file_path, _credentials)
-
     @classmethod
     def get_datasets_spatial_filtered(cls, filter_dataset):
         user_gdf = cls._get_user_geodataframe(filter_dataset)
@@ -423,6 +395,36 @@ class Dataset(CatalogEntity):
         join_gdf = gpd.sjoin(geographies_gdf1, geographies_gdf2, how='inner', op='intersects')
         return join_gdf['id'].unique()
 
+    @check_do_enabled
+    def download(self, file_path, credentials=None):
+        """Download dataset data as a local file. You need Data Observatory enabled in your CARTO
+        account, please contact us at support@carto.com for more information.
+
+        For premium datasets (those with `is_public_data` set to False), you need a subscription to the dataset.
+        Check the subscription guides for more information.
+
+        Args:
+            file_path (str): the file path where save the dataset (CSV).
+            credentials (:py:class:`Credentials <cartoframes.auth.Credentials>`, optional):
+                credentials of CARTO user account. If not provided,
+                a default credentials (if set with :py:meth:`set_default_credentials
+                <cartoframes.auth.set_default_credentials>`) will be used.
+
+        Returns:
+            os.path with the local file path with the file downloaded
+
+        :raises Exception: If you have not a valid license for the dataset being downloaded.
+        :raises ValueError: If the credentials argument is not valid.
+        """
+        _credentials = get_credentials(credentials)
+
+        if not self._is_subscribed(_credentials):
+            raise Exception('You are not subscribed to this Dataset yet. '
+                            'Please, use the subscribe method first.')
+
+        self._download(file_path, _credentials)
+
+    @check_do_enabled
     def subscribe(self, credentials=None):
         """Subscribe to a dataset. You need Data Observatory enabled in your CARTO account, please contact us at
         support@carto.com for more information.
@@ -457,6 +459,7 @@ class Dataset(CatalogEntity):
         else:
             utils.display_subscription_form(self.id, DATASET_TYPE, _credentials)
 
+    @check_do_enabled
     def subscription_info(self, credentials=None):
         """Get the subscription information of a Dataset, which includes the license, Terms of Service, rights, price, and
         estimated time of delivery, among other metadata of interest during the :py:attr:`Dataset.subscription` process.
diff --git a/cartoframes/data/observatory/catalog/entity.py b/cartoframes/data/observatory/catalog/entity.py
index 87072426..a3876cb2 100644
--- a/cartoframes/data/observatory/catalog/entity.py
+++ b/cartoframes/data/observatory/catalog/entity.py
@@ -1,8 +1,6 @@
 import pandas as pd
 
 from abc import ABC
-from google.api_core.exceptions import NotFound
-
 from carto.exceptions import CartoException
 
 from ...clients.bigquery_client import BigQueryClient
@@ -123,14 +121,11 @@ class CatalogEntity(ABC):
 
         project, dataset, table = full_remote_table_name.split('.')
 
-        try:
-            column_names = bq_client.get_table_column_names(project, dataset, table)
-            query = 'SELECT * FROM `{}`'.format(full_remote_table_name)
-            job = bq_client.query(query)
+        column_names = bq_client.get_table_column_names(project, dataset, table)
+        query = 'SELECT * FROM `{}`'.format(full_remote_table_name)
+        job = bq_client.query(query)
 
-            bq_client.download_to_file(job, file_path, column_names=column_names)
-        except NotFound:
-            raise CartoException('You have not purchased the dataset `{}` yet'.format(self.id))
+        bq_client.download_to_file(job, file_path, column_names=column_names)
 
         log.info('Data saved: {}.'.format(file_path))
         log.info("To read it you can do: `pandas.read_csv('{}')`.".format(file_path))
diff --git a/cartoframes/data/observatory/catalog/geography.py b/cartoframes/data/observatory/catalog/geography.py
index 7530e1d7..ed253e50 100644
--- a/cartoframes/data/observatory/catalog/geography.py
+++ b/cartoframes/data/observatory/catalog/geography.py
@@ -1,5 +1,3 @@
-from carto.exceptions import CartoException
-
 from .entity import CatalogEntity
 from .repository.dataset_repo import get_dataset_repo
 from .repository.geography_repo import get_geography_repo
@@ -7,7 +5,7 @@ from .repository.constants import GEOGRAPHY_FILTER
 from . import subscription_info
 from . import subscriptions
 from . import utils
-from ....utils.utils import get_credentials, check_credentials
+from ....utils.utils import get_credentials, check_credentials, check_do_enabled
 
 GEOGRAPHY_TYPE = 'geography'
 
@@ -176,6 +174,7 @@ class Geography(CatalogEntity):
         return self.data['summary_json']
 
     @classmethod
+    @check_do_enabled
     def get_all(cls, filters=None, credentials=None):
         """Get all the Geography instances that comply with the indicated filters (or all of them if no filters
         are passed. If credentials are given, only the geographies granted for those credentials are returned.
@@ -200,6 +199,7 @@ class Geography(CatalogEntity):
 
         return cls._entity_repo.get_all(filters, credentials)
 
+    @check_do_enabled
     def download(self, file_path, credentials=None):
         """Download geography data as a local file. You need Data Observatory enabled in your CARTO
         account, please contact us at support@carto.com for more information.
@@ -223,11 +223,12 @@ class Geography(CatalogEntity):
         _credentials = get_credentials(credentials)
 
         if not self._is_subscribed(_credentials):
-            raise CartoException('You are not subscribed to this Geography yet. Please, use the subscribe method '
-                                 'first.')
+            raise Exception('You are not subscribed to this Geography yet. '
+                            'Please, use the subscribe method first.')
 
         self._download(file_path, _credentials)
 
+    @check_do_enabled
     def subscribe(self, credentials=None):
         """Subscribe to a Geography. You need Data Observatory enabled in your CARTO account, please contact us at
         support@carto.com for more information.
@@ -262,6 +263,7 @@ class Geography(CatalogEntity):
         else:
             utils.display_subscription_form(self.id, GEOGRAPHY_TYPE, _credentials)
 
+    @check_do_enabled
     def subscription_info(self, credentials=None):
         """Get the subscription information of a Geography, which includes the license, TOS, rights, prize and
         estimated_time_of_delivery, among other metadata of interest during the subscription process.
diff --git a/cartoframes/data/observatory/catalog/subscriptions.py b/cartoframes/data/observatory/catalog/subscriptions.py
index 67e04b45..fe693a33 100644
--- a/cartoframes/data/observatory/catalog/subscriptions.py
+++ b/cartoframes/data/observatory/catalog/subscriptions.py
@@ -47,10 +47,7 @@ def fetch_subscriptions(credentials):
         api_key_auth_client = credentials.get_api_key_auth_client()
         do_manager = DOSubscriptionManager(api_key_auth_client)
         if do_manager is not None:
-            try:
-                return do_manager.all()
-            except Exception:
-                return []
+            return do_manager.all()
     return []
 
 
diff --git a/cartoframes/data/observatory/catalog/utils.py b/cartoframes/data/observatory/catalog/utils.py
index d960164a..2a07e950 100644
--- a/cartoframes/data/observatory/catalog/utils.py
+++ b/cartoframes/data/observatory/catalog/utils.py
@@ -47,19 +47,29 @@ def _display_existing_subscription_message_notebook(entity_id, entity_type):
 
 def _display_existing_subscription_message_cli(entity_id, entity_type):
     message = ('Subscription already purchased:\n' +
-               '- The {0} "{1}" has already been purchased.'.format(entity_type, entity_id))
+               'The {0} "{1}" has already been purchased.'.format(entity_type, entity_id))
     print(message)
 
 
 def _display_subscription_form_notebook(entity_id, entity_type, info, credentials):
+    if info.get('estimated_delivery_days') == 0:
+        delivery_message = '''
+        This {type} is available for Instant Subscription for your organization,
+        so it will automatically process the order and you will get immediate access to the {type}.
+        '''.format(type=entity_type)
+    else:
+        delivery_message = '''
+        This {type} will be available in your account in about {days} days.
+        We will contact you shortly to complete the subscription details.
+        '''.format(type=entity_type, days=info.get('estimated_delivery_days'))
+
     message = '''
     <h3>Subscription contract</h3>
     You are about to subscribe to <b>{id}</b>.
     The cost of this {type} is <b>${price}</b>.
     If you want to proceed, a Request will be sent to CARTO who will
     order the data and load it into your account.
-    This {type} is available for Instant Order for your organization,
-    so it will automatically process the order and you will get immediate access to the {type}.
+    {delivery_message}
     In order to proceed we need you to agree to the License of the {type}
     available at <b><a href="{link}" target="_blank">this link</a></b>.
     <br>Do you want to proceed?
@@ -67,6 +77,7 @@ def _display_subscription_form_notebook(entity_id, entity_type, info, credential
         id=entity_id,
         type=entity_type,
         price=info.get('subscription_list_price'),
+        delivery_message=delivery_message,
         link=info.get('tos_link'))
 
     responses = {
@@ -120,20 +131,31 @@ def _create_notebook_form(entity_id, entity_type, message, responses, credential
 
 
 def _display_subscription_form_cli(entity_id, entity_type, info, credentials):
+    if info.get('estimated_delivery_days') == 0:
+        delivery_message = (
+            'This {type} is available for Instant Subscription for your organization, '
+            'so it will automatically process the order and you will get immediate access to the {type}.'
+        ).format(type=entity_type)
+    else:
+        delivery_message = (
+            'This {type} will be available in your account in about {days} days. '
+            'We will contact you shortly to complete the subscription details.'
+        ).format(type=entity_type, days=info.get('estimated_delivery_days'))
+
     message = (
         'Subscription contract:\n'
         'You are about to subscribe to "{id}". '
         'The cost of this {type} is ${price}. '
         'If you want to proceed, a Request will be sent to CARTO who will '
         'order the data and load it into your account. '
-        'This {type} is available for Instant Order for your organization, '
-        'so it will automatically process the order and you will get immediate access to the {type}. '
+        '{delivery_message} '
         'In order to proceed we need you to agree to the License of the {type} '
         'available at this link: {link}.\n'
         'Do you want to proceed?').format(
             id=entity_id,
             type=entity_type,
             price=info.get('subscription_list_price'),
+            delivery_message=delivery_message,
             link=info.get('tos_link'))
 
     responses = {
diff --git a/cartoframes/utils/utils.py b/cartoframes/utils/utils.py
index cac25310..05d43e9d 100644
--- a/cartoframes/utils/utils.py
+++ b/cartoframes/utils/utils.py
@@ -15,9 +15,8 @@ import semantic_version
 
 from functools import wraps
 from warnings import catch_warnings, filterwarnings
+from pyrestcli.exceptions import ServerErrorException
 
-from ..auth.credentials import Credentials
-from ..auth import defaults
 from ..core.logger import log
 
 GEOM_TYPE_POINT = 'point'
@@ -305,12 +304,14 @@ def is_table_name(data):
 
 
 def get_credentials(credentials=None):
+    from ..auth import defaults
     _credentials = credentials or defaults.get_default_credentials()
     check_credentials(_credentials)
     return _credentials
 
 
 def check_credentials(credentials):
+    from ..auth.credentials import Credentials
     if not isinstance(credentials, Credentials):
         raise AttributeError('Credentials attribute is required. '
                              'Please pass a `Credentials` instance '
@@ -405,3 +406,18 @@ def check_package(pkg_name, spec='*', is_optional=False):
         else:
             raise Exception('Package "{0}" is not installed. '.format(pkg_name) +
                             'Please run: pip install {0}'.format(pkg_name))
+
+
+def check_do_enabled(method):
+    def fn(*args, **kw):
+        try:
+            return method(*args, **kw)
+        except ServerErrorException as e:
+            if str(e) == "['The user does not have Data Observatory enabled']":
+                raise Exception(
+                    'We are sorry, the Data Observatory is not enabled for your account yet. '
+                    'Please contact your customer success manager or send an email to '
+                    'sales@carto.com to request access to it.')
+            else:
+                raise e
+    return fn
diff --git a/setup.py b/setup.py
index 8b3df9d4..04e5f061 100644
--- a/setup.py
+++ b/setup.py
@@ -25,7 +25,7 @@ def get_version():
 
 REQUIRES = [
     'appdirs>=1.4.3,<2.0',
-    'carto>=1.8.2,<2.0',
+    'carto>=1.8.3,<2.0',
     'jinja2>=2.10.1,<3.0',
     'geopandas>=0.6.0,<1.0',
     'tqdm>=4.32.1,<5.0',


Test patch:
diff --git a/tests/unit/data/observatory/catalog/test_dataset.py b/tests/unit/data/observatory/catalog/test_dataset.py
index a4bf918d..bd557383 100644
--- a/tests/unit/data/observatory/catalog/test_dataset.py
+++ b/tests/unit/data/observatory/catalog/test_dataset.py
@@ -2,8 +2,7 @@ import pytest
 import pandas as pd
 
 from unittest.mock import patch
-from carto.exceptions import CartoException
-from google.api_core.exceptions import NotFound
+from pyrestcli.exceptions import ServerErrorException
 
 from cartoframes.auth import Credentials
 from cartoframes.data.observatory.catalog.entity import CatalogList
@@ -236,7 +235,7 @@ class TestDataset(object):
     def test_get_all_datasets_credentials(self, mocked_repo):
         # Given
         mocked_repo.return_value = test_datasets
-        credentials = Credentials('user', '1234')
+        credentials = Credentials('fake_user', '1234')
 
         # When
         datasets = Dataset.get_all(credentials=credentials)
@@ -247,6 +246,24 @@ class TestDataset(object):
         assert isinstance(datasets, CatalogList)
         assert datasets == test_datasets
 
+    @patch.object(DatasetRepository, 'get_all')
+    def test_get_all_datasets_credentials_without_do_enabled(self, mocked_repo):
+        # Given
+        def raise_exception(a, b):
+            raise ServerErrorException(['The user does not have Data Observatory enabled'])
+        mocked_repo.side_effect = raise_exception
+        credentials = Credentials('fake_user', '1234')
+
+        # When
+        with pytest.raises(Exception) as e:
+            Dataset.get_all(credentials=credentials)
+
+        # Then
+        assert str(e.value) == (
+            'We are sorry, the Data Observatory is not enabled for your account yet. '
+            'Please contact your customer success manager or send an email to '
+            'sales@carto.com to request access to it.')
+
     def test_dataset_list_is_printed_with_classname_and_slugs(self):
         # Given
         datasets = CatalogList([test_dataset1, test_dataset2])
@@ -301,106 +318,73 @@ class TestDataset(object):
     @patch.object(DatasetRepository, 'get_by_id')
     @patch('cartoframes.data.observatory.catalog.entity._get_bigquery_client')
     def test_dataset_download(self, mocked_bq_client, get_by_id_mock, get_all_mock):
-        # mock dataset
+        # Given
         get_by_id_mock.return_value = test_dataset1
         dataset = Dataset.get(test_dataset1.id)
-
-        # mock subscriptions
         get_all_mock.return_value = [dataset]
-
-        # mock big query client
         mocked_bq_client.return_value = BigQueryClientMock()
-
-        # test
         credentials = Credentials('fake_user', '1234')
 
+        # Then
         dataset.download('fake_path', credentials)
 
     @patch.object(DatasetRepository, 'get_all')
     @patch.object(DatasetRepository, 'get_by_id')
     @patch('cartoframes.data.observatory.catalog.entity._get_bigquery_client')
-    def test_dataset_not_available_in_bq_download_fails(self, mocked_bq_client, get_by_id_mock, get_all_mock):
-        # mock dataset
-        get_by_id_mock.return_value = test_dataset2
-        dataset = Dataset.get(test_dataset2.id)
-
-        # mock subscriptions
-        get_all_mock.return_value = [dataset]
-
-        # mock big query client
-        mocked_bq_client.return_value = BigQueryClientMock()
-
-        # test
-        credentials = Credentials('fake_user', '1234')
-
-        with pytest.raises(CartoException) as e:
-            dataset.download('fake_path', credentials)
-
-        error = '{} is not ready for Download. Please, contact us for more information.'.format(dataset)
-        assert str(e.value) == error
-
-    @patch.object(DatasetRepository, 'get_all')
-    @patch.object(DatasetRepository, 'get_by_id')
-    @patch('cartoframes.data.observatory.catalog.entity._get_bigquery_client')
-    def test_dataset_not_subscribed_download_fails(self, mocked_bq_client, get_by_id_mock, get_all_mock):
-        # mock dataset
+    def test_dataset_download_not_subscribed(self, mocked_bq_client, get_by_id_mock, get_all_mock):
+        # Given
         get_by_id_mock.return_value = test_dataset2
         dataset = Dataset.get(test_dataset2.id)
-
-        # mock subscriptions
         get_all_mock.return_value = []
-
-        # mock big query client
         mocked_bq_client.return_value = BigQueryClientMock()
-
-        # test
         credentials = Credentials('fake_user', '1234')
 
-        with pytest.raises(CartoException) as e:
+        # When
+        with pytest.raises(Exception) as e:
             dataset.download('fake_path', credentials)
 
-        error = 'You are not subscribed to this Dataset yet. Please, use the subscribe method first.'
-        assert str(e.value) == error
+        # Then
+        assert str(e.value) == (
+            'You are not subscribed to this Dataset yet. '
+            'Please, use the subscribe method first.')
 
     @patch.object(DatasetRepository, 'get_all')
     @patch.object(DatasetRepository, 'get_by_id')
     @patch('cartoframes.data.observatory.catalog.entity._get_bigquery_client')
-    def test_dataset_not_subscribed_but_public_download_works(self, mocked_bq_client, get_by_id_mock, get_all_mock):
-        # mock dataset
+    def test_dataset_download_not_subscribed_but_public(self, mocked_bq_client, get_by_id_mock, get_all_mock):
+        # Given
         get_by_id_mock.return_value = test_dataset1  # is public
         dataset = Dataset.get(test_dataset1.id)
-
-        # mock subscriptions
         get_all_mock.return_value = []
-
-        # mock big query client
         mocked_bq_client.return_value = BigQueryClientMock()
-
-        # test
         credentials = Credentials('fake_user', '1234')
 
+        # Then
         dataset.download('fake_path', credentials)
 
     @patch.object(DatasetRepository, 'get_all')
     @patch.object(DatasetRepository, 'get_by_id')
     @patch('cartoframes.data.observatory.catalog.entity._get_bigquery_client')
-    def test_dataset_download_raises_without_do_active(self, mocked_bq_client, get_by_id_mock, get_all_mock):
-        # mock dataset
+    def test_dataset_download_without_do_enabled(self, mocked_bq_client, get_by_id_mock, get_all_mock):
+        # Given
         get_by_id_mock.return_value = test_dataset1
         dataset = Dataset.get(test_dataset1.id)
-
-        # mock subscriptions
         get_all_mock.return_value = []
-
-        # mock big query client
-        mocked_bq_client.return_value = BigQueryClientMock(NotFound('Fake error'))
-
-        # test
+        mocked_bq_client.return_value = BigQueryClientMock(
+            ServerErrorException(['The user does not have Data Observatory enabled'])
+        )
         credentials = Credentials('fake_user', '1234')
 
-        with pytest.raises(CartoException):
+        # When
+        with pytest.raises(Exception) as e:
             dataset.download('fake_path', credentials)
 
+        # Then
+        assert str(e.value) == (
+            'We are sorry, the Data Observatory is not enabled for your account yet. '
+            'Please contact your customer success manager or send an email to '
+            'sales@carto.com to request access to it.')
+
     @patch('cartoframes.data.observatory.catalog.subscriptions.get_subscription_ids')
     @patch('cartoframes.data.observatory.catalog.utils.display_subscription_form')
     @patch('cartoframes.data.observatory.catalog.utils.display_existing_subscription_message')
@@ -409,7 +393,7 @@ class TestDataset(object):
         expected_id = db_dataset1['id']
         expected_subscribed_ids = []
         mock_subscription_ids.return_value = expected_subscribed_ids
-        credentials = Credentials('user', '1234')
+        credentials = Credentials('fake_user', '1234')
         dataset = Dataset(db_dataset1)
 
         # When
@@ -428,7 +412,7 @@ class TestDataset(object):
         expected_id = db_dataset1['id']
         expected_subscribed_ids = [expected_id]
         mock_subscription_ids.return_value = expected_subscribed_ids
-        credentials = Credentials('user', '1234')
+        credentials = Credentials('fake_user', '1234')
         dataset = Dataset(db_dataset1)
 
         # When
@@ -444,7 +428,7 @@ class TestDataset(object):
     @patch('cartoframes.auth.defaults.get_default_credentials')
     def test_dataset_subscribe_default_credentials(self, mocked_credentials, mock_display_form, mock_subscription_ids):
         # Given
-        expected_credentials = Credentials('user', '1234')
+        expected_credentials = Credentials('fake_user', '1234')
         mocked_credentials.return_value = expected_credentials
         dataset = Dataset(db_dataset1)
 
@@ -469,11 +453,31 @@ class TestDataset(object):
                                 'Please pass a `Credentials` instance '
                                 'or use the `set_default_credentials` function.')
 
+    @patch('cartoframes.data.observatory.catalog.subscriptions.get_subscription_ids')
+    @patch('cartoframes.data.observatory.catalog.utils.display_subscription_form')
+    def test_dataset_subscribe_without_do_enabled(self, mock_display_form, mock_subscription_ids):
+        # Given
+        def raise_exception(a, b, c):
+            raise ServerErrorException(['The user does not have Data Observatory enabled'])
+        mock_display_form.side_effect = raise_exception
+        dataset = Dataset(db_dataset1)
+        credentials = Credentials('fake_user', '1234')
+
+        # When
+        with pytest.raises(Exception) as e:
+            dataset.subscribe(credentials)
+
+        # Then
+        assert str(e.value) == (
+            'We are sorry, the Data Observatory is not enabled for your account yet. '
+            'Please contact your customer success manager or send an email to '
+            'sales@carto.com to request access to it.')
+
     @patch('cartoframes.data.observatory.catalog.subscription_info.fetch_subscription_info')
     def test_dataset_subscription_info(self, mock_fetch):
         # Given
         mock_fetch.return_value = test_subscription_info
-        credentials = Credentials('user', '1234')
+        credentials = Credentials('fake_user', '1234')
         dataset = Dataset(db_dataset1)
 
         # When
@@ -498,7 +502,7 @@ class TestDataset(object):
     @patch('cartoframes.auth.defaults.get_default_credentials')
     def test_dataset_subscription_info_default_credentials(self, mocked_credentials, mock_fetch):
         # Given
-        expected_credentials = Credentials('user', '1234')
+        expected_credentials = Credentials('fake_user', '1234')
         mocked_credentials.return_value = expected_credentials
         dataset = Dataset(db_dataset1)
 
@@ -522,15 +526,39 @@ class TestDataset(object):
                                 'Please pass a `Credentials` instance '
                                 'or use the `set_default_credentials` function.')
 
+    @patch('cartoframes.data.observatory.catalog.subscription_info.fetch_subscription_info')
+    def test_dataset_subscription_info_without_do_enabled(self, mock_fetch):
+        # Given
+        def raise_exception(a, b, c):
+            raise ServerErrorException(['The user does not have Data Observatory enabled'])
+        mock_fetch.side_effect = raise_exception
+        dataset = Dataset(db_dataset1)
+        credentials = Credentials('fake_user', '1234')
+
+        # When
+        with pytest.raises(Exception) as e:
+            dataset.subscription_info(credentials)
+
+        # Then
+        assert str(e.value) == (
+            'We are sorry, the Data Observatory is not enabled for your account yet. '
+            'Please contact your customer success manager or send an email to '
+            'sales@carto.com to request access to it.')
+
     def test_dataset_is_available_in(self):
+        # Given
         dataset_in_bq = Dataset(db_dataset1)
         dataset_not_in_bq = Dataset(db_dataset2)
 
+        # Then
         assert dataset_in_bq._is_available_in('bq')
         assert not dataset_not_in_bq._is_available_in('bq')
 
     def test_dataset_is_available_in_with_empty_field(self):
+        # Given
         db_dataset = dict(db_dataset1)
         db_dataset['available_in'] = None
         dataset_null = Dataset(db_dataset)
+
+        # Then
         assert not dataset_null._is_available_in('bq')
diff --git a/tests/unit/data/observatory/catalog/test_geography.py b/tests/unit/data/observatory/catalog/test_geography.py
index 1dcb0803..c8453978 100644
--- a/tests/unit/data/observatory/catalog/test_geography.py
+++ b/tests/unit/data/observatory/catalog/test_geography.py
@@ -2,8 +2,7 @@ import pytest
 import pandas as pd
 
 from unittest.mock import patch
-from carto.exceptions import CartoException
-from google.api_core.exceptions import NotFound
+from pyrestcli.exceptions import ServerErrorException
 
 from cartoframes.auth import Credentials
 from cartoframes.data.observatory.catalog.entity import CatalogList
@@ -162,7 +161,7 @@ class TestGeography(object):
     def test_get_all_geographies_credentials(self, mocked_repo):
         # Given
         mocked_repo.return_value = test_geographies
-        credentials = Credentials('user', '1234')
+        credentials = Credentials('fake_user', '1234')
 
         # When
         geographies = Geography.get_all(credentials=credentials)
@@ -173,6 +172,24 @@ class TestGeography(object):
         assert isinstance(geographies, CatalogList)
         assert geographies == test_geographies
 
+    @patch.object(GeographyRepository, 'get_all')
+    def test_get_all_geographies_credentials_without_do_enabled(self, mocked_repo):
+        # Given
+        def raise_exception(a, b):
+            raise ServerErrorException(['The user does not have Data Observatory enabled'])
+        mocked_repo.side_effect = raise_exception
+        credentials = Credentials('fake_user', '1234')
+
+        # When
+        with pytest.raises(Exception) as e:
+            Geography.get_all(credentials=credentials)
+
+        # Then
+        assert str(e.value) == (
+            'We are sorry, the Data Observatory is not enabled for your account yet. '
+            'Please contact your customer success manager or send an email to '
+            'sales@carto.com to request access to it.')
+
     def test_geography_list_is_printed_with_classname_and_slugs(self):
         # Given
         geographies = CatalogList([test_geography1, test_geography2])
@@ -227,102 +244,73 @@ class TestGeography(object):
     @patch.object(GeographyRepository, 'get_by_id')
     @patch('cartoframes.data.observatory.catalog.entity._get_bigquery_client')
     def test_geography_download(self, mocked_bq_client, get_by_id_mock, get_all_mock):
-        # mock geography
+        # Given
         get_by_id_mock.return_value = test_geography1
         geography = Geography.get(test_geography1.id)
-
-        # mock subscriptions
         get_all_mock.return_value = [geography]
-
-        # mock big query client
         mocked_bq_client.return_value = BigQueryClientMock()
-
-        # test
         credentials = Credentials('fake_user', '1234')
 
+        # Then
         geography.download('fake_path', credentials)
 
     @patch.object(GeographyRepository, 'get_all')
     @patch.object(GeographyRepository, 'get_by_id')
     @patch('cartoframes.data.observatory.catalog.entity._get_bigquery_client')
-    def test_geography_not_available_in_bq_download_fails(self, mocked_bq_client, get_by_id_mock, get_all_mock):
-        # mock geography
-        get_by_id_mock.return_value = test_geography2
-        geography = Geography.get(test_geography2.id)
-
-        # mock subscriptions
-        get_all_mock.return_value = [geography]
-
-        # mock big query client
-        mocked_bq_client.return_value = BigQueryClientMock()
-
-        # test
-        credentials = Credentials('fake_user', '1234')
-
-        with pytest.raises(CartoException) as e:
-            geography.download('fake_path', credentials)
-
-        error = '{} is not ready for Download. Please, contact us for more information.'.format(geography)
-        assert str(e.value) == error
-
-    @patch.object(GeographyRepository, 'get_all')
-    @patch.object(GeographyRepository, 'get_by_id')
-    @patch('cartoframes.data.observatory.catalog.entity._get_bigquery_client')
-    def test_geography_not_subscribed_download_fails(self, mocked_bq_client, get_by_id_mock, get_all_mock):
-        # mock dataset
+    def test_geography_download_not_subscribed(self, mocked_bq_client, get_by_id_mock, get_all_mock):
+        # Given
         get_by_id_mock.return_value = test_geography2
         geography = Geography.get(test_geography2.id)
-
-        # mock subscriptions
         get_all_mock.return_value = []
-
-        # mock big query client
         mocked_bq_client.return_value = BigQueryClientMock()
-
-        # test
         credentials = Credentials('fake_user', '1234')
 
-        with pytest.raises(CartoException) as e:
+        # When
+        with pytest.raises(Exception) as e:
             geography.download('fake_path', credentials)
 
-        error = 'You are not subscribed to this Geography yet. Please, use the subscribe method first.'
-        assert str(e.value) == error
+        # Then
+        assert str(e.value) == (
+            'You are not subscribed to this Geography yet. '
+            'Please, use the subscribe method first.')
 
     @patch.object(GeographyRepository, 'get_all')
     @patch.object(GeographyRepository, 'get_by_id')
     @patch('cartoframes.data.observatory.catalog.entity._get_bigquery_client')
-    def test_geography_not_subscribed_but_public_download_works(self, mocked_bq_client, get_by_id_mock, get_all_mock):
-        # mock dataset
+    def test_geography_download_not_subscribed_but_public(self, mocked_bq_client, get_by_id_mock, get_all_mock):
+        # Given
         get_by_id_mock.return_value = test_geography1  # is public
         geography = Geography.get(test_geography1.id)
-
-        # mock subscriptions
         get_all_mock.return_value = []
-
-        # mock big query client
         mocked_bq_client.return_value = BigQueryClientMock()
-
-        # test
         credentials = Credentials('fake_user', '1234')
 
+        # Then
         geography.download('fake_path', credentials)
 
+    @patch.object(GeographyRepository, 'get_all')
     @patch.object(GeographyRepository, 'get_by_id')
     @patch('cartoframes.data.observatory.catalog.entity._get_bigquery_client')
-    def test_geography_download_raises_without_do_active(self, mocked_bq_client, mocked_repo):
-        # mock geography
-        mocked_repo.return_value = test_geography1
-
-        # mock big query client
-        mocked_bq_client.return_value = BigQueryClientMock(NotFound('Fake error'))
-
-        # test
+    def test_geography_download_without_do_enabled(self, mocked_bq_client, get_by_id_mock, get_all_mock):
+        # Given
+        get_by_id_mock.return_value = test_geography1
+        geography = Geography.get(test_geography1.id)
+        get_all_mock.return_value = []
+        mocked_bq_client.return_value = BigQueryClientMock(
+            ServerErrorException(['The user does not have Data Observatory enabled'])
+        )
         credentials = Credentials('fake_user', '1234')
 
-        geography = Geography.get(test_geography1.id)
-        with pytest.raises(CartoException):
+        # When
+        with pytest.raises(Exception) as e:
             geography.download('fake_path', credentials)
 
+        # Then
+        assert str(e.value) == (
+            'We are sorry, the Data Observatory is not enabled for your account yet. '
+            'Please contact your customer success manager or send an email to '
+            'sales@carto.com to request access to it.')
+
     @patch('cartoframes.data.observatory.catalog.subscriptions.get_subscription_ids')
     @patch('cartoframes.data.observatory.catalog.utils.display_subscription_form')
     @patch('cartoframes.data.observatory.catalog.utils.display_existing_subscription_message')
@@ -331,7 +319,7 @@ class TestGeography(object):
         expected_id = db_geography1['id']
         expected_subscribed_ids = []
         mock_subscription_ids.return_value = expected_subscribed_ids
-        credentials = Credentials('user', '1234')
+        credentials = Credentials('fake_user', '1234')
         geography = Geography(db_geography1)
 
         # When
@@ -350,7 +338,7 @@ class TestGeography(object):
         expected_id = db_geography1['id']
         expected_subscribed_ids = [expected_id]
         mock_subscription_ids.return_value = expected_subscribed_ids
-        credentials = Credentials('user', '1234')
+        credentials = Credentials('fake_user', '1234')
         geography = Geography(db_geography1)
 
         # When
@@ -367,7 +355,7 @@ class TestGeography(object):
     def test_geography_subscribe_default_credentials(
       self, mocked_credentials, mock_display_form, mock_subscription_ids):
         # Given
-        expected_credentials = Credentials('user', '1234')
+        expected_credentials = Credentials('fake_user', '1234')
         mocked_credentials.return_value = expected_credentials
         geography = Geography(db_geography1)
 
@@ -392,11 +380,31 @@ class TestGeography(object):
                                 'Please pass a `Credentials` instance '
                                 'or use the `set_default_credentials` function.')
 
+    @patch('cartoframes.data.observatory.catalog.subscriptions.get_subscription_ids')
+    @patch('cartoframes.data.observatory.catalog.utils.display_subscription_form')
+    def test_geography_subscribe_without_do_enabled(self, mock_display_form, mock_subscription_ids):
+        # Given
+        def raise_exception(a, b, c):
+            raise ServerErrorException(['The user does not have Data Observatory enabled'])
+        mock_display_form.side_effect = raise_exception
+        geography = Geography(db_geography1)
+        credentials = Credentials('fake_user', '1234')
+
+        # When
+        with pytest.raises(Exception) as e:
+            geography.subscribe(credentials)
+
+        # Then
+        assert str(e.value) == (
+            'We are sorry, the Data Observatory is not enabled for your account yet. '
+            'Please contact your customer success manager or send an email to '
+            'sales@carto.com to request access to it.')
+
     @patch('cartoframes.data.observatory.catalog.subscription_info.fetch_subscription_info')
     def test_geography_subscription_info(self, mock_fetch):
         # Given
         mock_fetch.return_value = test_subscription_info
-        credentials = Credentials('user', '1234')
+        credentials = Credentials('fake_user', '1234')
         geography = Geography(db_geography1)
 
         # When
@@ -421,7 +429,7 @@ class TestGeography(object):
     @patch('cartoframes.auth.defaults.get_default_credentials')
     def test_geography_subscription_info_default_credentials(self, mocked_credentials, mock_fetch):
         # Given
-        expected_credentials = Credentials('user', '1234')
+        expected_credentials = Credentials('fake_user', '1234')
         mocked_credentials.return_value = expected_credentials
         geography = Geography(db_geography1)
 
@@ -445,6 +453,25 @@ class TestGeography(object):
                                 'Please pass a `Credentials` instance '
                                 'or use the `set_default_credentials` function.')
 
+    @patch('cartoframes.data.observatory.catalog.subscription_info.fetch_subscription_info')
+    def test_geography_subscription_info_without_do_enabled(self, mock_fetch):
+        # Given
+        def raise_exception(a, b, c):
+            raise ServerErrorException(['The user does not have Data Observatory enabled'])
+        mock_fetch.side_effect = raise_exception
+        geography = Geography(db_geography1)
+        credentials = Credentials('fake_user', '1234')
+
+        # When
+        with pytest.raises(Exception) as e:
+            geography.subscription_info(credentials)
+
+        # Then
+        assert str(e.value) == (
+            'We are sorry, the Data Observatory is not enabled for your account yet. '
+            'Please contact your customer success manager or send an email to '
+            'sales@carto.com to request access to it.')
+
     def test_geography_is_available_in(self):
         geography_in_bq = Geography(db_geography1)
         geography_not_in_bq = Geography(db_geography2)