sfq 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sfq/__init__.py +137 -0
- sfq/py.typed +0 -0
- sfq-0.0.1.dist-info/METADATA +129 -0
- sfq-0.0.1.dist-info/RECORD +5 -0
- sfq-0.0.1.dist-info/WHEEL +4 -0
sfq/__init__.py
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
import http.client
|
2
|
+
import logging
|
3
|
+
import time
|
4
|
+
import json
|
5
|
+
from urllib.parse import urlparse, quote
|
6
|
+
|
7
|
+
logging.basicConfig(level=logging.INFO)
|
8
|
+
|
9
|
+
class SFAuth:
|
10
|
+
def __init__(
|
11
|
+
self,
|
12
|
+
instance_url,
|
13
|
+
client_id,
|
14
|
+
refresh_token,
|
15
|
+
api_version="v62.0",
|
16
|
+
token_endpoint="/services/oauth2/token",
|
17
|
+
access_token=None,
|
18
|
+
token_expiration_time=None,
|
19
|
+
token_lifetime=15 * 60,
|
20
|
+
):
|
21
|
+
"""
|
22
|
+
Initializes the SFAuth with necessary parameters.
|
23
|
+
|
24
|
+
:param instance_url: The Salesforce instance URL.
|
25
|
+
:param client_id: The client ID for OAuth.
|
26
|
+
:param refresh_token: The refresh token for OAuth.
|
27
|
+
:param api_version: The Salesforce API version (default is "v62.0").
|
28
|
+
:param token_endpoint: The token endpoint (default is "/services/oauth2/token").
|
29
|
+
:param access_token: The access token for the current session (default is None).
|
30
|
+
:param token_expiration_time: The expiration time of the access token (default is None).
|
31
|
+
:param token_lifetime: The lifetime of the access token (default is 15 minutes).
|
32
|
+
"""
|
33
|
+
self.instance_url = instance_url
|
34
|
+
self.client_id = client_id
|
35
|
+
self.refresh_token = refresh_token
|
36
|
+
self.api_version = api_version
|
37
|
+
self.token_endpoint = token_endpoint
|
38
|
+
self.access_token = access_token
|
39
|
+
self.token_expiration_time = token_expiration_time
|
40
|
+
self.token_lifetime = token_lifetime
|
41
|
+
|
42
|
+
def _prepare_payload(self):
|
43
|
+
"""Prepare the payload for the token request."""
|
44
|
+
return {
|
45
|
+
"grant_type": "refresh_token",
|
46
|
+
"client_id": self.client_id,
|
47
|
+
"refresh_token": self.refresh_token,
|
48
|
+
}
|
49
|
+
|
50
|
+
def _send_post_request(self, payload):
|
51
|
+
"""Send a POST request to the Salesforce token endpoint using http.client."""
|
52
|
+
parsed_url = urlparse(self.instance_url)
|
53
|
+
conn = http.client.HTTPSConnection(parsed_url.netloc)
|
54
|
+
|
55
|
+
headers = {"Content-Type": "application/x-www-form-urlencoded"}
|
56
|
+
body = "&".join([f"{key}={value}" for key, value in payload.items()])
|
57
|
+
|
58
|
+
try:
|
59
|
+
conn.request("POST", self.token_endpoint, body, headers)
|
60
|
+
response = conn.getresponse()
|
61
|
+
data = response.read().decode("utf-8")
|
62
|
+
if response.status == 200:
|
63
|
+
return json.loads(data)
|
64
|
+
else:
|
65
|
+
logging.error(
|
66
|
+
f"HTTP error occurred: {response.status} {response.reason}"
|
67
|
+
)
|
68
|
+
logging.error(f"Response content: {data}")
|
69
|
+
except Exception as err:
|
70
|
+
logging.error(f"Other error occurred: {err}")
|
71
|
+
finally:
|
72
|
+
conn.close()
|
73
|
+
|
74
|
+
return None
|
75
|
+
|
76
|
+
def _refresh_token_if_needed(self):
|
77
|
+
"""Automatically refresh the token if it has expired or is missing."""
|
78
|
+
_token_expiration = self._is_token_expired
|
79
|
+
if self.access_token and not _token_expiration:
|
80
|
+
return
|
81
|
+
|
82
|
+
if not self.access_token:
|
83
|
+
logging.debug("No access token available. Requesting a new one.")
|
84
|
+
elif _token_expiration:
|
85
|
+
logging.debug("Access token has expired. Requesting a new one.")
|
86
|
+
|
87
|
+
payload = self._prepare_payload()
|
88
|
+
token_data = self._send_post_request(payload)
|
89
|
+
if token_data:
|
90
|
+
self.access_token = token_data["access_token"]
|
91
|
+
self.token_expiration_time = int(token_data["issued_at"]) + int(self.token_lifetime)
|
92
|
+
logging.debug("Access token refreshed successfully.")
|
93
|
+
else:
|
94
|
+
logging.error("Failed to refresh access token.")
|
95
|
+
|
96
|
+
|
97
|
+
def _is_token_expired(self):
|
98
|
+
"""Check if the access token has expired."""
|
99
|
+
return time.time() >= self.token_expiration_time
|
100
|
+
|
101
|
+
def query(self, query, tooling=False):
|
102
|
+
"""Query Salesforce using SOQL or Tooling API, depending on the `tooling` parameter."""
|
103
|
+
self._refresh_token_if_needed()
|
104
|
+
|
105
|
+
if not self.access_token:
|
106
|
+
logging.error("No access token available to make the query.")
|
107
|
+
return None
|
108
|
+
|
109
|
+
if tooling:
|
110
|
+
query_endpoint = f"/services/data/{self.api_version}/tooling/query"
|
111
|
+
else:
|
112
|
+
query_endpoint = f"/services/data/{self.api_version}/query"
|
113
|
+
|
114
|
+
headers = {"Authorization": f"Bearer {self.access_token}"}
|
115
|
+
|
116
|
+
# Handle special characters in the query
|
117
|
+
encoded_query = quote(query)
|
118
|
+
params = f"?q={encoded_query}"
|
119
|
+
|
120
|
+
parsed_url = urlparse(self.instance_url)
|
121
|
+
conn = http.client.HTTPSConnection(parsed_url.netloc)
|
122
|
+
|
123
|
+
try:
|
124
|
+
conn.request("GET", query_endpoint + params, headers=headers)
|
125
|
+
response = conn.getresponse()
|
126
|
+
data = response.read().decode("utf-8")
|
127
|
+
if response.status == 200:
|
128
|
+
return json.loads(data)
|
129
|
+
else:
|
130
|
+
logging.error(f"HTTP error occurred during query: {response.status} {response.reason}")
|
131
|
+
logging.error(f"Response content: {data}")
|
132
|
+
except Exception as err:
|
133
|
+
logging.error(f"Other error occurred during query: {err}")
|
134
|
+
finally:
|
135
|
+
conn.close()
|
136
|
+
|
137
|
+
return None
|
sfq/py.typed
ADDED
File without changes
|
@@ -0,0 +1,129 @@
|
|
1
|
+
Metadata-Version: 2.3
|
2
|
+
Name: sfq
|
3
|
+
Version: 0.0.1
|
4
|
+
Summary: Python wrapper for the Salesforce's Query API.
|
5
|
+
Author-email: David Moruzzi <sfq.pypi@dmoruzi.com>
|
6
|
+
Keywords: salesforce,salesforce query
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
8
|
+
Classifier: Intended Audience :: Developers
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
10
|
+
Classifier: Programming Language :: Python :: 3.13
|
11
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
12
|
+
Requires-Python: >=3.12
|
13
|
+
Description-Content-Type: text/markdown
|
14
|
+
|
15
|
+
# sfq (Salesforce Query)
|
16
|
+
|
17
|
+
sfq is a lightweight Python wrapper library designed to simplify querying Salesforce, reducing repetitive code for accessing Salesforce data.
|
18
|
+
|
19
|
+
For more varied workflows, consider using an alternative like [Simple Salesforce](https://simple-salesforce.readthedocs.io/en/stable/). This library was even referenced on the [Salesforce Developers Blog](https://developer.salesforce.com/blogs/2021/09/how-to-automate-data-extraction-from-salesforce-using-python).
|
20
|
+
|
21
|
+
## Features
|
22
|
+
|
23
|
+
- Simplified query execution for Salesforce instances.
|
24
|
+
- Integration with Salesforce authentication via refresh tokens.
|
25
|
+
- Option to interact with Salesforce Tooling API for more advanced queries.
|
26
|
+
|
27
|
+
## Installation
|
28
|
+
|
29
|
+
You can install the `sfq` library using `pip`:
|
30
|
+
|
31
|
+
```bash
|
32
|
+
pip install sfq
|
33
|
+
```
|
34
|
+
|
35
|
+
## Usage
|
36
|
+
|
37
|
+
### Querying Salesforce Data
|
38
|
+
|
39
|
+
```python
|
40
|
+
from sfq import SFAuth
|
41
|
+
|
42
|
+
# Initialize the SFAuth class with authentication details
|
43
|
+
sf = SFAuth(
|
44
|
+
instance_url="https://example-dev-ed.trailblaze.my.salesforce.com",
|
45
|
+
client_id="PlatformCLI",
|
46
|
+
refresh_token="your-refresh-token-here"
|
47
|
+
)
|
48
|
+
|
49
|
+
# Execute a query to fetch account records
|
50
|
+
print(sf.query("SELECT Id FROM Account LIMIT 5"))
|
51
|
+
|
52
|
+
# Execute a query to fetch Tooling API data
|
53
|
+
print(sf.query("SELECT Id, FullName, Metadata FROM SandboxSettings LIMIT 5", tooling=True))
|
54
|
+
```
|
55
|
+
|
56
|
+
### Querying from Bash
|
57
|
+
|
58
|
+
You can easily incorporate this into ad-hoc bash scripts or commands:
|
59
|
+
|
60
|
+
```bash
|
61
|
+
python -c "from sfq import SFAuth; sf = SFAuth(instance_url='$instance_url', client_id='$client_id', refresh_token='$refresh_token'); print(sf.query('$query'))" | jq -r '.records[].Id'
|
62
|
+
```
|
63
|
+
|
64
|
+
## How to Obtain Salesforce Tokens
|
65
|
+
|
66
|
+
To use the `sfq` library, you'll need a **client ID** and **refresh token**. The easiest way to obtain these is by using the Salesforce CLI:
|
67
|
+
|
68
|
+
### Steps to Get Tokens
|
69
|
+
|
70
|
+
1. **Install the Salesforce CLI**:
|
71
|
+
Follow the instructions on the [Salesforce CLI installation page](https://developer.salesforce.com/tools/salesforcecli).
|
72
|
+
|
73
|
+
2. **Authenticate with Salesforce**:
|
74
|
+
Login to your Salesforce org using the following command:
|
75
|
+
|
76
|
+
```bash
|
77
|
+
sf org login web --alias int --instance-url https://corpa--int.sandbox.my.salesforce.com
|
78
|
+
```
|
79
|
+
|
80
|
+
3. **Display Org Details**:
|
81
|
+
To get the client ID, refresh token, and instance URL, run:
|
82
|
+
|
83
|
+
```bash
|
84
|
+
sf org display --target-org int --verbose --json
|
85
|
+
```
|
86
|
+
|
87
|
+
The output will look like this:
|
88
|
+
|
89
|
+
```json
|
90
|
+
{
|
91
|
+
"status": 0,
|
92
|
+
"result": {
|
93
|
+
"id": "00Daa0000000000000",
|
94
|
+
"apiVersion": "62.0",
|
95
|
+
"accessToken": "your-access-token-here",
|
96
|
+
"instanceUrl": "https://example-dev-ed.trailblaze.my.salesforce.com",
|
97
|
+
"username": "user@example.com",
|
98
|
+
"clientId": "PlatformCLI",
|
99
|
+
"connectedStatus": "Connected",
|
100
|
+
"sfdxAuthUrl": "force://PlatformCLI::your-refresh-token-here::https://example-dev-ed.trailblaze.my.salesforce.com",
|
101
|
+
"alias": "int"
|
102
|
+
}
|
103
|
+
}
|
104
|
+
```
|
105
|
+
|
106
|
+
4. **Extract and Use the Tokens**:
|
107
|
+
The `sfdxAuthUrl` is structured as:
|
108
|
+
|
109
|
+
```
|
110
|
+
force://<client_id>::<refresh_token>::<instance_url>
|
111
|
+
```
|
112
|
+
|
113
|
+
You can extract and use the tokens in a bash script like this:
|
114
|
+
|
115
|
+
```bash
|
116
|
+
query="SELECT Id FROM User WHERE IsActive = true AND Profile.Name = 'System Administrator'"
|
117
|
+
|
118
|
+
sfdxAuthUrl=$(sf org display --target-org int --verbose --json | jq -r '.result.sfdxAuthUrl' | sed 's/force:\/\///')
|
119
|
+
clientId=$(echo "$sfdxAuthUrl" | sed 's/::/\n/g' | sed -n '1p')
|
120
|
+
refreshToken=$(echo "$sfdxAuthUrl" | sed 's/::/\n/g' | sed -n '2p')
|
121
|
+
instanceUrl=$(echo "$sfdxAuthUrl" | sed 's/::/\n/g' | sed -n '3p')
|
122
|
+
|
123
|
+
pip install sfq && python -c "from sfq import SFAuth; sf = SFAuth(instance_url='$instanceUrl', client_id='$clientId', refresh_token='$refreshToken'); print(sf.query('$query'))" | jq -r '.records[].Id'
|
124
|
+
```
|
125
|
+
|
126
|
+
## Notes
|
127
|
+
|
128
|
+
- **Authentication**: Make sure your refresh token is kept secure, as it grants access to your Salesforce instance.
|
129
|
+
- **Tooling API**: You can set the `tooling=True` argument in the `query` method to access the Salesforce Tooling API for more advanced metadata queries.
|
@@ -0,0 +1,5 @@
|
|
1
|
+
sfq/__init__.py,sha256=pOOF_qbF_5lWmZCp7Zth_cllmqD9Svr3pOVu01-1h5w,5180
|
2
|
+
sfq/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
sfq-0.0.1.dist-info/METADATA,sha256=tDxHUnStTdh2UrOqI9ItvquS3BHSVANmdhuF2-Sshlo,4585
|
4
|
+
sfq-0.0.1.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
5
|
+
sfq-0.0.1.dist-info/RECORD,,
|