Skip to content
Merged
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
54 changes: 54 additions & 0 deletions .github/workflows/publish-pypi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: Publish to PyPI

# Publish to PyPI when a tag is pushed
on:
push:
tags:
- 'ckanapi-**'

jobs:
build:
if: github.repository == 'ckan/ckanapi'
name: Build distribution
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install pypa/build
run: python3 -m pip install build --user
- name: Build a binary wheel and a source tarball
run: python3 -m build
- name: Store the distribution packages
uses: actions/upload-artifact@v4
with:
name: python-package-distributions
path: dist/

publish-to-pypi:
name: Publish Python distribution on PyPI
needs:
- build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/ckanapi
permissions:
id-token: write
steps:
- name: Download all the dists
uses: actions/download-artifact@v4
with:
name: python-package-distributions
path: dist/
- name: Publish distribution to PyPI
uses: pypa/gh-action-pypi-publish@release/v1

publishSkipped:
if: github.repository != 'ckan/ckanapi'
runs-on: ubuntu-latest
steps:
- run: |
echo "## Skipping PyPI publish on downstream repository" >> $GITHUB_STEP_SUMMARY
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ jobs:
test:
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
runs-on: ubuntu-latest
container:
# INFO: python 2 is no longer supported in
Expand Down
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,9 +65,8 @@ For this example, we use --insecure as the CKAN demo uses a
self-signed certificate.

Local CKAN actions may be run by specifying the config file with -c.
If no remote server or config file is specified the CLI will look for
a development.ini file in the current directory, much like paster
commands.
If no remote server or config file is specified, the CLI will look for
a ckan.ini file in the current directory, much like `ckan` commands.

Local CKAN actions are performed by the site user (default system
administrator) when -u is not specified.
Expand Down Expand Up @@ -497,7 +496,7 @@ groups = demo.action.group_list(id='data-explorer')

To run the tests:

python setup.py test
python -m unittest discover


## License
Expand Down
2 changes: 1 addition & 1 deletion ckanapi/cli/action.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def action(ckan, arguments, stdin=None):
action_args = {}
with open(expanduser(arguments['--input'])) as in_f:
action_args = json.loads(
in_f.read().decode('utf-8') if sys.version_info.major == 2 else in_f.read())
in_f.read())
else:
action_args = {}
for kv in arguments['KEY=STRING']:
Expand Down
13 changes: 4 additions & 9 deletions ckanapi/cli/batch.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@
from ckanapi.cli import workers
from ckanapi.cli.utils import completion_stats, compact_json, quiet_int_pipe

try:
unicode
except NameError:
unicode = str


def batch_actions(ckan, arguments,
worker_pool=None, stdin=None, stdout=None, stderr=None):
Expand Down Expand Up @@ -143,7 +138,7 @@ def reply(action, error, response):
obj = json.loads(line.decode('utf-8'))
except UnicodeDecodeError as e:
obj = None
reply('read', 'UnicodeDecodeError', unicode(e))
reply('read', 'UnicodeDecodeError', str(e))
continue

requests_kwargs = None
Expand All @@ -163,7 +158,7 @@ def reply(action, error, response):
reply('read', 'IOError', {
'parameter':fkey,
'file_name':fvalue,
'error':unicode(e.args[1]),
'error':str(e.args[1]),
})
continue

Expand All @@ -173,9 +168,9 @@ def reply(action, error, response):
except ValidationError as e:
reply(action, 'ValidationError', e.error_dict)
except SearchIndexError as e:
reply(action, 'SearchIndexError', unicode(e))
reply(action, 'SearchIndexError', str(e))
except NotAuthorized as e:
reply(action, 'NotAuthorized', unicode(e))
reply(action, 'NotAuthorized', str(e))
except NotFound:
reply(action, 'NotFound', obj)
else:
Expand Down
2 changes: 1 addition & 1 deletion ckanapi/cli/ckan_click.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ def api(context, args):
from ckanapi.cli.main import main
import sys
sys.argv[1:] = args
context.exit(main(running_with_paster=True) or 0)
context.exit(main(running_with_ckan_command=True) or 0)
23 changes: 8 additions & 15 deletions ckanapi/cli/delete.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,13 @@
from datetime import datetime
from itertools import chain
import re
try:
from urllib.parse import urlparse
except ImportError:
from urlparse import urlparse
from urllib.parse import urlparse

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

try:
unicode
except NameError:
unicode = str

def delete_things(ckan, thing, arguments,
worker_pool=None, stdin=None, stdout=None, stderr=None):
Expand Down Expand Up @@ -137,20 +130,20 @@ def extract_ids_or_names(line):
except ValueError:
return [line.strip()] # 5
if isinstance(j, list) and all(
isinstance(e, unicode) for e in j):
isinstance(e, str) for e in j):
return j # 4
elif isinstance(j, unicode):
elif isinstance(j, str):
return [j] # 3
elif isinstance(j, dict):
if 'id' in j and isinstance(j['id'], unicode):
if 'id' in j and isinstance(j['id'], str):
return [j['id']] # 1
if 'name' in j and isinstance(j['name'], unicode):
if 'name' in j and isinstance(j['name'], str):
return [j['name']] # 1 again
if 'results' in j and isinstance(j['results'], list):
out = []
for r in j['results']:
if (not isinstance(r, dict) or 'id' not in r or
not isinstance(r['id'], unicode)):
not isinstance(r['id'], str)):
break
out.append(r['id'])
else:
Expand Down Expand Up @@ -203,7 +196,7 @@ def reply(error, response):
try:
name = json.loads(line.decode('utf-8'))
except UnicodeDecodeError as e:
reply('UnicodeDecodeError', unicode(e))
reply('UnicodeDecodeError', str(e))
continue

try:
Expand All @@ -213,7 +206,7 @@ def reply(error, response):
ckan.call_action(thing_delete, {'id': name},
requests_kwargs=requests_kwargs)
except NotAuthorized as e:
reply('NotAuthorized', unicode(e))
reply('NotAuthorized', str(e))
except NotFound:
reply('NotFound', name)
else:
Expand Down
20 changes: 6 additions & 14 deletions ckanapi/cli/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,13 @@
import requests
from datetime import datetime
import re
try:
from urllib.parse import urlparse
except ImportError:
from urlparse import urlparse
from urllib.parse import urlparse

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

try:
unicode
except NameError:
unicode = str


def load_things(ckan, thing, arguments,
worker_pool=None, stdin=None, stdout=None, stderr=None):
Expand Down Expand Up @@ -167,7 +159,7 @@ def reply(action, error, response):
obj = json.loads(line.decode('utf-8'))
except UnicodeDecodeError as e:
obj = None
reply('read', 'UnicodeDecodeError', unicode(e))
reply('read', 'UnicodeDecodeError', str(e))
continue

requests_kwargs = None
Expand All @@ -191,7 +183,7 @@ def reply(action, error, response):
except NotFound:
pass
except NotAuthorized as e:
reply('show', 'NotAuthorized', unicode(e))
reply('show', 'NotAuthorized', str(e))
continue
name = obj.get('name')
if not existing and name:
Expand All @@ -201,7 +193,7 @@ def reply(action, error, response):
except NotFound:
pass
except NotAuthorized as e:
reply('show', 'NotAuthorized', unicode(e))
reply('show', 'NotAuthorized', str(e))
continue

if existing:
Expand Down Expand Up @@ -233,9 +225,9 @@ def reply(action, error, response):
except ValidationError as e:
reply(act, 'ValidationError', e.error_dict)
except SearchIndexError as e:
reply(act, 'SearchIndexError', unicode(e))
reply(act, 'SearchIndexError', str(e))
except NotAuthorized as e:
reply(act, 'NotAuthorized', unicode(e))
reply(act, 'NotAuthorized', str(e))
except NotFound:
reply(act, 'NotFound', obj)
else:
Expand Down
20 changes: 2 additions & 18 deletions ckanapi/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@
import sys
import os
from docopt import docopt
from pkg_resources import load_entry_point
import subprocess

from ckanapi.version import __version__
Expand All @@ -105,25 +104,19 @@

# explicit logger namespace for easy logging handlers
log = getLogger('ckan.ckanapi')
PYTHON2 = str is bytes

def parse_arguments():
# docopt is awesome
return docopt(__doc__, version=__version__)


def main(running_with_paster=False):
def main(running_with_ckan_command=False):
"""
ckanapi command line entry point
"""
arguments = parse_arguments()

if not running_with_paster and not arguments['--remote']:
if PYTHON2:
ckan_ini = os.environ.get('CKAN_INI')
if ckan_ini and not arguments['--config']:
sys.argv[1:1] = ['--config', ckan_ini]
return _switch_to_paster(arguments)
if not running_with_ckan_command and not arguments['--remote']:
return _switch_to_ckan_click(arguments)

if arguments['--remote']:
Expand Down Expand Up @@ -196,15 +189,6 @@ def main(running_with_paster=False):
assert 0, arguments # we shouldn't be here


def _switch_to_paster(arguments):
"""
** legacy python2-only **
With --config we switch to the paster command version of the cli
"""
sys.argv[1:1] = ["--plugin=ckanapi", "ckanapi"]
sys.exit(load_entry_point('PasteScript', 'console_scripts', 'paster')())


def _switch_to_ckan_click(arguments):
"""
Local commands must be run through ckan CLI to set up environment
Expand Down
29 changes: 0 additions & 29 deletions ckanapi/cli/paster.py

This file was deleted.

2 changes: 1 addition & 1 deletion ckanapi/cli/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def search_datasets(ckan, arguments, stdin=None, stdout=None, stderr=None):
action_args = {}
with open(expanduser(arguments['--input'])) as in_f:
action_args = json.loads(
in_f.read().decode('utf-8') if sys.version_info.major == 2 else in_f.read())
in_f.read())
else:
action_args = {}
for kv in arguments['KEY=STRING']:
Expand Down
3 changes: 1 addition & 2 deletions ckanapi/datapackage.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import requests
import json

import six
import slugify

from ckanapi.cli.utils import pretty_json
Expand Down Expand Up @@ -170,7 +169,7 @@ def _convert_to_datapackage_resource(resource_dict):
resource['name'] = resource_dict['id']

schema = resource_dict.get('schema')
if isinstance(schema, six.string_types):
if isinstance(schema, str):
try:
resource['schema'] = json.loads(schema)
except ValueError:
Expand Down
8 changes: 2 additions & 6 deletions ckanapi/remoteckan.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
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
from urllib.request import Request, urlopen, HTTPError
from urllib.parse import urlparse

from ckanapi.errors import CKANAPIError
from ckanapi.common import (ActionShortcut, prepare_action,
Expand Down
Loading