Ejemplo n.º 1
0
    def call_action(self, action, data_dict=None, context=None, apikey=None,
            files=None, requests_kwargs=None):
        """
        :param action: the action name, e.g. 'package_create'
        :param data_dict: the dict to pass to the action as JSON,
                          defaults to {}
        :param context: always set to None for RemoteCKAN
        :param apikey: API key for authentication
        :param files: None or {field-name: file-to-be-sent, ...}

        This function parses the response from the server as JSON and
        returns the decoded value.  When an error is returned this
        function will convert it back to an exception that matches the
        one the action function itself raised.
        """
        if context:
            raise CKANAPIError("RemoteCKAN.call_action does not support "
                "use of context parameter, use apikey instead")
        if files and self.get_only:
            raise CKANAPIError("RemoteCKAN: files may not be sent when "
                "get_only is True")
        url, data, headers = prepare_action(
            action, data_dict, apikey or self.apikey, files)
        headers['User-Agent'] = self.user_agent
        url = self.address.rstrip('/') + '/' + url
        requests_kwargs = requests_kwargs or {}
        if self.get_only:
            status, response = self._request_fn_get(url, data_dict, headers, requests_kwargs)
        else:
            status, response = self._request_fn(url, data, headers, files, requests_kwargs)
        return reverse_apicontroller_action(url, status, response)
Ejemplo n.º 2
0
    def call_action(self,
                    action,
                    data_dict=None,
                    context=None,
                    apikey=None,
                    files=None,
                    requests_kwargs=None,
                    progress=None):
        """
        :param action: the action name, e.g. 'package_create'
        :param data_dict: the dict to pass to the action as JSON,
                          defaults to {}
        :param context: always set to None for RemoteCKAN
        :param apikey: API key for authentication
        :param files: None or {field-name: file-to-be-sent, ...}
        :param progress: A callable that takes an instance of
            :class:`requests_toolbelt.MultipartEncoder` as parameter and returns
            a callback funtion. The callback function will be called every time
            data is read from the file-to-be-sent and it will be passed the
            instance of :class:`requests_toolbelt.MultipartEncoderMonitor`. This
            monitor has the attribute `bytes_read` that can be used to display
            a progress bar. An example is implemented in ckanapi.cli.

        This function parses the response from the server as JSON and
        returns the decoded value.  When an error is returned this
        function will convert it back to an exception that matches the
        one the action function itself raised.
        """
        if context:
            raise CKANAPIError("RemoteCKAN.call_action does not support "
                               "use of context parameter, use apikey instead")
        if files and self.get_only:
            raise CKANAPIError("RemoteCKAN: files may not be sent when "
                               "get_only is True")
        url, data, headers = prepare_action(action, data_dict, apikey
                                            or self.apikey, files)
        headers['User-Agent'] = self.user_agent
        url = self.address.rstrip('/') + '/' + url
        requests_kwargs = requests_kwargs or {}
        if not self.session:
            self.session = requests.Session()
        if self.get_only:
            status, response = self._request_fn_get(url, data_dict, headers,
                                                    requests_kwargs)
        else:
            status, response = self._request_fn(url, data, headers, files,
                                                requests_kwargs, progress)
        return reverse_apicontroller_action(url, status, response)
Ejemplo n.º 3
0
    def test_restore_catalog_failing_destination_portal(
            self, mock_action, mock_push_thm, mock_push_dst):

        identifiers = [ds['identifier'] for ds in self.catalog.datasets]
        mock_action.return_value.organization_list.return_value = \
            ['org_1', 'org_2']
        mock_action.return_value.organization_show.side_effect = [
            {
                'packages': [{
                    'id': identifiers[0]
                }]
            },
            {
                'packages': [{
                    'id': identifiers[1]
                }]
            },
        ]
        mock_push_dst.side_effect = CKANAPIError('Broken destination portal')

        pushed = restore_catalog_to_ckan(self.catalog, 'origin', 'destination',
                                         'apikey')
        mock_push_dst.assert_any_call(self.catalog, 'org_1', identifiers[0],
                                      'destination', 'apikey', None, False,
                                      False, None, None)
        mock_push_dst.assert_any_call(self.catalog, 'org_2', identifiers[1],
                                      'destination', 'apikey', None, False,
                                      False, None, None)
        expected = {'org_1': [], 'org_2': []}
        self.assertDictEqual(expected, pushed)
Ejemplo n.º 4
0
 def _request_fn(self, url, data, headers, files, requests_kwargs,
                 progress):
     if files:  # use streaming
         newfiles = dict([(k, (getattr(files[k], 'name',
                                       'upload_filename'), files[k]))
                          for k in files])
         intersect = set(data.keys()) & set(newfiles.keys())
         if intersect:
             raise CKANAPIError('field-name for files ("{}")'.format(
                 ', '.join(list(intersect))) +
                                ' cannot also be field name in data_dict.')
         data.update(newfiles)
         m = MultipartEncoder(data)
         if progress:
             m = MultipartEncoderMonitor(m, progress(m))
         headers.update({'Content-Type': m.content_type})
         r = self.session.post(url,
                               data=m,
                               headers=headers,
                               allow_redirects=False,
                               **requests_kwargs)
     else:
         r = self.session.post(url,
                               data=data,
                               headers=headers,
                               files=files,
                               allow_redirects=False,
                               **requests_kwargs)
     # allow_redirects=False because: if a post is redirected (e.g. 301 due
     # to a http to https redirect), then the second request is made to the
     # new URL, but *without* the data. This gives a confusing "No request
     # body data" error. It is better to just return the 301 to the user, so
     # we disallow redirects.
     return r.status_code, r.text
Ejemplo n.º 5
0
 def test_restore_failing_organization_to_ckan(self, mock_push_thm,
                                               mock_push_dst):
     # Continua subiendo el segundo dataset a pesar que el primero falla
     effects = [
         CKANAPIError('broken dataset'),
         self.catalog.datasets[1]['identifier']
     ]
     mock_push_dst.side_effect = effects
     identifiers = [ds['identifier'] for ds in self.catalog.datasets]
     pushed = restore_organization_to_ckan(self.catalog, 'owner_org',
                                           'portal', 'apikey', identifiers)
     self.assertEqual([identifiers[1]], pushed)
     mock_push_thm.assert_called_with(self.catalog, 'portal', 'apikey')
     mock_push_dst.assert_called_with(self.catalog,
                                      'owner_org',
                                      identifiers[1],
                                      'portal',
                                      'apikey',
                                      catalog_id=None,
                                      demote_superThemes=False,
                                      demote_themes=False,
                                      download_strategy=None,
                                      generate_new_access_url=None,
                                      origin_tz=DEFAULT_TIMEZONE,
                                      dst_tz=DEFAULT_TIMEZONE)
Ejemplo n.º 6
0
def reverse_apicontroller_action(url, status, response):
    """
    Make an API call look like a direct action call by reversing the
    exception -> HTTP response translation that ApiController.action does
    """
    try:
        parsed = json.loads(response)
        if parsed.get('success'):
            return parsed['result']
        if hasattr(parsed, 'get'):
            err = parsed.get('error', {})
        else:
            err = {}
    except (AttributeError, ValueError):
        err = {}

    etype = err.get('__type')
    emessage = err.get('message', '').split(': ', 1)[-1]
    if etype == 'Search Query Error':
        # I refuse to eval(emessage), even if it would be more correct
        raise SearchQueryError(emessage)
    elif etype == 'Search Error':
        # I refuse to eval(emessage), even if it would be more correct
        raise SearchError(emessage)
    elif etype == 'Search Index Error':
        raise SearchIndexError(emessage)
    elif etype == 'Validation Error':
        raise ValidationError(err)
    elif etype == 'Not Found Error':
        raise NotFound(emessage)
    elif etype == 'Authorization Error':
        raise NotAuthorized(err)

    # don't recognize the error
    raise CKANAPIError(repr([url, status, response]))
Ejemplo n.º 7
0
 def test_resource_upload_error(self, mock_portal):
     mock_portal.return_value.action.resource_patch = MagicMock(
         side_effect=CKANAPIError('broken resource'))
     resources = {self.distribution_id: 'tests/samples/resource_sample.csv'}
     res = resources_update('portal', 'key', self.catalog.distributions,
                            resources)
     mock_portal.return_value.action.resource_patch.assert_called_with(
         id=self.distribution_id, resource_type='file.upload', upload=ANY)
     self.assertEqual([], res)
Ejemplo n.º 8
0
 def test_restore_catalog_failing_origin_portal(self, mock_action,
                                                mock_push_thm,
                                                mock_push_dst):
     mock_action.return_value.organization_list.side_effect = \
         CKANAPIError('Broken origin portal')
     pushed = restore_catalog_to_ckan(self.catalog, 'origin', 'destination',
                                      'apikey')
     self.assertDictEqual({}, pushed)
     mock_push_thm.assert_not_called()
     mock_push_dst.assert_not_called()
Ejemplo n.º 9
0
 def test_resource_upload_error(self, mock_portal):
     mock_portal.return_value.action.resource_patch = MagicMock(
         side_effect=CKANAPIError('broken resource'))
     resources = {self.distribution_id: 'tests/samples/resource_sample.csv'}
     res = resources_update('portal', 'key', self.catalog.distributions,
                            resources)
     _, _, kwargs = \
         mock_portal.return_value.action.resource_patch.mock_calls[0]
     self.assertEqual(self.distribution_id, kwargs['id'])
     self.assertEqual('Convocatorias abiertas durante el año 2015',
                      kwargs['name'])
     self.assertEqual('file.upload', kwargs['resource_type'])
     self.assertEqual([], res)
Ejemplo n.º 10
0
 def call_action(self, action, data_dict=None, context=None, apikey=None,
         files=None):
     """
     :param action: the action name, e.g. 'package_create'
     :param data_dict: the dict to pass to the action, defaults to {}
     :param context: an override for the context to use for this action,
                     remember to include a 'user' when necessary
     :param apikey: not supported
     :param files: not supported
     """
     if not data_dict:
         data_dict = []
     if context is None:
         context = self.context
     if apikey:
         # FIXME: allow use of apikey to set a user in context?
         raise CKANAPIError("LocalCKAN.call_action does not support "
             "use of apikey parameter, use context['user'] instead")
     if files:
         raise CKANAPIError("TestAppCKAN.call_action does not support "
             "file uploads, consider contributing it if you need it")
     # copy dicts because actions may modify the dicts they are passed
     return self._get_action(action)(dict(context), dict(data_dict))
Ejemplo n.º 11
0
    def call_action(self,
                    action,
                    data_dict=None,
                    context=None,
                    apikey=None,
                    files=None,
                    requests_kwargs=None):
        """
        :param action: the action name, e.g. 'package_create'
        :param data_dict: the dict to pass to the action, defaults to {}
        :param context: an override for the context to use for this action,
                        remember to include a 'user' when necessary
        :param apikey: not supported
        :param files: None or {field-name: file-to-be-sent, ...}
        :param requests_kwargs: ignored for LocalCKAN (requests not used)
        """
        # copy dicts because actions may modify the dicts they are passed
        # (CKAN...you so crazy)
        data_dict = dict(data_dict or [])
        context = dict(self.context if context is None else context)
        if apikey:
            # FIXME: allow use of apikey to set a user in context?
            raise CKANAPIError(
                "LocalCKAN.call_action does not support "
                "use of apikey parameter, use context['user'] instead")

        to_close = []
        try:
            for fieldname in files or []:
                f = files[fieldname]
                if isinstance(f, tuple):
                    # requests accepts (filename, file...) tuples
                    filename, f = f[:2]
                else:
                    filename = f.name
                try:
                    f.seek(0)
                except (AttributeError, IOError):
                    f = _write_temp_file(f)
                    to_close.append(f)
                field_storage = FieldStorage()
                field_storage.file = f
                field_storage.filename = filename
                data_dict[fieldname] = field_storage

            return self._get_action(action)(context, data_dict)
        finally:
            for f in to_close:
                f.close()
Ejemplo n.º 12
0
    def test_restore_catalog_failing_destination_portal(
            self, mock_action, mock_push_thm, mock_push_dst):

        identifiers = [ds['identifier'] for ds in self.catalog.datasets]
        mock_action.return_value.organization_list.return_value = \
            ['org_1', 'org_2']
        mock_action.return_value.organization_show.side_effect = [
            {
                'packages': [{
                    'id': identifiers[0]
                }]
            },
            {
                'packages': [{
                    'id': identifiers[1]
                }]
            },
        ]
        mock_push_dst.side_effect = CKANAPIError('Broken destination portal')

        pushed = restore_catalog_to_ckan(self.catalog, 'origin', 'destination',
                                         'apikey')
        mock_push_dst.assert_any_call(self.catalog,
                                      'org_1',
                                      identifiers[0],
                                      'destination',
                                      'apikey',
                                      catalog_id=None,
                                      demote_superThemes=False,
                                      demote_themes=False,
                                      download_strategy=None,
                                      generate_new_access_url=None,
                                      origin_tz=DEFAULT_TIMEZONE,
                                      dst_tz=DEFAULT_TIMEZONE)
        mock_push_dst.assert_any_call(self.catalog,
                                      'org_2',
                                      identifiers[1],
                                      'destination',
                                      'apikey',
                                      catalog_id=None,
                                      demote_superThemes=False,
                                      demote_themes=False,
                                      download_strategy=None,
                                      generate_new_access_url=None,
                                      origin_tz=DEFAULT_TIMEZONE,
                                      dst_tz=DEFAULT_TIMEZONE)
        expected = {'org_1': [], 'org_2': []}
        self.assertDictEqual(expected, pushed)
Ejemplo n.º 13
0
 def test_restore_failing_organization_to_ckan(self, mock_push_thm,
                                               mock_push_dst):
     # Continua subiendo el segundo dataset a pesar que el primero falla
     effects = [
         CKANAPIError('broken dataset'),
         self.catalog.datasets[1]['identifier']
     ]
     mock_push_dst.side_effect = effects
     identifiers = [ds['identifier'] for ds in self.catalog.datasets]
     pushed = restore_organization_to_ckan(self.catalog, 'owner_org',
                                           'portal', 'apikey', identifiers)
     self.assertEqual([identifiers[1]], pushed)
     mock_push_thm.assert_called_with(self.catalog, 'portal', 'apikey')
     mock_push_dst.assert_called_with(self.catalog, 'owner_org',
                                      identifiers[1], 'portal', 'apikey',
                                      None, False, False, None, None)
Ejemplo n.º 14
0
    def call_action(self,
                    action,
                    data_dict=None,
                    context=None,
                    apikey=None,
                    files=None):
        """
        :param action: the action name, e.g. 'package_create'
        :param data_dict: the dict to pass to the action as JSON,
                          defaults to {}
        :param context: not supported
        :param files: None or {field-name: file-to-be-sent, ...}

        This function parses the response from the server as JSON and
        returns the decoded value.  When an error is returned this
        function will convert it back to an exception that matches the
        one the action function itself raised.
        """
        if context:
            raise CKANAPIError("TestAppCKAN.call_action does not support "
                               "use of context parameter, use apikey instead")
        url, data, headers = prepare_action(action, data_dict, apikey
                                            or self.apikey, files)

        kwargs = {}
        if files:
            # Convert the list of (fieldname, file_object) tuples into the
            # (fieldname, filename, file_contents) tuples that webtests needs.
            upload_files = []
            for fieldname, file_ in files.items():
                if hasattr(file_, 'name'):
                    filename = os.path.split(file_.name)[1]
                else:
                    filename = fieldname
                upload_files.append((fieldname, filename, file_.read()))
            kwargs['upload_files'] = upload_files

        r = self.test_app.post('/' + url,
                               params=data,
                               headers=headers,
                               expect_errors=True,
                               **kwargs)
        return reverse_apicontroller_action(url, r.status, r.body)