aio-sf 0.1.0b1__tar.gz → 0.1.0b3__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.
Files changed (54) hide show
  1. aio_sf-0.1.0b3/.cursor/rules/api-structure.mdc +225 -0
  2. aio_sf-0.1.0b3/.cursor/rules/async-patterns.mdc +12 -0
  3. aio_sf-0.1.0b3/.cursor/rules/project-tooling.mdc +6 -0
  4. {aio_sf-0.1.0b1 → aio_sf-0.1.0b3}/.github/workflows/test.yml +8 -4
  5. {aio_sf-0.1.0b1 → aio_sf-0.1.0b3}/PKG-INFO +23 -37
  6. {aio_sf-0.1.0b1 → aio_sf-0.1.0b3}/README.md +20 -33
  7. {aio_sf-0.1.0b1 → aio_sf-0.1.0b3}/RELEASE.md +11 -11
  8. {aio_sf-0.1.0b1 → aio_sf-0.1.0b3}/pyproject.toml +16 -10
  9. aio_sf-0.1.0b3/pytest.ini +11 -0
  10. {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b3/src/aio_sf}/__init__.py +8 -7
  11. {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b3/src/aio_sf}/api/__init__.py +36 -0
  12. aio_sf-0.1.0b3/src/aio_sf/api/auth/__init__.py +16 -0
  13. aio_sf-0.1.0b3/src/aio_sf/api/auth/base.py +43 -0
  14. aio_sf-0.1.0b3/src/aio_sf/api/auth/client_credentials.py +94 -0
  15. aio_sf-0.1.0b3/src/aio_sf/api/auth/refresh_token.py +114 -0
  16. aio_sf-0.1.0b3/src/aio_sf/api/auth/sfdx_cli.py +119 -0
  17. aio_sf-0.1.0b3/src/aio_sf/api/auth/static_token.py +31 -0
  18. {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b3/src/aio_sf}/api/bulk_v2/client.py +12 -12
  19. aio_sf-0.1.0b3/src/aio_sf/api/client.py +276 -0
  20. aio_sf-0.1.0b3/src/aio_sf/api/collections/__init__.py +33 -0
  21. aio_sf-0.1.0b3/src/aio_sf/api/collections/client.py +660 -0
  22. aio_sf-0.1.0b3/src/aio_sf/api/collections/types.py +70 -0
  23. {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b3/src/aio_sf}/api/describe/client.py +16 -16
  24. {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b3/src/aio_sf}/api/query/client.py +40 -18
  25. {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b3/src/aio_sf}/api/types.py +43 -2
  26. {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b3/src/aio_sf}/exporter/__init__.py +0 -4
  27. {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b3/src/aio_sf}/exporter/bulk_export.py +22 -43
  28. {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b3/src/aio_sf}/exporter/parquet_writer.py +4 -50
  29. aio_sf-0.1.0b3/tests/__init__.py +1 -0
  30. aio_sf-0.1.0b3/tests/conftest.py +78 -0
  31. aio_sf-0.1.0b3/tests/test_api_clients.py +199 -0
  32. aio_sf-0.1.0b3/tests/test_auth.py +174 -0
  33. aio_sf-0.1.0b3/tests/test_client.py +113 -0
  34. {aio_sf-0.1.0b1 → aio_sf-0.1.0b3}/uv.lock +39 -442
  35. aio_sf-0.1.0b1/.cursor/rules/async-patterns.mdc +0 -37
  36. aio_sf-0.1.0b1/.cursor/rules/data-export.mdc +0 -39
  37. aio_sf-0.1.0b1/.cursor/rules/import-conventions.mdc +0 -38
  38. aio_sf-0.1.0b1/.cursor/rules/packaging.mdc +0 -40
  39. aio_sf-0.1.0b1/.cursor/rules/project-structure.mdc +0 -26
  40. aio_sf-0.1.0b1/.cursor/rules/salesforce-api.mdc +0 -37
  41. aio_sf-0.1.0b1/contacts.parquet +0 -0
  42. aio_sf-0.1.0b1/src/aio_salesforce/api/README.md +0 -107
  43. aio_sf-0.1.0b1/src/aio_salesforce/connection.py +0 -511
  44. aio_sf-0.1.0b1/src/aio_salesforce/exporter/parquet_writer.py.backup +0 -326
  45. aio_sf-0.1.0b1/test.py +0 -51
  46. {aio_sf-0.1.0b1 → aio_sf-0.1.0b3}/.github/workflows/publish.yml +0 -0
  47. {aio_sf-0.1.0b1 → aio_sf-0.1.0b3}/.gitignore +0 -0
  48. {aio_sf-0.1.0b1 → aio_sf-0.1.0b3}/LICENSE +0 -0
  49. {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b3/src/aio_sf}/api/bulk_v2/__init__.py +0 -0
  50. {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b3/src/aio_sf}/api/bulk_v2/types.py +0 -0
  51. {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b3/src/aio_sf}/api/describe/__init__.py +0 -0
  52. {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b3/src/aio_sf}/api/describe/types.py +0 -0
  53. {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b3/src/aio_sf}/api/query/__init__.py +0 -0
  54. {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b3/src/aio_sf}/api/query/types.py +0 -0
@@ -0,0 +1,225 @@
1
+ ---
2
+ description: adding new apis
3
+ alwaysApply: false
4
+ ---
5
+ # Salesforce API Organization
6
+
7
+ This directory contains organized Salesforce API clients following a consistent convention.
8
+
9
+ ## Directory Structure Convention
10
+
11
+ Each API module follows this structure:
12
+
13
+ ```
14
+ api/
15
+ ├── {api_name}/
16
+ │ ├── __init__.py # Export API client and types
17
+ │ ├── client.py # Main API client class
18
+ │ └── types.py # TypedDict definitions for responses
19
+ └── __init__.py # Export all APIs and types
20
+ ```
21
+
22
+ ## Current APIs
23
+
24
+ ### `describe/` - Describe API
25
+ - **Client**: `DescribeAPI`
26
+ - **Purpose**: Object describe/metadata, organization info, limits
27
+ - **Key Methods**:
28
+ - `describe_sobject()` → `SObjectDescribe`
29
+ - `list_sobjects()` → `List[SObjectInfo]`
30
+ - `get_organization_info()` → `OrganizationInfo`
31
+ - `get_limits()` → `OrganizationLimits`
32
+
33
+ ### `bulk_v2/` - Bulk API v2
34
+ - **Client**: `BulkV2API`
35
+ - **Purpose**: Large data operations, bulk queries
36
+ - **Key Methods**:
37
+ - `create_job()` → `BulkJobInfo`
38
+ - `get_job_status()` → `BulkJobStatus`
39
+ - `get_job_results()` → `Tuple[str, Optional[str]]`
40
+ - `wait_for_job_completion()` → `BulkJobStatus`
41
+
42
+ ### `query/` - Query API
43
+ - **Client**: `QueryAPI`
44
+ - **Purpose**: SOQL queries, QueryMore, SOSL search
45
+ - **Key Methods**:
46
+ - `soql(query, include_deleted=False)` → `QueryResult` (with async iteration)
47
+ - `sosl(search)` → `List[Dict[str, Any]]` (SOSL search)
48
+ - `explain(query)` → `Dict[str, Any]` (query execution plan)
49
+ - `query_more()` → `QueryMoreResponse` (internal pagination)
50
+ - **Features**: SOQL injection protection, automatic pagination, deleted records support
51
+ - **Note**: Batch size is controlled by Salesforce, not configurable in Query API
52
+
53
+ ### `collections/` - Collections API
54
+ - **Client**: `CollectionsAPI`
55
+ - **Purpose**: Bulk record operations using composite sObject collections
56
+ - **Key Methods**:
57
+ - `insert(sobject_type, records, all_or_none=True, batch_size=200, max_concurrent_batches=5)` → `CollectionInsertResponse`
58
+ - `update(sobject_type, records, all_or_none=True, batch_size=200, max_concurrent_batches=5)` → `CollectionUpdateResponse`
59
+ - `upsert(sobject_type, records, external_id_field, all_or_none=True, batch_size=200, max_concurrent_batches=5)` → `CollectionUpsertResponse`
60
+ - `delete(record_ids, all_or_none=True, batch_size=2000, max_concurrent_batches=5)` → `CollectionDeleteResponse`
61
+ - **Features**: Atomic operations (all-or-none), automatic batching for large datasets, concurrent batch processing, order preservation
62
+ - **Limits**: Insert/Update/Upsert: 200 records per batch, Delete: 2000 records per batch
63
+ - **Order Guarantee**: Results maintain the same order as input records, enabling error correlation by index
64
+ - **Note**: Automatically splits large datasets into batches and processes them concurrently for optimal performance
65
+
66
+ ## Naming Conventions
67
+
68
+ ### API Clients
69
+ - **Class Name**: `{ApiName}API` (e.g., `DescribeAPI`, `BulkV2API`, `CollectionsAPI`)
70
+ - **File**: `client.py`
71
+ - **Connection Property**: `sf.{api_name}` (e.g., `sf.describe`, `sf.bulk_v2`, `sf.collections`)
72
+
73
+ ### Types
74
+ - **File**: `types.py`
75
+ - **Naming**: Descriptive, specific to the API
76
+ - **Examples**: `SObjectDescribe`, `BulkJobInfo`, `OrganizationLimits`
77
+
78
+ ### Methods
79
+ - **Return Types**: Always use TypedDict for structured responses
80
+ - **Naming**: Clear, action-oriented (e.g., `get_job_status`, `describe_sobject`)
81
+ - **Parameters**: Use typed parameters with Optional where appropriate
82
+
83
+ ## Adding New APIs
84
+
85
+ When adding a new API (e.g., `query` for SOQL):
86
+
87
+ 1. **Create directory**: `api/query/`
88
+ 2. **Create files**:
89
+ ```python
90
+ # api/query/__init__.py
91
+ from .client import QueryAPI
92
+ from .types import QueryResult, QueryError
93
+ __all__ = ["QueryAPI", "QueryResult", "QueryError"]
94
+
95
+ # api/query/client.py
96
+ class QueryAPI:
97
+ def __init__(self, connection): ...
98
+ async def execute(self, soql: str) -> QueryResult: ...
99
+
100
+ # api/query/types.py
101
+ class QueryResult(TypedDict): ...
102
+ ```
103
+ 3. **Add to main API `__init__.py`**:
104
+ ```python
105
+ from .query import QueryAPI, QueryResult, QueryError
106
+ ```
107
+ 4. **Add to connection**:
108
+ ```python
109
+ @property
110
+ def query(self):
111
+ if self._query_api is None:
112
+ from .api.query import QueryAPI
113
+ self._query_api = QueryAPI(self)
114
+ return self._query_api
115
+ ```# Salesforce API Organization
116
+
117
+ This directory contains organized Salesforce API clients following a consistent convention.
118
+
119
+ ## Directory Structure Convention
120
+
121
+ Each API module follows this structure:
122
+
123
+ ```
124
+ api/
125
+ ├── {api_name}/
126
+ │ ├── __init__.py # Export API client and types
127
+ │ ├── client.py # Main API client class
128
+ │ └── types.py # TypedDict definitions for responses
129
+ └── __init__.py # Export all APIs and types
130
+ ```
131
+
132
+ ## Current APIs
133
+
134
+ ### `describe/` - Describe API
135
+ - **Client**: `DescribeAPI`
136
+ - **Purpose**: Object describe/metadata, organization info, limits
137
+ - **Key Methods**:
138
+ - `describe_sobject()` → `SObjectDescribe`
139
+ - `list_sobjects()` → `List[SObjectInfo]`
140
+ - `get_organization_info()` → `OrganizationInfo`
141
+ - `get_limits()` → `OrganizationLimits`
142
+
143
+ ### `bulk_v2/` - Bulk API v2
144
+ - **Client**: `BulkV2API`
145
+ - **Purpose**: Large data operations, bulk queries
146
+ - **Key Methods**:
147
+ - `create_job()` → `BulkJobInfo`
148
+ - `get_job_status()` → `BulkJobStatus`
149
+ - `get_job_results()` → `Tuple[str, Optional[str]]`
150
+ - `wait_for_job_completion()` → `BulkJobStatus`
151
+
152
+ ### `query/` - Query API
153
+ - **Client**: `QueryAPI`
154
+ - **Purpose**: SOQL queries, QueryMore, SOSL search
155
+ - **Key Methods**:
156
+ - `soql(query, include_deleted=False)` → `QueryResult` (with async iteration)
157
+ - `sosl(search)` → `List[Dict[str, Any]]` (SOSL search)
158
+ - `explain(query)` → `Dict[str, Any]` (query execution plan)
159
+ - `query_more()` → `QueryMoreResponse` (internal pagination)
160
+ - **Features**: SOQL injection protection, automatic pagination, deleted records support
161
+ - **Note**: Batch size is controlled by Salesforce, not configurable in Query API
162
+
163
+ ### `collections/` - Collections API
164
+ - **Client**: `CollectionsAPI`
165
+ - **Purpose**: Bulk record operations using composite sObject collections
166
+ - **Key Methods**:
167
+ - `insert(sobject_type, records, all_or_none=True, batch_size=200, max_concurrent_batches=5)` → `CollectionInsertResponse`
168
+ - `update(sobject_type, records, all_or_none=True, batch_size=200, max_concurrent_batches=5)` → `CollectionUpdateResponse`
169
+ - `upsert(sobject_type, records, external_id_field, all_or_none=True, batch_size=200, max_concurrent_batches=5)` → `CollectionUpsertResponse`
170
+ - `delete(record_ids, all_or_none=True, batch_size=2000, max_concurrent_batches=5)` → `CollectionDeleteResponse`
171
+ - **Features**: Atomic operations (all-or-none), automatic batching for large datasets, concurrent batch processing, order preservation
172
+ - **Limits**: Insert/Update/Upsert: 200 records per batch, Delete: 2000 records per batch
173
+ - **Order Guarantee**: Results maintain the same order as input records, enabling error correlation by index
174
+ - **Note**: Automatically splits large datasets into batches and processes them concurrently for optimal performance
175
+
176
+ ## Naming Conventions
177
+
178
+ ### API Clients
179
+ - **Class Name**: `{ApiName}API` (e.g., `DescribeAPI`, `BulkV2API`, `CollectionsAPI`)
180
+ - **File**: `client.py`
181
+ - **Connection Property**: `sf.{api_name}` (e.g., `sf.describe`, `sf.bulk_v2`, `sf.collections`)
182
+
183
+ ### Types
184
+ - **File**: `types.py`
185
+ - **Naming**: Descriptive, specific to the API
186
+ - **Examples**: `SObjectDescribe`, `BulkJobInfo`, `OrganizationLimits`
187
+
188
+ ### Methods
189
+ - **Return Types**: Always use TypedDict for structured responses
190
+ - **Naming**: Clear, action-oriented (e.g., `get_job_status`, `describe_sobject`)
191
+ - **Parameters**: Use typed parameters with Optional where appropriate
192
+
193
+ ## Adding New APIs
194
+
195
+ When adding a new API (e.g., `query` for SOQL):
196
+
197
+ 1. **Create directory**: `api/query/`
198
+ 2. **Create files**:
199
+ ```python
200
+ # api/query/__init__.py
201
+ from .client import QueryAPI
202
+ from .types import QueryResult, QueryError
203
+ __all__ = ["QueryAPI", "QueryResult", "QueryError"]
204
+
205
+ # api/query/client.py
206
+ class QueryAPI:
207
+ def __init__(self, connection): ...
208
+ async def execute(self, soql: str) -> QueryResult: ...
209
+
210
+ # api/query/types.py
211
+ class QueryResult(TypedDict): ...
212
+ ```
213
+ 3. **Add to main API `__init__.py`**:
214
+ ```python
215
+ from .query import QueryAPI, QueryResult, QueryError
216
+ ```
217
+ 4. **Add to connection**:
218
+ ```python
219
+ @property
220
+ def query(self):
221
+ if self._query_api is None:
222
+ from .api.query import QueryAPI
223
+ self._query_api = QueryAPI(self)
224
+ return self._query_api
225
+ ```
@@ -0,0 +1,12 @@
1
+ ---
2
+ globs: *.py
3
+ description: "Async programming patterns and conventions"
4
+ ---
5
+
6
+ # Async Programming Patterns
7
+
8
+ This project uses async/await patterns throughout. Follow these conventions:
9
+
10
+ ## Client Management
11
+ - Always use `async with SalesforceClient(auth_strategy=auth_strategy) as sf:` for client handling
12
+ - All API interaction should happen through the client object
@@ -0,0 +1,6 @@
1
+ ---
2
+ globs: *.py
3
+ description: "Project tooling"
4
+ ---
5
+
6
+ This project uses uv. Do not use pip.
@@ -27,18 +27,22 @@ jobs:
27
27
  - name: Install dependencies
28
28
  run: uv sync --all-extras --dev
29
29
 
30
+ - name: Run unit tests
31
+ run: |
32
+ uv run pytest tests/ -v --cov=aio_sf --cov-report=xml
33
+
30
34
  - name: Test core installation (no exporter)
31
35
  run: |
32
36
  uv run --isolated python -c "
33
- from aio_salesforce import SalesforceConnection, ClientCredentialsAuth
37
+ from aio_sf import SalesforceClient, ClientCredentialsAuth
34
38
  print('✅ Core installation works')
35
39
  "
36
40
 
37
41
  - name: Test full installation (with exporter)
38
42
  run: |
39
43
  uv run python -c "
40
- from aio_salesforce import SalesforceConnection
41
- from aio_salesforce.exporter import bulk_query, ParquetWriter
44
+ from aio_sf import SalesforceClient
45
+ from aio_sf.exporter import bulk_query, ParquetWriter
42
46
  print('✅ Full installation works')
43
47
  "
44
48
 
@@ -48,4 +52,4 @@ jobs:
48
52
  - name: Test wheel installation
49
53
  run: |
50
54
  uv run --isolated pip install dist/*.whl
51
- uv run --isolated python -c "import aio_salesforce; print('✅ Wheel installation works')"
55
+ uv run --isolated python -c "import aio_sf; print('✅ Wheel installation works')"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aio-sf
3
- Version: 0.1.0b1
3
+ Version: 0.1.0b3
4
4
  Summary: Async Salesforce library for Python with Bulk API 2.0 support
5
5
  Project-URL: Homepage, https://github.com/callawaycloud/aio-salesforce
6
6
  Project-URL: Repository, https://github.com/callawaycloud/aio-salesforce
@@ -32,11 +32,9 @@ Classifier: Development Status :: 3 - Alpha
32
32
  Classifier: Intended Audience :: Developers
33
33
  Classifier: License :: OSI Approved :: MIT License
34
34
  Classifier: Programming Language :: Python :: 3
35
- Classifier: Programming Language :: Python :: 3.9
36
- Classifier: Programming Language :: Python :: 3.10
37
35
  Classifier: Programming Language :: Python :: 3.11
38
36
  Classifier: Programming Language :: Python :: 3.12
39
- Requires-Python: >=3.9
37
+ Requires-Python: >=3.11
40
38
  Requires-Dist: httpx>=0.25.0
41
39
  Requires-Dist: pydantic>=2.0.0
42
40
  Requires-Dist: python-dotenv>=1.0.0
@@ -48,6 +46,7 @@ Provides-Extra: dev
48
46
  Requires-Dist: black>=23.0.0; extra == 'dev'
49
47
  Requires-Dist: mypy>=1.5.0; extra == 'dev'
50
48
  Requires-Dist: pre-commit>=3.0.0; extra == 'dev'
49
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == 'dev'
51
50
  Requires-Dist: pytest-cov>=4.0.0; extra == 'dev'
52
51
  Requires-Dist: pytest-mock>=3.10.0; extra == 'dev'
53
52
  Requires-Dist: pytest>=7.0.0; extra == 'dev'
@@ -61,28 +60,31 @@ Description-Content-Type: text/markdown
61
60
 
62
61
  # aio-sf
63
62
 
64
- An async Salesforce library for Python with Bulk API 2.0 support.
63
+ An async Salesforce library for Python.
65
64
 
66
65
  ## Features
67
66
 
68
67
  ### ✅ Supported APIs
69
- - [x] **OAuth Client Credentials Flow** - Automatic authentication
70
68
  - [x] **Bulk API 2.0** - Efficient querying of large datasets
71
69
  - [x] **Describe API** - Field metadata and object descriptions
72
70
  - [x] **SOQL Query API** - Standard Salesforce queries
73
-
74
- ### 🔄 Planned APIs
75
- - [ ] **SObjects API** - Standard CRUD operations
71
+ - [x] **SObjects Collections API** - CRUD on collections of SObjects (up to 2000 records at a time)
76
72
  - [ ] **Tooling API** - Development and deployment tools
77
73
  - [ ] **Bulk API 1.0** - Legacy bulk operations
78
74
  - [ ] **Streaming API** - Real-time event streaming
79
75
 
76
+ ### ✅ Supported Authentication Strategies
77
+ - [x] **OAuth Client Credentials** - Automatic authentication
78
+ - [x] **Static Token** - Existing access tokens
79
+ - [x] **Refresh Token** - Refresh token flow
80
+ - [x] **SFDX CLI** - Login by grabbing a token from the SFDX CLI
81
+ - [ ] **Password Authentication** - Password + ST authentication (soap login)
82
+
80
83
  ### 🚀 Export Features
81
84
  - [x] **Parquet Export** - Efficient columnar storage with schema mapping
82
85
  - [x] **CSV Export** - Simple text format export
83
86
  - [x] **Resume Support** - Resume interrupted queries using job IDs
84
87
  - [x] **Streaming Processing** - Memory-efficient processing of large datasets
85
- - [x] **Type Mapping** - Automatic Salesforce to PyArrow type conversion
86
88
 
87
89
  ## Installation
88
90
 
@@ -104,7 +106,7 @@ uv add "aio-sf[exporter]"
104
106
  ```python
105
107
  import asyncio
106
108
  import os
107
- from aio_salesforce import SalesforceConnection, ClientCredentialsAuth
109
+ from aio_sf import SalesforceClient, ClientCredentialsAuth
108
110
 
109
111
  async def main():
110
112
  auth = ClientCredentialsAuth(
@@ -113,7 +115,7 @@ async def main():
113
115
  instance_url=os.getenv('SF_INSTANCE_URL'),
114
116
  )
115
117
 
116
- async with SalesforceConnection(auth_strategy=auth) as sf:
118
+ async with SalesforceClient(auth_strategy=auth) as sf:
117
119
  print(f"✅ Connected to: {sf.instance_url}")
118
120
 
119
121
  sobjects = await sf.describe.list_sobjects()
@@ -133,50 +135,34 @@ async def main():
133
135
 
134
136
  query_result = await sf.query.soql(query)
135
137
  # Loop over records using async iteration
138
+ # or: await query_result.collect_all() to collect all records into a list
136
139
  async for record in query_result:
137
140
  print(record.get("AccountId"))
138
141
 
142
+ # Create a new Account
143
+ await sf.collections.insert(
144
+ sobject_type="Account",
145
+ records=[{"Name": "Test Account"}]
146
+ )
147
+
139
148
  asyncio.run(main())
140
149
  ```
141
150
 
142
151
 
143
152
 
144
-
145
153
  ## Exporter
146
154
 
147
155
  The Exporter library contains a streamlined and "opinionated" way to export data from Salesforce to various formats.
148
156
 
149
- ### 2. Query Records
150
- ```python
151
- from aio_salesforce.exporter import bulk_query
152
-
153
- async def main():
154
- # ... authentication code from above ...
155
-
156
- async with SalesforceConnection(auth_strategy=auth) as sf:
157
- # Execute bulk query
158
- query_result = await bulk_query(
159
- sf=sf,
160
- soql_query="SELECT Id, Name, Email FROM Contact LIMIT 1000"
161
- )
162
-
163
- # Process records
164
- count = 0
165
- async for record in query_result:
166
- print(f"Contact: {record['Name']} - {record['Email']}")
167
- count += 1
168
-
169
- print(f"Processed {count} records")
170
- ```
171
157
 
172
158
  ### 3. Export to Parquet
173
159
  ```python
174
- from aio_salesforce.exporter import bulk_query, write_query_to_parquet
160
+ from aio_sf.exporter import bulk_query, write_query_to_parquet
175
161
 
176
162
  async def main():
177
163
  # ... authentication code from above ...
178
164
 
179
- async with SalesforceConnection(auth_strategy=auth) as sf:
165
+ async with SalesforceClient(auth_strategy=auth) as sf:
180
166
  # Query with proper schema
181
167
  query_result = await bulk_query(
182
168
  sf=sf,
@@ -1,27 +1,30 @@
1
1
  # aio-sf
2
2
 
3
- An async Salesforce library for Python with Bulk API 2.0 support.
3
+ An async Salesforce library for Python.
4
4
 
5
5
  ## Features
6
6
 
7
7
  ### ✅ Supported APIs
8
- - [x] **OAuth Client Credentials Flow** - Automatic authentication
9
8
  - [x] **Bulk API 2.0** - Efficient querying of large datasets
10
9
  - [x] **Describe API** - Field metadata and object descriptions
11
10
  - [x] **SOQL Query API** - Standard Salesforce queries
12
-
13
- ### 🔄 Planned APIs
14
- - [ ] **SObjects API** - Standard CRUD operations
11
+ - [x] **SObjects Collections API** - CRUD on collections of SObjects (up to 2000 records at a time)
15
12
  - [ ] **Tooling API** - Development and deployment tools
16
13
  - [ ] **Bulk API 1.0** - Legacy bulk operations
17
14
  - [ ] **Streaming API** - Real-time event streaming
18
15
 
16
+ ### ✅ Supported Authentication Strategies
17
+ - [x] **OAuth Client Credentials** - Automatic authentication
18
+ - [x] **Static Token** - Existing access tokens
19
+ - [x] **Refresh Token** - Refresh token flow
20
+ - [x] **SFDX CLI** - Login by grabbing a token from the SFDX CLI
21
+ - [ ] **Password Authentication** - Password + ST authentication (soap login)
22
+
19
23
  ### 🚀 Export Features
20
24
  - [x] **Parquet Export** - Efficient columnar storage with schema mapping
21
25
  - [x] **CSV Export** - Simple text format export
22
26
  - [x] **Resume Support** - Resume interrupted queries using job IDs
23
27
  - [x] **Streaming Processing** - Memory-efficient processing of large datasets
24
- - [x] **Type Mapping** - Automatic Salesforce to PyArrow type conversion
25
28
 
26
29
  ## Installation
27
30
 
@@ -43,7 +46,7 @@ uv add "aio-sf[exporter]"
43
46
  ```python
44
47
  import asyncio
45
48
  import os
46
- from aio_salesforce import SalesforceConnection, ClientCredentialsAuth
49
+ from aio_sf import SalesforceClient, ClientCredentialsAuth
47
50
 
48
51
  async def main():
49
52
  auth = ClientCredentialsAuth(
@@ -52,7 +55,7 @@ async def main():
52
55
  instance_url=os.getenv('SF_INSTANCE_URL'),
53
56
  )
54
57
 
55
- async with SalesforceConnection(auth_strategy=auth) as sf:
58
+ async with SalesforceClient(auth_strategy=auth) as sf:
56
59
  print(f"✅ Connected to: {sf.instance_url}")
57
60
 
58
61
  sobjects = await sf.describe.list_sobjects()
@@ -72,50 +75,34 @@ async def main():
72
75
 
73
76
  query_result = await sf.query.soql(query)
74
77
  # Loop over records using async iteration
78
+ # or: await query_result.collect_all() to collect all records into a list
75
79
  async for record in query_result:
76
80
  print(record.get("AccountId"))
77
81
 
82
+ # Create a new Account
83
+ await sf.collections.insert(
84
+ sobject_type="Account",
85
+ records=[{"Name": "Test Account"}]
86
+ )
87
+
78
88
  asyncio.run(main())
79
89
  ```
80
90
 
81
91
 
82
92
 
83
-
84
93
  ## Exporter
85
94
 
86
95
  The Exporter library contains a streamlined and "opinionated" way to export data from Salesforce to various formats.
87
96
 
88
- ### 2. Query Records
89
- ```python
90
- from aio_salesforce.exporter import bulk_query
91
-
92
- async def main():
93
- # ... authentication code from above ...
94
-
95
- async with SalesforceConnection(auth_strategy=auth) as sf:
96
- # Execute bulk query
97
- query_result = await bulk_query(
98
- sf=sf,
99
- soql_query="SELECT Id, Name, Email FROM Contact LIMIT 1000"
100
- )
101
-
102
- # Process records
103
- count = 0
104
- async for record in query_result:
105
- print(f"Contact: {record['Name']} - {record['Email']}")
106
- count += 1
107
-
108
- print(f"Processed {count} records")
109
- ```
110
97
 
111
98
  ### 3. Export to Parquet
112
99
  ```python
113
- from aio_salesforce.exporter import bulk_query, write_query_to_parquet
100
+ from aio_sf.exporter import bulk_query, write_query_to_parquet
114
101
 
115
102
  async def main():
116
103
  # ... authentication code from above ...
117
104
 
118
- async with SalesforceConnection(auth_strategy=auth) as sf:
105
+ async with SalesforceClient(auth_strategy=auth) as sf:
119
106
  # Query with proper schema
120
107
  query_result = await bulk_query(
121
108
  sf=sf,
@@ -26,12 +26,7 @@
26
26
  - **Purpose**: Test your packaging pipeline
27
27
 
28
28
  ### Tagged Push → PyPI
29
- 1. **Update version** in `src/aio_salesforce/__init__.py`:
30
- ```python
31
- __version__ = "0.2.0" # Update this
32
- ```
33
-
34
- 2. **Create and push tag**:
29
+ 1. **Create and push tag** (version is automatically derived from tag):
35
30
  ```bash
36
31
  git add -A
37
32
  git commit -m "Release v0.2.0"
@@ -39,7 +34,7 @@
39
34
  git push origin main --tags
40
35
  ```
41
36
 
42
- 3. **Automatic PyPI Publishing**:
37
+ 2. **Automatic PyPI Publishing**:
43
38
  - GitHub Actions detects the tag
44
39
  - Builds and publishes to PyPI automatically
45
40
  - Requires manual approval in the `pypi` environment
@@ -71,14 +66,19 @@ pip install --index-url https://test.pypi.org/simple/ aio-salesforce
71
66
 
72
67
  ## Version Strategy
73
68
 
69
+ ### Automatic Versioning
70
+ - **Version is automatically derived from Git tags** - no manual version updates needed!
71
+ - **Tagged commits**: Use the exact tag (e.g., `v0.2.0` → version `0.2.0`)
72
+ - **Development builds**: Auto-generate dev versions (e.g., `0.1.0b3.dev0+gf2b7d84`)
73
+
74
+ ### Semantic Versioning
74
75
  - **Patch** (0.1.1): Bug fixes, small improvements
75
- - **Minor** (0.2.0): New features, backwards compatible
76
+ - **Minor** (0.2.0): New features, backwards compatible
76
77
  - **Major** (1.0.0): Breaking changes
77
78
 
78
79
  ## Checklist Before Release
79
80
 
80
- - [ ] Update version in `__init__.py`
81
- - [ ] Update CHANGELOG (if you have one)
82
81
  - [ ] All tests passing in CI
83
- - [ ] Documentation updated
82
+ - [ ] Documentation updated
84
83
  - [ ] No breaking changes (or properly documented)
84
+ - [ ] Choose appropriate tag name (version is automatically derived from tag)
@@ -1,5 +1,5 @@
1
1
  [build-system]
2
- requires = ["hatchling"]
2
+ requires = ["hatchling", "hatch-vcs"]
3
3
  build-backend = "hatchling.build"
4
4
 
5
5
  [project]
@@ -16,12 +16,10 @@ classifiers = [
16
16
  "Intended Audience :: Developers",
17
17
  "License :: OSI Approved :: MIT License",
18
18
  "Programming Language :: Python :: 3",
19
- "Programming Language :: Python :: 3.9",
20
- "Programming Language :: Python :: 3.10",
21
19
  "Programming Language :: Python :: 3.11",
22
20
  "Programming Language :: Python :: 3.12",
23
21
  ]
24
- requires-python = ">=3.9"
22
+ requires-python = ">=3.11"
25
23
  dependencies = [
26
24
  "httpx>=0.25.0",
27
25
  "pydantic>=2.0.0",
@@ -39,6 +37,7 @@ all = [
39
37
  ]
40
38
  dev = [
41
39
  "pytest>=7.0.0",
40
+ "pytest-asyncio>=0.21.0",
42
41
  "pytest-cov>=4.0.0",
43
42
  "pytest-mock>=3.10.0",
44
43
  "black>=23.0.0",
@@ -54,18 +53,18 @@ Repository = "https://github.com/callawaycloud/aio-salesforce"
54
53
  Issues = "https://github.com/callawaycloud/aio-salesforce/issues"
55
54
 
56
55
  # [project.scripts]
57
- # aio-salesforce = "aio_salesforce.cli:main" # CLI not implemented yet
56
+ # aio-sf = "aio_sf.cli:main" # CLI not implemented yet
58
57
 
59
58
  [tool.hatch.version]
60
- path = "src/aio_salesforce/__init__.py"
59
+ source = "vcs"
61
60
 
62
61
  [tool.hatch.build.targets.wheel]
63
- packages = ["src/aio_salesforce"]
62
+ packages = ["src/aio_sf"]
64
63
 
65
64
  # Black configuration
66
65
  [tool.black]
67
66
  line-length = 88
68
- target-version = ['py39', 'py310', 'py311', 'py312']
67
+ target-version = ['py311', 'py312']
69
68
  include = '\.pyi?$'
70
69
  extend-exclude = '''
71
70
  /(
@@ -83,7 +82,7 @@ extend-exclude = '''
83
82
 
84
83
  # Ruff configuration
85
84
  [tool.ruff]
86
- target-version = "py39"
85
+ target-version = "py311"
87
86
  line-length = 88
88
87
  select = [
89
88
  "E", # pycodestyle errors
@@ -105,7 +104,7 @@ ignore = [
105
104
 
106
105
  # MyPy configuration
107
106
  [tool.mypy]
108
- python_version = "3.9"
107
+ python_version = "3.11"
109
108
  check_untyped_defs = true
110
109
  disallow_any_generics = true
111
110
  disallow_incomplete_defs = true
@@ -146,3 +145,10 @@ exclude_lines = [
146
145
  "class .*\\bProtocol\\):",
147
146
  "@(abc\\.)?abstractmethod",
148
147
  ]
148
+
149
+ [dependency-groups]
150
+ dev = [
151
+ "pytest>=8.4.2",
152
+ "pytest-asyncio>=1.2.0",
153
+ "pytest-cov>=7.0.0",
154
+ ]
@@ -0,0 +1,11 @@
1
+ [pytest]
2
+ minversion = 7.0
3
+ addopts =
4
+ -ra
5
+ --strict-markers
6
+ --strict-config
7
+ testpaths = tests
8
+ asyncio_mode = auto
9
+ markers =
10
+ slow: marks tests as slow (deselect with '-m "not slow"')
11
+ integration: marks tests as integration tests (require real Salesforce org)