Skip to content

Commit 1ffee34

Browse files
committed
Add support for operations with line items
Add line items Update doc Clean up pprint Add more fields Add news
1 parent 991c115 commit 1ffee34

File tree

7 files changed

+203
-1
lines changed

7 files changed

+203
-1
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.coverage
2+
tests/__pycache__/
3+
veryfi/__pycache__/

NEWS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
CHANGES
22
=======
33

4+
3.1.0
5+
-----
6+
* Add support for operations with line items
7+
48
3.0.0
59
-----
610
* Use v8 by default, lower timeout

requirements.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
requests>=2.22.0
1+
requests>=2.22.0
2+
pydantic==1.9.0

tests/test_line_items.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import pytest
2+
import responses
3+
4+
from veryfi import *
5+
6+
7+
@pytest.mark.parametrize("client_secret", [None, "s"])
8+
@responses.activate
9+
def test_line_items(client_secret):
10+
mock_doc_id = 1
11+
mock_line_item_id = 1
12+
mock_resp = {
13+
"line_items": [
14+
{
15+
"date": "",
16+
"description": "foo",
17+
"discount": 0.0,
18+
"id": mock_line_item_id,
19+
"order": 1,
20+
"price": 0.0,
21+
"quantity": 1.0,
22+
"reference": "",
23+
"sku": "",
24+
"tax": 0.0,
25+
"tax_rate": 0.0,
26+
"total": 1.0,
27+
"type": "food",
28+
"unit_of_measure": "",
29+
}
30+
],
31+
}
32+
client = Client(client_id="v", client_secret=client_secret, username="o", api_key="c")
33+
responses.add(
34+
responses.GET,
35+
f"{client.versioned_url}/partner/documents/{mock_doc_id}/line-items/",
36+
json=mock_resp,
37+
status=200,
38+
)
39+
assert client.get_line_items(mock_doc_id) == mock_resp
40+
41+
responses.add(
42+
responses.GET,
43+
f"{client.versioned_url}/partner/documents/{mock_doc_id}/line-items/{mock_line_item_id}",
44+
json=mock_resp["line_items"][0],
45+
status=200,
46+
)
47+
assert client.get_line_item(mock_doc_id, mock_line_item_id) == mock_resp["line_items"][0]
48+
49+
mock_resp["line_items"][0]["description"] = "bar"
50+
responses.add(
51+
responses.PUT,
52+
f"{client.versioned_url}/partner/documents/{mock_doc_id}/line-items/{mock_line_item_id}",
53+
json=mock_resp["line_items"][0],
54+
status=200,
55+
)
56+
assert (
57+
client.update_line_item(mock_doc_id, mock_line_item_id, {"description": "foo"})
58+
== mock_resp["line_items"][0]
59+
)
60+
61+
responses.add(
62+
responses.DELETE,
63+
f"{client.versioned_url}/partner/documents/{mock_doc_id}/line-items/{mock_line_item_id}",
64+
json={},
65+
status=200,
66+
)
67+
assert client.delete_line_item(mock_doc_id, mock_line_item_id) is None
68+
69+
responses.add(
70+
responses.DELETE,
71+
f"{client.versioned_url}/partner/documents/{mock_doc_id}/line-items/",
72+
json={},
73+
status=200,
74+
)
75+
assert client.delete_line_items(mock_doc_id) is None
76+
77+
responses.add(
78+
responses.POST,
79+
f"{client.versioned_url}/partner/documents/{mock_doc_id}/line-items/",
80+
json=mock_resp["line_items"][0],
81+
status=200,
82+
)
83+
with pytest.raises(Exception):
84+
client.add_line_item(mock_doc_id, {"order": 1})
85+
with pytest.raises(Exception):
86+
client.add_line_item(mock_doc_id, {"order": 1, "description": "foo"})
87+
88+
assert (
89+
client.add_line_item(mock_doc_id, {"order": 1, "description": "foo", "total": 1.0})
90+
== mock_resp["line_items"][0]
91+
)

veryfi/client.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import requests
1010

11+
from veryfi.model import AddLineItem, UpdateLineItem
1112
from veryfi.errors import VeryfiClientError
1213

1314

@@ -237,3 +238,69 @@ def update_document(self, document_id: int, **kwargs) -> Dict:
237238
endpoint_name = f"/documents/{document_id}/"
238239

239240
return self._request("PUT", endpoint_name, kwargs)
241+
242+
def get_line_items(self, document_id):
243+
"""
244+
Retrieve all line items for a document.
245+
:param document_id: ID of the document you'd like to retrieve
246+
:return: List of line items extracted from the document
247+
"""
248+
endpoint_name = f"/documents/{document_id}/line-items/"
249+
request_arguments = {}
250+
line_items = self._request("GET", endpoint_name, request_arguments)
251+
return line_items
252+
253+
def get_line_item(self, document_id, line_item_id):
254+
"""
255+
Retrieve a line item for existing document by ID.
256+
:param document_id: ID of the document you'd like to retrieve
257+
:param line_item_id: ID of the line item you'd like to retrieve
258+
:return: Line item extracted from the document
259+
"""
260+
endpoint_name = f"/documents/{document_id}/line-items/{line_item_id}"
261+
request_arguments = {}
262+
line_items = self._request("GET", endpoint_name, request_arguments)
263+
return line_items
264+
265+
def add_line_item(self, document_id: int, payload: Dict) -> Dict:
266+
"""
267+
Add a new line item on an existing document.
268+
:param document_id: ID of the document you'd like to update
269+
:param payload: line item object to add
270+
:return: Added line item data
271+
"""
272+
endpoint_name = f"/documents/{document_id}/line-items/"
273+
request_arguments = AddLineItem(**payload).dict(exclude_none=True)
274+
return self._request("POST", endpoint_name, request_arguments)
275+
276+
def update_line_item(self, document_id: int, line_item_id: int, payload: Dict) -> Dict:
277+
"""
278+
Update an existing line item on an existing document.
279+
:param document_id: ID of the document you'd like to update
280+
:param line_item_id: ID of the line item you'd like to update
281+
:param payload: line item object to update
282+
283+
:return: Line item data with updated fields, if fields are writable. Otherwise line item data with unchanged fields.
284+
"""
285+
endpoint_name = f"/documents/{document_id}/line-items/{line_item_id}"
286+
request_arguments = UpdateLineItem(**payload).dict(exclude_none=True)
287+
return self._request("PUT", endpoint_name, request_arguments)
288+
289+
def delete_line_items(self, document_id):
290+
"""
291+
Delete all line items on an existing document.
292+
:param document_id: ID of the document you'd like to delete
293+
"""
294+
endpoint_name = f"/documents/{document_id}/line-items/"
295+
request_arguments = {}
296+
self._request("DELETE", endpoint_name, request_arguments)
297+
298+
def delete_line_item(self, document_id, line_item_id):
299+
"""
300+
Delete an existing line item on an existing document.
301+
:param document_id: ID of the document you'd like to delete
302+
:param line_item_id: ID of the line item you'd like to delete
303+
"""
304+
endpoint_name = f"/documents/{document_id}/line-items/{line_item_id}"
305+
request_arguments = {}
306+
self._request("DELETE", endpoint_name, request_arguments)

veryfi/errors.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ class BadRequest(VeryfiClientError):
4242
pass
4343

4444

45+
class ResourceNotFound(VeryfiClientError):
46+
pass
47+
48+
4549
class UnexpectedHTTPMethod(VeryfiClientError):
4650
pass
4751

@@ -56,6 +60,7 @@ class InternalError(VeryfiClientError):
5660

5761
_error_map = {
5862
400: BadRequest,
63+
404: ResourceNotFound,
5964
401: UnauthorizedAccessToken,
6065
405: UnexpectedHTTPMethod,
6166
409: AccessLimitReached,

veryfi/model.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from typing import Optional
2+
from pydantic import BaseModel
3+
4+
5+
class SharedLineItem(BaseModel):
6+
sku: Optional[str]
7+
category: Optional[str]
8+
tax: Optional[float]
9+
price: Optional[float]
10+
unit_of_measure: Optional[str]
11+
quantity: Optional[float]
12+
upc: Optional[str]
13+
tax_rate: Optional[float]
14+
discount_rate: Optional[float]
15+
start_date: Optional[str]
16+
end_date: Optional[str]
17+
hsn: Optional[str]
18+
section: Optional[str]
19+
weight: Optional[str]
20+
21+
22+
class AddLineItem(SharedLineItem):
23+
order: int
24+
description: str
25+
total: float
26+
27+
28+
class UpdateLineItem(SharedLineItem):
29+
order: Optional[int]
30+
description: Optional[str]
31+
total: Optional[float]

0 commit comments

Comments
 (0)