Skip to content

Commit

Permalink
Merge pull request #335 from ej2/0.9.6
Browse files Browse the repository at this point in the history
0.9.6
  • Loading branch information
ej2 authored Jan 4, 2024
2 parents 1f2bdb2 + 269b84e commit bb7381e
Show file tree
Hide file tree
Showing 22 changed files with 476 additions and 313 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/codecov.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- name: Upload coverage reports to Codecov
uses: codecov/codecov-action@v3
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
2 changes: 1 addition & 1 deletion .github/workflows/pylint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10"]
python-version: ["3.8", "3.9", "3.10", "3.11"]
steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
Changelog
=========
* 0.9.6 (January 2, 2024)
* Replace RAuth with requests_oauthlib
* Removed python 2 code from client.py
* Removed unused dependencies from Pipfile
* Added new fields to Employee object
* Added VendorAddr to Bill object
* Added new fields to Estimate object
* Fix TaxInclusiveAmt and vendor setting 1099 creation
* Updated readme and contributing

* 0.9.5 (November 1, 2023)
* Added the ability to void all voidable QB types
* Added to_ref to CreditMemo object
Expand Down
12 changes: 6 additions & 6 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ url = "https://pypi.org/simple"
verify_ssl = true

[dev-packages]
coverage = "*"
twine = "*"
pytest = "*"
pytest-cov = "*"

[packages]
urllib3 = ">=1.26.5"
bleach = ">=3.3.0"
urllib3 = ">=2.1.0"
intuit-oauth = "==1.2.4"
rauth = ">=0.7.3"
requests = ">=2.31.0"
simplejson = ">=3.19.1"
nose = "*"
coverage = "*"
twine = "*"
requests_oauthlib = ">=1.3.1"
617 changes: 380 additions & 237 deletions Pipfile.lock

Large diffs are not rendered by default.

12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@ Then create a QuickBooks client object passing in the AuthClient, refresh token,
company_id='COMPANY_ID',
)

If you need to access a minor version (See [Minor versions](https://developer.intuit.com/docs/0100_quickbooks_online/0200_dev_guides/accounting/minor_versions) for
If you need to access a minor version (See [Minor versions](https://developer.intuit.com/app/developer/qbo/docs/learn/explore-the-quickbooks-online-api/minor-versions#working-with-minor-versions) for
details) pass in minorversion when setting up the client:

client = QuickBooks(
auth_client=auth_client,
refresh_token='REFRESH_TOKEN',
company_id='COMPANY_ID',
minorversion=59
minorversion=69
)

Object Operations
Expand All @@ -74,7 +74,9 @@ List of objects:

**Note:** The maximum number of entities that can be returned in a
response is 1000. If the result size is not specified, the default
number is 100. (See [Intuit developer guide](https://developer.intuit.com/docs/0100_accounting/0300_developer_guides/querying_data) for details)
number is 100. (See [Query operations and syntax](https://developer.intuit.com/app/developer/qbo/docs/learn/explore-the-quickbooks-online-api/data-queries) for details)

**Warning:** You should never allow user input to pass into a query without sanitizing it first! This library DOES NOT sanitize user input!

Filtered list of objects:

Expand Down Expand Up @@ -104,6 +106,8 @@ List with custom Where Clause (do not include the `"WHERE"`):

customers = Customer.where("Active = True AND CompanyName LIKE 'S%'", qb=client)



List with custom Where and ordering

customers = Customer.where("Active = True AND CompanyName LIKE 'S%'", order_by='DisplayName', qb=client)
Expand All @@ -112,7 +116,7 @@ List with custom Where Clause and paging:

customers = Customer.where("CompanyName LIKE 'S%'", start_position=1, max_results=25, qb=client)

Filtering a list with a custom query (See [Intuit developer guide](https://developer.intuit.com/docs/0100_accounting/0300_developer_guides/querying_data) for
Filtering a list with a custom query (See [Query operations and syntax](https://developer.intuit.com/app/developer/qbo/docs/learn/explore-the-quickbooks-online-api/data-queries) for
supported SQL statements):

customers = Customer.query("SELECT * FROM Customer WHERE Active = True", qb=client)
Expand Down
21 changes: 12 additions & 9 deletions contributing.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,35 @@
# Contributing

I am accepting pull requests. Sometimes life gets busy and it takes me a little while to get everything merged in. To help speed up the process, please write tests to cover your changes. I will review/merge them as soon as possible.
I am accepting pull requests. Sometimes life gets busy and it takes me a little while to get everything reviewed and merged in. To help speed up the process, please write tests to cover your changes. I will review/merge them as soon as possible.

# Testing

I use [nose](https://nose.readthedocs.io/en/latest/index.html) and [Coverage](https://coverage.readthedocs.io/en/latest/) to run the test suite.
I use [pytest](https://docs.pytest.org/en/7.4.x/contents.html), [Coverage](https://coverage.readthedocs.io/en/latest/), and [pytest-cov](https://pytest-cov.readthedocs.io/en/latest/) to run the test suite.

*WARNING*: The Tests connect to the QBO API and create/modify/delete data. DO NOT USE A PRODUCTION ACCOUNT!

## Testing setup:

1. Create/login into your [Intuit Developer account](https://developer.intuit.com).
2. On your Intuit Developer account, create a Sandbox company and an App.
3. Go to the Intuit Developer OAuth 2.0 Playground and fill out the form to get an **access token** and **refresh token**. You will need to copy the following values into your enviroment variables:
3. Go to the Intuit Developer OAuth 2.0 Playground and fill out the form to get a **refresh token**. You will need to copy the following values into your enviroment variables:
```
export CLIENT_ID="<Client ID>"
export CLIENT_SECRET="<Client Secret>"
export COMPANY_ID="<Realm ID>"
export ACCESS_TOKEN="<Access token>"
export COMPANY_ID="<Realm ID>"
export REFRESH_TOKEN="<Refresh token>"
```

*Note*: You will need to update the access token when it expires.
*Note*: You will need to update the refresh token when it expires.

5. Install *nose* and *coverage*. Using Pip:
`pip install nose coverage`
5. Install *pytest*, *coverage*, and *pytest-cov*. Using Pip (or whatever):
`pip install pytest coverage pytest-cov`

6. Run `nosetests . --with-coverage --cover-package=quickbooks`
6. Run all tests: ```pytest --cov```
Run only unit tests: ```pytest tests/unit --cov```
Run only integration tests: ```pytest tests/intergration --cov```



## Creating new tests
Normal Unit tests that do not connect to the QBO API should be located under `test/unit` Test that connect to QBO API should go under `tests/integration`. Inheriting from `QuickbooksTestCase` will automatically setup `self.qb_client` to use when connecting to QBO.
Expand Down
35 changes: 11 additions & 24 deletions quickbooks/client.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,14 @@
import warnings

try: # Python 3
import http.client as httplib
from urllib.parse import parse_qsl
from functools import partial
to_bytes = lambda value, *args, **kwargs: bytes(value, "utf-8", *args, **kwargs)
except ImportError: # Python 2
import httplib
from urlparse import parse_qsl
to_bytes = str

import http.client as httplib
import textwrap
import codecs
import json

from . import exceptions
import base64
import hashlib
import hmac

try:
from rauth import OAuth1Session, OAuth1Service, OAuth2Session
except ImportError:
print("Please import Rauth:\n\n")
print("http://rauth.readthedocs.org/en/latest/\n")
raise
from . import exceptions
from requests_oauthlib import OAuth2Session

to_bytes = lambda value, *args, **kwargs: bytes(value, "utf-8", *args, **kwargs)


class Environments(object):
Expand Down Expand Up @@ -102,10 +86,13 @@ def _start_session(self):
self.auth_client.refresh(refresh_token=self.refresh_token)

self.session = OAuth2Session(
client_id=self.auth_client.client_id,
client_secret=self.auth_client.client_secret,
access_token=self.auth_client.access_token,
self.auth_client.client_id,
token={
'access_token': self.auth_client.access_token,
'refresh_token': self.auth_client.refresh_token,
}
)

return self.auth_client.refresh_token

def _drop(self):
Expand Down
4 changes: 3 additions & 1 deletion quickbooks/objects/bill.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from quickbooks.objects.detailline import DetailLine, ItemBasedExpenseLine, AccountBasedExpenseLine, \
TDSLine
from .base import Ref, LinkedTxn, QuickbooksManagedObject, QuickbooksTransactionEntity, \
LinkedTxnMixin
LinkedTxnMixin, Address
from .tax import TxnTaxDetail
from ..mixins import DeleteMixin

Expand All @@ -20,6 +20,7 @@ class Bill(DeleteMixin, QuickbooksManagedObject, QuickbooksTransactionEntity, Li
"AttachableRef": Ref,
"DepartmentRef": Ref,
"TxnTaxDetail": TxnTaxDetail,
"VendorAddr": Address,
}

list_dict = {
Expand Down Expand Up @@ -53,6 +54,7 @@ def __init__(self):
self.VendorRef = None
self.DepartmentRef = None
self.APAccountRef = None
self.VendorAddr = None

self.LinkedTxn = []
self.Line = []
Expand Down
3 changes: 1 addition & 2 deletions quickbooks/objects/detailline.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,6 @@ class AccountBasedExpenseLineDetail(QuickbooksBaseObject):
def __init__(self):
super(AccountBasedExpenseLineDetail, self).__init__()
self.BillableStatus = None
self.TaxAmount = 0
self.TaxInclusiveAmt = 0

self.CustomerRef = None
Expand Down Expand Up @@ -234,8 +233,8 @@ def __init__(self):
super(ItemBasedExpenseLineDetail, self).__init__()
self.BillableStatus = None
self.UnitPrice = 0
self.TaxInclusiveAmt = 0
self.Qty = 0
self.TaxInclusiveAmt = 0
self.ItemRef = None
self.ClassRef = None
self.PriceLevelRef = None
Expand Down
9 changes: 7 additions & 2 deletions quickbooks/objects/employee.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .base import Address, PhoneNumber, QuickbooksManagedObject, QuickbooksTransactionEntity, Ref
from .base import Address, PhoneNumber, QuickbooksManagedObject, QuickbooksTransactionEntity, Ref, EmailAddress


class Employee(QuickbooksManagedObject, QuickbooksTransactionEntity):
Expand All @@ -8,7 +8,9 @@ class Employee(QuickbooksManagedObject, QuickbooksTransactionEntity):

class_dict = {
"PrimaryAddr": Address,
"PrimaryPhone": PhoneNumber
"PrimaryPhone": PhoneNumber,
"Mobile": PhoneNumber,
"PrimaryEmailAddr": EmailAddress,
}

qbo_object_name = "Employee"
Expand All @@ -26,6 +28,7 @@ def __init__(self):
self.EmployeeNumber = ""
self.Title = ""
self.BillRate = 0
self.CostRate = 0
self.BirthDate = ""
self.Gender = None
self.HiredDate = ""
Expand All @@ -36,6 +39,8 @@ def __init__(self):

self.PrimaryAddr = None
self.PrimaryPhone = None
self.Mobile = None
self.EmailAddress = None

def __str__(self):
return self.DisplayName
Expand Down
1 change: 1 addition & 0 deletions quickbooks/objects/estimate.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ def __init__(self):
self.ClassRef = None
self.SalesTermRef = None
self.ShipMethodRef = None
self.TrackingNum = ""

self.CustomField = []
self.LinkedTxn = []
Expand Down
2 changes: 1 addition & 1 deletion quickbooks/objects/vendor.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ def __init__(self):
self.Balance = 0
self.BillRate = 0
self.AcctNum = ""
self.Vendor1099 = True
self.Vendor1099 = False
self.TaxReportingBasis = ""

self.BillAddr = None
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
intuit-oauth==1.2.4
rauth>=0.7.3
requests_oauthlib>=1.3.1
requests>=2.31.0
simplejson>=3.19.1
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def read(*parts):
return fp.read()


VERSION = (0, 9, 5)
VERSION = (0, 9, 6)
version = '.'.join(map(str, VERSION))

setup(
Expand All @@ -32,7 +32,7 @@ def read(*parts):
install_requires=[
'setuptools',
'intuit-oauth==1.2.4',
'rauth>=0.7.3',
'requests_oauthlib>=1.3.1',
'requests>=2.31.0',
'simplejson>=3.19.1',
'python-dateutil',
Expand All @@ -50,6 +50,7 @@ def read(*parts):
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
'Programming Language :: Python :: 3.10',
'Programming Language :: Python :: 3.11',
],
packages=find_packages(exclude=("tests",)),
)
2 changes: 1 addition & 1 deletion tests/integration/test_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def setUp(self):
)

self.qb_client = QuickBooks(
minorversion=65,
minorversion=69,
auth_client=self.auth_client,
refresh_token=os.environ.get('REFRESH_TOKEN'),
company_id=os.environ.get('COMPANY_ID'),
Expand Down
21 changes: 20 additions & 1 deletion tests/integration/test_bill.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from datetime import datetime

from quickbooks.objects.base import Ref
from quickbooks.objects.base import Ref, Address
from quickbooks.objects.bill import Bill
from quickbooks.objects.detailline import AccountBasedExpenseLine, AccountBasedExpenseLineDetail
from quickbooks.objects.vendor import Vendor
Expand Down Expand Up @@ -30,10 +30,29 @@ def test_create(self):
vendor = Vendor.all(max_results=1, qb=self.qb_client)[0]
bill.VendorRef = vendor.to_ref()

# Test undocumented VendorAddr field
bill.VendorAddr = Address()
bill.VendorAddr.Line1 = "123 Main"
bill.VendorAddr.Line2 = "Apartment 1"
bill.VendorAddr.City = "City"
bill.VendorAddr.Country = "U.S.A"
bill.VendorAddr.CountrySubDivisionCode = "CA"
bill.VendorAddr.PostalCode = "94030"

bill.save(qb=self.qb_client)

query_bill = Bill.get(bill.Id, qb=self.qb_client)

self.assertEqual(query_bill.Id, bill.Id)
self.assertEqual(len(query_bill.Line), 1)
self.assertEqual(query_bill.Line[0].Amount, 200.0)

self.assertEqual(query_bill.VendorAddr.Line1, bill.VendorAddr.Line1)
self.assertEqual(query_bill.VendorAddr.Line2, bill.VendorAddr.Line2)
self.assertEqual(query_bill.VendorAddr.City, bill.VendorAddr.City)
self.assertEqual(query_bill.VendorAddr.Country, bill.VendorAddr.Country)
self.assertEqual(query_bill.VendorAddr.CountrySubDivisionCode, bill.VendorAddr.CountrySubDivisionCode)
self.assertEqual(query_bill.VendorAddr.PostalCode, bill.VendorAddr.PostalCode)



2 changes: 2 additions & 0 deletions tests/integration/test_estimate.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ def test_create(self):
line2.DetailType = "DiscountLineDetail"

estimate.Line.append(line2)
estimate.TrackingNum = "42"

estimate.save(qb=self.qb_client)

Expand Down Expand Up @@ -134,3 +135,4 @@ def test_create(self):
estimate.Line[1].DiscountLineDetail.DiscountAccountRef.value)
self.assertEqual(query_estimate.Line[2].DiscountLineDetail.DiscountAccountRef.name,
estimate.Line[1].DiscountLineDetail.DiscountAccountRef.name)
self.assertEqual(query_estimate.TrackingNum, estimate.TrackingNum)
Loading

0 comments on commit bb7381e

Please sign in to comment.