qubesync 0.1.0__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.
- qubesync-0.1.0/LICENSE +21 -0
- qubesync-0.1.0/PKG-INFO +388 -0
- qubesync-0.1.0/README.md +361 -0
- qubesync-0.1.0/pyproject.toml +41 -0
- qubesync-0.1.0/setup.cfg +4 -0
- qubesync-0.1.0/src/__init__.py +3 -0
- qubesync-0.1.0/src/qubesync/__init__.py +58 -0
- qubesync-0.1.0/src/qubesync/client.py +134 -0
- qubesync-0.1.0/src/qubesync/request_builder.py +194 -0
- qubesync-0.1.0/src/qubesync.egg-info/PKG-INFO +388 -0
- qubesync-0.1.0/src/qubesync.egg-info/SOURCES.txt +15 -0
- qubesync-0.1.0/src/qubesync.egg-info/dependency_links.txt +1 -0
- qubesync-0.1.0/src/qubesync.egg-info/requires.txt +1 -0
- qubesync-0.1.0/src/qubesync.egg-info/top_level.txt +1 -0
- qubesync-0.1.0/tests/test_invoice_query.py +12 -0
- qubesync-0.1.0/tests/test_qube_sync.py +385 -0
- qubesync-0.1.0/tests/test_request_builder.py +481 -0
qubesync-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 QubeSync
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
qubesync-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: qubesync
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A Python library for QuickBooks Desktop / Enterprise integration via QubeSync API with intuitive DSL for building QBXML requests.
|
|
5
|
+
Author-email: QubeSync <support@qubesync.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/qubeintegrations/qube_sync_py
|
|
8
|
+
Project-URL: Documentation, https://docs.qubesync.com
|
|
9
|
+
Project-URL: Repository, https://github.com/qubeintegrations/qube_sync_py
|
|
10
|
+
Project-URL: Issues, https://github.com/qubeintegrations/qube_sync_py/issues
|
|
11
|
+
Keywords: quickbooks,qbxml,accounting,integration,api,dsl
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: requests
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# QubeSync Python Library
|
|
29
|
+
|
|
30
|
+
A Python library for seamless QuickBooks integration via the QubeSync API. This library provides both a robust API client for QubeSync operations and a powerful DSL (Domain Specific Language) for building QuickBooks XML requests.
|
|
31
|
+
|
|
32
|
+
## Features
|
|
33
|
+
|
|
34
|
+
- 🔌 **Complete QubeSync API client** - Connect, queue requests, handle webhooks
|
|
35
|
+
- 🏗️ **Intuitive Request Builder DSL** - Build QBXML requests with Python syntax
|
|
36
|
+
- 🔒 **Webhook security** - Built-in signature verification for webhooks
|
|
37
|
+
- 🚀 **Multiple syntax styles** - Context managers, fluent chaining, or Ruby-like syntax
|
|
38
|
+
- ✅ **Fully tested** - comprehensive tests ensuring reliability
|
|
39
|
+
- 📦 **Clean imports** - Simple `from qubesync import ...` syntax
|
|
40
|
+
|
|
41
|
+
## Installation
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install qubesync
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Or install from source:
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
git clone https://github.com/qubeintegrations/qube_sync_py
|
|
51
|
+
cd qube_sync_py
|
|
52
|
+
pip install -e .
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Quick Start
|
|
56
|
+
|
|
57
|
+
### Environment Setup
|
|
58
|
+
|
|
59
|
+
Set your QubeSync credentials:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
export QUBE_API_KEY="your_api_key_here"
|
|
63
|
+
export QUBE_WEBHOOK_SECRET="your_webhook_secret_here"
|
|
64
|
+
export QUBE_URL="https://qubesync.com/api/v1" # Optional, defaults to this
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Basic Usage
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from qubesync import QubeSync, RequestBuilder
|
|
71
|
+
|
|
72
|
+
# 1. Create a connection
|
|
73
|
+
connection = QubeSync.create_connection({
|
|
74
|
+
"name": "My Customer Inc.", # optional (will populate from QuickBooks on first sync if blank)
|
|
75
|
+
"redirect_url": "https://your-app.com/qbd-setup/callback"
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
# 2. Build a request using the DSL
|
|
79
|
+
request = RequestBuilder(version="16.0")
|
|
80
|
+
with request as r:
|
|
81
|
+
with r.QBXML() as qbxml:
|
|
82
|
+
with qbxml.QBXMLMsgsRq(onError='stopOnError') as msgs:
|
|
83
|
+
with msgs.CustomerQueryRq(requestID='customer_123', iterator='Start') as query:
|
|
84
|
+
query.MaxReturned(50)
|
|
85
|
+
query.IncludeRetElement('ListID')
|
|
86
|
+
query.IncludeRetElement('Name')
|
|
87
|
+
query.IncludeRetElement('Email')
|
|
88
|
+
|
|
89
|
+
# 3. Queue the request
|
|
90
|
+
response = QubeSync.queue_request(connection['id'], {
|
|
91
|
+
'request_json': request.as_json(),
|
|
92
|
+
'webhook_url': 'https://your-app.com/webhooks/qb_response'
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
print(f"Request queued with ID: {response['id']}")
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## API Client Reference
|
|
99
|
+
|
|
100
|
+
### Connection Management
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
from qubesync import QubeSync
|
|
104
|
+
|
|
105
|
+
# Create a new connection
|
|
106
|
+
connection = QubeSync.create_connection({
|
|
107
|
+
"name": "My Customer Inc.", # optional (will populate from QuickBooks on first sync if blank)
|
|
108
|
+
"redirect_url": "https://your-app.com/qbd-setup/callback"
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
# Get connection details
|
|
112
|
+
connection = QubeSync.get_connection(connection_id)
|
|
113
|
+
|
|
114
|
+
# Delete a connection
|
|
115
|
+
QubeSync.delete_connection(connection_id)
|
|
116
|
+
|
|
117
|
+
# Generate QWC file for QuickBooks
|
|
118
|
+
qwc_content = QubeSync.get_qwc(connection_id)
|
|
119
|
+
|
|
120
|
+
# Generate password for QuickBooks connection
|
|
121
|
+
password = QubeSync.generate_password(connection_id)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Request Management
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
# Queue a request
|
|
128
|
+
request_data = {
|
|
129
|
+
'request_json': {...}, # Built using RequestBuilder
|
|
130
|
+
'webhook_url': 'https://your-app.com/webhook'
|
|
131
|
+
}
|
|
132
|
+
response = QubeSync.queue_request(connection_id, request_data)
|
|
133
|
+
|
|
134
|
+
# Get request status
|
|
135
|
+
request = QubeSync.get_request(request_id)
|
|
136
|
+
|
|
137
|
+
# Get all requests for a connection
|
|
138
|
+
requests = QubeSync.get_requests(connection_id)
|
|
139
|
+
|
|
140
|
+
# Delete a request
|
|
141
|
+
QubeSync.delete_request(request_id)
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Webhook Handling
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
# Verify and parse webhook payload
|
|
148
|
+
def handle_webhook(request):
|
|
149
|
+
signature = request.headers.get('X-Qube-Signature')
|
|
150
|
+
body = request.body.decode('utf-8')
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
webhook_data = QubeSync.verify_and_build_webhook(body, signature)
|
|
154
|
+
# Process the verified webhook data in the background
|
|
155
|
+
# to ensure a quick response
|
|
156
|
+
print(f"Received response for request: {webhook_data['request_id']}")
|
|
157
|
+
return {"status": "success"}
|
|
158
|
+
except (StaleWebhookError, InvalidWebhookSignatureError) as e:
|
|
159
|
+
print(f"Webhook verification failed: {e}")
|
|
160
|
+
return {"status": "error"}, 400
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## RequestBuilder DSL
|
|
164
|
+
|
|
165
|
+
The RequestBuilder provides an intuitive Python DSL for constructing QBXML requests that get converted to JSON format for the QubeSync API.
|
|
166
|
+
|
|
167
|
+
### Context Manager Style (Recommended)
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
from qubesync import RequestBuilder
|
|
171
|
+
|
|
172
|
+
request = RequestBuilder(version="16.0")
|
|
173
|
+
with request as r:
|
|
174
|
+
with r.QBXML() as qbxml:
|
|
175
|
+
with qbxml.QBXMLMsgsRq(onError='stopOnError') as msgs:
|
|
176
|
+
with msgs.InvoiceQueryRq(requestID='inv_query_123') as query:
|
|
177
|
+
query.MaxReturned(25)
|
|
178
|
+
query.IncludeRetElement('TxnID')
|
|
179
|
+
query.IncludeRetElement('TxnNumber')
|
|
180
|
+
query.IncludeRetElement('CustomerRef')
|
|
181
|
+
query.IncludeRetElement('TxnDate')
|
|
182
|
+
query.IncludeRetElement('DueDate')
|
|
183
|
+
query.IncludeRetElement('BalanceRemaining')
|
|
184
|
+
|
|
185
|
+
json_data = request.as_json()
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Fluent Chaining Style
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from qubesync import FluentRequestBuilder
|
|
192
|
+
|
|
193
|
+
request = FluentRequestBuilder(version="16.0")
|
|
194
|
+
request.QBXML().QBXMLMsgsRq(onError='stopOnError').ItemQueryRq(
|
|
195
|
+
requestID='item_query_456'
|
|
196
|
+
).add_children([
|
|
197
|
+
('MaxReturned', 100),
|
|
198
|
+
('IncludeRetElement', 'ListID'),
|
|
199
|
+
('IncludeRetElement', 'Name'),
|
|
200
|
+
('IncludeRetElement', 'Type'),
|
|
201
|
+
('IncludeRetElement', 'SalePrice')
|
|
202
|
+
])
|
|
203
|
+
|
|
204
|
+
json_data = request.as_json()
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Ruby-like Style
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from qubesync import RequestBuilder
|
|
211
|
+
|
|
212
|
+
request = RequestBuilder(version="16.0")
|
|
213
|
+
qbxml = request.QBXML()
|
|
214
|
+
msgs = qbxml.QBXMLMsgsRq(onError='stopOnError')
|
|
215
|
+
query = msgs.CustomerQueryRq(requestID='customer_789', iterator='Start')
|
|
216
|
+
query.MaxReturned(30)
|
|
217
|
+
query.IncludeRetElement('ListID')
|
|
218
|
+
query.IncludeRetElement('Name')
|
|
219
|
+
query.IncludeRetElement('CompanyName')
|
|
220
|
+
|
|
221
|
+
json_data = request.as_json()
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Handling Python Reserved Words
|
|
225
|
+
|
|
226
|
+
Use trailing underscores for Python reserved words:
|
|
227
|
+
|
|
228
|
+
```python
|
|
229
|
+
request = RequestBuilder(version="16.0")
|
|
230
|
+
with request as r:
|
|
231
|
+
with r.QBXML() as qbxml:
|
|
232
|
+
with qbxml.QBXMLMsgsRq(onError='stopOnError') as msgs:
|
|
233
|
+
# Use trailing underscore for 'class', 'for', 'if', etc.
|
|
234
|
+
with msgs.CustomQueryRq(class_='invoice', for_='reports') as query:
|
|
235
|
+
query.MaxReturned(100)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Complex Nested Structures
|
|
239
|
+
|
|
240
|
+
```python
|
|
241
|
+
request = RequestBuilder(version="16.0")
|
|
242
|
+
with request as r:
|
|
243
|
+
with r.QBXML() as qbxml:
|
|
244
|
+
with qbxml.QBXMLMsgsRq(onError='stopOnError') as msgs:
|
|
245
|
+
with msgs.InvoiceAddRq(requestID='invoice_add_999') as add_req:
|
|
246
|
+
with add_req.InvoiceAdd() as invoice:
|
|
247
|
+
with invoice.CustomerRef() as customer_ref:
|
|
248
|
+
customer_ref.ListID('80000001-1234567890')
|
|
249
|
+
|
|
250
|
+
invoice.TxnDate('2024-01-15')
|
|
251
|
+
invoice.RefNumber('INV-2024-001')
|
|
252
|
+
|
|
253
|
+
with invoice.InvoiceLineAdd() as line:
|
|
254
|
+
with line.ItemRef() as item_ref:
|
|
255
|
+
item_ref.ListID('80000002-1234567890')
|
|
256
|
+
line.Desc('Professional Services')
|
|
257
|
+
line.Quantity(10)
|
|
258
|
+
line.Rate(150.00)
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Example Output
|
|
262
|
+
|
|
263
|
+
The RequestBuilder generates clean JSON that the QubeSync API processes:
|
|
264
|
+
|
|
265
|
+
```json
|
|
266
|
+
{
|
|
267
|
+
"version": "16.0",
|
|
268
|
+
"request": [
|
|
269
|
+
{
|
|
270
|
+
"name": "QBXML",
|
|
271
|
+
"children": [
|
|
272
|
+
{
|
|
273
|
+
"name": "QBXMLMsgsRq",
|
|
274
|
+
"attributes": {"onError": "stopOnError"},
|
|
275
|
+
"children": [
|
|
276
|
+
{
|
|
277
|
+
"name": "CustomerQueryRq",
|
|
278
|
+
"attributes": {"requestID": "customer_123", "iterator": "Start"},
|
|
279
|
+
"children": [
|
|
280
|
+
{"name": "MaxReturned", "text": 50},
|
|
281
|
+
{"name": "IncludeRetElement", "text": "ListID"},
|
|
282
|
+
{"name": "IncludeRetElement", "text": "Name"},
|
|
283
|
+
{"name": "IncludeRetElement", "text": "Email"}
|
|
284
|
+
]
|
|
285
|
+
}
|
|
286
|
+
]
|
|
287
|
+
}
|
|
288
|
+
]
|
|
289
|
+
}
|
|
290
|
+
]
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Error Handling
|
|
295
|
+
|
|
296
|
+
```python
|
|
297
|
+
from qubesync import (
|
|
298
|
+
QubeSync,
|
|
299
|
+
QubeSyncError,
|
|
300
|
+
ConfigError,
|
|
301
|
+
StaleWebhookError,
|
|
302
|
+
InvalidWebhookSignatureError
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
try:
|
|
306
|
+
connection = QubeSync.create_connection({"name": "Test"})
|
|
307
|
+
except ConfigError as e:
|
|
308
|
+
print(f"Configuration error: {e}")
|
|
309
|
+
except QubeSyncError as e:
|
|
310
|
+
print(f"API error: {e}")
|
|
311
|
+
|
|
312
|
+
# Webhook verification
|
|
313
|
+
try:
|
|
314
|
+
webhook_data = QubeSync.verify_and_build_webhook(body, signature)
|
|
315
|
+
except StaleWebhookError:
|
|
316
|
+
print("Webhook is too old")
|
|
317
|
+
except InvalidWebhookSignatureError:
|
|
318
|
+
print("Invalid webhook signature")
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Available Classes
|
|
322
|
+
|
|
323
|
+
### API Client
|
|
324
|
+
- `QubeSync` - Main API client for all QubeSync operations
|
|
325
|
+
- `QubeSyncError` - Base exception class
|
|
326
|
+
- `ConfigError` - Configuration/environment variable errors
|
|
327
|
+
- `StaleWebhookError` - Webhook timestamp too old
|
|
328
|
+
- `InvalidWebhookSignatureError` - Webhook signature verification failed
|
|
329
|
+
|
|
330
|
+
### Request Building
|
|
331
|
+
- `RequestBuilder` - Context manager style DSL (recommended)
|
|
332
|
+
- `FluentRequestBuilder` - Method chaining style DSL
|
|
333
|
+
- `RequestElement` - Individual request element (advanced usage)
|
|
334
|
+
|
|
335
|
+
## Development
|
|
336
|
+
|
|
337
|
+
### Running Tests
|
|
338
|
+
|
|
339
|
+
```bash
|
|
340
|
+
# Run all tests
|
|
341
|
+
python run_tests.py
|
|
342
|
+
|
|
343
|
+
# Run with verbose output
|
|
344
|
+
python run_tests.py --verbose
|
|
345
|
+
|
|
346
|
+
# Run specific test file
|
|
347
|
+
python run_tests.py tests/test_request_builder.py
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Running Examples
|
|
351
|
+
|
|
352
|
+
```bash
|
|
353
|
+
# Run all examples
|
|
354
|
+
python examples.py
|
|
355
|
+
|
|
356
|
+
# Use virtual environment
|
|
357
|
+
./bin/python examples.py
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## Contributing
|
|
361
|
+
|
|
362
|
+
1. Fork the repository
|
|
363
|
+
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
|
364
|
+
3. Make your changes
|
|
365
|
+
4. Run tests (`python run_tests.py`)
|
|
366
|
+
5. Commit your changes (`git commit -m 'Add amazing feature'`)
|
|
367
|
+
6. Push to the branch (`git push origin feature/amazing-feature`)
|
|
368
|
+
7. Open a Pull Request
|
|
369
|
+
|
|
370
|
+
## License
|
|
371
|
+
|
|
372
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
373
|
+
|
|
374
|
+
## Support
|
|
375
|
+
|
|
376
|
+
- 📧 Email: support@qubesync.com
|
|
377
|
+
- 📖 Documentation: [QubeSync Docs](https://docs.qubesync.com)
|
|
378
|
+
- 🐛 Issues: [GitHub Issues](https://github.com/qubeintegrations/qube_sync_py/issues)
|
|
379
|
+
|
|
380
|
+
## Changelog
|
|
381
|
+
|
|
382
|
+
### v0.1.0
|
|
383
|
+
- Initial release
|
|
384
|
+
- Complete QubeSync API client
|
|
385
|
+
- RequestBuilder DSL with multiple syntax styles
|
|
386
|
+
- Comprehensive test suite
|
|
387
|
+
- Webhook security verification
|
|
388
|
+
- Clean module structure with intuitive imports
|