aio-sf 0.1.0b9__py3-none-any.whl → 0.1.0b11__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.
@@ -1,7 +1,7 @@
1
1
  """Salesforce Collections API module."""
2
2
 
3
3
  from .client import CollectionsAPI
4
- from .batch import ProgressInfo, ProgressCallback
4
+ from .batch import ResultInfo, ResultCallback
5
5
  from .retry import ShouldRetryCallback, default_should_retry
6
6
  from .types import (
7
7
  CollectionError,
@@ -14,8 +14,8 @@ from .types import (
14
14
 
15
15
  __all__ = [
16
16
  "CollectionsAPI",
17
- "ProgressInfo",
18
- "ProgressCallback",
17
+ "ResultInfo",
18
+ "ResultCallback",
19
19
  "ShouldRetryCallback",
20
20
  "default_should_retry",
21
21
  "CollectionError",
@@ -17,21 +17,25 @@ from .types import CollectionResult
17
17
  logger = logging.getLogger(__name__)
18
18
 
19
19
 
20
- class ProgressInfo(TypedDict):
21
- """Progress information for batch operations."""
20
+ class ResultInfo(TypedDict):
21
+ """Result information provided after each batch completes."""
22
22
 
23
+ successes: List[CollectionResult] # Successful results from this batch
24
+ errors: List[
25
+ CollectionResult
26
+ ] # Failed results from this batch (API and HTTP errors)
23
27
  total_records: int # Total records being processed
24
28
  records_completed: int # Records finished (succeeded or failed permanently)
25
- records_succeeded: int # Records that succeeded
26
- records_failed: int # Records that failed permanently (exhausted retries)
29
+ records_succeeded: int # Records that succeeded so far
30
+ records_failed: int # Records that failed permanently so far
27
31
  records_pending: int # Records still being retried
28
32
  current_attempt: int # Current retry attempt number (1-indexed)
29
33
  current_batch_size: int # Batch size for current attempt
30
34
  current_concurrency: int # Concurrency level for current attempt
31
35
 
32
36
 
33
- # Type alias for progress callback
34
- ProgressCallback = Callable[[ProgressInfo], Awaitable[None]]
37
+ # Type alias for result callback
38
+ ResultCallback = Callable[[ResultInfo], Awaitable[None]]
35
39
 
36
40
 
37
41
  def split_into_batches(
@@ -65,8 +69,9 @@ async def process_batches_concurrently(
65
69
  operation_func,
66
70
  max_concurrent_batches: int,
67
71
  total_records: int,
68
- on_batch_complete: Optional[ProgressCallback] = None,
72
+ on_result: Optional[ResultCallback] = None,
69
73
  progress_state: Optional[Dict[str, int]] = None,
74
+ final_results: Optional[List] = None,
70
75
  *args,
71
76
  **kwargs,
72
77
  ) -> List[Any]:
@@ -80,8 +85,8 @@ async def process_batches_concurrently(
80
85
  :param operation_func: Function to call for each batch
81
86
  :param max_concurrent_batches: Maximum number of concurrent batch operations
82
87
  :param total_records: Total number of records being processed
83
- :param on_batch_complete: Optional callback invoked after each batch completes
84
- :param progress_state: Dict with progress state (updated by caller)
88
+ :param on_result: Optional callback invoked after each batch completes with results
89
+ :param progress_state: Dict with progress state (to include in callback)
85
90
  :param args: Additional positional arguments for operation_func
86
91
  :param kwargs: Additional keyword arguments for operation_func
87
92
  :returns: List of results from all batches in the same order as input
@@ -91,7 +96,7 @@ async def process_batches_concurrently(
91
96
  raise ValueError("max_concurrent_batches must be greater than 0")
92
97
 
93
98
  semaphore = asyncio.Semaphore(max_concurrent_batches)
94
- callback_lock = asyncio.Lock() if on_batch_complete else None
99
+ callback_lock = asyncio.Lock() if on_result else None
95
100
 
96
101
  async def process_batch_with_semaphore(batch_index: int, batch):
97
102
  async with semaphore:
@@ -104,10 +109,37 @@ async def process_batches_concurrently(
104
109
  )
105
110
  result = [e for _ in range(len(batch))]
106
111
 
107
- # Invoke progress callback if provided
108
- if on_batch_complete and callback_lock and progress_state:
112
+ # Invoke callback if provided, with results and progress state
113
+ if on_result and callback_lock and progress_state:
109
114
  async with callback_lock:
110
- progress_info: ProgressInfo = {
115
+ # Split results into successes and errors
116
+ successes: List[CollectionResult] = []
117
+ errors: List[CollectionResult] = []
118
+
119
+ for item in result:
120
+ # Convert exceptions to CollectionResult format
121
+ if isinstance(item, Exception):
122
+ error_result = convert_exception_to_result(item)
123
+ errors.append(error_result)
124
+ elif item.get("success", False):
125
+ successes.append(item)
126
+ else:
127
+ errors.append(item)
128
+
129
+ # Update progress_state cumulatively for per-batch reporting
130
+ # We only count successes as completed here (errors remain pending until final)
131
+ progress_state["records_succeeded"] += len(successes)
132
+ progress_state["records_completed"] += len(successes)
133
+ progress_state["records_pending"] = progress_state[
134
+ "total_records"
135
+ ] - (
136
+ progress_state["records_succeeded"]
137
+ + progress_state["records_failed"]
138
+ )
139
+
140
+ result_info: ResultInfo = {
141
+ "successes": successes,
142
+ "errors": errors,
111
143
  "total_records": progress_state["total_records"],
112
144
  "records_completed": progress_state["records_completed"],
113
145
  "records_succeeded": progress_state["records_succeeded"],
@@ -117,7 +149,7 @@ async def process_batches_concurrently(
117
149
  "current_batch_size": progress_state["current_batch_size"],
118
150
  "current_concurrency": progress_state["current_concurrency"],
119
151
  }
120
- await on_batch_complete(progress_info)
152
+ await on_result(result_info)
121
153
 
122
154
  return result
123
155
 
@@ -141,7 +173,7 @@ async def process_with_retries(
141
173
  max_attempts: int,
142
174
  should_retry_callback: Optional[ShouldRetryCallback],
143
175
  max_concurrent_batches: Union[int, List[int]],
144
- on_batch_complete: Optional[ProgressCallback],
176
+ on_result: Optional[ResultCallback],
145
177
  max_limit: int,
146
178
  *args,
147
179
  **kwargs,
@@ -155,7 +187,7 @@ async def process_with_retries(
155
187
  :param max_attempts: Maximum number of attempts per record
156
188
  :param should_retry_callback: Optional callback to determine if record should be retried
157
189
  :param max_concurrent_batches: Maximum concurrent batches (int or list of ints per attempt)
158
- :param on_batch_complete: Progress callback
190
+ :param on_result: Callback invoked after each batch completes with results and progress
159
191
  :param max_limit: Maximum batch size limit for the operation
160
192
  :param args: Additional args for operation_func
161
193
  :param kwargs: Additional kwargs for operation_func
@@ -203,14 +235,15 @@ async def process_with_retries(
203
235
  records_to_process = [r.record for r in current_records]
204
236
  batches = split_into_batches(records_to_process, current_batch_size, max_limit)
205
237
 
206
- # Process batches with current concurrency level (no callback here)
238
+ # Process batches - callback will be invoked for each batch with results
207
239
  batch_results = await process_batches_concurrently(
208
240
  batches,
209
241
  operation_func,
210
242
  current_concurrency,
211
243
  len(records_to_process),
212
- None, # Don't invoke callback during batch processing
213
- None,
244
+ on_result,
245
+ progress_state,
246
+ final_results,
214
247
  *args,
215
248
  **kwargs,
216
249
  )
@@ -224,11 +257,7 @@ async def process_with_retries(
224
257
  final_results,
225
258
  )
226
259
 
227
- # Update progress state based on results
228
- # Count completed records (those not being retried)
229
- records_completed_this_round = len(current_records) - len(records_to_retry)
230
-
231
- # Count successes and failures in final_results so far
260
+ # Update progress state based on results for next iteration
232
261
  records_succeeded = sum(
233
262
  1 for r in final_results if r is not None and r.get("success", False)
234
263
  )
@@ -241,20 +270,6 @@ async def process_with_retries(
241
270
  progress_state["records_failed"] = records_failed
242
271
  progress_state["records_pending"] = len(records_to_retry)
243
272
 
244
- # Invoke progress callback after we know the results
245
- if on_batch_complete:
246
- progress_info: ProgressInfo = {
247
- "total_records": progress_state["total_records"],
248
- "records_completed": progress_state["records_completed"],
249
- "records_succeeded": progress_state["records_succeeded"],
250
- "records_failed": progress_state["records_failed"],
251
- "records_pending": progress_state["records_pending"],
252
- "current_attempt": progress_state["current_attempt"],
253
- "current_batch_size": progress_state["current_batch_size"],
254
- "current_concurrency": progress_state["current_concurrency"],
255
- }
256
- await on_batch_complete(progress_info)
257
-
258
273
  if records_to_retry:
259
274
  logger.info(
260
275
  f"Retrying {len(records_to_retry)} failed records "
@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING
8
8
  if TYPE_CHECKING:
9
9
  from ..client import SalesforceClient
10
10
 
11
- from .batch import ProgressCallback, process_with_retries
11
+ from .batch import ResultCallback, process_with_retries
12
12
  from .records import (
13
13
  detect_record_type_and_sobject,
14
14
  prepare_records,
@@ -92,7 +92,7 @@ class CollectionsAPI:
92
92
  batch_size: Union[int, List[int]] = 200,
93
93
  max_concurrent_batches: Union[int, List[int]] = 5,
94
94
  api_version: Optional[str] = None,
95
- on_batch_complete: Optional[ProgressCallback] = None,
95
+ on_result: Optional[ResultCallback] = None,
96
96
  max_attempts: int = 1,
97
97
  should_retry: Optional[ShouldRetryCallback] = None,
98
98
  ) -> CollectionInsertResponse:
@@ -109,7 +109,7 @@ class CollectionsAPI:
109
109
  :param batch_size: Batch size (int for same size, or list of ints per attempt). Max 200.
110
110
  :param max_concurrent_batches: Maximum number of concurrent batch operations
111
111
  :param api_version: API version to use
112
- :param on_batch_complete: Optional async callback invoked after each batch completes
112
+ :param on_result: Optional async callback invoked after each batch completes with results
113
113
  :param max_attempts: Maximum number of attempts per record (default: 1, no retries)
114
114
  :param should_retry: Optional callback to determine if a failed record should be retried
115
115
  :returns: List of results for each record, in same order as input
@@ -139,7 +139,7 @@ class CollectionsAPI:
139
139
  max_attempts,
140
140
  should_retry,
141
141
  max_concurrent_batches,
142
- on_batch_complete,
142
+ on_result,
143
143
  self.MAX_RECORDS_INSERT,
144
144
  actual_sobject_type,
145
145
  all_or_none,
@@ -200,7 +200,7 @@ class CollectionsAPI:
200
200
  batch_size: Union[int, List[int]] = 200,
201
201
  max_concurrent_batches: Union[int, List[int]] = 5,
202
202
  api_version: Optional[str] = None,
203
- on_batch_complete: Optional[ProgressCallback] = None,
203
+ on_result: Optional[ResultCallback] = None,
204
204
  max_attempts: int = 1,
205
205
  should_retry: Optional[ShouldRetryCallback] = None,
206
206
  ) -> CollectionUpdateResponse:
@@ -217,7 +217,7 @@ class CollectionsAPI:
217
217
  :param batch_size: Batch size (int for same size, or list of ints per attempt). Max 200.
218
218
  :param max_concurrent_batches: Maximum number of concurrent batch operations
219
219
  :param api_version: API version to use
220
- :param on_batch_complete: Optional async callback invoked after each batch completes
220
+ :param on_result: Optional async callback invoked after each batch completes with results
221
221
  :param max_attempts: Maximum number of attempts per record (default: 1, no retries)
222
222
  :param should_retry: Optional callback to determine if a failed record should be retried
223
223
  :returns: List of results for each record, in same order as input
@@ -247,7 +247,7 @@ class CollectionsAPI:
247
247
  max_attempts,
248
248
  should_retry,
249
249
  max_concurrent_batches,
250
- on_batch_complete,
250
+ on_result,
251
251
  self.MAX_RECORDS_UPDATE,
252
252
  actual_sobject_type,
253
253
  all_or_none,
@@ -313,7 +313,7 @@ class CollectionsAPI:
313
313
  batch_size: Union[int, List[int]] = 200,
314
314
  max_concurrent_batches: Union[int, List[int]] = 5,
315
315
  api_version: Optional[str] = None,
316
- on_batch_complete: Optional[ProgressCallback] = None,
316
+ on_result: Optional[ResultCallback] = None,
317
317
  max_attempts: int = 1,
318
318
  should_retry: Optional[ShouldRetryCallback] = None,
319
319
  ) -> CollectionUpsertResponse:
@@ -331,7 +331,7 @@ class CollectionsAPI:
331
331
  :param batch_size: Batch size (int for same size, or list of ints per attempt). Max 200.
332
332
  :param max_concurrent_batches: Maximum number of concurrent batch operations
333
333
  :param api_version: API version to use
334
- :param on_batch_complete: Optional async callback invoked after each batch completes
334
+ :param on_result: Optional async callback invoked after each batch completes with results
335
335
  :param max_attempts: Maximum number of attempts per record (default: 1, no retries)
336
336
  :param should_retry: Optional callback to determine if a failed record should be retried
337
337
  :returns: List of results for each record, in same order as input
@@ -361,7 +361,7 @@ class CollectionsAPI:
361
361
  max_attempts,
362
362
  should_retry,
363
363
  max_concurrent_batches,
364
- on_batch_complete,
364
+ on_result,
365
365
  self.MAX_RECORDS_UPSERT,
366
366
  external_id_field,
367
367
  actual_sobject_type,
@@ -411,7 +411,7 @@ class CollectionsAPI:
411
411
  batch_size: Union[int, List[int]] = 200,
412
412
  max_concurrent_batches: Union[int, List[int]] = 5,
413
413
  api_version: Optional[str] = None,
414
- on_batch_complete: Optional[ProgressCallback] = None,
414
+ on_result: Optional[ResultCallback] = None,
415
415
  max_attempts: int = 1,
416
416
  should_retry: Optional[ShouldRetryCallback] = None,
417
417
  ) -> CollectionDeleteResponse:
@@ -427,7 +427,7 @@ class CollectionsAPI:
427
427
  :param batch_size: Batch size (int for same size, or list of ints per attempt). Max 200.
428
428
  :param max_concurrent_batches: Maximum number of concurrent batch operations
429
429
  :param api_version: API version to use
430
- :param on_batch_complete: Optional async callback invoked after each batch completes
430
+ :param on_result: Optional async callback invoked after each batch completes with results
431
431
  :param max_attempts: Maximum number of attempts per record (default: 1, no retries)
432
432
  :param should_retry: Optional callback to determine if a failed record should be retried
433
433
  :returns: List of results for each record, in same order as input
@@ -453,7 +453,7 @@ class CollectionsAPI:
453
453
  max_attempts,
454
454
  should_retry,
455
455
  max_concurrent_batches,
456
- on_batch_complete,
456
+ on_result,
457
457
  self.MAX_RECORDS_DELETE,
458
458
  all_or_none,
459
459
  api_version,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aio-sf
3
- Version: 0.1.0b9
3
+ Version: 0.1.0b11
4
4
  Summary: Async Salesforce library for Python
5
5
  Project-URL: Homepage, https://github.com/callawaycloud/aio-salesforce
6
6
  Project-URL: Repository, https://github.com/callawaycloud/aio-salesforce
@@ -166,15 +166,19 @@ async with SalesforceClient(auth_strategy=auth) as sf:
166
166
 
167
167
  **Advanced - With Retries, Concurrency Scaling, and Progress:**
168
168
  ```python
169
- from aio_sf.api.collections import ProgressInfo
169
+ from aio_sf.api.collections import ResultInfo
170
170
 
171
- async def on_progress(info: ProgressInfo):
171
+ async def on_result(info: ResultInfo):
172
+ # Called after each batch completes with successes and errors split
172
173
  print(
173
- f"Attempt {info['current_attempt']}: "
174
- f"{info['records_succeeded']} succeeded, "
175
- f"{info['records_failed']} failed, "
174
+ f"Batch: {len(info['successes'])} succeeded, {len(info['errors'])} failed | "
175
+ f"Attempt {info['current_attempt']}, "
176
+ f"Overall: {info['records_succeeded']} OK, {info['records_failed']} failed, "
176
177
  f"{info['records_pending']} pending"
177
178
  )
179
+ # Inspect errors (includes both API errors and HTTP failures)
180
+ for error in info['errors']:
181
+ print(f" Error: {error['errors']}")
178
182
 
179
183
  async with SalesforceClient(auth_strategy=auth) as sf:
180
184
  results = await sf.collections.insert(
@@ -183,7 +187,7 @@ async with SalesforceClient(auth_strategy=auth) as sf:
183
187
  batch_size=[200, 100, 25], # Shrink batch size on retry
184
188
  max_concurrent_batches=[5, 3, 1], # Reduce concurrency on retry
185
189
  max_attempts=5, # Retry up to 5 times
186
- on_batch_complete=on_progress, # Progress callback
190
+ on_result=on_result, # Callback with results
187
191
  )
188
192
  ```
189
193
 
@@ -11,9 +11,9 @@ aio_sf/api/auth/static_token.py,sha256=xAXpLkzRh6hDEiaKeR9N3Tk-0PuAAZKKU9Ivs0W81
11
11
  aio_sf/api/bulk_v2/__init__.py,sha256=TxNM9dFmRX5k57Wj_JnposeEqqd9xcJx78wI-d6VH0o,315
12
12
  aio_sf/api/bulk_v2/client.py,sha256=t2vl8bIIvSrbg5T1mFDm7O4vq0GyDihfkVHamGYnZ7I,6817
13
13
  aio_sf/api/bulk_v2/types.py,sha256=18TN_VMisKJVCvo39q36moYXQhGLRUs73CKWIxBeAcs,1471
14
- aio_sf/api/collections/__init__.py,sha256=kE9NTBr-iBgQy-ZQAkJD92m7wYk7FQQ-K0gcKnvUoIg,685
15
- aio_sf/api/collections/batch.py,sha256=2Ie6qeIR-INe4q3c8uoFuXfTonW9bLTDsACuIIqgmAM,14009
16
- aio_sf/api/collections/client.py,sha256=IhIn6D-PpunSeztueK4Rs7AIHp57vhNyH3CuvcXnR6c,19098
14
+ aio_sf/api/collections/__init__.py,sha256=Loy9PVf7VsFqs9J2s5n6DO1txy6H5xdHU422CnuMkaM,677
15
+ aio_sf/api/collections/batch.py,sha256=9C3r0eUxClOqBi6fsAgN3TVaI4ZOcOPGfO7zlBqXvzc,14656
16
+ aio_sf/api/collections/client.py,sha256=gR1z3Q5K6iZvcYzJJ6n0azoEdNYp7UdefHMV8jb2RZY,19044
17
17
  aio_sf/api/collections/records.py,sha256=UwI4mjoMkJPBCYbsNUmj5HTBjbGdcaMHT6UiJ4EmcUA,5371
18
18
  aio_sf/api/collections/retry.py,sha256=RXTpMhWyeYsXNiojgEYIVnGtg8S0iYMxgfWgX3APahE,4669
19
19
  aio_sf/api/collections/types.py,sha256=vWjVVySjV8eRFgdGHiP8QiyaZZ5PtxMl0PyFEZ6yNtY,762
@@ -26,7 +26,7 @@ aio_sf/api/query/types.py,sha256=Wfk75kJpNDCGpTHonCbzjWvayy8guA3eyZp3hE7nBt0,845
26
26
  aio_sf/exporter/__init__.py,sha256=waTegrvw_SvJzREAWD4twSDldSL-HfvhLTLLT1o765o,771
27
27
  aio_sf/exporter/bulk_export.py,sha256=2GtiwXChf7dq7dByGLPDhIJJg-yq9eyoE57H4Ekqaus,13169
28
28
  aio_sf/exporter/parquet_writer.py,sha256=jGiLooxyaqciSDUbXj5F_4uWoR_YrQaB-PrDfRuXR3Y,14495
29
- aio_sf-0.1.0b9.dist-info/METADATA,sha256=ofb2S9YAaOQMkydePRKMeGkMwxtahx5AQyL-Pv9rDsQ,7991
30
- aio_sf-0.1.0b9.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
31
- aio_sf-0.1.0b9.dist-info/licenses/LICENSE,sha256=gu0Cbpiqs-vX7YgJJhGI1jH1mHup3dZMrZc-gmpEG60,1071
32
- aio_sf-0.1.0b9.dist-info/RECORD,,
29
+ aio_sf-0.1.0b11.dist-info/METADATA,sha256=oRytg8N6fOafyfFhHOCAMiYLqIeokqjp_5Rz7yu4aeE,8282
30
+ aio_sf-0.1.0b11.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
31
+ aio_sf-0.1.0b11.dist-info/licenses/LICENSE,sha256=gu0Cbpiqs-vX7YgJJhGI1jH1mHup3dZMrZc-gmpEG60,1071
32
+ aio_sf-0.1.0b11.dist-info/RECORD,,