@voxgig/sdkgen 4.23.0 → 4.24.1
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.
- package/bin/voxgig-sdkgen +18 -2
- package/dist/action/doctor.d.ts +1 -1
- package/dist/action/doctor.js +12 -10
- package/dist/action/doctor.js.map +1 -1
- package/dist/action/edition.js +4 -0
- package/dist/action/edition.js.map +1 -1
- package/dist/action/feature.js +5 -1
- package/dist/action/feature.js.map +1 -1
- package/dist/action/remove.d.ts +16 -0
- package/dist/action/remove.js +345 -0
- package/dist/action/remove.js.map +1 -0
- package/dist/action/target.js +6 -1
- package/dist/action/target.js.map +1 -1
- package/dist/cmp/Deploy.js +9 -6
- package/dist/cmp/Deploy.js.map +1 -1
- package/dist/helpers/semver.js +3 -1
- package/dist/helpers/semver.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/project/.sdk/tm/c/core/context.c +3 -1
- package/project/.sdk/tm/c/feature/paging.c +37 -3
- package/project/.sdk/tm/c/feature/test.c +51 -27
- package/project/.sdk/tm/c/utility/prepare_query.c +2 -1
- package/project/.sdk/tm/c/utility/transform_request.c +25 -3
- package/project/.sdk/tm/clojure/src/sdk/core.clj +21 -7
- package/project/.sdk/tm/clojure/src/sdk/features.clj +68 -18
- package/project/.sdk/tm/cpp/core/types.hpp +1 -1
- package/project/.sdk/tm/cpp/feature/paging.hpp +36 -3
- package/project/.sdk/tm/cpp/feature/test.hpp +41 -13
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +16 -4
- package/project/.sdk/tm/csharp/core/Context.cs +1 -1
- package/project/.sdk/tm/csharp/feature/PagingFeature.cs +58 -4
- package/project/.sdk/tm/csharp/feature/TestFeature.cs +13 -21
- package/project/.sdk/tm/csharp/utility/Fetcher.cs +19 -12
- package/project/.sdk/tm/csharp/utility/PrepareQuery.cs +1 -1
- package/project/.sdk/tm/csharp/utility/TransformRequest.cs +23 -3
- package/project/.sdk/tm/elixir/lib/projectname/context.ex +1 -1
- package/project/.sdk/tm/elixir/lib/projectname/feature/paging.ex +52 -10
- package/project/.sdk/tm/elixir/lib/projectname/feature/test.ex +47 -17
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +30 -9
- package/project/.sdk/tm/go/core/context.go +13 -13
- package/project/.sdk/tm/go/feature/paging_feature.go +40 -2
- package/project/.sdk/tm/go/feature/test_feature.go +3 -15
- package/project/.sdk/tm/go/test/feature_corpus_test.go +1 -0
- package/project/.sdk/tm/go/test/feature_test.go +54 -0
- package/project/.sdk/tm/go/test/pipeline_test.go +163 -57
- package/project/.sdk/tm/go/test/primary_utility_test.go +53 -0
- package/project/.sdk/tm/go/utility/fetcher.go +41 -20
- package/project/.sdk/tm/go/utility/prepare_query.go +1 -1
- package/project/.sdk/tm/go/utility/transform_request.go +22 -3
- package/project/.sdk/tm/java/core/Context.java +2 -1
- package/project/.sdk/tm/java/feature/PagingFeature.java +46 -4
- package/project/.sdk/tm/java/feature/TestFeature.java +10 -13
- package/project/.sdk/tm/java/test/FeatureCorpusTest.java +3 -0
- package/project/.sdk/tm/java/test/FeatureTest.java +42 -0
- package/project/.sdk/tm/java/test/PipelineTest.java +141 -48
- package/project/.sdk/tm/java/test/PrimaryUtilityTest.java +67 -6
- package/project/.sdk/tm/java/utility/Fetcher.java +36 -28
- package/project/.sdk/tm/java/utility/PrepareQuery.java +1 -1
- package/project/.sdk/tm/java/utility/TransformRequest.java +18 -3
- package/project/.sdk/tm/js/src/Context.js +7 -2
- package/project/.sdk/tm/js/src/feature/paging/PagingFeature.js +24 -6
- package/project/.sdk/tm/js/src/feature/test/TestFeature.js +7 -5
- package/project/.sdk/tm/js/src/utility/PrepareQueryUtility.js +1 -1
- package/project/.sdk/tm/js/test/feature/Corpus.test.js +4 -0
- package/project/.sdk/tm/js/test/pipeline.test.js +88 -22
- package/project/.sdk/tm/js/test/utility/PrimaryUtility.test.js +47 -1
- package/project/.sdk/tm/kotlin/core/Context.kt +1 -1
- package/project/.sdk/tm/kotlin/feature/PagingFeature.kt +36 -4
- package/project/.sdk/tm/kotlin/feature/TestFeature.kt +11 -14
- package/project/.sdk/tm/kotlin/utility/Fetcher.kt +33 -24
- package/project/.sdk/tm/kotlin/utility/Prepare.kt +1 -1
- package/project/.sdk/tm/kotlin/utility/ResultTransform.kt +18 -3
- package/project/.sdk/tm/lua/core/context.lua +8 -1
- package/project/.sdk/tm/lua/core/control.lua +3 -0
- package/project/.sdk/tm/lua/feature/paging_feature.lua +44 -5
- package/project/.sdk/tm/lua/feature/test_feature.lua +32 -14
- package/project/.sdk/tm/lua/test/omni.lua +8 -1
- package/project/.sdk/tm/lua/test/pipeline_test.lua +107 -28
- package/project/.sdk/tm/lua/test/primary_utility_test.lua +83 -3
- package/project/.sdk/tm/lua/utility/prepare_query.lua +2 -1
- package/project/.sdk/tm/lua/utility/transform_request.lua +19 -3
- package/project/.sdk/tm/ocaml/sdk_features.ml +72 -21
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +23 -8
- package/project/.sdk/tm/perl/core/context.pm +2 -1
- package/project/.sdk/tm/perl/feature/paging_feature.pm +31 -5
- package/project/.sdk/tm/perl/feature/test_feature.pm +46 -16
- package/project/.sdk/tm/perl/feature/timeout_feature.pm +17 -13
- package/project/.sdk/tm/perl/t/feature_corpus.t +14 -1
- package/project/.sdk/tm/perl/t/omni.pm +7 -1
- package/project/.sdk/tm/perl/t/pipeline.t +124 -27
- package/project/.sdk/tm/perl/t/primary_utility.t +73 -0
- package/project/.sdk/tm/perl/utility/fetcher.pm +15 -1
- package/project/.sdk/tm/perl/utility/prepare_query.pm +1 -0
- package/project/.sdk/tm/perl/utility/transform_request.pm +13 -3
- package/project/.sdk/tm/php/core/Context.php +5 -1
- package/project/.sdk/tm/php/core/Control.php +5 -0
- package/project/.sdk/tm/php/feature/PagingFeature.php +26 -5
- package/project/.sdk/tm/php/feature/TestFeature.php +27 -42
- package/project/.sdk/tm/php/test/FeatureCorpusTest.php +17 -1
- package/project/.sdk/tm/php/test/FeatureTest.php +44 -0
- package/project/.sdk/tm/php/test/PipelineTest.php +143 -26
- package/project/.sdk/tm/php/test/PrimaryUtilityTest.php +80 -2
- package/project/.sdk/tm/php/utility/Fetcher.php +17 -3
- package/project/.sdk/tm/php/utility/PrepareQuery.php +1 -1
- package/project/.sdk/tm/php/utility/TransformRequest.php +15 -3
- package/project/.sdk/tm/py/pkg/core/context.py +2 -1
- package/project/.sdk/tm/py/pkg/feature/paging_feature.py +29 -4
- package/project/.sdk/tm/py/pkg/feature/test_feature.py +29 -12
- package/project/.sdk/tm/py/pkg/utility/fetcher.py +63 -65
- package/project/.sdk/tm/py/pkg/utility/prepare_query.py +2 -1
- package/project/.sdk/tm/py/pkg/utility/transform_request.py +11 -3
- package/project/.sdk/tm/py/test/test_feature.py +27 -0
- package/project/.sdk/tm/py/test/test_feature_corpus.py +11 -1
- package/project/.sdk/tm/py/test/test_pipeline.py +108 -31
- package/project/.sdk/tm/py/test/test_primary_utility.py +76 -1
- package/project/.sdk/tm/rb/core/context.rb +1 -1
- package/project/.sdk/tm/rb/feature/paging_feature.rb +27 -5
- package/project/.sdk/tm/rb/feature/test_feature.rb +33 -7
- package/project/.sdk/tm/rb/test/feature_corpus_test.rb +12 -1
- package/project/.sdk/tm/rb/test/feature_test.rb +38 -0
- package/project/.sdk/tm/rb/test/pipeline_test.rb +100 -24
- package/project/.sdk/tm/rb/test/primary_utility_test.rb +59 -1
- package/project/.sdk/tm/rb/utility/fetcher.rb +49 -3
- package/project/.sdk/tm/rb/utility/prepare_query.rb +1 -1
- package/project/.sdk/tm/rb/utility/transform_request.rb +10 -3
- package/project/.sdk/tm/rust/core/context.rs +2 -6
- package/project/.sdk/tm/rust/feature/paging.rs +44 -3
- package/project/.sdk/tm/rust/feature/secrets.rs +1 -1
- package/project/.sdk/tm/rust/feature/test.rs +66 -33
- package/project/.sdk/tm/rust/utility/fetcher.rs +41 -14
- package/project/.sdk/tm/rust/utility/prepare_query.rs +1 -1
- package/project/.sdk/tm/rust/utility/transform_request.rs +31 -5
- package/project/.sdk/tm/scala/core/Context.scala +2 -1
- package/project/.sdk/tm/scala/feature/PagingFeature.scala +41 -4
- package/project/.sdk/tm/scala/feature/TestFeature.scala +11 -12
- package/project/.sdk/tm/scala/utility/Fetcher.scala +34 -13
- package/project/.sdk/tm/scala/utility/Prepare.scala +1 -1
- package/project/.sdk/tm/scala/utility/Transform.scala +19 -3
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/core/Context.swift +1 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/PagingFeature.swift +31 -4
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/TestFeature.swift +12 -9
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Fetcher.swift +22 -3
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Prepare.swift +1 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/ResultUtil.swift +14 -3
- package/project/.sdk/tm/ts/src/Context.ts +7 -2
- package/project/.sdk/tm/ts/src/feature/paging/PagingFeature.ts +19 -3
- package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +3 -0
- package/project/.sdk/tm/ts/src/utility/PrepareQueryUtility.ts +1 -1
- package/project/.sdk/tm/ts/test/feature/Corpus.test.ts +1 -0
- package/project/.sdk/tm/ts/test/pipeline.test.ts +52 -11
- package/project/.sdk/tm/zig/core/context.zig +1 -1
- package/project/.sdk/tm/zig/core/utility.zig +19 -5
- package/project/.sdk/tm/zig/feature/paging.zig +34 -4
- package/project/.sdk/tm/zig/feature/test.zig +54 -26
- package/project/sdkgen-package.json +1 -1
- package/src/action/doctor.ts +12 -9
- package/src/action/edition.ts +8 -0
- package/src/action/feature.ts +7 -1
- package/src/action/remove.ts +492 -0
- package/src/action/target.ts +8 -1
- package/src/cmp/Deploy.ts +11 -6
- package/src/helpers/semver.ts +3 -1
|
@@ -12,6 +12,22 @@ class ProjectNameFetcher
|
|
|
12
12
|
// Users can override by passing a User-Agent header in fetchdef.
|
|
13
13
|
public const DEFAULT_USER_AGENT = 'Mozilla/5.0 (compatible; ProjectNameSDK/1.0)';
|
|
14
14
|
|
|
15
|
+
// One cURL handle per process, reset between calls rather than closed:
|
|
16
|
+
// curl_reset keeps the connection cache, so the next request to the
|
|
17
|
+
// same host reuses the open connection.
|
|
18
|
+
private static $curl = null;
|
|
19
|
+
|
|
20
|
+
private static function curlHandle(string $fullurl)
|
|
21
|
+
{
|
|
22
|
+
if (self::$curl === null) {
|
|
23
|
+
self::$curl = curl_init();
|
|
24
|
+
} else {
|
|
25
|
+
curl_reset(self::$curl);
|
|
26
|
+
}
|
|
27
|
+
curl_setopt(self::$curl, CURLOPT_URL, $fullurl);
|
|
28
|
+
return self::$curl;
|
|
29
|
+
}
|
|
30
|
+
|
|
15
31
|
public static function defaultHttpFetch(string $fullurl, array $fetchdef): array
|
|
16
32
|
{
|
|
17
33
|
$method_str = strtoupper($fetchdef['method'] ?? 'GET');
|
|
@@ -130,7 +146,7 @@ class ProjectNameFetcher
|
|
|
130
146
|
];
|
|
131
147
|
}
|
|
132
148
|
|
|
133
|
-
$ch =
|
|
149
|
+
$ch = self::curlHandle($fullurl);
|
|
134
150
|
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
|
135
151
|
curl_setopt($ch, CURLOPT_HEADER, false);
|
|
136
152
|
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false);
|
|
@@ -157,7 +173,6 @@ class ProjectNameFetcher
|
|
|
157
173
|
$response_body = curl_exec($ch);
|
|
158
174
|
if ($response_body === false) {
|
|
159
175
|
$err = curl_error($ch) ?: 'curl_exec failed';
|
|
160
|
-
curl_close($ch);
|
|
161
176
|
return [
|
|
162
177
|
[
|
|
163
178
|
'status' => 0,
|
|
@@ -169,7 +184,6 @@ class ProjectNameFetcher
|
|
|
169
184
|
$err,
|
|
170
185
|
];
|
|
171
186
|
}
|
|
172
|
-
curl_close($ch);
|
|
173
187
|
|
|
174
188
|
$status = 0;
|
|
175
189
|
$status_text = '';
|
|
@@ -22,7 +22,7 @@ class ProjectNamePrepareQuery
|
|
|
22
22
|
foreach ($items as $item) {
|
|
23
23
|
$key = $item[0];
|
|
24
24
|
$val = $item[1];
|
|
25
|
-
if ($val !== null && is_string($key) && !in_array($key, $params, true)) {
|
|
25
|
+
if ($val !== null && is_string($key) && '$action' !== $key && !in_array($key, $params, true)) {
|
|
26
26
|
$out[$key] = $val;
|
|
27
27
|
}
|
|
28
28
|
}
|
|
@@ -16,12 +16,24 @@ class ProjectNameTransformRequest
|
|
|
16
16
|
}
|
|
17
17
|
$transform = ProjectNameHelpers::to_map(\Voxgig\Struct\Struct::getprop($point, 'transform'));
|
|
18
18
|
if (!$transform) {
|
|
19
|
-
return $ctx->reqdata;
|
|
19
|
+
return self::strip_action($ctx->reqdata);
|
|
20
20
|
}
|
|
21
21
|
$reqform = \Voxgig\Struct\Struct::getprop($transform, 'req');
|
|
22
22
|
if (!$reqform) {
|
|
23
|
-
return $ctx->reqdata;
|
|
23
|
+
return self::strip_action($ctx->reqdata);
|
|
24
24
|
}
|
|
25
|
-
return \Voxgig\Struct\Struct::transform(['reqdata' => $ctx->reqdata], $reqform);
|
|
25
|
+
return self::strip_action(\Voxgig\Struct\Struct::transform(['reqdata' => $ctx->reqdata], $reqform));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// `$action` selects the point (see MakePoint); it is never an API field,
|
|
29
|
+
// so the body is a copy without it. Arrays are values, so the unset
|
|
30
|
+
// never reaches the caller's copy.
|
|
31
|
+
private static function strip_action(mixed $reqdata): mixed
|
|
32
|
+
{
|
|
33
|
+
if (!is_array($reqdata) || !array_key_exists('$action', $reqdata)) {
|
|
34
|
+
return $reqdata;
|
|
35
|
+
}
|
|
36
|
+
unset($reqdata['$action']);
|
|
37
|
+
return $reqdata;
|
|
26
38
|
}
|
|
27
39
|
}
|
|
@@ -53,7 +53,8 @@ class ProjectNameContext:
|
|
|
53
53
|
self.ctrl.actor = ctrl_raw["actor"]
|
|
54
54
|
if isinstance(ctrl_raw.get("paging"), dict):
|
|
55
55
|
self.ctrl.paging = ctrl_raw["paging"]
|
|
56
|
-
elif basectx is not None and basectx.ctrl is not None
|
|
56
|
+
elif (basectx is not None and basectx.ctrl is not None
|
|
57
|
+
and get_ctx_prop(ctxmap, "opname") is None):
|
|
57
58
|
self.ctrl = basectx.ctrl
|
|
58
59
|
|
|
59
60
|
# Meta
|
|
@@ -12,8 +12,8 @@ from projectname_sdk.feature.base_feature import ProjectNameBaseFeature
|
|
|
12
12
|
# stamps page/limit (or a cursor) into the request query; on the way back
|
|
13
13
|
# (PreResult) it reads the server's pagination signals — a `Link:
|
|
14
14
|
# rel="next"` header, `X-Page`/`X-Next-Page`/`X-Total-Count` headers, or
|
|
15
|
-
# `next`/`cursor`/`nextCursor`/`hasMore` fields in the body
|
|
16
|
-
# them on `ctx.result.paging
|
|
15
|
+
# `next`/`cursor`/`nextCursor`/`hasMore` fields in the body (snake_case forms
|
|
16
|
+
# too) — and records them on `ctx.result.paging` and the caller's ctrl paging. A per-call cursor/page supplied via ctrl
|
|
17
17
|
# paging takes priority (used by auto-iteration). Parameter names
|
|
18
18
|
# (`pageParam`/`limitParam`/`cursorParam`), `startPage` and page size
|
|
19
19
|
# (`limit`) are configurable.
|
|
@@ -66,7 +66,11 @@ class ProjectNamePagingFeature(ProjectNameBaseFeature):
|
|
|
66
66
|
if paging.get("cursor") is not None:
|
|
67
67
|
spec.query[cursor_param] = paging["cursor"]
|
|
68
68
|
elif spec.query.get(page_param) is None:
|
|
69
|
-
page
|
|
69
|
+
# A record written back by PreResult holds the page just fetched
|
|
70
|
+
# as "page" and the one to fetch as "nextPage", so nextPage wins.
|
|
71
|
+
page = paging.get("nextPage")
|
|
72
|
+
if page is None:
|
|
73
|
+
page = paging.get("page")
|
|
70
74
|
if page is None:
|
|
71
75
|
page = self.options.get("startPage") or 1
|
|
72
76
|
spec.query[page_param] = page
|
|
@@ -166,14 +170,25 @@ class ProjectNamePagingFeature(ProjectNameBaseFeature):
|
|
|
166
170
|
paging["hasMore"] = more
|
|
167
171
|
explicit_more = True
|
|
168
172
|
|
|
169
|
-
# Body-level
|
|
173
|
+
# Body-level signals; snake_case forms are read first so camelCase wins.
|
|
170
174
|
if isinstance(body, dict):
|
|
171
175
|
if body.get("next") is not None:
|
|
172
176
|
paging["next"] = paging["next"] or body["next"]
|
|
177
|
+
if body.get("next_cursor") is not None:
|
|
178
|
+
paging["cursor"] = body["next_cursor"]
|
|
173
179
|
if body.get("cursor") is not None:
|
|
174
180
|
paging["cursor"] = body["cursor"]
|
|
175
181
|
if body.get("nextCursor") is not None:
|
|
176
182
|
paging["cursor"] = body["nextCursor"]
|
|
183
|
+
if paging["nextPage"] is None:
|
|
184
|
+
np = body.get("nextPage")
|
|
185
|
+
if np is None:
|
|
186
|
+
np = body.get("next_page")
|
|
187
|
+
if isinstance(np, (int, float, str)) and not isinstance(np, bool):
|
|
188
|
+
paging["nextPage"] = np
|
|
189
|
+
if isinstance(body.get("has_more"), bool):
|
|
190
|
+
paging["hasMore"] = body["has_more"]
|
|
191
|
+
explicit_more = True
|
|
177
192
|
if isinstance(body.get("hasMore"), bool):
|
|
178
193
|
paging["hasMore"] = body["hasMore"]
|
|
179
194
|
explicit_more = True
|
|
@@ -191,6 +206,16 @@ class ProjectNamePagingFeature(ProjectNameBaseFeature):
|
|
|
191
206
|
|
|
192
207
|
result.paging = paging
|
|
193
208
|
|
|
209
|
+
ctrl = ctx.ctrl
|
|
210
|
+
if ctrl is not None:
|
|
211
|
+
# The Context shares the caller's paging dict by reference, so
|
|
212
|
+
# refill it in place; a fresh dict on ctrl never reaches the caller.
|
|
213
|
+
if isinstance(getattr(ctrl, "paging", None), dict):
|
|
214
|
+
ctrl.paging.clear()
|
|
215
|
+
ctrl.paging.update(paging)
|
|
216
|
+
else:
|
|
217
|
+
ctrl.paging = paging
|
|
218
|
+
|
|
194
219
|
self.client._paging = {"last": paging}
|
|
195
220
|
|
|
196
221
|
def _is_list(self, ctx):
|
|
@@ -143,18 +143,11 @@ class ProjectNameTestFeature(ProjectNameBaseFeature):
|
|
|
143
143
|
args = test_self.build_args(fctx, op, update_match)
|
|
144
144
|
found = vs.select(entmap, args)
|
|
145
145
|
ent = vs.getelem(found, 0)
|
|
146
|
-
if ent is None and isinstance(entmap, dict):
|
|
147
|
-
for e in entmap.values():
|
|
148
|
-
if isinstance(e, dict):
|
|
149
|
-
ent = e
|
|
150
|
-
break
|
|
151
146
|
if ent is None:
|
|
147
|
+
# update miss: 404, never another record
|
|
152
148
|
return respond(404, None, {"statusText": "Not found"})
|
|
153
|
-
if isinstance(ent, dict):
|
|
154
|
-
|
|
155
|
-
if reqdata is not None:
|
|
156
|
-
for k, v in reqdata.items():
|
|
157
|
-
ent[k] = v
|
|
149
|
+
if isinstance(ent, dict) and isinstance(fctx.reqdata, dict):
|
|
150
|
+
vs.merge([ent, fctx.reqdata])
|
|
158
151
|
vs.delprop(ent, "$KEY")
|
|
159
152
|
out = vs.clone(ent)
|
|
160
153
|
return respond(200, out)
|
|
@@ -260,12 +253,36 @@ class ProjectNameTestFeature(ProjectNameBaseFeature):
|
|
|
260
253
|
|
|
261
254
|
return netsim_fetcher
|
|
262
255
|
|
|
256
|
+
# The entity's own endpoint: a terminal `{param}` marks a record route,
|
|
257
|
+
# and among equals the shallower path wins (the same rule as make_point).
|
|
258
|
+
@staticmethod
|
|
259
|
+
def pick_point(points):
|
|
260
|
+
if not isinstance(points, list) or len(points) == 0:
|
|
261
|
+
return None
|
|
262
|
+
|
|
263
|
+
def terminal(p):
|
|
264
|
+
parts = vs.getprop(p, "parts")
|
|
265
|
+
return isinstance(parts, list) and len(parts) > 0 and \
|
|
266
|
+
isinstance(parts[-1], str) and parts[-1].startswith("{")
|
|
267
|
+
|
|
268
|
+
def depth(p):
|
|
269
|
+
parts = vs.getprop(p, "parts")
|
|
270
|
+
return len(parts) if isinstance(parts, list) else 0
|
|
271
|
+
|
|
272
|
+
point = points[0]
|
|
273
|
+
for cand in points[1:]:
|
|
274
|
+
if terminal(cand) != terminal(point):
|
|
275
|
+
if terminal(cand):
|
|
276
|
+
point = cand
|
|
277
|
+
elif depth(cand) < depth(point):
|
|
278
|
+
point = cand
|
|
279
|
+
return point
|
|
280
|
+
|
|
263
281
|
def build_args(self, ctx, op, args):
|
|
264
282
|
opname = op.name
|
|
265
283
|
|
|
266
|
-
# Get last point from config.
|
|
267
284
|
points = vs.getpath(ctx.config, "entity." + ctx.entity.get_name() + ".op." + opname + ".points")
|
|
268
|
-
point =
|
|
285
|
+
point = self.pick_point(points)
|
|
269
286
|
|
|
270
287
|
# Path AND query: a path-only read misses a query-addressed record
|
|
271
288
|
# (e.g. GET /result?trace_id=), which has no path param at all.
|
|
@@ -2,21 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
import json
|
|
5
|
+
import threading
|
|
6
|
+
from http.cookiejar import DefaultCookiePolicy
|
|
5
7
|
from projectname_sdk.utility.voxgig_struct import voxgig_struct as vs
|
|
6
8
|
|
|
7
9
|
|
|
8
10
|
# Default User-Agent — many CDNs (notably Cloudflare) reject requests with
|
|
9
|
-
#
|
|
10
|
-
# the
|
|
11
|
-
#
|
|
12
|
-
#
|
|
11
|
+
# a library's default UA, returning 403 before the request even reaches
|
|
12
|
+
# the origin. Set a Mozilla-shaped UA so the SDK behaves like every other
|
|
13
|
+
# HTTP client by default. Users can still override by passing a User-Agent
|
|
14
|
+
# header in fetchdef.
|
|
13
15
|
_DEFAULT_USER_AGENT = "Mozilla/5.0 (compatible; ProjectNameSDK/1.0)"
|
|
14
16
|
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
_SESSION = None
|
|
19
|
+
_SESSION_LOCK = threading.Lock()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _session():
|
|
23
|
+
# Pool connections without retaining response cookies between calls.
|
|
24
|
+
global _SESSION
|
|
25
|
+
if _SESSION is None:
|
|
26
|
+
import requests
|
|
27
|
+
with _SESSION_LOCK:
|
|
28
|
+
if _SESSION is None:
|
|
29
|
+
session = requests.Session()
|
|
30
|
+
session.cookies.set_policy(DefaultCookiePolicy(allowed_domains=[]))
|
|
31
|
+
_SESSION = session
|
|
32
|
+
return _SESSION
|
|
33
|
+
|
|
19
34
|
|
|
35
|
+
def _default_http_fetch(fullurl, fetchdef):
|
|
20
36
|
method = fetchdef.get("method", "GET")
|
|
21
37
|
body_str = fetchdef.get("body")
|
|
22
38
|
headers = fetchdef.get("headers", {})
|
|
@@ -26,76 +42,58 @@ def _default_http_fetch(fullurl, fetchdef):
|
|
|
26
42
|
|
|
27
43
|
data = body_str.encode("utf-8") if body_str is not None else None
|
|
28
44
|
|
|
29
|
-
|
|
45
|
+
req_headers = {}
|
|
30
46
|
has_ua = False
|
|
31
47
|
for k, v in headers.items():
|
|
32
48
|
if k.lower() == "user-agent":
|
|
33
49
|
has_ua = True
|
|
34
|
-
|
|
50
|
+
req_headers[k] = v
|
|
35
51
|
if not has_ua:
|
|
36
|
-
|
|
52
|
+
req_headers["User-Agent"] = _DEFAULT_USER_AGENT
|
|
37
53
|
|
|
38
54
|
# Manual redirects: fetchdef["redirect"] == "manual" surfaces a 3xx as
|
|
39
|
-
# an ordinary response instead of auto-following it
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
#
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
# an ordinary response instead of auto-following it, which would replay
|
|
56
|
+
# the request, headers included, against whatever host Location names.
|
|
57
|
+
# Set by the station feature's transport middleware under an egress
|
|
58
|
+
# hosts policy (mirrors the ts fetch option).
|
|
59
|
+
kwargs = {"allow_redirects": fetchdef.get("redirect") != "manual"}
|
|
60
|
+
|
|
61
|
+
proxy = fetchdef.get("proxy")
|
|
62
|
+
if isinstance(proxy, str) and proxy:
|
|
63
|
+
kwargs["proxies"] = {"http": proxy, "https": proxy}
|
|
64
|
+
|
|
65
|
+
timeout = fetchdef.get("timeout")
|
|
66
|
+
if isinstance(timeout, (int, float)) and timeout > 0:
|
|
67
|
+
kwargs["timeout"] = timeout
|
|
49
68
|
|
|
50
69
|
try:
|
|
51
|
-
resp =
|
|
52
|
-
body = resp.
|
|
53
|
-
resp_headers = {}
|
|
54
|
-
for k, v in resp.getheaders():
|
|
55
|
-
resp_headers[k.lower()] = v
|
|
56
|
-
|
|
57
|
-
json_body = None
|
|
58
|
-
if len(body) > 0:
|
|
59
|
-
try:
|
|
60
|
-
json_body = json.loads(body)
|
|
61
|
-
except Exception:
|
|
62
|
-
pass
|
|
63
|
-
|
|
64
|
-
status = resp.getcode()
|
|
65
|
-
status_text = "OK" if status < 400 else "Error"
|
|
66
|
-
|
|
67
|
-
return {
|
|
68
|
-
"status": status,
|
|
69
|
-
"statusText": status_text,
|
|
70
|
-
"headers": resp_headers,
|
|
71
|
-
"json": lambda: json_body,
|
|
72
|
-
"body": body,
|
|
73
|
-
}, None
|
|
74
|
-
except urllib.error.HTTPError as e:
|
|
75
|
-
body = e.read().decode("utf-8")
|
|
76
|
-
resp_headers = {}
|
|
77
|
-
for k, v in e.headers.items():
|
|
78
|
-
resp_headers[k.lower()] = v
|
|
79
|
-
|
|
80
|
-
json_body = None
|
|
81
|
-
if len(body) > 0:
|
|
82
|
-
try:
|
|
83
|
-
json_body = json.loads(body)
|
|
84
|
-
except Exception:
|
|
85
|
-
pass
|
|
86
|
-
|
|
87
|
-
status_text = "OK" if e.code < 400 else "Error"
|
|
88
|
-
|
|
89
|
-
return {
|
|
90
|
-
"status": e.code,
|
|
91
|
-
"statusText": status_text,
|
|
92
|
-
"headers": resp_headers,
|
|
93
|
-
"json": lambda: json_body,
|
|
94
|
-
"body": body,
|
|
95
|
-
}, None
|
|
70
|
+
resp = _session().request(method, fullurl, data=data, headers=req_headers, **kwargs)
|
|
71
|
+
body = resp.content.decode("utf-8")
|
|
96
72
|
except Exception as e:
|
|
97
73
|
return None, str(e)
|
|
98
74
|
|
|
75
|
+
resp_headers = {}
|
|
76
|
+
for k, v in resp.headers.items():
|
|
77
|
+
resp_headers[k.lower()] = v
|
|
78
|
+
|
|
79
|
+
json_body = None
|
|
80
|
+
if len(body) > 0:
|
|
81
|
+
try:
|
|
82
|
+
json_body = json.loads(body)
|
|
83
|
+
except Exception:
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
status = resp.status_code
|
|
87
|
+
status_text = resp.reason or ("OK" if status < 400 else "Error")
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
"status": status,
|
|
91
|
+
"statusText": status_text,
|
|
92
|
+
"headers": resp_headers,
|
|
93
|
+
"json": lambda: json_body,
|
|
94
|
+
"body": body,
|
|
95
|
+
}, None
|
|
96
|
+
|
|
99
97
|
|
|
100
98
|
def fetcher_util(ctx, fullurl, fetchdef):
|
|
101
99
|
if ctx.client.mode != "live":
|
|
@@ -27,7 +27,8 @@ def prepare_query_util(ctx):
|
|
|
27
27
|
for item in reqmatch_items:
|
|
28
28
|
key = item[0]
|
|
29
29
|
val = item[1]
|
|
30
|
-
if val is not None and isinstance(key, str) and
|
|
30
|
+
if val is not None and isinstance(key, str) and key != "$action" \
|
|
31
|
+
and not _contains_param(params, key):
|
|
31
32
|
out[key] = val
|
|
32
33
|
|
|
33
34
|
return out
|
|
@@ -5,6 +5,14 @@ from projectname_sdk.utility.voxgig_struct import voxgig_struct as vs
|
|
|
5
5
|
from projectname_sdk.core.helpers import to_map
|
|
6
6
|
|
|
7
7
|
|
|
8
|
+
# `$action` selects the point (see make_point_util); it is never an API field,
|
|
9
|
+
# so the body is a copy without it. The caller's dict is left untouched.
|
|
10
|
+
def _strip_action(reqdata):
|
|
11
|
+
if not isinstance(reqdata, dict) or "$action" not in reqdata:
|
|
12
|
+
return reqdata
|
|
13
|
+
return {k: v for k, v in reqdata.items() if k != "$action"}
|
|
14
|
+
|
|
15
|
+
|
|
8
16
|
def transform_request_util(ctx):
|
|
9
17
|
spec = ctx.spec
|
|
10
18
|
point = ctx.point
|
|
@@ -14,14 +22,14 @@ def transform_request_util(ctx):
|
|
|
14
22
|
|
|
15
23
|
transform = to_map(vs.getprop(point, "transform"))
|
|
16
24
|
if transform is None:
|
|
17
|
-
return ctx.reqdata
|
|
25
|
+
return _strip_action(ctx.reqdata)
|
|
18
26
|
|
|
19
27
|
reqform = vs.getprop(transform, "req")
|
|
20
28
|
if reqform is None:
|
|
21
|
-
return ctx.reqdata
|
|
29
|
+
return _strip_action(ctx.reqdata)
|
|
22
30
|
|
|
23
31
|
reqdata = vs.transform({
|
|
24
32
|
"reqdata": ctx.reqdata,
|
|
25
33
|
}, reqform)
|
|
26
34
|
|
|
27
|
-
return reqdata
|
|
35
|
+
return _strip_action(reqdata)
|
|
@@ -724,6 +724,33 @@ class TestPagingFeature:
|
|
|
724
724
|
assert res["result"].paging["cursor"] == "abc"
|
|
725
725
|
assert res["result"].paging["hasMore"] is True
|
|
726
726
|
|
|
727
|
+
def test_snake_case_signals_and_ctrl_write_back(self):
|
|
728
|
+
server, calls = recording_server(lambda n, _fd: make_response(
|
|
729
|
+
200, {"has_more": True, "next_cursor": "c2"} if n == 1
|
|
730
|
+
else {"has_more": False}))
|
|
731
|
+
h = make_client([{"name": "paging"}], server=server)
|
|
732
|
+
pg = {}
|
|
733
|
+
ctrl = {"paging": pg}
|
|
734
|
+
res = h.op(op="list", path="/w", ctrl=ctrl)
|
|
735
|
+
assert res["result"].paging["hasMore"] is True
|
|
736
|
+
assert res["result"].paging["cursor"] == "c2"
|
|
737
|
+
assert pg["cursor"] == "c2" and pg["hasMore"] is True
|
|
738
|
+
h.op(op="list", path="/w", ctrl=ctrl)
|
|
739
|
+
assert re.search(r"[?&]cursor=c2(&|$)", calls[1]["url"])
|
|
740
|
+
assert pg["hasMore"] is False and pg["cursor"] is None
|
|
741
|
+
|
|
742
|
+
def test_continues_from_written_back_next_page(self):
|
|
743
|
+
server, calls = recording_server(lambda n, _fd: make_response(
|
|
744
|
+
200, {"next_page": 2} if n == 1 else {}, {"x-page": str(n)}))
|
|
745
|
+
h = make_client([{"name": "paging"}], server=server)
|
|
746
|
+
pg = {}
|
|
747
|
+
ctrl = {"paging": pg}
|
|
748
|
+
h.op(op="list", path="/w", ctrl=ctrl)
|
|
749
|
+
assert pg["page"] == 1 and pg["nextPage"] == 2 and pg["hasMore"] is True
|
|
750
|
+
h.op(op="list", path="/w", ctrl=ctrl)
|
|
751
|
+
assert re.search(r"[?&]page=2(&|$)", calls[1]["url"])
|
|
752
|
+
assert pg["hasMore"] is False
|
|
753
|
+
|
|
727
754
|
def test_non_list_op_is_not_paged(self):
|
|
728
755
|
server, calls = recording_server()
|
|
729
756
|
h = make_client([{"name": "paging"}], server=server)
|
|
@@ -240,6 +240,14 @@ def _subset(actual, expect, path):
|
|
|
240
240
|
"{}: got {!r}, want {!r}".format(path, actual, expect)
|
|
241
241
|
|
|
242
242
|
|
|
243
|
+
# Whether the SDK built the feature at all. The activity record is the
|
|
244
|
+
# wrong probe: most features create theirs on first use, so an idle client
|
|
245
|
+
# has none and every section but the eager ones read as inert.
|
|
246
|
+
def _present(client, name):
|
|
247
|
+
return any(getattr(f, "name", None) == name
|
|
248
|
+
for f in (getattr(client, "features", None) or []))
|
|
249
|
+
|
|
250
|
+
|
|
243
251
|
def _record(client, name):
|
|
244
252
|
return getattr(client, "_" + name, None)
|
|
245
253
|
|
|
@@ -280,7 +288,9 @@ class TestFeatureCorpus:
|
|
|
280
288
|
# Probed by ACTIVATING it: the feature defaults to inactive, so an
|
|
281
289
|
# idle client never builds it and its absence says nothing.
|
|
282
290
|
probe = _client({"feature": [{"name": name, "active": True}]})
|
|
283
|
-
if
|
|
291
|
+
if not _present(probe, name):
|
|
292
|
+
# The one line every runner prints for an inert section.
|
|
293
|
+
print("feature.{}: inert (this SDK does not generate the feature)".format(name))
|
|
284
294
|
pytest.skip("this SDK was generated without the {} feature".format(name))
|
|
285
295
|
|
|
286
296
|
ops = _usable_ops(2)
|