Pārlūkot izejas kodu

HUE-8208 [core] Add Salesforce Python library

Licensed under the Apache License, Version 2.0
Romain Rigaux 7 gadi atpakaļ
vecāks
revīzija
8ec77849a5

+ 25 - 0
desktop/core/ext-py/simple-salesforce-0.74.2/LICENSE.txt

@@ -0,0 +1,25 @@
+Copyright 2012 New Organizing Institute Education Fund
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+
+
+
+Some content based off Restforce 1.0
+
+Copyright (C) 2012 Dave Wingate
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ 3 - 0
desktop/core/ext-py/simple-salesforce-0.74.2/MANIFEST.in

@@ -0,0 +1,3 @@
+include LICENSE.txt
+include README.rst
+include MANIFEST.in

+ 388 - 0
desktop/core/ext-py/simple-salesforce-0.74.2/PKG-INFO

@@ -0,0 +1,388 @@
+Metadata-Version: 1.2
+Name: simple-salesforce
+Version: 0.74.2
+Summary: A basic Salesforce.com REST API client.
+Home-page: https://github.com/simple-salesforce/simple-salesforce
+Author: Nick Catalano
+Author-email: nickcatal@gmail.com
+Maintainer: Demian Brecht
+Maintainer-email: demianbrecht@gmail.com
+License: Apache 2.0
+Description: *****************
+        Simple Salesforce
+        *****************
+        
+        .. image:: https://api.travis-ci.org/simple-salesforce/simple-salesforce.svg?branch=master
+           :target: https://travis-ci.org/simple-salesforce/simple-salesforce
+        
+        .. image:: https://readthedocs.org/projects/simple-salesforce/badge/?version=latest
+           :target: http://simple-salesforce.readthedocs.io/en/latest/?badge=latest
+           :alt: Documentation Status
+        
+        Simple Salesforce is a basic Salesforce.com REST API client built for Python 2.6, 2.7, 3.3, 3.4, 3.5, and 3.6. The goal is to provide a very low-level interface to the REST Resource and APEX API, returning a dictionary of the API JSON response.
+        
+        You can find out more regarding the format of the results in the `Official Salesforce.com REST API Documentation`_
+        
+        .. _Official Salesforce.com REST API Documentation: http://www.salesforce.com/us/developer/docs/api_rest/index.htm
+        
+        Examples
+        --------
+        There are two ways to gain access to Salesforce
+        
+        The first is to simply pass the domain of your Salesforce instance and an access token straight to ``Salesforce()``
+        
+        For example:
+        
+        .. code-block:: python
+        
+            from simple_salesforce import Salesforce
+            sf = Salesforce(instance='na1.salesforce.com', session_id='')
+        
+        If you have the full URL of your instance (perhaps including the schema, as is included in the OAuth2 request process), you can pass that in instead using ``instance_url``:
+        
+        .. code-block:: python
+        
+            from simple_salesforce import Salesforce
+            sf = Salesforce(instance_url='https://na1.salesforce.com', session_id='')
+        
+        There are also two means of authentication, one that uses username, password and security token and the other that uses IP filtering, username, password  and organizationId
+        
+        To login using the security token method, simply include the Salesforce method and pass in your Salesforce username, password and token (this is usually provided when you change your password):
+        
+        .. code-block:: python
+        
+            from simple_salesforce import Salesforce
+            sf = Salesforce(username='myemail@example.com', password='password', security_token='token')
+        
+        To login using IP-whitelist Organization ID method, simply use your Salesforce username, password and organizationId:
+        
+        .. code-block:: python
+        
+            from simple_salesforce import Salesforce
+            sf = Salesforce(password='password', username='myemail@example.com', organizationId='OrgId')
+        
+        If you'd like to enter a sandbox, simply add ``domain='test'`` to your ``Salesforce()`` call.
+        
+        For example:
+        
+        .. code-block:: python
+        
+            from simple_salesforce import Salesforce
+            sf = Salesforce(username='myemail@example.com.sandbox', password='password', security_token='token', domain='test')
+        
+        Note that specifying if you want to use a domain is only necessary if you are using the built-in username/password/security token authentication and is used exclusively during the authentication step.
+        
+        If you'd like to keep track where your API calls are coming from, simply add ``client_id='My App'`` to your ``Salesforce()`` call.
+        
+        .. code-block:: python
+        
+            from simple_salesforce import Salesforce
+            sf = Salesforce(username='myemail@example.com.sandbox', password='password', security_token='token', client_id='My App', domain='test')
+        
+        If you view the API calls in your Salesforce instance by Client Id it will be prefixed with ``RestForce/``, for example ``RestForce/My App``.
+        
+        When instantiating a `Salesforce` object, it's also possible to include an
+        instance of `requests.Session`. This is to allow for specialized
+        session handling not otherwise exposed by simple_salesforce.
+        
+        For example:
+        
+        .. code-block:: python
+        
+           from simple_salesforce import Salesforce
+           import requests
+        
+           session = requests.Session()
+           # manipulate the session instance (optional)
+           sf = Salesforce(
+              username='user@example.com', password='password', organizationId='OrgId',
+              session=session)
+        
+        Record Management
+        -----------------
+        
+        To create a new 'Contact' in Salesforce:
+        
+        .. code-block:: python
+        
+            sf.Contact.create({'LastName':'Smith','Email':'example@example.com'})
+        
+        This will return a dictionary such as ``{u'errors': [], u'id': u'003e0000003GuNXAA0', u'success': True}``
+        
+        To get a dictionary with all the information regarding that record, use:
+        
+        .. code-block:: python
+        
+            contact = sf.Contact.get('003e0000003GuNXAA0')
+        
+        To get a dictionary with all the information regarding that record, using a **custom** field that was defined as External ID:
+        
+        .. code-block:: python
+        
+            contact = sf.Contact.get_by_custom_id('My_Custom_ID__c', '22')
+        
+        To change that contact's last name from 'Smith' to 'Jones' and add a first name of 'John' use:
+        
+        .. code-block:: python
+        
+            sf.Contact.update('003e0000003GuNXAA0',{'LastName': 'Jones', 'FirstName': 'John'})
+        
+        To delete the contact:
+        
+        .. code-block:: python
+        
+            sf.Contact.delete('003e0000003GuNXAA0')
+        
+        To retrieve a list of Contact records deleted over the past 10 days (datetimes are required to be in UTC):
+        
+        .. code-block:: python
+        
+            import pytz
+            import datetime
+            end = datetime.datetime.now(pytz.UTC)  # we need to use UTC as salesforce API requires this!
+            sf.Contact.deleted(end - datetime.timedelta(days=10), end)
+        
+        To retrieve a list of Contact records updated over the past 10 days (datetimes are required to be in UTC):
+        
+        .. code-block:: python
+        
+            import pytz
+            import datetime
+            end = datetime.datetime.now(pytz.UTC) # we need to use UTC as salesforce API requires this
+            sf.Contact.updated(end - datetime.timedelta(days=10), end)
+        
+        Note that Update, Delete and Upsert actions return the associated `Salesforce HTTP Status Code`_
+        
+        Use the same format to create any record, including 'Account', 'Opportunity', and 'Lead'.
+        Make sure to have all the required fields for any entry. The `Salesforce API`_ has all objects found under 'Reference -> Standard Objects' and the required fields can be found there.
+        
+        .. _Salesforce HTTP Status Code: http://www.salesforce.com/us/developer/docs/api_rest/Content/errorcodes.htm
+        .. _Salesforce API: https://www.salesforce.com/developer/docs/api/
+        
+        Queries
+        -------
+        
+        It's also possible to write select queries in Salesforce Object Query Language (SOQL) and search queries in Salesforce Object Search Language (SOSL).
+        
+        SOQL queries are done via:
+        
+        .. code-block:: python
+        
+            sf.query("SELECT Id, Email FROM Contact WHERE LastName = 'Jones'")
+        
+        If, due to an especially large result, Salesforce adds a ``nextRecordsUrl`` to your query result, such as ``"nextRecordsUrl" : "/services/data/v26.0/query/01gD0000002HU6KIAW-2000"``, you can pull the additional results with either the ID or the full URL (if using the full URL, you must pass 'True' as your second argument)
+        
+        .. code-block:: python
+        
+            sf.query_more("01gD0000002HU6KIAW-2000")
+            sf.query_more("/services/data/v26.0/query/01gD0000002HU6KIAW-2000", True)
+        
+        As a convenience, to retrieve all of the results in a single local method call use
+        
+        .. code-block:: python
+        
+            sf.query_all("SELECT Id, Email FROM Contact WHERE LastName = 'Jones'")
+        
+        SOSL queries are done via:
+        
+        .. code-block:: python
+        
+            sf.search("FIND {Jones}")
+        
+        There is also 'Quick Search', which inserts your query inside the {} in the SOSL syntax. Be careful, there is no escaping!
+        
+        .. code-block:: python
+        
+            sf.quick_search("Jones")
+        
+        Search and Quick Search return ``None`` if there are no records, otherwise they return a dictionary of search results.
+        
+        More details about syntax is available on the `Salesforce Query Language Documentation Developer Website`_
+        
+        .. _Salesforce Query Language Documentation Developer Website: http://www.salesforce.com/us/developer/docs/soql_sosl/index.htm
+        
+        Other Options
+        -------------
+        
+        To insert or update (upsert) a record using an external ID, use:
+        
+        .. code-block:: python
+        
+            sf.Contact.upsert('customExtIdField__c/11999',{'LastName': 'Smith','Email': 'smith@example.com'})
+        
+        To retrieve basic metadata use:
+        
+        .. code-block:: python
+        
+            sf.Contact.metadata()
+        
+        To retrieve a description of the object, use:
+        
+        .. code-block:: python
+        
+            sf.Contact.describe()
+        
+        To retrieve a description of the record layout of an object by its record layout unique id, use:
+        
+        .. code-block:: python
+        
+            sf.Contact.describe_layout('39wmxcw9r23r492')
+        
+        To retrieve a list of top level description of instance metadata, user:
+        
+        .. code-block:: python
+        
+            sf.describe()
+        
+            for x in sf.describe()["sobjects"]:
+              print x["label"]
+        
+        
+        Using Bulk
+        ----------
+        
+        You can use this library to access Bulk API functions.
+        
+        Create new records:
+        
+        .. code-block:: python
+        
+            data = [{'LastName':'Smith','Email':'example@example.com'}, {'LastName':'Jones','Email':'test@test.com'}]
+        
+            sf.bulk.Contact.insert(data)
+        
+        Update existing records:
+        
+        .. code-block:: python
+        
+            data = [{'Id': '0000000000AAAAA', 'Email': 'examplenew@example.com'}, {'Id': '0000000000BBBBB', 'Email': 'testnew@test.com'}]
+        
+            sf.bulk.Contact.update(data)
+        
+        Upsert records:
+        
+        .. code-block:: python
+        
+            data = [{'Id': '0000000000AAAAA', 'Email': 'examplenew2@example.com'}, {'Id': '', 'Email': 'foo@foo.com'}]
+        
+            sf.bulk.Contact.upsert(data, 'Id')
+        
+        Query records:
+        
+        .. code-block:: python
+        
+            query = 'SELECT Id, Name FROM Account LIMIT 10'
+        
+            sf.bulk.Account.query(query)
+        
+        Delete records (soft deletion):
+        
+        .. code-block:: python
+        
+            data = [{'Id': '0000000000AAAAA'}]
+        
+            sf.bulk.Contact.delete(data)
+        
+        Hard deletion:
+        
+        .. code-block:: python
+        
+            data = [{'Id': '0000000000BBBBB'}]
+        
+            sf.bulk.Contact.hard_delete(data)
+        
+        
+        Using Apex
+        ----------
+        
+        You can also use this library to call custom Apex methods:
+        
+        .. code-block:: python
+        
+            payload = {
+              "activity": [
+                {"user": "12345", "action": "update page", "time": "2014-04-21T13:00:15Z"}
+              ]
+            }
+            result = sf.apexecute('User/Activity', method='POST', data=payload)
+        
+        This would call the endpoint ``https://<instance>.salesforce.com/services/apexrest/User/Activity`` with ``data=`` as
+        the body content encoded with ``json.dumps``
+        
+        You can read more about Apex on the `Force.com Apex Code Developer's Guide`_
+        
+        .. _Force.com Apex Code Developer's Guide: http://www.salesforce.com/us/developer/docs/apexcode
+        
+        Additional Features
+        -------------------
+        
+        There are a few helper classes that are used internally and available to you.
+        
+        Included in them are ``SalesforceLogin``, which takes in a username, password, security token, optional version and optional domain and returns a tuple of ``(session_id, sf_instance)`` where `session_id` is the session ID to use for authentication to Salesforce and ``sf_instance`` is the domain of the instance of Salesforce to use for the session.
+        
+        For example, to use SalesforceLogin for a sandbox account you'd use:
+        
+        .. code-block:: python
+        
+            from simple_salesforce import SalesforceLogin
+            session_id, instance = SalesforceLogin(
+                username='myemail@example.com.sandbox',
+                password='password',
+                security_token='token',
+                domain='test')
+        
+        Simply leave off the final domain if you do not wish to use a sandbox.
+        
+        Also exposed is the ``SFType`` class, which is used internally by the ``__getattr__()`` method in the ``Salesforce()`` class and represents a specific SObject type. ``SFType`` requires ``object_name`` (i.e. ``Contact``), ``session_id`` (an authentication ID), ``sf_instance`` (hostname of your Salesforce instance), and an optional ``sf_version``
+        
+        To add a Contact using the default version of the API you'd use:
+        
+        .. code-block:: python
+        
+            from simple_salesforce import SFType
+            contact = SFType('Contact','sesssionid','na1.salesforce.com')
+            contact.create({'LastName':'Smith','Email':'example@example.com'})
+        
+        To use a proxy server between your client and the SalesForce endpoint, use the proxies argument when creating SalesForce object.
+        The proxy argument is the same as what requests uses, a map of scheme to proxy URL:
+        
+        .. code-block:: python
+        
+            proxies = {
+              "http": "http://10.10.1.10:3128",
+              "https": "http://10.10.1.10:1080",
+            }
+            SalesForce(instance='na1.salesforce.com', session_id='', proxies=proxies)
+        
+        All results are returned as JSON converted OrderedDict to preserve order of keys from REST responses.
+        
+        Authors & License
+        -----------------
+        
+        This package is released under an open source Apache 2.0 license. Simple-Salesforce was originally written by `Nick Catalano`_ but most newer features and bugfixes come from `community contributors`_. Pull requests submitted to the `GitHub Repo`_ are highly encouraged!
+        
+        Authentication mechanisms were adapted from Dave Wingate's `RestForce`_ and licensed under a MIT license
+        
+        The latest build status can be found at `Travis CI`_
+        
+        .. _Nick Catalano: https://github.com/nickcatal
+        .. _community contributors: https://github.com/simple-salesforce/simple-salesforce/graphs/contributors
+        .. _RestForce: http://pypi.python.org/pypi/RestForce/
+        .. _GitHub Repo: https://github.com/simple-salesforce/simple-salesforce
+        .. _Travis CI: https://travis-ci.org/simple-salesforce/simple-salesforce
+        
+Keywords: python salesforce salesforce.com
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: System Administrators
+Classifier: Operating System :: OS Independent
+Classifier: Topic :: Internet :: WWW/HTTP
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
+Classifier: Programming Language :: Python :: 3.6
+Classifier: Programming Language :: Python :: Implementation :: PyPy

+ 362 - 0
desktop/core/ext-py/simple-salesforce-0.74.2/README.rst

@@ -0,0 +1,362 @@
+*****************
+Simple Salesforce
+*****************
+
+.. image:: https://api.travis-ci.org/simple-salesforce/simple-salesforce.svg?branch=master
+   :target: https://travis-ci.org/simple-salesforce/simple-salesforce
+
+.. image:: https://readthedocs.org/projects/simple-salesforce/badge/?version=latest
+   :target: http://simple-salesforce.readthedocs.io/en/latest/?badge=latest
+   :alt: Documentation Status
+
+Simple Salesforce is a basic Salesforce.com REST API client built for Python 2.6, 2.7, 3.3, 3.4, 3.5, and 3.6. The goal is to provide a very low-level interface to the REST Resource and APEX API, returning a dictionary of the API JSON response.
+
+You can find out more regarding the format of the results in the `Official Salesforce.com REST API Documentation`_
+
+.. _Official Salesforce.com REST API Documentation: http://www.salesforce.com/us/developer/docs/api_rest/index.htm
+
+Examples
+--------
+There are two ways to gain access to Salesforce
+
+The first is to simply pass the domain of your Salesforce instance and an access token straight to ``Salesforce()``
+
+For example:
+
+.. code-block:: python
+
+    from simple_salesforce import Salesforce
+    sf = Salesforce(instance='na1.salesforce.com', session_id='')
+
+If you have the full URL of your instance (perhaps including the schema, as is included in the OAuth2 request process), you can pass that in instead using ``instance_url``:
+
+.. code-block:: python
+
+    from simple_salesforce import Salesforce
+    sf = Salesforce(instance_url='https://na1.salesforce.com', session_id='')
+
+There are also two means of authentication, one that uses username, password and security token and the other that uses IP filtering, username, password  and organizationId
+
+To login using the security token method, simply include the Salesforce method and pass in your Salesforce username, password and token (this is usually provided when you change your password):
+
+.. code-block:: python
+
+    from simple_salesforce import Salesforce
+    sf = Salesforce(username='myemail@example.com', password='password', security_token='token')
+
+To login using IP-whitelist Organization ID method, simply use your Salesforce username, password and organizationId:
+
+.. code-block:: python
+
+    from simple_salesforce import Salesforce
+    sf = Salesforce(password='password', username='myemail@example.com', organizationId='OrgId')
+
+If you'd like to enter a sandbox, simply add ``domain='test'`` to your ``Salesforce()`` call.
+
+For example:
+
+.. code-block:: python
+
+    from simple_salesforce import Salesforce
+    sf = Salesforce(username='myemail@example.com.sandbox', password='password', security_token='token', domain='test')
+
+Note that specifying if you want to use a domain is only necessary if you are using the built-in username/password/security token authentication and is used exclusively during the authentication step.
+
+If you'd like to keep track where your API calls are coming from, simply add ``client_id='My App'`` to your ``Salesforce()`` call.
+
+.. code-block:: python
+
+    from simple_salesforce import Salesforce
+    sf = Salesforce(username='myemail@example.com.sandbox', password='password', security_token='token', client_id='My App', domain='test')
+
+If you view the API calls in your Salesforce instance by Client Id it will be prefixed with ``RestForce/``, for example ``RestForce/My App``.
+
+When instantiating a `Salesforce` object, it's also possible to include an
+instance of `requests.Session`. This is to allow for specialized
+session handling not otherwise exposed by simple_salesforce.
+
+For example:
+
+.. code-block:: python
+
+   from simple_salesforce import Salesforce
+   import requests
+
+   session = requests.Session()
+   # manipulate the session instance (optional)
+   sf = Salesforce(
+      username='user@example.com', password='password', organizationId='OrgId',
+      session=session)
+
+Record Management
+-----------------
+
+To create a new 'Contact' in Salesforce:
+
+.. code-block:: python
+
+    sf.Contact.create({'LastName':'Smith','Email':'example@example.com'})
+
+This will return a dictionary such as ``{u'errors': [], u'id': u'003e0000003GuNXAA0', u'success': True}``
+
+To get a dictionary with all the information regarding that record, use:
+
+.. code-block:: python
+
+    contact = sf.Contact.get('003e0000003GuNXAA0')
+
+To get a dictionary with all the information regarding that record, using a **custom** field that was defined as External ID:
+
+.. code-block:: python
+
+    contact = sf.Contact.get_by_custom_id('My_Custom_ID__c', '22')
+
+To change that contact's last name from 'Smith' to 'Jones' and add a first name of 'John' use:
+
+.. code-block:: python
+
+    sf.Contact.update('003e0000003GuNXAA0',{'LastName': 'Jones', 'FirstName': 'John'})
+
+To delete the contact:
+
+.. code-block:: python
+
+    sf.Contact.delete('003e0000003GuNXAA0')
+
+To retrieve a list of Contact records deleted over the past 10 days (datetimes are required to be in UTC):
+
+.. code-block:: python
+
+    import pytz
+    import datetime
+    end = datetime.datetime.now(pytz.UTC)  # we need to use UTC as salesforce API requires this!
+    sf.Contact.deleted(end - datetime.timedelta(days=10), end)
+
+To retrieve a list of Contact records updated over the past 10 days (datetimes are required to be in UTC):
+
+.. code-block:: python
+
+    import pytz
+    import datetime
+    end = datetime.datetime.now(pytz.UTC) # we need to use UTC as salesforce API requires this
+    sf.Contact.updated(end - datetime.timedelta(days=10), end)
+
+Note that Update, Delete and Upsert actions return the associated `Salesforce HTTP Status Code`_
+
+Use the same format to create any record, including 'Account', 'Opportunity', and 'Lead'.
+Make sure to have all the required fields for any entry. The `Salesforce API`_ has all objects found under 'Reference -> Standard Objects' and the required fields can be found there.
+
+.. _Salesforce HTTP Status Code: http://www.salesforce.com/us/developer/docs/api_rest/Content/errorcodes.htm
+.. _Salesforce API: https://www.salesforce.com/developer/docs/api/
+
+Queries
+-------
+
+It's also possible to write select queries in Salesforce Object Query Language (SOQL) and search queries in Salesforce Object Search Language (SOSL).
+
+SOQL queries are done via:
+
+.. code-block:: python
+
+    sf.query("SELECT Id, Email FROM Contact WHERE LastName = 'Jones'")
+
+If, due to an especially large result, Salesforce adds a ``nextRecordsUrl`` to your query result, such as ``"nextRecordsUrl" : "/services/data/v26.0/query/01gD0000002HU6KIAW-2000"``, you can pull the additional results with either the ID or the full URL (if using the full URL, you must pass 'True' as your second argument)
+
+.. code-block:: python
+
+    sf.query_more("01gD0000002HU6KIAW-2000")
+    sf.query_more("/services/data/v26.0/query/01gD0000002HU6KIAW-2000", True)
+
+As a convenience, to retrieve all of the results in a single local method call use
+
+.. code-block:: python
+
+    sf.query_all("SELECT Id, Email FROM Contact WHERE LastName = 'Jones'")
+
+SOSL queries are done via:
+
+.. code-block:: python
+
+    sf.search("FIND {Jones}")
+
+There is also 'Quick Search', which inserts your query inside the {} in the SOSL syntax. Be careful, there is no escaping!
+
+.. code-block:: python
+
+    sf.quick_search("Jones")
+
+Search and Quick Search return ``None`` if there are no records, otherwise they return a dictionary of search results.
+
+More details about syntax is available on the `Salesforce Query Language Documentation Developer Website`_
+
+.. _Salesforce Query Language Documentation Developer Website: http://www.salesforce.com/us/developer/docs/soql_sosl/index.htm
+
+Other Options
+-------------
+
+To insert or update (upsert) a record using an external ID, use:
+
+.. code-block:: python
+
+    sf.Contact.upsert('customExtIdField__c/11999',{'LastName': 'Smith','Email': 'smith@example.com'})
+
+To retrieve basic metadata use:
+
+.. code-block:: python
+
+    sf.Contact.metadata()
+
+To retrieve a description of the object, use:
+
+.. code-block:: python
+
+    sf.Contact.describe()
+
+To retrieve a description of the record layout of an object by its record layout unique id, use:
+
+.. code-block:: python
+
+    sf.Contact.describe_layout('39wmxcw9r23r492')
+
+To retrieve a list of top level description of instance metadata, user:
+
+.. code-block:: python
+
+    sf.describe()
+
+    for x in sf.describe()["sobjects"]:
+      print x["label"]
+
+
+Using Bulk
+----------
+
+You can use this library to access Bulk API functions.
+
+Create new records:
+
+.. code-block:: python
+
+    data = [{'LastName':'Smith','Email':'example@example.com'}, {'LastName':'Jones','Email':'test@test.com'}]
+
+    sf.bulk.Contact.insert(data)
+
+Update existing records:
+
+.. code-block:: python
+
+    data = [{'Id': '0000000000AAAAA', 'Email': 'examplenew@example.com'}, {'Id': '0000000000BBBBB', 'Email': 'testnew@test.com'}]
+
+    sf.bulk.Contact.update(data)
+
+Upsert records:
+
+.. code-block:: python
+
+    data = [{'Id': '0000000000AAAAA', 'Email': 'examplenew2@example.com'}, {'Id': '', 'Email': 'foo@foo.com'}]
+
+    sf.bulk.Contact.upsert(data, 'Id')
+
+Query records:
+
+.. code-block:: python
+
+    query = 'SELECT Id, Name FROM Account LIMIT 10'
+
+    sf.bulk.Account.query(query)
+
+Delete records (soft deletion):
+
+.. code-block:: python
+
+    data = [{'Id': '0000000000AAAAA'}]
+
+    sf.bulk.Contact.delete(data)
+
+Hard deletion:
+
+.. code-block:: python
+
+    data = [{'Id': '0000000000BBBBB'}]
+
+    sf.bulk.Contact.hard_delete(data)
+
+
+Using Apex
+----------
+
+You can also use this library to call custom Apex methods:
+
+.. code-block:: python
+
+    payload = {
+      "activity": [
+        {"user": "12345", "action": "update page", "time": "2014-04-21T13:00:15Z"}
+      ]
+    }
+    result = sf.apexecute('User/Activity', method='POST', data=payload)
+
+This would call the endpoint ``https://<instance>.salesforce.com/services/apexrest/User/Activity`` with ``data=`` as
+the body content encoded with ``json.dumps``
+
+You can read more about Apex on the `Force.com Apex Code Developer's Guide`_
+
+.. _Force.com Apex Code Developer's Guide: http://www.salesforce.com/us/developer/docs/apexcode
+
+Additional Features
+-------------------
+
+There are a few helper classes that are used internally and available to you.
+
+Included in them are ``SalesforceLogin``, which takes in a username, password, security token, optional version and optional domain and returns a tuple of ``(session_id, sf_instance)`` where `session_id` is the session ID to use for authentication to Salesforce and ``sf_instance`` is the domain of the instance of Salesforce to use for the session.
+
+For example, to use SalesforceLogin for a sandbox account you'd use:
+
+.. code-block:: python
+
+    from simple_salesforce import SalesforceLogin
+    session_id, instance = SalesforceLogin(
+        username='myemail@example.com.sandbox',
+        password='password',
+        security_token='token',
+        domain='test')
+
+Simply leave off the final domain if you do not wish to use a sandbox.
+
+Also exposed is the ``SFType`` class, which is used internally by the ``__getattr__()`` method in the ``Salesforce()`` class and represents a specific SObject type. ``SFType`` requires ``object_name`` (i.e. ``Contact``), ``session_id`` (an authentication ID), ``sf_instance`` (hostname of your Salesforce instance), and an optional ``sf_version``
+
+To add a Contact using the default version of the API you'd use:
+
+.. code-block:: python
+
+    from simple_salesforce import SFType
+    contact = SFType('Contact','sesssionid','na1.salesforce.com')
+    contact.create({'LastName':'Smith','Email':'example@example.com'})
+
+To use a proxy server between your client and the SalesForce endpoint, use the proxies argument when creating SalesForce object.
+The proxy argument is the same as what requests uses, a map of scheme to proxy URL:
+
+.. code-block:: python
+
+    proxies = {
+      "http": "http://10.10.1.10:3128",
+      "https": "http://10.10.1.10:1080",
+    }
+    SalesForce(instance='na1.salesforce.com', session_id='', proxies=proxies)
+
+All results are returned as JSON converted OrderedDict to preserve order of keys from REST responses.
+
+Authors & License
+-----------------
+
+This package is released under an open source Apache 2.0 license. Simple-Salesforce was originally written by `Nick Catalano`_ but most newer features and bugfixes come from `community contributors`_. Pull requests submitted to the `GitHub Repo`_ are highly encouraged!
+
+Authentication mechanisms were adapted from Dave Wingate's `RestForce`_ and licensed under a MIT license
+
+The latest build status can be found at `Travis CI`_
+
+.. _Nick Catalano: https://github.com/nickcatal
+.. _community contributors: https://github.com/simple-salesforce/simple-salesforce/graphs/contributors
+.. _RestForce: http://pypi.python.org/pypi/RestForce/
+.. _GitHub Repo: https://github.com/simple-salesforce/simple-salesforce
+.. _Travis CI: https://travis-ci.org/simple-salesforce/simple-salesforce

+ 7 - 0
desktop/core/ext-py/simple-salesforce-0.74.2/setup.cfg

@@ -0,0 +1,7 @@
+[bdist_wheel]
+universal = 1
+
+[egg_info]
+tag_build = 
+tag_date = 0
+

+ 65 - 0
desktop/core/ext-py/simple-salesforce-0.74.2/setup.py

@@ -0,0 +1,65 @@
+"""Simple-Salesforce Package Setup"""
+
+from setuptools import setup
+import textwrap
+import sys
+import os
+
+
+pyver_install_requires = []
+pyver_tests_require = []
+if sys.version_info < (2, 7):
+    pyver_install_requires.append('ordereddict>=1.1')
+    pyver_tests_require.append('unittest2>=0.5.1')
+
+if sys.version_info < (3, 0):
+    pyver_tests_require.append('mock==1.0.1')
+
+
+here = os.path.abspath(os.path.dirname(__file__))
+
+about = {}
+with open(os.path.join(here, 'simple_salesforce', '__version__.py'), 'r') as f:
+    exec(f.read(), about)
+
+
+setup(
+    name=about['__title__'],
+    version=about['__version__'],
+    author=about['__author__'],
+    author_email=about['__author_email__'],
+    maintainer=about['__maintainer__'],
+    maintainer_email=about['__maintainer_email__'],
+    packages=['simple_salesforce',],
+    url=about['__url__'],
+    license=about['__license__'],
+    description=about['__description__'],
+    long_description=textwrap.dedent(open('README.rst', 'r').read()),
+
+    install_requires=[
+        'requests[security]'
+    ] + pyver_install_requires,
+    tests_require=[
+        'nose>=1.3.0',
+        'pytz>=2014.1.1',
+        'responses>=0.5.1',
+    ] + pyver_tests_require,
+    test_suite = 'nose.collector',
+
+    keywords=about['__keywords__'],
+    classifiers=[
+        'Development Status :: 4 - Beta',
+        'License :: OSI Approved :: Apache Software License',
+        'Intended Audience :: Developers',
+        'Intended Audience :: System Administrators',
+        'Operating System :: OS Independent',
+        'Topic :: Internet :: WWW/HTTP',
+        'Programming Language :: Python :: 2.6',
+        'Programming Language :: Python :: 2.7',
+        'Programming Language :: Python :: 3.3',
+        'Programming Language :: Python :: 3.4',
+        'Programming Language :: Python :: 3.5',
+        'Programming Language :: Python :: 3.6',
+        'Programming Language :: Python :: Implementation :: PyPy'
+    ]
+)

+ 27 - 0
desktop/core/ext-py/simple-salesforce-0.74.2/simple_salesforce/__init__.py

@@ -0,0 +1,27 @@
+"""Simple-Salesforce Package"""
+# flake8: noqa
+
+from simple_salesforce.api import (
+    Salesforce,
+    SalesforceAPI,
+    SFType
+)
+
+from simple_salesforce.bulk import (
+    SFBulkHandler
+)
+
+from simple_salesforce.login import (
+    SalesforceLogin
+)
+
+from simple_salesforce.exceptions import (
+    SalesforceError,
+    SalesforceMoreThanOneRecord,
+    SalesforceExpiredSession,
+    SalesforceRefusedRequest,
+    SalesforceResourceNotFound,
+    SalesforceGeneralError,
+    SalesforceMalformedRequest,
+    SalesforceAuthenticationFailed
+)

+ 14 - 0
desktop/core/ext-py/simple-salesforce-0.74.2/simple_salesforce/__version__.py

@@ -0,0 +1,14 @@
+"""Version details for simple-salesforce
+
+This file shamelessly taken from the requests library"""
+
+__title__ = 'simple-salesforce'
+__description__ = 'A basic Salesforce.com REST API client.'
+__url__ = 'https://github.com/simple-salesforce/simple-salesforce'
+__version__ = '0.74.2'
+__author__ = 'Nick Catalano'
+__author_email__ = 'nickcatal@gmail.com'
+__license__ = 'Apache 2.0'
+__maintainer__ = 'Demian Brecht'
+__maintainer_email__ = 'demianbrecht@gmail.com'
+__keywords__ = 'python salesforce salesforce.com'

+ 855 - 0
desktop/core/ext-py/simple-salesforce-0.74.2/simple_salesforce/api.py

@@ -0,0 +1,855 @@
+"""Core classes and exceptions for Simple-Salesforce"""
+
+
+# has to be defined prior to login import
+DEFAULT_API_VERSION = '38.0'
+
+
+import logging
+import warnings
+import requests
+import json
+import re
+from collections import namedtuple
+
+try:
+    from urlparse import urlparse, urljoin
+except ImportError:
+    # Python 3+
+    from urllib.parse import urlparse, urljoin
+
+from simple_salesforce.login import SalesforceLogin
+from simple_salesforce.util import date_to_iso8601, exception_handler
+from simple_salesforce.exceptions import (
+    SalesforceGeneralError
+)
+from simple_salesforce.bulk import SFBulkHandler
+
+try:
+    from collections import OrderedDict
+except ImportError:
+    # Python < 2.7
+    from ordereddict import OrderedDict
+
+#pylint: disable=invalid-name
+logger = logging.getLogger(__name__)
+
+
+def _warn_request_deprecation():
+    """Deprecation for (Salesforce/SFType).request attribute"""
+    warnings.warn(
+        'The request attribute has been deprecated and will be removed in a '
+        'future version. Please use Salesforce.session instead.',
+        DeprecationWarning
+    )
+
+
+Usage = namedtuple('Usage', 'used total')
+PerAppUsage = namedtuple('PerAppUsage', 'used total name')
+
+
+# pylint: disable=too-many-instance-attributes
+class Salesforce(object):
+    """Salesforce Instance
+
+    An instance of Salesforce is a handy way to wrap a Salesforce session
+    for easy use of the Salesforce REST API.
+    """
+    # pylint: disable=too-many-arguments
+    def __init__(
+            self, username=None, password=None, security_token=None,
+            session_id=None, instance=None, instance_url=None,
+            organizationId=None, sandbox=None, version=DEFAULT_API_VERSION,
+            proxies=None, session=None, client_id=None, domain=None):
+        """Initialize the instance with the given parameters.
+
+        Available kwargs
+
+        Password Authentication:
+
+        * username -- the Salesforce username to use for authentication
+        * password -- the password for the username
+        * security_token -- the security token for the username
+        * sandbox -- DEPRECATED: Use domain instead.
+        * domain -- The domain to using for connecting to Salesforce. Use
+                    common domains, such as 'login' or 'test', or
+                    Salesforce My domain. If not used, will default to
+                    'login'.
+
+        Direct Session and Instance Access:
+
+        * session_id -- Access token for this session
+
+        Then either
+        * instance -- Domain of your Salesforce instance, i.e.
+          `na1.salesforce.com`
+        OR
+        * instance_url -- Full URL of your instance i.e.
+          `https://na1.salesforce.com
+
+        Universal Kwargs:
+        * version -- the version of the Salesforce API to use, for example
+                     `29.0`
+        * proxies -- the optional map of scheme to proxy server
+        * session -- Custom requests session, created in calling code. This
+                     enables the use of requests Session features not otherwise
+                     exposed by simple_salesforce.
+
+        """
+        if (sandbox is not None) and (domain is not None):
+            raise ValueError("Both 'sandbox' and 'domain' arguments were "
+                             "supplied. Either may be supplied, but not "
+                             "both.")
+
+        if sandbox is not None:
+            warnings.warn("'sandbox' argument is deprecated. Use "
+                          "'domain' instead. Overriding 'domain' "
+                          "with 'sandbox' value.",
+                          DeprecationWarning)
+
+            domain = 'test' if sandbox else 'login'
+
+        if domain is None:
+            domain = 'login'
+
+        # Determine if the user passed in the optional version and/or
+        # domain kwargs
+        self.sf_version = version
+        self.domain = domain
+        self.session = session or requests.Session()
+        self.proxies = self.session.proxies
+        # override custom session proxies dance
+        if proxies is not None:
+            if not session:
+                self.session.proxies = self.proxies = proxies
+            else:
+                logger.warning(
+                    'Proxies must be defined on custom session object, '
+                    'ignoring proxies: %s', proxies
+                )
+
+        # Determine if the user wants to use our username/password auth or pass
+        # in their own information
+        if all(arg is not None for arg in (
+                username, password, security_token)):
+            self.auth_type = "password"
+
+            # Pass along the username/password to our login helper
+            self.session_id, self.sf_instance = SalesforceLogin(
+                session=self.session,
+                username=username,
+                password=password,
+                security_token=security_token,
+                sf_version=self.sf_version,
+                proxies=self.proxies,
+                client_id=client_id,
+                domain=self.domain)
+
+        elif all(arg is not None for arg in (
+                session_id, instance or instance_url)):
+            self.auth_type = "direct"
+            self.session_id = session_id
+
+            # If the user provides the full url (as returned by the OAuth
+            # interface for example) extract the hostname (which we rely on)
+            if instance_url is not None:
+                self.sf_instance = urlparse(instance_url).hostname
+            else:
+                self.sf_instance = instance
+
+        elif all(arg is not None for arg in (
+                username, password, organizationId)):
+            self.auth_type = 'ipfilter'
+
+            # Pass along the username/password to our login helper
+            self.session_id, self.sf_instance = SalesforceLogin(
+                session=self.session,
+                username=username,
+                password=password,
+                organizationId=organizationId,
+                sf_version=self.sf_version,
+                proxies=self.proxies,
+                client_id=client_id,
+                domain=self.domain)
+
+        else:
+            raise TypeError(
+                'You must provide login information or an instance and token'
+            )
+
+        self.auth_site = ('https://{domain}.salesforce.com'
+                          .format(domain=self.domain))
+
+        self.headers = {
+            'Content-Type': 'application/json',
+            'Authorization': 'Bearer ' + self.session_id,
+            'X-PrettyPrint': '1'
+        }
+
+        self.base_url = ('https://{instance}/services/data/v{version}/'
+                         .format(instance=self.sf_instance,
+                                 version=self.sf_version))
+        self.apex_url = ('https://{instance}/services/apexrest/'
+                         .format(instance=self.sf_instance))
+        self.bulk_url = ('https://{instance}/services/async/{version}/'
+                         .format(instance=self.sf_instance,
+                                 version=self.sf_version))
+
+        self.api_usage = {}
+
+    def describe(self):
+        """Describes all available objects
+        """
+        url = self.base_url + "sobjects"
+        result = self._call_salesforce('GET', url, name='describe')
+
+        json_result = result.json(object_pairs_hook=OrderedDict)
+        if len(json_result) == 0:
+            return None
+
+        return json_result
+
+    # SObject Handler
+    def __getattr__(self, name):
+        """Returns an `SFType` instance for the given Salesforce object type
+        (given in `name`).
+
+        The magic part of the SalesforceAPI, this function translates
+        calls such as `salesforce_api_instance.Lead.metadata()` into fully
+        constituted `SFType` instances to make a nice Python API wrapper
+        for the REST API.
+
+        Arguments:
+
+        * name -- the name of a Salesforce object type, e.g. Lead or Contact
+        """
+
+        # fix to enable serialization
+        # (https://github.com/heroku/simple-salesforce/issues/60)
+        if name.startswith('__'):
+            return super(Salesforce, self).__getattr__(name)
+
+        if name == 'bulk':
+            # Deal with bulk API functions
+            return SFBulkHandler(self.session_id, self.bulk_url, self.proxies,
+                                 self.session)
+
+        return SFType(
+            name, self.session_id, self.sf_instance, sf_version=self.sf_version,
+            proxies=self.proxies, session=self.session)
+
+    # User utility methods
+    def set_password(self, user, password):
+        """Sets the password of a user
+
+        salesforce dev documentation link:
+        https://www.salesforce.com/us/developer/docs/api_rest/Content/dome_sobject_user_password.htm
+
+        Arguments:
+
+        * user: the userID of the user to set
+        * password: the new password
+        """
+
+        url = self.base_url + 'sobjects/User/%s/password' % user
+        params = {'NewPassword': password}
+
+        result = self._call_salesforce('POST', url, data=json.dumps(params))
+
+        # salesforce return 204 No Content when the request is successful
+        if result.status_code != 200 and result.status_code != 204:
+            raise SalesforceGeneralError(url,
+                                         result.status_code,
+                                         'User',
+                                         result.content)
+        json_result = result.json(object_pairs_hook=OrderedDict)
+        if len(json_result) == 0:
+            return None
+
+        return json_result
+
+    # pylint: disable=invalid-name
+    def setPassword(self, user, password):
+        # pylint: disable=line-too-long
+        """Sets the password of a user
+
+        salesforce dev documentation link:
+        https://www.salesforce.com/us/developer/docs/api_rest/Content/dome_sobject_user_password.htm
+
+        Arguments:
+
+        * user: the userID of the user to set
+        * password: the new password
+        """
+        warnings.warn(
+            "This method has been deprecated."
+            "Please use set_password instead.",
+            DeprecationWarning)
+        return self.set_password(user, password)
+
+    # Generic Rest Function
+    def restful(self, path, params=None, method='GET', **kwargs):
+        """Allows you to make a direct REST call if you know the path
+
+        Arguments:
+
+        * path: The path of the request
+            Example: sobjects/User/ABC123/password'
+        * params: dict of parameters to pass to the path
+        * method: HTTP request method, default GET
+        * other arguments supported by requests.request (e.g. json, timeout)
+        """
+
+        url = self.base_url + path
+        result = self._call_salesforce(method, url, name=path, params=params,
+                                       **kwargs)
+
+        json_result = result.json(object_pairs_hook=OrderedDict)
+        if len(json_result) == 0:
+            return None
+
+        return json_result
+
+    # Search Functions
+    def search(self, search):
+        """Returns the result of a Salesforce search as a dict decoded from
+        the Salesforce response JSON payload.
+
+        Arguments:
+
+        * search -- the fully formatted SOSL search string, e.g.
+                    `FIND {Waldo}`
+        """
+        url = self.base_url + 'search/'
+
+        # `requests` will correctly encode the query string passed as `params`
+        params = {'q': search}
+        result = self._call_salesforce('GET', url, name='search', params=params)
+
+        json_result = result.json(object_pairs_hook=OrderedDict)
+        if len(json_result) == 0:
+            return None
+
+        return json_result
+
+    def quick_search(self, search):
+        """Returns the result of a Salesforce search as a dict decoded from
+        the Salesforce response JSON payload.
+
+        Arguments:
+
+        * search -- the non-SOSL search string, e.g. `Waldo`. This search
+                    string will be wrapped to read `FIND {Waldo}` before being
+                    sent to Salesforce
+        """
+        search_string = u'FIND {{{search_string}}}'.format(search_string=search)
+        return self.search(search_string)
+
+    def limits(self, **kwargs):
+        """Return the result of a Salesforce request to list Organization
+        limits.
+        """
+        url = self.base_url + 'limits/'
+        result = self._call_salesforce('GET', url, **kwargs)
+
+        if result.status_code != 200:
+            exception_handler(result)
+
+        return result.json(object_pairs_hook=OrderedDict)
+
+    # Query Handler
+    def query(self, query, include_deleted=False, **kwargs):
+        """Return the result of a Salesforce SOQL query as a dict decoded from
+        the Salesforce response JSON payload.
+
+        Arguments:
+
+        * query -- the SOQL query to send to Salesforce, e.g.
+                   SELECT Id FROM Lead WHERE Email = "waldo@somewhere.com"
+        * include_deleted -- True if deleted records should be included
+        """
+        url = self.base_url + ('queryAll/' if include_deleted else 'query/')
+        params = {'q': query}
+        # `requests` will correctly encode the query string passed as `params`
+        result = self._call_salesforce('GET', url, name='query',
+                                       params=params, **kwargs)
+
+        return result.json(object_pairs_hook=OrderedDict)
+
+    def query_more(
+            self, next_records_identifier, identifier_is_url=False,
+            include_deleted=False, **kwargs):
+        """Retrieves more results from a query that returned more results
+        than the batch maximum. Returns a dict decoded from the Salesforce
+        response JSON payload.
+
+        Arguments:
+
+        * next_records_identifier -- either the Id of the next Salesforce
+                                     object in the result, or a URL to the
+                                     next record in the result.
+        * identifier_is_url -- True if `next_records_identifier` should be
+                               treated as a URL, False if
+                               `next_records_identifier` should be treated as
+                               an Id.
+        * include_deleted -- True if the `next_records_identifier` refers to a
+                             query that includes deleted records. Only used if
+                             `identifier_is_url` is False
+        """
+        if identifier_is_url:
+            # Don't use `self.base_url` here because the full URI is provided
+            url = (u'https://{instance}{next_record_url}'
+                   .format(instance=self.sf_instance,
+                           next_record_url=next_records_identifier))
+        else:
+            endpoint = 'queryAll' if include_deleted else 'query'
+            url = self.base_url + '{query_endpoint}/{next_record_id}'
+            url = url.format(query_endpoint=endpoint,
+                             next_record_id=next_records_identifier)
+        result = self._call_salesforce('GET', url, name='query_more', **kwargs)
+
+        return result.json(object_pairs_hook=OrderedDict)
+
+    def query_all(self, query, include_deleted=False, **kwargs):
+        """Returns the full set of results for the `query`. This is a
+        convenience
+        wrapper around `query(...)` and `query_more(...)`.
+
+        The returned dict is the decoded JSON payload from the final call to
+        Salesforce, but with the `totalSize` field representing the full
+        number of results retrieved and the `records` list representing the
+        full list of records retrieved.
+
+        Arguments
+
+        * query -- the SOQL query to send to Salesforce, e.g.
+                   SELECT Id FROM Lead WHERE Email = "waldo@somewhere.com"
+        * include_deleted -- True if the query should include deleted records.
+        """
+
+        result = self.query(query, include_deleted=include_deleted, **kwargs)
+        all_records = []
+
+        while True:
+            all_records.extend(result['records'])
+            # fetch next batch if we're not done else break out of loop
+            if not result['done']:
+                result = self.query_more(result['nextRecordsUrl'],
+                                         identifier_is_url=True)
+            else:
+                break
+
+        result['records'] = all_records
+        return result
+
+    def apexecute(self, action, method='GET', data=None, **kwargs):
+        """Makes an HTTP request to an APEX REST endpoint
+
+        Arguments:
+
+        * action -- The REST endpoint for the request.
+        * method -- HTTP method for the request (default GET)
+        * data -- A dict of parameters to send in a POST / PUT request
+        * kwargs -- Additional kwargs to pass to `requests.request`
+        """
+        result = self._call_salesforce(
+            method,
+            self.apex_url + action,
+            name="apexexcute",
+            data=json.dumps(data), **kwargs
+        )
+        try:
+            response_content = result.json()
+        # pylint: disable=broad-except
+        except Exception:
+            response_content = result.text
+
+        return response_content
+
+    def _call_salesforce(self, method, url, name="", **kwargs):
+        """Utility method for performing HTTP call to Salesforce.
+
+        Returns a `requests.result` object.
+        """
+        result = self.session.request(
+            method, url, headers=self.headers, **kwargs)
+
+        if result.status_code >= 300:
+            exception_handler(result, name=name)
+
+        sforce_limit_info = result.headers.get('Sforce-Limit-Info')
+        if sforce_limit_info:
+            self.api_usage = self.parse_api_usage(sforce_limit_info)
+
+        return result
+
+    @property
+    def request(self):
+        """Deprecated access to self.session for backwards compatibility"""
+        _warn_request_deprecation()
+        return self.session
+
+    @request.setter
+    def request(self, session):
+        """Deprecated setter for self.session"""
+        _warn_request_deprecation()
+        self.session = session
+
+    @staticmethod
+    def parse_api_usage(sforce_limit_info):
+        """parse API usage and limits out of the Sforce-Limit-Info header
+
+        Arguments:
+
+        * sforce_limit_info: The value of response header 'Sforce-Limit-Info'
+            Example 1: 'api-usage=18/5000'
+            Example 2: 'api-usage=25/5000;
+                per-app-api-usage=17/250(appName=sample-connected-app)'
+        """
+        result = {}
+
+        api_usage = re.match(r'[^-]?api-usage=(?P<used>\d+)/(?P<tot>\d+)',
+                             sforce_limit_info)
+        pau = r'.+per-app-api-usage=(?P<u>\d+)/(?P<t>\d+)\(appName=(?P<n>.+)\)'
+        per_app_api_usage = re.match(pau, sforce_limit_info)
+
+        if api_usage and api_usage.groups():
+            groups = api_usage.groups()
+            result['api-usage'] = Usage(used=int(groups[0]),
+                                        total=int(groups[1]))
+        if per_app_api_usage and per_app_api_usage.groups():
+            groups = per_app_api_usage.groups()
+            result['per-app-api-usage'] = PerAppUsage(used=int(groups[0]),
+                                                      total=int(groups[1]),
+                                                      name=groups[2])
+
+        return result
+
+class SFType(object):
+    """An interface to a specific type of SObject"""
+
+    # pylint: disable=too-many-arguments
+    def __init__(
+            self, object_name, session_id, sf_instance,
+            sf_version=DEFAULT_API_VERSION, proxies=None, session=None):
+        """Initialize the instance with the given parameters.
+
+        Arguments:
+
+        * object_name -- the name of the type of SObject this represents,
+                         e.g. `Lead` or `Contact`
+        * session_id -- the session ID for authenticating to Salesforce
+        * sf_instance -- the domain of the instance of Salesforce to use
+        * sf_version -- the version of the Salesforce API to use
+        * proxies -- the optional map of scheme to proxy server
+        * session -- Custom requests session, created in calling code. This
+                     enables the use of requests Session features not otherwise
+                     exposed by simple_salesforce.
+        """
+        self.session_id = session_id
+        self.name = object_name
+        self.session = session or requests.Session()
+        # don't wipe out original proxies with None
+        if not session and proxies is not None:
+            self.session.proxies = proxies
+        self.api_usage = {}
+
+        self.base_url = (
+            u'https://{instance}/services/data/v{sf_version}/sobjects'
+            '/{object_name}/'.format(instance=sf_instance,
+                                     object_name=object_name,
+                                     sf_version=sf_version))
+
+    def metadata(self, headers=None):
+        """Returns the result of a GET to `.../{object_name}/` as a dict
+        decoded from the JSON payload returned by Salesforce.
+
+        Arguments:
+
+        * headers -- a dict with additional request headers.
+        """
+        result = self._call_salesforce('GET', self.base_url, headers=headers)
+        return result.json(object_pairs_hook=OrderedDict)
+
+    def describe(self, headers=None):
+        """Returns the result of a GET to `.../{object_name}/describe` as a
+        dict decoded from the JSON payload returned by Salesforce.
+
+        Arguments:
+
+        * headers -- a dict with additional request headers.
+        """
+        result = self._call_salesforce(
+            method='GET', url=urljoin(self.base_url, 'describe'),
+            headers=headers
+        )
+        return result.json(object_pairs_hook=OrderedDict)
+
+    def describe_layout(self, record_id, headers=None):
+        """Returns the layout of the object
+
+        Returns the result of a GET to
+        `.../{object_name}/describe/layouts/<recordid>` as a dict decoded from
+        the JSON payload returned by Salesforce.
+
+        Arguments:
+
+        * record_id -- the Id of the SObject to get
+        * headers -- a dict with additional request headers.
+        """
+        custom_url_part = 'describe/layouts/{record_id}'.format(
+            record_id=record_id
+        )
+        result = self._call_salesforce(
+            method='GET',
+            url=urljoin(self.base_url, custom_url_part),
+            headers=headers
+        )
+        return result.json(object_pairs_hook=OrderedDict)
+
+    def get(self, record_id, headers=None):
+        """Returns the result of a GET to `.../{object_name}/{record_id}` as a
+        dict decoded from the JSON payload returned by Salesforce.
+
+        Arguments:
+
+        * record_id -- the Id of the SObject to get
+        * headers -- a dict with additional request headers.
+        """
+        result = self._call_salesforce(
+            method='GET', url=urljoin(self.base_url, record_id),
+            headers=headers
+        )
+        return result.json(object_pairs_hook=OrderedDict)
+
+    def get_by_custom_id(self, custom_id_field, custom_id, headers=None):
+        """Return an ``SFType`` by custom ID
+
+        Returns the result of a GET to
+        `.../{object_name}/{custom_id_field}/{custom_id}` as a dict decoded
+        from the JSON payload returned by Salesforce.
+
+        Arguments:
+
+        * custom_id_field -- the API name of a custom field that was defined
+                             as an External ID
+        * custom_id - the External ID value of the SObject to get
+        * headers -- a dict with additional request headers.
+        """
+        custom_url = urljoin(
+            self.base_url, '{custom_id_field}/{custom_id}'.format(
+                custom_id_field=custom_id_field, custom_id=custom_id
+            )
+        )
+        result = self._call_salesforce(
+            method='GET', url=custom_url, headers=headers
+        )
+        return result.json(object_pairs_hook=OrderedDict)
+
+    def create(self, data, headers=None):
+        """Creates a new SObject using a POST to `.../{object_name}/`.
+
+        Returns a dict decoded from the JSON payload returned by Salesforce.
+
+        Arguments:
+
+        * data -- a dict of the data to create the SObject from. It will be
+                  JSON-encoded before being transmitted.
+        * headers -- a dict with additional request headers.
+        """
+        result = self._call_salesforce(
+            method='POST', url=self.base_url,
+            data=json.dumps(data), headers=headers
+        )
+        return result.json(object_pairs_hook=OrderedDict)
+
+    def upsert(self, record_id, data, raw_response=False, headers=None):
+        """Creates or updates an SObject using a PATCH to
+        `.../{object_name}/{record_id}`.
+
+        If `raw_response` is false (the default), returns the status code
+        returned by Salesforce. Otherwise, return the `requests.Response`
+        object.
+
+        Arguments:
+
+        * record_id -- an identifier for the SObject as described in the
+                       Salesforce documentation
+        * data -- a dict of the data to create or update the SObject from. It
+                  will be JSON-encoded before being transmitted.
+        * raw_response -- a boolean indicating whether to return the response
+                          directly, instead of the status code.
+        * headers -- a dict with additional request headers.
+        """
+        result = self._call_salesforce(
+            method='PATCH', url=urljoin(self.base_url, record_id),
+            data=json.dumps(data), headers=headers
+        )
+        return self._raw_response(result, raw_response)
+
+    def update(self, record_id, data, raw_response=False, headers=None):
+        """Updates an SObject using a PATCH to
+        `.../{object_name}/{record_id}`.
+
+        If `raw_response` is false (the default), returns the status code
+        returned by Salesforce. Otherwise, return the `requests.Response`
+        object.
+
+        Arguments:
+
+        * record_id -- the Id of the SObject to update
+        * data -- a dict of the data to update the SObject from. It will be
+                  JSON-encoded before being transmitted.
+        * raw_response -- a boolean indicating whether to return the response
+                          directly, instead of the status code.
+        * headers -- a dict with additional request headers.
+        """
+        result = self._call_salesforce(
+            method='PATCH', url=urljoin(self.base_url, record_id),
+            data=json.dumps(data), headers=headers
+        )
+        return self._raw_response(result, raw_response)
+
+    def delete(self, record_id, raw_response=False, headers=None):
+        """Deletes an SObject using a DELETE to
+        `.../{object_name}/{record_id}`.
+
+        If `raw_response` is false (the default), returns the status code
+        returned by Salesforce. Otherwise, return the `requests.Response`
+        object.
+
+        Arguments:
+
+        * record_id -- the Id of the SObject to delete
+        * raw_response -- a boolean indicating whether to return the response
+                          directly, instead of the status code.
+        * headers -- a dict with additional request headers.
+        """
+        result = self._call_salesforce(
+            method='DELETE', url=urljoin(self.base_url, record_id),
+            headers=headers
+        )
+        return self._raw_response(result, raw_response)
+
+    def deleted(self, start, end, headers=None):
+        # pylint: disable=line-too-long
+        """Gets a list of deleted records
+
+        Use the SObject Get Deleted resource to get a list of deleted records
+        for the specified object.
+        .../deleted/?start=2013-05-05T00:00:00+00:00&end=2013-05-10T00:00:00+00:00
+
+        * start -- start datetime object
+        * end -- end datetime object
+        * headers -- a dict with additional request headers.
+        """
+        url = urljoin(
+            self.base_url, 'deleted/?start={start}&end={end}'.format(
+                start=date_to_iso8601(start), end=date_to_iso8601(end)
+            )
+        )
+        result = self._call_salesforce(method='GET', url=url, headers=headers)
+        return result.json(object_pairs_hook=OrderedDict)
+
+    def updated(self, start, end, headers=None):
+        # pylint: disable=line-too-long
+        """Gets a list of updated records
+
+        Use the SObject Get Updated resource to get a list of updated
+        (modified or added) records for the specified object.
+
+         .../updated/?start=2014-03-20T00:00:00+00:00&end=2014-03-22T00:00:00+00:00
+
+        * start -- start datetime object
+        * end -- end datetime object
+        * headers -- a dict with additional request headers.
+        """
+        url = urljoin(
+            self.base_url, 'updated/?start={start}&end={end}'.format(
+                start=date_to_iso8601(start), end=date_to_iso8601(end)
+            )
+        )
+        result = self._call_salesforce(method='GET', url=url, headers=headers)
+        return result.json(object_pairs_hook=OrderedDict)
+
+    def _call_salesforce(self, method, url, **kwargs):
+        """Utility method for performing HTTP call to Salesforce.
+
+        Returns a `requests.result` object.
+        """
+        headers = {
+            'Content-Type': 'application/json',
+            'Authorization': 'Bearer ' + self.session_id,
+            'X-PrettyPrint': '1'
+        }
+        additional_headers = kwargs.pop('headers', dict())
+        headers.update(additional_headers or dict())
+        result = self.session.request(method, url, headers=headers, **kwargs)
+
+        if result.status_code >= 300:
+            exception_handler(result, self.name)
+
+        sforce_limit_info = result.headers.get('Sforce-Limit-Info')
+        if sforce_limit_info:
+            self.api_usage = Salesforce.parse_api_usage(sforce_limit_info)
+
+        return result
+
+    # pylint: disable=no-self-use
+    def _raw_response(self, response, body_flag):
+        """Utility method for processing the response and returning either the
+        status code or the response object.
+
+        Returns either an `int` or a `requests.Response` object.
+        """
+        if not body_flag:
+            return response.status_code
+
+        return response
+
+    @property
+    def request(self):
+        """Deprecated access to self.session for backwards compatibility"""
+        _warn_request_deprecation()
+        return self.session
+
+    @request.setter
+    def request(self, session):
+        """Deprecated setter for self.session"""
+        _warn_request_deprecation()
+        self.session = session
+
+
+class SalesforceAPI(Salesforce):
+    """Deprecated SalesforceAPI Instance
+
+    This class implements the Username/Password Authentication Mechanism using
+    Arguments It has since been surpassed by the 'Salesforce' class, which
+    relies on kwargs
+
+    """
+    # pylint: disable=too-many-arguments
+    def __init__(self, username, password, security_token, sandbox=False,
+                 sf_version='27.0'):
+        """Initialize the instance with the given parameters.
+
+        Arguments:
+
+        * username -- the Salesforce username to use for authentication
+        * password -- the password for the username
+        * security_token -- the security token for the username
+        * sandbox -- True if you want to login to `test.salesforce.com`, False
+                     if you want to login to `login.salesforce.com`.
+        * sf_version -- the version of the Salesforce API to use, for example
+                        "27.0"
+        """
+        warnings.warn(
+            "Use of login arguments has been deprecated. Please use kwargs",
+            DeprecationWarning
+        )
+
+        super(SalesforceAPI, self).__init__(username=username,
+                                            password=password,
+                                            security_token=security_token,
+                                            sandbox=sandbox,
+                                            version=sf_version)

+ 238 - 0
desktop/core/ext-py/simple-salesforce-0.74.2/simple_salesforce/bulk.py

@@ -0,0 +1,238 @@
+""" Classes for interacting with Salesforce Bulk API """
+
+try:
+    from collections import OrderedDict
+except ImportError:
+    # Python < 2.7
+    from ordereddict import OrderedDict
+
+import json
+import requests
+from time import sleep
+from simple_salesforce.util import call_salesforce
+
+
+class SFBulkHandler(object):
+    """ Bulk API request handler
+    Intermediate class which allows us to use commands,
+     such as 'sf.bulk.Contacts.create(...)'
+    This is really just a middle layer, whose sole purpose is
+    to allow the above syntax
+    """
+
+    def __init__(self, session_id, bulk_url, proxies=None, session=None):
+        """Initialize the instance with the given parameters.
+
+        Arguments:
+
+        * session_id -- the session ID for authenticating to Salesforce
+        * bulk_url -- API endpoint set in Salesforce instance
+        * proxies -- the optional map of scheme to proxy server
+        * session -- Custom requests session, created in calling code. This
+                     enables the use of requests Session features not otherwise
+                     exposed by simple_salesforce.
+        """
+        self.session_id = session_id
+        self.session = session or requests.Session()
+        self.bulk_url = bulk_url
+        # don't wipe out original proxies with None
+        if not session and proxies is not None:
+            self.session.proxies = proxies
+
+        # Define these headers separate from Salesforce class,
+        # as bulk uses a slightly different format
+        self.headers = {
+            'Content-Type': 'application/json',
+            'X-SFDC-Session': self.session_id,
+            'X-PrettyPrint': '1'
+        }
+
+    def __getattr__(self, name):
+        return SFBulkType(object_name=name, bulk_url=self.bulk_url,
+                          headers=self.headers, session=self.session)
+
+class SFBulkType(object):
+    """ Interface to Bulk/Async API functions"""
+
+    def __init__(self, object_name, bulk_url, headers, session):
+        """Initialize the instance with the given parameters.
+
+        Arguments:
+
+        * object_name -- the name of the type of SObject this represents,
+                         e.g. `Lead` or `Contact`
+        * bulk_url -- API endpoint set in Salesforce instance
+        * headers -- bulk API headers
+        * session -- Custom requests session, created in calling code. This
+                     enables the use of requests Session features not otherwise
+                     exposed by simple_salesforce.
+        """
+        self.object_name = object_name
+        self.bulk_url = bulk_url
+        self.session = session
+        self.headers = headers
+
+    def _create_job(self, operation, object_name, external_id_field=None):
+        """ Create a bulk job
+
+        Arguments:
+
+        * operation -- Bulk operation to be performed by job
+        * object_name -- SF object
+        * external_id_field -- unique identifier field for upsert operations
+        """
+
+        payload = {
+            'operation': operation,
+            'object': object_name,
+            'contentType': 'JSON'
+        }
+
+        if operation == 'upsert':
+            payload['externalIdFieldName'] = external_id_field
+
+        url = "{}{}".format(self.bulk_url, 'job')
+
+        result = call_salesforce(url=url, method='POST', session=self.session,
+                                  headers=self.headers,
+                                  data=json.dumps(payload))
+        return result.json(object_pairs_hook=OrderedDict)
+
+    def _close_job(self, job_id):
+        """ Close a bulk job """
+        payload = {
+            'state': 'Closed'
+        }
+
+        url = "{}{}{}".format(self.bulk_url, 'job/', job_id)
+
+        result = call_salesforce(url=url, method='POST', session=self.session,
+                                  headers=self.headers,
+                                  data=json.dumps(payload))
+        return result.json(object_pairs_hook=OrderedDict)
+
+    def _get_job(self, job_id):
+        """ Get an existing job to check the status """
+        url = "{}{}{}".format(self.bulk_url, 'job/', job_id)
+
+        result = call_salesforce(url=url, method='GET', session=self.session,
+                                  headers=self.headers)
+        return result.json(object_pairs_hook=OrderedDict)
+
+    def _add_batch(self, job_id, data, operation):
+        """ Add a set of data as a batch to an existing job
+        Separating this out in case of later
+        implementations involving multiple batches
+        """
+
+        url = "{}{}{}{}".format(self.bulk_url, 'job/', job_id, '/batch')
+
+        if operation != 'query':
+            data = json.dumps(data)
+
+        result = call_salesforce(url=url, method='POST', session=self.session,
+                                  headers=self.headers, data=data)
+        return result.json(object_pairs_hook=OrderedDict)
+
+    def _get_batch(self, job_id, batch_id):
+        """ Get an existing batch to check the status """
+
+        url = "{}{}{}{}{}".format(self.bulk_url, 'job/',
+                                  job_id, '/batch/', batch_id)
+
+        result = call_salesforce(url=url, method='GET', session=self.session,
+                                  headers=self.headers)
+        return result.json(object_pairs_hook=OrderedDict)
+
+    def _get_batch_results(self, job_id, batch_id, operation):
+        """ retrieve a set of results from a completed job """
+
+        url = "{}{}{}{}{}{}".format(self.bulk_url, 'job/', job_id, '/batch/',
+                                    batch_id, '/result')
+
+        result = call_salesforce(url=url, method='GET', session=self.session,
+                                  headers=self.headers)
+
+        if operation == 'query':
+            url_query_results = "{}{}{}".format(url, '/', result.json()[0])
+            query_result = call_salesforce(url=url_query_results, method='GET',
+                                            session=self.session,
+                                            headers=self.headers)
+            return query_result.json()
+
+        return result.json()
+
+    #pylint: disable=R0913
+    def _bulk_operation(self, object_name, operation, data,
+                        external_id_field=None, wait=5):
+        """ String together helper functions to create a complete
+        end-to-end bulk API request
+
+        Arguments:
+
+        * object_name -- SF object
+        * operation -- Bulk operation to be performed by job
+        * data -- list of dict to be passed as a batch
+        * external_id_field -- unique identifier field for upsert operations
+        * wait -- seconds to sleep between checking batch status
+        """
+
+        job = self._create_job(object_name=object_name, operation=operation,
+                               external_id_field=external_id_field)
+
+        batch = self._add_batch(job_id=job['id'], data=data,
+                                operation=operation)
+
+        self._close_job(job_id=job['id'])
+
+        batch_status = self._get_batch(job_id=batch['jobId'],
+                                       batch_id=batch['id'])['state']
+
+        while batch_status not in ['Completed', 'Failed', 'Not Processed']:
+            sleep(wait)
+            batch_status = self._get_batch(job_id=batch['jobId'],
+                                           batch_id=batch['id'])['state']
+
+        results = self._get_batch_results(job_id=batch['jobId'],
+                                          batch_id=batch['id'],
+                                          operation=operation)
+        return results
+
+    # _bulk_operation wrappers to expose supported Salesforce bulk operations
+    def delete(self, data):
+        """ soft delete records """
+        results = self._bulk_operation(object_name=self.object_name,
+                                       operation='delete', data=data)
+        return results
+
+    def insert(self, data):
+        """ insert records """
+        results = self._bulk_operation(object_name=self.object_name,
+                                       operation='insert', data=data)
+        return results
+
+    def upsert(self, data, external_id_field):
+        """ upsert records based on a unique identifier """
+        results = self._bulk_operation(object_name=self.object_name,
+                                       operation='upsert',
+                                       external_id_field=external_id_field,
+                                       data=data)
+        return results
+
+    def update(self, data):
+        """ update records """
+        results = self._bulk_operation(object_name=self.object_name,
+                                       operation='update', data=data)
+        return results
+
+    def hard_delete(self, data):
+        """ hard delete records """
+        results = self._bulk_operation(object_name=self.object_name,
+                                       operation='hardDelete', data=data)
+        return results
+
+    def query(self, data):
+        """ bulk query """
+        results = self._bulk_operation(object_name=self.object_name,
+                                       operation='query', data=data)
+        return results

+ 105 - 0
desktop/core/ext-py/simple-salesforce-0.74.2/simple_salesforce/exceptions.py

@@ -0,0 +1,105 @@
+"""All exceptions for Simple Salesforce"""
+
+
+class SalesforceError(Exception):
+    """Base Salesforce API exception"""
+
+    message = u'Unknown error occurred for {url}. Response content: {content}'
+
+    def __init__(self, url, status, resource_name, content):
+        """Initialize the SalesforceError exception
+
+        SalesforceError is the base class of exceptions in simple-salesforce
+
+        Args:
+            url: Salesforce URL that was called
+            status: Status code of the error response
+            resource_name: Name of the Salesforce resource being queried
+            content: content of the response
+        """
+        # TODO exceptions don't seem to be using parent constructors at all.
+        # this should be fixed.
+        # pylint: disable=super-init-not-called
+        self.url = url
+        self.status = status
+        self.resource_name = resource_name
+        self.content = content
+
+    def __str__(self):
+        return self.message.format(url=self.url, content=self.content)
+
+    def __unicode__(self):
+        return self.__str__()
+
+
+class SalesforceMoreThanOneRecord(SalesforceError):
+    """
+    Error Code: 300
+    The value returned when an external ID exists in more than one record. The
+    response body contains the list of matching records.
+    """
+    message = u"More than one record for {url}. Response content: {content}"
+
+
+class SalesforceMalformedRequest(SalesforceError):
+    """
+    Error Code: 400
+    The request couldn't be understood, usually because the JSON or XML body
+    contains an error.
+    """
+    message = u"Malformed request {url}. Response content: {content}"
+
+
+class SalesforceExpiredSession(SalesforceError):
+    """
+    Error Code: 401
+    The session ID or OAuth token used has expired or is invalid. The response
+    body contains the message and errorCode.
+    """
+    message = u"Expired session for {url}. Response content: {content}"
+
+
+class SalesforceRefusedRequest(SalesforceError):
+    """
+    Error Code: 403
+    The request has been refused. Verify that the logged-in user has
+    appropriate permissions.
+    """
+    message = u"Request refused for {url}. Response content: {content}"
+
+
+class SalesforceResourceNotFound(SalesforceError):
+    """
+    Error Code: 404
+    The requested resource couldn't be found. Check the URI for errors, and
+    verify that there are no sharing issues.
+    """
+    message = u'Resource {name} Not Found. Response content: {content}'
+
+    def __str__(self):
+        return self.message.format(name=self.resource_name,
+                                   content=self.content)
+
+class SalesforceAuthenticationFailed(SalesforceError):
+    """
+    Thrown to indicate that authentication with Salesforce failed.
+    """
+    def __init__(self, code, message):
+        # TODO exceptions don't seem to be using parent constructors at all.
+        # this should be fixed.
+        # pylint: disable=super-init-not-called
+        self.code = code
+        self.message = message
+
+    def __str__(self):
+        return u'{code}: {message}'.format(code=self.code,
+                                           message=self.message)
+
+class SalesforceGeneralError(SalesforceError):
+    """
+    A non-specific Salesforce error.
+    """
+    message = u'Error Code {status}. Response content: {content}'
+
+    def __str__(self):
+        return self.message.format(status=self.status, content=self.content)

+ 188 - 0
desktop/core/ext-py/simple-salesforce-0.74.2/simple_salesforce/login.py

@@ -0,0 +1,188 @@
+"""Login classes and functions for Simple-Salesforce
+
+Heavily Modified from RestForce 1.0.0
+"""
+
+DEFAULT_CLIENT_ID_PREFIX = 'RestForce'
+
+
+from simple_salesforce.api import DEFAULT_API_VERSION
+from simple_salesforce.util import getUniqueElementValueFromXmlString
+from simple_salesforce.exceptions import SalesforceAuthenticationFailed
+
+try:
+    # Python 3+
+    from html import escape
+except ImportError:
+    from cgi import escape
+import requests
+import warnings
+
+
+# pylint: disable=invalid-name,too-many-arguments,too-many-locals
+def SalesforceLogin(
+        username=None, password=None, security_token=None,
+        organizationId=None, sandbox=None, sf_version=DEFAULT_API_VERSION,
+        proxies=None, session=None, client_id=None, domain=None):
+    """Return a tuple of `(session_id, sf_instance)` where `session_id` is the
+    session ID to use for authentication to Salesforce and `sf_instance` is
+    the domain of the instance of Salesforce to use for the session.
+
+    Arguments:
+
+    * username -- the Salesforce username to use for authentication
+    * password -- the password for the username
+    * security_token -- the security token for the username
+    * organizationId -- the ID of your organization
+            NOTE: security_token an organizationId are mutually exclusive
+    * sandbox -- DEPRECATED: Use domain instead.
+    * sf_version -- the version of the Salesforce API to use, for example
+                    "27.0"
+    * proxies -- the optional map of scheme to proxy server
+    * session -- Custom requests session, created in calling code. This
+                 enables the use of requets Session features not otherwise
+                 exposed by simple_salesforce.
+    * client_id -- the ID of this client
+    * domain -- The domain to using for connecting to Salesforce. Use
+                common domains, such as 'login' or 'test', or
+                Salesforce My domain. If not used, will default to
+                'login'.
+    """
+    if (sandbox is not None) and (domain is not None):
+        raise ValueError("Both 'sandbox' and 'domain' arguments were "
+                         "supplied. Either may be supplied, but not "
+                         "both.")
+
+    if sandbox is not None:
+        warnings.warn("'sandbox' argument is deprecated. Use "
+                      "'domain' instead. Overriding 'domain' "
+                      "with 'sandbox' value.",
+                      DeprecationWarning)
+
+        domain = 'test' if sandbox else 'login'
+
+    if domain is None:
+        domain = 'login'
+
+    soap_url = 'https://{domain}.salesforce.com/services/Soap/u/{sf_version}'
+
+    if client_id:
+        client_id = "{prefix}/{app_name}".format(
+            prefix=DEFAULT_CLIENT_ID_PREFIX,
+            app_name=client_id)
+    else:
+        client_id = DEFAULT_CLIENT_ID_PREFIX
+
+    soap_url = soap_url.format(domain=domain,
+                               sf_version=sf_version)
+
+    # pylint: disable=E0012,deprecated-method
+    username = escape(username)
+    password = escape(password)
+
+    # Check if token authentication is used
+    if security_token is not None:
+        # Security Token Soap request body
+        login_soap_request_body = """<?xml version="1.0" encoding="utf-8" ?>
+        <env:Envelope
+                xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+                xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
+                xmlns:urn="urn:partner.soap.sforce.com">
+            <env:Header>
+                <urn:CallOptions>
+                    <urn:client>{client_id}</urn:client>
+                    <urn:defaultNamespace>sf</urn:defaultNamespace>
+                </urn:CallOptions>
+            </env:Header>
+            <env:Body>
+                <n1:login xmlns:n1="urn:partner.soap.sforce.com">
+                    <n1:username>{username}</n1:username>
+                    <n1:password>{password}{token}</n1:password>
+                </n1:login>
+            </env:Body>
+        </env:Envelope>""".format(
+            username=username, password=password, token=security_token,
+            client_id=client_id)
+
+    # Check if IP Filtering is used in conjunction with organizationId
+    elif organizationId is not None:
+        # IP Filtering Login Soap request body
+        login_soap_request_body = """<?xml version="1.0" encoding="utf-8" ?>
+        <soapenv:Envelope
+                xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
+                xmlns:urn="urn:partner.soap.sforce.com">
+            <soapenv:Header>
+                <urn:CallOptions>
+                    <urn:client>{client_id}</urn:client>
+                    <urn:defaultNamespace>sf</urn:defaultNamespace>
+                </urn:CallOptions>
+                <urn:LoginScopeHeader>
+                    <urn:organizationId>{organizationId}</urn:organizationId>
+                </urn:LoginScopeHeader>
+            </soapenv:Header>
+            <soapenv:Body>
+                <urn:login>
+                    <urn:username>{username}</urn:username>
+                    <urn:password>{password}</urn:password>
+                </urn:login>
+            </soapenv:Body>
+        </soapenv:Envelope>""".format(
+            username=username, password=password, organizationId=organizationId,
+            client_id=client_id)
+    elif username is not None and password is not None:
+        # IP Filtering for non self-service users
+        login_soap_request_body = """<?xml version="1.0" encoding="utf-8" ?>
+        <soapenv:Envelope
+                xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
+                xmlns:urn="urn:partner.soap.sforce.com">
+            <soapenv:Header>
+                <urn:CallOptions>
+                    <urn:client>{client_id}</urn:client>
+                    <urn:defaultNamespace>sf</urn:defaultNamespace>
+                </urn:CallOptions>
+            </soapenv:Header>
+            <soapenv:Body>
+                <urn:login>
+                    <urn:username>{username}</urn:username>
+                    <urn:password>{password}</urn:password>
+                </urn:login>
+            </soapenv:Body>
+        </soapenv:Envelope>""".format(
+            username=username, password=password, client_id=client_id)
+    else:
+        except_code = 'INVALID AUTH'
+        except_msg = (
+            'You must submit either a security token or organizationId for '
+            'authentication'
+        )
+        raise SalesforceAuthenticationFailed(except_code, except_msg)
+
+    login_soap_request_headers = {
+        'content-type': 'text/xml',
+        'charset': 'UTF-8',
+        'SOAPAction': 'login'
+    }
+    response = (session or requests).post(
+        soap_url, login_soap_request_body, headers=login_soap_request_headers,
+        proxies=proxies)
+
+    if response.status_code != 200:
+        except_code = getUniqueElementValueFromXmlString(
+            response.content, 'sf:exceptionCode')
+        except_msg = getUniqueElementValueFromXmlString(
+            response.content, 'sf:exceptionMessage')
+        raise SalesforceAuthenticationFailed(except_code, except_msg)
+
+    session_id = getUniqueElementValueFromXmlString(
+        response.content, 'sessionId')
+    server_url = getUniqueElementValueFromXmlString(
+        response.content, 'serverUrl')
+
+    sf_instance = (server_url
+                   .replace('http://', '')
+                   .replace('https://', '')
+                   .split('/')[0]
+                   .replace('-api', ''))
+
+    return session_id, sf_instance

+ 77 - 0
desktop/core/ext-py/simple-salesforce-0.74.2/simple_salesforce/util.py

@@ -0,0 +1,77 @@
+"""Utility functions for simple-salesforce"""
+
+import xml.dom.minidom
+
+from simple_salesforce.exceptions import (
+    SalesforceGeneralError, SalesforceExpiredSession,
+    SalesforceMalformedRequest, SalesforceMoreThanOneRecord,
+    SalesforceRefusedRequest, SalesforceResourceNotFound
+)
+
+
+# pylint: disable=invalid-name
+def getUniqueElementValueFromXmlString(xmlString, elementName):
+    """
+    Extracts an element value from an XML string.
+
+    For example, invoking
+    getUniqueElementValueFromXmlString(
+        '<?xml version="1.0" encoding="UTF-8"?><foo>bar</foo>', 'foo')
+    should return the value 'bar'.
+    """
+    xmlStringAsDom = xml.dom.minidom.parseString(xmlString)
+    elementsByName = xmlStringAsDom.getElementsByTagName(elementName)
+    elementValue = None
+    if len(elementsByName) > 0:
+        elementValue = elementsByName[0].toxml().replace(
+            '<' + elementName + '>', '').replace('</' + elementName + '>', '')
+    return elementValue
+
+
+def date_to_iso8601(date):
+    """Returns an ISO8601 string from a date"""
+    datetimestr = date.strftime('%Y-%m-%dT%H:%M:%S')
+    timezone_sign = date.strftime('%z')[0:1]
+    timezone_str = '%s:%s' % (
+        date.strftime('%z')[1:3], date.strftime('%z')[3:5])
+    return '{datetimestr}{tzsign}{timezone}'.format(
+        datetimestr=datetimestr,
+        tzsign=timezone_sign,
+        timezone=timezone_str
+        ).replace(':', '%3A').replace('+', '%2B')
+
+
+def exception_handler(result, name=""):
+    """Exception router. Determines which error to raise for bad results"""
+    try:
+        response_content = result.json()
+    # pylint: disable=broad-except
+    except Exception:
+        response_content = result.text
+
+    exc_map = {
+        300: SalesforceMoreThanOneRecord,
+        400: SalesforceMalformedRequest,
+        401: SalesforceExpiredSession,
+        403: SalesforceRefusedRequest,
+        404: SalesforceResourceNotFound,
+    }
+    exc_cls = exc_map.get(result.status_code, SalesforceGeneralError)
+
+    raise exc_cls(result.url, result.status_code, name, response_content)
+
+
+def call_salesforce(url, method, session, headers, **kwargs):
+    """Utility method for performing HTTP call to Salesforce.
+
+    Returns a `requests.result` object.
+    """
+
+    additional_headers = kwargs.pop('additional_headers', dict())
+    headers.update(additional_headers or dict())
+    result = session.request(method, url, headers=headers, **kwargs)
+
+    if result.status_code >= 300:
+        exception_handler(result)
+
+    return result

+ 2 - 1
ext/thirdparty/README.md

@@ -73,7 +73,8 @@ Checked-in third party dependencies
 |Y|requests|2.10.0|ASL2|https://github.com/kennethreitz/requests/|
 |Y|rsa|3.4.2|ASL2||
 |Y|sasl|0.1.1|Apache|http://pypi.python.org/pypi/sasl/0.1.1|
-|Y|simplejson|  | ||
+|Y|simplejson|  |MIT|3.15|
+|Y|simple salesforce|0.74.2|MIT|http://pypi.python.org/pypi/simple-salesforce|
 |Y|Six|1.9.0|MIT|http://pypi.python.org/pypi/six/1.9.0|
 |Y|South|1.0.2|Apache|http://south.aeracode.org/|
 |Y|sqlparse|0.2.0|BSD|https://github.com/andialbrecht/sqlparse|