Source code for pympl.function

from pympl.requeststring import RequestString
from pympl.resolvers import SchemaResolver
import pympl.exc as exc
from suds.sax.text import Text
from datetime import date


_functions = {}


def _make_request_string(string):
    if string:
        return (
            string if isinstance(string, basestring) else
            str(RequestString(string))
        )
    return ''


class FunctionMeta(type):
    def __init__(cls, name, bases, dict_):
        if name != 'Function':
            _functions[name] = cls
        type.__init__(cls, name, bases, dict_)


[docs]class Function(object): """ The base Function class that all of the others inherit from. """ __metaclass__ = FunctionMeta _signature = tuple() def __init__(self, client): self.client = client
[docs] def __call__(self, *args, **kwargs): """ A simple helper to make the function request and immediately return the result of its ``call()`` method. This allows us to do things like:: # Get the function object AuthenticateUser = client.fn.AuthenticateUser # Now call it with the requested parameters response = AuthenticateUser('username', 'password') This is short hand for:: request = client.fn.AuthenticateUser.make_request( 'username', 'password') response = request.call() """ request = self.make_request(*args, **kwargs) return request.call()
def __repr__(self): return "<pymple.function.%s>" % type(self).__name__
[docs] def make_request(self, *args, **kwargs): """ Creates and returns a request object for the function. This method is often overridden by specific functions to augment behavior. :return: The request :rtype: :class:`FunctionRequest <pympl.function.FunctionRequest>` """ return FunctionRequest(self, args, kwargs)
def _prepare_args(self, args, kwargs): all_kwargs = kwargs.copy() all_kwargs.update(self._prefill_args()) result = (self._encode_args(args), self._encode_kwargs(all_kwargs)) return result def _encode_args(self, args): encoded_args = [] for i, arg in enumerate(args): encoded_args.append( '' if arg is None else self._signature[i][1](arg)) return encoded_args def _encode_kwargs(self, kwargs): encoded_kwargs = {} for (arg, type_) in self._signature: if arg in kwargs: encoded_kwargs[arg] = ( '' if kwargs[arg] is None else type_(kwargs[arg]) ) return encoded_kwargs
class GuidPasswordPrefill(object): def _prefill_args(self): return { 'GUID': self.client.guid, 'Password': self.client.password }
[docs]class FunctionRequest(object): """ A simple object that binds the function being called with the arguments that are to be passed to that function when it is called. This class is also responsible for creating the appropriate response, upon completion of the request. By default, the response is parsed to a standard :class:`dict`. However, some functions return subclassed ``FunctionRequest`` which in turn parse responses into various other types of objects. """ def __init__(self, function, args, kwargs): self.function = function self.args = args self.kwargs = kwargs def __repr__(self): return "<pymple.function.%s>" % type(self).__name__
[docs] def call(self): """ Parses arguments and calls the underlying Ministry Platform SOAP function. """ function_name = type(self.function).__name__ function = getattr(self.function.client._suds.service, function_name) prepped_args = self.function._prepare_args(self.args, self.kwargs) return self._parse_response( function(*prepped_args[0], **prepped_args[1]) )
def _parse_response(self, response): result = self.function.client._suds.dict(response) for key, value in result.iteritems(): if isinstance(value, Text): result[key] = str(value) return result
[docs]class AddRecord(Function): """ Adds a record to a Ministry Platform table. Please note that it's generally easier to use the :obj:`pympl.Client.table` attribute to access a table object and create/update records via that mechanism. """ _signature = ( ('GUID', str), ('Password', str), ('UserID', int), ('TableName', str), ('PrimaryKeyField', str), ('RequestString', _make_request_string) ) def _prefill_args(self): return { 'GUID': self.client.guid, 'Password': self.client.password, 'UserID': self.client.user_id }
[docs] def make_request(self, *args, **kwargs): """ :param str TableName: The name of the Ministry Platform table to add a record to. :param str PrimaryKeyField: The primary key field name of the table that you want to add a record to. This is typically the singular form of the table name, suffixed with ``_ID``. For example, if you wanted to add a record to the ``Contacts`` table, the primary key field would be ``Contact_ID``. :param str|dict|RequestString RequestString: Either an instance of :class:`pympl.RequestString` or a manually-formatted Ministry Platform request string. See the Ministry Platform documentation on request strings if you want to perform the formatting yourself. Alternatively, you can also pass a :class:`dict` as the request string and it will be converted to a ``RequestString`` object and used that way. :return: A new request object, which can be used to query the Ministry Platform API. :rtype: :class:`AddRecordRequest <pympl.function.AddRecordRequest>` """ return AddRecordRequest(self, args, kwargs)
[docs]class AddRecordRequest(FunctionRequest): """ Parses the response into a tuple, if the request succeeds: ``(record_id, 0, message)``. """ def _parse_response(self, response): id_, junk, message = str(response).split('|', 2) if id_ == '0': raise exc.AddRecordError(message.lstrip('Exception: ')) return (int(id_), int(junk), message)
[docs]class UpdateRecord(Function): """ Like :class:`AddRecord <pympl.function.AddRecord>` but updates instead. """ _signature = ( ('GUID', str), ('Password', str), ('UserID', int), ('TableName', str), ('PrimaryKeyField', str), ('RequestString', _make_request_string) ) def _prefill_args(self): return { 'GUID': self.client.guid, 'Password': self.client.password, 'UserID': self.client.user_id }
[docs] def make_request(self, *args, **kwargs): """ :param str TableName: The name of the Ministry Platform table to add a record to. :param str PrimaryKeyField: The primary key field name of the table that you want to add a record to. This is typically the singular form of the table name, suffixed with ``_ID``. For example, if you wanted to add a record to the ``Contacts`` table, the primary key field would be ``Contact_ID``. :param str|dict|RequestString RequestString: Either an instance of :class:`pympl.RequestString` or a manually-formatted Ministry Platform request string. See the Ministry Platform documentation on request strings if you want to perform the formatting yourself. Alternatively, you can also pass a :class:`dict` as the request string and it will be converted to a ``RequestString`` object and used that way. :return: A new request object, which can be used to query the Ministry Platform API :rtype: :class:`UpdateRecordRequest <pympl.function.UpdateRecordRequest>` """ return UpdateRecordRequest(self, args, kwargs)
[docs]class UpdateRecordRequest(FunctionRequest): """ Virtually identical to :class:`pympl.function.AddRecordRequest`. """ def _parse_response(self, response): id_, junk, message = str(response).split('|', 2) if id_ == '0': raise exc.UpdateRecordError(message.lstrip('Exception: ')) return (int(id_), int(junk), message)
[docs]class AuthenticateUser(Function): """ Calls the MP SOAP function 'AuthenticateUser'. This is a common way to validate a user's credentials and check for basic authorization. For example, one might want to do something like this:: user_info = client.fn.AuthenticateUser('username', 'password') user_info['CanImpersonate'] user_info['UserID'] .. method:: make_request(*args, **kwargs) :param str UserName: The user name to check. :param str Password: The password to check. :return: The function request object :rtype: :class:`FunctionRequest <pympl.function.FunctionRequest>` """ _signature = ( ('UserName', str), ('Password', str), ('ServerName', str) ) def _prefill_args(self): return { 'ServerName': self.client.server_name }
[docs]class GetUserInfo(Function): """ Gets information about the requested user ID. The response from the server is returned as a :class:`GetUserInfoResponse <pympl.function.GetUserInfoResponse>`. Example:: user_info = client.fn.GetUserInfo(UserID=234) user_info.contact # A Contact table object for the user user_info.user # A User table object for the user """ _signature = ( ('GUID', str), ('Password', str), ('UserID', int) ) def _prefill_args(self): return { 'GUID': self.client.guid, 'Password': self.client.password }
[docs] def make_request(self, *args, **kwargs): """ :param int UserID: The user ID to look up. :return: The function request object. :rtype: :class:`GetUserInfoRequest <pympl.function.GetUserInfoRequest>` """ return GetUserInfoRequest(self, args, kwargs)
[docs]class GetUserInfoRequest(FunctionRequest): """ A simple ``FunctionRequest`` object that returns a :class:`GetUserInfoResponse <pympl.function.GetUserInfoResponse>` object, upon response parsing. """ def _parse_response(self, response): return GetUserInfoResponse(self, response)
[docs]class GetUserInfoResponse(object): """ Aggregates all of the data from ``GetUserInfo`` API call and dumps it into easy-to-use objects. .. attribute:: request The function request object. .. attribute:: raw The raw response XML. .. attribute:: contact A ``Contacts`` :class:`Table <pympl.table.Table>` object instantiated with the user's contact record data. .. attribute:: user A ``dp_Users`` :class:`Table <pympl.table.Table>` object instantiated with the user's user record data. .. attribute:: prefixes A :class:`list` of name prefixes from Ministry Platform. Example:: [{'Prefix': 'Mr.', 'Prefix_ID': 1}, {'Prefix': 'Mrs.', 'Prefix_ID': 2}, ... ] .. attribute:: suffixes A :class:`list` of name suffixes from Ministry Platform. Example:: [{'Suffix_ID': 1, 'Suffix': 'Jr.'}, {'Suffix_ID': 2, 'Suffix': 'Sr.'}, ... ] .. attribute:: genders A :class:`list` of genders from Ministry Platform. Example:: [{'Gender_ID': 1, 'Gender': 'Male'}, {'Gender_ID': 2, 'Gender': 'Female'}] .. attribute:: marital_statuses A :class:`list` of marital satuses from Ministry Platform. Example:: [ {'Marital_Status': 'Single', 'Marital_Status_ID': 1}, {'Marital_Status': 'Married', 'Marital_Status_ID': 2}, ... ] """ def __init__(self, request, response): self.request = request self.raw = response self._resolver = SchemaResolver(response.schema, response.diffgram) # Create some funsies self.contact = request.function.client.table.Contacts( self._resolver.Table.first()) self.user = request.function.client.table.dp_User( self._resolver.Table1.first()) self.prefixes = self._resolver.Table2 self.suffixes = self._resolver.Table3 self.genders = self._resolver.Table4 self.marital_statuses = self._resolver.Table5 def __repr__(self): return "<pymple.function.GetUserInfoResponse(%s)>" % ( self.user.get('User_Name') )
def _encode_file_contents(obj): if hasattr(obj, 'read'): return obj.read().encode('base64') else: return obj.encode('base64')
[docs]class AttachFile(Function): """ Attaches a file to a Ministry Platform record. For example:: file_guid, error_code, message = client.fn.AttachFile( FileContents=open('/path/to/file.jpg'), FileName='myfile.jpg', PageID=23, RecordID=798, FileDescription='A picture of a thing', IsImage=True, ResizeLongestDimension=0 # Don't resize the image ) """ _signature = ( ('GUID', str), ('Password', str), ('FileContents', _encode_file_contents), ('FileName', str), ('PageID', int), ('RecordID', int), ('FileDescription', str), ('IsImage', bool), ('ResizeLongestDimension', int) ) def _prefill_args(self): return { 'GUID': self.client.guid, 'Password': self.client.password, 'ResizeLongestDimension': 0 }
[docs] def make_request(self, *args, **kwargs): """ :param file FileContents: The file object to send to Ministry Platform. :param str FileName: The name of the file. :param int PageID: The page ID that the record belongs to. :param int RecordID: The record ID that you want to attach the file to. :param str FileDescription: A description of the file. :param bool IsImage: Whether or not the file is an image. :param int ResizeLongestDimension: The maximum size, in pixels, of an image's longest side. Set to ``0`` to disable resize. :return: The function request object :rtype: :class:`AttachFileRequest <pympl.function.AttachFileRequest>` """ return AttachFileRequest(self, args, kwargs)
[docs]class AttachFileRequest(FunctionRequest): """ A simple function request that parsers the string response into a convenient tuple: ``(guid, error_code, message)``. """ def _parse_response(self, response): guid, junk, message = str(response).split('|', 2) return guid, int(junk), message
class UpdateDefaultImage(Function, GuidPasswordPrefill): _signature = ( ('GUID', str), ('Password', str), ('PageID', int), ('RecordID', int), ('UniqueName', str) ) def make_request(self, *args, **kwargs): return UpdateDefaultImageRequest(self, args, kwargs) class UpdateDefaultImageRequest(FunctionRequest): def _parse_response(self, response): guid, junk, message = str(response).split('|', 2) if guid == '0': raise exc.UpdateDefaultImageError(message) return guid, int(junk), message
[docs]class ExecuteStoredProcedure(Function, GuidPasswordPrefill): """ Executes a stored procedure on the Ministry Platform MSSQL Server instance. NOTE: The :attr:`pympl.client.Client.sp` attribute is the preferred method of executing stored procedures. """ _signature = ( ('GUID', str), ('Password', str), ('StoredProcedureName', str), ('RequestString', _make_request_string) )
[docs] def make_request(self, *args, **kwargs): """ :param str StoredProcedureName: The name of the stored procedure to execute. Only stored procedures that begin with ``api`` can be called. :param str|dict|RequestString RequestString: The parameters to pass to the stored procedure. :return: The function request object :rtype: :class:`ExecuteStoredProcedureRequest <pympl.function.ExecuteStoredProcedureRequest>` """ return ExecuteStoredProcedureRequest(self, args, kwargs)
[docs]class ExecuteStoredProcedureRequest(FunctionRequest): """ The function request for stored procedures. Parses the API response into an instance of :class:`ExecuteStoredProcedureResponse <pympl.function.ExecuteStoredProcedureResponse>`. """ def _parse_response(self, response): return ExecuteStoredProcedureResponse(self, response) def __repr__(self): return "<pymple.function.ExecuteStoredProcedureRequest(%s)>" % ( self.kwargs.get('StoredProcedureName', 'N/A') )
[docs]class ExecuteStoredProcedureResponse(object): """ Contains all of the table information returned by a stored procedure call. This object can be iterated over, which yields each of the response tables. In addition, each table can be accessed via attributes on the object, e.g. ``response.table``, ``response.table2``, ``response.table3``. """ def __init__(self, request, response): self.request = request self.raw = response self._resolver = SchemaResolver(response.schema, response.diffgram) for i in self._resolver.tables: setattr(self, i.lower(), getattr(self._resolver, i)) def __repr__(self): return "<pymple.function.ExecuteStoredProcedureResponse(%s)>" % ( self.request.kwargs.get('StoredProcedureName', 'N/A') ) def __iter__(self): for i in self._resolver.tables: yield getattr(self._resolver, i)
[docs] def as_dict(self): """ Converts the response tables as a dictionary. :return: The stored procedure response. :rtype: dict """ result = {} for i in self._resolver.tables: result[i] = getattr(self._resolver, i) return result
class FindOrCreateUserAccount(Function, GuidPasswordPrefill): _signature = ( ('GUID', str), ('Password', str), ('FirstName', str), ('LastName', str), ('MobilePhone', str), ('EmailAddress', str) ) class UpdateUserAccount(Function, GuidPasswordPrefill): _signature = ( ('GUID', str), ('Password', str), ('UserID', int), ('FirstName', str), ('LastName', str), ('MobilePhone', str), ('EmailAddress', str), ('NewPassword', str), ('MiddleName', str), ('NickName', str), ('PrefixID', int), ('SuffixID', int), ('DOB', lambda x: x.strftime('%Y-%m-%d') if x else ''), ('GenderID', int), ('MaritalStatusID', int) ) def make_request(self, *args, **kwargs): return UpdateUserAccountRequest(self, args, kwargs) class UpdateUserAccountRequest(FunctionRequest): def _parse_response(self, response): id_, junk, message = str(response).split('|', 2) if id_ == '0': raise exc.UpdateUserAccountError(message.lstrip('Exception: ')) return int(id_), int(junk), message class ResetPassword(Function, GuidPasswordPrefill): _signature = ( ('GUID', str), ('Password', str), ('FirstName', str), ('EmailAddress', str) ) def make_request(self, *args, **kwargs): return ResetPasswordRequest(self, args, kwargs) class ResetPasswordRequest(FunctionRequest): def _parse_response(self, response): parsed_response = FunctionRequest._parse_response(self, response) id_, junk, message = str( parsed_response['ResetPasswordResult']).split('|', 2) if id_ == '0': raise exc.ResetPasswordError(message.lstrip('Exception: ')) return parsed_response
[docs]class FunctionRegistry(object): """ A simple object that facilitates instantiating function objects. Accessing an attribute of the function registry will return an instantiated function object, which can be used to query Ministry Platform:: # A function registry is always instantiated at Client.fn function_instance = client.fn.AuthenticateUser Functions are callables. Calling them will call Ministry Platform with the parameters specified:: response = function_instance('username', 'password') For specific information about the various functions, the parameters they receive and the objects they return, please reference the various ``Function`` class docs: * :class:`AddRecord <pympl.function.AddRecord>` * :class:`UpdateRecord <pympl.function.UpdateRecord>` * :class:`AuthenticateUser <pympl.function.AuthenticateUser>` * :class:`GetUserInfo <pympl.function.GetUserInfo>` * :class:`AttachFile <pympl.function.AttachFile>` * :class:`UpdateDefaultImage <pympl.function.UpdateDefaultImage>` * :class:`ExecuteStoredProcedure <pympl.function.ExecuteStoredProcedure>` * :class:`FindOrCreateUserAccount <pympl.function.FindOrCreateUserAccount>` * :class:`UpdateUserAccount <pympl.function.UpdateUserAccount>` * :class:`ResetPassword <pympl.function.ResetPassword>` """ _cache = {} def __init__(self, client): self.client = client def __getattr__(self, name): if name not in self._cache: try: self._cache[name] = _functions[name](self.client) except KeyError: raise AttributeError(name) return self._cache[name] def __repr__(self): return "<pymple.function.FunctionRegistry>"