jaseci 1.4.0.9__py3-none-any.whl → 1.4.0.11__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 jaseci might be problematic. Click here for more details.

Files changed (87) hide show
  1. jaseci/VERSION +1 -1
  2. jaseci/__init__.py +3 -0
  3. jaseci/actions/standard/elastic.py +3 -2
  4. jaseci/actions/standard/mail.py +3 -2
  5. jaseci/actions/standard/std.py +3 -2
  6. jaseci/actions/standard/stripe.py +3 -2
  7. jaseci/actions/standard/task.py +3 -5
  8. jaseci/actions/standard/tests/test_mail_lib.py +8 -7
  9. jaseci/actions/tests/test_std.py +4 -5
  10. jaseci/actor/walker.py +6 -3
  11. jaseci/api/config_api.py +3 -2
  12. jaseci/api/jac_api.py +2 -2
  13. jaseci/api/jsorc_api.py +60 -121
  14. jaseci/api/prometheus_api.py +14 -20
  15. jaseci/api/queue_api.py +9 -5
  16. jaseci/api/tests/test_global_api.py +3 -3
  17. jaseci/api/tests/test_logger_api.py +3 -3
  18. jaseci/api/user_api.py +3 -3
  19. jaseci/api/webhook_api.py +6 -4
  20. jaseci/attr/action.py +10 -4
  21. jaseci/element/master.py +2 -0
  22. jaseci/element/super_master.py +2 -0
  23. jaseci/hook/memory.py +3 -1
  24. jaseci/hook/redis.py +5 -4
  25. jaseci/jac/interpreter/interp.py +16 -4
  26. jaseci/jac/tests/test_book.py +2 -2
  27. jaseci/jsctl/jsctl.py +48 -15
  28. jaseci/jsctl/tests/test_jsctl.py +5 -0
  29. jaseci/jsorc.py +733 -0
  30. jaseci/jsorc_settings.py +184 -0
  31. jaseci/manifests/database.yaml +107 -0
  32. jaseci/manifests/elastic.yaml +5923 -0
  33. jaseci/manifests/prometheus.yaml +1273 -0
  34. jaseci/{svc/jsorc-backup/jaseci-redis.yaml → manifests/redis.yaml} +20 -0
  35. jaseci/svc/__init__.py +0 -25
  36. jaseci/svc/{elastic/elastic.py → elastic_svc.py} +5 -16
  37. jaseci/svc/kube_svc.py +240 -0
  38. jaseci/svc/{mail/mail.py → mail_svc.py} +14 -17
  39. jaseci/svc/{prometheus/prometheus.py → prome_svc.py} +5 -16
  40. jaseci/svc/{redis/redis.py → redis_svc.py} +14 -26
  41. jaseci/svc/{stripe/stripe.py → stripe_svc.py} +4 -7
  42. jaseci/svc/{task/task.py → task_svc.py} +27 -24
  43. jaseci/svc/{task/common.py → tasks.py} +287 -293
  44. jaseci/tests/jac_test_progs.py +21 -0
  45. jaseci/tests/test_core.py +14 -15
  46. jaseci/tests/test_jac.py +59 -60
  47. jaseci/tests/test_node.py +6 -13
  48. jaseci/tests/test_progs.py +74 -52
  49. jaseci/tests/test_stripe.py +6 -10
  50. jaseci/utils/actions/actions_manager.py +254 -0
  51. jaseci/{svc/actions_optimizer → utils/actions}/actions_optimizer.py +9 -19
  52. jaseci/utils/json_handler.py +2 -3
  53. jaseci/utils/test_core.py +4 -5
  54. jaseci/utils/utils.py +12 -0
  55. {jaseci-1.4.0.9.dist-info → jaseci-1.4.0.11.dist-info}/METADATA +2 -1
  56. {jaseci-1.4.0.9.dist-info → jaseci-1.4.0.11.dist-info}/RECORD +63 -80
  57. jaseci/svc/common.py +0 -763
  58. jaseci/svc/config.py +0 -9
  59. jaseci/svc/elastic/__init__.py +0 -3
  60. jaseci/svc/elastic/config.py +0 -8
  61. jaseci/svc/elastic/manifest.py +0 -1
  62. jaseci/svc/jsorc-backup/jsorc.py +0 -182
  63. jaseci/svc/jsorc-backup/promon/__init__.py +0 -0
  64. jaseci/svc/jsorc-backup/promon/promon.py +0 -202
  65. jaseci/svc/mail/__init__.py +0 -4
  66. jaseci/svc/mail/config.py +0 -25
  67. jaseci/svc/meta.py +0 -164
  68. jaseci/svc/postgres/__init__.py +0 -0
  69. jaseci/svc/postgres/manifest.py +0 -106
  70. jaseci/svc/prometheus/__init__.py +0 -5
  71. jaseci/svc/prometheus/config.py +0 -11
  72. jaseci/svc/prometheus/manifest.py +0 -1102
  73. jaseci/svc/redis/__init__.py +0 -5
  74. jaseci/svc/redis/config.py +0 -10
  75. jaseci/svc/redis/manifest.py +0 -65
  76. jaseci/svc/state.py +0 -17
  77. jaseci/svc/stripe/__init__.py +0 -3
  78. jaseci/svc/stripe/config.py +0 -7
  79. jaseci/svc/task/__init__.py +0 -5
  80. jaseci/svc/task/config.py +0 -17
  81. /jaseci/{svc/actions_optimizer → manifests}/__init__.py +0 -0
  82. /jaseci/{svc/jsorc-backup → utils/actions}/__init__.py +0 -0
  83. /jaseci/{svc/actions_optimizer → utils/actions}/actions_state.py +0 -0
  84. {jaseci-1.4.0.9.dist-info → jaseci-1.4.0.11.dist-info}/LICENSE +0 -0
  85. {jaseci-1.4.0.9.dist-info → jaseci-1.4.0.11.dist-info}/WHEEL +0 -0
  86. {jaseci-1.4.0.9.dist-info → jaseci-1.4.0.11.dist-info}/entry_points.txt +0 -0
  87. {jaseci-1.4.0.9.dist-info → jaseci-1.4.0.11.dist-info}/top_level.txt +0 -0
@@ -1,293 +1,287 @@
1
- import re
2
- from copy import deepcopy
3
- from typing import Tuple
4
- from uuid import UUID
5
-
6
- from celery import Task
7
- from jaseci.svc.task.config import DEFAULT_MSG
8
- from requests import get, post
9
- from requests.exceptions import HTTPError
10
-
11
-
12
- class Queue(Task):
13
- def run(self, wlk, nd, args):
14
- from jaseci.svc import MetaService
15
-
16
- hook = MetaService().build_hook()
17
-
18
- wlk = hook.get_obj_from_store(wlk)
19
- wlk._to_await = True
20
-
21
- nd = hook.get_obj_from_store(nd)
22
- resp = wlk.run(nd, *args)
23
- wlk.destroy()
24
-
25
- return {"anchor": wlk.anchor_value(), "response": resp}
26
-
27
-
28
- class ScheduledWalker(Task):
29
- def get_obj(self, jid):
30
- return self.hook.get_obj_from_store(jid)
31
-
32
- def run(self, name, ctx, nd=None, snt=None, mst=None):
33
- from jaseci.svc import MetaService
34
-
35
- self.hook = MetaService().build_hook()
36
-
37
- if mst:
38
- mst = self.get_obj(mst)
39
- else:
40
- return f"{DEFAULT_MSG} mst (Master) is required!"
41
-
42
- if mst is None:
43
- return f"{DEFAULT_MSG} Invalid Master!"
44
-
45
- try:
46
- if not snt:
47
- if mst.active_snt_id == "global":
48
- global_snt_id = self.hook.get_glob("GLOB_SENTINEL")
49
- snt = self.get_obj(global_snt_id)
50
- elif mst.active_snt_id:
51
- snt = self.get_obj(mst.active_snt_id)
52
- elif snt in mst.alias_map:
53
- snt = self.get_obj(mst.alias_map[snt])
54
- else:
55
- snt = self.get_obj(snt)
56
-
57
- if not snt:
58
- return f"{DEFAULT_MSG} Invalid Sentinel!"
59
-
60
- if not nd:
61
- if mst.active_gph_id:
62
- nd = self.get_obj(mst.active_gph_id)
63
- elif nd in mst.alias_map:
64
- nd = self.get_obj(mst.alias_map[nd])
65
- else:
66
- nd = self.get_obj(nd)
67
-
68
- if not nd:
69
- return f"{DEFAULT_MSG} Invalid Node!"
70
-
71
- return mst.walker_run(name, nd, ctx, ctx, snt, False, False)
72
- except Exception as e:
73
- return f"{DEFAULT_MSG} Error occured: {e}"
74
-
75
-
76
- class ScheduledSequence(Task):
77
- json_escape = re.compile(r"[^a-zA-Z0-9_]")
78
- internal = re.compile(r"\(([a-zA-Z0-9_\.\[\]\$\#\@\!]*?)\)")
79
- full = re.compile(r"^\{\{([a-zA-Z0-9_\.\[\]\$\#\(\)\@\!]*?)\}\}$")
80
- partial = re.compile(r"\{\{([a-zA-Z0-9_\.\[\]\$\#\(\)\@\!]*?)\}\}")
81
-
82
- def get_deep_value(self, data, keys, default):
83
- if len(keys) == 0:
84
- return data
85
-
86
- key = keys.pop(0)
87
- t = type(data)
88
-
89
- if t is dict and key in data:
90
- return self.get_deep_value(data[key], keys, default)
91
- elif t is list and key.isnumeric():
92
- return self.get_deep_value(data[int(key)], keys, default)
93
- else:
94
- return default
95
-
96
- def get_value(self, holder: Tuple, keys: str, default=None):
97
- while self.internal.search(keys):
98
- for intern in self.internal.findall(keys):
99
- keys = keys.replace(
100
- "(" + intern + ")", self.get_value(holder, intern, "")
101
- )
102
-
103
- if keys:
104
- keys = keys.split(".")
105
- key = keys.pop(0)
106
- if key == "#":
107
- return self.get_deep_value(holder[0], keys, default)
108
- elif key == "$":
109
- t = type(holder[1])
110
- if t is dict or t is list:
111
- return self.get_deep_value(holder[1], keys, default)
112
- else:
113
- return holder[1]
114
- elif key == "@":
115
- t = type(holder[2])
116
- if t is dict or t is list:
117
- return self.get_deep_value(holder[2], keys, default)
118
- else:
119
- return holder[2]
120
- elif key == "!":
121
- return holder[3]
122
- return default
123
-
124
- def compare(self, condition, expected, actual):
125
- if condition == "eq":
126
- return actual == expected
127
- elif condition == "ne":
128
- return actual != expected
129
- elif condition == "gt":
130
- return actual > expected
131
- elif condition == "gte":
132
- return actual >= expected
133
- elif condition == "lt":
134
- return actual < expected
135
- elif condition == "lte":
136
- return actual <= expected
137
- elif condition == "regex":
138
- return re.compile(expected).match(actual)
139
-
140
- def condition(self, holder: Tuple, filter):
141
- for cons in filter["condition"].keys():
142
- if not (filter["condition"][cons] is None) and not self.compare(
143
- cons, filter["condition"][cons], self.get_value(holder, filter["by"])
144
- ):
145
- return False
146
- return True
147
-
148
- def or_condition(self, holder: Tuple, filter):
149
- for filt in filter:
150
- if "condition" in filt and self.condition(holder, filt):
151
- return True
152
- elif "or" in filt and self.or_condition(holder, filt["or"]):
153
- return True
154
- elif "and" in filt and self.and_condition(holder, filt["and"]):
155
- return True
156
- return False
157
-
158
- def and_condition(self, holder: Tuple, filter):
159
- for filt in filter:
160
- if "condition" in filt and not self.condition(holder, filt):
161
- return False
162
- elif "or" in filt and not self.or_condition(holder, filt["or"]):
163
- return False
164
- elif "and" in filt and not self.and_condition(holder, filt["and"]):
165
- return False
166
- return True
167
-
168
- def deep_replace_str(self, holder: Tuple, data, key):
169
- matcher = self.full.match(data[key])
170
- if matcher:
171
- data[key] = self.get_value(holder, matcher.group(1))
172
- else:
173
- for rep in self.partial.findall(data[key]):
174
- data[key] = data[key].replace(
175
- "{{" + rep + "}}", self.get_value(holder, rep, "")
176
- )
177
-
178
- def deep_replace_dict(self, holder: Tuple, data):
179
- for key in data.keys():
180
- if key != "__def_loop__":
181
- t = type(data[key])
182
- if t is str:
183
- self.deep_replace_str(holder, data, key)
184
- elif t is dict:
185
- self.deep_replace_dict(holder, data[key])
186
-
187
- def save(self, holder: Tuple, req, params):
188
- if params in req:
189
- holder[0][self.json_escape.sub("_", req[params])] = holder[1]
190
-
191
- def trigger_interface(self, req: dict):
192
- from jaseci.svc import MetaService
193
-
194
- master = req.get("master")
195
- app = MetaService()
196
- if master is None:
197
- caller = app.build_master()
198
- trigger_type = "public"
199
- else:
200
- caller = app.build_hook().get_obj_from_store(master)
201
- trigger_type = "general"
202
-
203
- api = req.get("api")
204
- body = req.get("body", {})
205
-
206
- return getattr(caller, f"{trigger_type}_interface_to_api")(body, api)
207
-
208
- def run(self, *args, **kwargs):
209
- requests = kwargs.get("requests")
210
- persistence = kwargs.get("persistence", {})
211
- container = kwargs.get("container", {"current": persistence})
212
- index = container.get("index", "0")
213
-
214
- if "parent_current" in container:
215
- container["current"] = container["parent_current"]
216
-
217
- for req in requests:
218
- try:
219
- self.deep_replace_dict(
220
- (
221
- persistence,
222
- container["current"],
223
- container.get("parent_current", {}),
224
- index,
225
- ),
226
- req,
227
- )
228
- self.save((persistence, req), req, "save_req_to")
229
-
230
- method = req["method"].upper()
231
-
232
- if method == "JAC":
233
- container["current"] = self.trigger_interface(req)
234
- else:
235
- if method == "POST":
236
- response = post(
237
- req["api"],
238
- json=req.get("body", {}),
239
- headers=req.get("header", {}),
240
- )
241
- elif method == "GET":
242
- response = get(req["api"], headers=req.get("header", {}))
243
- response.raise_for_status()
244
- if "application/json" in response.headers.get("Content-Type"):
245
- container["current"] = response.json()
246
- else:
247
- container["current"] = response.text
248
-
249
- if "__def_loop__" in req:
250
- def_loop = req["__def_loop__"]
251
- for idx, loop in enumerate(
252
- self.get_value(
253
- (persistence, container["current"]), def_loop["by"], []
254
- )
255
- ):
256
- if "filter" in def_loop and not self.and_condition(
257
- (persistence, loop), def_loop["filter"]
258
- ):
259
- continue
260
- loop_container = {"parent_current": loop, "index": str(idx)}
261
- self.run(
262
- requests=deepcopy(def_loop["requests"]),
263
- persistence=persistence,
264
- container=loop_container,
265
- )
266
-
267
- self.save((persistence, container["current"]), req, "save_to")
268
-
269
- except Exception as err:
270
- container["current"] = (
271
- {
272
- "status": err.response.status_code,
273
- "message": err.response.reason,
274
- }
275
- if isinstance(err, HTTPError)
276
- else {
277
- "worker_error": str(err),
278
- }
279
- )
280
-
281
- self.save((persistence, container["current"]), req, "save_to")
282
-
283
- if "ignore_error" not in req or not req["ignore_error"]:
284
- if "parent_current" in container:
285
- raise err
286
- break
287
-
288
- if "break" in req and self.and_condition(
289
- (persistence, container["current"]), req["break"]
290
- ):
291
- break
292
-
293
- return persistence
1
+ import re
2
+ from copy import deepcopy
3
+ from typing import Tuple
4
+
5
+ from celery import Task
6
+ from requests import get, post
7
+ from requests.exceptions import HTTPError
8
+ from jaseci import JsOrc
9
+
10
+ DEFAULT_MSG = "Skipping scheduled walker!"
11
+
12
+
13
+ class Queue(Task):
14
+ def run(self, wlk, nd, args):
15
+ hook = JsOrc.hook()
16
+
17
+ wlk = hook.get_obj_from_store(wlk)
18
+ wlk._to_await = True
19
+
20
+ nd = hook.get_obj_from_store(nd)
21
+ resp = wlk.run(nd, *args)
22
+ wlk.destroy()
23
+
24
+ return {"anchor": wlk.anchor_value(), "response": resp}
25
+
26
+
27
+ class ScheduledWalker(Task):
28
+ def get_obj(self, jid):
29
+ return self.hook.get_obj_from_store(jid)
30
+
31
+ def run(self, name, ctx, nd=None, snt=None, mst=None):
32
+ self.hook = JsOrc.hook()
33
+
34
+ if mst:
35
+ mst = self.get_obj(mst)
36
+ else:
37
+ return f"{DEFAULT_MSG} mst (Master) is required!"
38
+
39
+ if mst is None:
40
+ return f"{DEFAULT_MSG} Invalid Master!"
41
+
42
+ try:
43
+ if not snt:
44
+ if mst.active_snt_id == "global":
45
+ global_snt_id = self.hook.get_glob("GLOB_SENTINEL")
46
+ snt = self.get_obj(global_snt_id)
47
+ elif mst.active_snt_id:
48
+ snt = self.get_obj(mst.active_snt_id)
49
+ elif snt in mst.alias_map:
50
+ snt = self.get_obj(mst.alias_map[snt])
51
+ else:
52
+ snt = self.get_obj(snt)
53
+
54
+ if not snt:
55
+ return f"{DEFAULT_MSG} Invalid Sentinel!"
56
+
57
+ if not nd:
58
+ if mst.active_gph_id:
59
+ nd = self.get_obj(mst.active_gph_id)
60
+ elif nd in mst.alias_map:
61
+ nd = self.get_obj(mst.alias_map[nd])
62
+ else:
63
+ nd = self.get_obj(nd)
64
+
65
+ if not nd:
66
+ return f"{DEFAULT_MSG} Invalid Node!"
67
+
68
+ return mst.walker_run(name, nd, ctx, ctx, snt, False, False)
69
+ except Exception as e:
70
+ return f"{DEFAULT_MSG} Error occured: {e}"
71
+
72
+
73
+ class ScheduledSequence(Task):
74
+ json_escape = re.compile(r"[^a-zA-Z0-9_]")
75
+ internal = re.compile(r"\(([a-zA-Z0-9_\.\[\]\$\#\@\!]*?)\)")
76
+ full = re.compile(r"^\{\{([a-zA-Z0-9_\.\[\]\$\#\(\)\@\!]*?)\}\}$")
77
+ partial = re.compile(r"\{\{([a-zA-Z0-9_\.\[\]\$\#\(\)\@\!]*?)\}\}")
78
+
79
+ def get_deep_value(self, data, keys, default):
80
+ if len(keys) == 0:
81
+ return data
82
+
83
+ key = keys.pop(0)
84
+ t = type(data)
85
+
86
+ if t is dict and key in data:
87
+ return self.get_deep_value(data[key], keys, default)
88
+ elif t is list and key.isnumeric():
89
+ return self.get_deep_value(data[int(key)], keys, default)
90
+ else:
91
+ return default
92
+
93
+ def get_value(self, holder: Tuple, keys: str, default=None):
94
+ while self.internal.search(keys):
95
+ for intern in self.internal.findall(keys):
96
+ keys = keys.replace(
97
+ "(" + intern + ")", self.get_value(holder, intern, "")
98
+ )
99
+
100
+ if keys:
101
+ keys = keys.split(".")
102
+ key = keys.pop(0)
103
+ if key == "#":
104
+ return self.get_deep_value(holder[0], keys, default)
105
+ elif key == "$":
106
+ t = type(holder[1])
107
+ if t is dict or t is list:
108
+ return self.get_deep_value(holder[1], keys, default)
109
+ else:
110
+ return holder[1]
111
+ elif key == "@":
112
+ t = type(holder[2])
113
+ if t is dict or t is list:
114
+ return self.get_deep_value(holder[2], keys, default)
115
+ else:
116
+ return holder[2]
117
+ elif key == "!":
118
+ return holder[3]
119
+ return default
120
+
121
+ def compare(self, condition, expected, actual):
122
+ if condition == "eq":
123
+ return actual == expected
124
+ elif condition == "ne":
125
+ return actual != expected
126
+ elif condition == "gt":
127
+ return actual > expected
128
+ elif condition == "gte":
129
+ return actual >= expected
130
+ elif condition == "lt":
131
+ return actual < expected
132
+ elif condition == "lte":
133
+ return actual <= expected
134
+ elif condition == "regex":
135
+ return re.compile(expected).match(actual)
136
+
137
+ def condition(self, holder: Tuple, filter):
138
+ for cons in filter["condition"].keys():
139
+ if not (filter["condition"][cons] is None) and not self.compare(
140
+ cons, filter["condition"][cons], self.get_value(holder, filter["by"])
141
+ ):
142
+ return False
143
+ return True
144
+
145
+ def or_condition(self, holder: Tuple, filter):
146
+ for filt in filter:
147
+ if "condition" in filt and self.condition(holder, filt):
148
+ return True
149
+ elif "or" in filt and self.or_condition(holder, filt["or"]):
150
+ return True
151
+ elif "and" in filt and self.and_condition(holder, filt["and"]):
152
+ return True
153
+ return False
154
+
155
+ def and_condition(self, holder: Tuple, filter):
156
+ for filt in filter:
157
+ if "condition" in filt and not self.condition(holder, filt):
158
+ return False
159
+ elif "or" in filt and not self.or_condition(holder, filt["or"]):
160
+ return False
161
+ elif "and" in filt and not self.and_condition(holder, filt["and"]):
162
+ return False
163
+ return True
164
+
165
+ def deep_replace_str(self, holder: Tuple, data, key):
166
+ matcher = self.full.match(data[key])
167
+ if matcher:
168
+ data[key] = self.get_value(holder, matcher.group(1))
169
+ else:
170
+ for rep in self.partial.findall(data[key]):
171
+ data[key] = data[key].replace(
172
+ "{{" + rep + "}}", self.get_value(holder, rep, "")
173
+ )
174
+
175
+ def deep_replace_dict(self, holder: Tuple, data):
176
+ for key in data.keys():
177
+ if key != "__def_loop__":
178
+ t = type(data[key])
179
+ if t is str:
180
+ self.deep_replace_str(holder, data, key)
181
+ elif t is dict:
182
+ self.deep_replace_dict(holder, data[key])
183
+
184
+ def save(self, holder: Tuple, req, params):
185
+ if params in req:
186
+ holder[0][self.json_escape.sub("_", req[params])] = holder[1]
187
+
188
+ def trigger_interface(self, req: dict):
189
+ master = req.get("master")
190
+ if master is None:
191
+ caller = JsOrc.master()
192
+ trigger_type = "public"
193
+ else:
194
+ caller = JsOrc.hook().get_obj_from_store(master)
195
+ trigger_type = "general"
196
+
197
+ api = req.get("api")
198
+ body = req.get("body", {})
199
+
200
+ return getattr(caller, f"{trigger_type}_interface_to_api")(body, api)
201
+
202
+ def run(self, *args, **kwargs):
203
+ requests = kwargs.get("requests")
204
+ persistence = kwargs.get("persistence", {})
205
+ container = kwargs.get("container", {"current": persistence})
206
+ index = container.get("index", "0")
207
+
208
+ if "parent_current" in container:
209
+ container["current"] = container["parent_current"]
210
+
211
+ for req in requests:
212
+ try:
213
+ self.deep_replace_dict(
214
+ (
215
+ persistence,
216
+ container["current"],
217
+ container.get("parent_current", {}),
218
+ index,
219
+ ),
220
+ req,
221
+ )
222
+ self.save((persistence, req), req, "save_req_to")
223
+
224
+ method = req["method"].upper()
225
+
226
+ if method == "JAC":
227
+ container["current"] = self.trigger_interface(req)
228
+ else:
229
+ if method == "POST":
230
+ response = post(
231
+ req["api"],
232
+ json=req.get("body", {}),
233
+ headers=req.get("header", {}),
234
+ )
235
+ elif method == "GET":
236
+ response = get(req["api"], headers=req.get("header", {}))
237
+ response.raise_for_status()
238
+ if "application/json" in response.headers.get("Content-Type"):
239
+ container["current"] = response.json()
240
+ else:
241
+ container["current"] = response.text
242
+
243
+ if "__def_loop__" in req:
244
+ def_loop = req["__def_loop__"]
245
+ for idx, loop in enumerate(
246
+ self.get_value(
247
+ (persistence, container["current"]), def_loop["by"], []
248
+ )
249
+ ):
250
+ if "filter" in def_loop and not self.and_condition(
251
+ (persistence, loop), def_loop["filter"]
252
+ ):
253
+ continue
254
+ loop_container = {"parent_current": loop, "index": str(idx)}
255
+ self.run(
256
+ requests=deepcopy(def_loop["requests"]),
257
+ persistence=persistence,
258
+ container=loop_container,
259
+ )
260
+
261
+ self.save((persistence, container["current"]), req, "save_to")
262
+
263
+ except Exception as err:
264
+ container["current"] = (
265
+ {
266
+ "status": err.response.status_code,
267
+ "message": err.response.reason,
268
+ }
269
+ if isinstance(err, HTTPError)
270
+ else {
271
+ "worker_error": str(err),
272
+ }
273
+ )
274
+
275
+ self.save((persistence, container["current"]), req, "save_to")
276
+
277
+ if "ignore_error" not in req or not req["ignore_error"]:
278
+ if "parent_current" in container:
279
+ raise err
280
+ break
281
+
282
+ if "break" in req and self.and_condition(
283
+ (persistence, container["current"]), req["break"]
284
+ ):
285
+ break
286
+
287
+ return persistence
@@ -557,6 +557,26 @@ check_dict_for_in_loop = """
557
557
  }
558
558
  """
559
559
 
560
+ list_pairwise = """
561
+ walker init{
562
+ with entry{
563
+ _list = [1,2,3,4];
564
+ p = _list.list::pairwise;
565
+ report p;
566
+ }
567
+ }
568
+ """
569
+
570
+ list_unique = """
571
+ walker init{
572
+ with entry{
573
+ _list = [1,2,3,4,2,3,4,5];
574
+ p = _list.list::unique;
575
+ report p;
576
+ }
577
+ }
578
+ """
579
+
560
580
  check_new_builtin = """
561
581
  walker init {
562
582
  with entry {
@@ -578,6 +598,7 @@ check_new_builtin = """
578
598
  }
579
599
  """
580
600
 
601
+
581
602
  continue_issue = """
582
603
  walker init {
583
604
  root {