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/log.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,12 +52,12 @@ multiple stream handlers, this version of the string
|
|
|
61
52
|
includes the thread identification number and should be
|
|
62
53
|
used for messages called from outside the main thread """
|
|
63
54
|
|
|
64
|
-
LOGGING_EXTRA = "[%(name)s] " if config.conf("LOGGING_EXTRA", cast
|
|
55
|
+
LOGGING_EXTRA = "[%(name)s] " if config.conf("LOGGING_EXTRA", cast=bool) else ""
|
|
65
56
|
""" The extra logging attributes that are going to be applied
|
|
66
57
|
to the format strings to obtain the final on the logging """
|
|
67
58
|
|
|
68
|
-
LOGGIGN_SYSLOG =
|
|
69
|
-
[appierSDID@0 tid
|
|
59
|
+
LOGGIGN_SYSLOG = '1 %%(asctime)s %%(hostname)s %s %%(process)d %%(thread)d \
|
|
60
|
+
[appierSDID@0 tid="%%(thread)d"] %%(json)s'
|
|
70
61
|
""" The format to be used for the message sent using the syslog
|
|
71
62
|
logger, should contain extra structured data """
|
|
72
63
|
|
|
@@ -81,34 +72,29 @@ SILENT = logging.CRITICAL + 1
|
|
|
81
72
|
or an handler, this is used as an utility for debugging
|
|
82
73
|
purposes more that a real feature for production systems """
|
|
83
74
|
|
|
84
|
-
LEVELS = (
|
|
85
|
-
"DEBUG",
|
|
86
|
-
"INFO",
|
|
87
|
-
"WARNING",
|
|
88
|
-
"ERROR",
|
|
89
|
-
"CRITICAL"
|
|
90
|
-
)
|
|
75
|
+
LEVELS = ("DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL")
|
|
91
76
|
""" The sequence of levels from the least sever to the
|
|
92
77
|
most sever this sequence may be used to find all the
|
|
93
78
|
levels that are considered more sever that a level """
|
|
94
79
|
|
|
95
80
|
LEVEL_ALIAS = {
|
|
96
|
-
"DEBU"
|
|
97
|
-
"WARN"
|
|
98
|
-
"INF"
|
|
99
|
-
"ERR"
|
|
100
|
-
"CRIT"
|
|
81
|
+
"DEBU": "DEBUG",
|
|
82
|
+
"WARN": "WARNING",
|
|
83
|
+
"INF": "INFO",
|
|
84
|
+
"ERR": "ERROR",
|
|
85
|
+
"CRIT": "CRITICAL",
|
|
101
86
|
}
|
|
102
87
|
""" Map defining a series of alias that may be used latter
|
|
103
88
|
for proper debug level resolution """
|
|
104
89
|
|
|
105
|
-
SYSLOG_PORTS = dict(tcp
|
|
90
|
+
SYSLOG_PORTS = dict(tcp=601, udp=514)
|
|
106
91
|
""" Dictionary that maps the multiple transport protocol
|
|
107
92
|
used by syslog with the appropriate default ports """
|
|
108
93
|
|
|
109
94
|
LOGGING_FORMAT = LOGGING_FORMAT_T % LOGGING_EXTRA
|
|
110
95
|
LOGGING_FORMAT_TID = LOGGING_FORMAT_TID_T % LOGGING_EXTRA
|
|
111
96
|
|
|
97
|
+
|
|
112
98
|
class MemoryHandler(logging.Handler):
|
|
113
99
|
"""
|
|
114
100
|
Logging handler that is used to store information in
|
|
@@ -116,8 +102,8 @@ class MemoryHandler(logging.Handler):
|
|
|
116
102
|
long as the execution session is the same.
|
|
117
103
|
"""
|
|
118
104
|
|
|
119
|
-
def __init__(self, level
|
|
120
|
-
logging.Handler.__init__(self, level
|
|
105
|
+
def __init__(self, level=logging.NOTSET, max_length=MAX_LENGTH):
|
|
106
|
+
logging.Handler.__init__(self, level=level)
|
|
121
107
|
self.max_length = max_length
|
|
122
108
|
self.messages = collections.deque()
|
|
123
109
|
self.messages_l = dict()
|
|
@@ -133,12 +119,14 @@ class MemoryHandler(logging.Handler):
|
|
|
133
119
|
def get_messages_l(self, level):
|
|
134
120
|
# in case the level is not found in the list of levels
|
|
135
121
|
# it's not considered valid and so an empty list is returned
|
|
136
|
-
try:
|
|
137
|
-
|
|
122
|
+
try:
|
|
123
|
+
index = LEVELS.index(level)
|
|
124
|
+
except Exception:
|
|
125
|
+
return collections.deque()
|
|
138
126
|
|
|
139
127
|
# retrieves the complete set of levels that are considered
|
|
140
128
|
# equal or more severe than the requested one
|
|
141
|
-
levels = LEVELS[:index + 1]
|
|
129
|
+
levels = LEVELS[: index + 1]
|
|
142
130
|
|
|
143
131
|
# creates the list that will hold the various message
|
|
144
132
|
# lists associated with the current severity level
|
|
@@ -149,7 +137,8 @@ class MemoryHandler(logging.Handler):
|
|
|
149
137
|
# list to the list of message lists
|
|
150
138
|
for level in levels:
|
|
151
139
|
_messages_l = self.messages_l.get(level, None)
|
|
152
|
-
if _messages_l == None:
|
|
140
|
+
if _messages_l == None:
|
|
141
|
+
_messages_l = collections.deque()
|
|
153
142
|
self.messages_l[level] = _messages_l
|
|
154
143
|
messages_l.append(_messages_l)
|
|
155
144
|
|
|
@@ -175,7 +164,8 @@ class MemoryHandler(logging.Handler):
|
|
|
175
164
|
# the one defined as maximum must pop message from queue
|
|
176
165
|
self.messages.appendleft(message)
|
|
177
166
|
messages_s = len(self.messages)
|
|
178
|
-
if messages_s > self.max_length:
|
|
167
|
+
if messages_s > self.max_length:
|
|
168
|
+
self.messages.pop()
|
|
179
169
|
|
|
180
170
|
# iterates over all the messages list included in the retrieve
|
|
181
171
|
# messages list to add the logging message to each of them
|
|
@@ -185,42 +175,42 @@ class MemoryHandler(logging.Handler):
|
|
|
185
175
|
# specified also for the more general queue
|
|
186
176
|
_messages_l.appendleft(message)
|
|
187
177
|
messages_s = len(_messages_l)
|
|
188
|
-
if messages_s > self.max_length:
|
|
178
|
+
if messages_s > self.max_length:
|
|
179
|
+
_messages_l.pop()
|
|
189
180
|
|
|
190
181
|
def clear(self):
|
|
191
182
|
self.messages = collections.deque()
|
|
192
183
|
self.messages_l = dict()
|
|
193
184
|
|
|
194
|
-
def get_latest(self, count
|
|
185
|
+
def get_latest(self, count=None, level=None):
|
|
195
186
|
count = count or 100
|
|
196
187
|
is_level = level and not legacy.is_string(level)
|
|
197
|
-
if is_level:
|
|
188
|
+
if is_level:
|
|
189
|
+
level = logging.getLevelName(level)
|
|
198
190
|
level = level.upper() if level else level
|
|
199
191
|
level = LEVEL_ALIAS.get(level, level)
|
|
200
192
|
messages = self.messages_l.get(level, []) if level else self.messages
|
|
201
193
|
slice = itertools.islice(messages, 0, count)
|
|
202
194
|
return list(slice)
|
|
203
195
|
|
|
204
|
-
def flush_to_file(
|
|
205
|
-
self,
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
clear = True
|
|
211
|
-
):
|
|
212
|
-
messages = self.get_latest(level = level, count = count or 65536)
|
|
213
|
-
if not messages: return
|
|
214
|
-
if reverse: messages.reverse()
|
|
196
|
+
def flush_to_file(self, path, count=None, level=None, reverse=True, clear=True):
|
|
197
|
+
messages = self.get_latest(level=level, count=count or 65536)
|
|
198
|
+
if not messages:
|
|
199
|
+
return
|
|
200
|
+
if reverse:
|
|
201
|
+
messages.reverse()
|
|
215
202
|
is_path = isinstance(path, legacy.STRINGS)
|
|
216
203
|
file = open(path, "wb") if is_path else path
|
|
217
204
|
try:
|
|
218
205
|
for message in messages:
|
|
219
|
-
message = legacy.bytes(message, "utf-8", force
|
|
206
|
+
message = legacy.bytes(message, "utf-8", force=True)
|
|
220
207
|
file.write(message + b"\n")
|
|
221
208
|
finally:
|
|
222
|
-
if is_path:
|
|
223
|
-
|
|
209
|
+
if is_path:
|
|
210
|
+
file.close()
|
|
211
|
+
if clear:
|
|
212
|
+
self.clear()
|
|
213
|
+
|
|
224
214
|
|
|
225
215
|
class BaseFormatter(logging.Formatter):
|
|
226
216
|
"""
|
|
@@ -237,19 +227,20 @@ class BaseFormatter(logging.Formatter):
|
|
|
237
227
|
|
|
238
228
|
@classmethod
|
|
239
229
|
def _wrap_record(cls, record):
|
|
240
|
-
if hasattr(record, "_wrapped"):
|
|
230
|
+
if hasattr(record, "_wrapped"):
|
|
231
|
+
return
|
|
241
232
|
record.hostname = socket.gethostname()
|
|
242
233
|
record.json = json.dumps(
|
|
243
234
|
dict(
|
|
244
|
-
message
|
|
245
|
-
hostname
|
|
246
|
-
lineno
|
|
247
|
-
module
|
|
248
|
-
callable
|
|
249
|
-
level
|
|
250
|
-
thread
|
|
251
|
-
process
|
|
252
|
-
logger
|
|
235
|
+
message=str(record.msg),
|
|
236
|
+
hostname=record.hostname,
|
|
237
|
+
lineno=record.lineno,
|
|
238
|
+
module=record.module,
|
|
239
|
+
callable=record.funcName,
|
|
240
|
+
level=record.levelname,
|
|
241
|
+
thread=record.thread,
|
|
242
|
+
process=record.process,
|
|
243
|
+
logger=record.name,
|
|
253
244
|
)
|
|
254
245
|
)
|
|
255
246
|
record._wrapped = True
|
|
@@ -257,12 +248,14 @@ class BaseFormatter(logging.Formatter):
|
|
|
257
248
|
def format(self, record):
|
|
258
249
|
# runs the wrapping operation on the record so that more
|
|
259
250
|
# information becomes available in it (as expected)
|
|
260
|
-
if self._wrap:
|
|
251
|
+
if self._wrap:
|
|
252
|
+
self.__class__._wrap_record(record)
|
|
261
253
|
|
|
262
254
|
# runs the basic format operation on the record so that
|
|
263
255
|
# it gets properly formatted into a plain string
|
|
264
256
|
return logging.Formatter.format(self, record)
|
|
265
257
|
|
|
258
|
+
|
|
266
259
|
class ThreadFormatter(BaseFormatter):
|
|
267
260
|
"""
|
|
268
261
|
Custom formatter class that changing the default format
|
|
@@ -278,14 +271,16 @@ class ThreadFormatter(BaseFormatter):
|
|
|
278
271
|
def format(self, record):
|
|
279
272
|
# runs the wrapping operation on the record so that more
|
|
280
273
|
# information becomes available in it (as expected)
|
|
281
|
-
if self._wrap:
|
|
274
|
+
if self._wrap:
|
|
275
|
+
self.__class__._wrap_record(record)
|
|
282
276
|
|
|
283
277
|
# retrieves the reference to the current thread and verifies
|
|
284
278
|
# if it represent the current process main thread, then selects
|
|
285
279
|
# the appropriate formating string taking that into account
|
|
286
280
|
current = threading.current_thread()
|
|
287
281
|
is_main = current.name == "MainThread"
|
|
288
|
-
if not is_main:
|
|
282
|
+
if not is_main:
|
|
283
|
+
return self._tidfmt.format(record)
|
|
289
284
|
return self._basefmt.format(record)
|
|
290
285
|
|
|
291
286
|
def set_base(self, value, *args, **kwargs):
|
|
@@ -294,8 +289,8 @@ class ThreadFormatter(BaseFormatter):
|
|
|
294
289
|
def set_tid(self, value, *args, **kwargs):
|
|
295
290
|
self._tidfmt = BaseFormatter(value, *args, **kwargs)
|
|
296
291
|
|
|
297
|
-
class DummyLogger(object):
|
|
298
292
|
|
|
293
|
+
class DummyLogger(object):
|
|
299
294
|
def debug(self, object):
|
|
300
295
|
pass
|
|
301
296
|
|
|
@@ -311,61 +306,59 @@ class DummyLogger(object):
|
|
|
311
306
|
def critical(self, object):
|
|
312
307
|
pass
|
|
313
308
|
|
|
314
|
-
|
|
309
|
+
|
|
310
|
+
def reload_format(app=None):
|
|
315
311
|
global LOGGING_FORMAT
|
|
316
312
|
global LOGGING_FORMAT_TID
|
|
317
313
|
|
|
318
314
|
app = app or common.base().get_app()
|
|
319
315
|
|
|
320
316
|
extra = LOGGING_EXTRA
|
|
321
|
-
if app and not app.is_parent():
|
|
317
|
+
if app and not app.is_parent():
|
|
318
|
+
extra = "[%(process)d] " + extra
|
|
322
319
|
|
|
323
320
|
LOGGING_FORMAT = LOGGING_FORMAT_T % extra
|
|
324
321
|
LOGGING_FORMAT_TID = LOGGING_FORMAT_TID_T % extra
|
|
325
322
|
|
|
323
|
+
|
|
326
324
|
def rotating_handler(
|
|
327
|
-
path
|
|
328
|
-
max_bytes = 1048576,
|
|
329
|
-
max_log = 5,
|
|
330
|
-
encoding = None,
|
|
331
|
-
delay = False
|
|
325
|
+
path="appier.log", max_bytes=1048576, max_log=5, encoding=None, delay=False
|
|
332
326
|
):
|
|
333
327
|
return logging.handlers.RotatingFileHandler(
|
|
334
|
-
path,
|
|
335
|
-
maxBytes = max_bytes,
|
|
336
|
-
backupCount = max_log,
|
|
337
|
-
encoding = encoding,
|
|
338
|
-
delay = delay
|
|
328
|
+
path, maxBytes=max_bytes, backupCount=max_log, encoding=encoding, delay=delay
|
|
339
329
|
)
|
|
340
330
|
|
|
331
|
+
|
|
341
332
|
def smtp_handler(
|
|
342
|
-
host
|
|
343
|
-
port
|
|
344
|
-
sender
|
|
345
|
-
receivers
|
|
346
|
-
subject
|
|
347
|
-
username
|
|
348
|
-
password
|
|
349
|
-
stls
|
|
333
|
+
host="localhost",
|
|
334
|
+
port=25,
|
|
335
|
+
sender="no-reply@appier.com",
|
|
336
|
+
receivers=[],
|
|
337
|
+
subject="Appier logging",
|
|
338
|
+
username=None,
|
|
339
|
+
password=None,
|
|
340
|
+
stls=False,
|
|
350
341
|
):
|
|
351
342
|
address = (host, port)
|
|
352
|
-
if username and password:
|
|
353
|
-
|
|
343
|
+
if username and password:
|
|
344
|
+
credentials = (username, password)
|
|
345
|
+
else:
|
|
346
|
+
credentials = None
|
|
354
347
|
has_secure = in_signature(logging.handlers.SMTPHandler.__init__, "secure")
|
|
355
|
-
if has_secure:
|
|
356
|
-
|
|
348
|
+
if has_secure:
|
|
349
|
+
kwargs = dict(secure=() if stls else None)
|
|
350
|
+
else:
|
|
351
|
+
kwargs = dict()
|
|
357
352
|
return logging.handlers.SMTPHandler(
|
|
358
|
-
address,
|
|
359
|
-
sender,
|
|
360
|
-
receivers,
|
|
361
|
-
subject,
|
|
362
|
-
credentials = credentials,
|
|
363
|
-
**kwargs
|
|
353
|
+
address, sender, receivers, subject, credentials=credentials, **kwargs
|
|
364
354
|
)
|
|
365
355
|
|
|
356
|
+
|
|
366
357
|
def in_signature(callable, name):
|
|
367
358
|
has_full = hasattr(inspect, "getfullargspec")
|
|
368
|
-
if has_full:
|
|
369
|
-
|
|
359
|
+
if has_full:
|
|
360
|
+
spec = inspect.getfullargspec(callable)
|
|
361
|
+
else:
|
|
362
|
+
spec = inspect.getargspec(callable)
|
|
370
363
|
args, _varargs, kwargs = spec[:3]
|
|
371
364
|
return (args and name in args) or (kwargs and "secure" in kwargs)
|
appier/meta.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"
|
|
@@ -40,6 +31,7 @@ __license__ = "Apache License, Version 2.0"
|
|
|
40
31
|
from . import common
|
|
41
32
|
from . import legacy
|
|
42
33
|
|
|
34
|
+
|
|
43
35
|
class Ordered(type):
|
|
44
36
|
"""
|
|
45
37
|
Metaclass to be used for classes where the
|
|
@@ -49,9 +41,12 @@ class Ordered(type):
|
|
|
49
41
|
|
|
50
42
|
def __new__(cls, name, bases, attrs):
|
|
51
43
|
new_cls = super(Ordered, cls).__new__(cls, name, bases, attrs)
|
|
52
|
-
new_cls._ordered = [
|
|
53
|
-
|
|
54
|
-
|
|
44
|
+
new_cls._ordered = [
|
|
45
|
+
(name, attrs.pop(name))
|
|
46
|
+
for name, value in legacy.eager(attrs.items())
|
|
47
|
+
if hasattr(value, "creation_counter")
|
|
48
|
+
]
|
|
49
|
+
new_cls._ordered.sort(key=lambda item: item[1].creation_counter)
|
|
55
50
|
new_cls._ordered = [name for name, value in new_cls._ordered]
|
|
56
51
|
return new_cls
|
|
57
52
|
|
|
@@ -59,11 +54,12 @@ class Ordered(type):
|
|
|
59
54
|
super(Ordered, cls).__init__(name, bases, attrs)
|
|
60
55
|
|
|
61
56
|
def __cmp__(self, value):
|
|
62
|
-
return cmp(self.__name__, value.__name__)
|
|
57
|
+
return cmp(self.__name__, value.__name__) # @UndefinedVariable
|
|
63
58
|
|
|
64
59
|
def __lt__(self, value):
|
|
65
60
|
return self.__name__.__lt__(value.__name__)
|
|
66
61
|
|
|
62
|
+
|
|
67
63
|
class Indexed(type):
|
|
68
64
|
"""
|
|
69
65
|
Meta class data type for the indexing of the various route
|
|
@@ -78,21 +74,26 @@ class Indexed(type):
|
|
|
78
74
|
# retrieves the complete set of elements in the current set of attributes
|
|
79
75
|
# that are marked with the "creation counter" and then sorts these same
|
|
80
76
|
# elements according to that value (sorting by order of definition)
|
|
81
|
-
ordered = [
|
|
82
|
-
|
|
83
|
-
|
|
77
|
+
ordered = [
|
|
78
|
+
(name, attrs.pop(name))
|
|
79
|
+
for name, value in legacy.eager(attrs.items())
|
|
80
|
+
if hasattr(value, "creation_counter")
|
|
81
|
+
]
|
|
82
|
+
ordered.sort(key=lambda item: item[1].creation_counter)
|
|
84
83
|
|
|
85
84
|
# in case there's no ordered related values there's nothing remaining
|
|
86
85
|
# to be done under this class creation and so the created class must
|
|
87
86
|
# be returned immediately to the caller method
|
|
88
|
-
if not ordered:
|
|
87
|
+
if not ordered:
|
|
88
|
+
return new_cls
|
|
89
89
|
|
|
90
90
|
# retrieves the reference to the current global application instance
|
|
91
91
|
# and verifies if the global (cache) base registered value is defined
|
|
92
92
|
# in case it's not defines it as it's going to be used to avoid the
|
|
93
93
|
# duplicated registration of routes
|
|
94
94
|
app = common.base().App
|
|
95
|
-
if not hasattr(app, "_BASE_REGISTERED"):
|
|
95
|
+
if not hasattr(app, "_BASE_REGISTERED"):
|
|
96
|
+
app._BASE_REGISTERED = []
|
|
96
97
|
registered = app._BASE_REGISTERED
|
|
97
98
|
|
|
98
99
|
# iterates over the complete set of ordered elements to be able to
|
|
@@ -102,7 +103,9 @@ class Indexed(type):
|
|
|
102
103
|
# to be able to register each of them properly
|
|
103
104
|
routes = function._routes if hasattr(function, "_routes") else []
|
|
104
105
|
errors = function._errors if hasattr(function, "_errors") else []
|
|
105
|
-
exceptions =
|
|
106
|
+
exceptions = (
|
|
107
|
+
function._exceptions if hasattr(function, "_exceptions") else []
|
|
108
|
+
)
|
|
106
109
|
customs = function._customs if hasattr(function, "_customs") else []
|
|
107
110
|
|
|
108
111
|
# iterates over the complete set of routes associated with the current
|
|
@@ -121,7 +124,8 @@ class Indexed(type):
|
|
|
121
124
|
# that's the situation continues the loop to be able to
|
|
122
125
|
# avoid duplicated route registration (possible with imports)
|
|
123
126
|
route_t = (method, url)
|
|
124
|
-
if route_t in registered:
|
|
127
|
+
if route_t in registered:
|
|
128
|
+
continue
|
|
125
129
|
registered.append(route_t)
|
|
126
130
|
|
|
127
131
|
# adds the new route to the application using the provided/unpacked
|
|
@@ -130,11 +134,11 @@ class Indexed(type):
|
|
|
130
134
|
method,
|
|
131
135
|
url,
|
|
132
136
|
function,
|
|
133
|
-
asynchronous
|
|
134
|
-
json
|
|
135
|
-
opts
|
|
136
|
-
context
|
|
137
|
-
priority
|
|
137
|
+
asynchronous=asynchronous,
|
|
138
|
+
json=json,
|
|
139
|
+
opts=opts,
|
|
140
|
+
context=new_name,
|
|
141
|
+
priority=priority,
|
|
138
142
|
)
|
|
139
143
|
|
|
140
144
|
for error in errors:
|
|
@@ -142,11 +146,11 @@ class Indexed(type):
|
|
|
142
146
|
app.add_error(
|
|
143
147
|
code,
|
|
144
148
|
function,
|
|
145
|
-
scope
|
|
146
|
-
json
|
|
147
|
-
opts
|
|
148
|
-
context
|
|
149
|
-
priority
|
|
149
|
+
scope=scope,
|
|
150
|
+
json=json,
|
|
151
|
+
opts=opts,
|
|
152
|
+
context=new_name,
|
|
153
|
+
priority=priority,
|
|
150
154
|
)
|
|
151
155
|
|
|
152
156
|
for exception in exceptions:
|
|
@@ -154,21 +158,17 @@ class Indexed(type):
|
|
|
154
158
|
app.add_exception(
|
|
155
159
|
exception,
|
|
156
160
|
function,
|
|
157
|
-
scope
|
|
158
|
-
json
|
|
159
|
-
opts
|
|
160
|
-
context
|
|
161
|
-
priority
|
|
161
|
+
scope=scope,
|
|
162
|
+
json=json,
|
|
163
|
+
opts=opts,
|
|
164
|
+
context=new_name,
|
|
165
|
+
priority=priority,
|
|
162
166
|
)
|
|
163
167
|
|
|
164
168
|
for custom in customs:
|
|
165
169
|
key, opts, priority = custom
|
|
166
170
|
app.add_custom(
|
|
167
|
-
key,
|
|
168
|
-
function,
|
|
169
|
-
opts = opts,
|
|
170
|
-
context = new_name,
|
|
171
|
-
priority = priority
|
|
171
|
+
key, function, opts=opts, context=new_name, priority=priority
|
|
172
172
|
)
|
|
173
173
|
|
|
174
174
|
return new_cls
|
appier/mock.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"
|
|
@@ -40,16 +31,18 @@ __license__ = "Apache License, Version 2.0"
|
|
|
40
31
|
from . import util
|
|
41
32
|
from . import legacy
|
|
42
33
|
|
|
43
|
-
class MockObject(object):
|
|
44
34
|
|
|
35
|
+
class MockObject(object):
|
|
45
36
|
def __init__(self, *args, **kwargs):
|
|
46
37
|
self.model = kwargs
|
|
47
38
|
|
|
48
39
|
def __getattribute__(self, name):
|
|
49
40
|
try:
|
|
50
41
|
model = object.__getattribute__(self, "model")
|
|
51
|
-
if name in model:
|
|
52
|
-
|
|
42
|
+
if name in model:
|
|
43
|
+
return model[name]
|
|
44
|
+
except AttributeError:
|
|
45
|
+
pass
|
|
53
46
|
return object.__getattribute__(self, name)
|
|
54
47
|
|
|
55
48
|
def __getitem__(self, key):
|
|
@@ -61,8 +54,8 @@ class MockObject(object):
|
|
|
61
54
|
def __delitem__(self, key):
|
|
62
55
|
self.model.__delitem__(key)
|
|
63
56
|
|
|
64
|
-
class MockResponse(MockObject):
|
|
65
57
|
|
|
58
|
+
class MockResponse(MockObject):
|
|
66
59
|
def read(self):
|
|
67
60
|
return self.data
|
|
68
61
|
|
|
@@ -78,8 +71,8 @@ class MockResponse(MockObject):
|
|
|
78
71
|
def info(self):
|
|
79
72
|
return self.headers
|
|
80
73
|
|
|
81
|
-
class MockApp(object):
|
|
82
74
|
|
|
75
|
+
class MockApp(object):
|
|
83
76
|
def get(self, *args, **kwargs):
|
|
84
77
|
return self.method("GET", *args, **kwargs)
|
|
85
78
|
|
|
@@ -102,11 +95,11 @@ class MockApp(object):
|
|
|
102
95
|
self,
|
|
103
96
|
method,
|
|
104
97
|
location,
|
|
105
|
-
query
|
|
106
|
-
data
|
|
107
|
-
scheme
|
|
108
|
-
address
|
|
109
|
-
headers
|
|
98
|
+
query="",
|
|
99
|
+
data=b"",
|
|
100
|
+
scheme="http",
|
|
101
|
+
address="127.0.0.1",
|
|
102
|
+
headers={},
|
|
110
103
|
):
|
|
111
104
|
# creates the dictionary that is going to hold the (initial)
|
|
112
105
|
# response structure and the "encapsulates" the provided data
|
|
@@ -116,33 +109,37 @@ class MockApp(object):
|
|
|
116
109
|
|
|
117
110
|
# verifies if the location contains a query part (question mark)
|
|
118
111
|
# and if that's the case splits the location in the two parts
|
|
119
|
-
if "?" in location:
|
|
112
|
+
if "?" in location:
|
|
113
|
+
location, query = location.split("?", 1)
|
|
120
114
|
|
|
121
115
|
# builds the map that is going to be used as the basis for the
|
|
122
116
|
# construction of the request, this map should be compliant with
|
|
123
117
|
# the WSGI standard and expectancy
|
|
124
118
|
environ = {
|
|
125
|
-
"REQUEST_METHOD"
|
|
126
|
-
"PATH_INFO"
|
|
127
|
-
"QUERY_STRING"
|
|
128
|
-
"SCRIPT_NAME"
|
|
129
|
-
"CONTENT_LENGTH"
|
|
130
|
-
"REMOTE_ADDR"
|
|
131
|
-
"wsgi.input"
|
|
132
|
-
"wsgi.url_scheme"
|
|
119
|
+
"REQUEST_METHOD": method,
|
|
120
|
+
"PATH_INFO": location,
|
|
121
|
+
"QUERY_STRING": query,
|
|
122
|
+
"SCRIPT_NAME": location,
|
|
123
|
+
"CONTENT_LENGTH": len(data),
|
|
124
|
+
"REMOTE_ADDR": address,
|
|
125
|
+
"wsgi.input": input,
|
|
126
|
+
"wsgi.url_scheme": scheme,
|
|
133
127
|
}
|
|
134
128
|
|
|
135
129
|
# iterates over the complete set of provided header value to set
|
|
136
130
|
# them with the proper HTTP_ prefix in the environment map
|
|
137
|
-
for name, value in headers:
|
|
131
|
+
for name, value in headers:
|
|
132
|
+
environ["HTTP_" + name.upper()] = value
|
|
138
133
|
|
|
139
134
|
def start_response(code, headers):
|
|
140
135
|
# splits the provided code string into its component
|
|
141
136
|
# verifying defaulting the status of the code to an
|
|
142
137
|
# empty string in case none is defined (only code)
|
|
143
138
|
code_s = code.split(" ", 1)
|
|
144
|
-
if len(code_s) == 1:
|
|
145
|
-
|
|
139
|
+
if len(code_s) == 1:
|
|
140
|
+
code, status = code_s[0], ""
|
|
141
|
+
else:
|
|
142
|
+
code, status = code_s
|
|
146
143
|
|
|
147
144
|
# converts the code into the integer representation
|
|
148
145
|
# so that its type is coherent with specification
|
|
@@ -164,6 +161,7 @@ class MockApp(object):
|
|
|
164
161
|
# "safe" context that replaces the current application's context
|
|
165
162
|
# in a proper way so that at the end of the call it's properly
|
|
166
163
|
# restored as expected by a possible control flow
|
|
167
|
-
with util.ctx_request(self):
|
|
164
|
+
with util.ctx_request(self):
|
|
165
|
+
result = self.application(environ, start_response)
|
|
168
166
|
response["data"] = b"".join(result)
|
|
169
167
|
return MockResponse(**response)
|