supersaas-api-client 2.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,221 @@
1
+ from .BaseApi import BaseApi
2
+ from ..Models.Appointment import Appointment
3
+ from ..Models.Slot import Slot
4
+
5
+
6
+ class Appointments(BaseApi):
7
+ def agenda(self, schedule_id, user, from_time=None, slot=False):
8
+ path = f"/agenda/{self._validate_id(schedule_id)}"
9
+ query = {
10
+ 'user': self._validate_present(user),
11
+ 'from': self._validate_datetime(from_time) if from_time else None
12
+ }
13
+ if slot:
14
+ query['slot'] = True
15
+ res = self.client.get(path, query)
16
+ return self.__map_slots_or_bookings(res)
17
+
18
+ def agenda_slots(self, schedule_id, user_id, from_time=None):
19
+ """Deprecated, will be removed in a future version. Use `agenda(..., slot=True)` instead."""
20
+ path = f"/agenda/{self._validate_id(schedule_id)}"
21
+ query = {
22
+ 'user': self._validate_present(user_id),
23
+ 'from': self._validate_datetime(from_time) if from_time else None,
24
+ 'slot': 'true'
25
+ }
26
+ res = self.client.get(path, query)
27
+ return self.__map_slots_or_bookings(res)
28
+
29
+ def available(self,
30
+ schedule_id,
31
+ from_time=None,
32
+ length_minutes=None,
33
+ resource=None,
34
+ full=None,
35
+ limit=None):
36
+ path = f"/free/{self._validate_id(schedule_id)}"
37
+ query = {
38
+ 'length':
39
+ self._validate_number(length_minutes) if length_minutes else None,
40
+ 'from': self._validate_datetime(from_time) if from_time else None,
41
+ 'resource': resource,
42
+ 'full': 'true' if full else None,
43
+ 'maxresults': self._validate_number(limit) if limit else None
44
+ }
45
+ res = self.client.get(path, query)
46
+ return self.__map_slots_or_bookings(res)
47
+
48
+ def list(self, schedule_id, form=None, start_time=None, limit=None, finish=None):
49
+ path = "/bookings"
50
+ query = {
51
+ 'schedule_id': self._validate_id(schedule_id),
52
+ 'form': 'true' if form else None,
53
+ 'start':
54
+ self._validate_datetime(start_time) if start_time else None,
55
+ 'limit': self._validate_number(limit) if limit else None,
56
+ 'finish':
57
+ self._validate_datetime(finish) if finish else None
58
+ }
59
+ res = self.client.get(path, query)
60
+ return self.__map_slots_or_bookings(res)
61
+
62
+ def get(self, schedule_id, appointment_id=None):
63
+ query = {'schedule_id': self._validate_id(schedule_id)}
64
+ path = f"/bookings/{self._validate_id(appointment_id)}"
65
+ res = self.client.get(path, query)
66
+ return Appointment(res)
67
+
68
+ def create(self, schedule_id, user_id, attributes,
69
+ form=None,
70
+ webhook=None):
71
+ path = "/bookings"
72
+ params = self.__create_update_params(schedule_id, attributes, form, webhook)
73
+ if user_id:
74
+ params['user_id'] = self._validate_id(user_id)
75
+ params['booking'] = dict(
76
+ filter(lambda item: item[1] is not None,
77
+ params['booking'].items()))
78
+ res = self.client.post(path, params)
79
+ return {'location': res}
80
+
81
+ def update(self, schedule_id, appointment_id, attributes, form=None, webhook=None):
82
+ """
83
+ Update an existing appointment/booking
84
+ """
85
+ path = f"/bookings/{self._validate_id(appointment_id)}"
86
+ params = self.__create_update_params(schedule_id, attributes, form, webhook)
87
+
88
+ # Attributes passed as {'booking': None} are sent as an empty booking
89
+ if params.get('booking') is None:
90
+ params['booking'] = {}
91
+
92
+ # Filter out None values from booking attributes
93
+ params['booking'] = {k: v for k, v in params['booking'].items() if v is not None}
94
+
95
+ res = self.client.put(path, params)
96
+ return Appointment(res)
97
+
98
+ def delete(self, schedule_id, appointment_id, webhook=None):
99
+ path = f'/bookings/{self._validate_id(appointment_id)}'
100
+ query = {'schedule_id': schedule_id, 'webhook': 'true' if webhook else None}
101
+ return self.client.delete(path, None, query)
102
+
103
+ def changes(self,
104
+ schedule_id,
105
+ from_time=None,
106
+ to=None,
107
+ slot=False,
108
+ user=None,
109
+ limit=None,
110
+ offset=None):
111
+ path = f"/changes/{self._validate_id(schedule_id)}"
112
+ params = self.__build_param({}, from_time, to, slot, user, limit,
113
+ offset)
114
+ res = self.client.get(path, params)
115
+ return self.__map_slots_or_bookings(res)
116
+
117
+ def changes_slots(self, schedule_id, from_time=None):
118
+ """Deprecated, will be removed in a future version. Use `changes(..., slot=True)` instead."""
119
+ path = f"/changes/{self._validate_id(schedule_id)}"
120
+ query = {
121
+ 'from': self._validate_datetime(from_time) if from_time else None,
122
+ 'slot': 'true'
123
+ }
124
+ res = self.client.get(path, query)
125
+ return self.__map_slots_or_bookings(res)
126
+
127
+ def range(self,
128
+ schedule_id,
129
+ today=False,
130
+ from_time=None,
131
+ to=None,
132
+ slot=False,
133
+ user=None,
134
+ resource_id=None,
135
+ service_id=None,
136
+ limit=None,
137
+ offset=None):
138
+ path = f"/range/{self._validate_id(schedule_id)}"
139
+ params = {}
140
+ params = self.__build_param(params, from_time, to, slot, user, limit,
141
+ offset, resource_id, service_id)
142
+ if today:
143
+ params['today'] = True
144
+ res = self.client.get(path, params)
145
+ return self.__map_slots_or_bookings(res)
146
+
147
+ def __create_update_params(self, schedule_id, attributes,
148
+ form=None,
149
+ webhook=None):
150
+ # Attributes may be passed already wrapped as {'booking': {...}}
151
+ if isinstance(attributes, dict) and 'booking' in attributes:
152
+ attributes = attributes['booking'] or {}
153
+ params = {
154
+ 'schedule_id': self._validate_id(schedule_id),
155
+ 'booking': {
156
+ 'start': attributes.get('start', None),
157
+ 'finish': attributes.get('finish', None),
158
+ 'email': attributes.get('email', None),
159
+ 'full_name': attributes.get('full_name', None),
160
+ 'address': attributes.get('address', None),
161
+ 'mobile': attributes.get('mobile', None),
162
+ 'phone': attributes.get('phone', None),
163
+ 'country': attributes.get('country', None),
164
+ 'field_1': attributes.get('field_1', None),
165
+ 'field_2': attributes.get('field_2', None),
166
+ 'field_1_r': attributes.get('field_1_r', None),
167
+ 'field_2_r': attributes.get('field_2_r', None),
168
+ 'super_field': attributes.get('super_field', None),
169
+ 'resource_id': attributes.get('resource_id', None),
170
+ 'slot_id': attributes.get('slot_id', None),
171
+ # description is for resource schedules, quantity (number of places) for capacity schedules,
172
+ # form_id attaches an existing form
173
+ 'description': attributes.get('description', None),
174
+ 'quantity': attributes.get('quantity', None),
175
+ 'form_id': attributes.get('form_id', None),
176
+ }
177
+ }
178
+ if form:
179
+ params['form'] = form
180
+ if webhook:
181
+ params['webhook'] = webhook
182
+ return params
183
+
184
+ def __map_slots_or_bookings(self, obj):
185
+ if isinstance(obj, list):
186
+ return [Appointment(attributes) for attributes in obj]
187
+ if 'slots' in obj:
188
+ return [Slot(attributes) for attributes in obj.get('slots', [])]
189
+ return [
190
+ Appointment(attributes)
191
+ for attributes in obj.get('bookings', [])
192
+ ]
193
+
194
+ def __build_param(self,
195
+ params,
196
+ from_time,
197
+ to,
198
+ slot,
199
+ user,
200
+ limit,
201
+ offset,
202
+ resource_id=None,
203
+ service_id=None):
204
+ if from_time:
205
+ params['from'] = self._validate_datetime(from_time)
206
+ if to:
207
+ params['to'] = self._validate_datetime(to)
208
+ if slot:
209
+ params['slot'] = True
210
+ if user or user == 0:
211
+ params['user'] = self._validate_user(user)
212
+ if limit:
213
+ params['limit'] = self._validate_number(limit)
214
+ if offset:
215
+ params['offset'] = self._validate_number(offset)
216
+ if resource_id is not None:
217
+ params['resource_id'] = self._validate_id(resource_id)
218
+ if service_id is not None:
219
+ params['service_id'] = self._validate_id(service_id)
220
+
221
+ return params
@@ -0,0 +1,69 @@
1
+ import re
2
+ from datetime import datetime
3
+ from SuperSaaS.Error import Error
4
+
5
+
6
+ class BaseApi:
7
+ INTEGER_REGEX = re.compile(r'\A[0-9]+\Z')
8
+ DATETIME_REGEX = re.compile(
9
+ r'\A\d{4}-\d{1,2}-\d{1,2}\s\d{1,2}:\d{1,2}:\d{1,2}\Z')
10
+ PROMOTION_REGEX = re.compile(r'\A[a-zA-Z0-9]+\Z')
11
+
12
+ def __init__(self, client):
13
+ self.client = client
14
+
15
+ def _validate_id(self, value):
16
+ return value
17
+
18
+ def _validate_number(self, value):
19
+ return self._validate_id(value)
20
+
21
+ def _validate_present(self, value):
22
+ if not value:
23
+ raise Error("Required parameter is missing.")
24
+ return value
25
+
26
+ def _validate_user(self, value):
27
+ if value is None:
28
+ return None
29
+
30
+ if not isinstance(value, (int, str)):
31
+ raise Error(f"Invalid user id parameter: {value}")
32
+ return value
33
+
34
+ def _validate_datetime(self, value):
35
+ if isinstance(value, datetime):
36
+ return value.strftime("%Y-%m-%d %H:%M:%S")
37
+ if isinstance(value, str) and self.DATETIME_REGEX.match(value):
38
+ return value
39
+ raise Error(
40
+ f"Invalid datetime parameter: {value}. Provide a Time object or formatted 'YYYY-DD-MM%HH:MM:SS' string."
41
+ )
42
+
43
+ def _validate_options(self, value, options):
44
+ if value in options:
45
+ return value
46
+ raise Error(
47
+ f"Invalid option parameter: {value}. Must be one of {', '.join(options)}."
48
+ )
49
+
50
+ def _validate_promotion(self, value):
51
+ if not isinstance(value, str) or not value or not re.match(
52
+ self.PROMOTION_REGEX, value):
53
+ raise Error(
54
+ 'Required parameter promotional code not found or contains other than alphanumeric characters.'
55
+ )
56
+ return value
57
+
58
+ def _validate_duplicate(self, value):
59
+ if not isinstance(value, str) or value not in ['ignore', 'raise']:
60
+ raise Error(
61
+ "Required parameter duplicate can only be 'ignore' or 'raise'."
62
+ )
63
+ return value
64
+
65
+ def _validate_notfound(self, value):
66
+ if not isinstance(value, str) or value not in ['error', 'ignore']:
67
+ raise Error(
68
+ "Required parameter notfound can only be 'error' or 'ignore'.")
69
+ return value
SuperSaaS/API/Forms.py ADDED
@@ -0,0 +1,31 @@
1
+ from .BaseApi import BaseApi
2
+ from ..Models.Form import Form
3
+ from ..Models.SuperForm import SuperForm
4
+
5
+
6
+ class Forms(BaseApi):
7
+
8
+ def get(self, form_id):
9
+ path = "/forms"
10
+ query = {'id': self._validate_id(form_id)}
11
+ res = self.client.get(path, query)
12
+ return Form(res)
13
+
14
+ def list(self, superform_id, from_time=None, user=None, limit=None, offset=None):
15
+ path = "/forms"
16
+ params = {'form_id': self._validate_id(superform_id)}
17
+ if from_time:
18
+ params['from'] = self._validate_datetime(from_time)
19
+ if user or user == 0:
20
+ params['user'] = self._validate_user(user)
21
+ if limit:
22
+ params['limit'] = self._validate_number(limit)
23
+ if offset:
24
+ params['offset'] = self._validate_number(offset)
25
+ res = self.client.get(path, params)
26
+ return [Form(attributes) for attributes in res]
27
+
28
+ def forms(self):
29
+ path = "/super_forms"
30
+ res = self.client.get(path)
31
+ return [SuperForm(attributes) for attributes in res]
@@ -0,0 +1,10 @@
1
+ from .BaseApi import BaseApi
2
+ from ..Models.Group import Group
3
+
4
+
5
+ class Groups(BaseApi):
6
+
7
+ def list(self):
8
+ path = '/groups'
9
+ res = self.client.get(path)
10
+ return [Group(attributes) for attributes in res]
SuperSaaS/API/Hooks.py ADDED
@@ -0,0 +1,23 @@
1
+ from .BaseApi import BaseApi
2
+
3
+
4
+ class Hooks(BaseApi):
5
+ """Manage webhooks (`/api/hooks`)."""
6
+
7
+ def list(self):
8
+ return self.client.get('/hooks')
9
+
10
+ def create(self, target_url, event, parent_id):
11
+ params = {
12
+ 'target_url': self._validate_present(target_url),
13
+ 'event': self._validate_present(event),
14
+ 'parent_id': self._validate_present(self._validate_id(parent_id))
15
+ }
16
+ return self.client.post('/hooks', params)
17
+
18
+ def delete(self, hook_id, parent_id):
19
+ query = {
20
+ 'id': self._validate_present(self._validate_id(hook_id)),
21
+ 'parent_id': self._validate_present(self._validate_id(parent_id))
22
+ }
23
+ return self.client.delete('/hooks', None, query)
@@ -0,0 +1,32 @@
1
+ from .BaseApi import BaseApi
2
+ from ..Models.Promotion import Promotion
3
+
4
+
5
+ class Promotions(BaseApi):
6
+
7
+ def list(self, limit=None, offset=None):
8
+ path = '/promotions'
9
+ params = {
10
+ 'limit':
11
+ self._validate_number(limit) if limit is not None else None,
12
+ 'offset':
13
+ self._validate_number(offset) if offset is not None else None
14
+ }
15
+ # Filter out None values, so Client.request does not append an empty "?" to the URL
16
+ params = {k: v for k, v in params.items() if v is not None}
17
+ res = self.client.get(path, params)
18
+ return [Promotion(attributes) for attributes in res]
19
+
20
+ def promotion(self, promotion_code):
21
+ path = '/promotions'
22
+ query = {'promotion_code': self._validate_promotion(promotion_code)}
23
+ res = self.client.get(path, query)
24
+ return [Promotion(attributes) for attributes in res]
25
+
26
+ def duplicate_promotion_code(self, promotion_code, template_code):
27
+ path = '/promotions'
28
+ query = {
29
+ 'id': self._validate_promotion(promotion_code),
30
+ 'template_code': self._validate_promotion(template_code)
31
+ }
32
+ self.client.post(path, query)
@@ -0,0 +1,29 @@
1
+ from .BaseApi import BaseApi
2
+ from ..Models.Schedule import Schedule
3
+ from ..Models.Resource import Resource
4
+ from ..Models.FieldList import FieldList
5
+
6
+ class Schedules(BaseApi):
7
+
8
+ def list(self):
9
+ path = "/schedules"
10
+ res = self.client.get(path)
11
+ return [Schedule(attributes) for attributes in res]
12
+
13
+ def resources(self, schedule_id):
14
+ query = {'schedule_id': self._validate_id(schedule_id)}
15
+ path = "/resources"
16
+ res = self.client.get(path, query)
17
+ return [Resource(attributes) for attributes in res]
18
+
19
+ def field_list(self, schedule_id):
20
+ path = '/field_list'
21
+ query = {'schedule_id': self._validate_id(schedule_id)}
22
+ res = self.client.get(path, query)
23
+ return [FieldList(attributes) for attributes in res]
24
+
25
+ def input_field_list(self, schedule_id):
26
+ path = '/input_field_list'
27
+ query = {'schedule_id': self._validate_id(schedule_id)}
28
+ res = self.client.get(path, query)
29
+ return [FieldList(attributes) for attributes in res]
SuperSaaS/API/Slots.py ADDED
@@ -0,0 +1,63 @@
1
+ from .BaseApi import BaseApi
2
+ from ..Models.Slot import Slot
3
+
4
+
5
+ class Slots(BaseApi):
6
+ """Manage slots of capacity type schedules (`/api/slots`)."""
7
+
8
+ ATTRIBUTES = ('start', 'finish', 'title', 'description', 'location',
9
+ 'capacity', 'price', 'color')
10
+
11
+ def list(self, schedule_id, start_time=None, finish=None, limit=None, offset=None):
12
+ path = "/slots"
13
+ query = {
14
+ 'schedule_id': self._validate_id(schedule_id),
15
+ 'start':
16
+ self._validate_datetime(start_time) if start_time else None,
17
+ 'finish': self._validate_datetime(finish) if finish else None,
18
+ 'limit': self._validate_number(limit) if limit else None,
19
+ 'offset': self._validate_number(offset) if offset else None
20
+ }
21
+ res = self.client.get(path, query)
22
+ return [Slot(attributes) for attributes in res]
23
+
24
+ def get(self, schedule_id, slot_id):
25
+ query = {'schedule_id': self._validate_id(schedule_id)}
26
+ path = self.__slot_path(slot_id)
27
+ res = self.client.get(path, query)
28
+ return Slot(res)
29
+
30
+ def create(self, schedule_id, attributes):
31
+ path = "/slots"
32
+ query = {'schedule_id': self._validate_id(schedule_id)}
33
+ res = self.client.post(path, self.__params(attributes), query)
34
+ return {'location': res}
35
+
36
+ def update(self, schedule_id, slot_id, attributes):
37
+ path = self.__slot_path(slot_id)
38
+ query = {'schedule_id': self._validate_id(schedule_id)}
39
+ return self.client.put(path, self.__params(attributes), query)
40
+
41
+ def delete(self, schedule_id, slot_id):
42
+ path = self.__slot_path(slot_id)
43
+ query = {'schedule_id': self._validate_id(schedule_id)}
44
+ return self.client.delete(path, None, query)
45
+
46
+ def __slot_path(self, slot_id):
47
+ return f"/slots/{self._validate_present(self._validate_id(slot_id))}"
48
+
49
+ def __params(self, attributes):
50
+ # Don't wrap attributes in 'slot' if they're already wrapped
51
+ if isinstance(attributes, dict) and 'slot' in attributes:
52
+ attributes = attributes['slot']
53
+ attributes = attributes or {}
54
+ slot = {}
55
+ for key in self.ATTRIBUTES:
56
+ value = attributes.get(key)
57
+ if key in ('start', 'finish') and value is not None:
58
+ value = self._validate_datetime(value)
59
+ # capacity: '-' means unlimited and 0 means no places. None values aren't sent, so
60
+ # capacity=None leaves the capacity unchanged instead of making it unlimited.
61
+ if value is not None:
62
+ slot[key] = value
63
+ return {'slot': slot}
SuperSaaS/API/Users.py ADDED
@@ -0,0 +1,98 @@
1
+ from .BaseApi import BaseApi
2
+ from ..Models.User import User
3
+ from ..Models.FieldList import FieldList
4
+
5
+
6
+ class Users(BaseApi):
7
+
8
+ def get(self, user_id=None, form=None):
9
+ path = self.__user_path(user_id)
10
+ query = {
11
+ 'form': 'true' if form else None
12
+ }
13
+ res = self.client.get(path, query)
14
+ return User(res)
15
+
16
+ def list(self, form=None, limit=None, offset=None):
17
+ path = self.__user_path()
18
+ query = {
19
+ 'form': 'true' if form else None,
20
+ 'limit': self._validate_number(limit) if limit else None,
21
+ 'offset': self._validate_number(offset) if offset else None
22
+ }
23
+ res = self.client.get(path, query)
24
+ return [User(attributes) for attributes in res]
25
+
26
+ def create(self, attributes, user_id=None, webhook=None, duplicate=None):
27
+ path = self.__user_path(user_id)
28
+ query = {'webhook': 'true' if webhook else None}
29
+ if duplicate:
30
+ query['duplicate'] = self._validate_duplicate(duplicate)
31
+ params = self.__params_for_update_create(attributes)
32
+ params['user'] = dict(
33
+ filter(lambda item: item[1] is not None, params['user'].items()))
34
+ res = self.client.post(path, params, query)
35
+ return {'location': res}
36
+
37
+ def update(self, user_id, attributes, webhook=None, notfound=None):
38
+ path = self.__user_path(user_id)
39
+ query = {'webhook': 'true' if webhook else None}
40
+ if notfound:
41
+ query['notfound'] = self._validate_notfound(notfound)
42
+ params = self.__params_for_update_create(attributes)
43
+ params['user'] = dict(
44
+ filter(lambda item: item[1] is not None, params['user'].items()))
45
+ return self.client.put(path, params, query)
46
+
47
+ def delete(self, user_id, webhook=None):
48
+ path = self.__user_path(self._validate_id(user_id))
49
+ query = {'webhook': 'true'} if webhook else None
50
+ return self.client.delete(path, None, query)
51
+
52
+ def field_list(self):
53
+ path = '/field_list'
54
+ res = self.client.get(path)
55
+ return [FieldList(attributes) for attributes in res]
56
+
57
+ def input_field_list(self):
58
+ path = '/input_field_list'
59
+ res = self.client.get(path)
60
+ return [FieldList(attributes) for attributes in res]
61
+
62
+ def __user_path(self, user_id=None):
63
+ return f"/users/{user_id}" if user_id else "/users"
64
+
65
+ def __params_for_update_create(self, attributes):
66
+ return {
67
+ 'user': {
68
+ 'name':
69
+ self._validate_present(attributes.get('name', ''))
70
+ if attributes.get('name', '') else None,
71
+ 'email':
72
+ attributes.get('email', None),
73
+ 'password':
74
+ attributes.get('password', None),
75
+ 'full_name':
76
+ attributes.get('full_name', None),
77
+ 'address':
78
+ attributes.get('address', None),
79
+ 'mobile':
80
+ attributes.get('mobile', None),
81
+ 'phone':
82
+ attributes.get('phone', None),
83
+ 'country':
84
+ attributes.get('country', None),
85
+ 'field_1':
86
+ attributes.get('field_1', None),
87
+ 'field_2':
88
+ attributes.get('field_2', None),
89
+ 'super_field':
90
+ attributes.get('super_field', None),
91
+ 'credit':
92
+ attributes.get('credit', None),
93
+ 'role':
94
+ attributes.get('role', None),
95
+ 'group':
96
+ attributes.get('group', None)
97
+ }
98
+ }
@@ -0,0 +1,14 @@
1
+ __all__ = [
2
+ "BaseApi", "Appointments", "Slots", "Forms", "Users", "Schedules", "Promotions",
3
+ "Groups", "Hooks"
4
+ ]
5
+
6
+ from .BaseApi import BaseApi
7
+ from .Appointments import Appointments
8
+ from .Slots import Slots
9
+ from .Forms import Forms
10
+ from .Users import Users
11
+ from .Schedules import Schedules
12
+ from .Promotions import Promotions
13
+ from .Groups import Groups
14
+ from .Hooks import Hooks