MambuPy 2.3.1__py3-none-any.whl → 2.3.3__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.
- MambuPy/__init__.py +1 -1
- MambuPy/api/classes.py +18 -19
- MambuPy/api/connector/rest.py +32 -9
- MambuPy/api/entities.py +19 -12
- MambuPy/api/mambuloan.py +3 -2
- MambuPy/api/mambustruct.py +8 -2
- MambuPy/mambuutil.py +69 -9
- mambupy/__init__.py +1 -1
- mambupy/api/classes.py +18 -19
- mambupy/api/connector/rest.py +32 -9
- mambupy/api/entities.py +19 -12
- mambupy/api/mambuloan.py +3 -2
- mambupy/api/mambustruct.py +8 -2
- mambupy/mambuutil.py +69 -9
- {mambupy-2.3.1.dist-info → mambupy-2.3.3.dist-info}/METADATA +2 -1
- {mambupy-2.3.1.dist-info → mambupy-2.3.3.dist-info}/RECORD +19 -19
- {mambupy-2.3.1.dist-info → mambupy-2.3.3.dist-info}/WHEEL +1 -1
- {mambupy-2.3.1.dist-info → mambupy-2.3.3.dist-info}/licenses/LICENSE +0 -0
- {mambupy-2.3.1.dist-info → mambupy-2.3.3.dist-info}/top_level.txt +0 -0
MambuPy/__init__.py
CHANGED
MambuPy/api/classes.py
CHANGED
|
@@ -31,32 +31,31 @@ class MambuMapObj:
|
|
|
31
31
|
for key, val in kwargs.items():
|
|
32
32
|
self._attrs[key] = val
|
|
33
33
|
|
|
34
|
-
def
|
|
34
|
+
def __getattr__(self, name):
|
|
35
35
|
"""Object-like get attribute
|
|
36
36
|
|
|
37
37
|
When accessing an attribute, tries to find it in the _attrs
|
|
38
38
|
dictionary, so now MambuMapObj may act not only as a dict-like
|
|
39
39
|
structure, but as a full object-like too (this is the getter
|
|
40
40
|
side).
|
|
41
|
+
|
|
42
|
+
Implemented as __getattr__ (not __getattribute__) so it only runs
|
|
43
|
+
after the normal attribute lookup already failed: a real
|
|
44
|
+
attribute/method always wins, and successful accesses keep the
|
|
45
|
+
native fast path instead of being intercepted on every access.
|
|
41
46
|
"""
|
|
42
|
-
try
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
#
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return object.__getattribute__(self, name)
|
|
55
|
-
# all else, read the property from the _attrs dict, but with a . syntax
|
|
56
|
-
# if a cf_class, just return its value
|
|
57
|
-
if _attrs[name].__class__.__name__ == self._cf_class.__name__:
|
|
58
|
-
return _attrs[name]["value"]
|
|
59
|
-
return _attrs[name]
|
|
47
|
+
# try to read the _attrs property
|
|
48
|
+
_attrs = object.__getattribute__(self, "_attrs")
|
|
49
|
+
if isinstance(_attrs, list) or name not in _attrs:
|
|
50
|
+
# magic won't happen when not a dict-like MambuMapObj or
|
|
51
|
+
# when _attrs has not the 'name' key: this is a genuine
|
|
52
|
+
# missing attribute, so AttributeError raises by default
|
|
53
|
+
raise AttributeError(name)
|
|
54
|
+
# all else, read the property from the _attrs dict, but with a . syntax
|
|
55
|
+
# if a cf_class, just return its value
|
|
56
|
+
if _attrs[name].__class__.__name__ == self._cf_class.__name__:
|
|
57
|
+
return _attrs[name]["value"]
|
|
58
|
+
return _attrs[name]
|
|
60
59
|
|
|
61
60
|
def __setattr__(self, name, value):
|
|
62
61
|
"""Object-like set attribute
|
MambuPy/api/connector/rest.py
CHANGED
|
@@ -9,11 +9,15 @@ Currently supports REST.
|
|
|
9
9
|
|
|
10
10
|
import base64
|
|
11
11
|
import copy
|
|
12
|
+
import datetime
|
|
13
|
+
import enum
|
|
12
14
|
import json
|
|
13
15
|
import mimetypes
|
|
16
|
+
import orjson
|
|
14
17
|
import os
|
|
15
18
|
import re
|
|
16
19
|
import uuid
|
|
20
|
+
from decimal import Decimal
|
|
17
21
|
|
|
18
22
|
import requests
|
|
19
23
|
from requests_toolbelt.multipart.encoder import MultipartEncoder
|
|
@@ -43,9 +47,27 @@ from MambuPy.mambuutil import (
|
|
|
43
47
|
logger = setup_logging(__name__)
|
|
44
48
|
|
|
45
49
|
|
|
50
|
+
class _MambuJSONEncoder(json.JSONEncoder):
|
|
51
|
+
"""JSON encoder that handles Python types not natively serializable by json.dumps."""
|
|
52
|
+
|
|
53
|
+
def default(self, obj):
|
|
54
|
+
if isinstance(obj, (datetime.datetime, datetime.date)):
|
|
55
|
+
return obj.isoformat()
|
|
56
|
+
if isinstance(obj, enum.Enum):
|
|
57
|
+
return obj.value
|
|
58
|
+
if isinstance(obj, Decimal):
|
|
59
|
+
return float(obj)
|
|
60
|
+
return super().default(obj)
|
|
61
|
+
|
|
62
|
+
|
|
46
63
|
def _configure_retry_strategy(session, retries=5):
|
|
47
64
|
"""Configure retry strategy for a session.
|
|
48
65
|
|
|
66
|
+
El HTTPAdapter se construye con pool_maxsize=50 para que los flujos
|
|
67
|
+
que despachan multiples requests concurrentes contra Mambu (p.ej.
|
|
68
|
+
fan-out de aprobaciones, cargar_datos paralelo) no queden encolados
|
|
69
|
+
sobre el default de urllib3 de 10 conexiones por host.
|
|
70
|
+
|
|
49
71
|
Args:
|
|
50
72
|
session (requests.Session): The session to configure
|
|
51
73
|
retries (int, optional): Number of retries. Defaults to 5.
|
|
@@ -65,7 +87,11 @@ def _configure_retry_strategy(session, retries=5):
|
|
|
65
87
|
"PATCH",
|
|
66
88
|
],
|
|
67
89
|
)
|
|
68
|
-
adapter = HTTPAdapter(
|
|
90
|
+
adapter = HTTPAdapter(
|
|
91
|
+
max_retries=retry_strategy,
|
|
92
|
+
pool_connections=50,
|
|
93
|
+
pool_maxsize=50,
|
|
94
|
+
)
|
|
69
95
|
session.mount("https://", adapter)
|
|
70
96
|
session.mount("http://", adapter)
|
|
71
97
|
|
|
@@ -154,11 +180,8 @@ class MambuConnectorREST(MambuConnector, MambuConnectorReader, MambuConnectorWri
|
|
|
154
180
|
return params
|
|
155
181
|
|
|
156
182
|
def __request_data(self, data):
|
|
157
|
-
if data
|
|
158
|
-
|
|
159
|
-
data = json.dumps(data)
|
|
160
|
-
except TypeError:
|
|
161
|
-
data = data
|
|
183
|
+
if isinstance(data, (dict, list)):
|
|
184
|
+
data = json.dumps(data, cls=_MambuJSONEncoder)
|
|
162
185
|
return data
|
|
163
186
|
|
|
164
187
|
def __request(self, method, url, params=None, data=None, content_type=None):
|
|
@@ -211,7 +234,7 @@ url %s, params %s, data %s, headers %s",
|
|
|
211
234
|
if hasattr(resp, "content"): # pragma: no cover
|
|
212
235
|
logger.warning("HTTPError, resp content: %s", resp.content)
|
|
213
236
|
try:
|
|
214
|
-
content =
|
|
237
|
+
content = orjson.loads(resp.content.decode())
|
|
215
238
|
except ValueError:
|
|
216
239
|
# in case resp.content doesn't conforms to json
|
|
217
240
|
content = {
|
|
@@ -287,7 +310,7 @@ url %s, params %s, data %s, headers %s",
|
|
|
287
310
|
def __list_request_cat_response(self, list_resp, resp):
|
|
288
311
|
if list_resp == b"":
|
|
289
312
|
list_resp = resp
|
|
290
|
-
elif len(
|
|
313
|
+
elif len(orjson.loads(resp.decode())) > 0:
|
|
291
314
|
list_resp = list_resp[:-1] + b"," + resp[1:]
|
|
292
315
|
|
|
293
316
|
return list_resp
|
|
@@ -327,7 +350,7 @@ url %s, params %s, data %s, headers %s",
|
|
|
327
350
|
method, url, params=copy.copy(params), data=copy.copy(data)
|
|
328
351
|
)
|
|
329
352
|
|
|
330
|
-
jsonresp = list(
|
|
353
|
+
jsonresp = list(orjson.loads(resp.decode()))
|
|
331
354
|
if len(jsonresp) < limit:
|
|
332
355
|
window = False
|
|
333
356
|
|
MambuPy/api/entities.py
CHANGED
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import copy
|
|
9
|
+
import datetime
|
|
10
|
+
import enum
|
|
9
11
|
from importlib import import_module
|
|
10
12
|
import json
|
|
13
|
+
import orjson
|
|
11
14
|
import time
|
|
12
15
|
|
|
13
16
|
from .classes import GenericClass
|
|
@@ -238,7 +241,7 @@ class MambuEntity(MambuStruct):
|
|
|
238
241
|
|
|
239
242
|
logger.debug("request several entities %s", cls.__name__)
|
|
240
243
|
list_resp = get_func(prefix, **params)
|
|
241
|
-
jsonresp = list(
|
|
244
|
+
jsonresp = list(orjson.loads(list_resp.decode()))
|
|
242
245
|
logger.debug("%s, %s retrieved", cls.__name__, len(jsonresp))
|
|
243
246
|
|
|
244
247
|
elements = []
|
|
@@ -310,8 +313,8 @@ class MambuEntity(MambuStruct):
|
|
|
310
313
|
instance = cls.__build_object(
|
|
311
314
|
connector=connector,
|
|
312
315
|
resp=resp,
|
|
313
|
-
attrs=dict(
|
|
314
|
-
tzattrs=dict(
|
|
316
|
+
attrs=dict(orjson.loads(resp.decode())),
|
|
317
|
+
tzattrs=dict(orjson.loads(resp.decode())),
|
|
315
318
|
get_entities=get_entities,
|
|
316
319
|
detailsLevel=detailsLevel,
|
|
317
320
|
debug=debug,
|
|
@@ -353,8 +356,8 @@ class MambuEntity(MambuStruct):
|
|
|
353
356
|
self.id, prefix=self._prefix, detailsLevel=detailsLevel
|
|
354
357
|
)
|
|
355
358
|
|
|
356
|
-
self._attrs.update(dict(
|
|
357
|
-
self._tzattrs = dict(
|
|
359
|
+
self._attrs.update(dict(orjson.loads(self._resp.decode())))
|
|
360
|
+
self._tzattrs = dict(orjson.loads(self._resp.decode()))
|
|
358
361
|
self._convertDict2Attrs()
|
|
359
362
|
self._extractCustomFields()
|
|
360
363
|
self._extractVOs()
|
|
@@ -470,8 +473,8 @@ class MambuEntityWritable(MambuStruct, MambuWritable):
|
|
|
470
473
|
self._resp = self._connector.mambu_create(
|
|
471
474
|
self._prefix, copy.deepcopy(self._attrs), **kwargs
|
|
472
475
|
)
|
|
473
|
-
self._attrs.update(dict(
|
|
474
|
-
self._tzattrs = dict(
|
|
476
|
+
self._attrs.update(dict(orjson.loads(self._resp.decode())))
|
|
477
|
+
self._tzattrs = dict(orjson.loads(self._resp.decode()))
|
|
475
478
|
self._detailsLevel = "FULL"
|
|
476
479
|
except MambuError:
|
|
477
480
|
raise
|
|
@@ -503,6 +506,10 @@ class MambuEntityWritable(MambuStruct, MambuWritable):
|
|
|
503
506
|
val = attrs[field]["value"]
|
|
504
507
|
except (TypeError, KeyError):
|
|
505
508
|
val = attrs[field]
|
|
509
|
+
if isinstance(val, (datetime.datetime, datetime.date)):
|
|
510
|
+
val = val.isoformat()
|
|
511
|
+
elif isinstance(val, enum.Enum):
|
|
512
|
+
val = val.value
|
|
506
513
|
return (operation, path, val)
|
|
507
514
|
|
|
508
515
|
def __patch_field_op(self, field, original_attrs):
|
|
@@ -567,7 +574,7 @@ class MambuEntityWritable(MambuStruct, MambuWritable):
|
|
|
567
574
|
# build fields_ops param with what detected using previous rules
|
|
568
575
|
# strings: (OP, PATH) (remove) or (OP, PATH, VALUE) (all else)
|
|
569
576
|
fields_ops = []
|
|
570
|
-
original_attrs = dict(
|
|
577
|
+
original_attrs = dict(orjson.loads(self._resp.decode()))
|
|
571
578
|
self._extractCustomFields(original_attrs)
|
|
572
579
|
self._updateVOs()
|
|
573
580
|
for field in fields:
|
|
@@ -703,7 +710,7 @@ class MambuEntityAttachable(MambuStruct, MambuAttachable):
|
|
|
703
710
|
notes=notes,
|
|
704
711
|
)
|
|
705
712
|
|
|
706
|
-
doc = MambuDocument(**dict(
|
|
713
|
+
doc = MambuDocument(**dict(orjson.loads(response.decode())))
|
|
707
714
|
self._attachments[str(doc["id"])] = doc
|
|
708
715
|
|
|
709
716
|
return response
|
|
@@ -736,7 +743,7 @@ class MambuEntityAttachable(MambuStruct, MambuAttachable):
|
|
|
736
743
|
paginationDetails=paginationDetails,
|
|
737
744
|
)
|
|
738
745
|
|
|
739
|
-
metadata_list =
|
|
746
|
+
metadata_list = orjson.loads(response.decode())
|
|
740
747
|
|
|
741
748
|
for metadata in metadata_list:
|
|
742
749
|
doc = MambuDocument(**metadata)
|
|
@@ -825,7 +832,7 @@ class MambuEntityCommentable(MambuStruct, MambuCommentable):
|
|
|
825
832
|
paginationDetails=paginationDetails,
|
|
826
833
|
)
|
|
827
834
|
|
|
828
|
-
comments_list =
|
|
835
|
+
comments_list = orjson.loads(response.decode())
|
|
829
836
|
|
|
830
837
|
for comment in comments_list:
|
|
831
838
|
comm = MambuComment(**comment)
|
|
@@ -848,7 +855,7 @@ class MambuEntityCommentable(MambuStruct, MambuCommentable):
|
|
|
848
855
|
owner_id=self.id, owner_type=self._ownerType, text=comment
|
|
849
856
|
)
|
|
850
857
|
|
|
851
|
-
comment = MambuComment(**dict(
|
|
858
|
+
comment = MambuComment(**dict(orjson.loads(response.decode())))
|
|
852
859
|
self._comments.insert(0, comment)
|
|
853
860
|
|
|
854
861
|
return response
|
MambuPy/api/mambuloan.py
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import copy
|
|
9
9
|
import datetime
|
|
10
10
|
import json
|
|
11
|
+
import orjson
|
|
11
12
|
|
|
12
13
|
from .entities import (
|
|
13
14
|
MambuEntity,
|
|
@@ -117,7 +118,7 @@ class MambuLoan(
|
|
|
117
118
|
"""Retrieves the installments schedule."""
|
|
118
119
|
resp = self._connector.mambu_loanaccount_getSchedule(self.id)
|
|
119
120
|
|
|
120
|
-
installments =
|
|
121
|
+
installments = orjson.loads(resp.decode())["installments"]
|
|
121
122
|
|
|
122
123
|
self.schedule = []
|
|
123
124
|
for installment in installments:
|
|
@@ -144,7 +145,7 @@ class MambuLoan(
|
|
|
144
145
|
resp = self._connector.mambu_change_state(
|
|
145
146
|
entid=self.id, prefix=self._prefix, action=action, notes=notes,
|
|
146
147
|
)
|
|
147
|
-
resp =
|
|
148
|
+
resp = orjson.loads(resp)
|
|
148
149
|
self.accountState = resp["accountState"]
|
|
149
150
|
else:
|
|
150
151
|
raise MambuPyError(
|
MambuPy/api/mambustruct.py
CHANGED
|
@@ -101,15 +101,21 @@ class MambuStruct(MambuMapObj):
|
|
|
101
101
|
return self.__getattribute_for_loop(ent, entity.value, entity.mcf)
|
|
102
102
|
return self.__getattribute_for_one_cf(ent, entity.value, entity.mcf)
|
|
103
103
|
|
|
104
|
-
def
|
|
104
|
+
def __getattr__(self, name):
|
|
105
105
|
"""Object-like get attribute for MambuStructs.
|
|
106
106
|
|
|
107
107
|
If the attribute is not present at the _attrs dict, tries to build a
|
|
108
108
|
function that calls :py:meth:`getEntities`, which in turns will try
|
|
109
109
|
to instantiate an entity according to the requested attribute name
|
|
110
|
+
|
|
111
|
+
Implemented as __getattr__ (not __getattribute__) so it only runs
|
|
112
|
+
after the normal lookup failed. We first delegate to the base
|
|
113
|
+
__getattr__ (the _attrs resolution), preserving precedence of
|
|
114
|
+
_attrs over the dynamic get_* magic, and only fall back to building
|
|
115
|
+
a getter when that raises AttributeError.
|
|
110
116
|
"""
|
|
111
117
|
try:
|
|
112
|
-
return super().
|
|
118
|
+
return super().__getattr__(name)
|
|
113
119
|
except AttributeError as attr_err:
|
|
114
120
|
if name[0:4] == "get_" and len(name) > 4:
|
|
115
121
|
ent = name[4:]
|
MambuPy/mambuutil.py
CHANGED
|
@@ -321,13 +321,46 @@ def _backup_db_previous_prep(callback, logger, kwargs):
|
|
|
321
321
|
data = {"callback": callback}
|
|
322
322
|
list_ret.append(data)
|
|
323
323
|
|
|
324
|
+
try:
|
|
325
|
+
backup_in_progress_wait = kwargs["backup_in_progress_wait"]
|
|
326
|
+
except KeyError:
|
|
327
|
+
backup_in_progress_wait = 900
|
|
328
|
+
list_ret.append(backup_in_progress_wait)
|
|
329
|
+
|
|
330
|
+
try:
|
|
331
|
+
backup_in_progress_retries = kwargs["backup_in_progress_retries"]
|
|
332
|
+
except KeyError:
|
|
333
|
+
backup_in_progress_retries = 3
|
|
334
|
+
list_ret.append(backup_in_progress_retries)
|
|
335
|
+
|
|
324
336
|
return list_ret
|
|
325
337
|
|
|
326
338
|
|
|
327
|
-
def
|
|
339
|
+
def _backup_db_in_progress(resp):
|
|
340
|
+
"""True if Mambu rejected the backup POST because one is already running.
|
|
341
|
+
|
|
342
|
+
Mambu answers with errorCode 1201 / DATABASE_BACKUP_IN_PROGRESS when a
|
|
343
|
+
database backup job is already in progress. That is a transient, retryable
|
|
344
|
+
condition (the running job finishes in ~14 min), not a real failure.
|
|
345
|
+
"""
|
|
328
346
|
try:
|
|
329
|
-
|
|
330
|
-
|
|
347
|
+
errors = json.loads(resp.content).get("errors", [])
|
|
348
|
+
return any(err.get("errorCode") == 1201 for err in errors)
|
|
349
|
+
except (ValueError, AttributeError):
|
|
350
|
+
return False
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _backup_db_request(
|
|
354
|
+
justbackup, data, user, pwd, logger=None,
|
|
355
|
+
backup_in_progress_wait=900, backup_in_progress_retries=3,
|
|
356
|
+
):
|
|
357
|
+
if justbackup:
|
|
358
|
+
return
|
|
359
|
+
|
|
360
|
+
posturl = iri_to_uri(getmambuurl() + "database/backup")
|
|
361
|
+
attempts_left = backup_in_progress_retries
|
|
362
|
+
while True:
|
|
363
|
+
try:
|
|
331
364
|
logger.info("open url: " + posturl)
|
|
332
365
|
logger.info("data: " + str(data))
|
|
333
366
|
resp = requests.post(
|
|
@@ -343,12 +376,26 @@ def _backup_db_request(justbackup, data, user, pwd, logger=None):
|
|
|
343
376
|
logger.info(resp.request.url)
|
|
344
377
|
logger.info(resp.request.body)
|
|
345
378
|
logger.info(str([(k, v) for k, v in resp.request.headers.items() if k != "Authorization"]))
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
379
|
+
except Exception as ex:
|
|
380
|
+
mess = "Error requesting backup: %s" % repr(ex)
|
|
381
|
+
logger.exception(mess)
|
|
382
|
+
raise MambuError(mess)
|
|
383
|
+
|
|
384
|
+
if resp.status_code == 202:
|
|
385
|
+
return
|
|
386
|
+
|
|
387
|
+
# Only retry the POST when Mambu says a backup is already in progress.
|
|
388
|
+
# Any other non-202 is a real failure and fails fast, as before.
|
|
389
|
+
if _backup_db_in_progress(resp) and attempts_left > 0:
|
|
390
|
+
attempts_left -= 1
|
|
391
|
+
logger.warning(
|
|
392
|
+
"Mambu backup already in progress (errorCode 1201); waiting "
|
|
393
|
+
"%ss before retrying POST (%s retries left)"
|
|
394
|
+
% (backup_in_progress_wait, attempts_left)
|
|
395
|
+
)
|
|
396
|
+
sleep(backup_in_progress_wait)
|
|
397
|
+
continue
|
|
350
398
|
|
|
351
|
-
if not justbackup and resp.status_code != 202:
|
|
352
399
|
mess = "Error posting request for backup: %s" % resp.content
|
|
353
400
|
logger.error(mess)
|
|
354
401
|
raise MambuCommError(mess)
|
|
@@ -433,6 +480,13 @@ def backup_db(callback, bool_func, output_fname, *args, **kwargs):
|
|
|
433
480
|
callback is called. False to throw error if callback isn't received
|
|
434
481
|
after retries.
|
|
435
482
|
|
|
483
|
+
* backup_in_progress_wait seconds to wait before retrying the POST when
|
|
484
|
+
Mambu rejects it with errorCode 1201 (a backup is already in progress).
|
|
485
|
+
Defaults to 900 (~the observed ~14 min a backup job takes).
|
|
486
|
+
|
|
487
|
+
* backup_in_progress_retries number of times to retry the POST on
|
|
488
|
+
errorCode 1201 before giving up and raising. Defaults to 3.
|
|
489
|
+
|
|
436
490
|
* returns a dictionary with info about the download
|
|
437
491
|
-latest boolean flag, if the db downloaded was the latest or not
|
|
438
492
|
|
|
@@ -448,10 +502,16 @@ def backup_db(callback, bool_func, output_fname, *args, **kwargs):
|
|
|
448
502
|
user,
|
|
449
503
|
pwd,
|
|
450
504
|
data,
|
|
505
|
+
backup_in_progress_wait,
|
|
506
|
+
backup_in_progress_retries,
|
|
451
507
|
) = _backup_db_previous_prep(callback, logger, kwargs)
|
|
452
508
|
|
|
453
509
|
# POST to request Mambu to prepare backup of its own DB
|
|
454
|
-
_backup_db_request(
|
|
510
|
+
_backup_db_request(
|
|
511
|
+
justbackup, data, user, pwd, logger,
|
|
512
|
+
backup_in_progress_wait=backup_in_progress_wait,
|
|
513
|
+
backup_in_progress_retries=backup_in_progress_retries,
|
|
514
|
+
)
|
|
455
515
|
|
|
456
516
|
# wait & timeout mechanism
|
|
457
517
|
data["latest"] = _backup_db_timeout_mechanism(
|
mambupy/__init__.py
CHANGED
mambupy/api/classes.py
CHANGED
|
@@ -31,32 +31,31 @@ class MambuMapObj:
|
|
|
31
31
|
for key, val in kwargs.items():
|
|
32
32
|
self._attrs[key] = val
|
|
33
33
|
|
|
34
|
-
def
|
|
34
|
+
def __getattr__(self, name):
|
|
35
35
|
"""Object-like get attribute
|
|
36
36
|
|
|
37
37
|
When accessing an attribute, tries to find it in the _attrs
|
|
38
38
|
dictionary, so now MambuMapObj may act not only as a dict-like
|
|
39
39
|
structure, but as a full object-like too (this is the getter
|
|
40
40
|
side).
|
|
41
|
+
|
|
42
|
+
Implemented as __getattr__ (not __getattribute__) so it only runs
|
|
43
|
+
after the normal attribute lookup already failed: a real
|
|
44
|
+
attribute/method always wins, and successful accesses keep the
|
|
45
|
+
native fast path instead of being intercepted on every access.
|
|
41
46
|
"""
|
|
42
|
-
try
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
#
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return object.__getattribute__(self, name)
|
|
55
|
-
# all else, read the property from the _attrs dict, but with a . syntax
|
|
56
|
-
# if a cf_class, just return its value
|
|
57
|
-
if _attrs[name].__class__.__name__ == self._cf_class.__name__:
|
|
58
|
-
return _attrs[name]["value"]
|
|
59
|
-
return _attrs[name]
|
|
47
|
+
# try to read the _attrs property
|
|
48
|
+
_attrs = object.__getattribute__(self, "_attrs")
|
|
49
|
+
if isinstance(_attrs, list) or name not in _attrs:
|
|
50
|
+
# magic won't happen when not a dict-like MambuMapObj or
|
|
51
|
+
# when _attrs has not the 'name' key: this is a genuine
|
|
52
|
+
# missing attribute, so AttributeError raises by default
|
|
53
|
+
raise AttributeError(name)
|
|
54
|
+
# all else, read the property from the _attrs dict, but with a . syntax
|
|
55
|
+
# if a cf_class, just return its value
|
|
56
|
+
if _attrs[name].__class__.__name__ == self._cf_class.__name__:
|
|
57
|
+
return _attrs[name]["value"]
|
|
58
|
+
return _attrs[name]
|
|
60
59
|
|
|
61
60
|
def __setattr__(self, name, value):
|
|
62
61
|
"""Object-like set attribute
|
mambupy/api/connector/rest.py
CHANGED
|
@@ -9,11 +9,15 @@ Currently supports REST.
|
|
|
9
9
|
|
|
10
10
|
import base64
|
|
11
11
|
import copy
|
|
12
|
+
import datetime
|
|
13
|
+
import enum
|
|
12
14
|
import json
|
|
13
15
|
import mimetypes
|
|
16
|
+
import orjson
|
|
14
17
|
import os
|
|
15
18
|
import re
|
|
16
19
|
import uuid
|
|
20
|
+
from decimal import Decimal
|
|
17
21
|
|
|
18
22
|
import requests
|
|
19
23
|
from requests_toolbelt.multipart.encoder import MultipartEncoder
|
|
@@ -43,9 +47,27 @@ from MambuPy.mambuutil import (
|
|
|
43
47
|
logger = setup_logging(__name__)
|
|
44
48
|
|
|
45
49
|
|
|
50
|
+
class _MambuJSONEncoder(json.JSONEncoder):
|
|
51
|
+
"""JSON encoder that handles Python types not natively serializable by json.dumps."""
|
|
52
|
+
|
|
53
|
+
def default(self, obj):
|
|
54
|
+
if isinstance(obj, (datetime.datetime, datetime.date)):
|
|
55
|
+
return obj.isoformat()
|
|
56
|
+
if isinstance(obj, enum.Enum):
|
|
57
|
+
return obj.value
|
|
58
|
+
if isinstance(obj, Decimal):
|
|
59
|
+
return float(obj)
|
|
60
|
+
return super().default(obj)
|
|
61
|
+
|
|
62
|
+
|
|
46
63
|
def _configure_retry_strategy(session, retries=5):
|
|
47
64
|
"""Configure retry strategy for a session.
|
|
48
65
|
|
|
66
|
+
El HTTPAdapter se construye con pool_maxsize=50 para que los flujos
|
|
67
|
+
que despachan multiples requests concurrentes contra Mambu (p.ej.
|
|
68
|
+
fan-out de aprobaciones, cargar_datos paralelo) no queden encolados
|
|
69
|
+
sobre el default de urllib3 de 10 conexiones por host.
|
|
70
|
+
|
|
49
71
|
Args:
|
|
50
72
|
session (requests.Session): The session to configure
|
|
51
73
|
retries (int, optional): Number of retries. Defaults to 5.
|
|
@@ -65,7 +87,11 @@ def _configure_retry_strategy(session, retries=5):
|
|
|
65
87
|
"PATCH",
|
|
66
88
|
],
|
|
67
89
|
)
|
|
68
|
-
adapter = HTTPAdapter(
|
|
90
|
+
adapter = HTTPAdapter(
|
|
91
|
+
max_retries=retry_strategy,
|
|
92
|
+
pool_connections=50,
|
|
93
|
+
pool_maxsize=50,
|
|
94
|
+
)
|
|
69
95
|
session.mount("https://", adapter)
|
|
70
96
|
session.mount("http://", adapter)
|
|
71
97
|
|
|
@@ -154,11 +180,8 @@ class MambuConnectorREST(MambuConnector, MambuConnectorReader, MambuConnectorWri
|
|
|
154
180
|
return params
|
|
155
181
|
|
|
156
182
|
def __request_data(self, data):
|
|
157
|
-
if data
|
|
158
|
-
|
|
159
|
-
data = json.dumps(data)
|
|
160
|
-
except TypeError:
|
|
161
|
-
data = data
|
|
183
|
+
if isinstance(data, (dict, list)):
|
|
184
|
+
data = json.dumps(data, cls=_MambuJSONEncoder)
|
|
162
185
|
return data
|
|
163
186
|
|
|
164
187
|
def __request(self, method, url, params=None, data=None, content_type=None):
|
|
@@ -211,7 +234,7 @@ url %s, params %s, data %s, headers %s",
|
|
|
211
234
|
if hasattr(resp, "content"): # pragma: no cover
|
|
212
235
|
logger.warning("HTTPError, resp content: %s", resp.content)
|
|
213
236
|
try:
|
|
214
|
-
content =
|
|
237
|
+
content = orjson.loads(resp.content.decode())
|
|
215
238
|
except ValueError:
|
|
216
239
|
# in case resp.content doesn't conforms to json
|
|
217
240
|
content = {
|
|
@@ -287,7 +310,7 @@ url %s, params %s, data %s, headers %s",
|
|
|
287
310
|
def __list_request_cat_response(self, list_resp, resp):
|
|
288
311
|
if list_resp == b"":
|
|
289
312
|
list_resp = resp
|
|
290
|
-
elif len(
|
|
313
|
+
elif len(orjson.loads(resp.decode())) > 0:
|
|
291
314
|
list_resp = list_resp[:-1] + b"," + resp[1:]
|
|
292
315
|
|
|
293
316
|
return list_resp
|
|
@@ -327,7 +350,7 @@ url %s, params %s, data %s, headers %s",
|
|
|
327
350
|
method, url, params=copy.copy(params), data=copy.copy(data)
|
|
328
351
|
)
|
|
329
352
|
|
|
330
|
-
jsonresp = list(
|
|
353
|
+
jsonresp = list(orjson.loads(resp.decode()))
|
|
331
354
|
if len(jsonresp) < limit:
|
|
332
355
|
window = False
|
|
333
356
|
|
mambupy/api/entities.py
CHANGED
|
@@ -6,8 +6,11 @@
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
import copy
|
|
9
|
+
import datetime
|
|
10
|
+
import enum
|
|
9
11
|
from importlib import import_module
|
|
10
12
|
import json
|
|
13
|
+
import orjson
|
|
11
14
|
import time
|
|
12
15
|
|
|
13
16
|
from .classes import GenericClass
|
|
@@ -238,7 +241,7 @@ class MambuEntity(MambuStruct):
|
|
|
238
241
|
|
|
239
242
|
logger.debug("request several entities %s", cls.__name__)
|
|
240
243
|
list_resp = get_func(prefix, **params)
|
|
241
|
-
jsonresp = list(
|
|
244
|
+
jsonresp = list(orjson.loads(list_resp.decode()))
|
|
242
245
|
logger.debug("%s, %s retrieved", cls.__name__, len(jsonresp))
|
|
243
246
|
|
|
244
247
|
elements = []
|
|
@@ -310,8 +313,8 @@ class MambuEntity(MambuStruct):
|
|
|
310
313
|
instance = cls.__build_object(
|
|
311
314
|
connector=connector,
|
|
312
315
|
resp=resp,
|
|
313
|
-
attrs=dict(
|
|
314
|
-
tzattrs=dict(
|
|
316
|
+
attrs=dict(orjson.loads(resp.decode())),
|
|
317
|
+
tzattrs=dict(orjson.loads(resp.decode())),
|
|
315
318
|
get_entities=get_entities,
|
|
316
319
|
detailsLevel=detailsLevel,
|
|
317
320
|
debug=debug,
|
|
@@ -353,8 +356,8 @@ class MambuEntity(MambuStruct):
|
|
|
353
356
|
self.id, prefix=self._prefix, detailsLevel=detailsLevel
|
|
354
357
|
)
|
|
355
358
|
|
|
356
|
-
self._attrs.update(dict(
|
|
357
|
-
self._tzattrs = dict(
|
|
359
|
+
self._attrs.update(dict(orjson.loads(self._resp.decode())))
|
|
360
|
+
self._tzattrs = dict(orjson.loads(self._resp.decode()))
|
|
358
361
|
self._convertDict2Attrs()
|
|
359
362
|
self._extractCustomFields()
|
|
360
363
|
self._extractVOs()
|
|
@@ -470,8 +473,8 @@ class MambuEntityWritable(MambuStruct, MambuWritable):
|
|
|
470
473
|
self._resp = self._connector.mambu_create(
|
|
471
474
|
self._prefix, copy.deepcopy(self._attrs), **kwargs
|
|
472
475
|
)
|
|
473
|
-
self._attrs.update(dict(
|
|
474
|
-
self._tzattrs = dict(
|
|
476
|
+
self._attrs.update(dict(orjson.loads(self._resp.decode())))
|
|
477
|
+
self._tzattrs = dict(orjson.loads(self._resp.decode()))
|
|
475
478
|
self._detailsLevel = "FULL"
|
|
476
479
|
except MambuError:
|
|
477
480
|
raise
|
|
@@ -503,6 +506,10 @@ class MambuEntityWritable(MambuStruct, MambuWritable):
|
|
|
503
506
|
val = attrs[field]["value"]
|
|
504
507
|
except (TypeError, KeyError):
|
|
505
508
|
val = attrs[field]
|
|
509
|
+
if isinstance(val, (datetime.datetime, datetime.date)):
|
|
510
|
+
val = val.isoformat()
|
|
511
|
+
elif isinstance(val, enum.Enum):
|
|
512
|
+
val = val.value
|
|
506
513
|
return (operation, path, val)
|
|
507
514
|
|
|
508
515
|
def __patch_field_op(self, field, original_attrs):
|
|
@@ -567,7 +574,7 @@ class MambuEntityWritable(MambuStruct, MambuWritable):
|
|
|
567
574
|
# build fields_ops param with what detected using previous rules
|
|
568
575
|
# strings: (OP, PATH) (remove) or (OP, PATH, VALUE) (all else)
|
|
569
576
|
fields_ops = []
|
|
570
|
-
original_attrs = dict(
|
|
577
|
+
original_attrs = dict(orjson.loads(self._resp.decode()))
|
|
571
578
|
self._extractCustomFields(original_attrs)
|
|
572
579
|
self._updateVOs()
|
|
573
580
|
for field in fields:
|
|
@@ -703,7 +710,7 @@ class MambuEntityAttachable(MambuStruct, MambuAttachable):
|
|
|
703
710
|
notes=notes,
|
|
704
711
|
)
|
|
705
712
|
|
|
706
|
-
doc = MambuDocument(**dict(
|
|
713
|
+
doc = MambuDocument(**dict(orjson.loads(response.decode())))
|
|
707
714
|
self._attachments[str(doc["id"])] = doc
|
|
708
715
|
|
|
709
716
|
return response
|
|
@@ -736,7 +743,7 @@ class MambuEntityAttachable(MambuStruct, MambuAttachable):
|
|
|
736
743
|
paginationDetails=paginationDetails,
|
|
737
744
|
)
|
|
738
745
|
|
|
739
|
-
metadata_list =
|
|
746
|
+
metadata_list = orjson.loads(response.decode())
|
|
740
747
|
|
|
741
748
|
for metadata in metadata_list:
|
|
742
749
|
doc = MambuDocument(**metadata)
|
|
@@ -825,7 +832,7 @@ class MambuEntityCommentable(MambuStruct, MambuCommentable):
|
|
|
825
832
|
paginationDetails=paginationDetails,
|
|
826
833
|
)
|
|
827
834
|
|
|
828
|
-
comments_list =
|
|
835
|
+
comments_list = orjson.loads(response.decode())
|
|
829
836
|
|
|
830
837
|
for comment in comments_list:
|
|
831
838
|
comm = MambuComment(**comment)
|
|
@@ -848,7 +855,7 @@ class MambuEntityCommentable(MambuStruct, MambuCommentable):
|
|
|
848
855
|
owner_id=self.id, owner_type=self._ownerType, text=comment
|
|
849
856
|
)
|
|
850
857
|
|
|
851
|
-
comment = MambuComment(**dict(
|
|
858
|
+
comment = MambuComment(**dict(orjson.loads(response.decode())))
|
|
852
859
|
self._comments.insert(0, comment)
|
|
853
860
|
|
|
854
861
|
return response
|
mambupy/api/mambuloan.py
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import copy
|
|
9
9
|
import datetime
|
|
10
10
|
import json
|
|
11
|
+
import orjson
|
|
11
12
|
|
|
12
13
|
from .entities import (
|
|
13
14
|
MambuEntity,
|
|
@@ -117,7 +118,7 @@ class MambuLoan(
|
|
|
117
118
|
"""Retrieves the installments schedule."""
|
|
118
119
|
resp = self._connector.mambu_loanaccount_getSchedule(self.id)
|
|
119
120
|
|
|
120
|
-
installments =
|
|
121
|
+
installments = orjson.loads(resp.decode())["installments"]
|
|
121
122
|
|
|
122
123
|
self.schedule = []
|
|
123
124
|
for installment in installments:
|
|
@@ -144,7 +145,7 @@ class MambuLoan(
|
|
|
144
145
|
resp = self._connector.mambu_change_state(
|
|
145
146
|
entid=self.id, prefix=self._prefix, action=action, notes=notes,
|
|
146
147
|
)
|
|
147
|
-
resp =
|
|
148
|
+
resp = orjson.loads(resp)
|
|
148
149
|
self.accountState = resp["accountState"]
|
|
149
150
|
else:
|
|
150
151
|
raise MambuPyError(
|
mambupy/api/mambustruct.py
CHANGED
|
@@ -101,15 +101,21 @@ class MambuStruct(MambuMapObj):
|
|
|
101
101
|
return self.__getattribute_for_loop(ent, entity.value, entity.mcf)
|
|
102
102
|
return self.__getattribute_for_one_cf(ent, entity.value, entity.mcf)
|
|
103
103
|
|
|
104
|
-
def
|
|
104
|
+
def __getattr__(self, name):
|
|
105
105
|
"""Object-like get attribute for MambuStructs.
|
|
106
106
|
|
|
107
107
|
If the attribute is not present at the _attrs dict, tries to build a
|
|
108
108
|
function that calls :py:meth:`getEntities`, which in turns will try
|
|
109
109
|
to instantiate an entity according to the requested attribute name
|
|
110
|
+
|
|
111
|
+
Implemented as __getattr__ (not __getattribute__) so it only runs
|
|
112
|
+
after the normal lookup failed. We first delegate to the base
|
|
113
|
+
__getattr__ (the _attrs resolution), preserving precedence of
|
|
114
|
+
_attrs over the dynamic get_* magic, and only fall back to building
|
|
115
|
+
a getter when that raises AttributeError.
|
|
110
116
|
"""
|
|
111
117
|
try:
|
|
112
|
-
return super().
|
|
118
|
+
return super().__getattr__(name)
|
|
113
119
|
except AttributeError as attr_err:
|
|
114
120
|
if name[0:4] == "get_" and len(name) > 4:
|
|
115
121
|
ent = name[4:]
|
mambupy/mambuutil.py
CHANGED
|
@@ -321,13 +321,46 @@ def _backup_db_previous_prep(callback, logger, kwargs):
|
|
|
321
321
|
data = {"callback": callback}
|
|
322
322
|
list_ret.append(data)
|
|
323
323
|
|
|
324
|
+
try:
|
|
325
|
+
backup_in_progress_wait = kwargs["backup_in_progress_wait"]
|
|
326
|
+
except KeyError:
|
|
327
|
+
backup_in_progress_wait = 900
|
|
328
|
+
list_ret.append(backup_in_progress_wait)
|
|
329
|
+
|
|
330
|
+
try:
|
|
331
|
+
backup_in_progress_retries = kwargs["backup_in_progress_retries"]
|
|
332
|
+
except KeyError:
|
|
333
|
+
backup_in_progress_retries = 3
|
|
334
|
+
list_ret.append(backup_in_progress_retries)
|
|
335
|
+
|
|
324
336
|
return list_ret
|
|
325
337
|
|
|
326
338
|
|
|
327
|
-
def
|
|
339
|
+
def _backup_db_in_progress(resp):
|
|
340
|
+
"""True if Mambu rejected the backup POST because one is already running.
|
|
341
|
+
|
|
342
|
+
Mambu answers with errorCode 1201 / DATABASE_BACKUP_IN_PROGRESS when a
|
|
343
|
+
database backup job is already in progress. That is a transient, retryable
|
|
344
|
+
condition (the running job finishes in ~14 min), not a real failure.
|
|
345
|
+
"""
|
|
328
346
|
try:
|
|
329
|
-
|
|
330
|
-
|
|
347
|
+
errors = json.loads(resp.content).get("errors", [])
|
|
348
|
+
return any(err.get("errorCode") == 1201 for err in errors)
|
|
349
|
+
except (ValueError, AttributeError):
|
|
350
|
+
return False
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def _backup_db_request(
|
|
354
|
+
justbackup, data, user, pwd, logger=None,
|
|
355
|
+
backup_in_progress_wait=900, backup_in_progress_retries=3,
|
|
356
|
+
):
|
|
357
|
+
if justbackup:
|
|
358
|
+
return
|
|
359
|
+
|
|
360
|
+
posturl = iri_to_uri(getmambuurl() + "database/backup")
|
|
361
|
+
attempts_left = backup_in_progress_retries
|
|
362
|
+
while True:
|
|
363
|
+
try:
|
|
331
364
|
logger.info("open url: " + posturl)
|
|
332
365
|
logger.info("data: " + str(data))
|
|
333
366
|
resp = requests.post(
|
|
@@ -343,12 +376,26 @@ def _backup_db_request(justbackup, data, user, pwd, logger=None):
|
|
|
343
376
|
logger.info(resp.request.url)
|
|
344
377
|
logger.info(resp.request.body)
|
|
345
378
|
logger.info(str([(k, v) for k, v in resp.request.headers.items() if k != "Authorization"]))
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
379
|
+
except Exception as ex:
|
|
380
|
+
mess = "Error requesting backup: %s" % repr(ex)
|
|
381
|
+
logger.exception(mess)
|
|
382
|
+
raise MambuError(mess)
|
|
383
|
+
|
|
384
|
+
if resp.status_code == 202:
|
|
385
|
+
return
|
|
386
|
+
|
|
387
|
+
# Only retry the POST when Mambu says a backup is already in progress.
|
|
388
|
+
# Any other non-202 is a real failure and fails fast, as before.
|
|
389
|
+
if _backup_db_in_progress(resp) and attempts_left > 0:
|
|
390
|
+
attempts_left -= 1
|
|
391
|
+
logger.warning(
|
|
392
|
+
"Mambu backup already in progress (errorCode 1201); waiting "
|
|
393
|
+
"%ss before retrying POST (%s retries left)"
|
|
394
|
+
% (backup_in_progress_wait, attempts_left)
|
|
395
|
+
)
|
|
396
|
+
sleep(backup_in_progress_wait)
|
|
397
|
+
continue
|
|
350
398
|
|
|
351
|
-
if not justbackup and resp.status_code != 202:
|
|
352
399
|
mess = "Error posting request for backup: %s" % resp.content
|
|
353
400
|
logger.error(mess)
|
|
354
401
|
raise MambuCommError(mess)
|
|
@@ -433,6 +480,13 @@ def backup_db(callback, bool_func, output_fname, *args, **kwargs):
|
|
|
433
480
|
callback is called. False to throw error if callback isn't received
|
|
434
481
|
after retries.
|
|
435
482
|
|
|
483
|
+
* backup_in_progress_wait seconds to wait before retrying the POST when
|
|
484
|
+
Mambu rejects it with errorCode 1201 (a backup is already in progress).
|
|
485
|
+
Defaults to 900 (~the observed ~14 min a backup job takes).
|
|
486
|
+
|
|
487
|
+
* backup_in_progress_retries number of times to retry the POST on
|
|
488
|
+
errorCode 1201 before giving up and raising. Defaults to 3.
|
|
489
|
+
|
|
436
490
|
* returns a dictionary with info about the download
|
|
437
491
|
-latest boolean flag, if the db downloaded was the latest or not
|
|
438
492
|
|
|
@@ -448,10 +502,16 @@ def backup_db(callback, bool_func, output_fname, *args, **kwargs):
|
|
|
448
502
|
user,
|
|
449
503
|
pwd,
|
|
450
504
|
data,
|
|
505
|
+
backup_in_progress_wait,
|
|
506
|
+
backup_in_progress_retries,
|
|
451
507
|
) = _backup_db_previous_prep(callback, logger, kwargs)
|
|
452
508
|
|
|
453
509
|
# POST to request Mambu to prepare backup of its own DB
|
|
454
|
-
_backup_db_request(
|
|
510
|
+
_backup_db_request(
|
|
511
|
+
justbackup, data, user, pwd, logger,
|
|
512
|
+
backup_in_progress_wait=backup_in_progress_wait,
|
|
513
|
+
backup_in_progress_retries=backup_in_progress_retries,
|
|
514
|
+
)
|
|
455
515
|
|
|
456
516
|
# wait & timeout mechanism
|
|
457
517
|
data["latest"] = _backup_db_timeout_mechanism(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: MambuPy
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.3
|
|
4
4
|
Summary: A python library for using Mambu APIs.
|
|
5
5
|
Author-email: "Javier Novoa C." <jstitch@gmail.com>
|
|
6
6
|
License: LICENSE
|
|
@@ -701,6 +701,7 @@ Requires-Dist: PyYAML==6.0.1
|
|
|
701
701
|
Requires-Dist: requests==2.31.0
|
|
702
702
|
Requires-Dist: requests_toolbelt==1.0.0
|
|
703
703
|
Requires-Dist: dateutils==0.6.12
|
|
704
|
+
Requires-Dist: orjson==3.9.7
|
|
704
705
|
Provides-Extra: full
|
|
705
706
|
Requires-Dist: SQLAlchemy==1.3.6; extra == "full"
|
|
706
707
|
Requires-Dist: mysqlclient==2.1.0; extra == "full"
|
|
@@ -1,27 +1,27 @@
|
|
|
1
|
-
MambuPy/__init__.py,sha256=
|
|
1
|
+
MambuPy/__init__.py,sha256=a6Mg2XoeK9HEyfcabXM2mDQqn0twIDXQsa3DS9jO9Mc,1548
|
|
2
2
|
MambuPy/mambuconfig.py,sha256=6T8jT9LvB2e2HkR6DVD-BwksOgucqo617_xYIlWrc3M,7441
|
|
3
3
|
MambuPy/mambugeturl.py,sha256=D7jTr3tY1nPMXER-s5Uqaoi7_L6WjHTLLnqRYYWAh2Q,23405
|
|
4
|
-
MambuPy/mambuutil.py,sha256=
|
|
4
|
+
MambuPy/mambuutil.py,sha256=7svr8agXPz-a0v6x7a0yFcO4NQBP11fkmWB3zSmH8DU,15648
|
|
5
5
|
MambuPy/api/__init__.py,sha256=B8lABs6zq9owokHc4VGDQ_AirzszWiu7EAYM-FsaSWE,621
|
|
6
|
-
MambuPy/api/classes.py,sha256=
|
|
7
|
-
MambuPy/api/entities.py,sha256=
|
|
6
|
+
MambuPy/api/classes.py,sha256=W5aDtx3kM1YOChJkkYBTGKPpbMu_YycGxTd7Syz7ew4,7943
|
|
7
|
+
MambuPy/api/entities.py,sha256=XRaPGhQ5FhXXwrfv9eN_3IPEydS-0yciy46pfqwuDH4,34275
|
|
8
8
|
MambuPy/api/interfaces.py,sha256=vRf1NDskUum-rnyyCygCVlYvuRWu_QvsEDwekcYK308,4870
|
|
9
9
|
MambuPy/api/mambubranch.py,sha256=yfpMN_OWFIaa_EGpEmOzoH-_uFkWExfuq5IDVDp61F8,778
|
|
10
10
|
MambuPy/api/mambucentre.py,sha256=2ferlA_8GTAoom-jwjBKVMUHGV0Um59vSl6_pdesT-4,952
|
|
11
11
|
MambuPy/api/mambuclient.py,sha256=JEtajMTzCM3lDKIzVEGS1j7wMxo6y9zryztaXoBC6Ms,2006
|
|
12
12
|
MambuPy/api/mambucustomfield.py,sha256=61dEHVmB4gcchGWJ51YVrcxXVyQfqmVffVWWAaBdtpQ,2233
|
|
13
13
|
MambuPy/api/mambugroup.py,sha256=uOcevAlmxJajCLsAwsgqLbTxVmwssKZmZn-q02FaHDQ,1528
|
|
14
|
-
MambuPy/api/mambuloan.py,sha256=
|
|
14
|
+
MambuPy/api/mambuloan.py,sha256=Apg-H8XVEAtmyFWBcaYWGpUbHJQy4j_iDRCokjpJ5lQ,11659
|
|
15
15
|
MambuPy/api/mambuproduct.py,sha256=xD8BQSVuGQgjij0_u-wTR5zL_NRNmeuDK9-2GQNwknI,2871
|
|
16
16
|
MambuPy/api/mamburole.py,sha256=TeeoM2MqhB1cd6TV_7nk1e6sVQRzFJehqTK8nEzqw7Y,565
|
|
17
|
-
MambuPy/api/mambustruct.py,sha256=
|
|
17
|
+
MambuPy/api/mambustruct.py,sha256=RQi837WYHEf9hAfZQp2uCH04DbKaEKeGw99nPEsr6Ek,30766
|
|
18
18
|
MambuPy/api/mambutask.py,sha256=pZ71_SjpNs5ZVAXuIKi84gpq56a8lbj8HZLhn_c7iWk,2552
|
|
19
19
|
MambuPy/api/mambutransaction.py,sha256=epr09hmEQbyq_eS1p1NFRvqNKR0febBfL6WvcLAmUWE,1837
|
|
20
20
|
MambuPy/api/mambuuser.py,sha256=PkW64XFSxRzUEOc2-D2yfJkAKwq6cNyCMecKSmaPoKY,3488
|
|
21
21
|
MambuPy/api/vos.py,sha256=ppsHTITgQ_waymyV9-lxUpBTCC3TsfQpw51ZbTMlcXM,2590
|
|
22
22
|
MambuPy/api/connector/__init__.py,sha256=gTRQao9uSbJ-iDO-CuH4rLGNsVgrKDVeN2nau6t5o7Q,173
|
|
23
23
|
MambuPy/api/connector/mambuconnector.py,sha256=1sp9UYybtpTAUrW4RhHYQ-O-xvATwwP1VCQOFAkAmDw,9847
|
|
24
|
-
MambuPy/api/connector/rest.py,sha256=
|
|
24
|
+
MambuPy/api/connector/rest.py,sha256=LOSWR1nRT22qcOejVYt4LR1Hcetp8at1Z43bUNV1a_o,34068
|
|
25
25
|
MambuPy/orm/__init__.py,sha256=U8zAMqm-W-5c2QaLmcbxR0l2g0o6gUw26w0TgrjRsHM,801
|
|
26
26
|
MambuPy/orm/schema_activities.py,sha256=uKva4zqloesXSGJxth8w4GN-zz8PC1IIpQMs8pDalAI,3391
|
|
27
27
|
MambuPy/orm/schema_addresses.py,sha256=dLvEISuZuo6faWK1i3iU56vmwnrM16X5X40IhWOq4Ec,1598
|
|
@@ -41,30 +41,30 @@ MambuPy/rest/mambuactivity.py,sha256=lpLkTO4mtn3VfsmobAlWeAWqXWCTQ4I2f7koNiDIG54
|
|
|
41
41
|
MambuPy/utils/__init__.py,sha256=xHJUVRE3PdETa8SPaUwdgKQfx8iMOAurSmnm7ROND_k,119
|
|
42
42
|
MambuPy/utils/update_user_roles.py,sha256=CUkkBv2HJ-LaczmYu9Bud269Kb9NisJDoli64KZ1BeI,14675
|
|
43
43
|
MambuPy/utils/userdeactivate.py,sha256=Rjlznm2KW23bqnrR_ogB36-O2rIHcBn9xTJdVIMynZQ,3134
|
|
44
|
-
mambupy/__init__.py,sha256=
|
|
44
|
+
mambupy/__init__.py,sha256=a6Mg2XoeK9HEyfcabXM2mDQqn0twIDXQsa3DS9jO9Mc,1548
|
|
45
45
|
mambupy/mambuconfig.py,sha256=6T8jT9LvB2e2HkR6DVD-BwksOgucqo617_xYIlWrc3M,7441
|
|
46
46
|
mambupy/mambugeturl.py,sha256=D7jTr3tY1nPMXER-s5Uqaoi7_L6WjHTLLnqRYYWAh2Q,23405
|
|
47
|
-
mambupy/mambuutil.py,sha256=
|
|
47
|
+
mambupy/mambuutil.py,sha256=7svr8agXPz-a0v6x7a0yFcO4NQBP11fkmWB3zSmH8DU,15648
|
|
48
48
|
mambupy/api/__init__.py,sha256=B8lABs6zq9owokHc4VGDQ_AirzszWiu7EAYM-FsaSWE,621
|
|
49
|
-
mambupy/api/classes.py,sha256=
|
|
50
|
-
mambupy/api/entities.py,sha256=
|
|
49
|
+
mambupy/api/classes.py,sha256=W5aDtx3kM1YOChJkkYBTGKPpbMu_YycGxTd7Syz7ew4,7943
|
|
50
|
+
mambupy/api/entities.py,sha256=XRaPGhQ5FhXXwrfv9eN_3IPEydS-0yciy46pfqwuDH4,34275
|
|
51
51
|
mambupy/api/interfaces.py,sha256=vRf1NDskUum-rnyyCygCVlYvuRWu_QvsEDwekcYK308,4870
|
|
52
52
|
mambupy/api/mambubranch.py,sha256=yfpMN_OWFIaa_EGpEmOzoH-_uFkWExfuq5IDVDp61F8,778
|
|
53
53
|
mambupy/api/mambucentre.py,sha256=2ferlA_8GTAoom-jwjBKVMUHGV0Um59vSl6_pdesT-4,952
|
|
54
54
|
mambupy/api/mambuclient.py,sha256=JEtajMTzCM3lDKIzVEGS1j7wMxo6y9zryztaXoBC6Ms,2006
|
|
55
55
|
mambupy/api/mambucustomfield.py,sha256=61dEHVmB4gcchGWJ51YVrcxXVyQfqmVffVWWAaBdtpQ,2233
|
|
56
56
|
mambupy/api/mambugroup.py,sha256=uOcevAlmxJajCLsAwsgqLbTxVmwssKZmZn-q02FaHDQ,1528
|
|
57
|
-
mambupy/api/mambuloan.py,sha256=
|
|
57
|
+
mambupy/api/mambuloan.py,sha256=Apg-H8XVEAtmyFWBcaYWGpUbHJQy4j_iDRCokjpJ5lQ,11659
|
|
58
58
|
mambupy/api/mambuproduct.py,sha256=xD8BQSVuGQgjij0_u-wTR5zL_NRNmeuDK9-2GQNwknI,2871
|
|
59
59
|
mambupy/api/mamburole.py,sha256=TeeoM2MqhB1cd6TV_7nk1e6sVQRzFJehqTK8nEzqw7Y,565
|
|
60
|
-
mambupy/api/mambustruct.py,sha256=
|
|
60
|
+
mambupy/api/mambustruct.py,sha256=RQi837WYHEf9hAfZQp2uCH04DbKaEKeGw99nPEsr6Ek,30766
|
|
61
61
|
mambupy/api/mambutask.py,sha256=pZ71_SjpNs5ZVAXuIKi84gpq56a8lbj8HZLhn_c7iWk,2552
|
|
62
62
|
mambupy/api/mambutransaction.py,sha256=epr09hmEQbyq_eS1p1NFRvqNKR0febBfL6WvcLAmUWE,1837
|
|
63
63
|
mambupy/api/mambuuser.py,sha256=PkW64XFSxRzUEOc2-D2yfJkAKwq6cNyCMecKSmaPoKY,3488
|
|
64
64
|
mambupy/api/vos.py,sha256=ppsHTITgQ_waymyV9-lxUpBTCC3TsfQpw51ZbTMlcXM,2590
|
|
65
65
|
mambupy/api/connector/__init__.py,sha256=gTRQao9uSbJ-iDO-CuH4rLGNsVgrKDVeN2nau6t5o7Q,173
|
|
66
66
|
mambupy/api/connector/mambuconnector.py,sha256=1sp9UYybtpTAUrW4RhHYQ-O-xvATwwP1VCQOFAkAmDw,9847
|
|
67
|
-
mambupy/api/connector/rest.py,sha256=
|
|
67
|
+
mambupy/api/connector/rest.py,sha256=LOSWR1nRT22qcOejVYt4LR1Hcetp8at1Z43bUNV1a_o,34068
|
|
68
68
|
mambupy/orm/__init__.py,sha256=U8zAMqm-W-5c2QaLmcbxR0l2g0o6gUw26w0TgrjRsHM,801
|
|
69
69
|
mambupy/orm/schema_activities.py,sha256=uKva4zqloesXSGJxth8w4GN-zz8PC1IIpQMs8pDalAI,3391
|
|
70
70
|
mambupy/orm/schema_addresses.py,sha256=dLvEISuZuo6faWK1i3iU56vmwnrM16X5X40IhWOq4Ec,1598
|
|
@@ -84,8 +84,8 @@ mambupy/rest/mambuactivity.py,sha256=lpLkTO4mtn3VfsmobAlWeAWqXWCTQ4I2f7koNiDIG54
|
|
|
84
84
|
mambupy/utils/__init__.py,sha256=xHJUVRE3PdETa8SPaUwdgKQfx8iMOAurSmnm7ROND_k,119
|
|
85
85
|
mambupy/utils/update_user_roles.py,sha256=CUkkBv2HJ-LaczmYu9Bud269Kb9NisJDoli64KZ1BeI,14675
|
|
86
86
|
mambupy/utils/userdeactivate.py,sha256=Rjlznm2KW23bqnrR_ogB36-O2rIHcBn9xTJdVIMynZQ,3134
|
|
87
|
-
mambupy-2.3.
|
|
88
|
-
mambupy-2.3.
|
|
89
|
-
mambupy-2.3.
|
|
90
|
-
mambupy-2.3.
|
|
91
|
-
mambupy-2.3.
|
|
87
|
+
mambupy-2.3.3.dist-info/licenses/LICENSE,sha256=8MsXxZQL8tpxeo5kLHxQ-46sKu11g-_YOu0YnceJhuA,35165
|
|
88
|
+
mambupy-2.3.3.dist-info/METADATA,sha256=9tCwdkeZMK-xrn1fwF4jVgIehuCVw7MuBJv6VPSEkVU,43288
|
|
89
|
+
mambupy-2.3.3.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
90
|
+
mambupy-2.3.3.dist-info/top_level.txt,sha256=60TnLWD6zn0VyfBdMGCHI1_RLrKyG-anEUDQeGNpn9g,16
|
|
91
|
+
mambupy-2.3.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|