Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.9.6 #335

Merged
merged 17 commits into from
Jan 4, 2024
Merged

0.9.6 #335

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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