sfq 0.0.4__tar.gz → 0.0.6__tar.gz

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.
@@ -0,0 +1 @@
1
+ 3.7
@@ -1,3 +1,22 @@
1
+ Metadata-Version: 2.1
2
+ Name: sfq
3
+ Version: 0.0.6
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.7
10
+ Classifier: Programming Language :: Python :: 3.8
11
+ Classifier: Programming Language :: Python :: 3.9
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Requires-Python: >=3.7
17
+ Requires-Dist: prompt-toolkit>=3.0.3
18
+ Description-Content-Type: text/markdown
19
+
1
20
  # sfq (Salesforce Query)
2
21
 
3
22
  sfq is a lightweight Python wrapper library designed to simplify querying Salesforce, reducing repetitive code for accessing Salesforce data.
@@ -20,7 +39,25 @@ pip install sfq
20
39
 
21
40
  ## Usage
22
41
 
23
- ### Querying Salesforce Data
42
+ ### Interactive Querying
43
+
44
+ ```powershell
45
+ usage: python -m sfq [-a SFDXAUTHURL] [--dry-run] [--disable-fuzzy-completion]
46
+
47
+ Interactively query Salesforce data with real-time autocompletion.
48
+
49
+ options:
50
+ -h, --help show this help message and exit
51
+ -a, --sfdxAuthUrl SFDXAUTHURL
52
+ Salesforce auth url
53
+ --dry-run Print the query without executing it
54
+ --disable-fuzzy-completion
55
+ Disable fuzzy completion
56
+ ```
57
+
58
+ You can run the `sfq` library in interactive mode by passing the `-a` option with the `SFDX_AUTH_URL` argument or by setting the `SFDX_AUTH_URL` environment variable.
59
+
60
+ ### Library Querying
24
61
 
25
62
  ```python
26
63
  from sfq import SFAuth
@@ -39,7 +76,7 @@ print(sf.query("SELECT Id FROM Account LIMIT 5"))
39
76
  print(sf.query("SELECT Id, FullName, Metadata FROM SandboxSettings LIMIT 5", tooling=True))
40
77
  ```
41
78
 
42
- ### Querying from Bash
79
+ ### Bash Querying
43
80
 
44
81
  You can easily incorporate this into ad-hoc bash scripts or commands:
45
82
 
@@ -112,4 +149,4 @@ To use the `sfq` library, you'll need a **client ID** and **refresh token**. The
112
149
  ## Notes
113
150
 
114
151
  - **Authentication**: Make sure your refresh token is kept secure, as it grants access to your Salesforce instance.
115
- - **Tooling API**: You can set the `tooling=True` argument in the `query` method to access the Salesforce Tooling API for more advanced metadata queries.
152
+ - **Tooling API**: You can set the `tooling=True` argument in the `query` method to access the Salesforce Tooling API for more advanced metadata queries. This is limited to library usage only.
@@ -1,19 +1,3 @@
1
- Metadata-Version: 2.4
2
- Name: sfq
3
- Version: 0.0.4
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.9
10
- Classifier: Programming Language :: Python :: 3.10
11
- Classifier: Programming Language :: Python :: 3.12
12
- Classifier: Programming Language :: Python :: 3.13
13
- Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
- Requires-Python: >=3.9
15
- Description-Content-Type: text/markdown
16
-
17
1
  # sfq (Salesforce Query)
18
2
 
19
3
  sfq is a lightweight Python wrapper library designed to simplify querying Salesforce, reducing repetitive code for accessing Salesforce data.
@@ -36,7 +20,25 @@ pip install sfq
36
20
 
37
21
  ## Usage
38
22
 
39
- ### Querying Salesforce Data
23
+ ### Interactive Querying
24
+
25
+ ```powershell
26
+ usage: python -m sfq [-a SFDXAUTHURL] [--dry-run] [--disable-fuzzy-completion]
27
+
28
+ Interactively query Salesforce data with real-time autocompletion.
29
+
30
+ options:
31
+ -h, --help show this help message and exit
32
+ -a, --sfdxAuthUrl SFDXAUTHURL
33
+ Salesforce auth url
34
+ --dry-run Print the query without executing it
35
+ --disable-fuzzy-completion
36
+ Disable fuzzy completion
37
+ ```
38
+
39
+ You can run the `sfq` library in interactive mode by passing the `-a` option with the `SFDX_AUTH_URL` argument or by setting the `SFDX_AUTH_URL` environment variable.
40
+
41
+ ### Library Querying
40
42
 
41
43
  ```python
42
44
  from sfq import SFAuth
@@ -55,7 +57,7 @@ print(sf.query("SELECT Id FROM Account LIMIT 5"))
55
57
  print(sf.query("SELECT Id, FullName, Metadata FROM SandboxSettings LIMIT 5", tooling=True))
56
58
  ```
57
59
 
58
- ### Querying from Bash
60
+ ### Bash Querying
59
61
 
60
62
  You can easily incorporate this into ad-hoc bash scripts or commands:
61
63
 
@@ -128,4 +130,4 @@ To use the `sfq` library, you'll need a **client ID** and **refresh token**. The
128
130
  ## Notes
129
131
 
130
132
  - **Authentication**: Make sure your refresh token is kept secure, as it grants access to your Salesforce instance.
131
- - **Tooling API**: You can set the `tooling=True` argument in the `query` method to access the Salesforce Tooling API for more advanced metadata queries.
133
+ - **Tooling API**: You can set the `tooling=True` argument in the `query` method to access the Salesforce Tooling API for more advanced metadata queries. This is limited to library usage only.
@@ -1,17 +1,21 @@
1
1
  [project]
2
2
  name = "sfq"
3
- version = "0.0.4"
3
+ version = "0.0.6"
4
4
  description = "Python wrapper for the Salesforce's Query API."
5
5
  readme = "README.md"
6
6
  authors = [{ name = "David Moruzzi", email = "sfq.pypi@dmoruzi.com" }]
7
7
  keywords = ["salesforce", "salesforce query"]
8
- requires-python = ">=3.9"
9
- dependencies = []
8
+ requires-python = ">=3.7"
9
+ dependencies = [
10
+ "prompt-toolkit>=3.0.3",
11
+ ]
10
12
 
11
13
  classifiers = [
12
14
  "Development Status :: 3 - Alpha",
13
15
  "Intended Audience :: Developers",
14
16
  "Topic :: Software Development :: Libraries :: Python Modules",
17
+ "Programming Language :: Python :: 3.7",
18
+ "Programming Language :: Python :: 3.8",
15
19
  "Programming Language :: Python :: 3.9",
16
20
  "Programming Language :: Python :: 3.10",
17
21
  "Programming Language :: Python :: 3.12",
@@ -20,4 +24,4 @@ classifiers = [
20
24
 
21
25
  [build-system]
22
26
  requires = ["hatchling"]
23
- build-backend = "hatchling.build"
27
+ build-backend = "hatchling.build"
@@ -13,7 +13,7 @@ class SFAuth:
13
13
  instance_url,
14
14
  client_id,
15
15
  refresh_token,
16
- api_version="v62.0",
16
+ api_version="v63.0",
17
17
  token_endpoint="/services/oauth2/token",
18
18
  access_token=None,
19
19
  token_expiration_time=None,
@@ -131,7 +131,7 @@ class SFAuth:
131
131
  try:
132
132
  return time.time() >= float(self.token_expiration_time)
133
133
  except (TypeError, ValueError):
134
- return False
134
+ return True
135
135
 
136
136
  def query(self, query, tooling=False):
137
137
  """Query Salesforce using SOQL or Tooling API, depending on the `tooling` parameter."""
@@ -0,0 +1,151 @@
1
+ import difflib
2
+ import http.client
3
+ import json
4
+
5
+ from sfq import SFAuth
6
+
7
+ from prompt_toolkit import prompt
8
+ from prompt_toolkit.completion import Completer, Completion
9
+
10
+ def _interactive_shell(sf: SFAuth, dry_run: bool, disable_fuzzy_completion: bool):
11
+ """Runs an interactive REPL for querying Salesforce data with real-time autocompletion."""
12
+
13
+ sobject = None
14
+ fields = None
15
+
16
+ class DynamicSeparatorCompleter(Completer):
17
+ """Custom completer that adapts to different separators."""
18
+
19
+ def __init__(self, words: list[str], separators: list[str] = [","]):
20
+ self.words = words
21
+ self.separators = separators
22
+
23
+ def get_completions(self, document, complete_event):
24
+ text_before_cursor = document.text_before_cursor
25
+
26
+ for separator in self.separators:
27
+ if separator in text_before_cursor:
28
+ last_token = text_before_cursor.split(separator)[-1].strip()
29
+ break
30
+ else:
31
+ last_token = text_before_cursor.strip()
32
+
33
+
34
+ matches_difflib = difflib.get_close_matches(last_token, self.words, n=20, cutoff=0.6)
35
+ matches_starting = [word for word in self.words if word.lower().startswith(last_token.lower())]
36
+
37
+ if not disable_fuzzy_completion and (len(last_token) > 6 or not(matches_starting)):
38
+ matches = matches_difflib
39
+ else:
40
+ matches = matches_starting
41
+
42
+ for word in matches:
43
+ yield Completion(word, start_position=-len(last_token))
44
+
45
+ def _get_objects(sf: SFAuth):
46
+ """Retrieve available Salesforce objects."""
47
+ host = sf.instance_url.split("://")[1].split("/")[0]
48
+ conn = http.client.HTTPSConnection(host)
49
+ uri = f"/services/data/{sf.api_version}/sobjects/"
50
+ headers = {'Authorization': f'Bearer {sf._refresh_token_if_needed()}'}
51
+ conn.request("GET", uri, headers=headers)
52
+ response = conn.getresponse()
53
+
54
+ if response.status != 200:
55
+ print(f'Error: {response.status} {response.reason}')
56
+ return []
57
+
58
+ data = json.loads(response.read())
59
+ return [sobject['name'] for sobject in data['sobjects']]
60
+
61
+ def _get_fields(sobject: str, sf: SFAuth):
62
+ """Retrieve available fields for a given Salesforce object."""
63
+ host = sf.instance_url.split("://")[1].split("/")[0]
64
+ conn = http.client.HTTPSConnection(host)
65
+ uri = f"/services/data/{sf.api_version}/sobjects/{sobject}/describe/"
66
+ headers = {'Authorization': f'Bearer {sf._refresh_token_if_needed()}'}
67
+ conn.request("GET", uri, headers=headers)
68
+ response = conn.getresponse()
69
+
70
+ if response.status != 200:
71
+ print(f'Error: {response.status} {response.reason}')
72
+ raise ValueError(f'Unable to fetch fields for sObject "{sobject}": {response.status}, {response.reason}')
73
+
74
+ data = json.loads(response.read())
75
+ return [f['name'] for f in data['fields']]
76
+
77
+ available_objects = _get_objects(sf)
78
+
79
+ object_completer = DynamicSeparatorCompleter(available_objects)
80
+ while not sobject:
81
+ sobject = prompt('FROM ', completer=object_completer).strip()
82
+
83
+
84
+ available_fields = _get_fields(sobject, sf)
85
+ field_completer = DynamicSeparatorCompleter(available_fields, separators=[","])
86
+ while not fields:
87
+ fields = prompt('SELECT ', completer=field_completer).strip()
88
+
89
+ where_completer = DynamicSeparatorCompleter(available_fields, separators=[" AND ", " OR "])
90
+ where = prompt("WHERE ", completer=where_completer).strip()
91
+ where_clause = f"WHERE {where}" if where else ""
92
+
93
+ limit = prompt("LIMIT ", default="200").strip()
94
+ limit_clause = f"LIMIT {limit}" if limit else ""
95
+
96
+ query = f"SELECT {fields} FROM {sobject} {where_clause} {limit_clause}".replace(' ', ' ')
97
+
98
+ if dry_run:
99
+ print('\nDry-run, skipping execution...')
100
+ print(f'\nQuery: {query}\n')
101
+ return query
102
+
103
+ print('\nExecuting query...\n')
104
+ data = sf.query(query)
105
+ print(json.dumps(data, indent=4))
106
+ print(f'\nQuery: {query}\n')
107
+ return data
108
+
109
+ if __name__ == "__main__":
110
+ import argparse
111
+ import os
112
+
113
+ parser = argparse.ArgumentParser(
114
+ description='Interactively query Salesforce data with real-time autocompletion.'
115
+ )
116
+ parser.add_argument(
117
+ '-a', '--sfdxAuthUrl', type=str, help='Salesforce auth url', default=os.environ.get('SFDX_AUTH_URL')
118
+ )
119
+ parser.add_argument(
120
+ '--dry-run', action='store_true', help='Print the query without executing it', default=str(os.environ.get('SFQ_DRY_RUN')),
121
+ )
122
+ parser.add_argument(
123
+ '--disable-fuzzy-completion', action='store_true', help='Disable fuzzy completion', default=str(os.environ.get('SFQ_DISABLE_FUZZY_COMPLETION')),
124
+ )
125
+ args = parser.parse_args()
126
+
127
+ if not args.sfdxAuthUrl:
128
+ raise ValueError('SFDX_AUTH_URL environment variable is not set nor provided as an argument')
129
+
130
+ try:
131
+ if args.dry_run.lower() not in ['true', '1']:
132
+ args.dry_run = False
133
+ except AttributeError:
134
+ pass
135
+
136
+ try:
137
+ if args.disable_fuzzy_completion.lower() not in ['true', '1']:
138
+ args.disable_fuzzy_completion = False
139
+ except AttributeError:
140
+ pass
141
+
142
+
143
+ _interactive_shell(
144
+ SFAuth(
145
+ instance_url=f"https://{str(args.sfdxAuthUrl).split('@')[1]}",
146
+ client_id=str(args.sfdxAuthUrl).split('//')[1].split('::')[0],
147
+ refresh_token=str(args.sfdxAuthUrl).split('::')[1].split('@')[0],
148
+ ),
149
+ args.dry_run,
150
+ args.disable_fuzzy_completion
151
+ )
sfq-0.0.6/uv.lock ADDED
@@ -0,0 +1,35 @@
1
+ version = 1
2
+ revision = 1
3
+ requires-python = ">=3.7"
4
+
5
+ [[package]]
6
+ name = "prompt-toolkit"
7
+ version = "3.0.3"
8
+ source = { registry = "https://pypi.org/simple" }
9
+ dependencies = [
10
+ { name = "wcwidth" },
11
+ ]
12
+ sdist = { url = "https://files.pythonhosted.org/packages/8f/bc/58ba47a2a864d8e3d968d03b577c85fbdf52c8d324a030df71ac9c06c1b5/prompt_toolkit-3.0.3.tar.gz", hash = "sha256:a402e9bf468b63314e37460b68ba68243d55b2f8c4d0192f85a019af3945050e", size = 2997855 }
13
+ wheels = [
14
+ { url = "https://files.pythonhosted.org/packages/f5/22/f00412fafc68169054cc623a35c32773f22b403ddbe516c8adfdecf25341/prompt_toolkit-3.0.3-py3-none-any.whl", hash = "sha256:c93e53af97f630f12f5f62a3274e79527936ed466f038953dfa379d4941f651a", size = 348445 },
15
+ ]
16
+
17
+ [[package]]
18
+ name = "sfq"
19
+ version = "0.0.6"
20
+ source = { editable = "." }
21
+ dependencies = [
22
+ { name = "prompt-toolkit" },
23
+ ]
24
+
25
+ [package.metadata]
26
+ requires-dist = [{ name = "prompt-toolkit", specifier = ">=3.0.3" }]
27
+
28
+ [[package]]
29
+ name = "wcwidth"
30
+ version = "0.2.13"
31
+ source = { registry = "https://pypi.org/simple" }
32
+ sdist = { url = "https://files.pythonhosted.org/packages/6c/63/53559446a878410fc5a5974feb13d31d78d752eb18aeba59c7fef1af7598/wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5", size = 101301 }
33
+ wheels = [
34
+ { url = "https://files.pythonhosted.org/packages/fd/84/fd2ba7aafacbad3c4201d395674fc6348826569da3c0937e75505ead3528/wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859", size = 34166 },
35
+ ]
sfq-0.0.4/.python-version DELETED
@@ -1 +0,0 @@
1
- 3.9
sfq-0.0.4/uv.lock DELETED
@@ -1,7 +0,0 @@
1
- version = 1
2
- requires-python = ">=3.9"
3
-
4
- [[package]]
5
- name = "sfq"
6
- version = "0.0.4"
7
- source = { editable = "." }
File without changes
File without changes
File without changes