commit
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
"""
|
||||
Library to interface with the WordPress XML-RPC API.
|
||||
|
||||
See README for usage instructions.
|
||||
"""
|
||||
from wordpress_xmlrpc.base import *
|
||||
from wordpress_xmlrpc.wordpress import *
|
||||
from . import methods
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,150 @@
|
||||
import collections
|
||||
import sys
|
||||
|
||||
from wordpress_xmlrpc.compat import xmlrpc_client, dict_type
|
||||
from wordpress_xmlrpc.exceptions import ServerConnectionError, UnsupportedXmlrpcMethodError, InvalidCredentialsError, XmlrpcDisabledError
|
||||
|
||||
|
||||
class Client(object):
|
||||
"""
|
||||
Connection to a WordPress XML-RPC API endpoint.
|
||||
|
||||
To execute XML-RPC methods, pass an instance of an
|
||||
`XmlrpcMethod`-derived class to `Client`'s `call` method.
|
||||
"""
|
||||
|
||||
def __init__(self, url, username, password, blog_id=0, transport=None):
|
||||
self.url = url
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.blog_id = blog_id
|
||||
|
||||
try:
|
||||
self.server = xmlrpc_client.ServerProxy(url, allow_none=True, transport=transport)
|
||||
self.supported_methods = self.server.mt.supportedMethods()
|
||||
except xmlrpc_client.ProtocolError:
|
||||
e = sys.exc_info()[1]
|
||||
raise ServerConnectionError(repr(e))
|
||||
|
||||
def call(self, method):
|
||||
if method.method_name not in self.supported_methods:
|
||||
raise UnsupportedXmlrpcMethodError(method.method_name)
|
||||
|
||||
server_method = getattr(self.server, method.method_name)
|
||||
args = method.get_args(self)
|
||||
|
||||
try:
|
||||
raw_result = server_method(*args)
|
||||
except xmlrpc_client.Fault:
|
||||
e = sys.exc_info()[1]
|
||||
if e.faultCode == 403:
|
||||
raise InvalidCredentialsError(e.faultString)
|
||||
elif e.faultCode == 405:
|
||||
raise XmlrpcDisabledError(e.faultString)
|
||||
else:
|
||||
raise
|
||||
return method.process_result(raw_result)
|
||||
|
||||
|
||||
class XmlrpcMethod(object):
|
||||
"""
|
||||
Base class for XML-RPC methods.
|
||||
|
||||
Child classes can override methods and properties to customize behavior:
|
||||
|
||||
Properties:
|
||||
* `method_name`: XML-RPC method name (e.g., 'wp.getUserInfo')
|
||||
* `method_args`: Tuple of method-specific required parameters
|
||||
* `optional_args`: Tuple of method-specific optional parameters
|
||||
* `results_class`: Python class which will convert an XML-RPC response dict into an object
|
||||
"""
|
||||
method_name = None
|
||||
method_args = tuple()
|
||||
optional_args = tuple()
|
||||
results_class = None
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if self.method_args or self.optional_args:
|
||||
if self.optional_args:
|
||||
max_num_args = len(self.method_args) + len(self.optional_args)
|
||||
if not (len(self.method_args) <= len(args) <= max_num_args):
|
||||
raise ValueError("Invalid number of parameters to %s" % self.method_name)
|
||||
else:
|
||||
if len(args) != len(self.method_args):
|
||||
raise ValueError("Invalid number of parameters to %s" % self.method_name)
|
||||
|
||||
for i, arg_name in enumerate(self.method_args):
|
||||
setattr(self, arg_name, args[i])
|
||||
|
||||
if self.optional_args:
|
||||
for i, arg_name in enumerate(self.optional_args, start=len(self.method_args)):
|
||||
if i >= len(args):
|
||||
break
|
||||
setattr(self, arg_name, args[i])
|
||||
|
||||
if 'results_class' in kwargs:
|
||||
self.results_class = kwargs['results_class']
|
||||
|
||||
def default_args(self, client):
|
||||
"""
|
||||
Builds set of method-non-specific arguments.
|
||||
"""
|
||||
return tuple()
|
||||
|
||||
def get_args(self, client):
|
||||
"""
|
||||
Builds final set of XML-RPC method arguments based on
|
||||
the method's arguments, any default arguments, and their
|
||||
defined respective ordering.
|
||||
"""
|
||||
default_args = self.default_args(client)
|
||||
|
||||
if self.method_args or self.optional_args:
|
||||
optional_args = getattr(self, 'optional_args', tuple())
|
||||
args = []
|
||||
for arg in (self.method_args + optional_args):
|
||||
if hasattr(self, arg):
|
||||
obj = getattr(self, arg)
|
||||
if hasattr(obj, 'struct'):
|
||||
args.append(obj.struct)
|
||||
else:
|
||||
args.append(obj)
|
||||
args = list(default_args) + args
|
||||
else:
|
||||
args = default_args
|
||||
|
||||
return args
|
||||
|
||||
def process_result(self, raw_result):
|
||||
"""
|
||||
Performs actions on the raw result from the XML-RPC response.
|
||||
|
||||
If a `results_class` is defined, the response will be converted
|
||||
into one or more object instances of that class.
|
||||
"""
|
||||
if self.results_class and raw_result:
|
||||
if isinstance(raw_result, dict_type):
|
||||
return self.results_class(raw_result)
|
||||
elif isinstance(raw_result, collections.Iterable):
|
||||
return [self.results_class(result) for result in raw_result]
|
||||
|
||||
return raw_result
|
||||
|
||||
|
||||
class AnonymousMethod(XmlrpcMethod):
|
||||
"""
|
||||
An XML-RPC method for which no authentication is required.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class AuthenticatedMethod(XmlrpcMethod):
|
||||
"""
|
||||
An XML-RPC method for which user authentication is required.
|
||||
|
||||
Blog ID, username and password details will be passed from
|
||||
the `Client` instance to the method call.
|
||||
"""
|
||||
|
||||
def default_args(self, client):
|
||||
return (client.blog_id, client.username, client.password)
|
||||
@@ -0,0 +1,28 @@
|
||||
try:
|
||||
import xmlrpclib as xmlrpc_client # py2.x
|
||||
except ImportError:
|
||||
from xmlrpc import client as xmlrpc_client # py3.x
|
||||
|
||||
try:
|
||||
import httplib as http_client # py2.x
|
||||
except ImportError:
|
||||
from http import client as http_client #py3.x
|
||||
|
||||
|
||||
import types
|
||||
try:
|
||||
dict_type = types.DictType # py2.x
|
||||
except AttributeError:
|
||||
dict_type = dict # py3.x
|
||||
|
||||
|
||||
try:
|
||||
from ConfigParser import ConfigParser # py2.x
|
||||
except ImportError:
|
||||
from configparser import ConfigParser # py3.x
|
||||
|
||||
try:
|
||||
unicode('test')
|
||||
except NameError:
|
||||
def unicode(s):
|
||||
return s
|
||||
@@ -0,0 +1,47 @@
|
||||
class ServerConnectionError(Exception):
|
||||
"""
|
||||
An error while attempting to connect to the XML-RPC endpoint.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class UnsupportedXmlrpcMethodError(Exception):
|
||||
"""
|
||||
An error while attempting to call a method that is not
|
||||
supported by the XML-RPC server.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class XmlrpcDisabledError(Exception):
|
||||
"""
|
||||
An error when XML-RPC services are disabled in WordPress.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class InvalidCredentialsError(Exception):
|
||||
"""
|
||||
An error when the XML-RPC server rejects the user's credentials
|
||||
(username/password combination).
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class FieldConversionError(Exception):
|
||||
"""
|
||||
An error while converting field Python value to XML-RPC value type.
|
||||
|
||||
Attributes:
|
||||
`field_name`: name of the field
|
||||
`input_value`: value that was passed to the conversion function
|
||||
"""
|
||||
def __init__(self, field_name, error):
|
||||
self.field_name = field_name
|
||||
self.error = error
|
||||
|
||||
def __str__(self):
|
||||
return repr(self)
|
||||
|
||||
def __repr__(self):
|
||||
return self.field_name
|
||||
@@ -0,0 +1,135 @@
|
||||
import datetime
|
||||
|
||||
from wordpress_xmlrpc.compat import xmlrpc_client
|
||||
|
||||
|
||||
class FieldMap(object):
|
||||
"""
|
||||
Container for settings mapping a WordPress XML-RPC request/response struct
|
||||
to a Python, programmer-friendly class.
|
||||
|
||||
Parameters:
|
||||
`inputName`: name of the field in XML-RPC response.
|
||||
`outputNames`: (optional) list of field names to use when generating new XML-RPC request. defaults to `[inputName]`
|
||||
`default`: (optional) default value to use when none is supplied in XML-RPC response. defaults to `None`
|
||||
`conversion`: (optional) function to convert Python value to XML-RPC value for XML-RPC request.
|
||||
"""
|
||||
|
||||
def __init__(self, inputName, outputNames=None, default=None, conversion=None):
|
||||
self.name = inputName
|
||||
self.output_names = outputNames or [inputName]
|
||||
self.default = default
|
||||
self.conversion = conversion
|
||||
|
||||
def convert_to_python(self, xmlrpc=None):
|
||||
"""
|
||||
Extracts a value for the field from an XML-RPC response.
|
||||
"""
|
||||
if xmlrpc:
|
||||
return xmlrpc.get(self.name, self.default)
|
||||
elif self.default:
|
||||
return self.default
|
||||
else:
|
||||
return None
|
||||
|
||||
def convert_to_xmlrpc(self, input_value):
|
||||
"""
|
||||
Convert a Python value to the expected XML-RPC value type.
|
||||
"""
|
||||
if self.conversion:
|
||||
return self.conversion(input_value)
|
||||
else:
|
||||
return input_value
|
||||
|
||||
def get_outputs(self, input_value):
|
||||
"""
|
||||
Generate a set of output values for a given input.
|
||||
"""
|
||||
output_value = self.convert_to_xmlrpc(input_value)
|
||||
|
||||
output = {}
|
||||
for name in self.output_names:
|
||||
output[name] = output_value
|
||||
|
||||
return output
|
||||
|
||||
|
||||
class IntegerFieldMap(FieldMap):
|
||||
"""
|
||||
FieldMap pre-configured for handling integer fields.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if 'conversion' not in kwargs:
|
||||
kwargs['conversion'] = int
|
||||
|
||||
super(IntegerFieldMap, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class DateTimeFieldMap(FieldMap):
|
||||
"""
|
||||
FieldMap pre-configured for handling DateTime fields.
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
if 'conversion' not in kwargs:
|
||||
kwargs['conversion'] = xmlrpc_client.DateTime
|
||||
|
||||
super(DateTimeFieldMap, self).__init__(*args, **kwargs)
|
||||
|
||||
def convert_to_python(self, xmlrpc=None):
|
||||
if xmlrpc:
|
||||
# make sure we have an `xmlrpc_client.DateTime` instance
|
||||
raw_value = xmlrpc.get(self.name, self.default)
|
||||
if not isinstance(raw_value, xmlrpc_client.DateTime):
|
||||
raw_value = xmlrpc_client.DateTime(raw_value)
|
||||
|
||||
# extract its timetuple and convert to datetime
|
||||
try:
|
||||
tt = raw_value.timetuple()
|
||||
except ValueError:
|
||||
# Workaround for a combination of Python and WordPress bug
|
||||
# which would return a null date for Draft posts. This is not
|
||||
# the case for recent versions of WP, but drafts created a that
|
||||
# time still have a null date.
|
||||
# The python bug is http://bugs.python.org/issue2623 and
|
||||
# affects xmlrpclib when fed a timezone aware DateTime
|
||||
if str(raw_value) == "00000000T00:00:00Z":
|
||||
raw_value = xmlrpc_client.DateTime("00010101T00:00:00")
|
||||
tt = raw_value.timetuple()
|
||||
else:
|
||||
raise
|
||||
return datetime.datetime(*tuple(tt)[:6])
|
||||
elif self.default:
|
||||
return self.default
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
class TermsListFieldMap(FieldMap):
|
||||
"""
|
||||
FieldMap that converts to/from WordPress objects.
|
||||
"""
|
||||
def __init__(self, object_class, *args, **kwargs):
|
||||
self.object_class = object_class
|
||||
super(TermsListFieldMap, self).__init__(*args, **kwargs)
|
||||
|
||||
def convert_to_python(self, xmlrpc=None):
|
||||
if xmlrpc and self.name in xmlrpc:
|
||||
values = []
|
||||
for value in xmlrpc.get(self.name):
|
||||
values.append(self.object_class(value))
|
||||
return values
|
||||
else:
|
||||
return []
|
||||
|
||||
def convert_to_xmlrpc(self, input_value):
|
||||
if input_value:
|
||||
values = {}
|
||||
for term in input_value:
|
||||
if term.taxonomy not in values:
|
||||
values[term.taxonomy] = []
|
||||
values[term.taxonomy].append(int(term.id))
|
||||
return values
|
||||
else:
|
||||
return None
|
||||
@@ -0,0 +1,10 @@
|
||||
"""
|
||||
Implementations of standard WordPress XML-RPC APIs.
|
||||
"""
|
||||
from wordpress_xmlrpc.methods import posts
|
||||
from wordpress_xmlrpc.methods import pages
|
||||
from wordpress_xmlrpc.methods import demo
|
||||
from wordpress_xmlrpc.methods import users
|
||||
from wordpress_xmlrpc.methods import options
|
||||
from wordpress_xmlrpc.methods import comments
|
||||
from wordpress_xmlrpc.methods import media
|
||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,126 @@
|
||||
from wordpress_xmlrpc.base import *
|
||||
from wordpress_xmlrpc.wordpress import WordPressComment
|
||||
|
||||
|
||||
class GetComment(AuthenticatedMethod):
|
||||
"""
|
||||
Retrieve an individual comment.
|
||||
|
||||
Parameters:
|
||||
`comment_id`: ID of the comment to retrieve.
|
||||
|
||||
Returns: :class:`WordPressPost` instance.
|
||||
"""
|
||||
method_name = 'wp.getComment'
|
||||
method_args = ('comment_id',)
|
||||
results_class = WordPressComment
|
||||
|
||||
|
||||
class NewComment(AuthenticatedMethod):
|
||||
"""
|
||||
Create a new comment on a post.
|
||||
|
||||
Parameters:
|
||||
`post_id`: The id of the post to add a comment to.
|
||||
`comment`: A :class:`WordPressComment` instance with at least the `content` value set.
|
||||
|
||||
Returns: ID of the newly-created comment (an integer).
|
||||
"""
|
||||
method_name = 'wp.newComment'
|
||||
method_args = ('post_id', 'comment')
|
||||
|
||||
|
||||
class NewAnonymousComment(AnonymousMethod):
|
||||
"""
|
||||
Create a new comment on a post without authenticating.
|
||||
|
||||
NOTE: Requires support on the blog by setting the following filter in a plugin or theme:
|
||||
|
||||
add_filter( 'xmlrpc_allow_anonymous_comments', '__return_true' );
|
||||
|
||||
Parameters:
|
||||
`post_id`: The id of the post to add a comment to.
|
||||
`comment`: A :class:`WordPressComment` instance with at least the `content` value set.
|
||||
|
||||
Returns: ID of the newly-created comment (an integer).
|
||||
"""
|
||||
method_name = 'wp.newComment'
|
||||
method_args = ('post_id', 'comment')
|
||||
|
||||
|
||||
class EditComment(AuthenticatedMethod):
|
||||
"""
|
||||
Edit an existing comment.
|
||||
|
||||
Parameters:
|
||||
`comment_id`: The id of the comment to edit.
|
||||
`comment`: A :class:`WordPressComment` instance with at least the `content` value set.
|
||||
|
||||
Returns: `True` on successful edit.
|
||||
"""
|
||||
method_name = 'wp.editComment'
|
||||
method_args = ('comment_id', 'comment')
|
||||
|
||||
|
||||
class DeleteComment(AuthenticatedMethod):
|
||||
"""
|
||||
Delete an existing comment.
|
||||
|
||||
Parameters:
|
||||
`comment_id`: The id of the comment to be deleted.
|
||||
|
||||
Returns: `True` on successful deletion.
|
||||
"""
|
||||
method_name = 'wp.deleteComment'
|
||||
method_args = ('comment_id',)
|
||||
|
||||
|
||||
class GetCommentStatusList(AuthenticatedMethod):
|
||||
"""
|
||||
Retrieve the set of possible blog comment statuses (e.g., "approve," "hold," "spam").
|
||||
|
||||
Parameters:
|
||||
None
|
||||
|
||||
Returns: `dict` of values and their pretty names.
|
||||
|
||||
Example:
|
||||
>>> client.call(GetCommentStatusList())
|
||||
{'hold': 'Unapproved', 'approve': 'Approved', 'spam': 'Spam'}
|
||||
"""
|
||||
method_name = 'wp.getCommentStatusList'
|
||||
|
||||
|
||||
class GetCommentCount(AuthenticatedMethod):
|
||||
"""
|
||||
Retrieve comment count for a specific post.
|
||||
|
||||
Parameters:
|
||||
`post_id`: The id of the post to retrieve comment count for.
|
||||
|
||||
Returns: `dict` of comment counts for the post divided by comment status.
|
||||
|
||||
Example:
|
||||
>>> client.call(GetCommentCount(1))
|
||||
{'awaiting_moderation': '2', 'total_comments': 23, 'approved': '18', 'spam': 3}
|
||||
"""
|
||||
method_name = 'wp.getCommentCount'
|
||||
method_args = ('post_id',)
|
||||
|
||||
|
||||
class GetComments(AuthenticatedMethod):
|
||||
"""
|
||||
Gets a set of comments for a post.
|
||||
|
||||
Parameters:
|
||||
`filter`: a `dict` with the following values:
|
||||
* `post_id`: the id of the post to retrieve comments for
|
||||
* `status`: type of comments of comments to retrieve (optional, defaults to 'approve')
|
||||
* `number`: number of comments to retrieve (optional, defaults to 10)
|
||||
* `offset`: retrieval offset (optional, defaults to 0)
|
||||
|
||||
Returns: `list` of :class:`WordPressComment` instances.
|
||||
"""
|
||||
method_name = 'wp.getComments'
|
||||
method_args = ('filter',)
|
||||
results_class = WordPressComment
|
||||
@@ -0,0 +1,10 @@
|
||||
from wordpress_xmlrpc.base import *
|
||||
|
||||
|
||||
class SayHello(AnonymousMethod):
|
||||
method_name = 'demo.sayHello'
|
||||
|
||||
|
||||
class AddTwoNumbers(AnonymousMethod):
|
||||
method_name = 'demo.addTwoNumbers'
|
||||
method_args = ('number1', 'number2')
|
||||
@@ -0,0 +1,55 @@
|
||||
from wordpress_xmlrpc.base import *
|
||||
from wordpress_xmlrpc.wordpress import WordPressMedia
|
||||
|
||||
|
||||
class GetMediaLibrary(AuthenticatedMethod):
|
||||
"""
|
||||
Retrieve filtered list of media library items.
|
||||
|
||||
Parameters:
|
||||
`filter`: `dict` with optional keys:
|
||||
* `number`: number of media items to retrieve
|
||||
* `offset`: query offset
|
||||
* `parent_id`: ID of post the media item is attached to.
|
||||
Use empty string (default) to show all media items.
|
||||
Use `0` to show unattached media items.
|
||||
* `mime_type`: file mime-type to filter by (e.g., 'image/jpeg')
|
||||
|
||||
Returns: `list` of :class:`WordPressMedia` instances.
|
||||
"""
|
||||
method_name = 'wp.getMediaLibrary'
|
||||
method_args = ('filter',)
|
||||
results_class = WordPressMedia
|
||||
|
||||
|
||||
class GetMediaItem(AuthenticatedMethod):
|
||||
"""
|
||||
Retrieve an individual media item.
|
||||
|
||||
Parameters:
|
||||
`attachment_id`: ID of the media item.
|
||||
|
||||
Returns: :class:`WordPressMedia` instance.
|
||||
"""
|
||||
method_name = 'wp.getMediaItem'
|
||||
method_args = ('attachment_id',)
|
||||
results_class = WordPressMedia
|
||||
|
||||
|
||||
class UploadFile(AuthenticatedMethod):
|
||||
"""
|
||||
Upload a file to the blog.
|
||||
|
||||
Note: the file is not attached to or inserted into any blog posts.
|
||||
|
||||
Parameters:
|
||||
`data`: `dict` with three items:
|
||||
* `name`: filename
|
||||
* `type`: MIME-type of the file
|
||||
* `bits`: base-64 encoded contents of the file. See xmlrpclib.Binary()
|
||||
* `overwrite` (optional): flag to override an existing file with this name
|
||||
|
||||
Returns: `dict` with keys `id`, `file` (filename), `url` (public URL), and `type` (MIME-type).
|
||||
"""
|
||||
method_name = 'wp.uploadFile'
|
||||
method_args = ('data',)
|
||||
@@ -0,0 +1,35 @@
|
||||
from wordpress_xmlrpc.base import *
|
||||
from wordpress_xmlrpc.wordpress import WordPressOption
|
||||
|
||||
|
||||
class GetOptions(AuthenticatedMethod):
|
||||
"""
|
||||
Retrieve list of blog options.
|
||||
|
||||
Parameters:
|
||||
`options`: `list` of option names to retrieve; if empty, all options will be retrieved
|
||||
|
||||
Returns: `list` of :class:`WordPressOption` instances.
|
||||
"""
|
||||
method_name = 'wp.getOptions'
|
||||
method_args = ('options',)
|
||||
|
||||
def process_result(self, options_dict):
|
||||
options = []
|
||||
for key, value in options_dict.items():
|
||||
value['name'] = key
|
||||
options.append(WordPressOption(value))
|
||||
return options
|
||||
|
||||
|
||||
class SetOptions(GetOptions):
|
||||
"""
|
||||
Update the value of an existing blog option.
|
||||
|
||||
Parameters:
|
||||
`options`: `dict` of key/value pairs
|
||||
|
||||
Returns: `list` of :class:`WordPressOption` instances representing the updated options.
|
||||
"""
|
||||
method_name = 'wp.setOptions'
|
||||
method_args = ('options',)
|
||||
@@ -0,0 +1,33 @@
|
||||
from wordpress_xmlrpc.base import *
|
||||
|
||||
|
||||
class GetPageStatusList(AuthenticatedMethod):
|
||||
"""
|
||||
Retrieve the set of possible blog page statuses (e.g., "draft," "private," "publish").
|
||||
|
||||
Parameters:
|
||||
None
|
||||
|
||||
Returns: `dict` of values and their pretty names.
|
||||
|
||||
Example:
|
||||
>>> client.call(GetPageStatusList())
|
||||
{'draft': 'Draft', 'private': 'Private', 'publish': 'Published'}
|
||||
"""
|
||||
method_name = 'wp.getPageStatusList'
|
||||
|
||||
|
||||
class GetPageTemplates(AuthenticatedMethod):
|
||||
"""
|
||||
Retrieve the list of blog templates.
|
||||
|
||||
Parameters:
|
||||
None
|
||||
|
||||
Returns: `dict` of values and their paths.
|
||||
|
||||
Example:
|
||||
>>> client.call(GetPageTemplates())
|
||||
{'Default': 'default', 'Sidebar Template': 'sidebar-page.php', 'Showcase Template': 'showcase.php'}
|
||||
"""
|
||||
method_name = 'wp.getPageTemplates'
|
||||
@@ -0,0 +1,177 @@
|
||||
from wordpress_xmlrpc.base import *
|
||||
from wordpress_xmlrpc.wordpress import WordPressPost, WordPressPostType
|
||||
|
||||
|
||||
class GetPosts(AuthenticatedMethod):
|
||||
"""
|
||||
Retrieve posts from the blog.
|
||||
|
||||
Parameters:
|
||||
`filter`: optional `dict` of filters:
|
||||
* `number`
|
||||
* `offset`
|
||||
* `orderby`
|
||||
* `order`: 'ASC' or 'DESC'
|
||||
* `post_type`: Defaults to 'post'
|
||||
* `post_status`
|
||||
|
||||
Returns: `list` of :class:`WordPressPost` instances.
|
||||
"""
|
||||
method_name = 'wp.getPosts'
|
||||
optional_args = ('filter', 'fields')
|
||||
results_class = WordPressPost
|
||||
|
||||
|
||||
class GetPost(AuthenticatedMethod):
|
||||
"""
|
||||
Retrieve an individual blog post.
|
||||
|
||||
Parameters:
|
||||
`post_id`: ID of the blog post to retrieve.
|
||||
|
||||
Returns: :class:`WordPressPost` instance.
|
||||
"""
|
||||
method_name = 'wp.getPost'
|
||||
method_args = ('post_id',)
|
||||
optional_args = ('fields',)
|
||||
results_class = WordPressPost
|
||||
|
||||
|
||||
class NewPost(AuthenticatedMethod):
|
||||
"""
|
||||
Create a new post on the blog.
|
||||
|
||||
Parameters:
|
||||
`content`: A :class:`WordPressPost` instance with at least the `title` and `content` values set.
|
||||
|
||||
Returns: ID of the newly-created blog post (an integer).
|
||||
"""
|
||||
method_name = 'wp.newPost'
|
||||
method_args = ('content',)
|
||||
|
||||
|
||||
class EditPost(AuthenticatedMethod):
|
||||
"""
|
||||
Edit an existing blog post.
|
||||
|
||||
Parameters:
|
||||
`post_id`: ID of the blog post to edit.
|
||||
`content`: A :class:`WordPressPost` instance with the new values for the blog post.
|
||||
|
||||
Returns: `True` on successful edit.
|
||||
"""
|
||||
method_name = 'wp.editPost'
|
||||
method_args = ('post_id', 'content')
|
||||
|
||||
|
||||
class DeletePost(AuthenticatedMethod):
|
||||
"""
|
||||
Delete a blog post.
|
||||
|
||||
Parameters:
|
||||
`post_id`: ID of the blog post to delete.
|
||||
|
||||
Returns: `True` on successful deletion.
|
||||
"""
|
||||
method_name = 'wp.deletePost'
|
||||
method_args = ('post_id', )
|
||||
|
||||
|
||||
class GetPostStatusList(AuthenticatedMethod):
|
||||
"""
|
||||
Retrieve the set of possible blog post statuses (e.g., "draft," "private," "publish").
|
||||
|
||||
Parameters:
|
||||
None
|
||||
|
||||
Returns: `dict` of values and their pretty names.
|
||||
|
||||
Example:
|
||||
>>> client.call(GetPostStatusList())
|
||||
{'draft': 'Draft', 'private': 'Private', 'pending': 'Pending Review', 'publish': 'Published'}
|
||||
"""
|
||||
method_name = 'wp.getPostStatusList'
|
||||
|
||||
|
||||
class GetPostFormats(AuthenticatedMethod):
|
||||
"""
|
||||
Retrieve the set of post formats used by the blog.
|
||||
|
||||
Parameters:
|
||||
None
|
||||
|
||||
Returns: `dict` containing a `dict` of all blog post formats (`all`)
|
||||
and a list of formats `supported` by the theme.
|
||||
|
||||
Example:
|
||||
>>> client.call(GetPostFormats())
|
||||
{'all': {'status': 'Status', 'quote': 'Quote', 'image': 'Image', 'aside': 'Aside', 'standard': 'Standard', 'link': 'Link', 'chat': 'Chat', 'video': 'Video', 'audio': 'Audio', 'gallery': 'Gallery'},
|
||||
'supported': ['aside', 'link', 'gallery', 'status', 'quote', 'image']}
|
||||
"""
|
||||
method_name = 'wp.getPostFormats'
|
||||
|
||||
def get_args(self, client):
|
||||
args = super(GetPostFormats, self).get_args(client)
|
||||
args += ({'show-supported': True},)
|
||||
return args
|
||||
|
||||
|
||||
class GetPostTypes(AuthenticatedMethod):
|
||||
"""
|
||||
Retrieve a list of post types used by the blog.
|
||||
|
||||
Parameters:
|
||||
None
|
||||
|
||||
Returns: `dict` with names as keys and :class:`WordPressPostType` instances as values.
|
||||
"""
|
||||
method_name = 'wp.getPostTypes'
|
||||
results_class = WordPressPostType
|
||||
|
||||
def process_result(self, raw_result):
|
||||
result = {}
|
||||
for name, raw_value in raw_result.items():
|
||||
result[name] = self.results_class(raw_value)
|
||||
return result
|
||||
|
||||
|
||||
class GetPostType(AuthenticatedMethod):
|
||||
"""
|
||||
Retrieve an individual blog post type.
|
||||
|
||||
Parameters:
|
||||
`post_type`: Name of the blog post type to retrieve.
|
||||
|
||||
Returns: :class:`WordPressPostType` instance.
|
||||
"""
|
||||
method_name = 'wp.getPostType'
|
||||
method_args = ('post_type',)
|
||||
results_class = WordPressPostType
|
||||
|
||||
|
||||
class GetRevisions(AuthenticatedMethod):
|
||||
"""
|
||||
Retrieve all revisions of a post.
|
||||
|
||||
Parameters:
|
||||
`post_id`: ID of the post.
|
||||
|
||||
Returns: `list` of :class:`WordPressPost` instances.
|
||||
"""
|
||||
method_name = 'wp.getRevisions'
|
||||
method_args = ('post_id',)
|
||||
optional_args = ('fields',)
|
||||
results_class = WordPressPost
|
||||
|
||||
|
||||
class RestoreRevision(AuthenticatedMethod):
|
||||
"""
|
||||
Restores a post to a previous revision.
|
||||
|
||||
Parameters:
|
||||
`revision_id`: ID of the revision to revert to.
|
||||
|
||||
Returns: `True` on successful reversion.
|
||||
"""
|
||||
method_name = 'wp.restoreRevision'
|
||||
method_args = ('revision_id',)
|
||||
@@ -0,0 +1,111 @@
|
||||
from wordpress_xmlrpc.base import *
|
||||
from wordpress_xmlrpc.wordpress import WordPressTaxonomy, WordPressTerm
|
||||
|
||||
|
||||
class GetTaxonomies(AuthenticatedMethod):
|
||||
"""
|
||||
Retrieve the list of available taxonomies for the blog.
|
||||
|
||||
Parameters:
|
||||
None
|
||||
|
||||
Returns: `list` of :class:`WordPressTaxonomy` instances.
|
||||
"""
|
||||
method_name = 'wp.getTaxonomies'
|
||||
results_class = WordPressTaxonomy
|
||||
|
||||
|
||||
class GetTaxonomy(AuthenticatedMethod):
|
||||
"""
|
||||
Retrieve an individual taxonomy.
|
||||
|
||||
Parameters:
|
||||
`taxonomy`: name of the taxonomy
|
||||
|
||||
Returns: :class:`WordPressTaxonomy` instance.
|
||||
"""
|
||||
method_name = 'wp.getTaxonomy'
|
||||
method_args = ('taxonomy',)
|
||||
results_class = WordPressTaxonomy
|
||||
|
||||
|
||||
class GetTerms(AuthenticatedMethod):
|
||||
"""
|
||||
Retrieve the list of available terms for a taxonomy.
|
||||
|
||||
Parameters:
|
||||
`taxonomy`: name of the taxonomy
|
||||
|
||||
`filter`: optional `dict` of filters:
|
||||
* `number`
|
||||
* `offset`
|
||||
* `orderby`
|
||||
* `order`: 'ASC' or 'DESC'
|
||||
* `hide_empty`: Whether to return terms with count==0
|
||||
* `search`: Case-insensitive search on term names
|
||||
|
||||
Returns: `list` of :class:`WordPressTerm` instances.
|
||||
"""
|
||||
method_name = 'wp.getTerms'
|
||||
method_args = ('taxonomy',)
|
||||
optional_args = ('filter',)
|
||||
results_class = WordPressTerm
|
||||
|
||||
|
||||
class GetTerm(AuthenticatedMethod):
|
||||
"""
|
||||
Retrieve an individual term.
|
||||
|
||||
Parameters:
|
||||
`taxonomy`: name of the taxonomy
|
||||
|
||||
`term_id`: ID of the term
|
||||
|
||||
Returns: :class:`WordPressTerm` instance.
|
||||
"""
|
||||
method_name = 'wp.getTerm'
|
||||
method_args = ('taxonomy', 'term_id')
|
||||
results_class = WordPressTerm
|
||||
|
||||
|
||||
class NewTerm(AuthenticatedMethod):
|
||||
"""
|
||||
Create new term.
|
||||
|
||||
Parameters:
|
||||
`term`: instance of :class:`WordPressTerm`
|
||||
|
||||
Returns: ID of newly-created term (an integer).
|
||||
"""
|
||||
method_name = 'wp.newTerm'
|
||||
method_args = ('term',)
|
||||
|
||||
|
||||
class EditTerm(AuthenticatedMethod):
|
||||
"""
|
||||
Edit an existing term.
|
||||
|
||||
Parameters:
|
||||
`term_id`: ID of the term to edit.
|
||||
|
||||
`term`: A :class:`WordPressTerm` instance with the new values for the term.
|
||||
|
||||
Returns: `True` on successful edit.
|
||||
"""
|
||||
method_name = 'wp.editTerm'
|
||||
method_args = ('term_id', 'term')
|
||||
|
||||
|
||||
class DeleteTerm(AuthenticatedMethod):
|
||||
"""
|
||||
Delete a term.
|
||||
|
||||
Parameters:
|
||||
`taxonomy`: name of the taxonomy
|
||||
|
||||
`term_id`: ID of the term to delete.
|
||||
|
||||
Returns: `True` on successful deletion.
|
||||
"""
|
||||
method_name = 'wp.deleteTerm'
|
||||
method_args = ('taxonomy', 'term_id')
|
||||
@@ -0,0 +1,98 @@
|
||||
from wordpress_xmlrpc.base import *
|
||||
from wordpress_xmlrpc.wordpress import WordPressBlog, WordPressAuthor, WordPressUser
|
||||
|
||||
|
||||
class GetUsers(AuthenticatedMethod):
|
||||
"""
|
||||
Retrieve list of users in the blog.
|
||||
|
||||
Parameters:
|
||||
`filter`: optional `dict` of filters:
|
||||
* `number`
|
||||
* `offset`
|
||||
* `role`
|
||||
|
||||
`fields`: optional `list` of fields to return. Specific fields, or groups 'basic' or 'all'.
|
||||
|
||||
Returns: `list` of :class:`WordPressUser` instances.
|
||||
"""
|
||||
method_name = 'wp.getUsers'
|
||||
optional_args = ('filter', 'fields')
|
||||
results_class = WordPressUser
|
||||
|
||||
|
||||
class GetUser(AuthenticatedMethod):
|
||||
"""
|
||||
Retrieve an individual user.
|
||||
|
||||
Parameters:
|
||||
`user_id`: ID of the user
|
||||
`fields`: (optional) `list` of fields to return. Specific fields, or groups 'basic' or 'all'.
|
||||
|
||||
Returns: :class:`WordPressUser` instance.
|
||||
"""
|
||||
method_name = 'wp.getUser'
|
||||
method_args = ('user_id',)
|
||||
optional_args = ('fields',)
|
||||
results_class = WordPressUser
|
||||
|
||||
|
||||
class GetProfile(AuthenticatedMethod):
|
||||
"""
|
||||
Retrieve information about the connected user.
|
||||
|
||||
Parameters:
|
||||
None
|
||||
|
||||
Returns: instance of :class:`WordPressUser` representing the user whose credentials are being used with the XML-RPC API.
|
||||
"""
|
||||
method_name = 'wp.getProfile'
|
||||
results_class = WordPressUser
|
||||
|
||||
|
||||
class EditProfile(AuthenticatedMethod):
|
||||
"""
|
||||
Edit profile fields of the connected user.
|
||||
|
||||
Parameters:
|
||||
`user`: `WordPressUser` instance.
|
||||
|
||||
Returns: `True` on successful edit.
|
||||
"""
|
||||
method_name = 'wp.editProfile'
|
||||
method_args = ('user',)
|
||||
|
||||
|
||||
class GetUserInfo(GetProfile):
|
||||
"""Alias for GetProfile for backwards compatibility"""
|
||||
pass
|
||||
|
||||
|
||||
class GetUsersBlogs(AuthenticatedMethod):
|
||||
"""
|
||||
Retrieve list of blogs that this user belongs to.
|
||||
|
||||
Parameters:
|
||||
None
|
||||
|
||||
Returns: `list` of :class:`WordPressBlog` instances.
|
||||
"""
|
||||
method_name = 'wp.getUsersBlogs'
|
||||
results_class = WordPressBlog
|
||||
|
||||
def get_args(self, client):
|
||||
# strip off first (blog_id) parameter
|
||||
return super(GetUsersBlogs, self).get_args(client)[1:]
|
||||
|
||||
|
||||
class GetAuthors(AuthenticatedMethod):
|
||||
"""
|
||||
Retrieve list of authors in the blog.
|
||||
|
||||
Parameters:
|
||||
None
|
||||
|
||||
Returns: `list` of :class:`WordPressAuthor` instances.
|
||||
"""
|
||||
method_name = 'wp.getAuthors'
|
||||
results_class = WordPressAuthor
|
||||
@@ -0,0 +1,16 @@
|
||||
from wordpress_xmlrpc.compat import xmlrpc_client, http_client
|
||||
|
||||
|
||||
class ProxiedTransport(xmlrpc_client.Transport):
|
||||
def set_proxy(self, proxy):
|
||||
self.proxy = proxy
|
||||
|
||||
def make_connection(self, host):
|
||||
self.host = host
|
||||
return http_client.HTTP(self.proxy)
|
||||
|
||||
def send_request(self, connection, handler, request_body):
|
||||
connection.putrequest("POST", "http://%s%s" % (self.host, handler))
|
||||
|
||||
def send_host(self, connection, host):
|
||||
connection.putheader('Host', host)
|
||||
@@ -0,0 +1,260 @@
|
||||
import sys
|
||||
from .compat import *
|
||||
from .fieldmaps import FieldMap, IntegerFieldMap, DateTimeFieldMap, TermsListFieldMap
|
||||
from wordpress_xmlrpc.exceptions import FieldConversionError
|
||||
|
||||
|
||||
class WordPressBase(object):
|
||||
"""
|
||||
Base class for representing a WordPress object. Handles conversion
|
||||
of an XML-RPC response to an object, and construction of a `struct`
|
||||
to use in XML-RPC requests.
|
||||
|
||||
Child classes should define a `definition` property that contains
|
||||
the list of fields and a `FieldMap` instance to handle conversion
|
||||
for XML-RPC calls.
|
||||
"""
|
||||
definition = {}
|
||||
|
||||
def __init__(self, xmlrpc=None):
|
||||
# create private variable containing all FieldMaps for the `definition`
|
||||
self._def = {}
|
||||
|
||||
for key, value in self.definition.items():
|
||||
# if the definition was not a FieldMap, create a simple FieldMap
|
||||
if isinstance(value, FieldMap):
|
||||
self._def[key] = value
|
||||
else:
|
||||
self._def[key] = FieldMap(value)
|
||||
|
||||
# convert and store the value on this instance if non-empty
|
||||
try:
|
||||
converted_value = self._def[key].convert_to_python(xmlrpc)
|
||||
except Exception:
|
||||
e = sys.exc_info()[1]
|
||||
raise FieldConversionError(key, e)
|
||||
if converted_value is not None:
|
||||
setattr(self, key, converted_value)
|
||||
|
||||
@property
|
||||
def struct(self):
|
||||
"""
|
||||
XML-RPC-friendly representation of the current object state
|
||||
"""
|
||||
data = {}
|
||||
for var, fmap in self._def.items():
|
||||
if hasattr(self, var):
|
||||
data.update(fmap.get_outputs(getattr(self, var)))
|
||||
return data
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s>' % (self.__class__.__name__, str(self).encode('utf-8'))
|
||||
|
||||
|
||||
class WordPressTaxonomy(WordPressBase):
|
||||
definition = {
|
||||
'name': FieldMap('name', default=''),
|
||||
'label': 'label',
|
||||
'labels': 'labels',
|
||||
'hierarchical': 'hierarchical',
|
||||
'public': 'public',
|
||||
'show_ui': 'show_ui',
|
||||
'cap': 'cap',
|
||||
'is_builtin': '_builtin',
|
||||
'object_type': 'object_type'
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
if hasattr(self, 'name'):
|
||||
return self.name
|
||||
return unicode('')
|
||||
|
||||
|
||||
class WordPressTerm(WordPressBase):
|
||||
definition = {
|
||||
'id': 'term_id',
|
||||
'group': 'term_group',
|
||||
'taxonomy': 'taxonomy',
|
||||
'taxonomy_id': 'term_taxonomy_id',
|
||||
'name': FieldMap('name', default=''),
|
||||
'slug': 'slug',
|
||||
'description': 'description',
|
||||
'parent': 'parent',
|
||||
'count': IntegerFieldMap('count')
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
if hasattr(self, 'name'):
|
||||
return self.name
|
||||
return unicode('')
|
||||
|
||||
|
||||
class WordPressPost(WordPressBase):
|
||||
definition = {
|
||||
'id': 'post_id',
|
||||
'user': 'post_author',
|
||||
'date': DateTimeFieldMap('post_date_gmt'),
|
||||
'date_modified': DateTimeFieldMap('post_modified_gmt'),
|
||||
'slug': 'post_name',
|
||||
'post_status': 'post_status',
|
||||
'title': FieldMap('post_title', default='Untitled'),
|
||||
'content': 'post_content',
|
||||
'excerpt': 'post_excerpt',
|
||||
'link': 'link',
|
||||
'comment_status': 'comment_status',
|
||||
'ping_status': 'ping_status',
|
||||
'terms': TermsListFieldMap(WordPressTerm, 'terms'),
|
||||
'terms_names': 'terms_names',
|
||||
'custom_fields': 'custom_fields',
|
||||
'enclosure': 'enclosure',
|
||||
'password': 'post_password',
|
||||
'post_format': 'post_format',
|
||||
'thumbnail': 'post_thumbnail',
|
||||
'sticky': 'sticky',
|
||||
'post_type': FieldMap('post_type', default='post'),
|
||||
'parent_id': 'post_parent',
|
||||
'menu_order': IntegerFieldMap('menu_order'),
|
||||
'guid': 'guid',
|
||||
'mime_type': 'post_mime_type',
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
if hasattr(self, 'title'):
|
||||
return self.title
|
||||
return unicode('')
|
||||
|
||||
|
||||
class WordPressPage(WordPressPost):
|
||||
definition = dict(WordPressPost.definition, **{
|
||||
'template': 'wp_page_template',
|
||||
'post_type': FieldMap('post_type', default='page'),
|
||||
})
|
||||
|
||||
|
||||
class WordPressComment(WordPressBase):
|
||||
definition = {
|
||||
'id': 'comment_id',
|
||||
'user': 'user_id',
|
||||
'post': 'post_id',
|
||||
'post_title': 'post_title',
|
||||
'parent': 'comment_parent',
|
||||
'date_created': DateTimeFieldMap('date_created_gmt'),
|
||||
'status': 'status',
|
||||
'content': FieldMap('content', default=''),
|
||||
'link': 'link',
|
||||
'author': 'author',
|
||||
'author_url': 'author_url',
|
||||
'author_email': 'author_email',
|
||||
'author_ip': 'author_ip',
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
if hasattr(self, 'content'):
|
||||
return self.content
|
||||
return unicode('')
|
||||
|
||||
|
||||
class WordPressBlog(WordPressBase):
|
||||
definition = {
|
||||
'id': 'blogid',
|
||||
'name': FieldMap('blogName', default=''),
|
||||
'url': 'url',
|
||||
'xmlrpc': 'xmlrpc',
|
||||
'is_admin': FieldMap('isAdmin', default=False),
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
if hasattr(self, 'name'):
|
||||
return self.name
|
||||
return unicode('')
|
||||
|
||||
|
||||
class WordPressAuthor(WordPressBase):
|
||||
definition = {
|
||||
'id': 'user_id',
|
||||
'user_login': 'user_login',
|
||||
'display_name': FieldMap('display_name', default=''),
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
if hasattr(self, 'display_name'):
|
||||
return self.display_name
|
||||
return unicode('')
|
||||
|
||||
|
||||
class WordPressUser(WordPressBase):
|
||||
definition = {
|
||||
'id': 'user_id',
|
||||
'username': 'username',
|
||||
'roles': 'roles',
|
||||
'nickname': 'nickname',
|
||||
'url': 'url',
|
||||
'first_name': 'first_name',
|
||||
'last_name': 'last_name',
|
||||
'registered': DateTimeFieldMap('registered'),
|
||||
'bio': 'bio',
|
||||
'email': 'email',
|
||||
'nicename': 'nicename',
|
||||
'display_name': 'display_name',
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
if hasattr(self, 'nickname'):
|
||||
return self.nickname
|
||||
return unicode('')
|
||||
|
||||
|
||||
class WordPressMedia(WordPressBase):
|
||||
definition = {
|
||||
'id': 'attachment_id',
|
||||
'parent': 'parent',
|
||||
'title': FieldMap('title', default=''),
|
||||
'description': 'description',
|
||||
'caption': 'caption',
|
||||
'date_created': DateTimeFieldMap('date_created_gmt'),
|
||||
'link': 'link',
|
||||
'thumbnail': 'thumbnail',
|
||||
'metadata': 'metadata',
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
if hasattr(self, 'title'):
|
||||
return self.title
|
||||
return unicode('')
|
||||
|
||||
|
||||
class WordPressOption(WordPressBase):
|
||||
definition = {
|
||||
'name': FieldMap('name', default=''),
|
||||
'description': 'desc',
|
||||
'value': FieldMap('value', default=''),
|
||||
'read_only': FieldMap('readonly', default=False),
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
if hasattr(self, 'name') and hasattr(self, 'value'):
|
||||
return '%s="%s"' % (self.name, self.value)
|
||||
return unicode('')
|
||||
|
||||
|
||||
class WordPressPostType(WordPressBase):
|
||||
definition = {
|
||||
'name': 'name',
|
||||
'label': FieldMap('label', default=''),
|
||||
'labels': 'labels',
|
||||
'cap': 'cap',
|
||||
'map_meta_cap': 'map_meta_cap',
|
||||
'hierarchical': 'hierarchical',
|
||||
'menu_icon': 'menu_icon',
|
||||
'menu_position': 'menu_position',
|
||||
'public': 'public',
|
||||
'show_in_menu': 'show_in_menu',
|
||||
'taxonomies': 'taxonomies',
|
||||
'is_builtin': '_builtin',
|
||||
'supports': 'supports',
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
if hasattr(self, 'name'):
|
||||
return self.name
|
||||
return unicode('')
|
||||
Reference in New Issue
Block a user