Skip to content

Commit 4393c5f

Browse files
authored
Merge pull request aleaforny#6 from rehanzo/master
Make config values configurable
2 parents 8def23e + 869b408 commit 4393c5f

File tree

2 files changed

+122
-75
lines changed

2 files changed

+122
-75
lines changed

README.md

+13-8
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,15 @@ How-to-use (By example)
2121
--------
2222

2323
The code is documented and there are no different endpoints with this API so there are no so many methods.
24-
25-
1) Create the `Invoice` object
24+
1) Create the `config` object
25+
```python
26+
config = InvoiceClientConfig(api_key="YOUR_API_KEY_HERE")
2627
```
28+
29+
2) Create the `Invoice` object
30+
```python
2731
invoice = InvoiceGenerator(
32+
config=config,
2833
sender="Invoiced, Inc.",
2934
to="Parag",
3035
logo="https://invoiced.com/img/logo-invoice.png",
@@ -34,8 +39,8 @@ invoice = InvoiceGenerator(
3439
)
3540
```
3641

37-
2) Add one or several items to it
38-
```
42+
3) Add one or several items to it
43+
```python
3944
invoice.add_item(
4045
name="Starter plan",
4146
quantity=1,
@@ -48,13 +53,13 @@ invoice.add_item(
4853
)
4954
```
5055

51-
3) You can basically customise the object after hand (useful if you have to process things after generating the invoice but before actually sending it, perhaps for some async tasks...)
52-
```
56+
4) You can basically customise the object after hand (useful if you have to process things after generating the invoice but before actually sending it, perhaps for some async tasks...)
57+
```python
5358
invoice.toggle_subtotal(shipping=True)
5459
```
5560

56-
4) Finally download the file (this will actually call the API). It can be absolute or relative path.
57-
```
61+
5) Finally download the file (this will actually call the API). It can be absolute or relative path.
62+
```python
5863
invoice.download("my-awesome-invoice.pdf")
5964
```
6065

invoice_generator.py

+109-67
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,37 @@
22
import json
33
import pytz
44
import locale
5+
from locale import getlocale
6+
from tzlocal import get_localzone
57

68
from datetime import datetime
79

810

11+
class InvoiceClientConfig:
12+
api_key: str
13+
date_format: str
14+
locale: str | None
15+
timezone: str
16+
endpoint_url: str
17+
18+
def __init__(
19+
self,
20+
api_key: str,
21+
date_format: str = "%d %b %Y",
22+
locale: str | None = getlocale()[0] or None,
23+
timezone: str = str(get_localzone()),
24+
endpoint_url: str = "https://invoice-generator.com",
25+
):
26+
self.api_key = api_key
27+
self.date_format = date_format
28+
self.locale = locale
29+
self.timezone = timezone
30+
self.endpoint_url = endpoint_url
31+
32+
933
class InvoiceGenerator:
10-
""" API Object for Invoice-Generator tool - https://invoice-generator.com/ """
34+
"""API Object for Invoice-Generator tool - https://invoice-generator.com/"""
1135

12-
URL = "https://invoice-generator.com"
13-
API_KEY = "SET_YOUR_API_KEY_HERE"
14-
DATE_FORMAT = "%d %b %Y"
15-
LOCALE = "fr_FR"
16-
TIMEZONE = "Europe/Paris"
1736
# Below are the default template parameters that can be changed (see https://github.com/Invoiced/invoice-generator-api/)
1837
TEMPLATE_PARAMETERS = [
1938
"header",
@@ -39,32 +58,37 @@ class InvoiceGenerator:
3958
"notes_title",
4059
]
4160

42-
def __init__(self, sender, to,
43-
logo=None,
44-
ship_to=None,
45-
number=None,
46-
payments_terms=None,
47-
due_date=None,
48-
notes=None,
49-
terms=None,
50-
currency="USD",
51-
date=datetime.now(tz=pytz.timezone(TIMEZONE)),
52-
discounts=0,
53-
tax=0,
54-
shipping=0,
55-
amount_paid=0,
56-
):
57-
""" Object constructor """
61+
def __init__(
62+
self,
63+
config: InvoiceClientConfig,
64+
sender: str,
65+
to: str,
66+
logo: str | None = None,
67+
ship_to: str | None = None,
68+
number: int | None = None,
69+
payments_terms: str | None = None,
70+
due_date=None,
71+
notes: str | None = None,
72+
terms: str | None = None,
73+
currency: str = "USD",
74+
date: datetime | None = None,
75+
discounts: float = 0,
76+
tax: float = 0,
77+
shipping: float = 0,
78+
amount_paid: float = 0,
79+
):
80+
"""Object constructor"""
5881
self.logo = logo
82+
self.config = config
5983
self.sender = sender
6084
self.to = to
6185
self.ship_to = ship_to
6286
self.number = number
6387
self.currency = currency
6488
self.custom_fields = []
65-
self.date = date
89+
self.date = date or datetime.now(tz=pytz.timezone(self.config.timezone))
6690
self.payment_terms = payments_terms
67-
self.due_date = due_date
91+
self.due_date: datetime | None = due_date
6892
self.items = []
6993
self.fields = {"tax": "%", "discounts": False, "shipping": False}
7094
self.discounts = discounts
@@ -83,79 +107,97 @@ def _to_json(self):
83107
We are also resetting the two list of Objects items and custom_fields so that it can be JSON serializable
84108
Finally, we are handling template customization with its dict
85109
"""
86-
locale.setlocale(locale.LC_ALL, InvoiceGenerator.LOCALE)
87-
object_dict = self.__dict__
88-
object_dict['from'] = object_dict.get('sender')
89-
object_dict['date'] = self.date.strftime(InvoiceGenerator.DATE_FORMAT)
90-
if object_dict['due_date'] is not None:
91-
object_dict['due_date'] = self.due_date.strftime(InvoiceGenerator.DATE_FORMAT)
92-
object_dict.pop('sender')
93-
for index, item in enumerate(object_dict['items']):
94-
object_dict['items'][index] = item.__dict__
95-
for index, custom_field in enumerate(object_dict['custom_fields']):
96-
object_dict['custom_fields'][index] = custom_field.__dict__
110+
locale.setlocale(locale.LC_ALL, self.config.locale)
111+
object_dict = self.__dict__.copy()
112+
object_dict["from"] = object_dict.get("sender")
113+
object_dict["date"] = self.date.strftime(self.config.date_format)
114+
object_dict["due_date"] = (
115+
self.due_date.strftime(self.config.date_format) if self.due_date else None
116+
)
117+
object_dict.pop("sender")
118+
for index, item in enumerate(object_dict["items"]):
119+
object_dict["items"][index] = item.__dict__
120+
for index, custom_field in enumerate(object_dict["custom_fields"]):
121+
object_dict["custom_fields"][index] = custom_field.__dict__
97122
for template_parameter, value in self.template.items():
98123
object_dict[template_parameter] = value
99-
object_dict.pop('template')
124+
object_dict.pop("template")
125+
object_dict.pop("config")
100126
return json.dumps(object_dict)
101127

102128
def add_custom_field(self, name=None, value=None):
103-
""" Add a custom field to the invoice """
104-
self.custom_fields.append(CustomField(
105-
name=name,
106-
value=value
107-
))
108-
109-
def add_item(self, name=None, quantity=0, unit_cost=0.0, description=None):
110-
""" Add item to the invoice """
111-
self.items.append(Item(
112-
name=name,
113-
quantity=quantity,
114-
unit_cost=unit_cost,
115-
description=description
116-
))
129+
"""Add a custom field to the invoice"""
130+
self.custom_fields.append(CustomField(name=name, value=value))
131+
132+
def add_item(
133+
self,
134+
name: str | None = None,
135+
quantity=0,
136+
unit_cost=0.0,
137+
description: str | None = None,
138+
):
139+
"""Add item to the invoice"""
140+
self.items.append(
141+
Item(
142+
name=name,
143+
quantity=quantity,
144+
unit_cost=unit_cost,
145+
description=description or "",
146+
)
147+
)
117148

118149
def download(self, file_path):
119-
""" Directly send the request and store the file on path """
150+
"""Directly send the request and store the file on path"""
120151
json_string = self._to_json()
121-
response = requests.post(InvoiceGenerator.URL, json=json.loads(json_string), stream=True, headers={'Accept-Language': InvoiceGenerator.LOCALE, 'Authorization': f'Bearer {self.API_KEY}'})
152+
headers = {
153+
"Authorization": f"Bearer {self.config.api_key}",
154+
}
155+
if self.config.locale:
156+
headers["Accept-Language"] = self.config.locale
157+
response = requests.post(
158+
self.config.endpoint_url,
159+
json=json.loads(json_string),
160+
stream=True,
161+
headers=headers,
162+
)
122163
if response.status_code == 200:
123-
open(file_path, 'wb').write(response.content)
164+
open(file_path, "wb").write(response.content)
124165
else:
125-
raise Exception(f"Invoice download request returned the following message:{response.json()} Response code = {response.status_code} ")
126-
166+
raise Exception(
167+
f"Invoice download request returned the following message:{response.json()} Response code = {response.status_code} "
168+
)
127169

128170
def set_template_text(self, template_parameter, value):
129-
""" If you want to change a default value for customising your invoice template, call this method """
171+
"""If you want to change a default value for customising your invoice template, call this method"""
130172
if template_parameter in InvoiceGenerator.TEMPLATE_PARAMETERS:
131173
self.template[template_parameter] = value
132174
else:
133-
raise ValueError("The parameter {} is not a valid template parameter. See docs.".format(template_parameter))
175+
raise ValueError(
176+
"The parameter {} is not a valid template parameter. See docs.".format(
177+
template_parameter
178+
)
179+
)
134180

135181
def toggle_subtotal(self, tax="%", discounts=False, shipping=False):
136-
""" Toggle lines of subtotal """
137-
self.fields = {
138-
"tax": tax,
139-
"discounts": discounts,
140-
"shipping": shipping
141-
}
182+
"""Toggle lines of subtotal"""
183+
self.fields = {"tax": tax, "discounts": discounts, "shipping": shipping}
142184

143185

144186
class Item:
145-
""" Item object for an invoice """
187+
"""Item object for an invoice"""
146188

147189
def __init__(self, name, quantity, unit_cost, description=""):
148-
""" Object constructor """
190+
"""Object constructor"""
149191
self.name = name
150192
self.quantity = quantity
151193
self.unit_cost = unit_cost
152194
self.description = description
153195

154196

155197
class CustomField:
156-
""" Custom Field object for an invoice """
198+
"""Custom Field object for an invoice"""
157199

158200
def __init__(self, name, value):
159-
""" Object constructor """
201+
"""Object constructor"""
160202
self.name = name
161203
self.value = value

0 commit comments

Comments
 (0)