Skip to content

Commit 5dcc3b5

Browse files
committed
First commit
1 parent f942123 commit 5dcc3b5

5 files changed

Lines changed: 196 additions & 0 deletions

File tree

salesforce/__init__.py

Whitespace-only changes.

salesforce/client.py

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import requests
2+
from salesforce.decorators import access_token_required
3+
from salesforce.exceptions import UnknownError, BadOAuthTokenError
4+
from urllib.parse import unquote, urlencode
5+
6+
7+
class Client(object):
8+
BASE_URL = '{}services/data/'
9+
SALESFORCE_REQUEST_TOKEN_URL = 'https://login.salesforce.com/services/oauth2/token'
10+
SALESFORCE_AUTHORIZE_URL = 'https://login.salesforce.com/services/oauth2/authorize'
11+
12+
def __init__(self, client_id, client_secret, instance_url, version):
13+
self.client_id = client_id
14+
self.client_secret = client_secret
15+
self.instance_url = self.BASE_URL.format(instance_url)
16+
self.rest_url = self.instance_url + '{}/'.format(version)
17+
if version.startswith('v'):
18+
version = version[1:]
19+
self.version = version
20+
self.access_token = None
21+
self.resource_urls = {}
22+
23+
def _get_resource_url(self, name):
24+
url = self.resource_urls.get(name, None)
25+
if url:
26+
return url
27+
user_info = self.get_user_info()
28+
for k, v in user_info['urls'].items():
29+
self.resource_urls[k] = v.replace('{version}', self.version)
30+
return self.resource_urls.get(name, None)
31+
32+
def set_access_token(self, token):
33+
self.access_token = token['access_token']
34+
35+
def authorization_url(self, redirect_uri):
36+
params = {
37+
'response_type': 'code',
38+
'client_id': self.client_id,
39+
'redirect_uri': redirect_uri
40+
}
41+
url = self.SALESFORCE_AUTHORIZE_URL
42+
return '{}?{}'.format(url, urlencode(params))
43+
44+
def exchange_code(self, redirect_uri, code):
45+
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
46+
data = {
47+
'grant_type': 'authorization_code',
48+
'redirect_uri': redirect_uri,
49+
'code': unquote(code),
50+
'client_id': self.client_id,
51+
'client_secret': self.client_secret
52+
}
53+
return self._request('POST', self.SALESFORCE_REQUEST_TOKEN_URL, data=data, headers=headers)
54+
55+
def extend_token(self, refresh_token):
56+
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
57+
data = {
58+
'grant_type': 'refresh_token',
59+
'refresh_token': unquote(refresh_token),
60+
'client_id': self.client_id,
61+
'client_secret': self.client_secret
62+
}
63+
return self._request('POST', self.SALESFORCE_REQUEST_TOKEN_URL, data=data, headers=headers)
64+
65+
@access_token_required
66+
def get_user_info(self):
67+
return self._request('GET', 'https://login.salesforce.com/services/oauth2/userinfo')
68+
69+
def get_versions(self):
70+
return self._get(self.instance_url)
71+
72+
@access_token_required
73+
def get_resources_by_version(self):
74+
return self._get(self.rest_url)
75+
76+
@access_token_required
77+
def get_limits(self):
78+
return self._get(self.rest_url + 'limits/')
79+
80+
@access_token_required
81+
def get_describe_global(self):
82+
return self._get(self.rest_url + 'sobjects/')
83+
84+
@access_token_required
85+
def get_sobject(self, sobject):
86+
return self._get(self.rest_url + 'sobjects/{}/'.format(sobject))
87+
88+
@access_token_required
89+
def create_sobject(self, sobject, data):
90+
return self._post(self.rest_url + 'sobjects/{}/'.format(sobject), json=data)
91+
92+
@access_token_required
93+
def get_sobject_describe(self, sobject):
94+
return self._get(self.rest_url + 'sobjects/{}/describe/'.format(sobject))
95+
96+
def create_apex_class(self, name, body):
97+
data = {
98+
'ApiVersion': self.version,
99+
'Body': body,
100+
'Name': name
101+
}
102+
url = self.rest_url + 'tooling/sobjects/ApexClass'
103+
return self._request('POST', url, json=data)
104+
105+
def create_remote_site(self, name, url):
106+
data = '<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><env:Header><urn:SessionHeader xmlns:urn="http://soap.sforce.com/2006/04/metadata"><urn:sessionId>{sessionId}</urn:sessionId></urn:SessionHeader></env:Header><env:Body><createMetadata xmlns="http://soap.sforce.com/2006/04/metadata"><metadata xsi:type="RemoteSiteSetting"><fullName>{name}</fullName><isActive>true</isActive><url>{url}</url></metadata></createMetadata></env:Body></env:Envelope>'
107+
data = data.replace('{name}', name).replace('{url}', url).replace('{sessionId}', self.access_token)
108+
109+
headers = {
110+
'SOAPAction': 'RemoteSiteSetting',
111+
'Content-Type': 'text/xml'
112+
}
113+
url = self._get_resource_url('metadata')
114+
return self._request('POST', url, data=data, headers=headers)
115+
116+
def create_apex_trigger(self, name, body, sobject):
117+
data = {
118+
'ApiVersion': self.version,
119+
'Body': body,
120+
'Name': name,
121+
'TableEnumOrId': sobject
122+
}
123+
url = self.rest_url + 'tooling/sobjects/ApexTrigger'
124+
return self._request('POST', url, json=data)
125+
126+
def _get(self, url, **kwargs):
127+
return self._request('GET', url, **kwargs)
128+
129+
def _post(self, url, **kwargs):
130+
return self._request('POST', url, **kwargs)
131+
132+
def _put(self, url, **kwargs):
133+
return self._request('PUT', url, **kwargs)
134+
135+
def _delete(self, url, **kwargs):
136+
return self._request('DELETE', url, **kwargs)
137+
138+
def _request(self, method, url, headers=None, **kwargs):
139+
_headers = {
140+
'Authorization': 'Bearer {}'.format(self.access_token),
141+
'Accept': 'application/json',
142+
'Content-Type': 'application/json'
143+
}
144+
if headers:
145+
_headers.update(headers)
146+
return self._parse(requests.request(method, url, headers=_headers, **kwargs))
147+
148+
def _parse(self, response):
149+
print(response.status_code)
150+
print(response.text)
151+
status_code = response.status_code
152+
if status_code == 200 or status_code == 201:
153+
return response.json()
154+
if status_code == 204:
155+
return None
156+
if status_code == 403:
157+
raise BadOAuthTokenError()
158+
raise UnknownError()

salesforce/decorators.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
from salesforce.exceptions import AccessTokenRequired
2+
from functools import wraps
3+
4+
5+
def access_token_required(func):
6+
@wraps(func)
7+
def helper(*args, **kwargs):
8+
client = args[0]
9+
if not client.access_token:
10+
raise AccessTokenRequired('You must set the Access Token.')
11+
return func(*args, **kwargs)
12+
13+
return helper

salesforce/exceptions.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
class BaseError(Exception):
2+
pass
3+
4+
5+
class UnknownError(BaseError):
6+
pass
7+
8+
9+
class AccessTokenRequired(BaseError):
10+
pass
11+
12+
13+
class BadOAuthTokenError(BaseError):
14+
pass

setup.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
from setuptools import setup
2+
3+
setup(name='salesforce',
4+
version='0.1',
5+
description='API wrapper for Salesforce written in Python',
6+
url='https://github.com/GearPlug/salesforce-python',
7+
author='Miguel Ferrer',
8+
author_email='ingferrermiguel@gmail.com',
9+
license='GPL',
10+
packages=['salesforce'],
11+
zip_safe=False)

0 commit comments

Comments
 (0)