Repository: softlayer/softlayer-python
Pull request #519 (2015-04-11 15:37:57)

Ticket CLI Improvements
* The `limit` option for ticket updates (as shown in `slcli ticket detail <id> --count=10`) will now the last updates instead of the first ones. For example, if there are 20 updates on a ticket and the user specifies a `--count=5` then updates 16-20 will be shown.
* `slcli ticket detail <id>` will now show the author for each update at the beginning of the update.
* Ticket listing and detail commands now show ticket status
* Several small style issues as well to bring this in line with other commands.


Patch:
diff --git a/SoftLayer/API.py b/SoftLayer/API.py
index 2e86aad4..5b75fd96 100644
--- a/SoftLayer/API.py
+++ b/SoftLayer/API.py
@@ -12,20 +12,11 @@
 from SoftLayer import consts
 from SoftLayer import transports
 
-# pylint: disable=invalid-name
-
-
 API_PUBLIC_ENDPOINT = consts.API_PUBLIC_ENDPOINT
 API_PRIVATE_ENDPOINT = consts.API_PRIVATE_ENDPOINT
-__all__ = [
-    'create_client_from_env',
-    'Client',
-    'BaseClient',
-    'API_PUBLIC_ENDPOINT',
-    'API_PRIVATE_ENDPOINT',
-]
-
-VALID_CALL_ARGS = set((
+__all__ = ['Client', 'API_PUBLIC_ENDPOINT', 'API_PRIVATE_ENDPOINT']
+
+VALID_CALL_ARGS = set([
     'id',
     'mask',
     'filter',
@@ -34,22 +25,11 @@
     'raw_headers',
     'limit',
     'offset',
-))
+])
 
 
-def create_client_from_env(username=None,
-                           api_key=None,
-                           endpoint_url=None,
-                           timeout=None,
-                           auth=None,
-                           config_file=None,
-                           proxy=None,
-                           user_agent=None,
-                           transport=None):
-    """Creates a SoftLayer API client using your environment.
-
-    Settings are loaded via keyword arguments, environemtal variables and
-    config file.
+class Client(object):
+    """A SoftLayer API client.
 
     :param username: an optional API username if you wish to bypass the
         package's built-in username
@@ -71,62 +51,38 @@ def create_client_from_env(username=None,
     Usage:
 
         >>> import SoftLayer
-        >>> client = SoftLayer.create_client_from_env()
+        >>> client = SoftLayer.Client(username="username", api_key="api_key")
         >>> resp = client['Account'].getObject()
         >>> resp['companyName']
         'Your Company'
 
     """
-    settings = config.get_client_settings(username=username,
-                                          api_key=api_key,
-                                          endpoint_url=endpoint_url,
-                                          timeout=timeout,
-                                          proxy=proxy,
-                                          config_file=config_file)
-
-    # Default the transport to use XMLRPC
-    if transport is None:
-        transport = transports.XmlRpcTransport(
-            endpoint_url=settings.get('endpoint_url'),
-            proxy=settings.get('proxy'),
-            timeout=settings.get('timeout'),
-            user_agent=user_agent,
-        )
-
-    # If we have enough information to make an auth driver, let's do it
-    if auth is None and settings.get('username') and settings.get('api_key'):
-
-        auth = slauth.BasicAuthentication(
-            settings.get('username'),
-            settings.get('api_key'),
-        )
-
-    return BaseClient(auth=auth, transport=transport)
-
-
-def Client(**kwargs):
-    """Get a SoftLayer API Client using environmental settings.
-
-    Deprecated in favor of create_client_from_env()
-    """
-    warnings.warn("use SoftLayer.create_client_from_env() instead",
-                  DeprecationWarning)
-    return create_client_from_env(**kwargs)
-
-
-class BaseClient(object):
-    """Base SoftLayer API client.
-
-    :param auth: auth driver that looks like SoftLayer.auth.AuthenticationBase
-    :param transport: An object that's callable with this signature:
-                      transport(SoftLayer.transports.Request)
-    """
-
     _prefix = "SoftLayer_"
 
-    def __init__(self, auth=None, transport=None):
-        self.auth = auth
-        self.transport = transport
+    def __init__(self, username=None, api_key=None, endpoint_url=None,
+                 timeout=None, auth=None, config_file=None, proxy=None,
+                 user_agent=None, transport=None):
+
+        settings = config.get_client_settings(username=username,
+                                              api_key=api_key,
+                                              endpoint_url=endpoint_url,
+                                              timeout=timeout,
+                                              auth=auth,
+                                              proxy=proxy,
+                                              config_file=config_file)
+        self.auth = settings.get('auth')
+
+        self.endpoint_url = (settings.get('endpoint_url') or
+                             API_PUBLIC_ENDPOINT).rstrip('/')
+        self.transport = transport or transports.XmlRpcTransport()
+
+        self.timeout = None
+        if settings.get('timeout'):
+            self.timeout = float(settings.get('timeout'))
+        self.proxy = None
+        if settings.get('proxy'):
+            self.proxy = settings.get('proxy')
+        self.user_agent = user_agent
 
     def authenticate_with_password(self, username, password,
                                    security_question_id=None,
@@ -156,7 +112,7 @@ def __getitem__(self, name):
 
         Usage:
             >>> import SoftLayer
-            >>> client = SoftLayer.create_client_from_env()
+            >>> client = SoftLayer.Client()
             >>> client['Account']
             <Service: Account>
 
@@ -176,7 +132,7 @@ def call(self, service, method, *args, **kwargs):
 
         Usage:
             >>> import SoftLayer
-            >>> client = SoftLayer.create_client_from_env()
+            >>> client = SoftLayer.Client()
             >>> client['Account'].getVirtualGuests(mask="id", limit=10)
             [...]
 
@@ -189,10 +145,13 @@ def call(self, service, method, *args, **kwargs):
             raise TypeError(
                 'Invalid keyword arguments: %s' % ','.join(invalid_kwargs))
 
-        if self._prefix and not service.startswith(self._prefix):
+        if not service.startswith(self._prefix):
             service = self._prefix + service
 
-        http_headers = {}
+        http_headers = {
+            'User-Agent': self.user_agent or consts.USER_AGENT,
+            'Content-Type': 'application/xml',
+        }
 
         if kwargs.get('compress', True):
             http_headers['Accept'] = '*/*'
@@ -202,10 +161,13 @@ def call(self, service, method, *args, **kwargs):
             http_headers.update(kwargs.get('raw_headers'))
 
         request = transports.Request()
+        request.endpoint = self.endpoint_url
         request.service = service
         request.method = method
         request.args = args
         request.transport_headers = http_headers
+        request.timeout = self.timeout
+        request.proxy = self.proxy
         request.identifier = kwargs.get('id')
         request.mask = kwargs.get('mask')
         request.filter = kwargs.get('filter')
@@ -282,7 +244,8 @@ def iter_call(self, service, method, *args, **kwargs):
                 break
 
     def __repr__(self):
-        return "Client(transport=%r, auth=%r)" % (self.transport, self.auth)
+        return "<Client: endpoint=%s, user=%r>" % (self.endpoint_url,
+                                                   self.auth)
 
     __str__ = __repr__
 
@@ -321,7 +284,7 @@ def call(self, name, *args, **kwargs):
 
         Usage:
             >>> import SoftLayer
-            >>> client = SoftLayer.create_client_from_env()
+            >>> client = SoftLayer.Client()
             >>> client['Account'].getVirtualGuests(mask="id", limit=10)
             [...]
 
@@ -341,7 +304,7 @@ def iter_call(self, name, *args, **kwargs):
 
         Usage:
             >>> import SoftLayer
-            >>> client = SoftLayer.create_client_from_env()
+            >>> client = SoftLayer.Client()
             >>> gen = client['Account'].getVirtualGuests(iter=True)
             >>> for virtual_guest in gen:
             ...     virtual_guest['id']
diff --git a/SoftLayer/CLI/config/__init__.py b/SoftLayer/CLI/config/__init__.py
index 95bbe328..f309ad65 100644
--- a/SoftLayer/CLI/config/__init__.py
+++ b/SoftLayer/CLI/config/__init__.py
@@ -5,15 +5,15 @@
 
 
 def get_settings_from_client(client):
-    """Pull out settings from a SoftLayer.BaseClient instance.
+    """Pull out settings from a SoftLayer.Client instance.
 
-    :param client: SoftLayer.BaseClient instance
+    :param client: SoftLayer.Client instance
     """
     settings = {
         'username': '',
         'api_key': '',
-        'timeout': '',
-        'endpoint_url': '',
+        'timeout': client.timeout or None,
+        'endpoint_url': client.endpoint_url,
     }
     try:
         settings['username'] = client.auth.username
@@ -21,12 +21,6 @@ def get_settings_from_client(client):
     except AttributeError:
         pass
 
-    try:
-        settings['timeout'] = client.transport.transport.timeout
-        settings['endpoint_url'] = client.transport.transport.endpoint_url
-    except AttributeError:
-        pass
-
     return settings
 
 
diff --git a/SoftLayer/CLI/core.py b/SoftLayer/CLI/core.py
index d2278394..af53810a 100644
--- a/SoftLayer/CLI/core.py
+++ b/SoftLayer/CLI/core.py
@@ -144,11 +144,9 @@ def cli(ctx,
             transport = SoftLayer.XmlRpcTransport()
 
         wrapped_transport = SoftLayer.TimingTransport(transport)
-        env.client = SoftLayer.create_client_from_env(
-            proxy=proxy,
-            config_file=config,
-            transport=wrapped_transport,
-        )
+        env.client = SoftLayer.Client(proxy=proxy,
+                                      config_file=config,
+                                      transport=wrapped_transport)
 
 
 @cli.resultcallback()
diff --git a/SoftLayer/CLI/ticket/__init__.py b/SoftLayer/CLI/ticket/__init__.py
index 876d166d..6d3fdf9c 100644
--- a/SoftLayer/CLI/ticket/__init__.py
+++ b/SoftLayer/CLI/ticket/__init__.py
@@ -1,7 +1,6 @@
 """Support tickets."""
 
 from SoftLayer.CLI import formatting
-from SoftLayer import utils
 
 import click
 
@@ -17,27 +16,43 @@ def get_ticket_results(mgr, ticket_id, update_count=1):
     :returns: a KeyValue table containing the details of the ticket
 
     """
-    result = mgr.get_ticket(ticket_id)
-    result = utils.NestedDict(result)
+    ticket = mgr.get_ticket(ticket_id)
 
     table = formatting.KeyValueTable(['Name', 'Value'])
     table.align['Name'] = 'r'
     table.align['Value'] = 'l'
 
-    table.add_row(['id', result['id']])
-    table.add_row(['title', result['title']])
-    if result['assignedUser']:
-        table.add_row(['assignedUser',
-                       "%s %s" % (result['assignedUser']['firstName'],
-                                  result['assignedUser']['lastName'])])
-    table.add_row(['createDate', result['createDate']])
-    table.add_row(['lastEditDate', result['lastEditDate']])
-
-    total_update_count = result['updateCount']
-    count = min(total_update_count, update_count)
-    for i, update in enumerate(result['updates'][:count]):
+    table.add_row(['id', ticket['id']])
+    table.add_row(['title', ticket['title']])
+    if ticket.get('assignedUser'):
+        user = ticket['assignedUser']
+        table.add_row([
+            'user',
+            "%s %s" % (user.get('firstName'), user.get('lastName')),
+        ])
+
+    table.add_row(['status', ticket['status']['name']])
+    table.add_row(['created', ticket.get('createDate')])
+    table.add_row(['edited', ticket.get('lastEditDate')])
+
+    # Only show up to the specified update count
+    updates = ticket.get('updates', [])
+    count = min(len(updates), update_count)
+    count_offset = len(updates) - count + 1  # Display as one-indexed
+    for i, update in enumerate(updates[-count:]):
+        wrapped_entry = ""
+
+        # Add user details (fields are different between employee and users)
+        editor = update.get('editor')
+        if editor:
+            if editor.get('displayName'):
+                wrapped_entry += "By %s (Employee)\n" % (editor['displayName'])
+            if editor.get('firstName'):
+                wrapped_entry += "By %s %s\n" % (editor.get('firstName'),
+                                                 editor.get('lastName'))
+
         # NOTE(kmcdonald): Windows new-line characters need to be stripped out
-        wrapped_entry = click.wrap_text(update['entry'].replace('\r', ''))
-        table.add_row(['Update %s' % (i + 1,), wrapped_entry])
+        wrapped_entry += click.wrap_text(update['entry'].replace('\r', ''))
+        table.add_row(['update %s' % (count_offset + i,), wrapped_entry])
 
     return table
diff --git a/SoftLayer/CLI/ticket/list.py b/SoftLayer/CLI/ticket/list.py
index 893d87ce..6ca5cdcd 100644
--- a/SoftLayer/CLI/ticket/list.py
+++ b/SoftLayer/CLI/ticket/list.py
@@ -11,30 +11,31 @@
 
 @click.command()
 @click.option('--open / --closed', 'is_open',
-              help="Display only open or closed tickets")
+              help="Display only open or closed tickets",
+              default=True)
 @environment.pass_env
 def cli(env, is_open):
     """List tickets."""
     ticket_mgr = SoftLayer.TicketManager(env.client)
 
-    tickets = ticket_mgr.list_tickets(open_status=not is_open,
-                                      closed_status=is_open)
+    tickets = ticket_mgr.list_tickets(open_status=is_open,
+                                      closed_status=not is_open)
 
-    table = formatting.Table(['id', 'assigned user', 'title',
-                              'creation date', 'last edit date'])
+    table = formatting.Table(['id', 'assigned_user', 'title',
+                              'last_edited', 'status'])
 
     for ticket in tickets:
-        user = 'N/A'
+        user = formatting.blank()
         if ticket.get('assignedUser'):
             user = "%s %s" % (ticket['assignedUser']['firstName'],
-                              ticket['assignedUser']['lastName']),
+                              ticket['assignedUser']['lastName'])
 
         table.add_row([
             ticket['id'],
             user,
             click.wrap_text(ticket['title']),
-            ticket['createDate'],
-            ticket['lastEditDate']
+            ticket['lastEditDate'],
+            ticket['status']['name'],
         ])
 
     return table
diff --git a/SoftLayer/__init__.py b/SoftLayer/__init__.py
index bcebac61..fef80242 100644
--- a/SoftLayer/__init__.py
+++ b/SoftLayer/__init__.py
@@ -6,8 +6,7 @@
     Usage:
 
         >>> import SoftLayer
-        >>> client = SoftLayer.create_client_from_env(username="username",
-                                                      api_key="api_key")
+        >>> client = SoftLayer.Client(username="username", api_key="api_key")
         >>> resp = client['Account'].getObject()
         >>> resp['companyName']
         'Your Company'
@@ -28,6 +27,5 @@
 __author__ = 'SoftLayer Technologies, Inc.'
 __license__ = 'MIT'
 __copyright__ = 'Copyright 2014 SoftLayer Technologies, Inc.'
-__all__ = ['BaseClient', 'create_client_from_env', 'Client',
-           'BasicAuthentication', 'SoftLayerError', 'SoftLayerAPIError',
-           'API_PUBLIC_ENDPOINT', 'API_PRIVATE_ENDPOINT']
+__all__ = ['Client', 'BasicAuthentication', 'SoftLayerError',
+           'SoftLayerAPIError', 'API_PUBLIC_ENDPOINT', 'API_PRIVATE_ENDPOINT']
diff --git a/SoftLayer/auth.py b/SoftLayer/auth.py
index 66049232..191351e0 100644
--- a/SoftLayer/auth.py
+++ b/SoftLayer/auth.py
@@ -7,12 +7,7 @@
 """
 # pylint: disable=no-self-use
 
-__all__ = [
-    'BasicAuthentication',
-    'TokenAuthentication',
-    'BasicHTTPAuthentication',
-    'AuthenticationBase',
-]
+__all__ = ['BasicAuthentication', 'TokenAuthentication', 'AuthenticationBase']
 
 
 class AuthenticationBase(object):
@@ -56,7 +51,7 @@ def get_request(self, request):
         return request
 
     def __repr__(self):
-        return "TokenAuthentication(%r)" % self.user_id
+        return "<TokenAuthentication: %s %s>" % (self.user_id, self.auth_token)
 
 
 class BasicAuthentication(AuthenticationBase):
@@ -78,24 +73,4 @@ def get_request(self, request):
         return request
 
     def __repr__(self):
-        return "BasicAuthentication(username=%r)" % self.username
-
-
-class BasicHTTPAuthentication(AuthenticationBase):
-    """Token-based authentication class.
-
-        :param username str: a user's username
-        :param api_key str: a user's API key
-    """
-    def __init__(self, username, api_key):
-        self.username = username
-        self.api_key = api_key
-
-    def get_request(self, request):
-        """Sets token-based auth headers."""
-        request.transport_user = self.username
-        request.transport_password = self.api_key
-        return request
-
-    def __repr__(self):
-        return "BasicHTTPAuthentication(username=%r)" % self.username
+        return "<BasicAuthentication: %s>" % (self.username)
diff --git a/SoftLayer/config.py b/SoftLayer/config.py
index 8c679e6c..81030517 100644
--- a/SoftLayer/config.py
+++ b/SoftLayer/config.py
@@ -8,6 +8,7 @@
 import os
 import os.path
 
+from SoftLayer import auth
 from SoftLayer import utils
 
 
@@ -16,13 +17,17 @@ def get_client_settings_args(**kwargs):
 
         :param \\*\\*kwargs: Arguments that are passed into the client instance
     """
-    return {
+    settings = {
         'endpoint_url': kwargs.get('endpoint_url'),
-        'timeout': float(kwargs.get('timeout') or 0),
+        'timeout': kwargs.get('timeout'),
+        'auth': kwargs.get('auth'),
         'proxy': kwargs.get('proxy'),
-        'username': kwargs.get('username'),
-        'api_key': kwargs.get('api_key'),
     }
+    username = kwargs.get('username')
+    api_key = kwargs.get('api_key')
+    if username and api_key and not settings['auth']:
+        settings['auth'] = auth.BasicAuthentication(username, api_key)
+    return settings
 
 
 def get_client_settings_env(**_):
@@ -30,12 +35,14 @@ def get_client_settings_env(**_):
 
         :param \\*\\*kwargs: Arguments that are passed into the client instance
     """
+    username = os.environ.get('SL_USERNAME')
+    api_key = os.environ.get('SL_API_KEY')
+    proxy = os.environ.get('https_proxy')
 
-    return {
-        'proxy': os.environ.get('https_proxy'),
-        'username': os.environ.get('SL_USERNAME'),
-        'api_key': os.environ.get('SL_API_KEY'),
-    }
+    config = {'proxy': proxy}
+    if username and api_key:
+        config['auth'] = auth.BasicAuthentication(username, api_key)
+    return config
 
 
 def get_client_settings_config_file(**kwargs):
@@ -51,7 +58,7 @@ def get_client_settings_config_file(**kwargs):
         'username': '',
         'api_key': '',
         'endpoint_url': '',
-        'timeout': '0',
+        'timeout': '',
         'proxy': '',
     })
     config.read(config_files)
@@ -59,14 +66,16 @@ def get_client_settings_config_file(**kwargs):
     if not config.has_section('softlayer'):
         return
 
-    return {
+    settings = {
         'endpoint_url': config.get('softlayer', 'endpoint_url'),
-        'timeout': config.getfloat('softlayer', 'timeout'),
+        'timeout': config.get('softlayer', 'timeout'),
         'proxy': config.get('softlayer', 'proxy'),
-        'username': config.get('softlayer', 'username'),
-        'api_key': config.get('softlayer', 'api_key'),
     }
-
+    username = config.get('softlayer', 'username')
+    api_key = config.get('softlayer', 'api_key')
+    if username and api_key:
+        settings['auth'] = auth.BasicAuthentication(username, api_key)
+    return settings
 
 SETTING_RESOLVERS = [get_client_settings_args,
                      get_client_settings_env,
@@ -77,7 +86,8 @@ def get_client_settings(**kwargs):
     """Parse client settings.
 
     Parses settings from various input methods, preferring earlier values
-    to later ones. The settings currently come from explicit user arguments,
+    to later ones. Once an 'auth' value is found, it returns the gathered
+    settings. The settings currently come from explicit user arguments,
     environmental variables and config files.
 
         :param \\*\\*kwargs: Arguments that are passed into the client instance
@@ -88,5 +98,6 @@ def get_client_settings(**kwargs):
         if settings:
             settings.update((k, v) for k, v in all_settings.items() if v)
             all_settings = settings
-
+            if all_settings.get('auth'):
+                break
     return all_settings
diff --git a/SoftLayer/managers/metadata.py b/SoftLayer/managers/metadata.py
index 38aa4db5..acad47b7 100644
--- a/SoftLayer/managers/metadata.py
+++ b/SoftLayer/managers/metadata.py
@@ -43,7 +43,7 @@ class MetadataManager(object):
         Usage:
 
             >>> import SoftLayer
-            >>> client = SoftLayer.create_client_from_env()
+            >>> client = SoftLayer.Client()
             >>> from SoftLayer import MetadataManager
             >>> meta = MetadataManager(client)
             >>> meta.get('datacenter')
@@ -56,13 +56,11 @@ class MetadataManager(object):
     attribs = METADATA_MAPPING
 
     def __init__(self, client=None, timeout=5):
+        url = consts.API_PRIVATE_ENDPOINT_REST.rstrip('/')
         if client is None:
-            transport = transports.RestTransport(
-                timeout=timeout,
-                endpoint_url=consts.API_PRIVATE_ENDPOINT_REST,
-            )
-            client = SoftLayer.BaseClient(transport=transport)
-
+            client = SoftLayer.Client(endpoint_url=url,
+                                      timeout=timeout,
+                                      transport=transports.RestTransport())
         self.client = client
 
     def get(self, name, param=None):
@@ -82,13 +80,10 @@ def get(self, name, param=None):
                 raise exceptions.SoftLayerError(
                     'Parameter required to get this attribute.')
 
-        params = tuple()
-        if param is not None:
-            params = (param,)
         try:
             return self.client.call('Resource_Metadata',
                                     self.attribs[name]['call'],
-                                    *params)
+                                    id=param)
         except exceptions.SoftLayerAPIError as ex:
             if ex.faultCode == 404:
                 return None
diff --git a/SoftLayer/managers/ticket.py b/SoftLayer/managers/ticket.py
index 6108358d..dd1be176 100644
--- a/SoftLayer/managers/ticket.py
+++ b/SoftLayer/managers/ticket.py
@@ -26,8 +26,8 @@ def list_tickets(self, open_status=True, closed_status=True):
         :param boolean open_status: include open tickets
         :param boolean closed_status: include closed tickets
         """
-        mask = ('mask[id, title, assignedUser[firstName, lastName],'
-                'createDate,lastEditDate,accountId]')
+        mask = ('id, title, assignedUser[firstName, lastName],'
+                'createDate,lastEditDate,accountId,status')
 
         call = 'getTickets'
         if not all([open_status, closed_status]):
@@ -36,8 +36,7 @@ def list_tickets(self, open_status=True, closed_status=True):
             elif closed_status:
                 call = 'getClosedTickets'
 
-        func = getattr(self.account, call)
-        return func(mask=mask)
+        return self.client.call('Account', call, mask=mask)
 
     def list_subjects(self):
         """List all tickets."""
@@ -51,8 +50,8 @@ def get_ticket(self, ticket_id):
                   the specified ticket.
 
         """
-        mask = ('mask[id, title, assignedUser[firstName, lastName],'
-                'createDate,lastEditDate,updates[entry],updateCount]')
+        mask = ('id, title, assignedUser[firstName, lastName],status,'
+                'createDate,lastEditDate,updates[entry,editor],updateCount')
         return self.ticket.getObject(id=ticket_id, mask=mask)
 
     def create_ticket(self, title=None, body=None, subject=None):
@@ -79,6 +78,4 @@ def update_ticket(self, ticket_id=None, body=None):
         :param integer ticket_id: the id of the ticket to update
         :param string body: entry to update in the ticket
         """
-
-        ticket = self.ticket.getObject(id=ticket_id)
-        return self.ticket.edit(ticket, body, id=ticket_id)
+        return self.ticket.addUpdate({'entry': body}, id=ticket_id)
diff --git a/SoftLayer/managers/vs.py b/SoftLayer/managers/vs.py
index 5739ebc5..64ae9577 100644
--- a/SoftLayer/managers/vs.py
+++ b/SoftLayer/managers/vs.py
@@ -64,7 +64,7 @@ def list_instances(self, hourly=True, monthly=True, tags=None, cpus=None,
            # SL_USERNAME = YOUR_USERNAME
            # SL_API_KEY = YOUR_API_KEY
            import SoftLayer
-           client = SoftLayer.create_client_from_env()
+           client = SoftLayer.Client()
 
            mgr = SoftLayer.VSManager(client)
            for vsi in mgr.list_instances(hourly=True, datacenter='dal05'):
@@ -154,7 +154,7 @@ def get_instance(self, instance_id, **kwargs):
            # SL_USERNAME = YOUR_USERNAME
            # SL_API_KEY = YOUR_API_KEY
            import SoftLayer
-           client = SoftLayer.create_client_from_env()
+           client = SoftLayer.Client()
 
            mgr = SoftLayer.VSManager(client)
            vsi = mgr.get_instance(12345)
@@ -226,7 +226,7 @@ def cancel_instance(self, instance_id):
            # SL_USERNAME = YOUR_USERNAME
            # SL_API_KEY = YOUR_API_KEY
            import SoftLayer
-           client = SoftLayer.create_client_from_env()
+           client = SoftLayer.Client()
 
            mgr = SoftLayer.VSManager(client)
            mgr.cancel_instance(12345)
@@ -249,7 +249,7 @@ def reload_instance(self, instance_id, post_uri=None, ssh_keys=None):
            # SL_USERNAME = YOUR_USERNAME
            # SL_API_KEY = YOUR_API_KEY
            import SoftLayer
-           client = SoftLayer.create_client_from_env()
+           client = SoftLayer.Client()
 
            post_uri = 'https://somehost.com/bootstrap.sh'
            mgr = SoftLayer.VSManager(client)
@@ -606,7 +606,7 @@ def upgrade(self, instance_id, cpus=None, memory=None,
 
            # Upgrade instance 12345 to 4 CPUs and 4 GB of memory
            import SoftLayer
-           client = SoftLayer.create_client_from_env()
+           client = SoftLayer.Client(config="~/.softlayer")
 
            mgr = SoftLayer.VSManager(client)
            mgr.upgrade(12345, cpus=4, memory=4)
diff --git a/SoftLayer/transports.py b/SoftLayer/transports.py
index b30d5552..d73e59b7 100644
--- a/SoftLayer/transports.py
+++ b/SoftLayer/transports.py
@@ -5,7 +5,6 @@
 
     :license: MIT, see LICENSE for more details.
 """
-from SoftLayer import consts
 from SoftLayer import exceptions
 from SoftLayer import utils
 
@@ -20,19 +19,20 @@
 # transports.Request does have a lot of instance attributes. :(
 # pylint: disable=too-many-instance-attributes
 
-__all__ = [
-    'Request',
-    'XmlRpcTransport',
-    'RestTransport',
-    'TimingTransport',
-    'FixtureTransport',
-]
+__all__ = ['Request',
+           'XmlRpcTransport',
+           'RestTransport',
+           'TimingTransport',
+           'FixtureTransport']
 
 
 class Request(object):
     """Transport request object."""
 
     def __init__(self):
+        #: The SoftLayer endpoint address.
+        self.endpoint = None
+
         #: API service name. E.G. SoftLayer_Account
         self.service = None
 
@@ -45,15 +45,15 @@ def __init__(self):
         #: API headers, used for authentication, masks, limits, offsets, etc.
         self.headers = {}
 
-        #: Transport user.
-        self.transport_user = None
-
-        #: Transport password.
-        self.transport_password = None
-
         #: Transport headers.
         self.transport_headers = {}
 
+        #: Integer timeout.
+        self.timeout = None
+
+        #: URL to proxy API requests to.
+        self.proxy = None
+
         #: Boolean specifying if the server certificate should be verified.
         self.verify = True
 
@@ -78,69 +78,58 @@ def __init__(self):
 
 class XmlRpcTransport(object):
     """XML-RPC transport."""
-    def __init__(self,
-                 endpoint_url=None,
-                 timeout=None,
-                 proxy=None,
-                 user_agent=None):
-
-        self.endpoint_url = (endpoint_url or
-                             consts.API_PUBLIC_ENDPOINT).rstrip('/')
-        self.timeout = timeout or None
-        self.proxy = proxy
-        self.user_agent = user_agent or consts.USER_AGENT
 
     def __call__(self, request):
         """Makes a SoftLayer API call against the XML-RPC endpoint.
 
         :param request request: Request object
         """
-        largs = list(request.args)
+        try:
+            largs = list(request.args)
 
-        headers = request.headers
+            headers = request.headers
 
-        if request.identifier is not None:
-            header_name = request.service + 'InitParameters'
-            headers[header_name] = {'id': request.identifier}
+            if request.identifier is not None:
+                header_name = request.service + 'InitParameters'
+                headers[header_name] = {'id': request.identifier}
 
-        if request.mask is not None:
-            headers.update(_format_object_mask(request.mask, request.service))
+            if request.mask is not None:
+                headers.update(_format_object_mask(request.mask,
+                                                   request.service))
 
-        if request.filter is not None:
-            headers['%sObjectFilter' % request.service] = request.filter
+            if request.filter is not None:
+                headers['%sObjectFilter' % request.service] = request.filter
 
-        if request.limit:
-            headers['resultLimit'] = {
-                'limit': request.limit,
-                'offset': request.offset or 0,
-            }
+            if request.limit:
+                headers['resultLimit'] = {
+                    'limit': request.limit,
+                    'offset': request.offset or 0,
+                }
 
-        largs.insert(0, {'headers': headers})
-        request.transport_headers.setdefault('Content-Type', 'application/xml')
-        request.transport_headers.setdefault('User-Agent', self.user_agent)
+            largs.insert(0, {'headers': headers})
 
-        url = '/'.join([self.endpoint_url, request.service])
-        payload = utils.xmlrpc_client.dumps(tuple(largs),
-                                            methodname=request.method,
-                                            allow_none=True)
-        LOGGER.debug("=== REQUEST ===")
-        LOGGER.info('POST %s', url)
-        LOGGER.debug(request.transport_headers)
-        LOGGER.debug(payload)
+            url = '/'.join([request.endpoint, request.service])
+            payload = utils.xmlrpc_client.dumps(tuple(largs),
+                                                methodname=request.method,
+                                                allow_none=True)
+            LOGGER.debug("=== REQUEST ===")
+            LOGGER.info('POST %s', url)
+            LOGGER.debug(request.transport_headers)
+            LOGGER.debug(payload)
 
-        try:
             response = requests.request('POST', url,
                                         data=payload,
                                         headers=request.transport_headers,
-                                        timeout=self.timeout,
+                                        timeout=request.timeout,
                                         verify=request.verify,
                                         cert=request.cert,
-                                        proxies=_proxies_dict(self.proxy))
+                                        proxies=_proxies_dict(request.proxy))
             LOGGER.debug("=== RESPONSE ===")
             LOGGER.debug(response.headers)
             LOGGER.debug(response.content)
             response.raise_for_status()
-            return utils.xmlrpc_client.loads(response.content)[0][0]
+            result = utils.xmlrpc_client.loads(response.content,)[0][0]
+            return result
         except utils.xmlrpc_client.Fault as ex:
             # These exceptions are formed from the XML-RPC spec
             # http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
@@ -165,23 +154,7 @@ def __call__(self, request):
 
 
 class RestTransport(object):
-    """REST transport.
-
-    Currently only supports GET requests (no POST, PUT, DELETE) and lacks
-    support for masks, filters, limits and offsets.
-    """
-
-    def __init__(self,
-                 endpoint_url=None,
-                 timeout=None,
-                 proxy=None,
-                 user_agent=None):
-
-        self.endpoint_url = (endpoint_url or
-                             consts.API_PUBLIC_ENDPOINT_REST).rstrip('/')
-        self.timeout = timeout or None
-        self.proxy = proxy
-        self.user_agent = user_agent or consts.USER_AGENT
+    """REST transport."""
 
     def __call__(self, request):
         """Makes a SoftLayer API call against the REST endpoint.
@@ -190,17 +163,9 @@ def __call__(self, request):
 
         :param request request: Request object
         """
-        url_parts = [self.endpoint_url, request.service]
+        url_parts = [request.endpoint, request.service, request.method]
         if request.identifier is not None:
             url_parts.append(str(request.identifier))
-        if request.method is not None:
-            url_parts.append(request.method)
-        for arg in request.args:
-            url_parts.append(str(arg))
-
-        request.transport_headers.setdefault('Content-Type',
-                                             'application/json')
-        request.transport_headers.setdefault('User-Agent', self.user_agent)
 
         url = '%s.%s' % ('/'.join(url_parts), 'json')
 
@@ -210,10 +175,10 @@ def __call__(self, request):
         try:
             resp = requests.request('GET', url,
                                     headers=request.transport_headers,
-                                    timeout=self.timeout,
+                                    timeout=request.timeout,
                                     verify=request.verify,
                                     cert=request.cert,
-                                    proxies=_proxies_dict(self.proxy))
+                                    proxies=_proxies_dict(request.proxy))
             LOGGER.debug("=== RESPONSE ===")
             LOGGER.debug(resp.headers)
             LOGGER.debug(resp.content)
diff --git a/docs/api/client.rst b/docs/api/client.rst
index 320e7a53..0a38a2c8 100644
--- a/docs/api/client.rst
+++ b/docs/api/client.rst
@@ -14,7 +14,7 @@ that will help to use the SoftLayer API.
 ::
 
 	>>> import SoftLayer
-	>>> client = SoftLayer.create_client_from_env(username="username", api_key="api_key")
+	>>> client = SoftLayer.Client(username="username", api_key="api_key")
 	>>> resp = client['Account'].getObject()
 	>>> resp['companyName']
 	'Your Company'
@@ -30,7 +30,7 @@ Creating a client instance by passing in the username/api_key:
 ::
 
     import SoftLayer
-    client = SoftLayer.create_client_from_env(username='YOUR_USERNAME', api_key='YOUR_API_KEY')
+    client = SoftLayer.Client(username='YOUR_USERNAME', api_key='YOUR_API_KEY')
 
 Creating a client instance with environmental variables set:
 ::
@@ -39,17 +39,17 @@ Creating a client instance with environmental variables set:
     $ export SL_API_KEY=YOUR_API_KEY
     $ python
     >>> import SoftLayer
-    >>> client = SoftLayer.create_client_from_env()
+    >>> client = SoftLayer.Client()
 
 Below is an example of creating a client instance with more options. This will
 create a client with the private API endpoint (only accessible from the
 SoftLayer private network) and a timeout of 4 minutes.
 ::
 
-    client = SoftLayer.create_client_from_env(username='YOUR_USERNAME',
-                                              api_key='YOUR_API_KEY'
-                                              endpoint_url=SoftLayer.API_PRIVATE_ENDPOINT,
-                                              timeout=240)
+    client = SoftLayer.Client(username='YOUR_USERNAME',
+                              api_key='YOUR_API_KEY'
+                              endpoint_url=SoftLayer.API_PRIVATE_ENDPOINT,
+                              timeout=240)
 
 Managers
 --------
@@ -163,6 +163,140 @@ have billing implications.
 
 API Reference
 -------------
+.. autoclass:: SoftLayer.Client
+   :members:
+   :undoc-members:
 
-.. automodule:: SoftLayer
-	:members:
+   .. automethod:: SoftLayer.Client.__getitem__
+
+.. autoclass:: SoftLayer.API.Service
+   :members:
+   :undoc-members:
+
+   .. automethod:: SoftLayer.API.Service.__call__
+
+
+.. automodule:: SoftLayer.exceptions
+   :members:
+   :undoc-members:
+
+
+Backwards Compatibility
+-----------------------
+As of version 3.0 of the API bindings, the old API methods and parameters no
+longer work. Below are examples of converting old API calls to the new ones.
+
+**Get the IP address for an account**
+::
+
+	# Old
+	import SoftLayer.API
+	client = SoftLayer.API.Client('SoftLayer_Account', None, 'username', 'api_key')
+	client.set_object_mask({'ipAddresses' : None})
+	client.set_result_limit(10, offset=10)
+	client.getObject()
+
+	# New
+	import SoftLayer
+	client = SoftLayer.Client(username='username', api_key='api_key')
+	client['Account'].getObject(mask="ipAddresses", limit=10, offset=0)
+
+**Importing the module**
+::
+
+	# Old
+	import SoftLayer.API
+
+	# New
+	import SoftLayer
+
+**Creating a client instance**
+::
+
+	# Old
+	client = SoftLayer.API.Client('SoftLayer_Account', None, 'username', 'api_key')
+
+	# New
+	client = SoftLayer.Client(username='username', api_key='api_key')
+	service = client['Account']
+
+**Making an API call**
+::
+
+	# Old
+	client = SoftLayer.API.Client('SoftLayer_Account', None, 'username', 'api_key')
+	client.getObject()
+
+	# New
+	client = SoftLayer.Client(username='username', api_key='api_key')
+	client['Account'].getObject()
+
+	# Optionally
+	service = client['Account']
+	service.getObject()
+
+**Setting Object Mask**
+::
+
+	# Old
+	client.set_object_mask({'ipAddresses' : None})
+
+	# New
+	client['Account'].getObject(mask="ipAddresses")
+
+**Using Init Parameter**
+::
+
+	# Old
+	client.set_init_parameter(1234)
+
+	# New
+	client['Account'].getObject(id=1234)
+
+**Setting Result Limit and Offset**
+::
+
+	# Old
+	client.set_result_limit(10, offset=10)
+
+	# New
+	client['Account'].getObject(limit=10, offset=10)
+
+**Adding Additional Headers**
+::
+
+	# Old
+	# These headers are persisted accross API calls
+	client.add_header('header', 'value')
+
+	# New
+	# These headers are NOT persisted accross API calls
+	client['Account'].getObject(headers={'header': 'value'})
+
+**Removing Additional Headers**
+::
+
+	# Old
+	client.remove_header('header')
+
+	# New
+	client['Account'].getObject()
+
+**Adding Additional HTTP Headers**
+::
+
+	# Old
+	client.add_raw_header('header', 'value')
+
+	# New
+	client['Account'].getObject(raw_headers={'header': 'value'})
+
+**Changing Authentication Credentials**
+::
+
+	# Old
+	client.set_authentication('username', 'api_key')
+
+	# New
+	client.username = 'username'
+	client.api_key = 'api_key'
diff --git a/docs/config_file.rst b/docs/config_file.rst
index ecea6364..b7952f5e 100644
--- a/docs/config_file.rst
+++ b/docs/config_file.rst
@@ -6,7 +6,7 @@ Configuration File
 The SoftLayer API bindings load your settings from a number of different
 locations.
 
-* Input directly into SoftLayer.create_client_from_env(...)
+* Input directly into SoftLayer.Client(...)
 * Enviorment variables (`SL_USERNAME`, `SL_API_KEY`)
 * Config file locations (`~/.softlayer`, `/etc/softlayer.conf`)
 * Or argument (`-C/path/to/config` or `--config=/path/to/config`)


Test patch:
diff --git a/SoftLayer/testing/__init__.py b/SoftLayer/testing/__init__.py
index 5f24b483..9e90e801 100644
--- a/SoftLayer/testing/__init__.py
+++ b/SoftLayer/testing/__init__.py
@@ -84,13 +84,18 @@ def tear_down(self):
 
     def setUp(self):  # NOQA
         testtools.TestCase.setUp(self)
+        self.env = environment.Environment()
 
         # Create a crazy mockable, fixture client
+        auth = SoftLayer.BasicAuthentication('default-user', 'default-key')
         self.mocks = MockableTransport(SoftLayer.FixtureTransport())
         self.transport = SoftLayer.TimingTransport(self.mocks)
-        self.client = SoftLayer.BaseClient(transport=self.transport)
+        self.client = SoftLayer.Client(
+            transport=self.transport,
+            auth=auth,
+            timeout=10,
+            endpoint_url='default-endpoint-url')
 
-        self.env = environment.Environment()
         self.env.client = self.client
         return self.set_up()
 
diff --git a/SoftLayer/testing/fixtures/SoftLayer_Account.py b/SoftLayer/testing/fixtures/SoftLayer_Account.py
index 3701cbd7..994d5fb6 100644
--- a/SoftLayer/testing/fixtures/SoftLayer_Account.py
+++ b/SoftLayer/testing/fixtures/SoftLayer_Account.py
@@ -361,6 +361,7 @@
             "id": 1001,
             "name": "Open"
         },
+        'assignedUser': {'firstName': 'John', 'lastName': 'Smith'},
         "statusId": 1001,
         "title": "Cloud Instance Cancellation - 08/01/13"
     }]
diff --git a/SoftLayer/testing/fixtures/SoftLayer_Ticket.py b/SoftLayer/testing/fixtures/SoftLayer_Ticket.py
index d3415457..4933cc62 100644
--- a/SoftLayer/testing/fixtures/SoftLayer_Ticket.py
+++ b/SoftLayer/testing/fixtures/SoftLayer_Ticket.py
@@ -12,7 +12,16 @@
         "name": "Closed"
     },
     "statusId": 1002,
-    "title": "Cloud Instance Cancellation - 08/01/13"
+    "title": "Cloud Instance Cancellation - 08/01/13",
+    'updateCount': 3,
+    'updates': [
+        {'entry': 'a bot says something'},
+        {'entry': 'user says something',
+         'editor': {'firstName': 'John', 'lastName': 'Smith'}},
+        {'entry': 'employee says something',
+         'editor': {'displayName': 'emp1'}},
+
+    ]
 }
 
 createStandardTicket = {
@@ -23,3 +32,4 @@
     "title": "Cloud Instance Cancellation - 08/01/13"
 }
 edit = True
+addUpdate = {}
diff --git a/SoftLayer/tests/CLI/modules/config_tests.py b/SoftLayer/tests/CLI/modules/config_tests.py
index 630cfa4c..8299d7b9 100644
--- a/SoftLayer/tests/CLI/modules/config_tests.py
+++ b/SoftLayer/tests/CLI/modules/config_tests.py
@@ -9,34 +9,23 @@
 
 import mock
 
-import SoftLayer
-from SoftLayer import auth
 from SoftLayer.CLI.config import setup as config
 from SoftLayer.CLI import exceptions
 from SoftLayer import consts
 from SoftLayer import testing
-from SoftLayer import transports
 
 
 class TestHelpShow(testing.TestCase):
 
-    def set_up(self):
-        transport = transports.XmlRpcTransport(
-            endpoint_url='http://endpoint-url',
-        )
-        self.env.client = SoftLayer.BaseClient(
-            transport=transport,
-            auth=auth.BasicAuthentication('username', 'api-key'))
-
     def test_show(self):
         result = self.run_command(['config', 'show'])
 
         self.assertEqual(result.exit_code, 0)
         self.assertEqual(json.loads(result.output),
-                         {'Username': 'username',
-                          'API Key': 'api-key',
-                          'Endpoint URL': 'not set',
-                          'Timeout': 'not set'})
+                         {'Username': 'default-user',
+                          'API Key': 'default-key',
+                          'Endpoint URL': 'default-endpoint-url',
+                          'Timeout': 10.0})
 
 
 class TestHelpSetup(testing.TestCase):
@@ -91,6 +80,7 @@ def test_get_user_input_private(self, input, getpass):
         self.assertEqual(username, 'user')
         self.assertEqual(secret, 'A' * 64)
         self.assertEqual(endpoint_url, consts.API_PRIVATE_ENDPOINT)
+        self.assertEqual(timeout, 10)
 
     @mock.patch('SoftLayer.CLI.environment.Environment.getpass')
     @mock.patch('SoftLayer.CLI.environment.Environment.input')
diff --git a/SoftLayer/tests/CLI/modules/ticket_tests.py b/SoftLayer/tests/CLI/modules/ticket_tests.py
new file mode 100644
index 00000000..5a579c64
--- /dev/null
+++ b/SoftLayer/tests/CLI/modules/ticket_tests.py
@@ -0,0 +1,40 @@
+"""
+    SoftLayer.tests.CLI.modules.ticket_tests
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    :license: MIT, see LICENSE for more details.
+"""
+from SoftLayer import testing
+
+import json
+
+
+class TicketTests(testing.TestCase):
+
+    def test_list(self):
+        result = self.run_command(['ticket', 'list'])
+
+        expected = [{
+            'assigned_user': 'John Smith',
+            'id': 102,
+            'last_edited': '2013-08-01T14:16:47-07:00',
+            'status': 'Open',
+            'title': 'Cloud Instance Cancellation - 08/01/13'}]
+        self.assertEqual(result.exit_code, 0)
+        self.assertEqual(json.loads(result.output), expected)
+
+    def test_detail(self):
+        result = self.run_command(['ticket', 'detail', '1'])
+
+        expected = {
+            'created': '2013-08-01T14:14:04-07:00',
+            'edited': '2013-08-01T14:16:47-07:00',
+            'id': 100,
+            'status': 'Closed',
+            'title': 'Cloud Instance Cancellation - 08/01/13',
+            'update 1': 'a bot says something',
+            'update 2': 'By John Smith\nuser says something',
+            'update 3': 'By emp1 (Employee)\nemployee says something',
+        }
+        self.assertEqual(result.exit_code, 0)
+        self.assertEqual(json.loads(result.output), expected)
diff --git a/SoftLayer/tests/api_tests.py b/SoftLayer/tests/api_tests.py
index f18e5a75..6df36510 100644
--- a/SoftLayer/tests/api_tests.py
+++ b/SoftLayer/tests/api_tests.py
@@ -8,34 +8,38 @@
 
 import SoftLayer
 import SoftLayer.API
+from SoftLayer import consts
 from SoftLayer import testing
-from SoftLayer import transports
+
+TEST_AUTH_HEADERS = {
+    'authenticate': {'username': 'default-user', 'apiKey': 'default-key'}
+}
 
 
 class Inititialization(testing.TestCase):
     def test_init(self):
         client = SoftLayer.Client(username='doesnotexist',
-                                  api_key='issurelywrong',
-                                  timeout=10)
+                                  api_key='issurelywrong', timeout=10)
 
         self.assertIsInstance(client.auth, SoftLayer.BasicAuthentication)
         self.assertEqual(client.auth.username, 'doesnotexist')
         self.assertEqual(client.auth.api_key, 'issurelywrong')
-        self.assertIsNotNone(client.transport)
-        self.assertIsInstance(client.transport, transports.XmlRpcTransport)
-        self.assertEqual(client.transport.timeout, 10)
+        self.assertEqual(client.endpoint_url,
+                         SoftLayer.API_PUBLIC_ENDPOINT.rstrip('/'))
+        self.assertEqual(client.timeout, 10)
 
     @mock.patch('SoftLayer.config.get_client_settings')
     def test_env(self, get_client_settings):
         auth = mock.Mock()
         get_client_settings.return_value = {
+            'auth': auth,
             'timeout': 10,
             'endpoint_url': 'http://endpoint_url/',
         }
-        client = SoftLayer.Client(auth=auth)
+        client = SoftLayer.Client()
         self.assertEqual(client.auth.get_headers(), auth.get_headers())
-        self.assertEqual(client.transport.timeout, 10)
-        self.assertEqual(client.transport.endpoint_url, 'http://endpoint_url')
+        self.assertEqual(client.timeout, 10)
+        self.assertEqual(client.endpoint_url, 'http://endpoint_url')
 
 
 class ClientMethods(testing.TestCase):
@@ -72,12 +76,14 @@ def test_simple_call(self):
 
         self.assertEqual(resp, {"test": "result"})
         self.assert_called_with('SoftLayer_SERVICE', 'METHOD',
+                                endpoint=self.client.endpoint_url,
                                 mask=None,
                                 filter=None,
                                 identifier=None,
                                 args=tuple(),
                                 limit=None,
                                 offset=None,
+                                headers=TEST_AUTH_HEADERS,
                                 )
 
     def test_complex(self):
@@ -95,30 +101,32 @@ def test_complex(self):
 
         self.assertEqual(resp, {"test": "result"})
         self.assert_called_with('SoftLayer_SERVICE', 'METHOD',
+                                endpoint=self.client.endpoint_url,
                                 mask={'object': {'attribute': ''}},
                                 filter=_filter,
                                 identifier=5678,
                                 args=(1234,),
                                 limit=9,
                                 offset=10,
+                                headers=TEST_AUTH_HEADERS,
                                 )
 
-    @mock.patch('SoftLayer.API.BaseClient.iter_call')
+    @mock.patch('SoftLayer.API.Client.iter_call')
     def test_iterate(self, _iter_call):
         self.client['SERVICE'].METHOD(iter=True)
         _iter_call.assert_called_with('SERVICE', 'METHOD')
 
-    @mock.patch('SoftLayer.API.BaseClient.iter_call')
+    @mock.patch('SoftLayer.API.Client.iter_call')
     def test_service_iter_call(self, _iter_call):
         self.client['SERVICE'].iter_call('METHOD', 'ARG')
         _iter_call.assert_called_with('SERVICE', 'METHOD', 'ARG')
 
-    @mock.patch('SoftLayer.API.BaseClient.iter_call')
+    @mock.patch('SoftLayer.API.Client.iter_call')
     def test_service_iter_call_with_chunk(self, _iter_call):
         self.client['SERVICE'].iter_call('METHOD', 'ARG', chunk=2)
         _iter_call.assert_called_with('SERVICE', 'METHOD', 'ARG', chunk=2)
 
-    @mock.patch('SoftLayer.API.BaseClient.call')
+    @mock.patch('SoftLayer.API.Client.call')
     def test_iter_call(self, _call):
         # chunk=100, no limit
         _call.side_effect = [list(range(100)), list(range(100, 125))]
@@ -199,8 +207,10 @@ def test_call_compression_enabled(self):
         self.client['SERVICE'].METHOD(compress=True)
 
         expected_headers = {
+            'Content-Type': 'application/xml',
             'Accept-Encoding': 'gzip, deflate, compress',
             'Accept': '*/*',
+            'User-Agent': consts.USER_AGENT,
         }
         self.assert_called_with('SoftLayer_SERVICE', 'METHOD',
                                 transport_headers=expected_headers)
@@ -213,7 +223,9 @@ def test_call_compression_override(self):
             raw_headers={'Accept-Encoding': 'gzip'})
 
         expected_headers = {
+            'Content-Type': 'application/xml',
             'Accept-Encoding': 'gzip',
+            'User-Agent': consts.USER_AGENT,
         }
         self.assert_called_with('SoftLayer_SERVICE', 'METHOD',
                                 transport_headers=expected_headers)
@@ -233,9 +245,9 @@ def test_init(self, get_client_settings):
     def test_init_with_proxy(self, get_client_settings):
         get_client_settings.return_value = {'proxy': 'http://localhost:3128'}
         client = SoftLayer.Client()
-        self.assertEqual(client.transport.proxy, 'http://localhost:3128')
+        self.assertEqual(client.proxy, 'http://localhost:3128')
 
-    @mock.patch('SoftLayer.API.BaseClient.call')
+    @mock.patch('SoftLayer.API.Client.call')
     def test_authenticate_with_password(self, _call):
         _call.return_value = {
             'userId': 12345,
diff --git a/SoftLayer/tests/auth_tests.py b/SoftLayer/tests/auth_tests.py
index 6bac999f..240af3a9 100644
--- a/SoftLayer/tests/auth_tests.py
+++ b/SoftLayer/tests/auth_tests.py
@@ -63,23 +63,4 @@ def test_repr(self):
         s = repr(self.auth)
         self.assertIn('TokenAuthentication', s)
         self.assertIn('12345', s)
-
-
-class TestBasicHTTPAuthentication(testing.TestCase):
-    def set_up(self):
-        self.auth = auth.BasicHTTPAuthentication('USERNAME', 'APIKEY')
-
-    def test_attribs(self):
-        self.assertEqual(self.auth.username, 'USERNAME')
-        self.assertEqual(self.auth.api_key, 'APIKEY')
-
-    def test_get_request(self):
-        req = transports.Request()
-        authed_req = self.auth.get_request(req)
-        self.assertEqual(authed_req.transport_user, 'USERNAME')
-        self.assertEqual(authed_req.transport_password, 'APIKEY')
-
-    def test_repr(self):
-        s = repr(self.auth)
-        self.assertIn('BasicHTTPAuthentication', s)
-        self.assertIn('USERNAME', s)
+        self.assertIn('TOKEN', s)
diff --git a/SoftLayer/tests/config_tests.py b/SoftLayer/tests/config_tests.py
index 3ef805e9..3db988c0 100644
--- a/SoftLayer/tests/config_tests.py
+++ b/SoftLayer/tests/config_tests.py
@@ -50,10 +50,28 @@ def test_username_api_key(self):
 
         self.assertEqual(result['endpoint_url'], 'http://endpoint/')
         self.assertEqual(result['timeout'], 10)
-        self.assertEqual(result['username'], 'username')
-        self.assertEqual(result['api_key'], 'api_key')
+        self.assertEqual(result['auth'].username, 'username')
+        self.assertEqual(result['auth'].api_key, 'api_key')
         self.assertEqual(result['proxy'], 'https://localhost:3128')
 
+    def test_no_auth(self):
+        result = config.get_client_settings_args()
+
+        self.assertEqual(result, {
+            'endpoint_url': None,
+            'timeout': None,
+            'proxy': None,
+            'auth': None,
+        })
+
+    def test_with_auth(self):
+        auth = mock.Mock()
+        result = config.get_client_settings_args(auth=auth)
+
+        self.assertEqual(result['endpoint_url'], None)
+        self.assertEqual(result['timeout'], None)
+        self.assertEqual(result['auth'], auth)
+
 
 class TestGetClientSettingsEnv(testing.TestCase):
 
@@ -63,8 +81,15 @@ class TestGetClientSettingsEnv(testing.TestCase):
     def test_username_api_key(self):
         result = config.get_client_settings_env()
 
-        self.assertEqual(result['username'], 'username')
-        self.assertEqual(result['api_key'], 'api_key')
+        self.assertEqual(result['auth'].username, 'username')
+        self.assertEqual(result['auth'].api_key, 'api_key')
+
+    @mock.patch.dict('os.environ', {'SL_USERNAME': '', 'SL_API_KEY': ''})
+    def test_no_auth(self):
+        result = config.get_client_settings_env()
+
+        # proxy might get ANY value depending on test env.
+        self.assertEqual(result, {'proxy': mock.ANY})
 
 
 class TestGetClientSettingsConfigFile(testing.TestCase):
@@ -74,10 +99,10 @@ def test_username_api_key(self, config_parser):
         result = config.get_client_settings_config_file()
 
         self.assertEqual(result['endpoint_url'], config_parser().get())
-        self.assertEqual(result['timeout'], config_parser().getfloat())
+        self.assertEqual(result['timeout'], config_parser().get())
         self.assertEqual(result['proxy'], config_parser().get())
-        self.assertEqual(result['username'], config_parser().get())
-        self.assertEqual(result['api_key'], config_parser().get())
+        self.assertEqual(result['auth'].username, config_parser().get())
+        self.assertEqual(result['auth'].api_key, config_parser().get())
 
     @mock.patch('six.moves.configparser.RawConfigParser')
     def test_no_section(self, config_parser):
diff --git a/SoftLayer/tests/functional_tests.py b/SoftLayer/tests/functional_tests.py
index 53abe6eb..52383431 100644
--- a/SoftLayer/tests/functional_tests.py
+++ b/SoftLayer/tests/functional_tests.py
@@ -38,14 +38,13 @@ def test_failed_auth(self):
     def test_no_hostname(self):
         try:
             request = transports.Request()
+            request.endpoint = 'http://notvalidsoftlayer.com'
             request.service = 'SoftLayer_Account'
             request.method = 'getObject'
             request.id = 1234
 
             # This test will fail if 'notvalidsoftlayer.com' becomes a thing
-            transport = transports.XmlRpcTransport(
-                endpoint_url='http://notvalidsoftlayer.com',
-            )
+            transport = transports.XmlRpcTransport()
             transport(request)
         except SoftLayer.SoftLayerAPIError as ex:
             self.assertIn('not known', str(ex))
diff --git a/SoftLayer/tests/managers/metadata_tests.py b/SoftLayer/tests/managers/metadata_tests.py
index 1ee2b3bd..8a5c28cb 100644
--- a/SoftLayer/tests/managers/metadata_tests.py
+++ b/SoftLayer/tests/managers/metadata_tests.py
@@ -21,6 +21,7 @@ def test_get(self):
 
         self.assertEqual('dal01', resp)
         self.assert_called_with('SoftLayer_Resource_Metadata', 'Datacenter',
+                                timeout=10.0,
                                 identifier=None)
 
     def test_no_param(self):
@@ -28,15 +29,14 @@ def test_no_param(self):
 
         self.assertEqual('dal01', resp)
         self.assert_called_with('SoftLayer_Resource_Metadata', 'Datacenter',
-                                identifier=None,
-                                args=tuple())
+                                identifier=None)
 
     def test_w_param(self):
         resp = self.metadata.get('vlans', '1:2:3:4:5')
 
         self.assertEqual([10, 124], resp)
         self.assert_called_with('SoftLayer_Resource_Metadata', 'Vlans',
-                                args=('1:2:3:4:5',))
+                                identifier='1:2:3:4:5')
 
     def test_user_data(self):
         resp = self.metadata.get('user_data')
diff --git a/SoftLayer/tests/managers/ticket_tests.py b/SoftLayer/tests/managers/ticket_tests.py
index 2254816c..60222d11 100644
--- a/SoftLayer/tests/managers/ticket_tests.py
+++ b/SoftLayer/tests/managers/ticket_tests.py
@@ -74,23 +74,6 @@ def test_create_ticket(self):
     def test_update_ticket(self):
         # test a full update
         self.ticket.update_ticket(100, body='Update1')
-        args = (
-            {
-                "accountId": 1234,
-                "assignedUserId": 12345,
-                "createDate": "2013-08-01T14:14:04-07:00",
-                "id": 100,
-                "lastEditDate": "2013-08-01T14:16:47-07:00",
-                "lastEditType": "AUTO",
-                "modifyDate": "2013-08-01T14:16:47-07:00",
-                "status": {
-                    "id": 1002,
-                    "name": "Closed"
-                },
-                "statusId": 1002,
-                "title": "Cloud Instance Cancellation - 08/01/13"
-            },
-            'Update1')
-        self.assert_called_with('SoftLayer_Ticket', 'edit',
-                                args=args,
+        self.assert_called_with('SoftLayer_Ticket', 'addUpdate',
+                                args=({'entry': 'Update1'},),
                                 identifier=100)
diff --git a/SoftLayer/tests/transport_tests.py b/SoftLayer/tests/transport_tests.py
index c4977ff0..29b9a7e7 100644
--- a/SoftLayer/tests/transport_tests.py
+++ b/SoftLayer/tests/transport_tests.py
@@ -10,7 +10,6 @@
 import requests
 
 import SoftLayer
-from SoftLayer import consts
 from SoftLayer import testing
 from SoftLayer import transports
 
@@ -18,9 +17,7 @@
 class TestXmlRpcAPICall(testing.TestCase):
 
     def set_up(self):
-        self.transport = transports.XmlRpcTransport(
-            endpoint_url='http://something.com',
-        )
+        self.transport = transports.XmlRpcTransport()
         self.response = mock.MagicMock()
         self.response.content = '''<?xml version="1.0" encoding="utf-8"?>
 <params>
@@ -55,14 +52,14 @@ def test_call(self, request):
 '''
 
         req = transports.Request()
+        req.endpoint = 'http://something.com'
         req.service = 'SoftLayer_Service'
         req.method = 'getObject'
         resp = self.transport(req)
 
         request.assert_called_with('POST',
                                    'http://something.com/SoftLayer_Service',
-                                   headers={'Content-Type': 'application/xml',
-                                            'User-Agent': consts.USER_AGENT},
+                                   headers={},
                                    proxies=None,
                                    data=data,
                                    timeout=None,
@@ -72,6 +69,7 @@ def test_call(self, request):
 
     def test_proxy_without_protocol(self):
         req = transports.Request()
+        req.endpoint = 'http://something.com'
         req.service = 'SoftLayer_Service'
         req.method = 'Resource'
         req.proxy = 'localhost:3128'
@@ -85,20 +83,21 @@ def test_proxy_without_protocol(self):
     @mock.patch('requests.request')
     def test_valid_proxy(self, request):
         request.return_value = self.response
-        self.transport.proxy = 'http://localhost:3128'
 
         req = transports.Request()
+        req.endpoint = 'http://something.com'
         req.service = 'SoftLayer_Service'
         req.method = 'Resource'
+        req.proxy = 'http://localhost:3128'
         self.transport(req)
 
         request.assert_called_with(
             'POST',
             mock.ANY,
+            headers={},
             proxies={'https': 'http://localhost:3128',
                      'http': 'http://localhost:3128'},
             data=mock.ANY,
-            headers=mock.ANY,
             timeout=None,
             cert=None,
             verify=True)
@@ -238,6 +237,7 @@ def test_request_exception(self, request):
         request().raise_for_status.side_effect = e
 
         req = transports.Request()
+        req.endpoint = 'http://something.com'
         req.service = 'SoftLayer_Service'
         req.method = 'getObject'
 
@@ -247,14 +247,13 @@ def test_request_exception(self, request):
 class TestRestAPICall(testing.TestCase):
 
     def set_up(self):
-        self.transport = transports.RestTransport(
-            endpoint_url='http://something.com',
-        )
+        self.transport = transports.RestTransport()
 
     @mock.patch('requests.request')
     def test_basic(self, request):
         request().content = '{}'
         req = transports.Request()
+        req.endpoint = 'http://something.com'
         req.service = 'SoftLayer_Service'
         req.method = 'Resource'
 
@@ -262,7 +261,7 @@ def test_basic(self, request):
         self.assertEqual(resp, {})
         request.assert_called_with(
             'GET', 'http://something.com/SoftLayer_Service/Resource.json',
-            headers=mock.ANY,
+            headers={},
             verify=True,
             cert=None,
             proxies=None,
@@ -282,6 +281,7 @@ def test_basic(self, request):
 
     def test_proxy_without_protocol(self):
         req = transports.Request()
+        req.endpoint = 'http://something.com'
         req.service = 'SoftLayer_Service'
         req.method = 'Resource'
         req.proxy = 'localhost:3128'
@@ -295,11 +295,12 @@ def test_proxy_without_protocol(self):
     @mock.patch('requests.request')
     def test_valid_proxy(self, request):
         request().content = '{}'
-        self.transport.proxy = 'http://localhost:3128'
 
         req = transports.Request()
+        req.endpoint = 'http://something.com'
         req.service = 'SoftLayer_Service'
         req.method = 'Resource'
+        req.proxy = 'http://localhost:3128'
 
         self.transport(req)
         request.assert_called_with(
@@ -316,6 +317,7 @@ def test_with_id(self, request):
         request().content = '{}'
 
         req = transports.Request()
+        req.endpoint = 'http://something.com'
         req.service = 'SoftLayer_Service'
         req.method = 'getObject'
         req.identifier = 2
@@ -325,29 +327,8 @@ def test_with_id(self, request):
         self.assertEqual(resp, {})
         request.assert_called_with(
             'GET',
-            'http://something.com/SoftLayer_Service/2/getObject.json',
-            headers=mock.ANY,
-            verify=True,
-            cert=None,
-            proxies=None,
-            timeout=None)
-
-    @mock.patch('requests.request')
-    def test_with_args(self, request):
-        request().content = '{}'
-
-        req = transports.Request()
-        req.service = 'SoftLayer_Service'
-        req.method = 'getObject'
-        req.args = ('test', 1)
-
-        resp = self.transport(req)
-
-        self.assertEqual(resp, {})
-        request.assert_called_with(
-            'GET',
-            'http://something.com/SoftLayer_Service/getObject/test/1.json',
-            headers=mock.ANY,
+            'http://something.com/SoftLayer_Service/getObject/2.json',
+            headers={},
             verify=True,
             cert=None,
             proxies=None,
@@ -362,6 +343,7 @@ def test_unknown_error(self, request):
         request().raise_for_status.side_effect = e
 
         req = transports.Request()
+        req.endpoint = 'http://something.com'
         req.service = 'SoftLayer_Service'
         req.method = 'getObject'