sfq 0.0.4__py3-none-any.whl → 0.0.5__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 CHANGED
@@ -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."""
sfq/__main__.py ADDED
@@ -0,0 +1,153 @@
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
+ token = sf._refresh_token_if_needed()
51
+ headers = {'Authorization': f'Bearer {token}'}
52
+ print(f'token: {token}')
53
+ conn.request("GET", uri, headers=headers)
54
+ response = conn.getresponse()
55
+
56
+ if response.status != 200:
57
+ print(f'Error: {response.status} {response.reason}')
58
+ return []
59
+
60
+ data = json.loads(response.read())
61
+ return [sobject['name'] for sobject in data['sobjects']]
62
+
63
+ def _get_fields(sobject: str, sf: SFAuth):
64
+ """Retrieve available fields for a given Salesforce object."""
65
+ host = sf.instance_url.split("://")[1].split("/")[0]
66
+ conn = http.client.HTTPSConnection(host)
67
+ uri = f"/services/data/{sf.api_version}/sobjects/{sobject}/describe/"
68
+ headers = {'Authorization': f'Bearer {sf._refresh_token_if_needed()}'}
69
+ conn.request("GET", uri, headers=headers)
70
+ response = conn.getresponse()
71
+
72
+ if response.status != 200:
73
+ print(f'Error: {response.status} {response.reason}')
74
+ raise ValueError(f'Unable to fetch fields for sObject "{sobject}": {response.status}, {response.reason}')
75
+
76
+ data = json.loads(response.read())
77
+ return [f['name'] for f in data['fields']]
78
+
79
+ available_objects = _get_objects(sf)
80
+
81
+ object_completer = DynamicSeparatorCompleter(available_objects)
82
+ while not sobject:
83
+ sobject = prompt('FROM ', completer=object_completer).strip()
84
+
85
+
86
+ available_fields = _get_fields(sobject, sf)
87
+ field_completer = DynamicSeparatorCompleter(available_fields, separators=[","])
88
+ while not fields:
89
+ fields = prompt('SELECT ', completer=field_completer).strip()
90
+
91
+ where_completer = DynamicSeparatorCompleter(available_fields, separators=[" AND ", " OR "])
92
+ where = prompt("WHERE ", completer=where_completer).strip()
93
+ where_clause = f"WHERE {where}" if where else ""
94
+
95
+ limit = prompt("LIMIT ", default="200").strip()
96
+ limit_clause = f"LIMIT {limit}" if limit else ""
97
+
98
+ query = f"SELECT {fields} FROM {sobject} {where_clause} {limit_clause}".replace(' ', ' ')
99
+
100
+ if dry_run:
101
+ print('\nDry-run, skipping execution...')
102
+ print(f'\nQuery: {query}\n')
103
+ return query
104
+
105
+ print('\nExecuting query...\n')
106
+ data = sf.query(query)
107
+ print(json.dumps(data, indent=4))
108
+ print(f'\nQuery: {query}\n')
109
+ return data
110
+
111
+ if __name__ == "__main__":
112
+ import argparse
113
+ import os
114
+
115
+ parser = argparse.ArgumentParser(
116
+ description='Interactively query Salesforce data with real-time autocompletion.'
117
+ )
118
+ parser.add_argument(
119
+ '-a', '--sfdxAuthUrl', type=str, help='Salesforce auth url', default=os.environ.get('SFDX_AUTH_URL')
120
+ )
121
+ parser.add_argument(
122
+ '--dry-run', action='store_true', help='Print the query without executing it', default=str(os.environ.get('SFQ_DRY_RUN')),
123
+ )
124
+ parser.add_argument(
125
+ '--disable-fuzzy-completion', action='store_true', help='Disable fuzzy completion', default=str(os.environ.get('SFQ_DISABLE_FUZZY_COMPLETION')),
126
+ )
127
+ args = parser.parse_args()
128
+
129
+ if not args.sfdxAuthUrl:
130
+ raise ValueError('SFDX_AUTH_URL environment variable is not set nor provided as an argument')
131
+
132
+ try:
133
+ if args.dry_run.lower() not in ['true', '1']:
134
+ args.dry_run = False
135
+ except AttributeError:
136
+ pass
137
+
138
+ try:
139
+ if args.disable_fuzzy_completion.lower() not in ['true', '1']:
140
+ args.disable_fuzzy_completion = False
141
+ except AttributeError:
142
+ pass
143
+
144
+
145
+ _interactive_shell(
146
+ SFAuth(
147
+ instance_url=f"https://{str(args.sfdxAuthUrl).split('@')[1]}",
148
+ client_id=str(args.sfdxAuthUrl).split('//')[1].split('::')[0],
149
+ refresh_token=str(args.sfdxAuthUrl).split('::')[1].split('@')[0],
150
+ ),
151
+ args.dry_run,
152
+ args.disable_fuzzy_completion
153
+ )
@@ -1,17 +1,20 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.1
2
2
  Name: sfq
3
- Version: 0.0.4
3
+ Version: 0.0.5
4
4
  Summary: Python wrapper for the Salesforce's Query API.
5
5
  Author-email: David Moruzzi <sfq.pypi@dmoruzi.com>
6
6
  Keywords: salesforce,salesforce query
7
7
  Classifier: Development Status :: 3 - Alpha
8
8
  Classifier: Intended Audience :: Developers
9
+ Classifier: Programming Language :: Python :: 3.7
10
+ Classifier: Programming Language :: Python :: 3.8
9
11
  Classifier: Programming Language :: Python :: 3.9
10
12
  Classifier: Programming Language :: Python :: 3.10
11
13
  Classifier: Programming Language :: Python :: 3.12
12
14
  Classifier: Programming Language :: Python :: 3.13
13
15
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
14
- Requires-Python: >=3.9
16
+ Requires-Python: >=3.7
17
+ Requires-Dist: prompt-toolkit>=3.0.3
15
18
  Description-Content-Type: text/markdown
16
19
 
17
20
  # sfq (Salesforce Query)
@@ -36,7 +39,25 @@ pip install sfq
36
39
 
37
40
  ## Usage
38
41
 
39
- ### 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
40
61
 
41
62
  ```python
42
63
  from sfq import SFAuth
@@ -55,7 +76,7 @@ print(sf.query("SELECT Id FROM Account LIMIT 5"))
55
76
  print(sf.query("SELECT Id, FullName, Metadata FROM SandboxSettings LIMIT 5", tooling=True))
56
77
  ```
57
78
 
58
- ### Querying from Bash
79
+ ### Bash Querying
59
80
 
60
81
  You can easily incorporate this into ad-hoc bash scripts or commands:
61
82
 
@@ -128,4 +149,4 @@ To use the `sfq` library, you'll need a **client ID** and **refresh token**. The
128
149
  ## Notes
129
150
 
130
151
  - **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.
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.
@@ -0,0 +1,6 @@
1
+ sfq/__init__.py,sha256=sIZr8eUzWV9q2PEenbCnu4mRBmv07FC7zjfpcjux_1g,6466
2
+ sfq/__main__.py,sha256=CqwwG2l6ZphmiOSeqQeB7W73hTlGKJRDYtlKLkM7Bps,5779
3
+ sfq/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ sfq-0.0.5.dist-info/METADATA,sha256=1f9a_TSpoLzcyMbqRrxwVyCqE5vj_WCivEg6uxNiNxQ,5491
5
+ sfq-0.0.5.dist-info/WHEEL,sha256=KGYbc1zXlYddvwxnNty23BeaKzh7YuoSIvIMO4jEhvw,87
6
+ sfq-0.0.5.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.17.1
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,5 +0,0 @@
1
- sfq/__init__.py,sha256=4MnDoSE32i6H_VF_WFyEN171kcyhfbI7GI4B_QAZMG4,6467
2
- sfq/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- sfq-0.0.4.dist-info/METADATA,sha256=kwZf2zmeERpSoj_GOmDtxKwxFF6N52txZVkEnYWH6NY,4685
4
- sfq-0.0.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
5
- sfq-0.0.4.dist-info/RECORD,,