kinto 18.1.1__py3-none-any.whl → 19.3.0__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.
Potentially problematic release.
This version of kinto might be problematic. Click here for more details.
- kinto/__init__.py +1 -0
- kinto/core/__init__.py +2 -1
- kinto/core/initialization.py +132 -53
- kinto/core/metrics.py +93 -0
- kinto/core/statsd.py +1 -63
- kinto/core/testing.py +5 -1
- kinto/core/utils.py +8 -0
- kinto/plugins/admin/VERSION +1 -1
- kinto/plugins/admin/build/VERSION +1 -1
- kinto/plugins/admin/build/assets/index-BKIg2XW8.js +165 -0
- kinto/plugins/admin/build/assets/{index-vylaZGUr.css → index-D8oiN37x.css} +1 -1
- kinto/plugins/admin/build/assets/{javascript-upQ8KtFH.js → javascript-iSgyE4tI.js} +1 -1
- kinto/plugins/admin/build/index.html +2 -2
- kinto/plugins/history/__init__.py +5 -6
- kinto/plugins/prometheus.py +186 -0
- kinto/plugins/quotas/__init__.py +5 -6
- kinto/plugins/statsd.py +70 -0
- {kinto-18.1.1.dist-info → kinto-19.3.0.dist-info}/METADATA +5 -4
- {kinto-18.1.1.dist-info → kinto-19.3.0.dist-info}/RECORD +32 -29
- {kinto-18.1.1.dist-info → kinto-19.3.0.dist-info}/WHEEL +1 -1
- kinto/plugins/admin/build/assets/index-iVTdxamX.js +0 -175
- /kinto/plugins/admin/build/assets/{asn1-8gHclKtu.js → asn1-CGOzndHr.js} +0 -0
- /kinto/plugins/admin/build/assets/{clojure-plf_rynZ.js → clojure-BMjYHr_A.js} +0 -0
- /kinto/plugins/admin/build/assets/{css-tpsEXL3H.js → css-BnMrqG3P.js} +0 -0
- /kinto/plugins/admin/build/assets/{logo-FQUYikj1.png → logo-VBRiKSPX.png} +0 -0
- /kinto/plugins/admin/build/assets/{mllike-ilm95jrV.js → mllike-C_8OmSiT.js} +0 -0
- /kinto/plugins/admin/build/assets/{python-xljIYvii.js → python-BuPzkPfP.js} +0 -0
- /kinto/plugins/admin/build/assets/{rpm-cddeyEgF.js → rpm-CTu-6PCP.js} +0 -0
- /kinto/plugins/admin/build/assets/{sql-3IaSLchm.js → sql-C4g8LzGK.js} +0 -0
- /kinto/plugins/admin/build/assets/{ttcn-cfg-9oMIyPXS.js → ttcn-cfg-BIkV9KBc.js} +0 -0
- {kinto-18.1.1.dist-info → kinto-19.3.0.dist-info}/LICENSE +0 -0
- {kinto-18.1.1.dist-info → kinto-19.3.0.dist-info}/entry_points.txt +0 -0
- {kinto-18.1.1.dist-info → kinto-19.3.0.dist-info}/top_level.txt +0 -0
kinto/__init__.py
CHANGED
kinto/core/__init__.py
CHANGED
|
@@ -66,8 +66,8 @@ DEFAULT_SETTINGS = {
|
|
|
66
66
|
"kinto.core.initialization.setup_authentication",
|
|
67
67
|
"kinto.core.initialization.setup_backoff",
|
|
68
68
|
"kinto.core.initialization.setup_sentry",
|
|
69
|
-
"kinto.core.initialization.setup_statsd",
|
|
70
69
|
"kinto.core.initialization.setup_listeners",
|
|
70
|
+
"kinto.core.initialization.setup_metrics",
|
|
71
71
|
"kinto.core.events.setup_transaction_hook",
|
|
72
72
|
),
|
|
73
73
|
"event_listeners": "",
|
|
@@ -96,6 +96,7 @@ DEFAULT_SETTINGS = {
|
|
|
96
96
|
"statsd_backend": "kinto.core.statsd",
|
|
97
97
|
"statsd_prefix": "kinto.core",
|
|
98
98
|
"statsd_url": None,
|
|
99
|
+
"metrics_matchdict_fields": [],
|
|
99
100
|
"storage_backend": "",
|
|
100
101
|
"storage_url": "",
|
|
101
102
|
"storage_max_fetch_size": 10000,
|
kinto/core/initialization.py
CHANGED
|
@@ -21,7 +21,7 @@ from pyramid.security import NO_PERMISSION_REQUIRED
|
|
|
21
21
|
from pyramid.settings import asbool, aslist
|
|
22
22
|
from pyramid_multiauth import MultiAuthenticationPolicy, MultiAuthPolicySelected
|
|
23
23
|
|
|
24
|
-
from kinto.core import cache, errors, permission, storage, utils
|
|
24
|
+
from kinto.core import cache, errors, metrics, permission, storage, utils
|
|
25
25
|
from kinto.core.events import ACTIONS, ResourceChanged, ResourceRead
|
|
26
26
|
|
|
27
27
|
|
|
@@ -334,51 +334,13 @@ def setup_sentry(config):
|
|
|
334
334
|
|
|
335
335
|
|
|
336
336
|
def setup_statsd(config):
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
config.registry.statsd = client
|
|
346
|
-
|
|
347
|
-
client.watch_execution_time(config.registry.cache, prefix="backend")
|
|
348
|
-
client.watch_execution_time(config.registry.storage, prefix="backend")
|
|
349
|
-
client.watch_execution_time(config.registry.permission, prefix="backend")
|
|
350
|
-
|
|
351
|
-
# Commit so that configured policy can be queried.
|
|
352
|
-
config.commit()
|
|
353
|
-
policy = config.registry.queryUtility(IAuthenticationPolicy)
|
|
354
|
-
if isinstance(policy, MultiAuthenticationPolicy):
|
|
355
|
-
for name, subpolicy in policy.get_policies():
|
|
356
|
-
client.watch_execution_time(subpolicy, prefix="authentication", classname=name)
|
|
357
|
-
else:
|
|
358
|
-
client.watch_execution_time(policy, prefix="authentication")
|
|
359
|
-
|
|
360
|
-
def on_new_response(event):
|
|
361
|
-
request = event.request
|
|
362
|
-
|
|
363
|
-
# Count unique users.
|
|
364
|
-
user_id = request.prefixed_userid
|
|
365
|
-
if user_id:
|
|
366
|
-
# Get rid of colons in metric packet (see #1282).
|
|
367
|
-
user_id = user_id.replace(":", ".")
|
|
368
|
-
client.count("users", unique=user_id)
|
|
369
|
-
|
|
370
|
-
# Count authentication verifications.
|
|
371
|
-
if hasattr(request, "authn_type"):
|
|
372
|
-
client.count(f"authn_type.{request.authn_type}")
|
|
373
|
-
|
|
374
|
-
# Count view calls.
|
|
375
|
-
service = request.current_service
|
|
376
|
-
if service:
|
|
377
|
-
client.count(f"view.{service.name}.{request.method}")
|
|
378
|
-
|
|
379
|
-
config.add_subscriber(on_new_response, NewResponse)
|
|
380
|
-
|
|
381
|
-
return client
|
|
337
|
+
# It would be pretty rare to find users that have a custom ``kinto.initialization_sequence`` setting.
|
|
338
|
+
# But just in case, warn that it will be removed in next major.
|
|
339
|
+
warnings.warn(
|
|
340
|
+
"``setup_statsd()`` is now deprecated. Use ``kinto.core.initialization.setup_metrics()`` instead.",
|
|
341
|
+
DeprecationWarning,
|
|
342
|
+
)
|
|
343
|
+
setup_metrics(config)
|
|
382
344
|
|
|
383
345
|
|
|
384
346
|
def install_middlewares(app, settings):
|
|
@@ -466,6 +428,125 @@ def setup_logging(config):
|
|
|
466
428
|
config.add_subscriber(on_new_response, NewResponse)
|
|
467
429
|
|
|
468
430
|
|
|
431
|
+
def setup_metrics(config):
|
|
432
|
+
settings = config.get_settings()
|
|
433
|
+
|
|
434
|
+
# Register a no-op metrics service by default.
|
|
435
|
+
config.registry.registerUtility(metrics.NoOpMetricsService(), metrics.IMetricsService)
|
|
436
|
+
|
|
437
|
+
# This does not fully respect the Pyramid/ZCA patterns, but the rest of Kinto uses
|
|
438
|
+
# `registry.storage`, `registry.cache`, etc. Consistency seems more important.
|
|
439
|
+
config.registry.__class__.metrics = property(
|
|
440
|
+
lambda reg: reg.queryUtility(metrics.IMetricsService)
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
def deprecated_registry(self):
|
|
444
|
+
warnings.warn(
|
|
445
|
+
"``config.registry.statsd`` is now deprecated. Use ``config.registry.metrics`` instead.",
|
|
446
|
+
DeprecationWarning,
|
|
447
|
+
)
|
|
448
|
+
return self.metrics
|
|
449
|
+
|
|
450
|
+
config.registry.__class__.statsd = property(deprecated_registry)
|
|
451
|
+
|
|
452
|
+
def on_app_created(event):
|
|
453
|
+
config = event.app
|
|
454
|
+
metrics_service = config.registry.metrics
|
|
455
|
+
|
|
456
|
+
metrics.watch_execution_time(metrics_service, config.registry.cache, prefix="backend")
|
|
457
|
+
metrics.watch_execution_time(metrics_service, config.registry.storage, prefix="backend")
|
|
458
|
+
metrics.watch_execution_time(metrics_service, config.registry.permission, prefix="backend")
|
|
459
|
+
|
|
460
|
+
policy = config.registry.queryUtility(IAuthenticationPolicy)
|
|
461
|
+
if isinstance(policy, MultiAuthenticationPolicy):
|
|
462
|
+
for name, subpolicy in policy.get_policies():
|
|
463
|
+
metrics.watch_execution_time(
|
|
464
|
+
metrics_service, subpolicy, prefix="authentication", classname=name
|
|
465
|
+
)
|
|
466
|
+
else:
|
|
467
|
+
metrics.watch_execution_time(metrics_service, policy, prefix="authentication")
|
|
468
|
+
|
|
469
|
+
config.add_subscriber(on_app_created, ApplicationCreated)
|
|
470
|
+
|
|
471
|
+
def on_new_response(event):
|
|
472
|
+
request = event.request
|
|
473
|
+
metrics_service = config.registry.metrics
|
|
474
|
+
|
|
475
|
+
try:
|
|
476
|
+
endpoint = utils.strip_uri_prefix(request.path)
|
|
477
|
+
except UnicodeDecodeError as e:
|
|
478
|
+
# This `on_new_response` callback is also called when a HTTP 400
|
|
479
|
+
# is returned because of an invalid UTF-8 path. We still want metrics.
|
|
480
|
+
endpoint = str(e)
|
|
481
|
+
|
|
482
|
+
# Count unique users.
|
|
483
|
+
user_id = request.prefixed_userid
|
|
484
|
+
if user_id:
|
|
485
|
+
# Get rid of colons in metric packet (see #1282).
|
|
486
|
+
auth, user_id = user_id.split(":")
|
|
487
|
+
metrics_service.count("users", unique=[("auth", auth), ("userid", user_id)])
|
|
488
|
+
|
|
489
|
+
# Add extra labels to metrics, based on fields extracted from the request matchdict.
|
|
490
|
+
metrics_matchdict_fields = aslist(settings["metrics_matchdict_fields"])
|
|
491
|
+
# Turn the `id` field of object endpoints into `{resource}_id` (eg. `mushroom_id`, `bucket_id`)
|
|
492
|
+
enhanced_matchdict = dict(request.matchdict or {})
|
|
493
|
+
try:
|
|
494
|
+
enhanced_matchdict[request.current_resource_name + "_id"] = enhanced_matchdict.get(
|
|
495
|
+
"id", ""
|
|
496
|
+
)
|
|
497
|
+
except AttributeError:
|
|
498
|
+
# Not on a resource.
|
|
499
|
+
pass
|
|
500
|
+
metrics_matchdict_labels = [
|
|
501
|
+
(field, enhanced_matchdict.get(field, "")) for field in metrics_matchdict_fields
|
|
502
|
+
]
|
|
503
|
+
|
|
504
|
+
# Count served requests.
|
|
505
|
+
metrics_service.count(
|
|
506
|
+
"request_summary",
|
|
507
|
+
unique=[
|
|
508
|
+
("method", request.method.lower()),
|
|
509
|
+
("endpoint", endpoint),
|
|
510
|
+
("status", str(request.response.status_code)),
|
|
511
|
+
]
|
|
512
|
+
+ metrics_matchdict_labels,
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
try:
|
|
516
|
+
current = utils.msec_time()
|
|
517
|
+
duration = current - request._received_at
|
|
518
|
+
metrics_service.observe(
|
|
519
|
+
"request_duration",
|
|
520
|
+
duration,
|
|
521
|
+
labels=[("endpoint", endpoint)] + metrics_matchdict_labels,
|
|
522
|
+
)
|
|
523
|
+
except AttributeError: # pragma: no cover
|
|
524
|
+
# Logging was not setup in this Kinto app (unlikely but possible)
|
|
525
|
+
pass
|
|
526
|
+
|
|
527
|
+
# Observe response size.
|
|
528
|
+
metrics_service.observe(
|
|
529
|
+
"request_size",
|
|
530
|
+
len(request.response.body or b""),
|
|
531
|
+
labels=[("endpoint", endpoint)] + metrics_matchdict_labels,
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
# Count authentication verifications.
|
|
535
|
+
if hasattr(request, "authn_type"):
|
|
536
|
+
metrics_service.count(f"authn_type.{request.authn_type}")
|
|
537
|
+
|
|
538
|
+
# Count view calls.
|
|
539
|
+
service = request.current_service
|
|
540
|
+
if service:
|
|
541
|
+
metrics_service.count(f"view.{service.name}.{request.method}")
|
|
542
|
+
|
|
543
|
+
config.add_subscriber(on_new_response, NewResponse)
|
|
544
|
+
|
|
545
|
+
# While statsd is deprecated, we include its plugin by default for retro-compability.
|
|
546
|
+
if settings["statsd_url"]:
|
|
547
|
+
config.include("kinto.plugins.statsd")
|
|
548
|
+
|
|
549
|
+
|
|
469
550
|
class EventActionFilter:
|
|
470
551
|
def __init__(self, actions, config):
|
|
471
552
|
actions = ACTIONS.from_string_list(actions)
|
|
@@ -518,11 +599,9 @@ def setup_listeners(config):
|
|
|
518
599
|
listener_mod = config.maybe_dotted(module_value)
|
|
519
600
|
listener = listener_mod.load_from_config(config, prefix)
|
|
520
601
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
key = f"listeners.{name}"
|
|
525
|
-
listener = statsd_client.timer(key)(listener.__call__)
|
|
602
|
+
wrapped_listener = metrics.listener_with_timer(
|
|
603
|
+
config, f"listeners.{name}", listener.__call__
|
|
604
|
+
)
|
|
526
605
|
|
|
527
606
|
# Optional filter by event action.
|
|
528
607
|
actions_setting = prefix + "actions"
|
|
@@ -548,11 +627,11 @@ def setup_listeners(config):
|
|
|
548
627
|
options = dict(for_actions=actions, for_resources=resource_names)
|
|
549
628
|
|
|
550
629
|
if ACTIONS.READ in actions:
|
|
551
|
-
config.add_subscriber(
|
|
630
|
+
config.add_subscriber(wrapped_listener, ResourceRead, **options)
|
|
552
631
|
actions = [a for a in actions if a != ACTIONS.READ]
|
|
553
632
|
|
|
554
633
|
if len(actions) > 0:
|
|
555
|
-
config.add_subscriber(
|
|
634
|
+
config.add_subscriber(wrapped_listener, ResourceChanged, **options)
|
|
556
635
|
|
|
557
636
|
|
|
558
637
|
def load_default_settings(config, default_settings):
|
kinto/core/metrics.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import types
|
|
2
|
+
|
|
3
|
+
from zope.interface import Interface, implementer
|
|
4
|
+
|
|
5
|
+
from kinto.core import utils
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class IMetricsService(Interface):
|
|
9
|
+
"""
|
|
10
|
+
An interface that defines the metrics service contract.
|
|
11
|
+
Any class implementing this must provide all its methods.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def timer(key):
|
|
15
|
+
"""
|
|
16
|
+
Watch execution time.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def observe(self, key, value, labels=[]):
|
|
20
|
+
"""
|
|
21
|
+
Observe a give `value` for the specified `key`.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def count(key, count=1, unique=None):
|
|
25
|
+
"""
|
|
26
|
+
Count occurrences. If `unique` is set, overwrites the counter value
|
|
27
|
+
on each call.
|
|
28
|
+
|
|
29
|
+
`unique` should be of type ``list[tuple[str,str]]``.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class NoOpTimer:
|
|
34
|
+
def __call__(self, f):
|
|
35
|
+
@utils.safe_wraps(f)
|
|
36
|
+
def _wrapped(*args, **kwargs):
|
|
37
|
+
return f(*args, **kwargs)
|
|
38
|
+
|
|
39
|
+
return _wrapped
|
|
40
|
+
|
|
41
|
+
def __enter__(self):
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
def __exit__(self, *args, **kwargs):
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@implementer(IMetricsService)
|
|
49
|
+
class NoOpMetricsService:
|
|
50
|
+
def timer(self, key):
|
|
51
|
+
return NoOpTimer()
|
|
52
|
+
|
|
53
|
+
def observe(self, key, value, labels=[]):
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
def count(self, key, count=1, unique=None):
|
|
57
|
+
pass
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def watch_execution_time(metrics_service, obj, prefix="", classname=None):
|
|
61
|
+
"""
|
|
62
|
+
Decorate all methods of an object in order to watch their execution time.
|
|
63
|
+
Metrics will be named `{prefix}.{classname}.{method}`.
|
|
64
|
+
"""
|
|
65
|
+
classname = classname or utils.classname(obj)
|
|
66
|
+
members = dir(obj)
|
|
67
|
+
for name in members:
|
|
68
|
+
value = getattr(obj, name)
|
|
69
|
+
is_method = isinstance(value, types.MethodType)
|
|
70
|
+
if not name.startswith("_") and is_method:
|
|
71
|
+
statsd_key = f"{prefix}.{classname}.{name}"
|
|
72
|
+
decorated_method = metrics_service.timer(statsd_key)(value)
|
|
73
|
+
setattr(obj, name, decorated_method)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def listener_with_timer(config, key, func):
|
|
77
|
+
"""
|
|
78
|
+
Add a timer with the specified `key` on the specified `func`.
|
|
79
|
+
This is used to avoid evaluating `config.registry.metrics` during setup time
|
|
80
|
+
to avoid having to deal with initialization order and configuration committing.
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
def wrapped(*args, **kwargs):
|
|
84
|
+
metrics_service = config.registry.metrics
|
|
85
|
+
if not metrics_service:
|
|
86
|
+
# This only happens if `kinto.core.initialization.setup_metrics` is
|
|
87
|
+
# not listed in the `initialization_sequence` setting.
|
|
88
|
+
return func(*args, **kwargs)
|
|
89
|
+
# If metrics are enabled, monitor execution time of listeners.
|
|
90
|
+
with metrics_service.timer(key):
|
|
91
|
+
return func(*args, **kwargs)
|
|
92
|
+
|
|
93
|
+
return wrapped
|
kinto/core/statsd.py
CHANGED
|
@@ -1,63 +1 @@
|
|
|
1
|
-
import
|
|
2
|
-
from urllib.parse import urlparse
|
|
3
|
-
|
|
4
|
-
from pyramid.exceptions import ConfigurationError
|
|
5
|
-
|
|
6
|
-
from kinto.core import utils
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
try:
|
|
10
|
-
import statsd as statsd_module
|
|
11
|
-
except ImportError: # pragma: no cover
|
|
12
|
-
statsd_module = None
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class Client:
|
|
16
|
-
def __init__(self, host, port, prefix):
|
|
17
|
-
self._client = statsd_module.StatsClient(host, port, prefix=prefix)
|
|
18
|
-
|
|
19
|
-
def watch_execution_time(self, obj, prefix="", classname=None):
|
|
20
|
-
classname = classname or utils.classname(obj)
|
|
21
|
-
members = dir(obj)
|
|
22
|
-
for name in members:
|
|
23
|
-
value = getattr(obj, name)
|
|
24
|
-
is_method = isinstance(value, types.MethodType)
|
|
25
|
-
if not name.startswith("_") and is_method:
|
|
26
|
-
statsd_key = f"{prefix}.{classname}.{name}"
|
|
27
|
-
decorated_method = self.timer(statsd_key)(value)
|
|
28
|
-
setattr(obj, name, decorated_method)
|
|
29
|
-
|
|
30
|
-
def timer(self, key):
|
|
31
|
-
return self._client.timer(key)
|
|
32
|
-
|
|
33
|
-
def count(self, key, count=1, unique=None):
|
|
34
|
-
if unique is None:
|
|
35
|
-
return self._client.incr(key, count=count)
|
|
36
|
-
else:
|
|
37
|
-
return self._client.set(key, unique)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def statsd_count(request, count_key):
|
|
41
|
-
statsd = request.registry.statsd
|
|
42
|
-
if statsd:
|
|
43
|
-
statsd.count(count_key)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
def load_from_config(config):
|
|
47
|
-
# If this is called, it means that a ``statsd_url`` was specified in settings.
|
|
48
|
-
# (see ``kinto.core.initialization``)
|
|
49
|
-
# Raise a proper error if the ``statsd`` module is not installed.
|
|
50
|
-
if statsd_module is None:
|
|
51
|
-
error_msg = "Please install Kinto with monitoring dependencies (e.g. statsd package)"
|
|
52
|
-
raise ConfigurationError(error_msg)
|
|
53
|
-
|
|
54
|
-
settings = config.get_settings()
|
|
55
|
-
uri = settings["statsd_url"]
|
|
56
|
-
uri = urlparse(uri)
|
|
57
|
-
|
|
58
|
-
if settings["project_name"] != "":
|
|
59
|
-
prefix = settings["project_name"]
|
|
60
|
-
else:
|
|
61
|
-
prefix = settings["statsd_prefix"]
|
|
62
|
-
|
|
63
|
-
return Client(uri.hostname, uri.port, prefix)
|
|
1
|
+
from kinto.plugins.statsd import load_from_config # noqa: F401
|
kinto/core/testing.py
CHANGED
|
@@ -8,15 +8,19 @@ import webtest
|
|
|
8
8
|
from cornice import errors as cornice_errors
|
|
9
9
|
from pyramid.url import parse_url_overrides
|
|
10
10
|
|
|
11
|
-
from kinto.core import DEFAULT_SETTINGS
|
|
11
|
+
from kinto.core import DEFAULT_SETTINGS
|
|
12
12
|
from kinto.core.storage import generators
|
|
13
13
|
from kinto.core.utils import encode64, follow_subrequest, memcache, sqlalchemy
|
|
14
|
+
from kinto.plugins import prometheus, statsd
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
skip_if_ci = unittest.skipIf("CI" in os.environ, "ci")
|
|
17
18
|
skip_if_no_postgresql = unittest.skipIf(sqlalchemy is None, "postgresql is not installed.")
|
|
18
19
|
skip_if_no_memcached = unittest.skipIf(memcache is None, "memcached is not installed.")
|
|
19
20
|
skip_if_no_statsd = unittest.skipIf(not statsd.statsd_module, "statsd is not installed.")
|
|
21
|
+
skip_if_no_prometheus = unittest.skipIf(
|
|
22
|
+
not prometheus.prometheus_module, "prometheus is not installed."
|
|
23
|
+
)
|
|
20
24
|
|
|
21
25
|
|
|
22
26
|
class DummyRequest(mock.MagicMock):
|
kinto/core/utils.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import collections.abc as collections_abc
|
|
2
|
+
import functools
|
|
2
3
|
import hashlib
|
|
3
4
|
import hmac
|
|
4
5
|
import os
|
|
@@ -541,3 +542,10 @@ def apply_json_patch(obj, ops):
|
|
|
541
542
|
raise ValueError(e)
|
|
542
543
|
|
|
543
544
|
return result
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def safe_wraps(wrapper, *args, **kwargs):
|
|
548
|
+
"""Safely wraps partial functions."""
|
|
549
|
+
while isinstance(wrapper, functools.partial):
|
|
550
|
+
wrapper = wrapper.func
|
|
551
|
+
return functools.wraps(wrapper, *args, **kwargs)
|
kinto/plugins/admin/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.4.1
|
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
1
|
+
3.4.1
|