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)