Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions ckanapi/cli/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ def action(ckan, arguments, stdin=None):
for kv in arguments['KEY=STRING']:
if hasattr(kv, 'decode'):
kv = kv.decode('utf-8')
skey, p, svalue = kv.partition('=')
jkey, p, jvalue = kv.partition(':')
fkey, p, fvalue = kv.partition('@')
skey, _, svalue = kv.partition('=')
jkey, _, jvalue = kv.partition(':')
fkey, _, fvalue = kv.partition('@')
if len(jkey) > len(skey) < len(fkey):
action_args[skey] = svalue
elif len(skey) > len(jkey) < len(fkey):
Expand Down
8 changes: 1 addition & 7 deletions ckanapi/cli/delete.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,8 @@
import json
from datetime import datetime
from itertools import chain
import re
try:
from urllib.parse import urlparse
except ImportError:
from urlparse import urlparse

from ckanapi.errors import (NotFound, NotAuthorized, ValidationError,
SearchIndexError)
from ckanapi.errors import NotFound, NotAuthorized
from ckanapi.cli import workers
from ckanapi.cli.utils import completion_stats, compact_json, quiet_int_pipe

Expand Down
15 changes: 10 additions & 5 deletions ckanapi/cli/dump.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
from datetime import datetime
import os

from ckanapi.errors import (CKANAPIError, NotFound, NotAuthorized, ValidationError,
SearchIndexError)
from ckanapi.errors import CKANAPIError, NotFound, NotAuthorized
from ckanapi.cli import workers
from ckanapi.cli.utils import completion_stats, compact_json, \
quiet_int_pipe
from ckanapi.datapackage import create_datapackage, \
populate_datastore_res_fields
from ckanapi.const import API_KEY_HEADER_NAME


def dump_things(ckan, thing, arguments,
Expand Down Expand Up @@ -107,7 +107,13 @@ def dump_things(ckan, thing, arguments,
datapackages_path = arguments['--datapackages']
apikey = arguments['--apikey']
if datapackages_path:
create_datapackage(record, datapackages_path, stderr, apikey)
create_datapackage(
record,
datapackages_path,
stderr,
apikey,
ckan.apikey_header_name or API_KEY_HEADER_NAME,
)

# keep the output in the same order as names
while expecting_number in results:
Expand Down Expand Up @@ -167,7 +173,7 @@ def reply(error, record=None):
for line in iter(stdin.readline, b''):
try:
name = json.loads(line.decode('utf-8'))
except UnicodeDecodeError as e:
except UnicodeDecodeError:
reply('UnicodeDecodeError')
continue

Expand Down Expand Up @@ -238,4 +244,3 @@ def populate_res_views(ckan, res):
if not views:
return # return if the resource views list is empty
res['resource_views'] = views

19 changes: 11 additions & 8 deletions ckanapi/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,43 @@
ckanapi action ACTION_NAME
[(KEY=STRING | KEY:JSON | KEY@FILE ) ... | -i | -I JSON_INPUT]
[-j | -J] [-P PROFILE ]
[[-c CONFIG] [-u USER] | -r SITE_URL [-a APIKEY] [-g] [--insecure]]
[[-c CONFIG] [-u USER] | -r SITE_URL [-a APIKEY] [-A APIKEY_HEADER_NAME] [-g] [--insecure]]
ckanapi batch [-I JSONL_INPUT] [-s START] [-m MAX] [--local-files]
[-p PROCESSES] [-l LOG_FILE] [-qwz]
[[-c CONFIG] [-u USER] | -r SITE_URL [-a APIKEY] [--insecure]]
[[-c CONFIG] [-u USER] | -r SITE_URL [-a APIKEY] [-A APIKEY_HEADER_NAME] [-g] [--insecure]]
ckanapi delete (datasets | groups | organizations | users | related)
(ID_OR_NAME ... | [-I JSONL_INPUT] [-s START] [-m MAX])
[-p PROCESSES] [-l LOG_FILE] [-qwz]
[[-c CONFIG] [-u USER] | -r SITE_URL [-a APIKEY] [--insecure]]
[[-c CONFIG] [-u USER] | -r SITE_URL [-a APIKEY] [-A APIKEY_HEADER_NAME] [-g] [--insecure]]
ckanapi dump (datasets | groups | organizations | users | related)
(ID_OR_NAME ... | --all) ([-O JSONL_OUTPUT] | [-D DIRECTORY])
[-p PROCESSES] [-dqwzRU]
[[-c CONFIG] [-u USER] | -r SITE_URL [-a APIKEY] [-g] [--insecure]]
[[-c CONFIG] [-u USER] | -r SITE_URL [-a APIKEY] [-A APIKEY_HEADER_NAME] [-g] [--insecure]]
ckanapi load datasets
[--upload-resources] [-I JSONL_INPUT] [-s START] [-m MAX]
[-p PROCESSES] [-l LOG_FILE] [-n | -o] [-qwz]
[[-c CONFIG] [-u USER] | -r SITE_URL [-a APIKEY] [--insecure]]
[[-c CONFIG] [-u USER] | -r SITE_URL [-a APIKEY] [-A APIKEY_HEADER_NAME] [-g] [--insecure]]
ckanapi load (groups | organizations)
[--upload-logo] [-I JSONL_INPUT] [-s START] [-m MAX]
[-p PROCESSES] [-l LOG_FILE] [-n | -o] [-qwzU]
[[-c CONFIG] [-u USER] | -r SITE_URL [-a APIKEY] [--insecure]]
[[-c CONFIG] [-u USER] | -r SITE_URL [-a APIKEY] [-A APIKEY_HEADER_NAME] [-g] [--insecure]]
ckanapi load (users | related)
[-I JSONL_INPUT] [-s START] [-m MAX] [-p PROCESSES] [-l LOG_FILE]
[-n | -o] [-qwz]
[[-c CONFIG] [-u USER] | -r SITE_URL [-a APIKEY] [--insecure]]
[[-c CONFIG] [-u USER] | -r SITE_URL [-a APIKEY] [-A APIKEY_HEADER_NAME] [-g] [--insecure]]
ckanapi search datasets
[(KEY=STRING | KEY:JSON ) ... | -i | -I JSON_INPUT]
[-O JSONL_OUTPUT] [-z]
[[-c CONFIG] [-u USER] | -r SITE_URL [-a APIKEY] [-g] [--insecure]]
[[-c CONFIG] [-u USER] | -r SITE_URL [-a APIKEY] [-A APIKEY_HEADER_NAME] [-g] [--insecure]]
ckanapi (-h | --help)
ckanapi --version

Options:
-h --help show this screen
--version show version
-a --apikey=APIKEY API key to use for remote actions
-A --apikey-header-name=APIKEY_HEADER_NAME
API key header name to use for remote actions
--all all the things
-c --config=CONFIG CKAN configuration file for local actions,
defaults to $CKAN_INI or development.ini
Expand Down Expand Up @@ -129,6 +131,7 @@ def main(running_with_paster=False):
if arguments['--remote']:
ckan = RemoteCKAN(arguments['--remote'],
apikey=arguments['--apikey'],
apikey_header_name=arguments['--apikey-header-name'],
user_agent="ckanapi-cli/{version} (+{url})".format(
version=__version__,
url='https://github.com/open-data/ckanapi'),
Expand Down
2 changes: 1 addition & 1 deletion ckanapi/cli/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import json
from os.path import expanduser

from ckanapi.cli.utils import compact_json, pretty_json
from ckanapi.cli.utils import compact_json
from ckanapi.errors import CLIError


Expand Down
8 changes: 3 additions & 5 deletions ckanapi/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def is_file_like(v):
isinstance(v, tuple) and len(v) >= 2 and hasattr(v[1], 'read'))


def prepare_action(action, data_dict=None, apikey=None, files=None,
def prepare_action(action, data_dict=None, apikey=None, apikey_header_name=None, files=None,
base_url='api/action/'):
"""
Return action_url, data_json, http_headers
Expand All @@ -84,10 +84,8 @@ def prepare_action(action, data_dict=None, apikey=None, files=None,
else:
data_dict = json.dumps(data_dict).encode('ascii')
headers['Content-Type'] = 'application/json'
if apikey:
apikey = str(apikey)
headers['X-CKAN-API-Key'] = apikey
headers['Authorization'] = apikey
if apikey and apikey_header_name:
headers[apikey_header_name] = apikey
url = base_url + action
return url, data_dict, headers

Expand Down
1 change: 1 addition & 0 deletions ckanapi/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
API_KEY_HEADER_NAME = "Authorization"
14 changes: 8 additions & 6 deletions ckanapi/datapackage.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@
}


def create_resource(resource, filename, datapackage_dir, stderr, apikey):
def create_resource(resource, filename, datapackage_dir, stderr, apikey, apikey_header_name):
'''Downloads the resource['url'] to disk.
'''
path = os.path.join('data', filename)
headers = {}
headers['X-CKAN-API-Key'] = apikey
headers['Authorization'] = apikey
headers[apikey_header_name] = apikey

try:
r = requests.get(resource['url'], headers=headers, stream=True)
Expand All @@ -34,14 +33,16 @@ def create_resource(resource, filename, datapackage_dir, stderr, apikey):
except requests.ConnectionError:
stderr.write('URL {url} refused connection. The resource will not be downloaded\n'.format(url=resource['url']))
except requests.exceptions.RequestException as e:
print(e.args)

stderr.write(str(e.args[0]) if len(e.args) > 0 else '')
stderr.write('\n')
except Exception as e:
stderr.write(str(e.args[0]) if len(e.args) > 0 else '')
return resource


def create_datapackage(record, base_path, stderr, apikey):
def create_datapackage(record, base_path, stderr, apikey, apikey_header_name):
# TODO: how are we going to handle which resources to
# leave alone? They're very inconsistent in some instances
# And I can't imagine anyone wants to download a copy
Expand All @@ -67,8 +68,9 @@ def create_datapackage(record, base_path, stderr, apikey):
filename = resource_filename(dres)

# download the resource
cres = \
create_resource(resource, filename, datapackage_dir, stderr, apikey)
cres = create_resource(
resource, filename, datapackage_dir, stderr, apikey, apikey_header_name
)
dres['path'] = 'data/' + filename

populate_schema_from_datastore(cres, dres)
Expand Down
47 changes: 35 additions & 12 deletions ckanapi/remoteckan.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
try:
from urllib2 import Request, urlopen, HTTPError
from urlparse import urlparse
except ImportError:
from urllib.request import Request, urlopen, HTTPError
from urllib.parse import urlparse

import os

import requests

from ckanapi.errors import CKANAPIError
from ckanapi.common import (ActionShortcut, prepare_action,
reverse_apicontroller_action)
from ckanapi.version import __version__
import os
from ckanapi.const import API_KEY_HEADER_NAME


# add your sites to remove parallel limits on ckanapi cli
MY_SITES = ['localhost', '127.0.0.1', '[::1]']
Expand All @@ -21,27 +24,34 @@
# add your site above instead of changing this
PARALLEL_LIMIT = os.getenv('CKANAPI_PARALLEL_LIMIT', default = 3)

import requests


class RemoteCKAN(object):
"""
An interface to the the CKAN API actions on a remote CKAN instance.

:param address: the web address of the CKAN instance, e.g.
'http://demo.ckan.org', stored as self.address
:param apikey: the API key to pass as an 'X-CKAN-API-Key' header
when actions are called, stored as self.apikey
:param apikey: the API key to pass as an authorization header
:param apikey_header_name: the header name to use for the API key
:param user_agent: the User-agent to report when making requests
:param get_only: only use GET requests (default: False)
:param session: session to use (default: None)
"""

base_url = 'api/action/'

def __init__(self, address, apikey=None, user_agent=None, get_only=False, session=None):
def __init__(
self,
address,
apikey=None,
apikey_header_name=API_KEY_HEADER_NAME,
user_agent=None,
get_only=False,
session=None,
):
self.address = address
self.apikey = apikey
self.apikey_header_name = apikey_header_name
self.get_only = get_only
self.session = session
if not user_agent:
Expand All @@ -60,8 +70,16 @@ def __init__(self, address, apikey=None, user_agent=None, get_only=False, sessio
# add your sites to MY_SITES above instead of removing this
self.parallel_limit = PARALLEL_LIMIT

def call_action(self, action, data_dict=None, context=None, apikey=None,
files=None, requests_kwargs=None):
def call_action(
self,
action,
data_dict=None,
context=None,
apikey=None,
apikey_header_name=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,
Expand All @@ -83,8 +101,13 @@ def call_action(self, action, data_dict=None, context=None, apikey=None,
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,
base_url=self.base_url)
action,
data_dict,
apikey or self.apikey,
apikey_header_name or self.apikey_header_name,
files,
base_url=self.base_url,
)
headers['User-Agent'] = self.user_agent
url = self.address.rstrip('/') + '/' + url
requests_kwargs = requests_kwargs or {}
Expand Down
15 changes: 10 additions & 5 deletions ckanapi/testappckan.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,22 @@
from ckanapi.errors import CKANAPIError
from ckanapi.common import (ActionShortcut, prepare_action,
reverse_apicontroller_action)
from ckanapi.const import API_KEY_HEADER_NAME


class TestAppCKAN(object):
"""
An interface to the the CKAN API actions on a paste TestApp

:param test_app: the paste.fixture.TestApp instance, stored as
self.test_app
:param apikey: the API key to pass as an 'X-CKAN-API-Key' header
when actions are called, stored as self.apikey
:param apikey: the API key to pass as an authorization header
:param apikey_header_name: the header name to use for the API key
"""
def __init__(self, test_app, apikey=None):
def __init__(self, test_app, apikey=None, apikey_header_name=API_KEY_HEADER_NAME):
self.test_app = test_app
self.apikey = apikey
self.apikey_header_name = apikey_header_name
self.action = ActionShortcut(self)

def call_action(self, action, data_dict=None, context=None, apikey=None,
Expand All @@ -35,8 +38,10 @@ def call_action(self, action, data_dict=None, context=None, apikey=None,
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)

url, data, headers = prepare_action(
action, data_dict, apikey or self.apikey, self.apikey_header_name, files
)

kwargs = {}
if files:
Expand Down
2 changes: 0 additions & 2 deletions ckanapi/tests/mock/mock_ckan.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import json
import cgi
import csv
from wsgiref.util import setup_testing_defaults
from wsgiref.simple_server import make_server
try:
from cStringIO import StringIO
Expand Down Expand Up @@ -73,4 +72,3 @@ def mock_ckan(environ, start_response):

httpd = make_server('localhost', 8901, mock_ckan)
httpd.serve_forever()

Loading