Skip to content

Commit a202613

Browse files
committed
Changes for release 0.8
1 parent 4cb2b6d commit a202613

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+292
-1010
lines changed

CHANGELOG.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
Changelog
22
=========
33

4+
* 0.8 (June 25th, 2019)
5+
* Removed support for OAuth 1.0
6+
* Replace OAuth Session Manager and CLI with intuit-oauth client.
7+
* Fixed on Invoice object that caused the DocNumber to be set to an empty string.
8+
* Added to_ref method to PaymentMethod object.
9+
* Added CompanyCurrency object
10+
* Fixed issue that prevented creation of TaxAgencies.
11+
412
* 0.7.5 (October 18th, 2018)
513
* Fixed bug with reporting authentication failure when attempting to download PDF (previously the error details were "lost").
614
* Added refresh_access_tokens to Oauth2SessionManager.

quickbooks/__init__.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
from .client import QuickBooks
2-
from .auth import Oauth1SessionManager, Oauth2SessionManager
1+
from .client import QuickBooks

quickbooks/auth.py

Lines changed: 0 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -47,103 +47,6 @@ def get_session(self):
4747
return self.session
4848

4949

50-
class Oauth1SessionManager(AuthSessionManager):
51-
oauth_version = 1.0
52-
request_token_url = "https://oauth.intuit.com/oauth/v1/get_request_token"
53-
access_token_url = "https://oauth.intuit.com/oauth/v1/get_access_token"
54-
authorize_url = "https://appcenter.intuit.com/Connect/Begin"
55-
56-
request_token = ''
57-
request_token_secret = ''
58-
59-
def __init__(self, **kwargs):
60-
if 'consumer_key' in kwargs:
61-
self.consumer_key = kwargs['consumer_key']
62-
63-
if 'consumer_secret' in kwargs:
64-
self.consumer_secret = kwargs['consumer_secret']
65-
66-
if 'access_token' in kwargs:
67-
self.access_token = kwargs['access_token']
68-
69-
if 'access_token_secret' in kwargs:
70-
self.access_token_secret = kwargs['access_token_secret']
71-
72-
def start_session(self):
73-
if not self.started:
74-
if self.consumer_key == '':
75-
raise QuickbooksException("Consumer Key missing. Cannot create session.")
76-
77-
if self.consumer_secret == '':
78-
raise QuickbooksException("Consumer Secret missing. Cannot create session.")
79-
80-
if self.access_token == '':
81-
raise QuickbooksException("Access Token missing. Cannot create session.")
82-
83-
if self.access_token_secret == '':
84-
raise QuickbooksException("Access Token Secret missing. Cannot create session.")
85-
86-
self.session = OAuth1Session(
87-
self.consumer_key,
88-
self.consumer_secret,
89-
self.access_token,
90-
self.access_token_secret,
91-
)
92-
93-
self.started = True
94-
95-
return self.session
96-
97-
def get_authorize_url(self, callback_url):
98-
"""
99-
Returns the Authorize URL as returned by QB, and specified by OAuth 1.0a.
100-
:return URI:
101-
"""
102-
self.authorize_url = self.authorize_url[:self.authorize_url.find('?')] \
103-
if '?' in self.authorize_url else self.authorize_url
104-
105-
qb_service = OAuth1Service(
106-
consumer_key=self.consumer_key,
107-
consumer_secret=self.consumer_secret,
108-
request_token_url=self.request_token_url,
109-
access_token_url=self.access_token_url,
110-
authorize_url=self.authorize_url,
111-
)
112-
113-
response = qb_service.get_raw_request_token(
114-
params={'oauth_callback': callback_url})
115-
116-
oauth_resp = dict(parse_qsl(response.text))
117-
118-
self.request_token = oauth_resp['oauth_token']
119-
self.request_token_secret = oauth_resp['oauth_token_secret']
120-
121-
return qb_service.get_authorize_url(self.request_token)
122-
123-
def get_access_tokens(self, oauth_verifier):
124-
"""
125-
Wrapper around get_auth_session, returns session, and sets access_token and
126-
access_token_secret on the QB Object.
127-
:param oauth_verifier: the oauth_verifier as specified by OAuth 1.0a
128-
"""
129-
qb_service = OAuth1Service(
130-
consumer_key=self.consumer_key,
131-
consumer_secret=self.consumer_secret,
132-
request_token_url=self.request_token_url,
133-
access_token_url=self.access_token_url,
134-
authorize_url=self.authorize_url,
135-
)
136-
137-
session = qb_service.get_auth_session(
138-
self.request_token,
139-
self.request_token_secret,
140-
data={'oauth_verifier': oauth_verifier})
141-
142-
self.access_token = session.access_token
143-
self.access_token_secret = session.access_token_secret
144-
return session
145-
146-
14750
class Oauth2SessionManager(AuthSessionManager):
14851
oauth_version = 2.0
14952
access_token_url = "https://oauth.platform.intuit.com/oauth2/v1/tokens/bearer"

quickbooks/cdc.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,16 @@ def change_data_capture(qbo_class_list, timestamp, qb=None):
2525

2626
query_response_list = cdc_response_dict[0]['QueryResponse']
2727
for query_response_dict in query_response_list:
28-
qb_object_name = [x for x in query_response_dict if x in cdc_class_names][0]
29-
qb_object_list = query_response_dict.pop(qb_object_name)
30-
qb_object_cls = cdc_class_dict[qb_object_name]
28+
qb_object_names = [x for x in query_response_dict if x in cdc_class_names]
3129

32-
query_response = QueryResponse.from_json(query_response_dict)
33-
query_response._object_list = [qb_object_cls.from_json(obj) for obj in qb_object_list]
30+
if len(qb_object_names) == 1:
31+
qb_object_name = qb_object_names[0]
32+
qb_object_list = query_response_dict.pop(qb_object_name)
33+
qb_object_cls = cdc_class_dict[qb_object_name]
3434

35-
setattr(cdc_response, qb_object_name, query_response)
35+
query_response = QueryResponse.from_json(query_response_dict)
36+
query_response._object_list = [qb_object_cls.from_json(obj) for obj in qb_object_list]
37+
38+
setattr(cdc_response, qb_object_name, query_response)
3639

3740
return cdc_response

quickbooks/client.py

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import hmac
1919

2020
try:
21-
from rauth import OAuth1Session, OAuth1Service
21+
from rauth import OAuth1Session, OAuth1Service, OAuth2Session
2222
except ImportError:
2323
print("Please import Rauth:\n\n")
2424
print("http://rauth.readthedocs.org/en/latest/\n")
@@ -28,7 +28,7 @@
2828
class QuickBooks(object):
2929
company_id = 0
3030
session = None
31-
session_manager = None
31+
auth_client = None
3232
sandbox = False
3333
minorversion = None
3434
verifier_token = None
@@ -42,11 +42,11 @@ class QuickBooks(object):
4242

4343
_BUSINESS_OBJECTS = [
4444
"Account", "Attachable", "Bill", "BillPayment",
45-
"Class", "CreditMemo", "Customer",
45+
"Class", "CreditMemo", "Customer", "CompanyCurrency",
4646
"Department", "Deposit", "Employee", "Estimate", "Invoice",
4747
"Item", "JournalEntry", "Payment", "PaymentMethod",
4848
"Purchase", "PurchaseOrder", "RefundReceipt",
49-
"SalesReceipt", "TaxCode", "TaxService/Taxcode", "TaxRate", "Term",
49+
"SalesReceipt", "TaxAgency", "TaxCode", "TaxService/Taxcode", "TaxRate", "Term",
5050
"TimeActivity", "Transfer", "Vendor", "VendorCredit"
5151
]
5252

@@ -64,6 +64,13 @@ def __new__(cls, **kwargs):
6464
else:
6565
instance = object.__new__(cls)
6666

67+
if 'refresh_token' in kwargs:
68+
instance.refresh_token = kwargs['refresh_token']
69+
70+
if 'auth_client' in kwargs:
71+
instance.auth_client = kwargs['auth_client']
72+
instance._start_session()
73+
6774
if 'company_id' in kwargs:
6875
instance.company_id = kwargs['company_id']
6976

@@ -73,14 +80,21 @@ def __new__(cls, **kwargs):
7380
if 'minorversion' in kwargs:
7481
instance.minorversion = kwargs['minorversion']
7582

76-
if 'session_manager' in kwargs:
77-
instance.session_manager = kwargs['session_manager']
78-
7983
if 'verifier_token' in kwargs:
8084
instance.verifier_token = kwargs.get('verifier_token')
8185

8286
return instance
8387

88+
def _start_session(self):
89+
if self.auth_client.access_token is None:
90+
self.auth_client.refresh(refresh_token=self.refresh_token)
91+
92+
self.session = OAuth2Session(
93+
client_id=self.auth_client.client_id,
94+
client_secret=self.auth_client.client_secret,
95+
access_token=self.auth_client.access_token,
96+
)
97+
8498
@classmethod
8599
def get_instance(cls):
86100
return cls.__instance
@@ -119,7 +133,6 @@ def validate_webhook_signature(self, request_body, signature, verifier_token=Non
119133
decoded_hex_signature = base64.b64decode(signature)
120134
return hmac_verifier_token_hash == decoded_hex_signature
121135

122-
123136
def get_current_user(self):
124137
"""Get data from the current user endpoint"""
125138
url = self.current_user_url
@@ -135,7 +148,6 @@ def get_report(self, report_type, qs=None):
135148
result = self.get(url, params=qs)
136149
return result
137150

138-
# TODO: is disconnect url the same for OAuth 1 and OAuth 2?
139151
def disconnect_account(self):
140152
"""
141153
Disconnect current account from the application
@@ -153,7 +165,6 @@ def change_data_capture(self, entity_string, changed_since):
153165
result = self.get(url, params=params)
154166
return result
155167

156-
# TODO: is reconnect url the same for OAuth 1 and OAuth 2?
157168
def reconnect_account(self):
158169
"""
159170
Reconnect current account by refreshing OAuth access tokens
@@ -243,18 +254,13 @@ def post(self, *args, **kwargs):
243254
return self.make_request("POST", *args, **kwargs)
244255

245256
def process_request(self, request_type, url, headers="", params="", data=""):
246-
if self.session_manager is None:
257+
if self.session is None:
247258
raise exceptions.QuickbooksException('No session manager')
248259

249-
if self.session_manager.oauth_version == 2.0:
250-
headers.update({'Authorization': 'Bearer ' + self.session_manager.access_token})
251-
return self.session_manager.get_session().request(
252-
request_type, url, headers=headers, params=params, data=data)
260+
headers.update({'Authorization': 'Bearer ' + self.session.access_token})
253261

254-
else:
255-
return self.session_manager.get_session().request(
256-
request_type, url, True, self.company_id,
257-
headers=headers, params=params, data=data)
262+
return self.session.request(
263+
request_type, url, headers=headers, params=params, data=data)
258264

259265
def get_single_object(self, qbbo, pk):
260266
url = "{0}/company/{1}/{2}/{3}/".format(self.api_url, self.company_id, qbbo.lower(), pk)
@@ -336,8 +342,8 @@ def misc_operation(self, end_point, request_body, content_type='application/json
336342
return results
337343

338344
def download_pdf(self, qbbo, item_id):
339-
if self.session_manager is None:
340-
raise exceptions.QuickbooksException('No session manager')
345+
if self.session is None:
346+
raise exceptions.QuickbooksException('No session')
341347

342348
url = "{0}/company/{1}/{2}/{3}/pdf".format(
343349
self.api_url, self.company_id, qbbo.lower(), item_id)
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
from six import python_2_unicode_compatible
2+
from .base import QuickbooksManagedObject, QuickbooksTransactionEntity, Ref, CustomField, MetaData
3+
4+
5+
@python_2_unicode_compatible
6+
class CompanyCurrency(QuickbooksManagedObject, QuickbooksTransactionEntity):
7+
"""
8+
QBO definition: Applicable only for those companies that enable multicurrency, a companycurrency object
9+
defines a currency that is active in the QuickBooks Online company. One or more companycurrency objects
10+
are active based on the company's multicurrency business requirements and correspond to the list
11+
displayed by the Currency Center in the QuickBooks Online UI
12+
"""
13+
14+
class_dict = {
15+
"CustomField": CustomField,
16+
"MetaData": MetaData,
17+
}
18+
19+
qbo_object_name = "CompanyCurrency"
20+
21+
def __init__(self):
22+
super(CompanyCurrency, self).__init__()
23+
24+
self.Id = None
25+
self.Code = ""
26+
self.Name = ""
27+
self.Active = True
28+
29+
self.CustomField = None
30+
self.MetaData = None
31+
32+
def __str__(self):
33+
return self.Name
34+
35+
def to_ref(self):
36+
ref = Ref()
37+
38+
ref.name = self.Name
39+
ref.type = self.qbo_object_name
40+
ref.value = self.Id
41+
42+
return ref

quickbooks/objects/detailline.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class DiscountLineDetail(QuickbooksBaseObject):
4444
"Discount": DiscountOverride,
4545
"ClassRef": Ref,
4646
"TaxCodeRef": Ref,
47+
"DiscountAccountRef": Ref
4748
}
4849

4950
def __init__(self):
@@ -52,6 +53,8 @@ def __init__(self):
5253
self.Discount = None
5354
self.ClassRef = None
5455
self.TaxCodeRef = None
56+
self.PercentBased = False
57+
self.DiscountPercent = 0
5558

5659

5760
class DiscountLine(DetailLine):
@@ -220,7 +223,7 @@ class ItemBasedExpenseLineDetail(QuickbooksBaseObject):
220223

221224
def __init__(self):
222225
super(ItemBasedExpenseLineDetail, self).__init__()
223-
self.BillableStatus = ""
226+
self.BillableStatus = None
224227
self.UnitPrice = 0
225228
self.TaxInclusiveAmt = 0
226229
self.Qty = 0

quickbooks/objects/invoice.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def __init__(self):
5858
self.Deposit = 0
5959
self.Balance = 0
6060
self.AllowIPNPayment = True
61-
self.DocNumber = ""
61+
self.DocNumber = None
6262
self.PrivateNote = ""
6363
self.DueDate = ""
6464
self.ShipDate = ""

quickbooks/objects/paymentmethod.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from six import python_2_unicode_compatible
2-
from .base import QuickbooksManagedObject, QuickbooksTransactionEntity
2+
from .base import QuickbooksManagedObject, QuickbooksTransactionEntity, Ref
33

44

55
@python_2_unicode_compatible
@@ -24,3 +24,8 @@ def __init__(self):
2424
def __str__(self):
2525
return self.Name
2626

27+
def to_ref(self):
28+
ref = Ref()
29+
ref.name = self.Name
30+
ref.type = self.qbo_object_name
31+
ref.value = self.Id

quickbooks/tools/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)