kinto 20.0.0__py3-none-any.whl → 20.4.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.

@@ -6,7 +6,7 @@
6
6
  <title>Kinto Administration</title>
7
7
  <meta name="viewport" content="width=device-width,initial-scale=1">
8
8
  <link rel="icon" type="image/x-icon" href="../images/favicon.png">
9
- <script type="module" crossorigin src="/v1/admin/assets/index-Bq62Gei8.js"></script>
9
+ <script type="module" crossorigin src="/v1/admin/assets/index-CylsivYB.js"></script>
10
10
  <link rel="stylesheet" crossorigin href="/v1/admin/assets/index-Cs7JVwIg.css">
11
11
  </head>
12
12
 
@@ -1,3 +1,5 @@
1
+ from pyramid.settings import aslist
2
+
1
3
  from kinto.authorization import PERMISSIONS_INHERITANCE_TREE
2
4
  from kinto.core import metrics
3
5
  from kinto.core.events import ResourceChanged
@@ -6,10 +8,18 @@ from .listener import on_resource_changed
6
8
 
7
9
 
8
10
  def includeme(config):
11
+ settings = config.get_settings()
12
+ exposed_settings = {}
13
+ if (trim_history_max := int(settings.get("history.auto_trim_max_count", "-1"))) > 0:
14
+ exposed_settings["auto_trim_max_count"] = trim_history_max
15
+ if trim_user_ids := aslist(settings.get("history.auto_trim_user_ids", "")):
16
+ exposed_settings["auto_trim_user_ids"] = trim_user_ids
17
+
9
18
  config.add_api_capability(
10
19
  "history",
11
20
  description="Track changes on data.",
12
21
  url="http://kinto.readthedocs.io/en/latest/api/1.x/history.html",
22
+ **exposed_settings,
13
23
  )
14
24
 
15
25
  # Activate end-points.
@@ -1,8 +1,13 @@
1
+ import logging
1
2
  from datetime import datetime, timezone
2
3
 
3
4
  from pyramid.settings import asbool, aslist
4
5
 
5
- from kinto.core.utils import instance_uri
6
+ from kinto.core.storage import Filter, Sort
7
+ from kinto.core.utils import COMPARISON, instance_uri
8
+
9
+
10
+ logger = logging.getLogger(__name__)
6
11
 
7
12
 
8
13
  def on_resource_changed(event):
@@ -14,15 +19,26 @@ def on_resource_changed(event):
14
19
  payload = event.payload
15
20
  resource_name = payload["resource_name"]
16
21
  event_uri = payload["uri"]
17
-
18
- bucket_id = None
19
- bucket_uri = None
20
- collection_uri = None
22
+ user_id = payload["user_id"]
21
23
 
22
24
  storage = event.request.registry.storage
23
25
  permission = event.request.registry.permission
24
26
  settings = event.request.registry.settings
25
27
 
28
+ excluded_user_ids = aslist(settings.get("history.exclude_user_ids", ""))
29
+ if user_id in excluded_user_ids:
30
+ logger.info(f"History entries for user {user_id!r} are disabled in config")
31
+ return
32
+
33
+ trim_history_max = int(settings.get("history.auto_trim_max_count", "-1"))
34
+ is_trim_enabled = trim_history_max > 0
35
+ trim_user_ids = aslist(settings.get("history.auto_trim_user_ids", ""))
36
+ is_trim_by_user_enabled = len(trim_user_ids) > 0
37
+
38
+ bucket_id = None
39
+ bucket_uri = None
40
+ collection_uri = None
41
+
26
42
  excluded_resources = aslist(settings.get("history.exclude_resources", ""))
27
43
 
28
44
  targets = []
@@ -38,6 +54,7 @@ def on_resource_changed(event):
38
54
  bucket_uri = instance_uri(event.request, "bucket", id=bucket_id)
39
55
 
40
56
  if bucket_uri in excluded_resources:
57
+ logger.info(f"History entries for bucket {bucket_uri!r} are disabled in config")
41
58
  continue
42
59
 
43
60
  if "collection_id" in payload:
@@ -46,6 +63,9 @@ def on_resource_changed(event):
46
63
  event.request, "collection", bucket_id=bucket_id, id=collection_id
47
64
  )
48
65
  if collection_uri in excluded_resources:
66
+ logger.info(
67
+ f"History entries for collection {collection_uri!r} are disabled in config"
68
+ )
49
69
  continue
50
70
 
51
71
  # On POST .../records, the URI does not contain the newly created
@@ -59,6 +79,7 @@ def on_resource_changed(event):
59
79
  uri = "/".join(parts)
60
80
 
61
81
  if uri in excluded_resources:
82
+ logger.info(f"History entries for record {uri!r} are disabled in config")
62
83
  continue
63
84
 
64
85
  targets.append((uri, target))
@@ -109,6 +130,48 @@ def on_resource_changed(event):
109
130
  # Note: this will be rolledback if the transaction is rolledback.
110
131
  entry = storage.create(parent_id=bucket_uri, resource_name="history", obj=attrs)
111
132
 
133
+ # If enabled, we trim history by resource.
134
+ # This means that we will only keep the last `auto_trim_max_count` history entries
135
+ # for this same type of object (eg. `collection`, `record`).
136
+ #
137
+ # If trim by user is enabled, we only trim if the user matches the config
138
+ # and we only delete the history entries of this user.
139
+ # This means that if a user touches X different types of objects, we will keep
140
+ # ``(X * auto_trim_max_count)`` entries.
141
+ if is_trim_enabled and (not is_trim_by_user_enabled or user_id in trim_user_ids):
142
+ filters = [
143
+ Filter("resource_name", resource_name, COMPARISON.EQ),
144
+ ]
145
+ if is_trim_by_user_enabled:
146
+ filters.append(Filter("user_id", user_id, COMPARISON.EQ))
147
+
148
+ # Identify the oldest entry to keep.
149
+ previous_entries = storage.list_all(
150
+ parent_id=bucket_uri,
151
+ resource_name="history",
152
+ filters=filters,
153
+ sorting=[Sort("last_modified", -1)],
154
+ limit=trim_history_max + 1,
155
+ )
156
+ # And delete all older ones.
157
+ if len(previous_entries) > trim_history_max:
158
+ trim_before_timestamp = previous_entries[-1]["last_modified"]
159
+ deleted = storage.delete_all(
160
+ parent_id=bucket_uri,
161
+ resource_name="history",
162
+ filters=filters
163
+ + [Filter("last_modified", trim_before_timestamp, COMPARISON.MAX)],
164
+ )
165
+ logger.info(f"Trimmed {len(deleted)} old history entries.")
166
+ else:
167
+ logger.info(
168
+ "No old history to trim for {user_id!r} on {resource_name!r} in {bucket_uri!r}."
169
+ )
170
+ else:
171
+ logger.info(
172
+ f"Trimming of old history entries is not enabled{f' for {user_id!r}.' if is_trim_enabled else '.'}"
173
+ )
174
+
112
175
  # Without explicit permissions, the ACLs on the history entries will
113
176
  # fully depend on the inherited permission tree (eg. bucket:read, bucket:write).
114
177
  # This basically means that if user loose the permissions on the related
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: kinto
3
- Version: 20.0.0
3
+ Version: 20.4.0
4
4
  Summary: Kinto Web Service - Store, Sync, Share, and Self-Host.
5
5
  Author-email: Mozilla Services <developers@kinto-storage.org>
6
6
  License: Copyright 2012 - Mozilla Foundation
@@ -70,6 +70,7 @@ Provides-Extra: dev
70
70
  Requires-Dist: build; extra == "dev"
71
71
  Requires-Dist: ruff; extra == "dev"
72
72
  Requires-Dist: twine; extra == "dev"
73
+ Dynamic: license-file
73
74
 
74
75
  Kinto
75
76
  =====
@@ -5,14 +5,14 @@ kinto/contribute.json,sha256=HT9QVB8rA8jWIREQoqWfMibfJXMAzbRsixW8F6O6cQY,792
5
5
  kinto/events.py,sha256=NMPvKUdbi25aYHhu9svzQsrEZMa9nyO4mtuMZC5871Q,85
6
6
  kinto/schema_validation.py,sha256=mtAmnl5HwiUsjS2gU8MKH4lkZ1380A5wZht-w9s5X7M,5306
7
7
  kinto/config/__init__.py,sha256=av8W0utmjheueFqrjTYEDk_vbpm3XYdHcqv5lppNR4k,2131
8
- kinto/config/kinto.tpl,sha256=-JOQe32J7JYpq7XxSefhaGsbnaArxliJJ4RCTI6VEsE,8117
9
- kinto/core/__init__.py,sha256=1torTd1T89nHUdsqmCJ3o85lJVakcsERIglEMu5VmkE,8171
8
+ kinto/config/kinto.tpl,sha256=Pm9p_oRsBlVoEXPVA2X6Wvv49jMOVv-27jw4rahVlwE,8201
9
+ kinto/core/__init__.py,sha256=s7PshdeKmpiqI5sW-zdC7p3Ig-e60w4kiUwlKi-impY,8655
10
10
  kinto/core/authentication.py,sha256=HLA0kREC3GMEsrIsHsQYjVNztYfAF01kb8-pLboByFs,1527
11
11
  kinto/core/authorization.py,sha256=GywY25KEzuSSAI709dFHDfdLnKxy3SLEYGwW5FkQ7Qc,13212
12
12
  kinto/core/decorators.py,sha256=3SAPWXlyPNUSICZ9mz04bcN-UdbnDuFOtU0bQHHzLis,2178
13
13
  kinto/core/errors.py,sha256=JXZjkPYjjC0I6x02d2VJRGeaQ2yZYS2zm5o7_ljfyes,8946
14
14
  kinto/core/events.py,sha256=SYpXgKMtVjiD9fwYJA2Omdom9yA3nBqi9btdvU1I_nc,10345
15
- kinto/core/initialization.py,sha256=pG-erCSlQr0fdINsim3c8G0X_T8wa7AQCHDIfObMM6E,26121
15
+ kinto/core/initialization.py,sha256=ZjmCKc8MYZXv63W2mv--fY8rXSLAnJa7RtCYdfK4jsg,26225
16
16
  kinto/core/metrics.py,sha256=Y6Mt4PUzy2-oudeGr_oCmtX8nIR4SZkzUlPxr58jr-g,2619
17
17
  kinto/core/openapi.py,sha256=92sZviff4NCxN0jMnu5lPUnF5iQbrKMGy7Cegf-VAME,3876
18
18
  kinto/core/schema.py,sha256=d5L5TQynRYJPkZ8Mu2X7F72xEh6SKDbrHK1CNTdOf2E,3646
@@ -58,17 +58,17 @@ kinto/core/resource/__init__.py,sha256=B-1X3nPzIZOiN8Dc_tSersAP-Wn4jXf3GcrctN2Ck
58
58
  kinto/core/resource/model.py,sha256=xjZ6shnhelXCdWvgw6yeOWXodxiKMm9iyDqLTk0i8Bs,15626
59
59
  kinto/core/resource/schema.py,sha256=EhPKDMlBjx60hXztMvywfo4IrRfwaZ3V-7sY_sl-BYk,16126
60
60
  kinto/core/resource/viewset.py,sha256=Wo7mQwmI08IGnSetaqGF66fCqYPB1pDUdZa3U92NIic,7613
61
- kinto/core/storage/__init__.py,sha256=EiGmwovOzB3j_d3pgQCgsVyoe0_Op9Ylgo2MfZW7Ugo,13258
61
+ kinto/core/storage/__init__.py,sha256=Bo9q5PCDQ7KkBuBAHt7UoZFr5WfVWwEbFJjGsuq4Oo4,13704
62
62
  kinto/core/storage/exceptions.py,sha256=o10f7LohwyCHOTlR-dOdnB4us_MdCGOJUxZO8HZ3Akc,1304
63
63
  kinto/core/storage/generators.py,sha256=rxWN9hOfOsB-PLeryhPGO-aE670sivr3LFRIExImpxc,1829
64
- kinto/core/storage/memory.py,sha256=Mf_cre6QP_nZnKNc5DgwFSJAkx_ycTd8wvFFszqANKs,19222
65
- kinto/core/storage/testing.py,sha256=i7FGzELcmayYu-VFNC_9nBd8QbOGhg53qouFdyhX9yY,76527
64
+ kinto/core/storage/memory.py,sha256=DR6gpqSYh4oGPBX9x4-dGLNITZi33T0VODsNWsPtgho,19875
65
+ kinto/core/storage/testing.py,sha256=4hGR6xc54M6H81IQb8XP7NP_iBP-X8m8PqKqixDs5nU,78411
66
66
  kinto/core/storage/utils.py,sha256=BHpohIKVOCtURjRbUT7O5AEhIKfSEFv-pfgRzq8Q5zs,1082
67
- kinto/core/storage/postgresql/__init__.py,sha256=gRtC0W3FVtXQNEF_K2qzzKUk-kbD18rnj6vya6tRg8A,39107
67
+ kinto/core/storage/postgresql/__init__.py,sha256=HMlUfTBsnsUQkLEhuhItU-9fgSOf9wTH6J09-1HW1fA,40157
68
68
  kinto/core/storage/postgresql/client.py,sha256=JTRxHK-8ipTXgs0E4PyiFiun-32yuzqFGsWTi-OuFfA,4270
69
69
  kinto/core/storage/postgresql/migrator.py,sha256=MQ_5aSrDi9-s2wlyyFyfhYP6HreCXjtlJzBI4b85u1I,3524
70
70
  kinto/core/storage/postgresql/pool.py,sha256=lOtclVagFqzzWbVxrGoWeKylpHlKdFgGz3Ef6cgGNJU,2219
71
- kinto/core/storage/postgresql/schema.sql,sha256=4q0NpjaX2GoiuGNnIVpMVuox9vQHGhh64qE4Z4a2FdQ,4095
71
+ kinto/core/storage/postgresql/schema.sql,sha256=qRmx8NSwqedwozOi-Nn-pnipubxUWLCcYTRDMkt2B80,4216
72
72
  kinto/core/storage/postgresql/migrations/migration_001_002.sql,sha256=GVJIs8MGmZEyp1i0KjsmGKv1pLlBRckn0EWX6Kl6uQE,428
73
73
  kinto/core/storage/postgresql/migrations/migration_002_003.sql,sha256=zlSZpG_2L-wd8KXh3szmbYtoGjAOOwy2gH7RFMUd61w,1203
74
74
  kinto/core/storage/postgresql/migrations/migration_003_004.sql,sha256=OZSbH6Yt1PA2zba8iQWIguaTsnn3v7bFF-rbg2_teXY,530
@@ -90,6 +90,7 @@ kinto/core/storage/postgresql/migrations/migration_018_019.sql,sha256=XK6ex2jwQQ
90
90
  kinto/core/storage/postgresql/migrations/migration_019_020.sql,sha256=yDQDdzU65cgctKIeo1YqMrFi7aU2VGdBhVUpVQAQOgM,306
91
91
  kinto/core/storage/postgresql/migrations/migration_020_021.sql,sha256=cEgfGNDLH3RyLswxy9YZazZtOvidxNsi57z7SGR8VsQ,2369
92
92
  kinto/core/storage/postgresql/migrations/migration_021_022.sql,sha256=MUIAfgbBDQLy8JfklEs6ekK93nr1buVRtmBAHaaMXug,1993
93
+ kinto/core/storage/postgresql/migrations/migration_022_023.sql,sha256=c1Fw10sxtaDqYJpxk1P9eZCtXP3uSmBY7-3IjZZLwjU,231
93
94
  kinto/core/views/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
94
95
  kinto/core/views/batch.py,sha256=sWNn67YqfTbiqwpbwbm1QyumcVGi8aNhlT5AtLToElI,5657
95
96
  kinto/core/views/errors.py,sha256=2uTy85UBQL1EDIMotzFBhVl14RmWwb3rupbsB0mycbs,6099
@@ -107,16 +108,16 @@ kinto/plugins/accounts/scripts.py,sha256=_LNkSpFprOMGHFKkRmmOqK31uoQW28yttPQztmf
107
108
  kinto/plugins/accounts/utils.py,sha256=xGtWwBPKVEwkbJbtJ_SUFgTJpwQzreoJG9XY86HPj7M,358
108
109
  kinto/plugins/accounts/views.py,sha256=NO2nZCS1v0dN390Hxhe55Bsi4IM-NH9jEStkiKH2AEU,5221
109
110
  kinto/plugins/admin/README.md,sha256=3a9inoO2IHH5aST3xZp_km3ddupHZqadWrQjOQifBRk,105
110
- kinto/plugins/admin/VERSION,sha256=E4ID4_YEoI-tDE0KKX7-LEhiGYINzdBT5yt9hDUEx6E,6
111
+ kinto/plugins/admin/VERSION,sha256=Tpnn6sIcK3GpcTnKzKaV2YyT-9fdJlBfBPUnGAmsS5Q,6
111
112
  kinto/plugins/admin/__init__.py,sha256=057R3q_S4CFiUuqwMIFkyu4prMBnUVc1eo8OUAPeJBs,1327
112
113
  kinto/plugins/admin/views.py,sha256=NHQncNB-32ytP03o-ZqisbYEoK7L2QcaK7RBuh0akwg,1338
113
- kinto/plugins/admin/build/VERSION,sha256=E4ID4_YEoI-tDE0KKX7-LEhiGYINzdBT5yt9hDUEx6E,6
114
- kinto/plugins/admin/build/index.html,sha256=QDP7gFvTpKSWTrAlGW6yqI9F3tt1DiSo-uO_feswUd8,467
114
+ kinto/plugins/admin/build/VERSION,sha256=Tpnn6sIcK3GpcTnKzKaV2YyT-9fdJlBfBPUnGAmsS5Q,6
115
+ kinto/plugins/admin/build/index.html,sha256=cfQ_QjlmMyviojnRWQFxC6J5nWbyo8WIFvWtmcyzbeo,467
115
116
  kinto/plugins/admin/build/assets/asn1-EdZsLKOL.js,sha256=rgO6M_APbSRME0yY3rddue8niU7UJmG2uWz4l1YeG6s,3981
116
117
  kinto/plugins/admin/build/assets/clojure-BMjYHr_A.js,sha256=SYpgEG0C5dkSQgMtAejALIQFzA3S0InWn1XquOeNGiA,10815
117
118
  kinto/plugins/admin/build/assets/css-BnMrqG3P.js,sha256=qj5HvX1-byua7Xs0K31qoFn_bncMhtZMsOr5e0A-yYo,27132
118
- kinto/plugins/admin/build/assets/index-Bq62Gei8.js,sha256=-HJFZMd4mb4gaoe1KT8lzbJujSNZUukTmIDlZk1VAw4,2755558
119
119
  kinto/plugins/admin/build/assets/index-Cs7JVwIg.css,sha256=dtEppIrcuaKwSBl-bfgIKrBnbrr7qFL7PST9yLyPfc0,171774
120
+ kinto/plugins/admin/build/assets/index-CylsivYB.js,sha256=4GW6MPa0FgDsBjUF2bjYB-8jjU5hjUXykaQMxjChFsM,2754443
120
121
  kinto/plugins/admin/build/assets/javascript-qCveANmP.js,sha256=ol336oG7yGQxC9YIvojoE-dqx7wd3lLp0Ikn2rV4F5g,17080
121
122
  kinto/plugins/admin/build/assets/logo-VBRiKSPX.png,sha256=4LXIgVZbcdbMun8bTlCFgnBik7uIkIRILVPFYrTUGgg,5578
122
123
  kinto/plugins/admin/build/assets/mllike-CXdrOF99.js,sha256=C_YMaIP-ED2BN1nxtpuqRq-LXcC1AqrkksJHg4pdh4Q,4788
@@ -126,8 +127,8 @@ kinto/plugins/admin/build/assets/sql-D0XecflT.js,sha256=3hecsZJ0WlDxeFA_1lrl_ySI
126
127
  kinto/plugins/admin/build/assets/ttcn-cfg-B9xdYoR4.js,sha256=jTZ_XHKOChJZEt-FzJbGHhvSYW42F1NccHg0lOY2FlM,4050
127
128
  kinto/plugins/admin/public/help.html,sha256=1hol7z5Sv0Xn3mYyEfPQWFOsrR24htlKhmnGA3rH8fs,958
128
129
  kinto/plugins/default_bucket/__init__.py,sha256=7TmBzFgW0gmYsetXr6Kqt9317XZ3PiB9gyCr-IBfmGg,7276
129
- kinto/plugins/history/__init__.py,sha256=s-RMNWaZlmBNd4sNsgJ-hDvMW4pPKUZ-sdZYubb0Kdo,1015
130
- kinto/plugins/history/listener.py,sha256=Tq5ZHpIOIzQs9yPXA1exEftPoYCuFQJvgxbaIb6XBrM,5033
130
+ kinto/plugins/history/__init__.py,sha256=ZuGoj9giDEefv2BNuaakgGEV87e85wvrkNgsmVAotIU,1443
131
+ kinto/plugins/history/listener.py,sha256=ABPRduTnkwH6oMzRyrQlF3AZoVt7brqwKVpA8ynY0Sw,7962
131
132
  kinto/plugins/history/views.py,sha256=NoBP-S7epeH5TLZZbIqfBmwMA2KaWmxP7lqPAS11BTU,2293
132
133
  kinto/plugins/openid/__init__.py,sha256=1Iv5SCa6vwEvoJkmGd45-TYm_mxz2okFj6u2VTNuVrk,4863
133
134
  kinto/plugins/openid/utils.py,sha256=n3KGS-ogXR2sg6j4QtdPe_DtEsqD7AGVT2K7vhl8yE8,273
@@ -140,9 +141,9 @@ kinto/views/contribute.py,sha256=PJoIMLj9_IszSjgZkaCd_TUjekDgNqjpmVTmRN9ztaA,983
140
141
  kinto/views/groups.py,sha256=jOq5fX0-4lwZE8k1q5HME2tU7x9052rtBPF7YqcJ-Qg,3181
141
142
  kinto/views/permissions.py,sha256=F0_eKx201WyLonXJ5vLdGKa9RcFKjvAihrEEhU1JuLw,9069
142
143
  kinto/views/records.py,sha256=lYfACW2L8qcQoyYBD5IX-fTPjFWmGp7GjHq_U4InlyE,5037
143
- kinto-20.0.0.dist-info/LICENSE,sha256=oNEIMTuTJzppR5ZEyi86yvvtSagveMYXTYFn56zF0Uk,561
144
- kinto-20.0.0.dist-info/METADATA,sha256=pKI1dh73e-geI9yxRyFWz3caWaVKYB17kcfMidCuQn8,8709
145
- kinto-20.0.0.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
146
- kinto-20.0.0.dist-info/entry_points.txt,sha256=3KlqBWPKY81mrCe_oX0I5s1cRO7Q53nCLbnVr5P9LH4,85
147
- kinto-20.0.0.dist-info/top_level.txt,sha256=EG_YmbZL6FAug9VwopG7JtF9SvH_r0DEnFp-3twPPys,6
148
- kinto-20.0.0.dist-info/RECORD,,
144
+ kinto-20.4.0.dist-info/licenses/LICENSE,sha256=oNEIMTuTJzppR5ZEyi86yvvtSagveMYXTYFn56zF0Uk,561
145
+ kinto-20.4.0.dist-info/METADATA,sha256=FJkC9Vuz90F7WOrCHXR68mhO8z59xIu5bkLBCe1Os2k,8731
146
+ kinto-20.4.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
147
+ kinto-20.4.0.dist-info/entry_points.txt,sha256=3KlqBWPKY81mrCe_oX0I5s1cRO7Q53nCLbnVr5P9LH4,85
148
+ kinto-20.4.0.dist-info/top_level.txt,sha256=EG_YmbZL6FAug9VwopG7JtF9SvH_r0DEnFp-3twPPys,6
149
+ kinto-20.4.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.8.2)
2
+ Generator: setuptools (78.1.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5