pygeobox 1.0.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.
pygeobox/usage.py ADDED
@@ -0,0 +1,211 @@
1
+ from urllib.parse import urlencode, urljoin
2
+ from typing import Optional, Dict, List, Union, TYPE_CHECKING
3
+ from datetime import datetime
4
+
5
+ from .base import Base
6
+ from .user import User
7
+ from .apikey import ApiKey
8
+ from .utils import clean_data
9
+ from .enums import UsageScale, UsageParam
10
+
11
+ if TYPE_CHECKING:
12
+ from . import GeoboxClient
13
+
14
+
15
+ class Usage(Base):
16
+
17
+ BASE_ENDPOINT = 'usage/'
18
+
19
+ def __init__(self,
20
+ api: 'GeoboxClient',
21
+ user: 'User'):
22
+ """
23
+ Constructs the necessary attributes for the Usage object.
24
+
25
+ Args:
26
+ api (Api): The API instance.
27
+ user (User): the user usage object.
28
+ """
29
+ self.api = api
30
+ self.user = user
31
+
32
+
33
+ def __repr__(self) -> str:
34
+ """
35
+ Return a string representation of the Usage object.
36
+
37
+ Returns:
38
+ str: A string representation of the Usage object.
39
+ """
40
+ return f"Usage(user={self.user})"
41
+
42
+
43
+ @classmethod
44
+ def get_api_usage(cls,
45
+ api: 'GeoboxClient',
46
+ resource: Union['User', 'ApiKey'],
47
+ scale: 'UsageScale',
48
+ param: 'UsageParam',
49
+ from_date: 'datetime' = None,
50
+ to_date: 'datetime' = None,
51
+ days_before_now: int = None,
52
+ limit: int = None) -> List:
53
+ """
54
+ Get the api usage of a user
55
+
56
+ Args:
57
+ api (GeoboxClient): The GeoboxClient instance for making requests.
58
+ resource (User | ApiKey): User or ApiKey object.
59
+ scale (UsageScale): the scale of the report.
60
+ param (UsageParam): traffic or calls.
61
+ from_date (datetime, optional): datetime object in this format: "%Y-%m-%dT%H:%M:%S".
62
+ to_date (datetime, optional): datetime object in this format: "%Y-%m-%dT%H:%M:%S".
63
+ days_before_now (int, optional): number of days befor now.
64
+ limit (int, optional): Number of items to return. default is 10.
65
+
66
+ Raises:
67
+ ValueError: one of days_before_now or from_date/to_date parameters must have value
68
+ ValueError: resource must be a 'user' or 'apikey' object
69
+
70
+ Returns:
71
+ List: usage report
72
+
73
+ Example:
74
+ >>> from geobox import GeoboxClient
75
+ >>> from geobox.usage import Usage
76
+ >>> client = GeoboxClient()
77
+ >>> user = client.get_user() # gets current user
78
+ >>> usage = Usage.get_api_usage(client,
79
+ ... resource=user,
80
+ ... scale=UsageScale.Day,
81
+ ... param=UsageParam.Calls,
82
+ ... days_before_now=5)
83
+ or
84
+ >>> usage = client.get_api_usage(resource=user,
85
+ ... scale=UsageScale.Day,
86
+ ... param=UsageParam.Calls,
87
+ ... days_before_now=5)
88
+ """
89
+ if not(from_date and to_date) and not days_before_now:
90
+ raise ValueError("one of days_before_now or from_date/to_date parameters must have value")
91
+
92
+ params = {}
93
+ if isinstance(resource, User):
94
+ params['eid'] = resource.user_id
95
+ elif isinstance(resource, ApiKey):
96
+ params['eid'] = resource.key
97
+ else:
98
+ raise ValueError("resource must be a 'user' or 'apikey' object")
99
+
100
+ params = clean_data({**params,
101
+ 'scale': scale.value if scale else None,
102
+ 'param': param.value if param else None,
103
+ 'from_date': from_date.strftime("%Y-%m-%dT%H:%M:%S.%f") if from_date else None,
104
+ 'to_date': to_date.strftime("%Y-%m-%dT%H:%M:%S.%f") if to_date else None,
105
+ 'days_before_now': days_before_now,
106
+ 'limit': limit
107
+ })
108
+ query_strings = urlencode(params)
109
+ endpoint = f"{cls.BASE_ENDPOINT}api?{query_strings}"
110
+
111
+ return api.get(endpoint)
112
+
113
+
114
+ @classmethod
115
+ def get_process_usage(cls,
116
+ api: 'GeoboxClient',
117
+ user_id: int = None,
118
+ from_date: datetime = None,
119
+ to_date: datetime = None,
120
+ days_before_now: int = None) -> float:
121
+ """
122
+ Get process usage of a user in seconds
123
+
124
+ Args:
125
+ api (GeoboxClient): The GeoboxClient instance for making requests.
126
+ user_id (int, optional): the id of the user. leave blank to get the current user report.
127
+ from_date (datetime, optional): datetime object in this format: "%Y-%m-%dT%H:%M:%S".
128
+ to_date (datetime, optional): datetime object in this format: "%Y-%m-%dT%H:%M:%S".
129
+ days_before_now (int, optional): number of days befor now.
130
+
131
+ Raises:
132
+ ValueError: one of days_before_now or from_date/to_date parameters must have value
133
+
134
+ Returns:
135
+ float: process usage of a user in seconds
136
+
137
+ Example:
138
+ >>> from geobox import GeoboxClient
139
+ >>> from geobox.usage import Usage
140
+ >>> client = GeoboxClient()
141
+ >>> process_usage = Usage.get_process_usage(client, days_before_now=5)
142
+ or
143
+ >>> process_usage = client.get_process_usage(days_before_now=5)
144
+ """
145
+ if not(from_date and to_date) and not days_before_now:
146
+ raise ValueError("one of days_before_now or from_date/to_date parameters must have value")
147
+
148
+ params = clean_data({
149
+ 'user_id': user_id if user_id else None,
150
+ 'from_date': from_date.strftime("%Y-%m-%dT%H:%M:%S.%f") if from_date else None,
151
+ 'to_date': to_date.strftime("%Y-%m-%dT%H:%M:%S.%f") if to_date else None,
152
+ 'days_before_now': days_before_now
153
+ })
154
+ query_strings = urlencode(params)
155
+ endpoint = f"{cls.BASE_ENDPOINT}process?{query_strings}"
156
+ return api.get(endpoint)
157
+
158
+
159
+ @classmethod
160
+ def get_usage_summary(cls, api: 'GeoboxClient', user_id: int = None) -> Dict:
161
+ """
162
+ Get the usage summary of a user
163
+
164
+ Args:
165
+ api (GeoboxClient): The API instance.
166
+ user_id (int, optional): the id of the user. leave blank to get the current user report.
167
+
168
+ Returns:
169
+ Dict: the usage summery of the users
170
+
171
+ Returns:
172
+ >>> from geobox import GeoboxClient
173
+ >>> from geobox.usage import Usage
174
+ >>> client = GeoboxClient()
175
+ >>> usage_summary = Usage.get_usage_summary(client)
176
+ or
177
+ >>> usage_summary = client.get_usage_summary()
178
+ """
179
+ params = clean_data({
180
+ 'user_id': user_id if user_id else None
181
+ })
182
+ query_strings = urlencode(params)
183
+ endpoint = f"{cls.BASE_ENDPOINT}summary?{query_strings}"
184
+ return api.get(endpoint)
185
+
186
+
187
+ @classmethod
188
+ def update_usage(cls, api: 'GeoboxClient', user_id: int = None) -> Dict:
189
+ """
190
+ Update usage of a user
191
+
192
+ Args:
193
+ api (GeoboxClient): The API instance.
194
+ user_id (int, optional): the id of the user. leave blank to get the current user report.
195
+
196
+ Returns:
197
+ Dict: the updated data
198
+
199
+ Example:
200
+ >>> from geobox import GeoboxClient
201
+ >>> from geobox.usage import Usage
202
+ >>> client = GeoboxClient()
203
+ >>> Usage.update_usage(client)
204
+ or
205
+ >>> client.update_usage()
206
+ """
207
+ data = clean_data({
208
+ 'user_id': user_id if user_id else None
209
+ })
210
+ endpoint = f"{cls.BASE_ENDPOINT}update"
211
+ return api.post(endpoint, payload=data)
pygeobox/user.py ADDED
@@ -0,0 +1,424 @@
1
+ from typing import List, Any, TYPE_CHECKING, Union, Dict
2
+ from urllib.parse import urlencode, urljoin
3
+
4
+ from .base import Base
5
+ from .utils import clean_data
6
+ from .enums import UserRole, UserStatus
7
+ from .plan import Plan
8
+
9
+ if TYPE_CHECKING:
10
+ from . import GeoboxClient
11
+
12
+
13
+ class User(Base):
14
+
15
+ BASE_ENDPOINT: str = 'users/'
16
+
17
+ def __init__(self, api: 'GeoboxClient', user_id: int, data: dict = {}) -> None:
18
+ """
19
+ Initialize a User instance.
20
+
21
+ Args:
22
+ api (GeoboxClient): The GeoboxClient instance for making requests.
23
+ user_id (int): the id of the user
24
+ data (Dict): The data of the user.
25
+ """
26
+ super().__init__(api, data=data)
27
+ self.user_id = user_id
28
+ self.endpoint = urljoin(self.BASE_ENDPOINT, f'{self.user_id}/') if self.user_id else 'me'
29
+
30
+
31
+ def __repr__(self) -> str:
32
+ """
33
+ Return a string representation of the User instance.
34
+
35
+ Returns:
36
+ str: A string representation of the User instance.
37
+ """
38
+ return f'User(first_name={self.first_name}, last_name={self.last_name})'
39
+
40
+
41
+ @property
42
+ def status(self) -> 'UserStatus':
43
+ """
44
+ User status Property
45
+
46
+ Returns:
47
+ UserStatus: the user status
48
+ """
49
+ return UserStatus(self.data.get('status')) if self.data.get('status') else None
50
+
51
+
52
+ @property
53
+ def plan(self) -> 'Plan':
54
+ """
55
+ User plan Property
56
+
57
+ Returns:
58
+ Plan: the plan object
59
+ """
60
+ plan = self.data.get('plan', {})
61
+ return Plan(self.api, plan.get('id'), plan) if plan else None
62
+
63
+
64
+ @classmethod
65
+ def get_users(cls, api: 'GeoboxClient', **kwargs) -> Union[List['User'], int]:
66
+ """
67
+ Retrieves a list of users (Permission Required)
68
+
69
+ Args:
70
+ api (GeoboxClient): The API instance.
71
+
72
+ Keyword Args:
73
+ status (UserStatus): the status of the users filter.
74
+ q (str): query filter based on OGC CQL standard. e.g. "field1 LIKE '%GIS%' AND created_at > '2021-01-01'"
75
+ search (str): search term for keyword-based searching among search_fields or all textual fields if search_fields does not have value. NOTE: if q param is defined this param will be ignored.
76
+ search_fields (str): comma separated list of fields for searching.
77
+ order_by (str): comma separated list of fields for sorting results [field1 A|D, field2 A|D, …]. e.g. name A, type D. NOTE: "A" denotes ascending order and "D" denotes descending order.
78
+ return_count (bool): Whether to return total count. default is False.
79
+ skip (int): Number of items to skip. default is 0.
80
+ limit (int): Number of items to return. default is 10.
81
+ user_id (int): Specific user. privileges required.
82
+ shared (bool): Whether to return shared maps. default is False.
83
+
84
+ Returns:
85
+ List[User] | int: list of users or the count number.
86
+
87
+ Example:
88
+ >>> from geobox import Geoboxclient
89
+ >>> from geobox.user import User
90
+ >>> client = GeoboxClient()
91
+ >>> users = User.get_users(client)
92
+ or
93
+ >>> users = client.get_users()
94
+ """
95
+ params = {
96
+ 'f': 'json',
97
+ 'q': kwargs.get('q'),
98
+ 'search': kwargs.get('search'),
99
+ 'search_fields': kwargs.get('search_fields'),
100
+ 'order_by': kwargs.get('order_by'),
101
+ 'return_count': kwargs.get('return_count', False),
102
+ 'skip': kwargs.get('skip', 0),
103
+ 'limit': kwargs.get('limit', 10),
104
+ 'user_id': kwargs.get('user_id'),
105
+ 'shared': kwargs.get('shared', False)
106
+ }
107
+ return super()._get_list(api, cls.BASE_ENDPOINT, params, factory_func=lambda api, item: User(api, item['id'], item))
108
+
109
+
110
+
111
+ @classmethod
112
+ def create_user(cls,
113
+ api: 'GeoboxClient',
114
+ username: str,
115
+ email: str,
116
+ password: str,
117
+ role: 'UserRole',
118
+ first_name: str,
119
+ last_name: str,
120
+ mobile: str,
121
+ status: 'UserStatus') -> 'User':
122
+ """
123
+ Create a User (Permission Required)
124
+
125
+ Args:
126
+ api (GeoboxClient): The GeoboxClient instance for making requests.
127
+ username (str): the username of the user.
128
+ email (str): the email of the user.
129
+ password (str): the password of the user.
130
+ role (UserRole): the role of the user.
131
+ first_name (str): the firstname of the user.
132
+ last_name (str): the lastname of the user.
133
+ mobile (str): the mobile number of the user. e.g. "+98 9120123456".
134
+ status (UserStatus): the status of the user.
135
+
136
+ Returns:
137
+ User: the user object.
138
+
139
+ Example:
140
+ >>> from geobox import GeoboxClient
141
+ >>> from geobox.user import User
142
+ >>> client = GeoboxClient()
143
+ >>> user = User.create_user(client,
144
+ ... username="user1",
145
+ ... email="user1@example.com",
146
+ ... password="P@ssw0rd",
147
+ ... role=UserRole.ACCOUNT_ADMIN,
148
+ ... first_name="user 1",
149
+ ... last_name="user 1",
150
+ ... mobile="+98 9120123456",
151
+ ... status=UserStatus.ACTIVE)
152
+ or
153
+ >>> user = client.create_user(username="user1",
154
+ ... email="user1@example.com",
155
+ ... password="P@ssw0rd",
156
+ ... role=UserRole.ACCOUNT_ADMIN,
157
+ ... first_name="user 1",
158
+ ... last_name="user 1",
159
+ ... mobile="+98 9120123456",
160
+ ... status=UserStatus.ACTIVE)
161
+ """
162
+ data = {
163
+ "username": username,
164
+ "email": email,
165
+ "password": password,
166
+ "role": role.value,
167
+ "first_name": first_name,
168
+ "last_name": last_name,
169
+ "mobile": mobile,
170
+ "status": status.value
171
+ }
172
+ return super()._create(api, cls.BASE_ENDPOINT, data, factory_func=lambda api, item: User(api, item['id'], item))
173
+
174
+
175
+
176
+ @classmethod
177
+ def search_users(cls, api: 'GeoboxClient', search: str = None, skip: int = 0, limit: int = 10) -> List['User']:
178
+ """
179
+ Get list of users based on the search term.
180
+
181
+ Args:
182
+ api (GeoboxClient): The GeoboxClient instance for making requests.
183
+ search (str, optional): The Search Term.
184
+ skip (int, optional): Number of items to skip. default is 0.
185
+ limit (int, optional): Number of items to return. default is 10.
186
+
187
+ Returns:
188
+ List[User]: A list of User instances.
189
+
190
+ Example:
191
+ >>> from geobox import GeoboxClient
192
+ >>> from geobox.user import User
193
+ >>> client = GeoboxClient()
194
+ >>> users = User.get_users(client, search="John")
195
+ or
196
+ >>> users = client.get_users(search="John")
197
+ """
198
+ params = {
199
+ 'search': search,
200
+ 'skip': skip,
201
+ 'limit': limit
202
+ }
203
+ endpoint = urljoin(cls.BASE_ENDPOINT, 'search/')
204
+ return super()._get_list(api, endpoint, params, factory_func=lambda api, item: User(api, item['id'], item))
205
+
206
+
207
+ @classmethod
208
+ def get_user(cls, api: 'GeoboxClient', user_id: int = 'me') -> 'User':
209
+ """
210
+ Get a user by its id (Permission Required)
211
+
212
+ Args:
213
+ api (GeoboxClient): The GeoboxClient instance for making requests.
214
+ user_id (int, optional): Specific user. don't specify a user_id to get the current user.
215
+
216
+ Returns:
217
+ User: the user object.
218
+
219
+ Raises:
220
+ NotFoundError: If the user with the specified id is not found.
221
+
222
+ Example:
223
+ >>> from geobox import GeoboxClient
224
+ >>> from geobox.user import User
225
+ >>> client = GeoboxClient()
226
+ >>> user = User.get_user(client, user_id=1)
227
+ or
228
+ >>> user = client.get_user(user_id=1)
229
+
230
+ get the current user
231
+ >>> user = User.get_user(client)
232
+ or
233
+ >>> user = client.get_user()
234
+ """
235
+ params = {
236
+ 'f': 'json'
237
+ }
238
+ return super()._get_detail(api, cls.BASE_ENDPOINT, user_id, params, factory_func=lambda api, item: User(api, item['id'], item))
239
+
240
+
241
+ def update(self, **kwargs) -> Dict:
242
+ """
243
+ Update the user (Permission Required)
244
+
245
+ Keyword Args:
246
+ username (str)
247
+ email (str)
248
+ first_name (str)
249
+ last_name (str)
250
+ mobile (str): e.g. "+98 9120123456"
251
+ status (UserStatus)
252
+ role (UserRole)
253
+ plan (Plan)
254
+ expiration_date (str)
255
+
256
+ Returns:
257
+ Dict: updated data
258
+
259
+ Example:
260
+ >>> from geobox imoprt GeoboxClient
261
+ >>> from geobox.user import User
262
+ >>> client = GeoboxClient()
263
+ >>> user = User.get_user(client, user_id=1)
264
+ >>> user.update_user(status=UserStatus.PENDING)
265
+ """
266
+ data = {
267
+ "csrf_token": kwargs.get('csrf_token'),
268
+ "username": kwargs.get('username'),
269
+ "email": kwargs.get('email'),
270
+ "first_name": kwargs.get('first_name'),
271
+ "last_name": kwargs.get('last_name'),
272
+ "mobile": kwargs.get('mobile'),
273
+ "status": kwargs.get('status').value if kwargs.get('status') else None,
274
+ "role": kwargs.get('role').value if kwargs.get('role') else None,
275
+ "plan_id": kwargs.get('plan').id if kwargs.get('plan') else None,
276
+ "expiration_date": kwargs.get('expiration_date')
277
+ }
278
+ return super()._update(self.endpoint, data)
279
+
280
+
281
+ def delete(self) -> None:
282
+ """
283
+ Delete the user (Permission Required)
284
+
285
+ Returns:
286
+ None
287
+
288
+ Example:
289
+ >>> from geobox import GeoboxClient
290
+ >>> from geobox.user import User
291
+ >>> client = GeoboxClient()
292
+ >>> user = User.get_user(client, user_id=1)
293
+ >>> user.delete()
294
+ """
295
+ super().delete(self.endpoint)
296
+
297
+
298
+ def get_sessions(self, user_id: int = 'me') -> List['Session']:
299
+ """
300
+ Get a list of user available sessions (Permission Required)
301
+
302
+ Args:
303
+ user_id (int, optional): Specific user. don't specify user_id to get the current user.
304
+
305
+ Returns:
306
+ List[Session]: list of user sessions.
307
+
308
+ Example:
309
+ >>> from geobox import GeoboxClient
310
+ >>> from geobox.user import User
311
+ >>> client = GeoboxClient()
312
+ >>> user = User.get_user(client, user_id=1)
313
+ or
314
+ >>> user = client.get_user(user_id=1)
315
+
316
+ >>> user.get_user_sessions()
317
+ or
318
+ >>> client.get_user_sessions()
319
+ """
320
+ params = clean_data({
321
+ 'f': 'json'
322
+ })
323
+ query_string = urlencode(params)
324
+ if user_id != 'me':
325
+ user = self.get_user(self.api, user_id=user_id)
326
+ endpoint = f"{self.BASE_ENDPOINT}{user_id}/sessions/?{query_string}"
327
+ else:
328
+ user = self
329
+ endpoint = urljoin(self.endpoint, f'sessions/?{query_string}')
330
+
331
+ response = self.api.get(endpoint)
332
+ return [Session(item['uuid'], item, user) for item in response]
333
+
334
+
335
+ def change_password(self, new_password: str) -> None:
336
+ """
337
+ Change the user password (privileges required)
338
+
339
+ Args:
340
+ new_password (str): new password for the user.
341
+
342
+ Returns:
343
+ None
344
+
345
+ Example:
346
+ >>> from geobox import GeoboxClient
347
+ >>> from geobox.user import User
348
+ >>> client = GeoboxClient()
349
+ >>> user = client.get_user()
350
+ >>> user.change_password(new_password='user_new_password')
351
+ """
352
+ data = clean_data({
353
+ "new_password": new_password
354
+ })
355
+ endpoint = urljoin(self.endpoint, 'change-password')
356
+ self.api.post(endpoint, data, is_json=False)
357
+
358
+
359
+ def renew_plan(self) -> None:
360
+ """
361
+ Renew the user plan
362
+
363
+ Returns:
364
+ None
365
+
366
+ Example:
367
+ >>> from geobox import GeoboxClient
368
+ >>> user = client.get_user(user_id=1)
369
+ >>> user.renew_plan()
370
+ """
371
+ endpoint = urljoin(self.endpoint, 'renewPlan')
372
+ self.api.post(endpoint)
373
+
374
+
375
+
376
+
377
+ class Session(Base):
378
+ def __init__(self, uuid: str, data: Dict, user: 'User'):
379
+ """
380
+ Initialize a user session instance.
381
+
382
+ Args:
383
+ uuid (str): The unique identifier for the user session.
384
+ data (Dict): The data of the session.
385
+ user (User): the user instance.
386
+ """
387
+ self.uuid = uuid
388
+ self.data = data
389
+ self.user = user
390
+ self.endpoint = urljoin(self.user.endpoint, f'sessions/{self.uuid}')
391
+
392
+
393
+ def __repr__(self) -> str:
394
+ """
395
+ Return a string representation of the resource.
396
+
397
+ Returns:
398
+ str: A string representation of the Session object.
399
+ """
400
+ return f"Session(user={self.user}, agent='{self.agent}')"
401
+
402
+
403
+ def close(self) -> None:
404
+ """
405
+ Close the user session
406
+
407
+ Returns:
408
+ None
409
+
410
+ Example:
411
+ >>> from geobox import geoboxClient
412
+ >>> from geobox.user import User
413
+ >>> client = GeoboxClient()
414
+ >>> user = User.get_user(client) # without user_id parameter, it gets the current user
415
+ or
416
+ >>> user = client.get_user() # without user_id parameter, it gets the current user
417
+ >>> session = user.get_sessions()[0]
418
+ >>> session.close()
419
+ """
420
+ data = clean_data({
421
+ 'user_id': self.user.user_id,
422
+ 'session_uuid': self.uuid
423
+ })
424
+ self.user.api.post(self.endpoint, data)
pygeobox/utils.py ADDED
@@ -0,0 +1,44 @@
1
+ from urllib.parse import urlparse, parse_qs, urlencode, urlunparse
2
+
3
+
4
+ def clean_data(data: dict) -> dict:
5
+ """
6
+ Cleans the input data by removing keys with None values.
7
+
8
+ Args:
9
+ data (dict): The input data.
10
+
11
+ Returns:
12
+ dict: The cleaned data.
13
+ """
14
+ return {k: v for k, v in data.items() if v is not None}
15
+
16
+ def join_url_params(base_url: str, params: dict) -> str:
17
+ """
18
+ Join URL with parameters while preserving existing query parameters.
19
+
20
+ Args:
21
+ base_url (str): Base URL that may contain existing parameters
22
+ params (dict): New parameters to add
23
+
24
+ Returns:
25
+ str: URL with all parameters properly joined
26
+ """
27
+ # Parse the URL
28
+ parsed = urlparse(base_url)
29
+
30
+ # Get existing parameters
31
+ existing_params = parse_qs(parsed.query)
32
+
33
+ # Update with new parameters
34
+ existing_params.update(params)
35
+
36
+ # Reconstruct the URL
37
+ return urlunparse((
38
+ parsed.scheme,
39
+ parsed.netloc,
40
+ parsed.path,
41
+ parsed.params,
42
+ urlencode(existing_params, doseq=True),
43
+ parsed.fragment
44
+ ))