Repository: CartoDB/cartoframes

Issue:
Catalog by smart classes
Pandas implementation of the catalog doesn't work pretty well because of these two main issues:

- Cannot implement a full extension of the classes without corner cases: https://github.com/CartoDB/cartoframes/issues/1032

- The logic of the classes is delegated to the user and it makes quite complicated when the catalog amount of data increases.

Thus, let's move to an approach where classes are smarter.

At the definition, we're just including the properties that work as methods. The rest of the properties are not defined but must appear with the same name as in the metadata DB.

`[]` Denotes a list that will work as an Entity List (see below)

Methods are replaced by properties. I think it's better for a catalog, i.e., `Catalog.countries` 👍  instead of `Catalog.countries()`  


**EntityList**

1. `get`: It will allow finding by id: `Catalog.countries.get('es')`

2. `to_dataframe`: returns a pandas dataframe of the list. 

### Classes

**Catalog**

Catalog.countries => [Country] #Static 
Catalog.datasets => [Datasets] #Static 
Catalog.categories => [Category] #Static 

**Country**

Country.get(<country_id>) => Country #Static
Country.id => String
Country.categories => [Category] 
Country.datasets => [Dataset]
Country.geographies => [Geography]

**Category**

Category.get(<category_id>) 
Category.id => String
Category.datasets => [Dataset] 
Category.geographies => [Geography] #It returns all the geographies with datasets for this category (country and category), This instance of category must be create with the optional parameter category_id
Category.countries => [Country]


**Dataset**

Dataset.get(<dataset_id>) #Static
Dataset.id => String
Dataset.variables => [Variable]
Dataset.variables_groups => { 'group_1': [Variable], 'group_2': [Variable] } # It removes the concept of Variables Groups!
Dataset.geography => Geography

**Variable**
Variable.get(<variable_id>) #static
Variable.id => String
Variable.dataset => Dataset

**Geography**

Geography.get(<geography_id>) 
Geography.datasets = [Dataset]
Geography.support = String (admin|quadgrid|postalcodes)
Geography.support_level = 1,2,3,4
Geography.country = Country

If Geography class is instantiate by providing category_id, datasets method will return all the datasets filtered by the category provided.


### Usage 

Get all categories of a country

>Country.get('usa').categories

Convert a list to pandas

> Country.get('usa').categories.to_dataframe().head()
> Country.get('usa').geographies.to_dataframe().head()
> Country.get('usa').datasets.to_dataframe().head()

Get all datasets of a category

> Country.get('usa').categories.get('demographics').datasets

Get all datasets of a category

> Category.get('geomgraphics').countries.get('usa').datasets

Get all boundaries with demographics datasets

> Country.get('usa').categories.get('demographics').geographies


Get all demographics datasets for block groups of a country

> Country.get('usa').categories.get('demographics').geographies.get('block_groups').datasets()


cc: @alrocar @cmongut 



Discussion:

Patch:
diff --git a/cartoframes/data/observatory/category.py b/cartoframes/data/observatory/category.py
index 50a36bb6..20b62dc8 100644
--- a/cartoframes/data/observatory/category.py
+++ b/cartoframes/data/observatory/category.py
@@ -1,5 +1,9 @@
 from __future__ import absolute_import
 
+from cartoframes.data.observatory.repository.constants import CATEGORY_FILTER
+
+from cartoframes.data.observatory.repository.geography_repo import get_geography_repo
+
 from .entity import CatalogEntity
 from .repository.category_repo import get_category_repo
 from .repository.dataset_repo import get_dataset_repo
@@ -11,7 +15,11 @@ class Category(CatalogEntity):
 
     @property
     def datasets(self):
-        return get_dataset_repo().get_by_category(self.id)
+        return get_dataset_repo().get_all({CATEGORY_FILTER: self.id})
+
+    @property
+    def geographies(self):
+        return get_geography_repo().get_all({CATEGORY_FILTER: self.id})
 
     @property
     def name(self):
diff --git a/cartoframes/data/observatory/country.py b/cartoframes/data/observatory/country.py
index e2ad2d77..53b87b5b 100644
--- a/cartoframes/data/observatory/country.py
+++ b/cartoframes/data/observatory/country.py
@@ -1,9 +1,12 @@
 from __future__ import absolute_import
 
+
 from .entity import CatalogEntity
 from .repository.geography_repo import get_geography_repo
 from .repository.country_repo import get_country_repo
 from .repository.dataset_repo import get_dataset_repo
+from .repository.category_repo import get_category_repo
+from .repository.constants import COUNTRY_FILTER
 
 
 class Country(CatalogEntity):
@@ -12,8 +15,12 @@ class Country(CatalogEntity):
 
     @property
     def datasets(self):
-        return get_dataset_repo().get_by_country(self.id)
+        return get_dataset_repo().get_all({COUNTRY_FILTER: self.id})
 
     @property
     def geographies(self):
-        return get_geography_repo().get_by_country(self.id)
+        return get_geography_repo().get_all({COUNTRY_FILTER: self.id})
+
+    @property
+    def categories(self):
+        return get_category_repo().get_all({COUNTRY_FILTER: self.id})
diff --git a/cartoframes/data/observatory/dataset.py b/cartoframes/data/observatory/dataset.py
index 108cc420..af6aecd4 100644
--- a/cartoframes/data/observatory/dataset.py
+++ b/cartoframes/data/observatory/dataset.py
@@ -1,9 +1,11 @@
 from __future__ import absolute_import
 
+
 from .entity import CatalogEntity
 from .repository.dataset_repo import get_dataset_repo
 from .repository.variable_repo import get_variable_repo
 from .repository.variable_group_repo import get_variable_group_repo
+from .repository.constants import DATASET_FILTER
 
 
 class Dataset(CatalogEntity):
@@ -11,11 +13,11 @@ class Dataset(CatalogEntity):
 
     @property
     def variables(self):
-        return get_variable_repo().get_by_dataset(self.id)
+        return get_variable_repo().get_all({DATASET_FILTER: self.id})
 
     @property
     def variables_groups(self):
-        return get_variable_group_repo().get_by_dataset(self.id)
+        return get_variable_group_repo().get_all({DATASET_FILTER: self.id})
 
     @property
     def name(self):
diff --git a/cartoframes/data/observatory/entity.py b/cartoframes/data/observatory/entity.py
index 2acb8d0a..ceaf83bd 100644
--- a/cartoframes/data/observatory/entity.py
+++ b/cartoframes/data/observatory/entity.py
@@ -29,6 +29,13 @@ class CatalogEntity(ABC):
     def id(self):
         return self.data[self.id_field]
 
+    @property
+    def slug(self):
+        try:
+            return self.data['slug']
+        except KeyError:
+            return None
+
     @classmethod
     def get(cls, id_):
         return cls.entity_repo.get_by_id(id_)
@@ -102,7 +109,7 @@ class CatalogList(list):
         super(CatalogList, self).__init__(data)
 
     def get(self, item_id):
-        return next(filter(lambda item: item.id == item_id, self), None)
+        return next(iter(filter(lambda item: item.id == item_id or item.slug == item_id, self)), None)
 
     def to_dataframe(self):
         return pd.DataFrame([item.data for item in self])
diff --git a/cartoframes/data/observatory/geography.py b/cartoframes/data/observatory/geography.py
index bd9656da..8f1e5c11 100644
--- a/cartoframes/data/observatory/geography.py
+++ b/cartoframes/data/observatory/geography.py
@@ -1,8 +1,10 @@
 from __future__ import absolute_import
 
+
 from .entity import CatalogEntity
 from .repository.dataset_repo import get_dataset_repo
 from .repository.geography_repo import get_geography_repo
+from .repository.constants import GEOGRAPHY_FILTER
 
 
 class Geography(CatalogEntity):
@@ -11,7 +13,7 @@ class Geography(CatalogEntity):
 
     @property
     def datasets(self):
-        return get_dataset_repo().get_by_geography(self.id)
+        return get_dataset_repo().get_all({GEOGRAPHY_FILTER: self.id})
 
     @property
     def name(self):
diff --git a/cartoframes/data/observatory/provider.py b/cartoframes/data/observatory/provider.py
index 85f908a7..26a06b4b 100644
--- a/cartoframes/data/observatory/provider.py
+++ b/cartoframes/data/observatory/provider.py
@@ -3,6 +3,7 @@ from __future__ import absolute_import
 from .entity import CatalogEntity
 from .repository.provider_repo import get_provider_repo
 from .repository.dataset_repo import get_dataset_repo
+from .repository.constants import PROVIDER_FILTER
 
 
 class Provider(CatalogEntity):
@@ -11,7 +12,7 @@ class Provider(CatalogEntity):
 
     @property
     def datasets(self):
-        return get_dataset_repo().get_by_provider(self.id)
+        return get_dataset_repo().get_all({PROVIDER_FILTER: self.id})
 
     @property
     def name(self):
diff --git a/cartoframes/data/observatory/repository/dataset_repo.py b/cartoframes/data/observatory/repository/dataset_repo.py
index 607fecdc..66a1eb9a 100644
--- a/cartoframes/data/observatory/repository/dataset_repo.py
+++ b/cartoframes/data/observatory/repository/dataset_repo.py
@@ -22,21 +22,6 @@ class DatasetRepository(EntityRepository):
         self.client.set_user_credentials(credentials)
         return self._get_filtered_entities(filters)
 
-    def get_by_country(self, iso_code3):
-        return self._get_filtered_entities({COUNTRY_FILTER: iso_code3})
-
-    def get_by_category(self, category_id):
-        return self._get_filtered_entities({CATEGORY_FILTER: category_id})
-
-    def get_by_variable(self, variable_id):
-        return self._get_filtered_entities({VARIABLE_FILTER: variable_id})
-
-    def get_by_geography(self, geography_id):
-        return self._get_filtered_entities({GEOGRAPHY_FILTER: geography_id})
-
-    def get_by_provider(self, provider_id):
-        return self._get_filtered_entities({PROVIDER_FILTER: provider_id})
-
     @classmethod
     def _get_entity_class(cls):
         from cartoframes.data.observatory.dataset import Dataset
diff --git a/cartoframes/data/observatory/repository/geography_repo.py b/cartoframes/data/observatory/repository/geography_repo.py
index b07126ee..fbf0065e 100644
--- a/cartoframes/data/observatory/repository/geography_repo.py
+++ b/cartoframes/data/observatory/repository/geography_repo.py
@@ -18,9 +18,6 @@ class GeographyRepository(EntityRepository):
     def __init__(self):
         super(GeographyRepository, self).__init__(_GEOGRAPHY_ID_FIELD, _ALLOWED_FILTERS, _GEOGRAPHY_SLUG_FIELD)
 
-    def get_by_country(self, iso_code3):
-        return self._get_filtered_entities({COUNTRY_FILTER: iso_code3})
-
     @classmethod
     def _get_entity_class(cls):
         from cartoframes.data.observatory.geography import Geography
diff --git a/cartoframes/data/observatory/repository/repo_client.py b/cartoframes/data/observatory/repository/repo_client.py
index d1634c03..acf081d8 100644
--- a/cartoframes/data/observatory/repository/repo_client.py
+++ b/cartoframes/data/observatory/repository/repo_client.py
@@ -72,7 +72,7 @@ class RepoClient(object):
         conditions = extra_conditions or []
 
         if filters is not None and len(filters) > 0:
-            conditions.extend([self._generate_condition(key, value) for key, value in filters.items()])
+            conditions.extend([self._generate_condition(key, value) for key, value in sorted(filters.items())])
 
         return conditions
 
diff --git a/cartoframes/data/observatory/repository/variable_group_repo.py b/cartoframes/data/observatory/repository/variable_group_repo.py
index dc947380..6db22584 100644
--- a/cartoframes/data/observatory/repository/variable_group_repo.py
+++ b/cartoframes/data/observatory/repository/variable_group_repo.py
@@ -19,9 +19,6 @@ class VariableGroupRepository(EntityRepository):
         super(VariableGroupRepository, self).__init__(_VARIABLE_GROUP_ID_FIELD, _ALLOWED_FILTERS,
                                                       _VARIABLE_GROUP_SLUG_FIELD)
 
-    def get_by_dataset(self, dataset_id):
-        return self._get_filtered_entities({DATASET_FILTER: dataset_id})
-
     @classmethod
     def _get_entity_class(cls):
         from cartoframes.data.observatory.variable_group import VariableGroup
diff --git a/cartoframes/data/observatory/repository/variable_repo.py b/cartoframes/data/observatory/repository/variable_repo.py
index 3a9b05ef..05016165 100644
--- a/cartoframes/data/observatory/repository/variable_repo.py
+++ b/cartoframes/data/observatory/repository/variable_repo.py
@@ -18,12 +18,6 @@ class VariableRepository(EntityRepository):
     def __init__(self):
         super(VariableRepository, self).__init__(_VARIABLE_ID_FIELD, _ALLOWED_DATASETS, _VARIABLE_SLUG_FIELD)
 
-    def get_by_dataset(self, dataset_id):
-        return self._get_filtered_entities({DATASET_FILTER: dataset_id})
-
-    def get_by_variable_group(self, variable_group_id):
-        return self._get_filtered_entities({VARIABLE_GROUP_FILTER: variable_group_id})
-
     @classmethod
     def _get_entity_class(cls):
         from cartoframes.data.observatory.variable import Variable
diff --git a/cartoframes/data/observatory/variable.py b/cartoframes/data/observatory/variable.py
index b13e3d6b..31a50e05 100644
--- a/cartoframes/data/observatory/variable.py
+++ b/cartoframes/data/observatory/variable.py
@@ -3,6 +3,7 @@ from __future__ import absolute_import
 from .entity import CatalogEntity
 from .repository.dataset_repo import get_dataset_repo
 from .repository.variable_repo import get_variable_repo
+from .repository.constants import VARIABLE_FILTER
 
 
 class Variable(CatalogEntity):
@@ -11,7 +12,7 @@ class Variable(CatalogEntity):
 
     @property
     def datasets(self):
-        return get_dataset_repo().get_by_variable(self.id)
+        return get_dataset_repo().get_all({VARIABLE_FILTER: self.id})
 
     @property
     def name(self):
diff --git a/cartoframes/data/observatory/variable_group.py b/cartoframes/data/observatory/variable_group.py
index 091943ad..6bf7c7a9 100644
--- a/cartoframes/data/observatory/variable_group.py
+++ b/cartoframes/data/observatory/variable_group.py
@@ -3,6 +3,7 @@ from __future__ import absolute_import
 from .entity import CatalogEntity
 from .repository.variable_group_repo import get_variable_group_repo
 from .repository.variable_repo import get_variable_repo
+from .repository.constants import VARIABLE_GROUP_FILTER
 
 
 class VariableGroup(CatalogEntity):
@@ -11,7 +12,7 @@ class VariableGroup(CatalogEntity):
 
     @property
     def variables(self):
-        return get_variable_repo().get_by_variable_group(self.id)
+        return get_variable_repo().get_all({VARIABLE_GROUP_FILTER: self.id})
 
     @property
     def name(self):


Test patch:
diff --git a/test/data/observatory/repository/test_category_repo.py b/test/data/observatory/repository/test_category_repo.py
index f748c29d..d8f34619 100644
--- a/test/data/observatory/repository/test_category_repo.py
+++ b/test/data/observatory/repository/test_category_repo.py
@@ -43,6 +43,30 @@ class TestCategoryRepo(unittest.TestCase):
         mocked_repo.assert_called_once_with(None)
         assert categories is None
 
+    @patch.object(RepoClient, 'get_categories_joined_datasets')
+    def test_get_all_only_uses_allowed_filters(self, mocked_repo):
+        # Given
+        mocked_repo.return_value = [db_category1, db_category2]
+        repo = CategoryRepository()
+        filters = {
+            'country_id': 'usa',
+            'dataset_id': 'carto-do.project.census2011',
+            'variable_id': 'population',
+            'geography_id': 'census-geo',
+            'variable_group_id': 'var-group',
+            'provider_id': 'open_data',
+            'fake_field_id': 'fake_value'
+        }
+
+        # When
+        categories = repo.get_all(filters)
+
+        # Then
+        mocked_repo.assert_called_once_with({
+            'country_id': 'usa'
+        })
+        assert categories == test_categories
+
     @patch.object(RepoClient, 'get_categories')
     def test_get_by_id(self, mocked_repo):
         # Given
diff --git a/test/data/observatory/repository/test_country_repo.py b/test/data/observatory/repository/test_country_repo.py
index 723cc38b..72c0a772 100644
--- a/test/data/observatory/repository/test_country_repo.py
+++ b/test/data/observatory/repository/test_country_repo.py
@@ -41,6 +41,30 @@ class TestCountryRepo(unittest.TestCase):
         # Then
         assert countries is None
 
+    @patch.object(RepoClient, 'get_countries')
+    def test_get_all_only_uses_allowed_filters(self, mocked_repo):
+        # Given
+        mocked_repo.return_value = [db_country1, db_country2]
+        repo = CountryRepository()
+        filters = {
+            'dataset_id': 'carto-do.project.census2011',
+            'category_id': 'demographics',
+            'variable_id': 'population',
+            'geography_id': 'census-geo',
+            'variable_group_id': 'var-group',
+            'provider_id': 'open_data',
+            'fake_field_id': 'fake_value'
+        }
+
+        # When
+        countries = repo.get_all(filters)
+
+        # Then
+        mocked_repo.assert_called_once_with({
+            'category_id': 'demographics'
+        })
+        assert countries == test_countries
+
     @patch.object(RepoClient, 'get_countries')
     def test_get_by_id(self, mocked_repo):
         # Given
diff --git a/test/data/observatory/repository/test_dataset_repo.py b/test/data/observatory/repository/test_dataset_repo.py
index a9a20348..93389a7e 100644
--- a/test/data/observatory/repository/test_dataset_repo.py
+++ b/test/data/observatory/repository/test_dataset_repo.py
@@ -61,6 +61,34 @@ class TestDatasetRepo(unittest.TestCase):
         mocked_repo.assert_called_once_with(None)
         assert datasets is None
 
+    @patch.object(RepoClient, 'get_datasets')
+    def test_get_all_only_uses_allowed_filters(self, mocked_repo):
+        # Given
+        mocked_repo.return_value = [db_dataset1, db_dataset2]
+        repo = DatasetRepository()
+        filters = {
+            'country_id': 'usa',
+            'category_id': 'demographics',
+            'variable_id': 'population',
+            'geography_id': 'census-geo',
+            'variable_group_id': 'var-group',
+            'provider_id': 'open_data',
+            'fake_field_id': 'fake_value'
+        }
+
+        # When
+        datasets = repo.get_all(filters)
+
+        # Then
+        mocked_repo.assert_called_once_with({
+            'country_id': 'usa',
+            'category_id': 'demographics',
+            'variable_id': 'population',
+            'geography_id': 'census-geo',
+            'provider_id': 'open_data'
+        })
+        assert datasets == test_datasets
+
     @patch.object(RepoClient, 'get_datasets')
     def test_get_by_id(self, mocked_repo):
         # Given
@@ -142,66 +170,6 @@ class TestDatasetRepo(unittest.TestCase):
         assert isinstance(datasets, CatalogList)
         assert datasets == test_datasets
 
-    @patch.object(RepoClient, 'get_datasets')
-    def test_get_by_country(self, mocked_repo):
-        # Given
-        mocked_repo.return_value = [db_dataset1, db_dataset2]
-        country_code = 'esp'
-        repo = DatasetRepository()
-
-        # When
-        datasets = repo.get_by_country(country_code)
-
-        # Then
-        mocked_repo.assert_called_once_with({'country_id': country_code})
-        assert isinstance(datasets, CatalogList)
-        assert datasets == test_datasets
-
-    @patch.object(RepoClient, 'get_datasets')
-    def test_get_by_category(self, mocked_repo):
-        # Given
-        mocked_repo.return_value = [db_dataset1, db_dataset2]
-        category_id = 'cat1'
-        repo = DatasetRepository()
-
-        # When
-        datasets = repo.get_by_category(category_id)
-
-        # Then
-        mocked_repo.assert_called_once_with({'category_id': category_id})
-        assert isinstance(datasets, CatalogList)
-        assert datasets == test_datasets
-
-    @patch.object(RepoClient, 'get_datasets')
-    def test_get_by_variable(self, mocked_repo):
-        # Given
-        mocked_repo.return_value = [db_dataset1, db_dataset2]
-        variable_id = 'var1'
-        repo = DatasetRepository()
-
-        # When
-        datasets = repo.get_by_variable(variable_id)
-
-        # Then
-        mocked_repo.assert_called_once_with({'variable_id': variable_id})
-        assert isinstance(datasets, CatalogList)
-        assert datasets == test_datasets
-
-    @patch.object(RepoClient, 'get_datasets')
-    def test_get_by_geography(self, mocked_repo):
-        # Given
-        mocked_repo.return_value = [db_dataset1, db_dataset2]
-        geography_id = 'geo_id'
-        repo = DatasetRepository()
-
-        # When
-        datasets = repo.get_by_geography(geography_id)
-
-        # Then
-        mocked_repo.assert_called_once_with({'geography_id': geography_id})
-        assert isinstance(datasets, CatalogList)
-        assert datasets == test_datasets
-
     @patch.object(RepoClient, 'get_datasets')
     def test_missing_fields_are_mapped_as_None(self, mocked_repo):
         # Given
diff --git a/test/data/observatory/repository/test_geography_repo.py b/test/data/observatory/repository/test_geography_repo.py
index 0504d34e..d360bfc4 100644
--- a/test/data/observatory/repository/test_geography_repo.py
+++ b/test/data/observatory/repository/test_geography_repo.py
@@ -42,6 +42,32 @@ class TestGeographyRepo(unittest.TestCase):
         mocked_repo.assert_called_once_with(None)
         assert geographies is None
 
+    @patch.object(RepoClient, 'get_geographies_joined_datasets')
+    def test_get_all_only_uses_allowed_filters(self, mocked_repo):
+        # Given
+        mocked_repo.return_value = [db_geography1, db_geography2]
+        repo = GeographyRepository()
+        filters = {
+            'country_id': 'usa',
+            'dataset_id': 'carto-do.project.census2011',
+            'category_id': 'demographics',
+            'variable_id': 'population',
+            'geography_id': 'census-geo',
+            'variable_group_id': 'var-group',
+            'provider_id': 'open_data',
+            'fake_field_id': 'fake_value'
+        }
+
+        # When
+        geographies = repo.get_all(filters)
+
+        # Then
+        mocked_repo.assert_called_once_with({
+            'country_id': 'usa',
+            'category_id': 'demographics'
+        })
+        assert geographies == test_geographies
+
     @patch.object(RepoClient, 'get_geographies')
     def test_get_by_id(self, mocked_repo):
         # Given
@@ -124,21 +150,6 @@ class TestGeographyRepo(unittest.TestCase):
         assert isinstance(geographies, CatalogList)
         assert geographies == test_geographies
 
-    @patch.object(RepoClient, 'get_geographies_joined_datasets')
-    def test_get_by_country(self, mocked_repo):
-        # Given
-        mocked_repo.return_value = [db_geography1, db_geography2]
-        country_code = 'esp'
-        repo = GeographyRepository()
-
-        # When
-        geographies = repo.get_by_country(country_code)
-
-        # Then
-        mocked_repo.assert_called_once_with({'country_id': country_code})
-        assert isinstance(geographies, CatalogList)
-        assert geographies == test_geographies
-
     @patch.object(RepoClient, 'get_geographies_joined_datasets')
     def test_get_all_with_join_filters(self, mocked_repo):
         # Given
diff --git a/test/data/observatory/repository/test_variable_group_repo.py b/test/data/observatory/repository/test_variable_group_repo.py
index eb028e8d..8e78fece 100644
--- a/test/data/observatory/repository/test_variable_group_repo.py
+++ b/test/data/observatory/repository/test_variable_group_repo.py
@@ -42,6 +42,31 @@ class TestVariableGroupRepo(unittest.TestCase):
         mocked_repo.assert_called_once_with(None)
         assert variables_groups is None
 
+    @patch.object(RepoClient, 'get_variables_groups')
+    def test_get_all_only_uses_allowed_filters(self, mocked_repo):
+        # Given
+        mocked_repo.return_value = [db_variable_group1, db_variable_group2]
+        repo = VariableGroupRepository()
+        filters = {
+            'country_id': 'usa',
+            'dataset_id': 'carto-do.project.census2011',
+            'category_id': 'demographics',
+            'variable_id': 'population',
+            'geography_id': 'census-geo',
+            'variable_group_id': 'var-group',
+            'provider_id': 'open_data',
+            'fake_field_id': 'fake_value'
+        }
+
+        # When
+        variables_groups = repo.get_all(filters)
+
+        # Then
+        mocked_repo.assert_called_once_with({
+            'dataset_id': 'carto-do.project.census2011'
+        })
+        assert variables_groups == test_variables_groups
+
     @patch.object(RepoClient, 'get_variables_groups')
     def test_get_by_id(self, mocked_repo):
         # Given
@@ -124,21 +149,6 @@ class TestVariableGroupRepo(unittest.TestCase):
         assert isinstance(variable_groups, CatalogList)
         assert variable_groups == test_variables_groups
 
-    @patch.object(RepoClient, 'get_variables_groups')
-    def test_get_by_dataset(self, mocked_repo):
-        # Given
-        mocked_repo.return_value = [db_variable_group1, db_variable_group2]
-        dataset_id = 'dataset1'
-        repo = VariableGroupRepository()
-
-        # When
-        variables_groups = repo.get_by_dataset(dataset_id)
-
-        # Then
-        mocked_repo.assert_called_once_with({'dataset_id': dataset_id})
-        assert isinstance(variables_groups, CatalogList)
-        assert variables_groups == test_variables_groups
-
     @patch.object(RepoClient, 'get_variables_groups')
     def test_missing_fields_are_mapped_as_None(self, mocked_repo):
         # Given
diff --git a/test/data/observatory/repository/test_variable_repo.py b/test/data/observatory/repository/test_variable_repo.py
index f0b14a83..8d5bc0af 100644
--- a/test/data/observatory/repository/test_variable_repo.py
+++ b/test/data/observatory/repository/test_variable_repo.py
@@ -42,6 +42,32 @@ class TestVariableRepo(unittest.TestCase):
         mocked_repo.assert_called_once_with(None)
         assert variables is None
 
+    @patch.object(RepoClient, 'get_variables')
+    def test_get_all_only_uses_allowed_filters(self, mocked_repo):
+        # Given
+        mocked_repo.return_value = [db_variable1, db_variable2]
+        repo = VariableRepository()
+        filters = {
+            'country_id': 'usa',
+            'dataset_id': 'carto-do.project.census2011',
+            'category_id': 'demographics',
+            'variable_id': 'population',
+            'geography_id': 'census-geo',
+            'variable_group_id': 'var-group',
+            'provider_id': 'open_data',
+            'fake_field_id': 'fake_value'
+        }
+
+        # When
+        variables = repo.get_all(filters)
+
+        # Then
+        mocked_repo.assert_called_once_with({
+            'dataset_id': 'carto-do.project.census2011',
+            'variable_group_id': 'var-group'
+        })
+        assert variables == test_variables
+
     @patch.object(RepoClient, 'get_variables')
     def test_get_by_id(self, mocked_repo):
         # Given
@@ -124,36 +150,6 @@ class TestVariableRepo(unittest.TestCase):
         assert isinstance(variables, CatalogList)
         assert variables == test_variables
 
-    @patch.object(RepoClient, 'get_variables')
-    def test_get_by_dataset(self, mocked_repo):
-        # Given
-        mocked_repo.return_value = [db_variable1, db_variable2]
-        dataset_id = 'dataset1'
-        repo = VariableRepository()
-
-        # When
-        variables = repo.get_by_dataset(dataset_id)
-
-        # Then
-        mocked_repo.assert_called_once_with({'dataset_id': dataset_id})
-        assert isinstance(variables, CatalogList)
-        assert variables == test_variables
-
-    @patch.object(RepoClient, 'get_variables')
-    def test_get_by_variable_group(self, mocked_repo):
-        # Given
-        mocked_repo.return_value = [db_variable1, db_variable2]
-        variable_group_id = 'vargroup1'
-        repo = VariableRepository()
-
-        # When
-        variables = repo.get_by_variable_group(variable_group_id)
-
-        # Then
-        mocked_repo.assert_called_once_with({'variable_group_id': variable_group_id})
-        assert isinstance(variables, CatalogList)
-        assert variables == test_variables
-
     @patch.object(RepoClient, 'get_variables')
     def test_missing_fields_are_mapped_as_None(self, mocked_repo):
         # Given
diff --git a/test/data/observatory/test_category.py b/test/data/observatory/test_category.py
index d2ad1aa1..8beedc3d 100644
--- a/test/data/observatory/test_category.py
+++ b/test/data/observatory/test_category.py
@@ -1,11 +1,13 @@
 import unittest
 import pandas as pd
+from cartoframes.data.observatory.repository.geography_repo import GeographyRepository
 
 from cartoframes.data.observatory.category import Category
 from cartoframes.data.observatory.repository.category_repo import CategoryRepository
 from cartoframes.data.observatory.repository.dataset_repo import DatasetRepository
 from cartoframes.data.observatory.entity import CatalogList
-from .examples import test_category1, test_datasets, test_categories, db_category1, test_category2, db_category2
+from .examples import test_category1, test_datasets, test_categories, db_category1, test_category2, db_category2, \
+    test_geographies
 
 try:
     from unittest.mock import Mock, patch
@@ -28,7 +30,19 @@ class TestCategory(unittest.TestCase):
         assert isinstance(category, Category)
         assert category == test_category1
 
-    @patch.object(DatasetRepository, 'get_by_category')
+    def test_get_category_by_id_from_categories_list(self):
+        # Given
+        categories = CatalogList([test_category1, test_category2])
+
+        # When
+        category = categories.get(test_category1.id)
+
+        # Then
+        assert isinstance(category, object)
+        assert isinstance(category, Category)
+        assert category == test_category1
+
+    @patch.object(DatasetRepository, 'get_all')
     def test_get_datasets_by_category(self, mocked_repo):
         # Given
         mocked_repo.return_value = test_datasets
@@ -37,10 +51,25 @@ class TestCategory(unittest.TestCase):
         datasets = test_category1.datasets
 
         # Then
+        mocked_repo.assert_called_once_with({'category_id': test_category1.id})
         assert isinstance(datasets, list)
         assert isinstance(datasets, CatalogList)
         assert datasets == test_datasets
 
+    @patch.object(GeographyRepository, 'get_all')
+    def test_get_geographies_by_category(self, mocked_repo):
+        # Given
+        mocked_repo.return_value = test_geographies
+
+        # When
+        geographies = test_category1.geographies
+
+        # Then
+        mocked_repo.assert_called_once_with({'category_id': test_category1.id})
+        assert isinstance(geographies, list)
+        assert isinstance(geographies, CatalogList)
+        assert geographies == test_geographies
+
     def test_category_properties(self):
         # Given
         category = Category(db_category1)
@@ -130,19 +159,6 @@ class TestCategory(unittest.TestCase):
         assert categories_repr == "[<Category('{id1}')>, <Category('{id2}')>]"\
                                   .format(id1=db_category1['id'], id2=db_category2['id'])
 
-    @patch.object(CategoryRepository, 'get_by_id')
-    def test_get_category_by_id(self, mocked_repo):
-        # Given
-        mocked_repo.return_value = test_category1
-
-        # When
-        category = Category.get('cat1')
-
-        # Then
-        assert isinstance(category, object)
-        assert isinstance(category, Category)
-        assert category == test_category1
-
     def test_categories_items_are_obtained_as_category(self):
         # Given
         categories = test_categories
diff --git a/test/data/observatory/test_country.py b/test/data/observatory/test_country.py
index 42d68eed..3e54c4dc 100644
--- a/test/data/observatory/test_country.py
+++ b/test/data/observatory/test_country.py
@@ -1,5 +1,6 @@
 import unittest
 import pandas as pd
+from cartoframes.data.observatory.repository.category_repo import CategoryRepository
 
 from cartoframes.data.observatory.entity import CatalogList
 from cartoframes.data.observatory.country import Country
@@ -7,7 +8,7 @@ from cartoframes.data.observatory.repository.geography_repo import GeographyRepo
 from cartoframes.data.observatory.repository.dataset_repo import DatasetRepository
 from cartoframes.data.observatory.repository.country_repo import CountryRepository
 from .examples import test_country1, test_datasets, test_countries, test_geographies, db_country1, test_country2, \
-    db_country2
+    db_country2, test_categories
 
 try:
     from unittest.mock import Mock, patch
@@ -30,7 +31,19 @@ class TestCountry(unittest.TestCase):
         assert isinstance(country, Country)
         assert country == test_country1
 
-    @patch.object(DatasetRepository, 'get_by_country')
+    def test_get_country_by_id_from_countries_list(self):
+        # Given
+        countries = CatalogList([test_country1, test_country2])
+
+        # When
+        country = countries.get(test_country1.id)
+
+        # Then
+        assert isinstance(country, object)
+        assert isinstance(country, Country)
+        assert country == test_country1
+
+    @patch.object(DatasetRepository, 'get_all')
     def test_get_datasets_by_country(self, mocked_repo):
         # Given
         mocked_repo.return_value = test_datasets
@@ -39,11 +52,12 @@ class TestCountry(unittest.TestCase):
         datasets = test_country1.datasets
 
         # Then
+        mocked_repo.assert_called_once_with({'country_id': test_country1.id})
         assert isinstance(datasets, list)
         assert isinstance(datasets, CatalogList)
         assert datasets == test_datasets
 
-    @patch.object(GeographyRepository, 'get_by_country')
+    @patch.object(GeographyRepository, 'get_all')
     def test_get_geographies_by_country(self, mocked_repo):
         # Given
         mocked_repo.return_value = test_geographies
@@ -52,10 +66,25 @@ class TestCountry(unittest.TestCase):
         geographies = test_country1.geographies
 
         # Then
+        mocked_repo.assert_called_once_with({'country_id': test_country1.id})
         assert isinstance(geographies, list)
         assert isinstance(geographies, CatalogList)
         assert geographies == test_geographies
 
+    @patch.object(CategoryRepository, 'get_all')
+    def test_get_categories_by_country(self, mocked_repo):
+        # Given
+        mocked_repo.return_value = test_categories
+
+        # When
+        categories = test_country1.categories
+
+        # Then
+        mocked_repo.assert_called_once_with({'country_id': test_country1.id})
+        assert isinstance(categories, list)
+        assert isinstance(categories, CatalogList)
+        assert categories == test_categories
+
     def test_country_properties(self):
         # Given
         country = Country(db_country1)
@@ -143,19 +172,6 @@ class TestCountry(unittest.TestCase):
         assert countries_repr == "[<Country('{id1}')>, <Country('{id2}')>]"\
                                  .format(id1=db_country1['id'], id2=db_country2['id'])
 
-    @patch.object(CountryRepository, 'get_by_id')
-    def test_get_country_by_id(self, mocked_repo):
-        # Given
-        mocked_repo.return_value = test_country1
-
-        # When
-        country = Country.get('esp')
-
-        # Then
-        assert isinstance(country, object)
-        assert isinstance(country, Country)
-        assert country == test_country1
-
     def test_countries_items_are_obtained_as_country(self):
         # Given
         countries = test_countries
diff --git a/test/data/observatory/test_dataset.py b/test/data/observatory/test_dataset.py
index 24523e37..b834bb1f 100644
--- a/test/data/observatory/test_dataset.py
+++ b/test/data/observatory/test_dataset.py
@@ -36,7 +36,31 @@ class TestDataset(unittest.TestCase):
         assert isinstance(dataset, Dataset)
         assert dataset == test_dataset1
 
-    @patch.object(VariableRepository, 'get_by_dataset')
+    def test_get_dataset_by_id_from_datasets_list(self):
+        # Given
+        datasets = CatalogList([test_dataset1, test_dataset2])
+
+        # When
+        dataset = datasets.get(test_dataset1.id)
+
+        # Then
+        assert isinstance(dataset, object)
+        assert isinstance(dataset, Dataset)
+        assert dataset == test_dataset1
+
+    def test_get_dataset_by_slug_from_datasets_list(self):
+        # Given
+        datasets = CatalogList([test_dataset1, test_dataset2])
+
+        # When
+        dataset = datasets.get(test_dataset1.slug)
+
+        # Then
+        assert isinstance(dataset, object)
+        assert isinstance(dataset, Dataset)
+        assert dataset == test_dataset1
+
+    @patch.object(VariableRepository, 'get_all')
     def test_get_variables_by_dataset(self, mocked_repo):
         # Given
         mocked_repo.return_value = test_variables
@@ -45,11 +69,12 @@ class TestDataset(unittest.TestCase):
         variables = test_dataset1.variables
 
         # Then
+        mocked_repo.assert_called_once_with({'dataset_id': test_dataset1.id})
         assert isinstance(variables, list)
         assert isinstance(variables, CatalogList)
         assert variables == test_variables
 
-    @patch.object(VariableGroupRepository, 'get_by_dataset')
+    @patch.object(VariableGroupRepository, 'get_all')
     def test_get_variables_groups_by_dataset(self, mocked_repo):
         # Given
         mocked_repo.return_value = test_variables_groups
@@ -58,6 +83,7 @@ class TestDataset(unittest.TestCase):
         variables_groups = test_dataset1.variables_groups
 
         # Then
+        mocked_repo.assert_called_once_with({'dataset_id': test_dataset1.id})
         assert isinstance(variables_groups, list)
         assert isinstance(variables_groups, CatalogList)
         assert variables_groups == test_variables_groups
@@ -68,6 +94,7 @@ class TestDataset(unittest.TestCase):
 
         # When
         dataset_id = dataset.id
+        slug = dataset.slug
         name = dataset.name
         description = dataset.description
         provider = dataset.provider
@@ -85,6 +112,7 @@ class TestDataset(unittest.TestCase):
 
         # Then
         assert dataset_id == db_dataset1['id']
+        assert slug == db_dataset1['slug']
         assert name == db_dataset1['name']
         assert description == db_dataset1['description']
         assert provider == db_dataset1['provider_id']
diff --git a/test/data/observatory/test_geography.py b/test/data/observatory/test_geography.py
index a2e83d1a..db384968 100644
--- a/test/data/observatory/test_geography.py
+++ b/test/data/observatory/test_geography.py
@@ -33,7 +33,31 @@ class TestGeography(unittest.TestCase):
         assert isinstance(geography, Geography)
         assert geography == test_geography1
 
-    @patch.object(DatasetRepository, 'get_by_geography')
+    def test_get_geography_by_id_from_geographies_list(self):
+        # Given
+        geographies = CatalogList([test_geography1, test_geography2])
+
+        # When
+        geography = geographies.get(test_geography1.id)
+
+        # Then
+        assert isinstance(geography, object)
+        assert isinstance(geography, Geography)
+        assert geography == test_geography1
+
+    def test_get_geography_by_slug_from_geographies_list(self):
+        # Given
+        geographies = CatalogList([test_geography1, test_geography2])
+
+        # When
+        geography = geographies.get(test_geography1.slug)
+
+        # Then
+        assert isinstance(geography, object)
+        assert isinstance(geography, Geography)
+        assert geography == test_geography1
+
+    @patch.object(DatasetRepository, 'get_all')
     def test_get_datasets_by_geography(self, mocked_repo):
         # Given
         mocked_repo.return_value = test_datasets
@@ -42,6 +66,7 @@ class TestGeography(unittest.TestCase):
         datasets = test_geography1.datasets
 
         # Then
+        mocked_repo.assert_called_once_with({'geography_id': test_geography1.id})
         assert isinstance(datasets, list)
         assert isinstance(datasets, CatalogList)
         assert datasets == test_datasets
@@ -52,6 +77,7 @@ class TestGeography(unittest.TestCase):
 
         # When
         geography_id = geography.id
+        slug = geography.slug
         name = geography.name
         description = geography.description
         country = geography.country
@@ -65,6 +91,7 @@ class TestGeography(unittest.TestCase):
 
         # Then
         assert geography_id == db_geography1['id']
+        assert slug == db_geography1['slug']
         assert name == db_geography1['name']
         assert description == db_geography1['description']
         assert country == db_geography1['country_id']
@@ -153,19 +180,6 @@ class TestGeography(unittest.TestCase):
         assert categories_repr == "[<Geography('{id1}')>, <Geography('{id2}')>]"\
                                   .format(id1=db_geography1['slug'], id2=db_geography2['slug'])
 
-    @patch.object(GeographyRepository, 'get_by_id')
-    def test_get_geography_by_id(self, mocked_repo):
-        # Given
-        mocked_repo.return_value = test_geography1
-
-        # When
-        geography = Geography.get(test_geography1.id)
-
-        # Then
-        assert isinstance(geography, object)
-        assert isinstance(geography, Geography)
-        assert geography == test_geography1
-
     def test_geographies_items_are_obtained_as_geography(self):
         # Given
         geographies = test_geographies
diff --git a/test/data/observatory/test_provider.py b/test/data/observatory/test_provider.py
index 38b7d8dd..007555e6 100644
--- a/test/data/observatory/test_provider.py
+++ b/test/data/observatory/test_provider.py
@@ -28,7 +28,19 @@ class TestProvider(unittest.TestCase):
         assert isinstance(provider, Provider)
         assert provider == test_provider1
 
-    @patch.object(DatasetRepository, 'get_by_provider')
+    def test_get_provider_by_id_from_providers_list(self):
+        # Given
+        providers = CatalogList([test_provider1, test_provider2])
+
+        # When
+        provider = providers.get(test_provider1.id)
+
+        # Then
+        assert isinstance(provider, object)
+        assert isinstance(provider, Provider)
+        assert provider == test_provider1
+
+    @patch.object(DatasetRepository, 'get_all')
     def test_get_datasets_by_provider(self, mocked_repo):
         # Given
         mocked_repo.return_value = test_datasets
@@ -37,6 +49,7 @@ class TestProvider(unittest.TestCase):
         datasets = test_provider1.datasets
 
         # Then
+        mocked_repo.assert_called_once_with({'provider_id': test_provider1.id})
         assert isinstance(datasets, list)
         assert isinstance(datasets, CatalogList)
         assert datasets == test_datasets
@@ -130,19 +143,6 @@ class TestProvider(unittest.TestCase):
         assert providers_repr == "[<Provider('{id1}')>, <Provider('{id2}')>]"\
                                  .format(id1=db_provider1['id'], id2=db_provider2['id'])
 
-    @patch.object(ProviderRepository, 'get_by_id')
-    def test_get_provider_by_id(self, mocked_repo):
-        # Given
-        mocked_repo.return_value = test_provider1
-
-        # When
-        provider = Provider.get('bbva')
-
-        # Then
-        assert isinstance(provider, object)
-        assert isinstance(provider, Provider)
-        assert provider == test_provider1
-
     def test_providers_items_are_obtained_as_provider(self):
         # Given
         providers = test_providers
diff --git a/test/data/observatory/test_variable.py b/test/data/observatory/test_variable.py
index 304c7c34..cdf539c5 100644
--- a/test/data/observatory/test_variable.py
+++ b/test/data/observatory/test_variable.py
@@ -28,7 +28,31 @@ class TestVariable(unittest.TestCase):
         assert isinstance(variable, Variable)
         assert variable == test_variable1
 
-    @patch.object(DatasetRepository, 'get_by_variable')
+    def test_get_variable_by_id_from_variables_list(self):
+        # Given
+        variables = CatalogList([test_variable1, test_variable2])
+
+        # When
+        variable = variables.get(test_variable1.id)
+
+        # Then
+        assert isinstance(variable, object)
+        assert isinstance(variable, Variable)
+        assert variable == test_variable1
+
+    def test_get_variable_by_slug_from_variables_list(self):
+        # Given
+        variables = CatalogList([test_variable1, test_variable2])
+
+        # When
+        variable = variables.get(test_variable1.slug)
+
+        # Then
+        assert isinstance(variable, object)
+        assert isinstance(variable, Variable)
+        assert variable == test_variable1
+
+    @patch.object(DatasetRepository, 'get_all')
     def test_get_datasets_by_variable(self, mocked_repo):
         # Given
         mocked_repo.return_value = test_datasets
@@ -37,6 +61,7 @@ class TestVariable(unittest.TestCase):
         datasets = test_variable1.datasets
 
         # Then
+        mocked_repo.assert_called_once_with({'variable_id': test_variable1.id})
         assert isinstance(datasets, list)
         assert isinstance(datasets, CatalogList)
         assert datasets == test_datasets
@@ -47,6 +72,7 @@ class TestVariable(unittest.TestCase):
 
         # When
         variable_id = variable.id
+        slug = variable.slug
         name = variable.name
         description = variable.description
         column_name = variable.column_name
@@ -59,6 +85,7 @@ class TestVariable(unittest.TestCase):
 
         # Then
         assert variable_id == db_variable1['id']
+        assert slug == db_variable1['slug']
         assert name == db_variable1['name']
         assert description == db_variable1['description']
         assert column_name == db_variable1['column_name']
@@ -146,19 +173,6 @@ class TestVariable(unittest.TestCase):
         assert variables_repr == "[<Variable('{id1}')>, <Variable('{id2}')>]"\
                                  .format(id1=db_variable1['slug'], id2=db_variable2['slug'])
 
-    @patch.object(VariableRepository, 'get_by_id')
-    def test_get_variable_by_id(self, mocked_repo):
-        # Given
-        mocked_repo.return_value = test_variable1
-
-        # When
-        variable = Variable.get(test_variable1.id)
-
-        # Then
-        assert isinstance(variable, object)
-        assert isinstance(variable, Variable)
-        assert variable == test_variable1
-
     def test_variables_items_are_obtained_as_variable(self):
         # Given
         variables = test_variables
diff --git a/test/data/observatory/test_variable_group.py b/test/data/observatory/test_variable_group.py
index 4d91b2f4..e25b927c 100644
--- a/test/data/observatory/test_variable_group.py
+++ b/test/data/observatory/test_variable_group.py
@@ -29,7 +29,31 @@ class TestVariableGroup(unittest.TestCase):
         assert isinstance(variable_group, VariableGroup)
         assert variable_group == test_variable_group1
 
-    @patch.object(VariableRepository, 'get_by_variable_group')
+    def test_get_variable_group_by_id_from_variables_groups_list(self):
+        # Given
+        variables_groups = CatalogList([test_variable_group1, test_variable_group2])
+
+        # When
+        variable_group = variables_groups.get(test_variable_group1.id)
+
+        # Then
+        assert isinstance(variable_group, object)
+        assert isinstance(variable_group, VariableGroup)
+        assert variable_group == test_variable_group1
+
+    def test_get_variable_group_by_slug_from_variables_groups_leist(selef):
+        # Given
+        variables_groups = CatalogList([test_variable_group1, test_variable_group2])
+
+        # When
+        variable_group = variables_groups.get(test_variable_group1.slug)
+
+        # Then
+        assert isinstance(variable_group, object)
+        assert isinstance(variable_group, VariableGroup)
+        assert variable_group == test_variable_group1
+
+    @patch.object(VariableRepository, 'get_all')
     def test_get_variables_by_variable_group(self, mocked_repo):
         # Given
         mocked_repo.return_value = test_variables
@@ -38,6 +62,7 @@ class TestVariableGroup(unittest.TestCase):
         variables = test_variable_group1.variables
 
         # Then
+        mocked_repo.assert_called_once_with({'variable_group_id': test_variable_group1.id})
         assert isinstance(variables, list)
         assert isinstance(variables, CatalogList)
         assert variables == test_variables
@@ -48,12 +73,14 @@ class TestVariableGroup(unittest.TestCase):
 
         # When
         variable_group_id = variable_group.id
+        slug = variable_group.slug
         name = variable_group.name
         dataset = variable_group.dataset
         starred = variable_group.starred
 
         # Then
         assert variable_group_id == db_variable_group1['id']
+        assert slug == db_variable_group1['slug']
         assert name == db_variable_group1['name']
         assert dataset == db_variable_group1['dataset_id']
         assert starred == db_variable_group1['starred']
@@ -135,19 +162,6 @@ class TestVariableGroup(unittest.TestCase):
         assert variables_groups_repr == "[<VariableGroup('{id1}')>, <VariableGroup('{id2}')>]"\
                                         .format(id1=db_variable_group1['slug'], id2=db_variable_group2['slug'])
 
-    @patch.object(VariableGroupRepository, 'get_by_id')
-    def test_get_variable_group_by_id(self, mocked_repo):
-        # Given
-        mocked_repo.return_value = test_variable_group1
-
-        # When
-        variable_group = VariableGroup.get(test_variable_group1.id)
-
-        # Then
-        assert isinstance(variable_group, object)
-        assert isinstance(variable_group, VariableGroup)
-        assert variable_group == test_variable_group1
-
     def test_variables_groups_items_are_obtained_as_variable_group(self):
         # Given
         variables_groups = test_variables_groups