aio-sf 0.1.0b1__py3-none-any.whl → 0.1.0b3__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.
Files changed (33) hide show
  1. {aio_salesforce → aio_sf}/__init__.py +8 -7
  2. {aio_salesforce → aio_sf}/api/__init__.py +36 -0
  3. aio_sf/api/auth/__init__.py +16 -0
  4. aio_sf/api/auth/base.py +43 -0
  5. aio_sf/api/auth/client_credentials.py +94 -0
  6. aio_sf/api/auth/refresh_token.py +114 -0
  7. aio_sf/api/auth/sfdx_cli.py +119 -0
  8. aio_sf/api/auth/static_token.py +31 -0
  9. {aio_salesforce → aio_sf}/api/bulk_v2/client.py +12 -12
  10. aio_sf/api/client.py +276 -0
  11. aio_sf/api/collections/__init__.py +33 -0
  12. aio_sf/api/collections/client.py +660 -0
  13. aio_sf/api/collections/types.py +70 -0
  14. {aio_salesforce → aio_sf}/api/describe/client.py +16 -16
  15. {aio_salesforce → aio_sf}/api/query/client.py +40 -18
  16. {aio_salesforce → aio_sf}/api/types.py +43 -2
  17. {aio_salesforce → aio_sf}/exporter/__init__.py +0 -4
  18. {aio_salesforce → aio_sf}/exporter/bulk_export.py +22 -43
  19. {aio_salesforce → aio_sf}/exporter/parquet_writer.py +4 -50
  20. {aio_sf-0.1.0b1.dist-info → aio_sf-0.1.0b3.dist-info}/METADATA +23 -37
  21. aio_sf-0.1.0b3.dist-info/RECORD +29 -0
  22. aio_salesforce/api/README.md +0 -107
  23. aio_salesforce/connection.py +0 -511
  24. aio_salesforce/exporter/parquet_writer.py.backup +0 -326
  25. aio_sf-0.1.0b1.dist-info/RECORD +0 -22
  26. {aio_salesforce → aio_sf}/api/bulk_v2/__init__.py +0 -0
  27. {aio_salesforce → aio_sf}/api/bulk_v2/types.py +0 -0
  28. {aio_salesforce → aio_sf}/api/describe/__init__.py +0 -0
  29. {aio_salesforce → aio_sf}/api/describe/types.py +0 -0
  30. {aio_salesforce → aio_sf}/api/query/__init__.py +0 -0
  31. {aio_salesforce → aio_sf}/api/query/types.py +0 -0
  32. {aio_sf-0.1.0b1.dist-info → aio_sf-0.1.0b3.dist-info}/WHEEL +0 -0
  33. {aio_sf-0.1.0b1.dist-info → aio_sf-0.1.0b3.dist-info}/licenses/LICENSE +0 -0
@@ -1,27 +1,28 @@
1
1
  """aio-salesforce: Async Salesforce library for Python with Bulk API 2.0 support."""
2
2
 
3
- __version__ = "0.1.0b1"
4
3
  __author__ = "Jonas"
5
4
  __email__ = "charlie@callaway.cloud"
6
5
 
7
- # Connection functionality
8
- from .connection import ( # noqa: F401
9
- SalesforceConnection,
6
+ # Client functionality
7
+ from .api.client import SalesforceClient # noqa: F401
8
+ from .api.auth import ( # noqa: F401
10
9
  SalesforceAuthError,
11
10
  AuthStrategy,
12
11
  ClientCredentialsAuth,
13
12
  RefreshTokenAuth,
14
13
  StaticTokenAuth,
14
+ SfdxCliAuth,
15
15
  )
16
16
 
17
- # Core package only exports connection functionality
18
- # Users import exporter functions directly: from aio_salesforce.exporter import bulk_query
17
+ # Core package only exports client functionality
18
+ # Users import exporter functions directly: from aio_sf.exporter import bulk_query
19
19
 
20
20
  __all__ = [
21
- "SalesforceConnection",
21
+ "SalesforceClient",
22
22
  "SalesforceAuthError",
23
23
  "AuthStrategy",
24
24
  "ClientCredentialsAuth",
25
25
  "RefreshTokenAuth",
26
26
  "StaticTokenAuth",
27
+ "SfdxCliAuth",
27
28
  ]
@@ -5,6 +5,7 @@ This package provides organized access to Salesforce APIs:
5
5
  - describe: Object and organization describe/metadata
6
6
  - bulk_v2: Bulk API v2 for large data operations
7
7
  - query: SOQL queries and QueryMore operations
8
+ - collections: Bulk record operations (insert, update, upsert, delete)
8
9
  """
9
10
 
10
11
  # Import API clients and types from organized submodules
@@ -15,6 +16,21 @@ from .bulk_v2 import (
15
16
  BulkJobStatus,
16
17
  BulkJobError,
17
18
  )
19
+ from .collections import (
20
+ CollectionsAPI,
21
+ CollectionError,
22
+ CollectionRequest,
23
+ CollectionResult,
24
+ CollectionResponse,
25
+ InsertCollectionRequest,
26
+ UpdateCollectionRequest,
27
+ UpsertCollectionRequest,
28
+ DeleteCollectionRequest,
29
+ CollectionInsertResponse,
30
+ CollectionUpdateResponse,
31
+ CollectionUpsertResponse,
32
+ CollectionDeleteResponse,
33
+ )
18
34
  from .describe import (
19
35
  DescribeAPI,
20
36
  FieldInfo,
@@ -25,7 +41,11 @@ from .describe import (
25
41
  RecordTypeInfo,
26
42
  SObjectDescribe,
27
43
  SObjectInfo,
44
+ )
45
+ from .types import (
28
46
  SalesforceAttributes,
47
+ SalesforceRecord,
48
+ GenericSalesforceRecord,
29
49
  )
30
50
  from .query import (
31
51
  QueryAPI,
@@ -39,6 +59,7 @@ from .query import (
39
59
  __all__ = [
40
60
  # API Clients
41
61
  "BulkV2API",
62
+ "CollectionsAPI",
42
63
  "DescribeAPI",
43
64
  "QueryAPI",
44
65
  # Bulk v2 Types
@@ -46,6 +67,19 @@ __all__ = [
46
67
  "BulkJobInfo",
47
68
  "BulkJobStatus",
48
69
  "BulkJobError",
70
+ # Collections Types
71
+ "CollectionError",
72
+ "CollectionRequest",
73
+ "CollectionResult",
74
+ "CollectionResponse",
75
+ "InsertCollectionRequest",
76
+ "UpdateCollectionRequest",
77
+ "UpsertCollectionRequest",
78
+ "DeleteCollectionRequest",
79
+ "CollectionInsertResponse",
80
+ "CollectionUpdateResponse",
81
+ "CollectionUpsertResponse",
82
+ "CollectionDeleteResponse",
49
83
  # Describe Types
50
84
  "FieldInfo",
51
85
  "LimitInfo",
@@ -56,6 +90,8 @@ __all__ = [
56
90
  "SObjectDescribe",
57
91
  "SObjectInfo",
58
92
  "SalesforceAttributes",
93
+ "SalesforceRecord",
94
+ "GenericSalesforceRecord",
59
95
  # Query Types
60
96
  "QueryResult",
61
97
  "QueryResponse",
@@ -0,0 +1,16 @@
1
+ """Authentication strategies for Salesforce API."""
2
+
3
+ from .base import AuthStrategy, SalesforceAuthError
4
+ from .client_credentials import ClientCredentialsAuth
5
+ from .refresh_token import RefreshTokenAuth
6
+ from .static_token import StaticTokenAuth
7
+ from .sfdx_cli import SfdxCliAuth
8
+
9
+ __all__ = [
10
+ "AuthStrategy",
11
+ "SalesforceAuthError",
12
+ "ClientCredentialsAuth",
13
+ "RefreshTokenAuth",
14
+ "StaticTokenAuth",
15
+ "SfdxCliAuth",
16
+ ]
@@ -0,0 +1,43 @@
1
+ """Base authentication strategy and exceptions."""
2
+
3
+ import time
4
+ from abc import ABC, abstractmethod
5
+ from typing import Optional
6
+
7
+ import httpx
8
+
9
+
10
+ class SalesforceAuthError(Exception):
11
+ """Raised when authentication fails or tokens are invalid."""
12
+
13
+ pass
14
+
15
+
16
+ class AuthStrategy(ABC):
17
+ """Abstract base class for Salesforce authentication strategies."""
18
+
19
+ def __init__(self, instance_url: str | None = None):
20
+ self.instance_url = instance_url.rstrip("/") if instance_url else None
21
+ self.access_token: Optional[str] = None
22
+ self.expires_at: Optional[int] = None
23
+
24
+ @abstractmethod
25
+ async def authenticate(self, http_client: httpx.AsyncClient) -> str:
26
+ """Authenticate and return access token."""
27
+ pass
28
+
29
+ @abstractmethod
30
+ async def refresh_if_needed(self, http_client: httpx.AsyncClient) -> str:
31
+ """Refresh token if needed and return access token."""
32
+ pass
33
+
34
+ @abstractmethod
35
+ def can_refresh(self) -> bool:
36
+ """Return True if this strategy can refresh expired tokens."""
37
+ pass
38
+
39
+ def is_token_expired(self) -> bool:
40
+ """Check if the current token is expired."""
41
+ if not self.expires_at:
42
+ return False
43
+ return self.expires_at <= int(time.time())
@@ -0,0 +1,94 @@
1
+ """OAuth Client Credentials authentication strategy."""
2
+
3
+ import base64
4
+ import logging
5
+ from urllib.parse import urljoin
6
+
7
+ import httpx
8
+
9
+ from .base import AuthStrategy, SalesforceAuthError
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class ClientCredentialsAuth(AuthStrategy):
15
+ """OAuth Client Credentials authentication strategy."""
16
+
17
+ def __init__(self, instance_url: str, client_id: str, client_secret: str):
18
+ super().__init__(instance_url)
19
+ self.client_id = client_id
20
+ self.client_secret = client_secret
21
+
22
+ async def authenticate(self, http_client: httpx.AsyncClient) -> str:
23
+ """Authenticate using OAuth client credentials flow."""
24
+ logger.info("Getting Salesforce access token using client credentials")
25
+
26
+ oauth_url = urljoin(self.instance_url, "/services/oauth2/token")
27
+ data = {
28
+ "grant_type": "client_credentials",
29
+ "client_id": self.client_id,
30
+ "client_secret": self.client_secret,
31
+ }
32
+
33
+ oauth_headers = {
34
+ "Content-Type": "application/x-www-form-urlencoded",
35
+ "Accept": "application/json",
36
+ }
37
+
38
+ try:
39
+ response = await http_client.post(
40
+ oauth_url, data=data, headers=oauth_headers
41
+ )
42
+ response.raise_for_status()
43
+ token_data = response.json()
44
+ self.access_token = token_data["access_token"]
45
+
46
+ # Get token expiration information
47
+ await self._get_token_expiration(http_client)
48
+
49
+ logger.info("Successfully obtained Salesforce access token")
50
+ return self.access_token
51
+
52
+ except httpx.HTTPError as e:
53
+ logger.error(f"HTTP error getting access token: {e}")
54
+ raise SalesforceAuthError(f"Authentication failed: {e}")
55
+ except Exception as e:
56
+ logger.error(f"Unexpected error getting access token: {e}")
57
+ raise SalesforceAuthError(f"Authentication failed: {e}")
58
+
59
+ async def refresh_if_needed(self, http_client: httpx.AsyncClient) -> str:
60
+ """Refresh token if needed (always re-authenticate for client credentials)."""
61
+ if self.access_token and not self.is_token_expired():
62
+ return self.access_token
63
+ return await self.authenticate(http_client)
64
+
65
+ def can_refresh(self) -> bool:
66
+ """Client credentials can always re-authenticate."""
67
+ return True
68
+
69
+ async def _get_token_expiration(self, http_client: httpx.AsyncClient) -> None:
70
+ """Get token expiration time via introspection."""
71
+ introspect_url = urljoin(self.instance_url, "/services/oauth2/introspect")
72
+ introspect_data = {
73
+ "token": self.access_token,
74
+ "token_type_hint": "access_token",
75
+ }
76
+
77
+ auth_string = base64.b64encode(
78
+ f"{self.client_id}:{self.client_secret}".encode("utf-8")
79
+ ).decode("utf-8")
80
+
81
+ introspect_response = await http_client.post(
82
+ introspect_url,
83
+ data=introspect_data,
84
+ headers={
85
+ "Authorization": f"Basic {auth_string}",
86
+ "Content-Type": "application/x-www-form-urlencoded",
87
+ "Accept": "application/json",
88
+ },
89
+ )
90
+ introspect_response.raise_for_status()
91
+ introspect_data = introspect_response.json()
92
+
93
+ # Set expiration time with 30 second buffer
94
+ self.expires_at = introspect_data["exp"] - 30
@@ -0,0 +1,114 @@
1
+ """OAuth Refresh Token authentication strategy."""
2
+
3
+ import base64
4
+ import logging
5
+ from urllib.parse import urljoin
6
+
7
+ import httpx
8
+
9
+ from .base import AuthStrategy, SalesforceAuthError
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class RefreshTokenAuth(AuthStrategy):
15
+ """OAuth Refresh Token authentication strategy."""
16
+
17
+ def __init__(
18
+ self,
19
+ instance_url: str,
20
+ access_token: str,
21
+ refresh_token: str,
22
+ client_id: str,
23
+ client_secret: str,
24
+ ):
25
+ super().__init__(instance_url)
26
+ self.access_token = access_token
27
+ self.refresh_token = refresh_token
28
+ self.client_id = client_id
29
+ self.client_secret = client_secret
30
+
31
+ async def authenticate(self, http_client: httpx.AsyncClient) -> str:
32
+ """Use the provided access token (refresh if needed)."""
33
+ if self.access_token and not self.is_token_expired():
34
+ return self.access_token
35
+ return await self._refresh_token(http_client)
36
+
37
+ async def refresh_if_needed(self, http_client: httpx.AsyncClient) -> str:
38
+ """Refresh token if needed."""
39
+ if self.access_token and not self.is_token_expired():
40
+ return self.access_token
41
+ return await self._refresh_token(http_client)
42
+
43
+ def can_refresh(self) -> bool:
44
+ """Refresh token auth can refresh tokens."""
45
+ return bool(self.refresh_token)
46
+
47
+ async def _refresh_token(self, http_client: httpx.AsyncClient) -> str:
48
+ """Refresh the access token using the refresh token."""
49
+ logger.info("Refreshing Salesforce access token using refresh token")
50
+
51
+ oauth_url = urljoin(self.instance_url, "/services/oauth2/token")
52
+ data = {
53
+ "grant_type": "refresh_token",
54
+ "refresh_token": self.refresh_token,
55
+ "client_id": self.client_id,
56
+ "client_secret": self.client_secret,
57
+ }
58
+
59
+ oauth_headers = {
60
+ "Content-Type": "application/x-www-form-urlencoded",
61
+ "Accept": "application/json",
62
+ }
63
+
64
+ try:
65
+ response = await http_client.post(
66
+ oauth_url, data=data, headers=oauth_headers
67
+ )
68
+ response.raise_for_status()
69
+ token_data = response.json()
70
+ self.access_token = token_data["access_token"]
71
+
72
+ # Update refresh token if a new one is provided
73
+ if "refresh_token" in token_data:
74
+ self.refresh_token = token_data["refresh_token"]
75
+
76
+ # Get token expiration information
77
+ await self._get_token_expiration(http_client)
78
+
79
+ logger.info("Successfully refreshed Salesforce access token")
80
+ return self.access_token
81
+
82
+ except httpx.HTTPError as e:
83
+ logger.error(f"HTTP error refreshing access token: {e}")
84
+ raise SalesforceAuthError(f"Token refresh failed: {e}")
85
+ except Exception as e:
86
+ logger.error(f"Unexpected error refreshing access token: {e}")
87
+ raise SalesforceAuthError(f"Token refresh failed: {e}")
88
+
89
+ async def _get_token_expiration(self, http_client: httpx.AsyncClient) -> None:
90
+ """Get token expiration time via introspection."""
91
+ introspect_url = urljoin(self.instance_url, "/services/oauth2/introspect")
92
+ introspect_data = {
93
+ "token": self.access_token,
94
+ "token_type_hint": "access_token",
95
+ }
96
+
97
+ auth_string = base64.b64encode(
98
+ f"{self.client_id}:{self.client_secret}".encode("utf-8")
99
+ ).decode("utf-8")
100
+
101
+ introspect_response = await http_client.post(
102
+ introspect_url,
103
+ data=introspect_data,
104
+ headers={
105
+ "Authorization": f"Basic {auth_string}",
106
+ "Content-Type": "application/x-www-form-urlencoded",
107
+ "Accept": "application/json",
108
+ },
109
+ )
110
+ introspect_response.raise_for_status()
111
+ introspect_data = introspect_response.json()
112
+
113
+ # Set expiration time with 30 second buffer
114
+ self.expires_at = introspect_data["exp"] - 30
@@ -0,0 +1,119 @@
1
+ """SFDX CLI authentication strategy."""
2
+
3
+ import asyncio
4
+ import json
5
+ import logging
6
+ import re
7
+ import time
8
+ from typing import Dict, Any
9
+
10
+ import httpx
11
+
12
+ from .base import AuthStrategy, SalesforceAuthError
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class SfdxCliAuth(AuthStrategy):
18
+ """SFDX CLI authentication strategy using 'sf org display' command."""
19
+
20
+ def __init__(self, username_or_alias: str):
21
+ """
22
+ Initialize SFDX CLI authentication strategy.
23
+
24
+ :param username_or_alias: Salesforce org username or alias configured in SFDX CLI
25
+ """
26
+ # We'll get the instance URL from the CLI command
27
+ super().__init__(None)
28
+ self.username_or_alias = username_or_alias
29
+ self._ansi_escape = re.compile(r"\x1B[@-_][0-?]*[ -/]*[@-~]")
30
+
31
+ async def authenticate(self, http_client: httpx.AsyncClient) -> str:
32
+ """Get access token from SFDX CLI."""
33
+ logger.info(
34
+ f"Getting Salesforce access token from SFDX CLI for {self.username_or_alias}"
35
+ )
36
+
37
+ try:
38
+ # Execute the SFDX command asynchronously
39
+ token_info = await self._execute_sfdx_command()
40
+
41
+ # Extract token and instance URL
42
+ self.access_token = token_info["accessToken"]
43
+ self.instance_url = token_info["instanceUrl"].rstrip("/")
44
+
45
+ # SFDX tokens typically have a reasonable expiration time
46
+ # We'll set a conservative 1-hour expiration to trigger refresh
47
+ self.expires_at = int(time.time()) + 3600
48
+
49
+ logger.info("Successfully obtained Salesforce access token from SFDX CLI")
50
+ return self.access_token
51
+
52
+ except Exception as e:
53
+ logger.error(f"Error getting access token from SFDX CLI: {e}")
54
+ raise SalesforceAuthError(f"SFDX CLI authentication failed: {e}")
55
+
56
+ async def refresh_if_needed(self, http_client: httpx.AsyncClient) -> str:
57
+ """Refresh token if needed (re-execute CLI command)."""
58
+ if self.access_token and not self.is_token_expired():
59
+ return self.access_token
60
+ return await self.authenticate(http_client)
61
+
62
+ def can_refresh(self) -> bool:
63
+ """SFDX CLI can always re-authenticate."""
64
+ return True
65
+
66
+ async def _execute_sfdx_command(self) -> Dict[str, Any]:
67
+ """
68
+ Execute the SFDX CLI command asynchronously and parse the result.
69
+
70
+ :returns: Dictionary containing accessToken and instanceUrl
71
+ :raises: SalesforceAuthError if command fails or output is invalid
72
+ """
73
+ cmd = f"sf org display -o {self.username_or_alias} --json"
74
+
75
+ try:
76
+ # Run the command asynchronously
77
+ process = await asyncio.create_subprocess_shell(
78
+ cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
79
+ )
80
+
81
+ stdout, stderr = await process.communicate()
82
+
83
+ if process.returncode != 0:
84
+ error_msg = stderr.decode().strip() if stderr else "Unknown error"
85
+ raise SalesforceAuthError(f"SFDX command failed: {error_msg}")
86
+
87
+ # Clean ANSI escape sequences from output
88
+ cleaned_output = self._ansi_escape.sub("", stdout.decode())
89
+
90
+ # Parse JSON response
91
+ try:
92
+ sfdx_info = json.loads(cleaned_output)
93
+ except json.JSONDecodeError as e:
94
+ raise SalesforceAuthError(f"Invalid JSON response from SFDX CLI: {e}")
95
+
96
+ # Validate response structure
97
+ if "result" not in sfdx_info:
98
+ raise SalesforceAuthError("SFDX CLI response missing 'result' field")
99
+
100
+ result = sfdx_info["result"]
101
+
102
+ if "accessToken" not in result:
103
+ raise SalesforceAuthError(
104
+ "SFDX CLI response missing 'accessToken' field"
105
+ )
106
+
107
+ if "instanceUrl" not in result:
108
+ raise SalesforceAuthError(
109
+ "SFDX CLI response missing 'instanceUrl' field"
110
+ )
111
+
112
+ return result
113
+
114
+ except asyncio.TimeoutError:
115
+ raise SalesforceAuthError("SFDX CLI command timed out")
116
+ except FileNotFoundError:
117
+ raise SalesforceAuthError(
118
+ "SFDX CLI not found. Please ensure Salesforce CLI is installed and in PATH"
119
+ )
@@ -0,0 +1,31 @@
1
+ """Static access token authentication strategy."""
2
+
3
+ import httpx
4
+
5
+ from .base import AuthStrategy, SalesforceAuthError
6
+
7
+
8
+ class StaticTokenAuth(AuthStrategy):
9
+ """Static access token authentication strategy (no refresh capability)."""
10
+
11
+ def __init__(self, instance_url: str, access_token: str):
12
+ super().__init__(instance_url)
13
+ self.access_token = access_token
14
+
15
+ async def authenticate(self, http_client: httpx.AsyncClient) -> str:
16
+ """Return the static access token."""
17
+ if not self.access_token:
18
+ raise SalesforceAuthError("No access token available")
19
+ return self.access_token
20
+
21
+ async def refresh_if_needed(self, http_client: httpx.AsyncClient) -> str:
22
+ """Cannot refresh static tokens."""
23
+ if self.is_token_expired():
24
+ raise SalesforceAuthError(
25
+ "Access token has expired and no refresh capability is available."
26
+ )
27
+ return await self.authenticate(http_client)
28
+
29
+ def can_refresh(self) -> bool:
30
+ """Static tokens cannot be refreshed."""
31
+ return False
@@ -14,7 +14,7 @@ from .types import (
14
14
  )
15
15
 
16
16
  if TYPE_CHECKING:
17
- from ...connection import SalesforceConnection
17
+ from ..client import SalesforceClient
18
18
 
19
19
  logger = logging.getLogger(__name__)
20
20
 
@@ -22,12 +22,12 @@ logger = logging.getLogger(__name__)
22
22
  class BulkV2API:
23
23
  """Salesforce Bulk API v2 methods."""
24
24
 
25
- def __init__(self, connection: "SalesforceConnection"):
26
- self.connection = connection
25
+ def __init__(self, client: "SalesforceClient"):
26
+ self.client = client
27
27
 
28
28
  def _get_base_url(self, api_version: Optional[str] = None) -> str:
29
29
  """Get the base URL for Bulk API v2 requests."""
30
- return self.connection.get_base_url(api_version)
30
+ return self.client.get_base_url(api_version)
31
31
 
32
32
  def _get_job_url(self, job_id: str, api_version: Optional[str] = None) -> str:
33
33
  """Get the URL for a specific bulk job."""
@@ -57,7 +57,7 @@ class BulkV2API:
57
57
 
58
58
  :param soql_query: The SOQL query to execute
59
59
  :param all_rows: If True, includes deleted and archived records (queryAll)
60
- :param api_version: API version to use (defaults to connection version)
60
+ :param api_version: API version to use (defaults to client version)
61
61
  :returns: Job information
62
62
  """
63
63
  job_url = self._get_jobs_url(api_version)
@@ -67,7 +67,7 @@ class BulkV2API:
67
67
  "contentType": "CSV",
68
68
  }
69
69
 
70
- response = await self.connection.post(job_url, json=job_data)
70
+ response = await self.client.post(job_url, json=job_data)
71
71
  response.raise_for_status()
72
72
  job_info = response.json()
73
73
 
@@ -85,11 +85,11 @@ class BulkV2API:
85
85
  Get the status of a bulk job.
86
86
 
87
87
  :param job_id: The job ID
88
- :param api_version: API version to use (defaults to connection version)
88
+ :param api_version: API version to use (defaults to client version)
89
89
  :returns: Job status information
90
90
  """
91
91
  status_url = self._get_job_url(job_id, api_version)
92
- response = await self.connection.get(status_url)
92
+ response = await self.client.get(status_url)
93
93
  response.raise_for_status()
94
94
  return response.json()
95
95
 
@@ -106,7 +106,7 @@ class BulkV2API:
106
106
  :param job_id: The job ID
107
107
  :param locator: Query locator for pagination (optional)
108
108
  :param max_records: Maximum number of records to fetch
109
- :param api_version: API version to use (defaults to connection version)
109
+ :param api_version: API version to use (defaults to client version)
110
110
  :returns: Tuple of (CSV response text, next locator or None)
111
111
  """
112
112
  results_url = self._get_job_results_url(job_id, api_version)
@@ -114,7 +114,7 @@ class BulkV2API:
114
114
  if locator:
115
115
  params["locator"] = locator
116
116
 
117
- response = await self.connection.get(results_url, params=params)
117
+ response = await self.client.get(results_url, params=params)
118
118
  response.raise_for_status()
119
119
 
120
120
  # Get next locator from headers
@@ -137,7 +137,7 @@ class BulkV2API:
137
137
  :param job_id: The job ID to monitor
138
138
  :param poll_interval: Time in seconds between status checks
139
139
  :param timeout: Maximum time to wait (None for no timeout)
140
- :param api_version: API version to use (defaults to connection version)
140
+ :param api_version: API version to use (defaults to client version)
141
141
  :returns: Final job status
142
142
  :raises TimeoutError: If job doesn't complete within timeout
143
143
  :raises Exception: If job fails
@@ -187,7 +187,7 @@ class BulkV2API:
187
187
  :param all_rows: If True, includes deleted and archived records
188
188
  :param poll_interval: Time in seconds between status checks
189
189
  :param timeout: Maximum time to wait (None for no timeout)
190
- :param api_version: API version to use (defaults to connection version)
190
+ :param api_version: API version to use (defaults to client version)
191
191
  :returns: Final job status
192
192
  """
193
193
  # Create the job