From e6a187e2bf69bd777645fe24ea6a4cb378ee7659 Mon Sep 17 00:00:00 2001 From: Lucas Romero Date: Thu, 18 Oct 2018 16:00:55 -0300 Subject: [PATCH 1/4] get_organizations_from_ckan --- pydatajson/federation.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pydatajson/federation.py b/pydatajson/federation.py index 6e57ec1..00cf64e 100644 --- a/pydatajson/federation.py +++ b/pydatajson/federation.py @@ -300,3 +300,8 @@ def push_new_themes(catalog, portal_url, apikey): catalog, portal_url, apikey, identifier=new_theme) pushed_names.append(name) return pushed_names + + +def get_organizations_from_ckan(portal_url): + ckan_portal = RemoteCKAN(portal_url) + return ckan_portal.call_action('group_tree', data_dict={'type': 'organization'}) From 05745cb26b17e97de52bc09316d27f65cf70d21d Mon Sep 17 00:00:00 2001 From: Lucas Romero Date: Thu, 18 Oct 2018 16:01:21 -0300 Subject: [PATCH 2/4] test de comportamiento --- tests/test_federation.py | 50 +++++++++++++++------------------------- 1 file changed, 19 insertions(+), 31 deletions(-) diff --git a/tests/test_federation.py b/tests/test_federation.py index 746ae23..58449e4 100644 --- a/tests/test_federation.py +++ b/tests/test_federation.py @@ -18,12 +18,15 @@ SAMPLES_DIR = os.path.join("tests", "samples") -class PushDatasetTestCase(unittest.TestCase): - +class FederationSuite(unittest.TestCase): @classmethod def get_sample(cls, sample_filename): return os.path.join(SAMPLES_DIR, sample_filename) + +@patch('pydatajson.federation.RemoteCKAN', autospec=True) +class PushDatasetTestCase(FederationSuite): + @classmethod def setUpClass(cls): cls.catalog = pydatajson.DataJson(cls.get_sample('full_data.json')) @@ -43,7 +46,6 @@ def setUpClass(cls): cls.minimum_dataset['distribution'][0][ 'identifier'] = cls.dataset['distribution'][0]['identifier'] - @patch('pydatajson.federation.RemoteCKAN', autospec=True) def test_id_is_created_correctly(self, mock_portal): def mock_call_action(action, data_dict=None): if action == 'package_update': @@ -62,7 +64,6 @@ def mock_call_action(action, data_dict=None): catalog_id=self.catalog_id) self.assertEqual(self.catalog_id + '_' + self.dataset_id, res_id) - @patch('pydatajson.federation.RemoteCKAN', autospec=True) def test_id_is_updated_correctly(self, mock_portal): def mock_call_action(action, data_dict=None): if action == 'package_update': @@ -81,7 +82,6 @@ def mock_call_action(action, data_dict=None): catalog_id=self.catalog_id) self.assertEqual(self.catalog_id + '_' + self.dataset_id, res_id) - @patch('pydatajson.federation.RemoteCKAN', autospec=True) def test_dataset_id_is_preserved_if_catalog_id_is_not_passed( self, mock_portal): def mock_call_action(action, data_dict=None): @@ -97,7 +97,6 @@ def mock_call_action(action, data_dict=None): 'portal', 'key') self.assertEqual(self.dataset_id, res_id) - @patch('pydatajson.federation.RemoteCKAN', autospec=True) def test_tags_are_passed_correctly(self, mock_portal): themes = self.dataset['theme'] keywords = [kw for kw in self.dataset['keyword']] @@ -132,7 +131,6 @@ def mock_call_action(action, data_dict=None): catalog_id=self.catalog_id) self.assertEqual(self.catalog_id + '_' + self.dataset_id, res_id) - @patch('pydatajson.federation.RemoteCKAN', autospec=True) def test_licenses_are_interpreted_correctly(self, mock_portal): def mock_call_action(action, data_dict=None): if action == 'license_list': @@ -149,7 +147,6 @@ def mock_call_action(action, data_dict=None): push_dataset_to_ckan(self.catalog, 'owner', self.dataset_id, 'portal', 'key', catalog_id=self.catalog_id) - @patch('pydatajson.federation.RemoteCKAN', autospec=True) def test_dataset_without_license_sets_notspecified(self, mock_portal): def mock_call_action(action, data_dict=None): if action == 'license_list': @@ -172,7 +169,6 @@ def mock_call_action(action, data_dict=None): 'key', catalog_id=self.minimum_catalog_id) - @patch('pydatajson.federation.RemoteCKAN', autospec=True) def test_dataset_level_wrappers(self, mock_portal): def mock_call_action(action, data_dict=None): if action == 'package_update': @@ -192,7 +188,6 @@ def mock_call_action(action, data_dict=None): self.assertEqual(self.dataset_id, restored_id) self.assertEqual(self.catalog_id + '_' + self.dataset_id, harvested_id) - @patch('pydatajson.federation.RemoteCKAN', autospec=True) def test_harvest_catalog_with_no_optional_parametres(self, mock_portal): def mock_call_action(action, data_dict=None): if action == 'package_update': @@ -218,7 +213,6 @@ def mock_call_action(action, data_dict=None): for ds in self.catalog.datasets], harvested_ids) - @patch('pydatajson.federation.RemoteCKAN', autospec=True) def test_harvest_catalog_with_dataset_list(self, mock_portal): def mock_call_action(action, data_dict=None): if action == 'package_update': @@ -254,7 +248,6 @@ def mock_call_action(action, data_dict=None): [self.catalog_id + '_' + ds_id for ds_id in dataset_list], harvested_ids) - @patch('pydatajson.federation.RemoteCKAN', autospec=True) def test_harvest_catalog_with_owner_org(self, mock_portal): def mock_call_action(action, data_dict=None): if action == 'package_update': @@ -275,7 +268,6 @@ def mock_call_action(action, data_dict=None): for ds in self.catalog.datasets], harvested_ids) - @patch('pydatajson.federation.RemoteCKAN', autospec=True) def test_harvest_catalog_with_errors(self, mock_portal): def mock_call_action(action, data_dict=None): if action == 'package_update': @@ -292,7 +284,6 @@ def mock_call_action(action, data_dict=None): self.assertDictEqual( {self.catalog.datasets[1]['identifier']: "some message"}, errors) - @patch('pydatajson.federation.RemoteCKAN', autospec=True) def test_harvest_catalog_with_empty_list(self, mock_portal): harvested_ids, _ = harvest_catalog_to_ckan( self.catalog, 'portal', 'key', self.catalog_id, @@ -301,7 +292,7 @@ def test_harvest_catalog_with_empty_list(self, mock_portal): self.assertEqual([], harvested_ids) -class RemoveDatasetTestCase(unittest.TestCase): +class RemoveDatasetTestCase(FederationSuite): @patch('pydatajson.federation.RemoteCKAN', autospec=True) def test_empty_search_doesnt_call_purge(self, mock_portal): @@ -378,22 +369,17 @@ def test_remove_through_filters_and_organization( 'dataset_purge', data_dict={'id': 'id_2'}) -class PushThemeTestCase(unittest.TestCase): - - @classmethod - def get_sample(cls, sample_filename): - return os.path.join(SAMPLES_DIR, sample_filename) +@patch('pydatajson.federation.RemoteCKAN', autospec=True) +class PushThemeTestCase(FederationSuite): @classmethod def setUpClass(cls): cls.catalog = pydatajson.DataJson(cls.get_sample('full_data.json')) - @patch('pydatajson.federation.RemoteCKAN', autospec=True) def test_empty_theme_search_raises_exception(self, mock_portal): with self.assertRaises(AssertionError): push_theme_to_ckan(self.catalog, 'portal_url', 'apikey') - @patch('pydatajson.federation.RemoteCKAN', autospec=True) def test_function_pushes_theme_by_identifier(self, mock_portal): mock_portal.return_value.call_action = MagicMock( return_value={'name': 'group_name'}) @@ -404,7 +390,6 @@ def test_function_pushes_theme_by_identifier(self, mock_portal): identifier='compras') self.assertEqual('group_name', result) - @patch('pydatajson.federation.RemoteCKAN', autospec=True) def test_function_pushes_theme_by_label(self, mock_portal): mock_portal.return_value.call_action = MagicMock( return_value={'name': 'other_name'}) @@ -415,7 +400,6 @@ def test_function_pushes_theme_by_label(self, mock_portal): label='Adjudicaciones') self.assertEqual('other_name', result) - @patch('pydatajson.federation.RemoteCKAN', autospec=True) def test_ckan_portal_is_called_with_correct_parametres(self, mock_portal): mock_portal.return_value.call_action = MagicMock( return_value={'name': u'contrataciones'}) @@ -431,16 +415,13 @@ def test_ckan_portal_is_called_with_correct_parametres(self, mock_portal): 'group_create', data_dict=group) -class PushCatalogThemesTestCase(unittest.TestCase): - @classmethod - def get_sample(cls, sample_filename): - return os.path.join(SAMPLES_DIR, sample_filename) +@patch('pydatajson.federation.RemoteCKAN', autospec=True) +class PushCatalogThemesTestCase(FederationSuite): @classmethod def setUpClass(cls): cls.catalog = pydatajson.DataJson(cls.get_sample('full_data.json')) - @patch('pydatajson.federation.RemoteCKAN', autospec=True) def test_empty_portal_pushes_every_theme(self, mock_portal): def mock_call_action(action, data_dict=None): if action == 'group_list': @@ -461,7 +442,6 @@ def mock_call_action(action, data_dict=None): [theme['id'] for theme in self.catalog['themeTaxonomy']], res_names) - @patch('pydatajson.federation.RemoteCKAN', autospec=True) def test_full_portal_pushes_nothing(self, mock_portal): def mock_call_action(action, data_dict=None): if action == 'group_list': @@ -476,7 +456,6 @@ def mock_call_action(action, data_dict=None): except AttributeError: self.assertCountEqual([], res_names) - @patch('pydatajson.federation.RemoteCKAN', autospec=True) def test_non_empty_intersection_pushes_missing_themes(self, mock_portal): def mock_call_action(action, data_dict=None): if action == 'group_list': @@ -497,3 +476,12 @@ def mock_call_action(action, data_dict=None): self.assertCountEqual( [theme['id'] for theme in self.catalog['themeTaxonomy']][2:], res_names) + + +@patch('pydatajson.federation.RemoteCKAN', autospec=True) +class OrganizationsTestCase(FederationSuite): + + def test_get_organization_calls_api_correctly(self, mock_portal): + get_organizations_from_ckan('portal_url') + mock_portal.return_value.call_action.assert_called_with( + 'group_tree', data_dict={'type': 'organization'}) From d4970a51379bae7547d47414a070063b5e1bcbd9 Mon Sep 17 00:00:00 2001 From: Lucas Romero Date: Fri, 19 Oct 2018 10:59:58 -0300 Subject: [PATCH 3/4] documentacion de get_organizations_from_ckan() --- docs/MANUAL.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/MANUAL.md b/docs/MANUAL.md index 207d9fe..e03b74f 100644 --- a/docs/MANUAL.md +++ b/docs/MANUAL.md @@ -428,6 +428,15 @@ Toma los siguientes parámetros: portal de destino. Si no se pasa, se toma como organización el catalog_id Retorna el id en el nodo de destino de los datasets federados. + +### Métodos para manejo de organizaciones + +- **pydatajson.federation.get_organizations_from_ckan()**: Devuelve el árbol de organizaciones del portal pasado por parámetro. +Toma los siguientes parámetros: + - **portal_url**: URL del portal de CKAN. Debe implementar el endpoint `/group_tree`. + + Retorna una lista de diccionarios con la información de las organizaciones. Recursivamente, dentro del campo `children`, + se encuentran las organizaciones dependientes en la jerarquía. ## Anexo I: Estructura de respuestas From 06272b60d8c89f66bd2be0f1490a97580efb7e06 Mon Sep 17 00:00:00 2001 From: Lucas Romero Date: Fri, 19 Oct 2018 11:03:50 -0300 Subject: [PATCH 4/4] pycodestyle fix --- pydatajson/federation.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pydatajson/federation.py b/pydatajson/federation.py index 00cf64e..1138121 100644 --- a/pydatajson/federation.py +++ b/pydatajson/federation.py @@ -303,5 +303,14 @@ def push_new_themes(catalog, portal_url, apikey): def get_organizations_from_ckan(portal_url): + """Toma la url de un portal y devuelve su árbol de organizaciones. + + Args: + portal_url (str): La URL del portal CKAN de origen. + Returns: + dict: Diccionarios anidados con la información de + las organizaciones. + """ ckan_portal = RemoteCKAN(portal_url) - return ckan_portal.call_action('group_tree', data_dict={'type': 'organization'}) + return ckan_portal.call_action('group_tree', + data_dict={'type': 'organization'})