#! /usr/bin/env python # encoding: utf-8 """ WordpressJsonWrapper ~~~~~~~~~~~~~~~~~~~~ This library provides a very thin wrapper around the Wordpress JSON API (WP-API). :copyright: (c) 2015 Stylight GmbH :licence: MIT, see LICENSE for more details. """ import requests import six __version__ = '0.2.4', __author__ = 'Raul Taranu, Dimitar Roustchev' methods = { 'get': 'GET', 'read': 'GET', 'retrieve': 'GET', 'list': 'GET', 'post': 'POST', 'create': 'POST', 'put': 'POST', 'update': 'POST', 'edit': 'POST', 'delete': 'DELETE', } component_conversions = { 'post': 'posts', 'user': 'users', 'taxonomy': 'taxonomies', 'category': 'categories', 'status': 'statuses', 'tag': 'tags', 'type': 'types', } component_expansions = { 'revisions': ['posts', 'revisions'], 'meta': ['posts', 'meta'], } class WordpressError(Exception): """Baseclass for all API errors.""" pass class WordpressAuthenticationError(WordpressError): """Raised if there was a problem authenticating with the API.""" pass class WordpressJsonWrapper(object): def __init__(self, site, user, pwd): self.auth = (user, pwd) self.site = site def __getattr__(self, method_name): def api_method(**kw): return self._request(method_name, **kw) return api_method def _expand_url_components(self, url_components, ids): """ >>> wp = WordpressJsonWrapper(None, None, None) >>> type(wp._expand_url_components(['foo'], {})) is list True >>> wp._expand_url_components(['revisions'], {'post': 3}) ['posts', 'revisions'] """ expanded_url_components = list() if component_expansions.get(url_components[0]): expanded_url_components = component_expansions[url_components[0]] return expanded_url_components return url_components def _get_ids(self, **kw): """ >>> wp = WordpressJsonWrapper(None, None, None) >>> type(wp._get_ids()) is dict True >>> wp._get_ids(test='foo', bar_id=42, foo_id=37)['foo'] 37 >>> wp._get_ids(test='foo', bar_id=42, foo_id=37)['bar'] 42 >>> wp._get_ids(test='foo', bar_id=42, foo_id=37).get('test') == None True >>> len(wp._get_ids(filter='test', bar_id=42, foo_id=37).keys()) 2 """ ids = dict() for key in kw.keys(): if len(key.split('_id')) > 1: ids.update({'%s' % key.split('_id')[0]: kw.get(key)}) return ids def _build_endpoint(self, url_components, ids): """ >>> wp = WordpressJsonWrapper(None, None, None) >>> wp._build_endpoint(['posts'], {'post': 42}) '/posts/42' >>> wp._build_endpoint(['post'], {'post': 24}) '/posts/24' >>> wp._build_endpoint(['revisions'], {'post': 12}) '/posts/12/revisions' >>> wp._build_endpoint(['categories'], {'post': 12}) '/categories' >>> wp._build_endpoint(['categories'], {'category': 1}) '/categories/1' >>> wp._build_endpoint(['statuses'], {'status': 3}) '/statuses/3' >>> wp._build_endpoint(['posts', 'meta'], {'post': 42}) '/posts/42/meta' >>> wp._build_endpoint(['posts', 'meta'], {'post': 42, 'meta': 37}) '/posts/42/meta/37' >>> wp._build_endpoint(['foo', 'bar'], {'bar': 37}) '/foo/bar/37' """ endpoint = '' url_components = self._expand_url_components(url_components, ids) for component in url_components: if component_conversions.get(component): component = component_conversions.get(component) endpoint += '/%s' % component if ids.get(component): endpoint += '/%s' % ids.get(component) elif ids.get(component[:-1]): endpoint += '/%s' % ids.get(component[:-1]) elif component.endswith('ies') and ids.get(component[:-3] + 'y'): endpoint += '/%s' % ids.get(component[:-3] + 'y') elif component.endswith('es') and ids.get(component[:-2]): endpoint += '/%s' % ids.get(component[:-2]) return endpoint def _determine_method(self, verb): """ >>> wp = WordpressJsonWrapper(None, None, None) >>> wp._determine_method('get') 'GET' >>> wp._determine_method('list') 'GET' >>> wp._determine_method('showme') Traceback (most recent call last): File "<stdin>", line 1, in ? AssertionError """ assert methods.get(verb.lower()) is not None return methods.get(verb.lower()) def _prepare_req(self, method_name, **kw): """ >>> wp = WordpressJsonWrapper(None, None, None) >>> wp._prepare_req('get_posts') ('GET', '/posts', {}, None, {}, {}) >>> wp._prepare_req('list_posts') ('GET', '/posts', {}, None, {}, {}) >>> wp._prepare_req('get_posts', filter={'post_status': 'draft'}) ('GET', '/posts', {'filter[post_status]': 'draft'}, None, {}, {}) >>> wp._prepare_req('get_posts', post_id=5) ('GET', '/posts/5', {}, None, {}, {}) >>> wp._prepare_req('get_post', post_id=6) ('GET', '/posts/6', {}, None, {}, {}) >>> wp._prepare_req('edit_post', post_id=7, data={'foo': 'bar'}) ('POST', '/posts/7', {}, None, {'foo': 'bar'}, {}) >>> wp._prepare_req('create_post', data={'foo': 'bar'}) ('POST', '/posts', {}, None, {'foo': 'bar'}, {}) >>> wp._prepare_req('delete_post', post_id=8) ('DELETE', '/posts/8', {}, None, {}, {}) >>> wp._prepare_req('get_post_revisions', post_id=9) ('GET', '/posts/9/revisions', {}, None, {}, {}) >>> wp._prepare_req('get_revisions', post_id=9) ('GET', '/posts/9/revisions', {}, None, {}, {}) >>> wp._prepare_req('get_post_meta', post_id=91) ('GET', '/posts/91/meta', {}, None, {}, {}) >>> wp._prepare_req('get_meta', post_id=91) ('GET', '/posts/91/meta', {}, None, {}, {}) >>> wp._prepare_req('get_meta', post_id=91, meta_id=5) ('GET', '/posts/91/meta/5', {}, None, {}, {}) >>> wp._prepare_req('create_meta', post_id=91, meta_id=5) ('POST', '/posts/91/meta/5', {}, None, {}, {}) >>> wp._prepare_req('update_meta', post_id=91, meta_id=5) ('POST', '/posts/91/meta/5', {}, None, {}, {}) >>> wp._prepare_req('get_user', user_id=4) ('GET', '/users/4', {}, None, {}, {}) >>> wp._prepare_req('get_user', user_id='me') ('GET', '/users/me', {}, None, {}, {}) >>> wp._prepare_req('get_taxonomies') ('GET', '/taxonomies', {}, None, {}, {}) >>> wp._prepare_req('get_taxonomies', taxonomy_id='category') ('GET', '/taxonomies/category', {}, None, {}, {}) >>> wp._prepare_req('get_taxonomies', taxonomy_id='post_tag') ('GET', '/taxonomies/post_tag', {}, None, {}, {}) >>> wp._prepare_req('get_taxonomy', taxonomy_id='post_format') ('GET', '/taxonomies/post_format', {}, None, {}, {}) >>> wp._prepare_req('get_taxonomy', taxonomy_id='post_status') ('GET', '/taxonomies/post_status', {}, None, {}, {}) >>> wp._prepare_req('get_categories') ('GET', '/categories', {}, None, {}, {}) >>> wp._prepare_req('get_tags') ('GET', '/tags', {}, None, {}, {}) >>> wp._prepare_req('get_types') ('GET', '/types', {}, None, {}, {}) >>> wp._prepare_req('get_statuses') ('GET', '/statuses', {}, None, {}, {}) >>> wp._prepare_req('get_posts', headers={'foo': 'bar'}) ('GET', '/posts', {}, None, {}, {'foo': 'bar'}) >>> wp._prepare_req('get_posts', params={'context': 'edit'}) ('GET', '/posts', {'context': 'edit'}, None, {}, {}) >>> wp._prepare_req( ... 'create_media', ... headers={'Content-Type': 'some/mime'}, ... data='image_data' ... ) ('POST', '/media', {}, 'image_data', None, \ {'Content-Type': 'some/mime'}) """ assert len(method_name.split('_')) > 1 method = self._determine_method(method_name.split('_')[0]) endpoints = method_name.split('_')[1:] ids = self._get_ids(**kw) endpoint = self._build_endpoint(endpoints, ids) # filters url_params = dict() if kw.get('filter'): for query_param, value in six.iteritems(kw.get('filter')): url_params.update({'filter[%s]' % query_param: value}) # url params if kw.get('params'): for query_param, value in six.iteritems(kw.get('params')): url_params.update({query_param: value}) # post data post_data = dict() if kw.get('data'): post_data = kw.get('data') # headers headers = dict() if kw.get('headers'): headers = kw.get('headers') if 'application/json' not in headers.get('Content-Type', 'application/json'): post_json = None else: post_json = post_data post_data = None return (method.upper(), endpoint, url_params, post_data, post_json, headers) def _request(self, method_name, **kw): method, endpoint, params, data, json, headers = self._prepare_req( method_name, **kw ) http_response = requests.request( method, self.site + endpoint, auth=self.auth, params=params, data=data, json=json, headers=headers ) if http_response.status_code not in [200, 201]: if 'application/json' in http_response.headers.get('Content-Type'): code = http_response.json().get('code') message = http_response.json().get('message') else: code = http_response.status_code message = http_response.text raise WordpressError(" ".join([ str(http_response.status_code), str(http_response.reason), ":", '[{code}] {message}'.format(code=code, message=message) ])) elif 'application/json' in http_response.headers.get('Content-Type'): return http_response.json() else: raise WordpressError(" ".join([ "Expected JSON response but got", http_response.headers.get('Content-Type')])) if __name__ == '__main__': import nose nose.runmodule(argv=['-vv', '--with-doctest'])