Skip to content

Commit

Permalink
Merge pull request #137 from datosgobar/132-push-theme-to-ckan
Browse files Browse the repository at this point in the history
132 push theme to ckan
  • Loading branch information
lrromero committed Apr 6, 2018
2 parents afc2856 + 260d7de commit c2a5c50
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 12 deletions.
12 changes: 11 additions & 1 deletion docs/MANUAL.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ Toma los siguientes parámetros:
mantener una consistencia más estricta dentro del catálogo a federar, es necesario validar los datos antes de pasarlos
a la función.

- **pydatajson.DataJson.remove_dataset_from_ckan()**: Hace un borrado físico de un dataset en un portal de CKAN.
- **pydatajson.federation.remove_dataset_from_ckan()**: Hace un borrado físico de un dataset en un portal de CKAN.
Toma los siguientes parámetros:
- **portal_url**: La URL del portal CKAN. Debe implementar la funcionalidad de `/data.json`.
- **apikey**: La apikey de un usuario con los permisos que le permitan borrar el dataset.
Expand All @@ -121,6 +121,16 @@ Toma los siguientes parámetros:
En caso de pasar más de un parámetro opcional, la función `remove_dataset_from_ckan()` borra aquellos datasets que
cumplan con todas las condiciones.

- **pydatajson.DataJson.push_theme_to_ckan()**: Crea un tema en el portal de destino
Toma los siguientes parámetros:
- **portal_url**: La URL del portal CKAN. Debe implementar la funcionalidad de `/data.json`.
- **apikey**: La apikey de un usuario con los permisos que le permitan borrar el dataset.
- **identifier** (opcional, default: None): Id del `theme` que se quiere federar, en el catálogo de origen.
- **label** (opcional, default: None): label del `theme` que se quiere federar, en el catálogo de origen.

Debe pasarse por lo menos uno de los 2 parámetros opcionales. En caso de que se provean los 2, se prioriza el
identifier sobre el label.

## Uso

### Setup
Expand Down
14 changes: 12 additions & 2 deletions pydatajson/ckan_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
# -*- coding: utf-8 -*-
import json
import re
import sys
from datetime import time
from dateutil import parser, tz
from .helpers import title_to_name
Expand Down Expand Up @@ -109,10 +108,21 @@ def map_distributions_to_resources(distributions, catalog_id=None):
resource['mimetype'] = distribution.get('mediaType')
resource['size'] = distribution.get('byteSize')
resource['accessURL'] = distribution.get('accessURL')
resource['fileName'] = distribution.get('fileName')
fileName = distribution.get('fileName')
if fileName:
resource['fileName'] = fileName
dist_fields = distribution.get('field')
if dist_fields:
resource['attributesDescription'] = json.dumps(dist_fields)
resources.append(resource)

return resources


def map_theme_to_group(theme):

return {
"name": title_to_name(theme.get('id') or theme['label']),
"title": theme.get('label'),
"description": theme.get('description'),
}
22 changes: 20 additions & 2 deletions pydatajson/federation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from __future__ import print_function
from ckanapi import RemoteCKAN
from ckanapi.errors import NotFound
from .ckan_utils import map_dataset_to_package
from .ckan_utils import map_dataset_to_package, map_theme_to_group
from .search import get_datasets


Expand All @@ -23,7 +23,6 @@ def push_dataset_to_ckan(catalog, owner_org, dataset_origin_identifier, portal_u
demote_superThemes(bool): Si está en true, los ids de los super themes del dataset, se propagan como grupo.
demote_themes(bool): Si está en true, los labels de los themes del dataset, pasan a ser tags. Sino,
se pasan como grupo.
Returns:
str: El id del dataset en el catálogo de destino.
"""
Expand Down Expand Up @@ -103,3 +102,22 @@ def remove_datasets_from_ckan(portal_url, apikey, filter_in=None, filter_out=Non

for identifier in identifiers:
ckan_portal.call_action('dataset_purge', data_dict={'id': identifier})


def push_theme_to_ckan(catalog, portal_url, apikey, identifier=None, label=None):
"""Escribe la metadata de un theme en el portal pasado por parámetro.
Args:
catalog (DataJson): El catálogo de origen que contiene el theme.
portal_url (str): La URL del portal CKAN de destino.
apikey (str): La apikey de un usuario con los permisos que le permitan crear o actualizar el dataset.
identifier (str): El identificador para buscar el theme en la taxonomia.
label (str): El label para buscar el theme en la taxonomia.
Returns:
str: El name del theme en el catálogo de destino.
"""
ckan_portal = RemoteCKAN(portal_url, apikey=apikey)
theme = catalog.get_theme(identifier=identifier, label=label)
group = map_theme_to_group(theme)
pushed_group = ckan_portal.call_action('group_create', data_dict=group)
return pushed_group['name']
57 changes: 52 additions & 5 deletions tests/test_ckan_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,8 @@

import unittest
import os
import json
import re
import sys
from dateutil import parser, tz
from .context import pydatajson
from pydatajson.ckan_utils import map_dataset_to_package, map_distributions_to_resources, convert_iso_string_to_utc
from pydatajson.ckan_utils import *
from pydatajson.helpers import title_to_name
SAMPLES_DIR = os.path.join("tests", "samples")

Expand Down Expand Up @@ -216,6 +212,57 @@ def test_resources_extra_attributes_are_created_correctly(self):
self.assertIsNone(resource.get('attributesDescription'))


class ThemeConversionTests(unittest.TestCase):

@classmethod
def get_sample(cls, sample_filename):
return os.path.join(SAMPLES_DIR, sample_filename)

@classmethod
def setUpClass(cls):
catalog = pydatajson.DataJson(cls.get_sample('full_data.json'))
cls.theme = catalog.get_theme(identifier='adjudicaciones')

def test_all_attributes_are_replicated_if_present(self):
group = map_theme_to_group(self.theme)
self.assertEqual('adjudicaciones', group['name'])
self.assertEqual('Adjudicaciones', group['title'])
self.assertEqual('Datasets sobre licitaciones adjudicadas.', group['description'])

def test_label_is_used_as_name_if_id_not_present(self):
missing_id = dict(self.theme)
missing_id['label'] = u'#Will be used as name#'
missing_id.pop('id')
group = map_theme_to_group(missing_id)
self.assertEqual('will-be-used-as-name', group['name'])
self.assertEqual('#Will be used as name#', group['title'])
self.assertEqual('Datasets sobre licitaciones adjudicadas.', group['description'])

def test_theme_missing_label(self):
missing_label = dict(self.theme)
missing_label.pop('label')
group = map_theme_to_group(missing_label)
self.assertEqual('adjudicaciones', group['name'])
self.assertIsNone(group.get('title'))
self.assertEqual('Datasets sobre licitaciones adjudicadas.', group['description'])

def test_theme_missing_description(self):
missing_description = dict(self.theme)
missing_description.pop('description')
group = map_theme_to_group(missing_description)
self.assertEqual('adjudicaciones', group['name'])
self.assertEqual('Adjudicaciones', group['title'])
self.assertIsNone(group['description'])

def test_id_special_characters_are_removed(self):
special_char_id = dict(self.theme)
special_char_id['id'] = u'#Théme& $id?'
group = map_theme_to_group(special_char_id)
self.assertEqual('theme-id', group['name'])
self.assertEqual('Adjudicaciones', group['title'])
self.assertEqual('Datasets sobre licitaciones adjudicadas.', group['description'])


class DatetimeConversionTests(unittest.TestCase):

def test_timezones_are_handled_correctly(self):
Expand Down
41 changes: 39 additions & 2 deletions tests/test_federation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
import unittest
import os
import re
import sys

try:
from mock import patch, MagicMock
except ImportError:
from unittest.mock import patch, MagicMock

from .context import pydatajson
from pydatajson.federation import push_dataset_to_ckan, remove_datasets_from_ckan
from pydatajson.federation import push_dataset_to_ckan, remove_datasets_from_ckan, push_theme_to_ckan
from ckanapi.errors import NotFound

SAMPLES_DIR = os.path.join("tests", "samples")
Expand Down Expand Up @@ -215,3 +215,40 @@ def test_remove_through_filters_and_organization(self, mock_portal, mock_search)
'portal', 'key', only_time_series=True, organization='some_org')
mock_portal.return_value.call_action.assert_called_with(
'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)

@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'})
result = push_theme_to_ckan(self.catalog, 'portal_url', 'apikey', 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'})
result = push_theme_to_ckan(self.catalog, 'portal_url', 'apikey', 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'})
group = {'name': u'contrataciones',
'title': u'Contrataciones',
'description': u'Datasets sobre contrataciones.'}
push_theme_to_ckan(self.catalog, 'portal_url', 'apikey', identifier='contrataciones')
mock_portal.return_value.call_action.assert_called_once_with('group_create', data_dict=group)

0 comments on commit c2a5c50

Please sign in to comment.