diff --git a/five9/five9_api.py b/five9/five9_api.py new file mode 100644 index 0000000..23687cc --- /dev/null +++ b/five9/five9_api.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# Copyright 2017-TODAY LasLabs Inc. +# License MIT (https://opensource.org/licenses/MIT). + + +class Five9Api(object): + """This is the opinionated Five9 API interaction.""" + + diff --git a/five9/models/__init__.py b/five9/models/__init__.py new file mode 100644 index 0000000..17b0a6f --- /dev/null +++ b/five9/models/__init__.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- +# Copyright 2017-TODAY LasLabs Inc. +# License MIT (https://opensource.org/licenses/MIT). + +import properties + + +class BaseModel(properties.HasProperties): + """This is the core interface to be inherited by all models.""" + + # The authenticated Five9 interface that should be used for the + # underlying data operations. This is set using the + five9 = None + + @staticmethod + def api(method): + """Use this decorator to wrap methods that interact with the API. + + These methods should always be called with an authenticated API + interface for their first parameter. This interface will be set as + the ``five9`` object, with the rest of the arguments being passed to + the actual method. + """ + + def decorated_method(self, api, *args, **kwargs): + self.five9 = api + return method(*args, **kwargs) + + return decorated_method + + @classmethod + def parse_response(cls, fields, records): + """Parse an API response into usable objects. + + Args: + fields (list[str]): List of strings indicating the fields that + are represented in the records, in the order presented in + the records.:: + + [ + 'number1', + 'number2', + 'number3', + 'first_name', + 'last_name', + 'company', + 'street', + 'city', + 'state', + 'zip', + ] + + records (list[dict]): A really crappy data structure representing + records as returned by Five9:: + + [ + { + 'values': { + 'data': [ + '8881234567', + None, + None, + 'Dave', + 'Lasley', + 'LasLabs Inc', + None, + 'Las Vegas', + 'NV', + '89123', + ] + } + } + ] + + Returns: + list[BaseModel]: List of parsed records. + """ + data = [i['values']['data'] for i in records] + return [ + cls(**{fields[idx]: row for idx, row in enumerate(d)}) + for d in data + ] + + def to_api(self): + """Return all of the properties and values in a dictionary.""" + return { + attr: getattr(self, attr) for attr in self._props.keys(), + } + + def __getattr__(self, item): + """Allow for dynamic attributes, because the fields can be changed. + + Items beginning with ``_`` are excluded from this logic. + """ + if item.startswith('_'): + return super(BaseModel, self).__getattr__(item) + try: + return super(BaseModel, self).__getattr__(item) + except AttributeError: + private_attr = '_%s' % item + try: + return super(BaseModel, self).__getattr__(private_attr) + except AttributeError: + new_attr = properties.basic.DynamicProperty( + 'This is a dynamically created attribute.', + ) + setattr(self, private_attr, new_attr) + self._props[item] = new_attr + return super(BaseModel, self).__getattr__(private_attr) + + # CRUD Interface + def create(self): + """Create the object on the API. Children should implement this.""" + raise NotImplementedError + + def delete(self): + """Delete the object from the API. Chilren should implement this.""" + raise NotImplementedError + + @classmethod + def get(cls, identifier): + """Get the object from the API. Children should implement this. + + Args: + identifier (mixed): The identifier to send as the search key. + + Returns: + list[BaseModel]: Recordset matching identifier. + """ + raise NotImplementedError + + @classmethod + def search(cls, query): + """Search for the query.""" + raise NotImplementedError + + def update(self): + """Update the object on the API. Children should implement this.""" + raise NotImplementedError + + def upsert(self): + """Create or Update the object on the API. Children should implement. + """ + raise NotImplementedError diff --git a/five9/models/contact.py b/five9/models/contact.py new file mode 100644 index 0000000..07fb0c3 --- /dev/null +++ b/five9/models/contact.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +# Copyright 2017-TODAY LasLabs Inc. +# License MIT (https://opensource.org/licenses/MIT). + +import properties + +from . import BaseModel + + +class Contact(BaseModel): + """This represents all Contact operations.""" + + first_name = properties.String( + 'First name', + ) + last_name = properties.String( + 'Last name', + ) + company = properties.String( + 'Company', + ) + street = properties.String( + 'Street Address', + ) + city = properties.String( + 'City', + ) + state = properties.String( + 'State', + ) + zip = properties.String( + 'Zip', + ) + number1 = properties.String( + 'First phone number', + ) + number1 = properties.String( + 'Second phone number', + ) + number1 = properties.String( + 'Third phone number', + ) diff --git a/five9/models/contact_field.py b/five9/models/contact_field.py new file mode 100644 index 0000000..257e875 --- /dev/null +++ b/five9/models/contact_field.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- +# Copyright 2017-TODAY LasLabs Inc. +# License MIT (https://opensource.org/licenses/MIT). + +import properties + +from zeep.client import ServiceProxy + +from . import BaseModel + + +class ContactField(BaseModel): + """This represents all Contact Field operations.""" + + displayAs = properties.StringChoice( + 'Display options for the data in the Agent desktop', + choices=['Short', 'Long', 'Invisible'], + descriptions={ + 'Short': 'Half line.', + 'Long': 'Full line.', + 'Invisible': 'Not represented.', + }, + ) + mapTo = properties.StringChoice( + 'Map of the system information inot the field. The field is ' + 'updated when a disposition is set.', + choices=['None', + 'LastAgent', + 'LastDisposition', + 'LastSystemDisposition', + 'LastAgentDisposition', + 'LastDispositionDateTime', + 'LastSystemDispositionDateTime', + 'LastAgentDispositionDateTime', + 'LastAttemptedNumber', + 'LastAttemptedNumberN1N2N3', + 'LastCampaign', + 'AttemptsForLastCampaign', + 'LastList', + 'CreatedDateTime', + 'LastModifiedDateTime', + ], + descriptions={ + 'None': 'No mapping.', + 'LastAgent': 'Name of last logged-in agent.', + 'LastDisposition': 'Name of last disposition assigned to a call.', + 'LastSystemDisposition': 'Name of last system disposition ' + 'assigned to a call.', + 'LastAgentDisposition': 'Name of last disposition assigned by an ' + 'agent to a call.', + 'LastDispositionDateTime': 'Date and time of last disposition ' + 'assigned to a call', + 'LastSystemDispositionDateTime': 'Date and time of last system ' + 'disposition assigned to a ' + 'call.', + 'LastAgentDispositionDateTime': 'Date and time of last ' + 'disposition assigned by an ' + 'agent to a call.', + 'LastAttemptedNumber': 'Last number attempted by the dialer or ' + 'by an agent.', + 'LastAttemptedNumberN1N2N3': 'Index of the last dialed phone ' + 'number in the record: number1, ' + 'number2 or number3.', + 'LastCampaign': 'Name of the last campaign that dialed the ' + 'record.', + 'AttemptsForLastCampaign': 'Dialing attempts for last campaign.', + 'LastList': 'Name of last list used.', + 'CreatedDateTime': 'Date and time of record creation in the ' + 'contact database.', + 'LastModifiedDateTime': 'Date and time of record modification in ' + 'the contact database.', + }, + ) + name = properties.String( + 'Name of the contact field.', + ) + restrictions = properties.basic.DynamicProperty( + 'Restrictions imposed on the data that can be stored in this field. ' + 'Not currently mapped.', + ) + system = properties.Bool( + 'Whether this field is set by the system or an agent. \n\n' + '• ``True``: Field set by system. \n' + '• ``False``: Field set by agent. \n' + ) + type = properties.StringChoice( + 'The type of data stored in this field.', + choices=['STRING', + 'NUMBER', + 'DATE', + 'TIME', + 'DATE_TIME', + 'CURRENCY', + 'BOOLEAN', + 'PERCENT', + 'EMAIL', + 'URL', + 'PHONE', + 'TIME_PERIOD', + ], + ) + + def create(self): + return self.five9.configuration.createContactField( + field=self.to_api(), + ) + + def delete(self): + return self.five9.configuration.deleteContactField( + fieldName=self.name, + ) + + @classmethod + @BaseModel.api + def get(cls, name_pattern): + response = cls.five9.configuration.getContactFields( + namePattern=name_pattern, + ) + return [cls(**response)] + + def update(self): + return self.five9.configuration.modifyContactField( + field=self.to_api() + )