sfq 0.0.18__tar.gz → 0.0.20__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sfq
3
- Version: 0.0.18
3
+ Version: 0.0.20
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sfq"
3
- version = "0.0.18"
3
+ version = "0.0.20"
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" }]
@@ -84,7 +84,7 @@ class SFAuth:
84
84
  access_token: Optional[str] = None,
85
85
  token_expiration_time: Optional[float] = None,
86
86
  token_lifetime: int = 15 * 60,
87
- user_agent: str = "sfq/0.0.18",
87
+ user_agent: str = "sfq/0.0.20",
88
88
  sforce_client: str = "_auto",
89
89
  proxy: str = "_auto",
90
90
  ) -> None:
@@ -100,7 +100,7 @@ class SFAuth:
100
100
  :param access_token: The access token for the current session (default is None).
101
101
  :param token_expiration_time: The expiration time of the access token (default is None).
102
102
  :param token_lifetime: The lifetime of the access token in seconds (default is 15 minutes).
103
- :param user_agent: Custom User-Agent string (default is "sfq/0.0.18").
103
+ :param user_agent: Custom User-Agent string (default is "sfq/0.0.20").
104
104
  :param sforce_client: Custom Application Identifier (default is user_agent).
105
105
  :param proxy: The proxy configuration, "_auto" to use environment (default is "_auto").
106
106
  """
@@ -510,6 +510,40 @@ class SFAuth:
510
510
  logger.error("Failed to fetch limits: %s", status)
511
511
  return None
512
512
 
513
+ def _paginate_query_result(self, initial_result: dict, headers: dict) -> dict:
514
+ """
515
+ Helper to paginate Salesforce query results (for both query and cquery).
516
+ Returns a dict with all records combined.
517
+ """
518
+ records = list(initial_result.get("records", []))
519
+ done = initial_result.get("done", True)
520
+ next_url = initial_result.get("nextRecordsUrl")
521
+ total_size = initial_result.get("totalSize", len(records))
522
+
523
+ while not done and next_url:
524
+ status_code, data = self._send_request(
525
+ method="GET",
526
+ endpoint=next_url,
527
+ headers=headers,
528
+ )
529
+ if status_code == 200:
530
+ next_result = json.loads(data)
531
+ records.extend(next_result.get("records", []))
532
+ done = next_result.get("done", True)
533
+ next_url = next_result.get("nextRecordsUrl")
534
+ total_size = next_result.get("totalSize", total_size)
535
+ else:
536
+ logger.error("Failed to fetch next records: %s", data)
537
+ break
538
+
539
+ paginated = dict(initial_result)
540
+ paginated["records"] = records
541
+ paginated["done"] = done
542
+ paginated["totalSize"] = total_size
543
+ if "nextRecordsUrl" in paginated:
544
+ del paginated["nextRecordsUrl"]
545
+ return paginated
546
+
513
547
  def query(self, query: str, tooling: bool = False) -> Optional[Dict[str, Any]]:
514
548
  """
515
549
  Execute a SOQL query using the REST or Tooling API.
@@ -524,53 +558,29 @@ class SFAuth:
524
558
  endpoint += query_string
525
559
  headers = self._get_common_headers()
526
560
 
527
- paginated_results = {"totalSize": 0, "done": False, "records": []}
528
-
529
561
  try:
530
- while True:
531
- logger.trace("Request endpoint: %s", endpoint)
532
- logger.trace("Request headers: %s", headers)
533
- headers = self._get_common_headers() # handle refresh token
534
-
535
- status_code, data = self._send_request(
536
- method="GET",
537
- endpoint=endpoint,
538
- headers=headers,
562
+ status_code, data = self._send_request(
563
+ method="GET",
564
+ endpoint=endpoint,
565
+ headers=headers,
566
+ )
567
+ if status_code == 200:
568
+ result = json.loads(data)
569
+ paginated = self._paginate_query_result(result, headers)
570
+ logger.debug(
571
+ "Query successful, returned %s records: %r",
572
+ paginated.get("totalSize"),
573
+ query,
539
574
  )
540
-
541
- if status_code == 200:
542
- current_results = json.loads(data)
543
- paginated_results["records"].extend(current_results["records"])
544
- query_done = current_results.get("done")
545
- if query_done:
546
- total_size = current_results.get("totalSize")
547
- paginated_results = {
548
- "totalSize": total_size,
549
- "done": query_done,
550
- "records": paginated_results["records"],
551
- }
552
- logger.debug(
553
- "Query successful, returned %s records: %r",
554
- total_size,
555
- query,
556
- )
557
- logger.trace("Query full response: %s", data)
558
- break
559
- endpoint = current_results.get("nextRecordsUrl")
560
- logger.debug(
561
- "Query batch successful, getting next batch: %s", endpoint
562
- )
563
- else:
564
- logger.debug("Query failed: %r", query)
565
- logger.error(
566
- "Query failed with HTTP status %s",
567
- status_code,
568
- )
569
- logger.debug("Query response: %s", data)
570
- break
571
-
572
- return paginated_results
573
-
575
+ logger.trace("Query full response: %s", paginated)
576
+ return paginated
577
+ else:
578
+ logger.debug("Query failed: %r", query)
579
+ logger.error(
580
+ "Query failed with HTTP status %s",
581
+ status_code,
582
+ )
583
+ logger.debug("Query response: %s", data)
574
584
  except Exception as err:
575
585
  logger.exception("Exception during query: %s", err)
576
586
 
@@ -664,7 +674,7 @@ class SFAuth:
664
674
  logger.warning("No queries to execute.")
665
675
  return None
666
676
 
667
- def _execute_batch(queries_batch):
677
+ def _execute_batch(batch_keys, batch_queries):
668
678
  endpoint = f"/services/data/{self.api_version}/composite/batch"
669
679
  headers = self._get_common_headers()
670
680
 
@@ -675,7 +685,7 @@ class SFAuth:
675
685
  "method": "GET",
676
686
  "url": f"/services/data/{self.api_version}/query?q={quote(query)}",
677
687
  }
678
- for query in queries_batch
688
+ for query in batch_queries
679
689
  ],
680
690
  }
681
691
 
@@ -692,51 +702,21 @@ class SFAuth:
692
702
  logger.trace("Composite query full response: %s", data)
693
703
  results = json.loads(data).get("results", [])
694
704
  for i, result in enumerate(results):
695
- records = []
696
- if "result" in result and "records" in result["result"]:
697
- records.extend(result["result"]["records"])
698
- # Handle pagination
699
- while not result["result"].get("done", True):
700
- headers = self._get_common_headers() # handles token refresh
701
- next_url = result["result"].get("nextRecordsUrl")
702
- if next_url:
703
- status_code, next_data = self._send_request(
704
- method="GET",
705
- endpoint=next_url,
706
- headers=headers,
707
- )
708
- if status_code == 200:
709
- next_results = json.loads(next_data)
710
- records.extend(next_results.get("records", []))
711
- result["result"]["done"] = next_results.get("done")
712
- else:
713
- logger.error(
714
- "Failed to fetch next records: %s",
715
- next_data,
716
- )
717
- break
718
- else:
719
- result["result"]["done"] = True
720
- paginated_results = result["result"]
721
- paginated_results["records"] = records
722
- if "nextRecordsUrl" in paginated_results:
723
- del paginated_results["nextRecordsUrl"]
724
- batch_results[keys[i]] = paginated_results
725
- if result.get("statusCode") != 200:
726
- logger.error("Query failed for key %s: %s", keys[i], result)
727
- logger.error(
728
- "Query failed with HTTP status %s (%s)",
729
- result.get("statusCode"),
730
- result.get("statusMessage"),
731
- )
732
- logger.trace("Query response: %s", result)
705
+ key = batch_keys[i]
706
+ if result.get("statusCode") == 200 and "result" in result:
707
+ paginated = self._paginate_query_result(result["result"], headers)
708
+ batch_results[key] = paginated
709
+ else:
710
+ logger.error("Query failed for key %s: %s", key, result)
711
+ batch_results[key] = result
733
712
  else:
734
713
  logger.error(
735
714
  "Composite query failed with HTTP status %s (%s)",
736
715
  status_code,
737
716
  data,
738
717
  )
739
- batch_results[keys[i]] = data
718
+ for i, key in enumerate(batch_keys):
719
+ batch_results[key] = data
740
720
  logger.trace("Composite query response: %s", data)
741
721
 
742
722
  return batch_results
@@ -746,11 +726,11 @@ class SFAuth:
746
726
 
747
727
  with ThreadPoolExecutor(max_workers=max_workers) as executor:
748
728
  futures = []
749
- BATCH_SIZE = 25
729
+ BATCH_SIZE = batch_size
750
730
  for i in range(0, len(keys), BATCH_SIZE):
751
731
  batch_keys = keys[i : i + BATCH_SIZE]
752
732
  batch_queries = [query_dict[key] for key in batch_keys]
753
- futures.append(executor.submit(_execute_batch, batch_queries))
733
+ futures.append(executor.submit(_execute_batch, batch_keys, batch_queries))
754
734
 
755
735
  for future in as_completed(futures):
756
736
  results_dict.update(future.result())
@@ -826,7 +806,8 @@ class SFAuth:
826
806
  if not sobject and not sobject_prefixes:
827
807
  sobject_prefixes = self.get_sobject_prefixes()
828
808
 
829
- sobject = str(sobject) or str(sobject_prefixes.get(str(key[:3]), None))
809
+ if not sobject:
810
+ sobject = str(sobject_prefixes.get(str(key[:3]), None))
830
811
 
831
812
  compositeRequest_payload.append(
832
813
  {
@@ -3,5 +3,5 @@ requires-python = ">=3.9"
3
3
 
4
4
  [[package]]
5
5
  name = "sfq"
6
- version = "0.0.18"
6
+ version = "0.0.20"
7
7
  source = { editable = "." }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes