appier 1.31.4__py2.py3-none-any.whl → 1.32.0__py2.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.
- appier/__init__.py +333 -52
- appier/amqp.py +29 -30
- appier/api.py +214 -212
- appier/asgi.py +54 -55
- appier/async_neo.py +46 -35
- appier/async_old.py +55 -42
- appier/asynchronous.py +7 -13
- appier/base.py +1762 -1429
- appier/bus.py +51 -52
- appier/cache.py +99 -84
- appier/common.py +9 -11
- appier/component.py +17 -19
- appier/compress.py +25 -28
- appier/config.py +96 -73
- appier/controller.py +9 -15
- appier/crypt.py +25 -21
- appier/data.py +73 -57
- appier/defines.py +191 -226
- appier/exceptions.py +103 -63
- appier/execution.py +94 -88
- appier/export.py +90 -88
- appier/extra.py +6 -13
- appier/extra_neo.py +8 -11
- appier/extra_old.py +18 -16
- appier/geo.py +57 -47
- appier/git.py +101 -90
- appier/graph.py +23 -24
- appier/http.py +520 -398
- appier/legacy.py +373 -180
- appier/log.py +90 -97
- appier/meta.py +42 -42
- appier/mock.py +32 -34
- appier/model.py +793 -681
- appier/model_a.py +208 -183
- appier/mongo.py +183 -107
- appier/observer.py +39 -31
- appier/part.py +23 -24
- appier/preferences.py +44 -47
- appier/queuing.py +78 -96
- appier/redisdb.py +40 -35
- appier/request.py +227 -175
- appier/scheduler.py +13 -18
- appier/serialize.py +37 -31
- appier/session.py +161 -147
- appier/settings.py +2 -11
- appier/smtp.py +53 -49
- appier/storage.py +39 -33
- appier/structures.py +50 -45
- appier/test/__init__.py +2 -11
- appier/test/base.py +111 -108
- appier/test/cache.py +28 -35
- appier/test/config.py +10 -19
- appier/test/crypt.py +3 -12
- appier/test/data.py +3 -12
- appier/test/exceptions.py +8 -17
- appier/test/export.py +16 -33
- appier/test/graph.py +27 -60
- appier/test/http.py +42 -54
- appier/test/legacy.py +20 -30
- appier/test/log.py +14 -35
- appier/test/mock.py +27 -123
- appier/test/model.py +79 -91
- appier/test/part.py +5 -14
- appier/test/preferences.py +5 -13
- appier/test/queuing.py +29 -37
- appier/test/request.py +61 -73
- appier/test/serialize.py +12 -23
- appier/test/session.py +10 -19
- appier/test/smtp.py +8 -14
- appier/test/structures.py +20 -24
- appier/test/typesf.py +14 -28
- appier/test/util.py +480 -438
- appier/typesf.py +251 -171
- appier/util.py +578 -407
- appier/validation.py +280 -143
- {appier-1.31.4.dist-info → appier-1.32.0.dist-info}/METADATA +6 -1
- appier-1.32.0.dist-info/RECORD +86 -0
- appier-1.31.4.dist-info/RECORD +0 -86
- {appier-1.31.4.dist-info → appier-1.32.0.dist-info}/LICENSE +0 -0
- {appier-1.31.4.dist-info → appier-1.32.0.dist-info}/WHEEL +0 -0
- {appier-1.31.4.dist-info → appier-1.32.0.dist-info}/top_level.txt +0 -0
appier/execution.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
|
|
4
4
|
# Hive Appier Framework
|
|
5
|
-
# Copyright (c) 2008-
|
|
5
|
+
# Copyright (c) 2008-2024 Hive Solutions Lda.
|
|
6
6
|
#
|
|
7
7
|
# This file is part of Hive Appier Framework.
|
|
8
8
|
#
|
|
@@ -22,16 +22,7 @@
|
|
|
22
22
|
__author__ = "João Magalhães <joamag@hive.pt>"
|
|
23
23
|
""" The author(s) of the module """
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
""" The version of the module """
|
|
27
|
-
|
|
28
|
-
__revision__ = "$LastChangedRevision$"
|
|
29
|
-
""" The revision number of the module """
|
|
30
|
-
|
|
31
|
-
__date__ = "$LastChangedDate$"
|
|
32
|
-
""" The last change date of the module """
|
|
33
|
-
|
|
34
|
-
__copyright__ = "Copyright (c) 2008-2022 Hive Solutions Lda."
|
|
25
|
+
__copyright__ = "Copyright (c) 2008-2024 Hive Solutions Lda."
|
|
35
26
|
""" The copyright for the module """
|
|
36
27
|
|
|
37
28
|
__license__ = "Apache License, Version 2.0"
|
|
@@ -61,6 +52,7 @@ background_t = None
|
|
|
61
52
|
""" The background execution task to be started by
|
|
62
53
|
the appier execution system (global value) """
|
|
63
54
|
|
|
55
|
+
|
|
64
56
|
class ExecutionThread(threading.Thread):
|
|
65
57
|
"""
|
|
66
58
|
The thread to be used in the execution of "random"
|
|
@@ -88,7 +80,7 @@ class ExecutionThread(threading.Thread):
|
|
|
88
80
|
Constructor of the class.
|
|
89
81
|
"""
|
|
90
82
|
|
|
91
|
-
threading.Thread.__init__(self, name
|
|
83
|
+
threading.Thread.__init__(self, name="Execution")
|
|
92
84
|
|
|
93
85
|
self.daemon = True
|
|
94
86
|
self.work_list = []
|
|
@@ -118,7 +110,8 @@ class ExecutionThread(threading.Thread):
|
|
|
118
110
|
while True:
|
|
119
111
|
# in case there is no work pending to be
|
|
120
112
|
# executed must exist immediately
|
|
121
|
-
if not self.work_list:
|
|
113
|
+
if not self.work_list:
|
|
114
|
+
break
|
|
122
115
|
|
|
123
116
|
# retrieves the current work tuple to
|
|
124
117
|
# be used and executes it in case the
|
|
@@ -144,18 +137,20 @@ class ExecutionThread(threading.Thread):
|
|
|
144
137
|
# executes the "callable" and logs the error in case the
|
|
145
138
|
# execution fails (must be done to log the error) then
|
|
146
139
|
# sets the error flag with the exception variable
|
|
147
|
-
try:
|
|
140
|
+
try:
|
|
141
|
+
callable(*args, **kwargs)
|
|
148
142
|
except Exception as exception:
|
|
149
143
|
error = exception
|
|
150
144
|
lines = traceback.format_exc().splitlines()
|
|
151
145
|
logger = common.base().get_logger()
|
|
152
146
|
logger.warning(str(exception))
|
|
153
|
-
for line in lines:
|
|
147
|
+
for line in lines:
|
|
148
|
+
logger.info(line)
|
|
154
149
|
|
|
155
150
|
# calls the callback method with the currently set error
|
|
156
151
|
# in order to notify the runtime about the problem, only
|
|
157
152
|
# calls the callback in case such method is defined
|
|
158
|
-
callback and callback(error
|
|
153
|
+
callback and callback(error=error)
|
|
159
154
|
|
|
160
155
|
# sleeps for a while so that the process may
|
|
161
156
|
# released for different tasks
|
|
@@ -164,20 +159,25 @@ class ExecutionThread(threading.Thread):
|
|
|
164
159
|
def stop(self):
|
|
165
160
|
self.run_flag = False
|
|
166
161
|
|
|
167
|
-
def insert_work(
|
|
162
|
+
def insert_work(
|
|
163
|
+
self, callable, args=[], kwargs={}, target_time=None, callback=None
|
|
164
|
+
):
|
|
168
165
|
target_time = target_time or time.time()
|
|
169
166
|
work = (target_time, callable, callback, args, kwargs)
|
|
170
167
|
self.work_lock.acquire()
|
|
171
|
-
try:
|
|
172
|
-
|
|
168
|
+
try:
|
|
169
|
+
heapq.heappush(self.work_list, work)
|
|
170
|
+
finally:
|
|
171
|
+
self.work_lock.release()
|
|
173
172
|
|
|
174
|
-
def background(timeout = None):
|
|
175
173
|
|
|
174
|
+
def background(timeout=None):
|
|
176
175
|
def decorator(function):
|
|
177
176
|
_timeout = timeout or 0.0
|
|
178
177
|
|
|
179
|
-
def schedule(error
|
|
180
|
-
if timeout == None and not force:
|
|
178
|
+
def schedule(error=None, force=False):
|
|
179
|
+
if timeout == None and not force:
|
|
180
|
+
return
|
|
181
181
|
target = time.time() + _timeout
|
|
182
182
|
insert_work(function, target, schedule)
|
|
183
183
|
|
|
@@ -187,18 +187,20 @@ def background(timeout = None):
|
|
|
187
187
|
# immediately (nothing to be done, duplicate)
|
|
188
188
|
fname = function.__name__
|
|
189
189
|
exists = fname in BACKGROUND
|
|
190
|
-
if exists:
|
|
190
|
+
if exists:
|
|
191
|
+
return function
|
|
191
192
|
|
|
192
193
|
# runs the scheduling operation on the task and
|
|
193
194
|
# then adds the function name to the list of already
|
|
194
195
|
# registered names
|
|
195
|
-
schedule(force
|
|
196
|
+
schedule(force=True)
|
|
196
197
|
BACKGROUND.append(fname)
|
|
197
198
|
return function
|
|
198
199
|
|
|
199
200
|
return decorator
|
|
200
201
|
|
|
201
|
-
|
|
202
|
+
|
|
203
|
+
def insert_work(callable, args=[], kwargs={}, target_time=None, callback=None):
|
|
202
204
|
"""
|
|
203
205
|
Runs the provided callable (function, method, etc) in a separated
|
|
204
206
|
thread context under submission of a queue system.
|
|
@@ -231,114 +233,116 @@ def insert_work(callable, args = [], kwargs = {}, target_time = None, callback =
|
|
|
231
233
|
"""
|
|
232
234
|
|
|
233
235
|
background_t.insert_work(
|
|
234
|
-
callable,
|
|
235
|
-
args = args,
|
|
236
|
-
kwargs = kwargs,
|
|
237
|
-
target_time = target_time,
|
|
238
|
-
callback = callback
|
|
236
|
+
callable, args=args, kwargs=kwargs, target_time=target_time, callback=callback
|
|
239
237
|
)
|
|
240
238
|
|
|
239
|
+
|
|
241
240
|
def interval_work(
|
|
242
|
-
callable,
|
|
243
|
-
args = [],
|
|
244
|
-
kwargs = {},
|
|
245
|
-
callback = None,
|
|
246
|
-
initial = None,
|
|
247
|
-
interval = 60,
|
|
248
|
-
eval = None
|
|
241
|
+
callable, args=[], kwargs={}, callback=None, initial=None, interval=60, eval=None
|
|
249
242
|
):
|
|
250
243
|
initial = initial or (eval and eval()) or time.time()
|
|
251
244
|
composed = build_composed(callable, initial, interval, eval, callback)
|
|
252
245
|
insert_work(
|
|
253
|
-
composed,
|
|
254
|
-
args = args,
|
|
255
|
-
kwargs = kwargs,
|
|
256
|
-
target_time = initial,
|
|
257
|
-
callback = callback
|
|
246
|
+
composed, args=args, kwargs=kwargs, target_time=initial, callback=callback
|
|
258
247
|
)
|
|
259
248
|
return initial
|
|
260
249
|
|
|
261
|
-
|
|
250
|
+
|
|
251
|
+
def seconds_work(callable, offset=0, *args, **kwargs):
|
|
262
252
|
eval = lambda: seconds_eval(offset)
|
|
263
|
-
return interval_work(callable, eval
|
|
253
|
+
return interval_work(callable, eval=eval, *args, **kwargs)
|
|
264
254
|
|
|
265
|
-
|
|
255
|
+
|
|
256
|
+
def minutes_work(callable, offset=0, *args, **kwargs):
|
|
266
257
|
eval = lambda: minutes_eval(offset)
|
|
267
|
-
return interval_work(callable, eval
|
|
258
|
+
return interval_work(callable, eval=eval, *args, **kwargs)
|
|
259
|
+
|
|
268
260
|
|
|
269
|
-
def hourly_work(callable, offset
|
|
261
|
+
def hourly_work(callable, offset=0, *args, **kwargs):
|
|
270
262
|
eval = lambda: hourly_eval(offset)
|
|
271
|
-
return interval_work(callable, eval
|
|
263
|
+
return interval_work(callable, eval=eval, *args, **kwargs)
|
|
264
|
+
|
|
272
265
|
|
|
273
|
-
def daily_work(callable, offset
|
|
266
|
+
def daily_work(callable, offset=0, *args, **kwargs):
|
|
274
267
|
eval = lambda: daily_eval(offset)
|
|
275
|
-
return interval_work(callable, eval
|
|
268
|
+
return interval_work(callable, eval=eval, *args, **kwargs)
|
|
276
269
|
|
|
277
|
-
|
|
270
|
+
|
|
271
|
+
def weekly_work(callable, weekday=4, offset=0, *args, **kwargs):
|
|
278
272
|
eval = lambda: weekly_eval(weekday, offset)
|
|
279
|
-
return interval_work(callable, eval
|
|
273
|
+
return interval_work(callable, eval=eval, *args, **kwargs)
|
|
274
|
+
|
|
280
275
|
|
|
281
|
-
def monthly_work(callable, monthday
|
|
276
|
+
def monthly_work(callable, monthday=1, offset=0, *args, **kwargs):
|
|
282
277
|
eval = lambda: monthly_eval(monthday, offset)
|
|
283
|
-
return interval_work(callable, eval
|
|
278
|
+
return interval_work(callable, eval=eval, *args, **kwargs)
|
|
284
279
|
|
|
285
|
-
|
|
280
|
+
|
|
281
|
+
def seconds_eval(offset, now=None):
|
|
286
282
|
now = now or datetime.datetime.utcnow()
|
|
287
|
-
next = now + datetime.timedelta(seconds
|
|
283
|
+
next = now + datetime.timedelta(seconds=offset)
|
|
288
284
|
next_tuple = next.utctimetuple()
|
|
289
285
|
return calendar.timegm(next_tuple)
|
|
290
286
|
|
|
291
|
-
|
|
287
|
+
|
|
288
|
+
def minutes_eval(offset, now=None):
|
|
292
289
|
now = now or datetime.datetime.utcnow()
|
|
293
290
|
current = datetime.datetime(
|
|
294
|
-
year = now.
|
|
295
|
-
month = now.month,
|
|
296
|
-
day = now.day,
|
|
297
|
-
hour = now.hour,
|
|
298
|
-
minute = now.minute
|
|
291
|
+
year=now.year, month=now.month, day=now.day, hour=now.hour, minute=now.minute
|
|
299
292
|
)
|
|
300
|
-
next = current + datetime.timedelta(minutes
|
|
293
|
+
next = current + datetime.timedelta(minutes=1, seconds=offset)
|
|
301
294
|
next_tuple = next.utctimetuple()
|
|
302
295
|
return calendar.timegm(next_tuple)
|
|
303
296
|
|
|
304
|
-
|
|
297
|
+
|
|
298
|
+
def hourly_eval(offset, now=None):
|
|
305
299
|
now = now or datetime.datetime.utcnow()
|
|
306
|
-
current = datetime.datetime(
|
|
307
|
-
|
|
300
|
+
current = datetime.datetime(
|
|
301
|
+
year=now.year, month=now.month, day=now.day, hour=now.hour
|
|
302
|
+
)
|
|
303
|
+
next = current + datetime.timedelta(hours=1, seconds=offset)
|
|
308
304
|
next_tuple = next.utctimetuple()
|
|
309
305
|
return calendar.timegm(next_tuple)
|
|
310
306
|
|
|
311
|
-
|
|
307
|
+
|
|
308
|
+
def daily_eval(offset, now=None):
|
|
312
309
|
now = now or datetime.datetime.utcnow()
|
|
313
|
-
today = datetime.datetime(year
|
|
314
|
-
tomorrow = today + datetime.timedelta(days
|
|
310
|
+
today = datetime.datetime(year=now.year, month=now.month, day=now.day)
|
|
311
|
+
tomorrow = today + datetime.timedelta(days=1, seconds=offset)
|
|
315
312
|
tomorrow_tuple = tomorrow.utctimetuple()
|
|
316
313
|
return calendar.timegm(tomorrow_tuple)
|
|
317
314
|
|
|
318
|
-
|
|
315
|
+
|
|
316
|
+
def weekly_eval(weekday, offset, now=None):
|
|
319
317
|
now = now or datetime.datetime.utcnow()
|
|
320
|
-
today = datetime.datetime(year
|
|
318
|
+
today = datetime.datetime(year=now.year, month=now.month, day=now.day)
|
|
321
319
|
distance = (weekday - today.weekday()) % 7
|
|
322
|
-
weekday = today + datetime.timedelta(days
|
|
323
|
-
if weekday < now:
|
|
320
|
+
weekday = today + datetime.timedelta(days=distance, seconds=offset)
|
|
321
|
+
if weekday < now:
|
|
322
|
+
weekday += datetime.timedelta(days=7)
|
|
324
323
|
weekday_tuple = weekday.utctimetuple()
|
|
325
324
|
return calendar.timegm(weekday_tuple)
|
|
326
325
|
|
|
327
|
-
|
|
326
|
+
|
|
327
|
+
def monthly_eval(monthday, offset, now=None):
|
|
328
328
|
now = now or datetime.datetime.utcnow()
|
|
329
|
-
next_year, next_month = (
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
329
|
+
next_year, next_month = (
|
|
330
|
+
(now.year + 1, 1) if now.month == 12 else (now.year, now.month + 1)
|
|
331
|
+
)
|
|
332
|
+
if now.day > monthday:
|
|
333
|
+
month, year = (next_month, next_year)
|
|
334
|
+
else:
|
|
335
|
+
month, year = (now.month, now.year)
|
|
336
|
+
monthday = datetime.datetime(year=year, month=month, day=monthday)
|
|
337
|
+
monthday = monthday + datetime.timedelta(seconds=offset)
|
|
334
338
|
if monthday < now:
|
|
335
|
-
monthday = datetime.datetime(year
|
|
336
|
-
monthday += datetime.timedelta(seconds
|
|
339
|
+
monthday = datetime.datetime(year=next_year, month=next_month, day=monthday.day)
|
|
340
|
+
monthday += datetime.timedelta(seconds=offset)
|
|
337
341
|
monthday_tuple = monthday.utctimetuple()
|
|
338
342
|
return calendar.timegm(monthday_tuple)
|
|
339
343
|
|
|
340
|
-
def build_composed(callable, target_time, interval, eval, callback):
|
|
341
344
|
|
|
345
|
+
def build_composed(callable, target_time, interval, eval, callback):
|
|
342
346
|
def composed(*args, **kwargs):
|
|
343
347
|
try:
|
|
344
348
|
# runs the initial callable, propagating the provided normal arguments
|
|
@@ -358,18 +362,20 @@ def build_composed(callable, target_time, interval, eval, callback):
|
|
|
358
362
|
final = time.time()
|
|
359
363
|
delta = final - target_time
|
|
360
364
|
is_valid = delta < interval
|
|
361
|
-
if is_valid:
|
|
362
|
-
|
|
365
|
+
if is_valid:
|
|
366
|
+
next_time = target_time + interval
|
|
367
|
+
else:
|
|
368
|
+
next_time = final + interval
|
|
363
369
|
|
|
364
370
|
# builds a new callable (composed) method taking into account the state and
|
|
365
371
|
# inserts the work unit again into the queue of processing
|
|
366
372
|
composed = build_composed(callable, next_time, interval, eval, callback)
|
|
367
373
|
insert_work(
|
|
368
374
|
composed,
|
|
369
|
-
args
|
|
370
|
-
kwargs
|
|
371
|
-
target_time
|
|
372
|
-
callback
|
|
375
|
+
args=args,
|
|
376
|
+
kwargs=kwargs,
|
|
377
|
+
target_time=next_time,
|
|
378
|
+
callback=callback,
|
|
373
379
|
)
|
|
374
380
|
|
|
375
381
|
# returns the current result from the original callable to the calling method,
|
appier/export.py
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# -*- coding: utf-8 -*-
|
|
3
3
|
|
|
4
4
|
# Hive Appier Framework
|
|
5
|
-
# Copyright (c) 2008-
|
|
5
|
+
# Copyright (c) 2008-2024 Hive Solutions Lda.
|
|
6
6
|
#
|
|
7
7
|
# This file is part of Hive Appier Framework.
|
|
8
8
|
#
|
|
@@ -22,16 +22,7 @@
|
|
|
22
22
|
__author__ = "João Magalhães <joamag@hive.pt>"
|
|
23
23
|
""" The author(s) of the module """
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
""" The version of the module """
|
|
27
|
-
|
|
28
|
-
__revision__ = "$LastChangedRevision$"
|
|
29
|
-
""" The revision number of the module """
|
|
30
|
-
|
|
31
|
-
__date__ = "$LastChangedDate$"
|
|
32
|
-
""" The last change date of the module """
|
|
33
|
-
|
|
34
|
-
__copyright__ = "Copyright (c) 2008-2022 Hive Solutions Lda."
|
|
25
|
+
__copyright__ = "Copyright (c) 2008-2024 Hive Solutions Lda."
|
|
35
26
|
""" The copyright for the module """
|
|
36
27
|
|
|
37
28
|
__license__ = "Apache License, Version 2.0"
|
|
@@ -66,18 +57,18 @@ basically adds new fields or updates existing fields in a previously
|
|
|
66
57
|
existing document, this strategy does not remove extra fields existing
|
|
67
58
|
in the previous document """
|
|
68
59
|
|
|
69
|
-
class ExportManager(object):
|
|
70
60
|
|
|
61
|
+
class ExportManager(object):
|
|
71
62
|
adapter = None
|
|
72
63
|
single = None
|
|
73
64
|
multiple = None
|
|
74
65
|
|
|
75
|
-
def __init__(self, adapter, single
|
|
66
|
+
def __init__(self, adapter, single=(), multiple=()):
|
|
76
67
|
self.adapter = adapter
|
|
77
68
|
self.single = single
|
|
78
69
|
self.multiple = multiple
|
|
79
70
|
|
|
80
|
-
def import_data(self, file_path, policy
|
|
71
|
+
def import_data(self, file_path, policy=IGNORE):
|
|
81
72
|
temporary_path = tempfile.mkdtemp()
|
|
82
73
|
base_path = temporary_path
|
|
83
74
|
single_path = os.path.join(base_path, "settings")
|
|
@@ -88,18 +79,16 @@ class ExportManager(object):
|
|
|
88
79
|
collection = self.adapter.collection(name)
|
|
89
80
|
source_path = os.path.join(single_path, "%s.json" % name)
|
|
90
81
|
file = open(source_path, "rb")
|
|
91
|
-
try:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
key = key,
|
|
97
|
-
policy = policy
|
|
98
|
-
)
|
|
82
|
+
try:
|
|
83
|
+
data = file.read()
|
|
84
|
+
finally:
|
|
85
|
+
file.close()
|
|
86
|
+
self._import_single(collection, data, key=key, policy=policy)
|
|
99
87
|
|
|
100
88
|
for name, key in self.multiple:
|
|
101
89
|
source_directory = os.path.join(base_path, name)
|
|
102
|
-
if not os.path.exists(source_directory):
|
|
90
|
+
if not os.path.exists(source_directory):
|
|
91
|
+
continue
|
|
103
92
|
|
|
104
93
|
collection = self.adapter.collection(name)
|
|
105
94
|
items = os.listdir(source_directory)
|
|
@@ -109,49 +98,52 @@ class ExportManager(object):
|
|
|
109
98
|
value, _extension = os.path.splitext(item)
|
|
110
99
|
source_path = os.path.join(source_directory, item)
|
|
111
100
|
file = open(source_path, "rb")
|
|
112
|
-
try:
|
|
113
|
-
|
|
101
|
+
try:
|
|
102
|
+
_data = file.read()
|
|
103
|
+
finally:
|
|
104
|
+
file.close()
|
|
114
105
|
|
|
115
106
|
data.append((value, _data))
|
|
116
107
|
|
|
117
|
-
self._import_multiple(
|
|
118
|
-
collection,
|
|
119
|
-
data,
|
|
120
|
-
key = key,
|
|
121
|
-
policy = policy
|
|
122
|
-
)
|
|
108
|
+
self._import_multiple(collection, data, key=key, policy=policy)
|
|
123
109
|
|
|
124
110
|
def export_data(self, file_path):
|
|
125
111
|
encoder = self.adapter.encoder()
|
|
126
112
|
temporary_path = tempfile.mkdtemp()
|
|
127
113
|
base_path = temporary_path
|
|
128
114
|
single_path = os.path.join(base_path, "settings")
|
|
129
|
-
if not os.path.exists(single_path):
|
|
115
|
+
if not os.path.exists(single_path):
|
|
116
|
+
os.makedirs(single_path)
|
|
130
117
|
|
|
131
118
|
for name, key in self.single:
|
|
132
119
|
collection = self.adapter.collection(name)
|
|
133
|
-
data = self._export_single(collection, key
|
|
120
|
+
data = self._export_single(collection, key=key, encoder=encoder)
|
|
134
121
|
target_path = os.path.join(single_path, "%s.json" % name)
|
|
135
122
|
file = open(target_path, "wb")
|
|
136
|
-
try:
|
|
137
|
-
|
|
123
|
+
try:
|
|
124
|
+
file.write(data)
|
|
125
|
+
finally:
|
|
126
|
+
file.close()
|
|
138
127
|
|
|
139
128
|
for name, key in self.multiple:
|
|
140
129
|
collection = self.adapter.collection(name)
|
|
141
|
-
data = self._export_multiple(collection, key
|
|
130
|
+
data = self._export_multiple(collection, key=key, encoder=encoder)
|
|
142
131
|
|
|
143
132
|
target_directory = os.path.join(base_path, name)
|
|
144
|
-
if not os.path.exists(target_directory):
|
|
133
|
+
if not os.path.exists(target_directory):
|
|
134
|
+
os.makedirs(target_directory)
|
|
145
135
|
|
|
146
136
|
for value, _data in data:
|
|
147
137
|
target_path = os.path.join(target_directory, "%s.json" % value)
|
|
148
138
|
file = open(target_path, "wb")
|
|
149
|
-
try:
|
|
150
|
-
|
|
139
|
+
try:
|
|
140
|
+
file.write(_data)
|
|
141
|
+
finally:
|
|
142
|
+
file.close()
|
|
151
143
|
|
|
152
144
|
self._create_zip(file_path, temporary_path)
|
|
153
145
|
|
|
154
|
-
def _import_single(self, collection, data, key, policy
|
|
146
|
+
def _import_single(self, collection, data, key, policy=IGNORE):
|
|
155
147
|
# loads the provided JSON data as a sequence of key value items
|
|
156
148
|
# and then starts loading all the values into the data source
|
|
157
149
|
data = data.decode("utf-8")
|
|
@@ -164,37 +156,41 @@ class ExportManager(object):
|
|
|
164
156
|
# that in case the underlying identifiers does not exists a
|
|
165
157
|
# new value is generated using the pre-defined strategy
|
|
166
158
|
if "_id" in entity:
|
|
167
|
-
try:
|
|
168
|
-
|
|
169
|
-
|
|
159
|
+
try:
|
|
160
|
+
entity["_id"] = self.adapter.object_id(entity["_id"])
|
|
161
|
+
except Exception:
|
|
162
|
+
entity["_id"] = entity["_id"]
|
|
163
|
+
else:
|
|
164
|
+
entity["_id"] = self.adapter.object_id()
|
|
170
165
|
|
|
171
166
|
# retrieves the key value for the current entity to
|
|
172
167
|
# be inserted and then tries to retrieve an existing
|
|
173
168
|
# entity for the same key, to avoid duplicated entry
|
|
174
169
|
value = entity.get(key, None)
|
|
175
|
-
if value:
|
|
176
|
-
|
|
170
|
+
if value:
|
|
171
|
+
entity_e = collection.find_one({key: value})
|
|
172
|
+
else:
|
|
173
|
+
entity_e = None
|
|
177
174
|
|
|
178
175
|
# in case there's no existing entity for the same key
|
|
179
176
|
# (normal situation) only need to insert the new entity
|
|
180
177
|
# otherwise must apply the selected conflict policy for
|
|
181
178
|
# the resolution of the data source conflict
|
|
182
|
-
if not entity_e:
|
|
183
|
-
|
|
179
|
+
if not entity_e:
|
|
180
|
+
collection.insert(entity)
|
|
181
|
+
elif policy == IGNORE:
|
|
182
|
+
continue
|
|
184
183
|
elif policy == OVERWRITE:
|
|
185
|
-
collection.remove({key
|
|
184
|
+
collection.remove({key: value})
|
|
186
185
|
collection.insert(entity)
|
|
187
186
|
elif policy == DUPLICATE:
|
|
188
187
|
collection.insert(entity)
|
|
189
188
|
elif policy == JOIN:
|
|
190
|
-
if "_id" in entity:
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
def _import_multiple(self, collection, data, key, policy = IGNORE):
|
|
189
|
+
if "_id" in entity:
|
|
190
|
+
del entity["_id"]
|
|
191
|
+
collection.update({"_id": entity_e["_id"]}, {"$set": entity})
|
|
192
|
+
|
|
193
|
+
def _import_multiple(self, collection, data, key, policy=IGNORE):
|
|
198
194
|
# iterates over the complete set of data element to load
|
|
199
195
|
# the JSON contents and then load the corresponding entity
|
|
200
196
|
# value into the data source
|
|
@@ -211,60 +207,65 @@ class ExportManager(object):
|
|
|
211
207
|
# that in case the underlying identifiers does not exists a
|
|
212
208
|
# new value is generated using the pre-defined strategy
|
|
213
209
|
if "_id" in entity:
|
|
214
|
-
try:
|
|
215
|
-
|
|
216
|
-
|
|
210
|
+
try:
|
|
211
|
+
entity["_id"] = self.adapter.object_id(entity["_id"])
|
|
212
|
+
except Exception:
|
|
213
|
+
entity["_id"] = entity["_id"]
|
|
214
|
+
else:
|
|
215
|
+
entity["_id"] = self.adapter.object_id()
|
|
217
216
|
|
|
218
217
|
# retrieves the key value for the current entity to
|
|
219
218
|
# be inserted and then tries to retrieve an existing
|
|
220
219
|
# entity for the same key, to avoid duplicated entry
|
|
221
220
|
value = entity.get(key, None)
|
|
222
|
-
if value:
|
|
223
|
-
|
|
221
|
+
if value:
|
|
222
|
+
entity_e = collection.find_one({key: value})
|
|
223
|
+
else:
|
|
224
|
+
entity_e = None
|
|
224
225
|
|
|
225
226
|
# in case there's no existing entity for the same key
|
|
226
227
|
# (normal situation) only need to insert the new entity
|
|
227
228
|
# otherwise must apply the selected conflict policy for
|
|
228
229
|
# the resolution of the data source conflict
|
|
229
|
-
if not entity_e:
|
|
230
|
-
|
|
230
|
+
if not entity_e:
|
|
231
|
+
collection.insert(entity)
|
|
232
|
+
elif policy == IGNORE:
|
|
233
|
+
continue
|
|
231
234
|
elif policy == OVERWRITE:
|
|
232
|
-
collection.remove({key
|
|
235
|
+
collection.remove({key: value})
|
|
233
236
|
collection.insert(entity)
|
|
234
237
|
elif policy == DUPLICATE:
|
|
235
238
|
collection.insert(entity)
|
|
236
239
|
elif policy == JOIN:
|
|
237
|
-
if "_id" in entity:
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
def _export_single(self, collection, key = "_id", encoder = None):
|
|
240
|
+
if "_id" in entity:
|
|
241
|
+
del entity["_id"]
|
|
242
|
+
collection.update({"_id": entity_e["_id"]}, {"$set": entity})
|
|
243
|
+
|
|
244
|
+
def _export_single(self, collection, key="_id", encoder=None):
|
|
245
245
|
entities = collection.find()
|
|
246
246
|
_entities = {}
|
|
247
247
|
for entity in entities:
|
|
248
248
|
value = entity[key]
|
|
249
249
|
value_s = self._to_key(value)
|
|
250
250
|
_entities[value_s] = entity
|
|
251
|
-
data = json.dumps(_entities, cls
|
|
251
|
+
data = json.dumps(_entities, cls=encoder)
|
|
252
252
|
data = legacy.bytes(data)
|
|
253
253
|
return data
|
|
254
254
|
|
|
255
|
-
def _export_multiple(self, collection, key
|
|
255
|
+
def _export_multiple(self, collection, key="_id", encoder=None):
|
|
256
256
|
entities = collection.find()
|
|
257
257
|
for entity in entities:
|
|
258
258
|
value = entity[key]
|
|
259
259
|
value_s = self._to_key(value)
|
|
260
260
|
value_s = self._escape_key(value_s)
|
|
261
|
-
_data = json.dumps(entity, cls
|
|
261
|
+
_data = json.dumps(entity, cls=encoder)
|
|
262
262
|
_data = legacy.bytes(_data)
|
|
263
263
|
yield (value_s, _data)
|
|
264
264
|
|
|
265
265
|
def _to_key(self, key):
|
|
266
266
|
key_t = type(key)
|
|
267
|
-
if key_t in legacy.STRINGS:
|
|
267
|
+
if key_t in legacy.STRINGS:
|
|
268
|
+
return key
|
|
268
269
|
key = legacy.UNICODE(key)
|
|
269
270
|
return key
|
|
270
271
|
|
|
@@ -272,16 +273,15 @@ class ExportManager(object):
|
|
|
272
273
|
return key.replace(":", "_")
|
|
273
274
|
|
|
274
275
|
def _deploy_zip(self, zip_path, path):
|
|
275
|
-
zip_file = zipfile.ZipFile(zip_path, mode
|
|
276
|
-
try:
|
|
277
|
-
|
|
276
|
+
zip_file = zipfile.ZipFile(zip_path, mode="r")
|
|
277
|
+
try:
|
|
278
|
+
zip_file.extractall(path)
|
|
279
|
+
finally:
|
|
280
|
+
zip_file.close()
|
|
278
281
|
|
|
279
282
|
def _create_zip(self, zip_path, path):
|
|
280
283
|
zip_file = zipfile.ZipFile(
|
|
281
|
-
zip_path,
|
|
282
|
-
mode = "w",
|
|
283
|
-
compression = zipfile.ZIP_DEFLATED,
|
|
284
|
-
allowZip64 = True
|
|
284
|
+
zip_path, mode="w", compression=zipfile.ZIP_DEFLATED, allowZip64=True
|
|
285
285
|
)
|
|
286
286
|
|
|
287
287
|
try:
|
|
@@ -290,20 +290,22 @@ class ExportManager(object):
|
|
|
290
290
|
_path = os.path.join(path, name)
|
|
291
291
|
is_file = os.path.isfile(_path)
|
|
292
292
|
|
|
293
|
-
if is_file:
|
|
294
|
-
|
|
293
|
+
if is_file:
|
|
294
|
+
zip_file.write(_path)
|
|
295
|
+
else:
|
|
296
|
+
self.__add_to_zip(zip_file, _path, base=path)
|
|
295
297
|
finally:
|
|
296
298
|
zip_file.close()
|
|
297
299
|
|
|
298
|
-
def __add_to_zip(self, zip_file, path, base
|
|
300
|
+
def __add_to_zip(self, zip_file, path, base=""):
|
|
299
301
|
list = os.listdir(path)
|
|
300
302
|
for name in list:
|
|
301
303
|
_path = os.path.join(path, name)
|
|
302
|
-
_path_out = _path[len(base):]
|
|
304
|
+
_path_out = _path[len(base) :]
|
|
303
305
|
_path_out = _path_out.replace("\\", "/")
|
|
304
306
|
_path_out = _path_out.strip("/")
|
|
305
307
|
|
|
306
308
|
if os.path.isfile(_path):
|
|
307
309
|
zip_file.write(_path, _path_out)
|
|
308
310
|
elif os.path.isdir(_path):
|
|
309
|
-
self.__add_to_zip(zip_file, _path, base
|
|
311
|
+
self.__add_to_zip(zip_file, _path, base=base)
|