Skip to content

Commit 64e001c

Browse files
authored
feat: allow invoice to be written to PDF file (#33)
Signed-off-by: Roald Nefs <info@roaldnefs.com>
1 parent 86cae18 commit 64e001c

4 files changed

Lines changed: 65 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ All notable changes in **python-transip** are documented below.
77
- The `transip.v6.objects.ApiTestService` service to allow calling the test resource to make sure everything is working.
88
- The `transip.v6.objects.InvoiceItemService` service to allow listing all invoice items on a `transip.v6.objects.Invoice` object.
99
- The `transip.mixins.ObjectUpdateMixin` mixin to allow calling `update()` on API object directly.
10+
- Allow an invoice to be written to a PDF file by calling the `pdf()` method on a `transip.v6.objects.Invoice` object.
1011

1112
[Unreleased]: https://github.com/roaldnefs/python-transip/compare/v0.3.0...HEAD

tests/services/test_invoices.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@
1717
# You should have received a copy of the GNU Lesser General Public License
1818
# along with python-transip. If not, see <https://www.gnu.org/licenses/>.
1919

20-
from typing import List, Tuple, Any, Dict
20+
from typing import List, Tuple, Any, Dict, Optional
2121
import responses # type: ignore
2222
import unittest
23+
import tempfile
24+
import os
2325

2426
from transip import TransIP
2527
from transip.v6.objects import Invoice, InvoiceItem
@@ -29,6 +31,7 @@ class InvoicesTest(unittest.TestCase):
2931
"""Test the InvoiceService."""
3032

3133
client: TransIP
34+
tmp_dir: Optional[tempfile.TemporaryDirectory]
3235

3336
@classmethod
3437
def setUpClass(cls) -> None:
@@ -65,3 +68,17 @@ def test_items_list(self) -> None:
6568
# Expect the get_id() method to return None as the invoice items don't
6669
# have a specific ID attribute.
6770
self.assertIsNone(items[0].get_id()) # type: ignore
71+
72+
@responses.activate
73+
def test_pdf(self) -> None:
74+
"""
75+
Check if the invoice can be written to a PDF file.
76+
"""
77+
invoice_id = "F0000.1911.0000.0004"
78+
invoice: Invoice = self.client.invoices.get(invoice_id) # type: ignore
79+
80+
# Write the invoice to a PDF file in a temporary directory.
81+
with tempfile.TemporaryDirectory() as tmp_dir:
82+
expected = os.path.join(tmp_dir, f"{invoice_id}.pdf")
83+
actual = invoice.pdf(tmp_dir)
84+
self.assertEqual(actual, expected)

transip/exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,7 @@ def __str__(self) -> str:
5050

5151
class TransIPParsingError(TransIPError):
5252
pass
53+
54+
55+
class TransIPIOError(TransIPError):
56+
pass

transip/v6/objects.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
# You should have received a copy of the GNU Lesser General Public License
1818
# along with python-transip. If not, see <https://www.gnu.org/licenses/>.
1919

20+
import os
21+
import base64
22+
2023
from typing import Optional, Type
2124

2225
from transip.base import ApiService, ApiObject
@@ -25,6 +28,7 @@
2528
ObjectDeleteMixin, ObjectUpdateMixin,
2629
CreateAttrsTuple, UpdateAttrsTuple
2730
)
31+
from transip.exceptions import TransIPIOError
2832

2933

3034
class ApiTestService(ApiService):
@@ -197,6 +201,44 @@ def items(self) -> InvoiceItemService:
197201
parent=self # type: ignore
198202
)
199203

204+
def pdf(self, file_path: str) -> Optional[str]:
205+
"""
206+
Write a invoice to a PDF file.
207+
208+
Args:
209+
file_path (str): Path to PDF file, if the path is a directory to
210+
PDF is saved using its invoice number.
211+
212+
Returns:
213+
str: The absolute path to the saved PDF file.
214+
215+
Raises:
216+
TransIPIOError: If the PDF data couldn't be written to file.
217+
"""
218+
invoice_id = self.get_id()
219+
if not invoice_id:
220+
return None
221+
222+
# Retrieve the base64 encoded PDF data
223+
encoded = self.service.client.get(f"/invoices/{invoice_id}/pdf")["pdf"]
224+
data = base64.b64decode(encoded.encode('ascii'))
225+
226+
file_path = os.path.abspath(file_path)
227+
if os.path.isdir(file_path):
228+
file_path = os.path.join(file_path, f"{invoice_id}.pdf")
229+
if os.path.exists(file_path):
230+
raise TransIPIOError(f"File {file_path} already exists")
231+
232+
try:
233+
with open(file_path, 'wb') as pdf_file:
234+
pdf_file.write(data)
235+
except OSError as exc:
236+
raise TransIPIOError(
237+
f"Unable to write PDF file {file_path}"
238+
) from exc
239+
240+
return file_path
241+
200242

201243
class InvoiceService(GetMixin, ListMixin, ApiService):
202244

0 commit comments

Comments
 (0)