2
2
import json
3
3
import pytz
4
4
import locale
5
+ from locale import getlocale
6
+ from tzlocal import get_localzone
5
7
6
8
from datetime import datetime
7
9
8
10
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
+
9
33
class InvoiceGenerator :
10
- """ API Object for Invoice-Generator tool - https://invoice-generator.com/ """
34
+ """API Object for Invoice-Generator tool - https://invoice-generator.com/"""
11
35
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"
17
36
# Below are the default template parameters that can be changed (see https://github.com/Invoiced/invoice-generator-api/)
18
37
TEMPLATE_PARAMETERS = [
19
38
"header" ,
@@ -39,32 +58,37 @@ class InvoiceGenerator:
39
58
"notes_title" ,
40
59
]
41
60
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"""
58
81
self .logo = logo
82
+ self .config = config
59
83
self .sender = sender
60
84
self .to = to
61
85
self .ship_to = ship_to
62
86
self .number = number
63
87
self .currency = currency
64
88
self .custom_fields = []
65
- self .date = date
89
+ self .date = date or datetime . now ( tz = pytz . timezone ( self . config . timezone ))
66
90
self .payment_terms = payments_terms
67
- self .due_date = due_date
91
+ self .due_date : datetime | None = due_date
68
92
self .items = []
69
93
self .fields = {"tax" : "%" , "discounts" : False , "shipping" : False }
70
94
self .discounts = discounts
@@ -83,79 +107,97 @@ def _to_json(self):
83
107
We are also resetting the two list of Objects items and custom_fields so that it can be JSON serializable
84
108
Finally, we are handling template customization with its dict
85
109
"""
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__
97
122
for template_parameter , value in self .template .items ():
98
123
object_dict [template_parameter ] = value
99
- object_dict .pop ('template' )
124
+ object_dict .pop ("template" )
125
+ object_dict .pop ("config" )
100
126
return json .dumps (object_dict )
101
127
102
128
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
+ )
117
148
118
149
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"""
120
151
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
+ )
122
163
if response .status_code == 200 :
123
- open (file_path , 'wb' ).write (response .content )
164
+ open (file_path , "wb" ).write (response .content )
124
165
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
+ )
127
169
128
170
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"""
130
172
if template_parameter in InvoiceGenerator .TEMPLATE_PARAMETERS :
131
173
self .template [template_parameter ] = value
132
174
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
+ )
134
180
135
181
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 }
142
184
143
185
144
186
class Item :
145
- """ Item object for an invoice """
187
+ """Item object for an invoice"""
146
188
147
189
def __init__ (self , name , quantity , unit_cost , description = "" ):
148
- """ Object constructor """
190
+ """Object constructor"""
149
191
self .name = name
150
192
self .quantity = quantity
151
193
self .unit_cost = unit_cost
152
194
self .description = description
153
195
154
196
155
197
class CustomField :
156
- """ Custom Field object for an invoice """
198
+ """Custom Field object for an invoice"""
157
199
158
200
def __init__ (self , name , value ):
159
- """ Object constructor """
201
+ """Object constructor"""
160
202
self .name = name
161
203
self .value = value
0 commit comments