aio-sf 0.1.0b1__tar.gz → 0.1.0b4__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.
- aio_sf-0.1.0b4/.cursor/rules/api-structure.mdc +225 -0
- aio_sf-0.1.0b4/.cursor/rules/async-patterns.mdc +12 -0
- aio_sf-0.1.0b4/.cursor/rules/project-tooling.mdc +6 -0
- {aio_sf-0.1.0b1 → aio_sf-0.1.0b4}/.github/workflows/test.yml +8 -4
- {aio_sf-0.1.0b1 → aio_sf-0.1.0b4}/PKG-INFO +24 -38
- {aio_sf-0.1.0b1 → aio_sf-0.1.0b4}/README.md +20 -33
- {aio_sf-0.1.0b1 → aio_sf-0.1.0b4}/RELEASE.md +11 -11
- {aio_sf-0.1.0b1 → aio_sf-0.1.0b4}/pyproject.toml +17 -11
- aio_sf-0.1.0b4/pytest.ini +11 -0
- {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b4/src/aio_sf}/__init__.py +8 -7
- {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b4/src/aio_sf}/api/__init__.py +36 -0
- aio_sf-0.1.0b4/src/aio_sf/api/auth/__init__.py +16 -0
- aio_sf-0.1.0b4/src/aio_sf/api/auth/base.py +43 -0
- aio_sf-0.1.0b4/src/aio_sf/api/auth/client_credentials.py +94 -0
- aio_sf-0.1.0b4/src/aio_sf/api/auth/refresh_token.py +114 -0
- aio_sf-0.1.0b4/src/aio_sf/api/auth/sfdx_cli.py +119 -0
- aio_sf-0.1.0b4/src/aio_sf/api/auth/static_token.py +31 -0
- {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b4/src/aio_sf}/api/bulk_v2/client.py +12 -12
- aio_sf-0.1.0b4/src/aio_sf/api/client.py +276 -0
- aio_sf-0.1.0b4/src/aio_sf/api/collections/__init__.py +33 -0
- aio_sf-0.1.0b4/src/aio_sf/api/collections/client.py +660 -0
- aio_sf-0.1.0b4/src/aio_sf/api/collections/types.py +70 -0
- {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b4/src/aio_sf}/api/describe/client.py +16 -16
- {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b4/src/aio_sf}/api/query/client.py +40 -18
- {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b4/src/aio_sf}/api/types.py +43 -2
- {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b4/src/aio_sf}/exporter/__init__.py +0 -4
- {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b4/src/aio_sf}/exporter/bulk_export.py +57 -72
- {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b4/src/aio_sf}/exporter/parquet_writer.py +15 -56
- aio_sf-0.1.0b4/tests/__init__.py +1 -0
- aio_sf-0.1.0b4/tests/conftest.py +78 -0
- aio_sf-0.1.0b4/tests/test_api_clients.py +199 -0
- aio_sf-0.1.0b4/tests/test_auth.py +174 -0
- aio_sf-0.1.0b4/tests/test_client.py +113 -0
- {aio_sf-0.1.0b1 → aio_sf-0.1.0b4}/uv.lock +39 -442
- aio_sf-0.1.0b1/.cursor/rules/async-patterns.mdc +0 -37
- aio_sf-0.1.0b1/.cursor/rules/data-export.mdc +0 -39
- aio_sf-0.1.0b1/.cursor/rules/import-conventions.mdc +0 -38
- aio_sf-0.1.0b1/.cursor/rules/packaging.mdc +0 -40
- aio_sf-0.1.0b1/.cursor/rules/project-structure.mdc +0 -26
- aio_sf-0.1.0b1/.cursor/rules/salesforce-api.mdc +0 -37
- aio_sf-0.1.0b1/contacts.parquet +0 -0
- aio_sf-0.1.0b1/src/aio_salesforce/api/README.md +0 -107
- aio_sf-0.1.0b1/src/aio_salesforce/connection.py +0 -511
- aio_sf-0.1.0b1/src/aio_salesforce/exporter/parquet_writer.py.backup +0 -326
- aio_sf-0.1.0b1/test.py +0 -51
- {aio_sf-0.1.0b1 → aio_sf-0.1.0b4}/.github/workflows/publish.yml +0 -0
- {aio_sf-0.1.0b1 → aio_sf-0.1.0b4}/.gitignore +0 -0
- {aio_sf-0.1.0b1 → aio_sf-0.1.0b4}/LICENSE +0 -0
- {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b4/src/aio_sf}/api/bulk_v2/__init__.py +0 -0
- {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b4/src/aio_sf}/api/bulk_v2/types.py +0 -0
- {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b4/src/aio_sf}/api/describe/__init__.py +0 -0
- {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b4/src/aio_sf}/api/describe/types.py +0 -0
- {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b4/src/aio_sf}/api/query/__init__.py +0 -0
- {aio_sf-0.1.0b1/src/aio_salesforce → aio_sf-0.1.0b4/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
|
|
@@ -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
|
|
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
|
|
41
|
-
from
|
|
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
|
|
55
|
+
uv run --isolated python -c "import aio_sf; print('✅ Wheel installation works')"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aio-sf
|
|
3
|
-
Version: 0.1.
|
|
4
|
-
Summary: Async Salesforce library for Python
|
|
3
|
+
Version: 0.1.0b4
|
|
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
|
|
7
7
|
Project-URL: Issues, https://github.com/callawaycloud/aio-salesforce/issues
|
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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. **
|
|
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
|
-
|
|
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,11 +1,11 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["hatchling"]
|
|
2
|
+
requires = ["hatchling", "hatch-vcs"]
|
|
3
3
|
build-backend = "hatchling.build"
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "aio-sf"
|
|
7
7
|
dynamic = ["version"]
|
|
8
|
-
description = "Async Salesforce library for Python
|
|
8
|
+
description = "Async Salesforce library for Python"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {file = "LICENSE"}
|
|
11
11
|
authors = [
|
|
@@ -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.
|
|
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-
|
|
56
|
+
# aio-sf = "aio_sf.cli:main" # CLI not implemented yet
|
|
58
57
|
|
|
59
58
|
[tool.hatch.version]
|
|
60
|
-
|
|
59
|
+
source = "vcs"
|
|
61
60
|
|
|
62
61
|
[tool.hatch.build.targets.wheel]
|
|
63
|
-
packages = ["src/
|
|
62
|
+
packages = ["src/aio_sf"]
|
|
64
63
|
|
|
65
64
|
# Black configuration
|
|
66
65
|
[tool.black]
|
|
67
66
|
line-length = 88
|
|
68
|
-
target-version = ['
|
|
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 = "
|
|
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.
|
|
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)
|