sfq 0.0.3__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
@@ -5,7 +5,7 @@ import os
5
5
  import json
6
6
  from urllib.parse import urlparse, quote
7
7
 
8
- logging.basicConfig(level=logging.INFO)
8
+ logging.basicConfig(level=logging.INFO, format='[sfq:%(lineno)d - %(levelname)s] %(message)s')
9
9
 
10
10
  class SFAuth:
11
11
  def __init__(
@@ -108,7 +108,7 @@ class SFAuth:
108
108
  """Automatically refresh the token if it has expired or is missing."""
109
109
  _token_expiration = self._is_token_expired()
110
110
  if self.access_token and not _token_expiration:
111
- return
111
+ return self.access_token
112
112
 
113
113
  if not self.access_token:
114
114
  logging.debug("No access token available. Requesting a new one.")
@@ -123,6 +123,7 @@ class SFAuth:
123
123
  logging.debug("Access token refreshed successfully.")
124
124
  else:
125
125
  logging.error("Failed to refresh access token.")
126
+ return self.access_token
126
127
 
127
128
 
128
129
  def _is_token_expired(self):
@@ -130,7 +131,7 @@ class SFAuth:
130
131
  try:
131
132
  return time.time() >= float(self.token_expiration_time)
132
133
  except (TypeError, ValueError):
133
- return False
134
+ return True
134
135
 
135
136
  def query(self, query, tooling=False):
136
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.3
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=vcAXd4v6uyjzAqOril31LG2WG2n3l6OHxPiYQA63uD4,6361
2
- sfq/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- sfq-0.0.3.dist-info/METADATA,sha256=y5Ir5XtomFESBbaktjuWzsA2d0Q40qlMIBXx1igISU4,4685
4
- sfq-0.0.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
5
- sfq-0.0.3.dist-info/RECORD,,