MambuPy 2.3.2__tar.gz → 2.3.3__tar.gz
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-2.3.2 → mambupy-2.3.3}/MambuPy/__init__.py +1 -1
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/api/classes.py +18 -19
- {mambupy-2.3.2/mambupy → mambupy-2.3.3/MambuPy}/api/connector/rest.py +14 -4
- {mambupy-2.3.2/mambupy → mambupy-2.3.3/MambuPy}/api/entities.py +13 -12
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/api/mambuloan.py +3 -2
- {mambupy-2.3.2/mambupy → mambupy-2.3.3/MambuPy}/api/mambustruct.py +8 -2
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/mambuutil.py +69 -9
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy.egg-info/PKG-INFO +2 -1
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy.egg-info/requires.txt +1 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/PKG-INFO +2 -1
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/__init__.py +1 -1
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/api/classes.py +18 -19
- {mambupy-2.3.2/MambuPy → mambupy-2.3.3/mambupy}/api/connector/rest.py +14 -4
- {mambupy-2.3.2/MambuPy → mambupy-2.3.3/mambupy}/api/entities.py +13 -12
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/api/mambuloan.py +3 -2
- {mambupy-2.3.2/MambuPy → mambupy-2.3.3/mambupy}/api/mambustruct.py +8 -2
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/mambuutil.py +69 -9
- {mambupy-2.3.2 → mambupy-2.3.3}/pyproject.toml +1 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/LICENSE +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/api/__init__.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/api/connector/__init__.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/api/connector/mambuconnector.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/api/interfaces.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/api/mambubranch.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/api/mambucentre.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/api/mambuclient.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/api/mambucustomfield.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/api/mambugroup.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/api/mambuproduct.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/api/mamburole.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/api/mambutask.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/api/mambutransaction.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/api/mambuuser.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/api/vos.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/mambuconfig.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/mambugeturl.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/orm/__init__.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/orm/schema_activities.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/orm/schema_addresses.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/orm/schema_branches.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/orm/schema_centres.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/orm/schema_clients.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/orm/schema_customfields.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/orm/schema_dummies.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/orm/schema_groups.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/orm/schema_loans.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/orm/schema_mambu.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/orm/schema_orm.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/orm/schema_tasks.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/orm/schema_users.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/rest/__init__.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/rest/mambuactivity.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/utils/__init__.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/utils/update_user_roles.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy/utils/userdeactivate.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy.egg-info/SOURCES.txt +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy.egg-info/dependency_links.txt +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/MambuPy.egg-info/top_level.txt +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/README.md +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/api/__init__.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/api/connector/__init__.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/api/connector/mambuconnector.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/api/interfaces.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/api/mambubranch.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/api/mambucentre.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/api/mambuclient.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/api/mambucustomfield.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/api/mambugroup.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/api/mambuproduct.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/api/mamburole.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/api/mambutask.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/api/mambutransaction.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/api/mambuuser.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/api/vos.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/mambuconfig.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/mambugeturl.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/orm/__init__.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/orm/schema_activities.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/orm/schema_addresses.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/orm/schema_branches.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/orm/schema_centres.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/orm/schema_clients.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/orm/schema_customfields.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/orm/schema_dummies.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/orm/schema_groups.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/orm/schema_loans.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/orm/schema_mambu.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/orm/schema_orm.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/orm/schema_tasks.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/orm/schema_users.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/rest/__init__.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/rest/mambuactivity.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/utils/__init__.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/utils/update_user_roles.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/mambupy/utils/userdeactivate.py +0 -0
- {mambupy-2.3.2 → mambupy-2.3.3}/setup.cfg +0 -0
|
@@ -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
|
|
@@ -13,6 +13,7 @@ import datetime
|
|
|
13
13
|
import enum
|
|
14
14
|
import json
|
|
15
15
|
import mimetypes
|
|
16
|
+
import orjson
|
|
16
17
|
import os
|
|
17
18
|
import re
|
|
18
19
|
import uuid
|
|
@@ -62,6 +63,11 @@ class _MambuJSONEncoder(json.JSONEncoder):
|
|
|
62
63
|
def _configure_retry_strategy(session, retries=5):
|
|
63
64
|
"""Configure retry strategy for a session.
|
|
64
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
|
+
|
|
65
71
|
Args:
|
|
66
72
|
session (requests.Session): The session to configure
|
|
67
73
|
retries (int, optional): Number of retries. Defaults to 5.
|
|
@@ -81,7 +87,11 @@ def _configure_retry_strategy(session, retries=5):
|
|
|
81
87
|
"PATCH",
|
|
82
88
|
],
|
|
83
89
|
)
|
|
84
|
-
adapter = HTTPAdapter(
|
|
90
|
+
adapter = HTTPAdapter(
|
|
91
|
+
max_retries=retry_strategy,
|
|
92
|
+
pool_connections=50,
|
|
93
|
+
pool_maxsize=50,
|
|
94
|
+
)
|
|
85
95
|
session.mount("https://", adapter)
|
|
86
96
|
session.mount("http://", adapter)
|
|
87
97
|
|
|
@@ -224,7 +234,7 @@ url %s, params %s, data %s, headers %s",
|
|
|
224
234
|
if hasattr(resp, "content"): # pragma: no cover
|
|
225
235
|
logger.warning("HTTPError, resp content: %s", resp.content)
|
|
226
236
|
try:
|
|
227
|
-
content =
|
|
237
|
+
content = orjson.loads(resp.content.decode())
|
|
228
238
|
except ValueError:
|
|
229
239
|
# in case resp.content doesn't conforms to json
|
|
230
240
|
content = {
|
|
@@ -300,7 +310,7 @@ url %s, params %s, data %s, headers %s",
|
|
|
300
310
|
def __list_request_cat_response(self, list_resp, resp):
|
|
301
311
|
if list_resp == b"":
|
|
302
312
|
list_resp = resp
|
|
303
|
-
elif len(
|
|
313
|
+
elif len(orjson.loads(resp.decode())) > 0:
|
|
304
314
|
list_resp = list_resp[:-1] + b"," + resp[1:]
|
|
305
315
|
|
|
306
316
|
return list_resp
|
|
@@ -340,7 +350,7 @@ url %s, params %s, data %s, headers %s",
|
|
|
340
350
|
method, url, params=copy.copy(params), data=copy.copy(data)
|
|
341
351
|
)
|
|
342
352
|
|
|
343
|
-
jsonresp = list(
|
|
353
|
+
jsonresp = list(orjson.loads(resp.decode()))
|
|
344
354
|
if len(jsonresp) < limit:
|
|
345
355
|
window = False
|
|
346
356
|
|
|
@@ -10,6 +10,7 @@ import datetime
|
|
|
10
10
|
import enum
|
|
11
11
|
from importlib import import_module
|
|
12
12
|
import json
|
|
13
|
+
import orjson
|
|
13
14
|
import time
|
|
14
15
|
|
|
15
16
|
from .classes import GenericClass
|
|
@@ -240,7 +241,7 @@ class MambuEntity(MambuStruct):
|
|
|
240
241
|
|
|
241
242
|
logger.debug("request several entities %s", cls.__name__)
|
|
242
243
|
list_resp = get_func(prefix, **params)
|
|
243
|
-
jsonresp = list(
|
|
244
|
+
jsonresp = list(orjson.loads(list_resp.decode()))
|
|
244
245
|
logger.debug("%s, %s retrieved", cls.__name__, len(jsonresp))
|
|
245
246
|
|
|
246
247
|
elements = []
|
|
@@ -312,8 +313,8 @@ class MambuEntity(MambuStruct):
|
|
|
312
313
|
instance = cls.__build_object(
|
|
313
314
|
connector=connector,
|
|
314
315
|
resp=resp,
|
|
315
|
-
attrs=dict(
|
|
316
|
-
tzattrs=dict(
|
|
316
|
+
attrs=dict(orjson.loads(resp.decode())),
|
|
317
|
+
tzattrs=dict(orjson.loads(resp.decode())),
|
|
317
318
|
get_entities=get_entities,
|
|
318
319
|
detailsLevel=detailsLevel,
|
|
319
320
|
debug=debug,
|
|
@@ -355,8 +356,8 @@ class MambuEntity(MambuStruct):
|
|
|
355
356
|
self.id, prefix=self._prefix, detailsLevel=detailsLevel
|
|
356
357
|
)
|
|
357
358
|
|
|
358
|
-
self._attrs.update(dict(
|
|
359
|
-
self._tzattrs = dict(
|
|
359
|
+
self._attrs.update(dict(orjson.loads(self._resp.decode())))
|
|
360
|
+
self._tzattrs = dict(orjson.loads(self._resp.decode()))
|
|
360
361
|
self._convertDict2Attrs()
|
|
361
362
|
self._extractCustomFields()
|
|
362
363
|
self._extractVOs()
|
|
@@ -472,8 +473,8 @@ class MambuEntityWritable(MambuStruct, MambuWritable):
|
|
|
472
473
|
self._resp = self._connector.mambu_create(
|
|
473
474
|
self._prefix, copy.deepcopy(self._attrs), **kwargs
|
|
474
475
|
)
|
|
475
|
-
self._attrs.update(dict(
|
|
476
|
-
self._tzattrs = dict(
|
|
476
|
+
self._attrs.update(dict(orjson.loads(self._resp.decode())))
|
|
477
|
+
self._tzattrs = dict(orjson.loads(self._resp.decode()))
|
|
477
478
|
self._detailsLevel = "FULL"
|
|
478
479
|
except MambuError:
|
|
479
480
|
raise
|
|
@@ -573,7 +574,7 @@ class MambuEntityWritable(MambuStruct, MambuWritable):
|
|
|
573
574
|
# build fields_ops param with what detected using previous rules
|
|
574
575
|
# strings: (OP, PATH) (remove) or (OP, PATH, VALUE) (all else)
|
|
575
576
|
fields_ops = []
|
|
576
|
-
original_attrs = dict(
|
|
577
|
+
original_attrs = dict(orjson.loads(self._resp.decode()))
|
|
577
578
|
self._extractCustomFields(original_attrs)
|
|
578
579
|
self._updateVOs()
|
|
579
580
|
for field in fields:
|
|
@@ -709,7 +710,7 @@ class MambuEntityAttachable(MambuStruct, MambuAttachable):
|
|
|
709
710
|
notes=notes,
|
|
710
711
|
)
|
|
711
712
|
|
|
712
|
-
doc = MambuDocument(**dict(
|
|
713
|
+
doc = MambuDocument(**dict(orjson.loads(response.decode())))
|
|
713
714
|
self._attachments[str(doc["id"])] = doc
|
|
714
715
|
|
|
715
716
|
return response
|
|
@@ -742,7 +743,7 @@ class MambuEntityAttachable(MambuStruct, MambuAttachable):
|
|
|
742
743
|
paginationDetails=paginationDetails,
|
|
743
744
|
)
|
|
744
745
|
|
|
745
|
-
metadata_list =
|
|
746
|
+
metadata_list = orjson.loads(response.decode())
|
|
746
747
|
|
|
747
748
|
for metadata in metadata_list:
|
|
748
749
|
doc = MambuDocument(**metadata)
|
|
@@ -831,7 +832,7 @@ class MambuEntityCommentable(MambuStruct, MambuCommentable):
|
|
|
831
832
|
paginationDetails=paginationDetails,
|
|
832
833
|
)
|
|
833
834
|
|
|
834
|
-
comments_list =
|
|
835
|
+
comments_list = orjson.loads(response.decode())
|
|
835
836
|
|
|
836
837
|
for comment in comments_list:
|
|
837
838
|
comm = MambuComment(**comment)
|
|
@@ -854,7 +855,7 @@ class MambuEntityCommentable(MambuStruct, MambuCommentable):
|
|
|
854
855
|
owner_id=self.id, owner_type=self._ownerType, text=comment
|
|
855
856
|
)
|
|
856
857
|
|
|
857
|
-
comment = MambuComment(**dict(
|
|
858
|
+
comment = MambuComment(**dict(orjson.loads(response.decode())))
|
|
858
859
|
self._comments.insert(0, comment)
|
|
859
860
|
|
|
860
861
|
return response
|
|
@@ -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(
|
|
@@ -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:]
|
|
@@ -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,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"
|
|
@@ -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
|
|
@@ -13,6 +13,7 @@ import datetime
|
|
|
13
13
|
import enum
|
|
14
14
|
import json
|
|
15
15
|
import mimetypes
|
|
16
|
+
import orjson
|
|
16
17
|
import os
|
|
17
18
|
import re
|
|
18
19
|
import uuid
|
|
@@ -62,6 +63,11 @@ class _MambuJSONEncoder(json.JSONEncoder):
|
|
|
62
63
|
def _configure_retry_strategy(session, retries=5):
|
|
63
64
|
"""Configure retry strategy for a session.
|
|
64
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
|
+
|
|
65
71
|
Args:
|
|
66
72
|
session (requests.Session): The session to configure
|
|
67
73
|
retries (int, optional): Number of retries. Defaults to 5.
|
|
@@ -81,7 +87,11 @@ def _configure_retry_strategy(session, retries=5):
|
|
|
81
87
|
"PATCH",
|
|
82
88
|
],
|
|
83
89
|
)
|
|
84
|
-
adapter = HTTPAdapter(
|
|
90
|
+
adapter = HTTPAdapter(
|
|
91
|
+
max_retries=retry_strategy,
|
|
92
|
+
pool_connections=50,
|
|
93
|
+
pool_maxsize=50,
|
|
94
|
+
)
|
|
85
95
|
session.mount("https://", adapter)
|
|
86
96
|
session.mount("http://", adapter)
|
|
87
97
|
|
|
@@ -224,7 +234,7 @@ url %s, params %s, data %s, headers %s",
|
|
|
224
234
|
if hasattr(resp, "content"): # pragma: no cover
|
|
225
235
|
logger.warning("HTTPError, resp content: %s", resp.content)
|
|
226
236
|
try:
|
|
227
|
-
content =
|
|
237
|
+
content = orjson.loads(resp.content.decode())
|
|
228
238
|
except ValueError:
|
|
229
239
|
# in case resp.content doesn't conforms to json
|
|
230
240
|
content = {
|
|
@@ -300,7 +310,7 @@ url %s, params %s, data %s, headers %s",
|
|
|
300
310
|
def __list_request_cat_response(self, list_resp, resp):
|
|
301
311
|
if list_resp == b"":
|
|
302
312
|
list_resp = resp
|
|
303
|
-
elif len(
|
|
313
|
+
elif len(orjson.loads(resp.decode())) > 0:
|
|
304
314
|
list_resp = list_resp[:-1] + b"," + resp[1:]
|
|
305
315
|
|
|
306
316
|
return list_resp
|
|
@@ -340,7 +350,7 @@ url %s, params %s, data %s, headers %s",
|
|
|
340
350
|
method, url, params=copy.copy(params), data=copy.copy(data)
|
|
341
351
|
)
|
|
342
352
|
|
|
343
|
-
jsonresp = list(
|
|
353
|
+
jsonresp = list(orjson.loads(resp.decode()))
|
|
344
354
|
if len(jsonresp) < limit:
|
|
345
355
|
window = False
|
|
346
356
|
|
|
@@ -10,6 +10,7 @@ import datetime
|
|
|
10
10
|
import enum
|
|
11
11
|
from importlib import import_module
|
|
12
12
|
import json
|
|
13
|
+
import orjson
|
|
13
14
|
import time
|
|
14
15
|
|
|
15
16
|
from .classes import GenericClass
|
|
@@ -240,7 +241,7 @@ class MambuEntity(MambuStruct):
|
|
|
240
241
|
|
|
241
242
|
logger.debug("request several entities %s", cls.__name__)
|
|
242
243
|
list_resp = get_func(prefix, **params)
|
|
243
|
-
jsonresp = list(
|
|
244
|
+
jsonresp = list(orjson.loads(list_resp.decode()))
|
|
244
245
|
logger.debug("%s, %s retrieved", cls.__name__, len(jsonresp))
|
|
245
246
|
|
|
246
247
|
elements = []
|
|
@@ -312,8 +313,8 @@ class MambuEntity(MambuStruct):
|
|
|
312
313
|
instance = cls.__build_object(
|
|
313
314
|
connector=connector,
|
|
314
315
|
resp=resp,
|
|
315
|
-
attrs=dict(
|
|
316
|
-
tzattrs=dict(
|
|
316
|
+
attrs=dict(orjson.loads(resp.decode())),
|
|
317
|
+
tzattrs=dict(orjson.loads(resp.decode())),
|
|
317
318
|
get_entities=get_entities,
|
|
318
319
|
detailsLevel=detailsLevel,
|
|
319
320
|
debug=debug,
|
|
@@ -355,8 +356,8 @@ class MambuEntity(MambuStruct):
|
|
|
355
356
|
self.id, prefix=self._prefix, detailsLevel=detailsLevel
|
|
356
357
|
)
|
|
357
358
|
|
|
358
|
-
self._attrs.update(dict(
|
|
359
|
-
self._tzattrs = dict(
|
|
359
|
+
self._attrs.update(dict(orjson.loads(self._resp.decode())))
|
|
360
|
+
self._tzattrs = dict(orjson.loads(self._resp.decode()))
|
|
360
361
|
self._convertDict2Attrs()
|
|
361
362
|
self._extractCustomFields()
|
|
362
363
|
self._extractVOs()
|
|
@@ -472,8 +473,8 @@ class MambuEntityWritable(MambuStruct, MambuWritable):
|
|
|
472
473
|
self._resp = self._connector.mambu_create(
|
|
473
474
|
self._prefix, copy.deepcopy(self._attrs), **kwargs
|
|
474
475
|
)
|
|
475
|
-
self._attrs.update(dict(
|
|
476
|
-
self._tzattrs = dict(
|
|
476
|
+
self._attrs.update(dict(orjson.loads(self._resp.decode())))
|
|
477
|
+
self._tzattrs = dict(orjson.loads(self._resp.decode()))
|
|
477
478
|
self._detailsLevel = "FULL"
|
|
478
479
|
except MambuError:
|
|
479
480
|
raise
|
|
@@ -573,7 +574,7 @@ class MambuEntityWritable(MambuStruct, MambuWritable):
|
|
|
573
574
|
# build fields_ops param with what detected using previous rules
|
|
574
575
|
# strings: (OP, PATH) (remove) or (OP, PATH, VALUE) (all else)
|
|
575
576
|
fields_ops = []
|
|
576
|
-
original_attrs = dict(
|
|
577
|
+
original_attrs = dict(orjson.loads(self._resp.decode()))
|
|
577
578
|
self._extractCustomFields(original_attrs)
|
|
578
579
|
self._updateVOs()
|
|
579
580
|
for field in fields:
|
|
@@ -709,7 +710,7 @@ class MambuEntityAttachable(MambuStruct, MambuAttachable):
|
|
|
709
710
|
notes=notes,
|
|
710
711
|
)
|
|
711
712
|
|
|
712
|
-
doc = MambuDocument(**dict(
|
|
713
|
+
doc = MambuDocument(**dict(orjson.loads(response.decode())))
|
|
713
714
|
self._attachments[str(doc["id"])] = doc
|
|
714
715
|
|
|
715
716
|
return response
|
|
@@ -742,7 +743,7 @@ class MambuEntityAttachable(MambuStruct, MambuAttachable):
|
|
|
742
743
|
paginationDetails=paginationDetails,
|
|
743
744
|
)
|
|
744
745
|
|
|
745
|
-
metadata_list =
|
|
746
|
+
metadata_list = orjson.loads(response.decode())
|
|
746
747
|
|
|
747
748
|
for metadata in metadata_list:
|
|
748
749
|
doc = MambuDocument(**metadata)
|
|
@@ -831,7 +832,7 @@ class MambuEntityCommentable(MambuStruct, MambuCommentable):
|
|
|
831
832
|
paginationDetails=paginationDetails,
|
|
832
833
|
)
|
|
833
834
|
|
|
834
|
-
comments_list =
|
|
835
|
+
comments_list = orjson.loads(response.decode())
|
|
835
836
|
|
|
836
837
|
for comment in comments_list:
|
|
837
838
|
comm = MambuComment(**comment)
|
|
@@ -854,7 +855,7 @@ class MambuEntityCommentable(MambuStruct, MambuCommentable):
|
|
|
854
855
|
owner_id=self.id, owner_type=self._ownerType, text=comment
|
|
855
856
|
)
|
|
856
857
|
|
|
857
|
-
comment = MambuComment(**dict(
|
|
858
|
+
comment = MambuComment(**dict(orjson.loads(response.decode())))
|
|
858
859
|
self._comments.insert(0, comment)
|
|
859
860
|
|
|
860
861
|
return response
|
|
@@ -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(
|
|
@@ -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:]
|
|
@@ -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(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|