appier 1.34.5__tar.gz → 1.34.7__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.
- {appier-1.34.5/src/appier.egg-info → appier-1.34.7}/PKG-INFO +1 -1
- appier-1.34.7/pyproject.toml +2 -0
- {appier-1.34.5 → appier-1.34.7}/setup.py +1 -1
- {appier-1.34.5 → appier-1.34.7}/src/appier/__init__.py +1 -1
- {appier-1.34.5 → appier-1.34.7}/src/appier/async_neo.py +2 -2
- {appier-1.34.5 → appier-1.34.7}/src/appier/async_old.py +1 -1
- {appier-1.34.5 → appier-1.34.7}/src/appier/base.py +26 -12
- {appier-1.34.5 → appier-1.34.7}/src/appier/config.py +1 -4
- {appier-1.34.5 → appier-1.34.7}/src/appier/data.py +2 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/model.py +8 -6
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/base.py +2 -5
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/data.py +10 -0
- appier-1.34.7/src/appier/test/error_handler.py +136 -0
- appier-1.34.7/src/appier/test/exception_handler.py +143 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/http.py +24 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/util.py +3 -16
- {appier-1.34.5 → appier-1.34.7}/src/appier/util.py +10 -10
- {appier-1.34.5 → appier-1.34.7/src/appier.egg-info}/PKG-INFO +1 -1
- {appier-1.34.5 → appier-1.34.7}/src/appier.egg-info/SOURCES.txt +3 -0
- {appier-1.34.5 → appier-1.34.7}/MANIFEST.in +0 -0
- {appier-1.34.5 → appier-1.34.7}/README.rst +0 -0
- {appier-1.34.5 → appier-1.34.7}/setup.cfg +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/amqp.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/api.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/asgi.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/asynchronous.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/asynchronous.pyi +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/base.pyi +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/bus.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/bus.pyi +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/cache.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/cache.pyi +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/common.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/component.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/component.pyi +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/compress.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/controller.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/crypt.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/data.pyi +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/defines.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/exceptions.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/execution.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/export.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/extra.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/extra_neo.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/extra_old.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/geo.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/git.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/graph.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/http.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/legacy.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/log.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/meta.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/mock.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/model.pyi +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/model_a.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/mongo.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/observer.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/part.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/part.pyi +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/preferences.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/preferences.pyi +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/queuing.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/redisdb.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/request.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/res/static/css/base.css +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/res/static/images/favicon.ico +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/res/static/js/base.js +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/res/templates/error.html.tpl +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/res/templates/holder.html.tpl +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/res/templates/layout.html.tpl +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/scheduler.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/scheduler.pyi +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/serialize.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/session.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/session.pyi +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/settings.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/smtp.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/storage.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/structures.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/__init__.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/cache.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/config.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/crypt.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/exceptions.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/export.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/graph.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/legacy.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/log.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/mock.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/model.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/part.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/preferences.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/queuing.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/request.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/scheduler.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/serialize.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/session.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/smtp.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/structures.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/typesf.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/test/validation.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/typesf.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier/validation.py +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier.egg-info/dependency_links.txt +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier.egg-info/not-zip-safe +0 -0
- {appier-1.34.5 → appier-1.34.7}/src/appier.egg-info/top_level.txt +0 -0
|
@@ -92,7 +92,7 @@ class CoroutineWrapper(object):
|
|
|
92
92
|
self._buffer.append(value)
|
|
93
93
|
|
|
94
94
|
|
|
95
|
-
class
|
|
95
|
+
class AsyncgenWrapper(object):
|
|
96
96
|
def __init__(self, async_iter):
|
|
97
97
|
self.async_iter = async_iter
|
|
98
98
|
self.current = None
|
|
@@ -149,7 +149,7 @@ def ensure_generator(value):
|
|
|
149
149
|
if hasattr(inspect, "isasyncgen") and inspect.isasyncgen(
|
|
150
150
|
value
|
|
151
151
|
): # @UndefinedVariable
|
|
152
|
-
return True,
|
|
152
|
+
return True, AsyncgenWrapper(value)
|
|
153
153
|
|
|
154
154
|
return False, value
|
|
155
155
|
|
|
@@ -94,7 +94,7 @@ NAME = "appier"
|
|
|
94
94
|
""" The name to be used to describe the framework while working
|
|
95
95
|
on its own environment, this is just a descriptive value """
|
|
96
96
|
|
|
97
|
-
VERSION = "1.34.
|
|
97
|
+
VERSION = "1.34.7"
|
|
98
98
|
""" The version of the framework that is currently installed
|
|
99
99
|
this value may be used for debugging/diagnostic purposes """
|
|
100
100
|
|
|
@@ -1781,7 +1781,7 @@ class App(
|
|
|
1781
1781
|
# so that class level operations may be performed
|
|
1782
1782
|
cls = self.__class__
|
|
1783
1783
|
|
|
1784
|
-
# tries to ensure that the
|
|
1784
|
+
# tries to ensure that the UUID value of the exception is set,
|
|
1785
1785
|
# notice that under some extreme occasions it may not be possible
|
|
1786
1786
|
# to ensure such behavior (eg: native code based exception)
|
|
1787
1787
|
if not hasattr(exception, "uid"):
|
|
@@ -1821,6 +1821,10 @@ class App(
|
|
|
1821
1821
|
# "raised" by the current exception object in handling
|
|
1822
1822
|
self.request.set_headers(headers)
|
|
1823
1823
|
|
|
1824
|
+
# sets the additional error trace identifier header in the request, to allow
|
|
1825
|
+
# extra debug support for the error
|
|
1826
|
+
self.request.set_header("X-Error-Id", exception.uid)
|
|
1827
|
+
|
|
1824
1828
|
# runs the on error processor in the base application object and in case
|
|
1825
1829
|
# a value is returned by a possible handler it is used as the response
|
|
1826
1830
|
# for the current request (instead of the normal handler)
|
|
@@ -2232,7 +2236,7 @@ class App(
|
|
|
2232
2236
|
def warning(self, message):
|
|
2233
2237
|
self.request.warning(message)
|
|
2234
2238
|
|
|
2235
|
-
def redirect(self, url, code=303, params=None, **kwargs):
|
|
2239
|
+
def redirect(self, url, code=303, relative=False, params=None, **kwargs):
|
|
2236
2240
|
# in case there are no explicit parameters provided then the
|
|
2237
2241
|
# named arguments should be used instead
|
|
2238
2242
|
if params == None:
|
|
@@ -2244,6 +2248,15 @@ class App(
|
|
|
2244
2248
|
if query:
|
|
2245
2249
|
url += ("&" if "?" in url else "?") + query
|
|
2246
2250
|
|
|
2251
|
+
# in case a relative URL is expected, makes sure we enforce one
|
|
2252
|
+
# preventing possible absolute URL redirections
|
|
2253
|
+
if relative:
|
|
2254
|
+
is_absolute = url.startswith(("http://", "https://", "//"))
|
|
2255
|
+
if is_absolute:
|
|
2256
|
+
raise exceptions.SecurityError(
|
|
2257
|
+
message="Attempt to redirect to absolute URL", code=401
|
|
2258
|
+
)
|
|
2259
|
+
|
|
2247
2260
|
# sets both the (redirection) code and the new location URL
|
|
2248
2261
|
# values in the current request (response) object
|
|
2249
2262
|
self.request.code = code
|
|
@@ -2454,11 +2467,7 @@ class App(
|
|
|
2454
2467
|
|
|
2455
2468
|
parameters = dict(kwargs)
|
|
2456
2469
|
parameters.update(
|
|
2457
|
-
sender=sender,
|
|
2458
|
-
receivers=receivers,
|
|
2459
|
-
cc=cc,
|
|
2460
|
-
bcc=bcc,
|
|
2461
|
-
subject=subject,
|
|
2470
|
+
sender=sender, receivers=receivers, cc=cc, bcc=bcc, subject=subject
|
|
2462
2471
|
)
|
|
2463
2472
|
|
|
2464
2473
|
if html == None:
|
|
@@ -4065,9 +4074,7 @@ class App(
|
|
|
4065
4074
|
# is possible raises a not found error
|
|
4066
4075
|
part_s = self.get_part(part)
|
|
4067
4076
|
if not part_s:
|
|
4068
|
-
raise exceptions.NotFoundError(
|
|
4069
|
-
message="Part not found '%s'" % part,
|
|
4070
|
-
)
|
|
4077
|
+
raise exceptions.NotFoundError(message="Part not found '%s'" % part)
|
|
4071
4078
|
|
|
4072
4079
|
# sends the static information taking into account the
|
|
4073
4080
|
# provided data and the base static path of the part
|
|
@@ -6131,7 +6138,10 @@ class App(
|
|
|
6131
6138
|
continue
|
|
6132
6139
|
if _handler[1] and not scope == _handler[1]:
|
|
6133
6140
|
continue
|
|
6134
|
-
if
|
|
6141
|
+
# if the handler has explicitly defined to handle JSON
|
|
6142
|
+
# contexts then it should only be used if the JSON flag
|
|
6143
|
+
# is also set to the same value as in the handler
|
|
6144
|
+
if not _handler[2] == None and not json == _handler[2]:
|
|
6135
6145
|
continue
|
|
6136
6146
|
handler = _handler
|
|
6137
6147
|
break
|
|
@@ -6576,6 +6586,10 @@ class WebApp(App):
|
|
|
6576
6586
|
# "raised" by the current exception object in handling
|
|
6577
6587
|
self.request.set_headers(headers)
|
|
6578
6588
|
|
|
6589
|
+
# sets the additional error trace identifier header in the request, to allow
|
|
6590
|
+
# extra debug support for the error
|
|
6591
|
+
self.request.set_header("X-Error-Id", exception.uid)
|
|
6592
|
+
|
|
6579
6593
|
# run the on error processor in the base application object and in case
|
|
6580
6594
|
# a value is returned by a possible handler it is used as the response
|
|
6581
6595
|
# for the current request (instead of the normal handler)
|
|
@@ -163,10 +163,7 @@ def load(names=(FILE_NAME,), path=None, encoding="utf-8", ctx=None):
|
|
|
163
163
|
paths = []
|
|
164
164
|
homes = get_homes()
|
|
165
165
|
for home in homes:
|
|
166
|
-
paths += [
|
|
167
|
-
os.path.join(home),
|
|
168
|
-
os.path.join(home, ".config"),
|
|
169
|
-
]
|
|
166
|
+
paths += [os.path.join(home), os.path.join(home, ".config")]
|
|
170
167
|
paths += [sys.prefix]
|
|
171
168
|
paths.append(path)
|
|
172
169
|
for path in paths:
|
|
@@ -148,10 +148,7 @@ so that they are lazy loaded from the data source, these kind
|
|
|
148
148
|
of types should be compliant to a common interface so that they
|
|
149
149
|
may be used "blindly" from an external entity """
|
|
150
150
|
|
|
151
|
-
REVERSE = dict(
|
|
152
|
-
descending="ascending",
|
|
153
|
-
ascending="descending",
|
|
154
|
-
)
|
|
151
|
+
REVERSE = dict(descending="ascending", ascending="descending")
|
|
155
152
|
""" The reverse order dictionary that maps a certain
|
|
156
153
|
order direction (as a string) with the opposite one
|
|
157
154
|
this may be used to "calculate" the reverse value """
|
|
@@ -357,6 +354,7 @@ class Model(legacy.with_meta(meta.Ordered, observer.Observable, *EXTRA_CLS)):
|
|
|
357
354
|
safe=True,
|
|
358
355
|
build=False,
|
|
359
356
|
fill=True,
|
|
357
|
+
fill_safe=False,
|
|
360
358
|
new=True,
|
|
361
359
|
**kwargs
|
|
362
360
|
):
|
|
@@ -398,7 +396,11 @@ class Model(legacy.with_meta(meta.Ordered, observer.Observable, *EXTRA_CLS)):
|
|
|
398
396
|
injected into the resulting instance.
|
|
399
397
|
:type fill: bool
|
|
400
398
|
:param fill: If the various attributes of the model should be "filled"
|
|
401
|
-
with default values (avoiding empty values)
|
|
399
|
+
with default values (avoiding empty values), not going to be applied to
|
|
400
|
+
safe attributes.
|
|
401
|
+
:type fill_safe: bool
|
|
402
|
+
:param fill_safe: If the safe values should also be "filled" meaning that
|
|
403
|
+
they are initialized in a forced "way".
|
|
402
404
|
:type new: bool
|
|
403
405
|
:param new: In case this value is valid the resulting instance is expected
|
|
404
406
|
to be considered as new meaning that no identifier attributes are set.
|
|
@@ -411,7 +413,7 @@ class Model(legacy.with_meta(meta.Ordered, observer.Observable, *EXTRA_CLS)):
|
|
|
411
413
|
model = util.get_object() if form else dict(kwargs)
|
|
412
414
|
if fill:
|
|
413
415
|
model = cls.fill(model, safe=not new)
|
|
414
|
-
instance = cls(fill=
|
|
416
|
+
instance = cls(fill=fill_safe)
|
|
415
417
|
instance.apply(model, form=form, safe_a=safe)
|
|
416
418
|
if build:
|
|
417
419
|
cls.build(instance.model, map=False)
|
|
@@ -173,7 +173,7 @@ class BaseTest(unittest.TestCase):
|
|
|
173
173
|
invalid_email=["john"],
|
|
174
174
|
valid_length=["1234"],
|
|
175
175
|
invalid_length=["12345"],
|
|
176
|
-
)
|
|
176
|
+
)
|
|
177
177
|
)
|
|
178
178
|
self.app._request = request
|
|
179
179
|
|
|
@@ -369,10 +369,7 @@ class BaseTest(unittest.TestCase):
|
|
|
369
369
|
self.skipTest("No Jinja2 template engine present")
|
|
370
370
|
|
|
371
371
|
self.app._register_bundle(
|
|
372
|
-
{
|
|
373
|
-
"hello": appier.legacy.u("olá"),
|
|
374
|
-
"world": appier.legacy.u("mundo"),
|
|
375
|
-
},
|
|
372
|
+
{"hello": appier.legacy.u("olá"), "world": appier.legacy.u("mundo")},
|
|
376
373
|
"pt_pt",
|
|
377
374
|
)
|
|
378
375
|
|
|
@@ -28,6 +28,8 @@ __copyright__ = "Copyright (c) 2008-2024 Hive Solutions Lda."
|
|
|
28
28
|
__license__ = "Apache License, Version 2.0"
|
|
29
29
|
""" The license for the module """
|
|
30
30
|
|
|
31
|
+
import os
|
|
32
|
+
import tempfile
|
|
31
33
|
import unittest
|
|
32
34
|
|
|
33
35
|
import appier
|
|
@@ -40,3 +42,11 @@ class DataTest(unittest.TestCase):
|
|
|
40
42
|
|
|
41
43
|
self.assertEqual(type(identifier), str)
|
|
42
44
|
self.assertEqual(len(identifier), 24)
|
|
45
|
+
|
|
46
|
+
def test_drop_db_missing(self):
|
|
47
|
+
fd, file_path = tempfile.mkstemp()
|
|
48
|
+
os.close(fd)
|
|
49
|
+
adapter = appier.TinyAdapter(file_path=file_path)
|
|
50
|
+
adapter.get_db()
|
|
51
|
+
os.remove(file_path)
|
|
52
|
+
adapter.drop_db()
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
# Hive Appier Framework
|
|
5
|
+
# Copyright (c) 2008-2024 Hive Solutions Lda.
|
|
6
|
+
#
|
|
7
|
+
# This file is part of Hive Appier Framework.
|
|
8
|
+
#
|
|
9
|
+
# Hive Appier Framework is free software: you can redistribute it and/or modify
|
|
10
|
+
# it under the terms of the Apache License as published by the Apache
|
|
11
|
+
# Foundation, either version 2.0 of the License, or (at your option) any
|
|
12
|
+
# later version.
|
|
13
|
+
#
|
|
14
|
+
# Hive Appier Framework is distributed in the hope that it will be useful,
|
|
15
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
16
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
17
|
+
# Apache License for more details.
|
|
18
|
+
#
|
|
19
|
+
# You should have received a copy of the Apache License along with
|
|
20
|
+
# Hive Appier Framework. If not, see <http://www.apache.org/licenses/>.
|
|
21
|
+
|
|
22
|
+
__author__ = "João Magalhães <joamag@hive.pt>"
|
|
23
|
+
""" The author(s) of the module """
|
|
24
|
+
|
|
25
|
+
__copyright__ = "Copyright (c) 2008-2024 Hive Solutions Lda."
|
|
26
|
+
""" The copyright for the module """
|
|
27
|
+
|
|
28
|
+
__license__ = "Apache License, Version 2.0"
|
|
29
|
+
""" The license for the module """
|
|
30
|
+
|
|
31
|
+
import unittest
|
|
32
|
+
|
|
33
|
+
import appier
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ErrorHandlerTest(unittest.TestCase):
|
|
37
|
+
def setUp(self):
|
|
38
|
+
self._original_handlers = appier.common.base().App._ERROR_HANDLERS
|
|
39
|
+
appier.common.base().App._ERROR_HANDLERS = {}
|
|
40
|
+
self.app = appier.App()
|
|
41
|
+
|
|
42
|
+
def tearDown(self):
|
|
43
|
+
self.app.unload()
|
|
44
|
+
appier.common.base().App._ERROR_HANDLERS = self._original_handlers
|
|
45
|
+
|
|
46
|
+
def test_basic_registration_and_call(self):
|
|
47
|
+
"""
|
|
48
|
+
Decorator should register the handler and it must be invoked by
|
|
49
|
+
:pyfunc:`appier.App.call_error` when an exception matching the provided
|
|
50
|
+
error code is raised.
|
|
51
|
+
|
|
52
|
+
The error handler is a JSON handler by default, to be able to properly
|
|
53
|
+
handle errors in an App.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
expected_message = "resource not found"
|
|
57
|
+
|
|
58
|
+
@appier.error_handler(404, json=True)
|
|
59
|
+
def not_found(_):
|
|
60
|
+
return expected_message
|
|
61
|
+
|
|
62
|
+
exc = appier.exceptions.NotFoundError("dummy")
|
|
63
|
+
result = self.app.call_error(exc, code=exc.code, scope=None, json=True)
|
|
64
|
+
|
|
65
|
+
self.assertEqual(result, expected_message)
|
|
66
|
+
|
|
67
|
+
handlers = appier.common.base().App._ERROR_HANDLERS.get(404)
|
|
68
|
+
self.assertNotEqual(handlers, None)
|
|
69
|
+
self.assertEqual(len(handlers), 1)
|
|
70
|
+
|
|
71
|
+
method, scope, json, opts, ctx, priority = handlers[0]
|
|
72
|
+
self.assertEqual(method, not_found)
|
|
73
|
+
self.assertEqual(scope, None)
|
|
74
|
+
self.assertEqual(json, True)
|
|
75
|
+
self.assertEqual(opts, None)
|
|
76
|
+
self.assertEqual(ctx, None)
|
|
77
|
+
self.assertEqual(priority, 1)
|
|
78
|
+
|
|
79
|
+
def test_web_handler(self):
|
|
80
|
+
"""
|
|
81
|
+
Test that in which the error handler is a web handler by default, to be
|
|
82
|
+
able to properly handle errors in an WebApp, because this is a App no
|
|
83
|
+
handler is called.
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
expected_message = "resource not found"
|
|
87
|
+
|
|
88
|
+
@appier.error_handler(404, json=False)
|
|
89
|
+
def not_found(_):
|
|
90
|
+
return expected_message
|
|
91
|
+
|
|
92
|
+
exc = appier.exceptions.NotFoundError("dummy")
|
|
93
|
+
result = self.app.call_error(exc, code=exc.code, scope=None, json=True)
|
|
94
|
+
|
|
95
|
+
self.assertEqual(result, None)
|
|
96
|
+
|
|
97
|
+
handlers = appier.common.base().App._ERROR_HANDLERS.get(404)
|
|
98
|
+
self.assertNotEqual(handlers, None)
|
|
99
|
+
self.assertEqual(len(handlers), 1)
|
|
100
|
+
|
|
101
|
+
method, scope, json, opts, ctx, priority = handlers[0]
|
|
102
|
+
self.assertEqual(method, not_found)
|
|
103
|
+
self.assertEqual(scope, None)
|
|
104
|
+
self.assertEqual(json, False)
|
|
105
|
+
self.assertEqual(opts, None)
|
|
106
|
+
self.assertEqual(ctx, None)
|
|
107
|
+
self.assertEqual(priority, 1)
|
|
108
|
+
|
|
109
|
+
def test_scope_registration(self):
|
|
110
|
+
"""
|
|
111
|
+
When a *scope* argument is provided, it should be stored in the handler
|
|
112
|
+
metadata so that the framework can later match it appropriately.
|
|
113
|
+
|
|
114
|
+
The error handler is a JSON handler by default, to be able to properly
|
|
115
|
+
handle errors in an App.
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
class DummyScope:
|
|
119
|
+
pass
|
|
120
|
+
|
|
121
|
+
@appier.error_handler(400, scope=DummyScope, json=True)
|
|
122
|
+
def bad_request(_):
|
|
123
|
+
return "bad request"
|
|
124
|
+
|
|
125
|
+
handlers = appier.common.base().App._ERROR_HANDLERS.get(400)
|
|
126
|
+
self.assertNotEqual(handlers, None)
|
|
127
|
+
self.assertEqual(len(handlers), 1)
|
|
128
|
+
|
|
129
|
+
method, scope, json, opts, ctx, priority = handlers[0]
|
|
130
|
+
|
|
131
|
+
self.assertEqual(method, bad_request)
|
|
132
|
+
self.assertEqual(scope, DummyScope)
|
|
133
|
+
self.assertEqual(json, True)
|
|
134
|
+
self.assertEqual(opts, None)
|
|
135
|
+
self.assertEqual(ctx, None)
|
|
136
|
+
self.assertEqual(priority, 1)
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
#!/usr/bin/python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
# Hive Appier Framework
|
|
5
|
+
# Copyright (c) 2008-2024 Hive Solutions Lda.
|
|
6
|
+
#
|
|
7
|
+
# This file is part of Hive Appier Framework.
|
|
8
|
+
#
|
|
9
|
+
# Hive Appier Framework is free software: you can redistribute it and/or modify
|
|
10
|
+
# it under the terms of the Apache License as published by the Apache
|
|
11
|
+
# Foundation, either version 2.0 of the License, or (at your option) any
|
|
12
|
+
# later version.
|
|
13
|
+
#
|
|
14
|
+
# Hive Appier Framework is distributed in the hope that it will be useful,
|
|
15
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
16
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
17
|
+
# Apache License for more details.
|
|
18
|
+
#
|
|
19
|
+
# You should have received a copy of the Apache License along with
|
|
20
|
+
# Hive Appier Framework. If not, see <http://www.apache.org/licenses/>.
|
|
21
|
+
|
|
22
|
+
__author__ = "João Magalhães <joamag@hive.pt>"
|
|
23
|
+
""" The author(s) of the module """
|
|
24
|
+
|
|
25
|
+
__copyright__ = "Copyright (c) 2008-2024 Hive Solutions Lda."
|
|
26
|
+
""" The copyright for the module """
|
|
27
|
+
|
|
28
|
+
__license__ = "Apache License, Version 2.0"
|
|
29
|
+
""" The license for the module """
|
|
30
|
+
|
|
31
|
+
import unittest
|
|
32
|
+
|
|
33
|
+
import appier
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ExceptionHandlerTest(unittest.TestCase):
|
|
37
|
+
def setUp(self):
|
|
38
|
+
self._original_handlers = appier.common.base().App._ERROR_HANDLERS
|
|
39
|
+
appier.common.base().App._ERROR_HANDLERS = {}
|
|
40
|
+
self.app = appier.App()
|
|
41
|
+
|
|
42
|
+
def tearDown(self):
|
|
43
|
+
self.app.unload()
|
|
44
|
+
appier.common.base().App._ERROR_HANDLERS = self._original_handlers
|
|
45
|
+
|
|
46
|
+
def test_basic_registration_and_call(self):
|
|
47
|
+
"""
|
|
48
|
+
Decorator should register the handler and it must be invoked by
|
|
49
|
+
:pyfunc:`appier.App.call_error` when an exception matching the provided
|
|
50
|
+
type is raised.
|
|
51
|
+
|
|
52
|
+
The exception handler is a JSON handler by default, to be able to properly
|
|
53
|
+
handle errors in an App.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
expected_message = "resource not found"
|
|
57
|
+
|
|
58
|
+
@appier.exception_handler(appier.exceptions.NotFoundError, json=True)
|
|
59
|
+
def not_found(_):
|
|
60
|
+
return expected_message
|
|
61
|
+
|
|
62
|
+
exc = appier.exceptions.NotFoundError("dummy")
|
|
63
|
+
result = self.app.call_error(exc, code=exc.code, scope=None, json=True)
|
|
64
|
+
|
|
65
|
+
self.assertEqual(result, expected_message)
|
|
66
|
+
|
|
67
|
+
handlers = appier.common.base().App._ERROR_HANDLERS.get(
|
|
68
|
+
appier.exceptions.NotFoundError
|
|
69
|
+
)
|
|
70
|
+
self.assertNotEqual(handlers, None)
|
|
71
|
+
self.assertEqual(len(handlers), 1)
|
|
72
|
+
|
|
73
|
+
method, scope, json, opts, ctx, priority = handlers[0]
|
|
74
|
+
self.assertEqual(method, not_found)
|
|
75
|
+
self.assertEqual(scope, None)
|
|
76
|
+
self.assertEqual(json, True)
|
|
77
|
+
self.assertEqual(opts, None)
|
|
78
|
+
self.assertEqual(ctx, None)
|
|
79
|
+
self.assertEqual(priority, 1)
|
|
80
|
+
|
|
81
|
+
def test_scope_registration(self):
|
|
82
|
+
"""
|
|
83
|
+
When a *scope* argument is provided, it should be stored in the handler
|
|
84
|
+
metadata so that the framework can later match it appropriately.
|
|
85
|
+
|
|
86
|
+
The exception handler is a JSON handler by default, to be able to properly
|
|
87
|
+
handle errors in an App.
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
class DummyScope:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
class DummyException(Exception):
|
|
94
|
+
code = 400
|
|
95
|
+
|
|
96
|
+
@appier.exception_handler(DummyException, scope=DummyScope, json=True)
|
|
97
|
+
def dummy_handler(_):
|
|
98
|
+
return "dummy"
|
|
99
|
+
|
|
100
|
+
handlers = appier.common.base().App._ERROR_HANDLERS.get(DummyException)
|
|
101
|
+
self.assertNotEqual(handlers, None)
|
|
102
|
+
self.assertEqual(len(handlers), 1)
|
|
103
|
+
|
|
104
|
+
method, scope, json, opts, ctx, priority = handlers[0]
|
|
105
|
+
|
|
106
|
+
self.assertEqual(method, dummy_handler)
|
|
107
|
+
self.assertEqual(scope, DummyScope)
|
|
108
|
+
self.assertEqual(json, True)
|
|
109
|
+
self.assertEqual(opts, None)
|
|
110
|
+
self.assertEqual(ctx, None)
|
|
111
|
+
self.assertEqual(priority, 1)
|
|
112
|
+
|
|
113
|
+
def test_web_handler(self):
|
|
114
|
+
"""
|
|
115
|
+
Test that in which the exception handler is a web handler by default, to be
|
|
116
|
+
able to properly handle errors in an WebApp, because this is an App no
|
|
117
|
+
handler is called.
|
|
118
|
+
"""
|
|
119
|
+
|
|
120
|
+
expected_message = "resource not found"
|
|
121
|
+
|
|
122
|
+
@appier.exception_handler(appier.exceptions.NotFoundError, json=False)
|
|
123
|
+
def not_found(_):
|
|
124
|
+
return expected_message
|
|
125
|
+
|
|
126
|
+
exc = appier.exceptions.NotFoundError("dummy")
|
|
127
|
+
result = self.app.call_error(exc, code=exc.code, scope=None, json=True)
|
|
128
|
+
|
|
129
|
+
self.assertEqual(result, None)
|
|
130
|
+
|
|
131
|
+
handlers = appier.common.base().App._ERROR_HANDLERS.get(
|
|
132
|
+
appier.exceptions.NotFoundError
|
|
133
|
+
)
|
|
134
|
+
self.assertNotEqual(handlers, None)
|
|
135
|
+
self.assertEqual(len(handlers), 1)
|
|
136
|
+
|
|
137
|
+
method, scope, json, opts, ctx, priority = handlers[0]
|
|
138
|
+
self.assertEqual(method, not_found)
|
|
139
|
+
self.assertEqual(scope, None)
|
|
140
|
+
self.assertEqual(json, False)
|
|
141
|
+
self.assertEqual(opts, None)
|
|
142
|
+
self.assertEqual(ctx, None)
|
|
143
|
+
self.assertEqual(priority, 1)
|
|
@@ -104,6 +104,9 @@ class HTTPTest(unittest.TestCase):
|
|
|
104
104
|
self.assertEqual(params, dict(hello=["world"]))
|
|
105
105
|
|
|
106
106
|
def test_redirect(self):
|
|
107
|
+
if appier.conf("NO_NETWORK", False, cast=bool):
|
|
108
|
+
self.skipTest("Network access is disabled")
|
|
109
|
+
|
|
107
110
|
_data, response = appier.get(
|
|
108
111
|
"https://%s/redirect-to" % self.httpbin,
|
|
109
112
|
params=dict(url="https://%s/" % self.httpbin),
|
|
@@ -135,6 +138,9 @@ class HTTPTest(unittest.TestCase):
|
|
|
135
138
|
self.assertEqual(code, 200)
|
|
136
139
|
|
|
137
140
|
def test_timeout(self):
|
|
141
|
+
if appier.conf("NO_NETWORK", False, cast=bool):
|
|
142
|
+
self.skipTest("Network access is disabled")
|
|
143
|
+
|
|
138
144
|
self.assertRaises(
|
|
139
145
|
BaseException,
|
|
140
146
|
lambda: appier.get(
|
|
@@ -155,6 +161,9 @@ class HTTPTest(unittest.TestCase):
|
|
|
155
161
|
self.assertNotEqual(data, None)
|
|
156
162
|
|
|
157
163
|
def test_get_f(self):
|
|
164
|
+
if appier.conf("NO_NETWORK", False, cast=bool):
|
|
165
|
+
self.skipTest("Network access is disabled")
|
|
166
|
+
|
|
158
167
|
file = appier.get_f("https://%s/image/png" % self.httpbin)
|
|
159
168
|
|
|
160
169
|
self.assertEqual(file.file_name, "default")
|
|
@@ -170,6 +179,9 @@ class HTTPTest(unittest.TestCase):
|
|
|
170
179
|
self.assertEqual(len(file.data_b64) > 100, True)
|
|
171
180
|
|
|
172
181
|
def test_generator(self):
|
|
182
|
+
if appier.conf("NO_NETWORK", False, cast=bool):
|
|
183
|
+
self.skipTest("Network access is disabled")
|
|
184
|
+
|
|
173
185
|
def text_g(message=[b"hello", b" ", b"world"]):
|
|
174
186
|
yield sum(len(value) for value in message)
|
|
175
187
|
for value in message:
|
|
@@ -185,6 +197,9 @@ class HTTPTest(unittest.TestCase):
|
|
|
185
197
|
self.assertEqual(data["data"], "hello world")
|
|
186
198
|
|
|
187
199
|
def test_file(self):
|
|
200
|
+
if appier.conf("NO_NETWORK", False, cast=bool):
|
|
201
|
+
self.skipTest("Network access is disabled")
|
|
202
|
+
|
|
188
203
|
data, response = appier.post(
|
|
189
204
|
"https://%s/post" % self.httpbin,
|
|
190
205
|
data=appier.legacy.BytesIO(b"hello world"),
|
|
@@ -198,6 +213,9 @@ class HTTPTest(unittest.TestCase):
|
|
|
198
213
|
self.assertEqual(data["data"], "hello world")
|
|
199
214
|
|
|
200
215
|
def test_multithread(self):
|
|
216
|
+
if appier.conf("NO_NETWORK", False, cast=bool):
|
|
217
|
+
self.skipTest("Network access is disabled")
|
|
218
|
+
|
|
201
219
|
threads = []
|
|
202
220
|
results = []
|
|
203
221
|
|
|
@@ -230,11 +248,17 @@ class HTTPTest(unittest.TestCase):
|
|
|
230
248
|
self.assertEqual(code, 200)
|
|
231
249
|
|
|
232
250
|
def test_error(self):
|
|
251
|
+
if appier.conf("NO_NETWORK", False, cast=bool):
|
|
252
|
+
self.skipTest("Network access is disabled")
|
|
253
|
+
|
|
233
254
|
self.assertRaises(
|
|
234
255
|
appier.HTTPError, lambda: appier.get("https://%s/status/404" % self.httpbin)
|
|
235
256
|
)
|
|
236
257
|
|
|
237
258
|
def test_invalid(self):
|
|
259
|
+
if appier.conf("NO_NETWORK", False, cast=bool):
|
|
260
|
+
self.skipTest("Network access is disabled")
|
|
261
|
+
|
|
238
262
|
self.assertRaises(
|
|
239
263
|
BaseException, lambda: appier.get("https://invalidlargedomain.org/")
|
|
240
264
|
)
|
|
@@ -555,11 +555,7 @@ class UtilTest(unittest.TestCase):
|
|
|
555
555
|
self.assertEqual(type(result), list)
|
|
556
556
|
self.assertEqual(result, [])
|
|
557
557
|
|
|
558
|
-
result = appier.email_base(
|
|
559
|
-
[
|
|
560
|
-
appier.legacy.u(""),
|
|
561
|
-
]
|
|
562
|
-
)
|
|
558
|
+
result = appier.email_base([appier.legacy.u("")])
|
|
563
559
|
self.assertEqual(type(result), list)
|
|
564
560
|
self.assertEqual(result, [])
|
|
565
561
|
|
|
@@ -582,8 +578,7 @@ class UtilTest(unittest.TestCase):
|
|
|
582
578
|
raise appier.OperationalError(message="hello")
|
|
583
579
|
|
|
584
580
|
struct = appier.lazy_dict(
|
|
585
|
-
first=appier.lazy(lambda: raiser()),
|
|
586
|
-
second=appier.lazy(lambda: 2),
|
|
581
|
+
first=appier.lazy(lambda: raiser()), second=appier.lazy(lambda: 2)
|
|
587
582
|
)
|
|
588
583
|
|
|
589
584
|
errors = appier.gather_errors(struct)
|
|
@@ -1077,15 +1072,7 @@ class UtilTest(unittest.TestCase):
|
|
|
1077
1072
|
),
|
|
1078
1073
|
)
|
|
1079
1074
|
|
|
1080
|
-
first = {
|
|
1081
|
-
"info": {
|
|
1082
|
-
"personal": {
|
|
1083
|
-
"general": {
|
|
1084
|
-
"kind": "human",
|
|
1085
|
-
}
|
|
1086
|
-
}
|
|
1087
|
-
}
|
|
1088
|
-
}
|
|
1075
|
+
first = {"info": {"personal": {"general": {"kind": "human"}}}}
|
|
1089
1076
|
second = {
|
|
1090
1077
|
"info": {
|
|
1091
1078
|
"personal": {"general": {"kind": "cat", "tail": "long", "meaw": 12}},
|
|
@@ -68,12 +68,7 @@ ALL_CAP_REGEX = re.compile(r"([a-z0-9])([A-Z])")
|
|
|
68
68
|
upper case letter regex that will provide a way of
|
|
69
69
|
putting the underscore in the middle of the transition """
|
|
70
70
|
|
|
71
|
-
SORT_MAP = {
|
|
72
|
-
"1": 1,
|
|
73
|
-
"-1": -1,
|
|
74
|
-
"ascending": 1,
|
|
75
|
-
"descending": -1,
|
|
76
|
-
}
|
|
71
|
+
SORT_MAP = {"1": 1, "-1": -1, "ascending": 1, "descending": -1}
|
|
77
72
|
""" The map associating the normalized (text) way of
|
|
78
73
|
representing sorting with the current infra-structure
|
|
79
74
|
number way of representing the same information """
|
|
@@ -2150,13 +2145,13 @@ def route(url, method="GET", asynchronous=False, json=False, opts=None, priority
|
|
|
2150
2145
|
return decorator
|
|
2151
2146
|
|
|
2152
2147
|
|
|
2153
|
-
def error_handler(code, scope=None, json=
|
|
2148
|
+
def error_handler(code, scope=None, json=None, opts=None, priority=1):
|
|
2154
2149
|
def decorator(function, *args, **kwargs):
|
|
2155
2150
|
if is_detached(function):
|
|
2156
2151
|
delay(function, *args, **kwargs)
|
|
2157
2152
|
else:
|
|
2158
2153
|
common.base().App.add_error(
|
|
2159
|
-
code, function, json=json, opts=opts, priority=priority
|
|
2154
|
+
code, function, scope=scope, json=json, opts=opts, priority=priority
|
|
2160
2155
|
)
|
|
2161
2156
|
return function
|
|
2162
2157
|
|
|
@@ -2173,13 +2168,18 @@ def error_handler(code, scope=None, json=False, opts=None, priority=1):
|
|
|
2173
2168
|
return decorator
|
|
2174
2169
|
|
|
2175
2170
|
|
|
2176
|
-
def exception_handler(exception, scope=None, json=
|
|
2171
|
+
def exception_handler(exception, scope=None, json=None, opts=None, priority=1):
|
|
2177
2172
|
def decorator(function, *args, **kwargs):
|
|
2178
2173
|
if is_detached(function):
|
|
2179
2174
|
delay(function, *args, **kwargs)
|
|
2180
2175
|
else:
|
|
2181
2176
|
common.base().App.add_exception(
|
|
2182
|
-
exception,
|
|
2177
|
+
exception,
|
|
2178
|
+
function,
|
|
2179
|
+
scope=scope,
|
|
2180
|
+
json=json,
|
|
2181
|
+
opts=opts,
|
|
2182
|
+
priority=priority,
|
|
2183
2183
|
)
|
|
2184
2184
|
return function
|
|
2185
2185
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
MANIFEST.in
|
|
2
2
|
README.rst
|
|
3
|
+
pyproject.toml
|
|
3
4
|
setup.cfg
|
|
4
5
|
setup.py
|
|
5
6
|
src/appier/__init__.py
|
|
@@ -81,6 +82,8 @@ src/appier/test/cache.py
|
|
|
81
82
|
src/appier/test/config.py
|
|
82
83
|
src/appier/test/crypt.py
|
|
83
84
|
src/appier/test/data.py
|
|
85
|
+
src/appier/test/error_handler.py
|
|
86
|
+
src/appier/test/exception_handler.py
|
|
84
87
|
src/appier/test/exceptions.py
|
|
85
88
|
src/appier/test/export.py
|
|
86
89
|
src/appier/test/graph.py
|
|
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
|
|
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
|