@voxgig/sdkgen 1.5.1 → 2.0.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/dist/cmp/AgentGuideContent.js +5 -0
- package/dist/cmp/AgentGuideContent.js.map +1 -1
- package/dist/cmp/Feature.js +13 -0
- package/dist/cmp/Feature.js.map +1 -1
- package/dist/helpers/canonType.d.ts +3 -1
- package/dist/helpers/canonType.js +44 -1
- package/dist/helpers/canonType.js.map +1 -1
- package/dist/helpers/packageMeta.js +7 -0
- package/dist/helpers/packageMeta.js.map +1 -1
- package/dist/sdkgen.d.ts +3 -3
- package/dist/sdkgen.js +4 -1
- package/dist/sdkgen.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +8 -8
- package/project/.sdk/model/feature/paging.aontu +7 -0
- package/project/.sdk/model/target/py-data.aontu +64 -0
- package/project/.sdk/src/cmp/c/fragment/Main.fragment.c +89 -1
- package/project/.sdk/src/cmp/clojure/Main_clojure.ts +1 -0
- package/project/.sdk/src/cmp/csharp/fragment/Main.fragment.cs +97 -0
- package/project/.sdk/src/cmp/dart/fragment/Main.fragment.dart +78 -0
- package/project/.sdk/src/cmp/elixir/fragment/Main.fragment.ex +84 -0
- package/project/.sdk/src/cmp/go/fragment/Main.fragment.go +87 -0
- package/project/.sdk/src/cmp/js/fragment/Main.fragment.js +75 -0
- package/project/.sdk/src/cmp/lua/fragment/Main.fragment.lua +85 -0
- package/project/.sdk/src/cmp/ocaml/Main_ocaml.ts +4 -0
- package/project/.sdk/src/cmp/perl/fragment/Main.fragment.pm +80 -0
- package/project/.sdk/src/cmp/php/fragment/Main.fragment.php +86 -0
- package/project/.sdk/src/cmp/py/Entity_py.ts +9 -2
- package/project/.sdk/src/cmp/py/MainEntity_py.ts +1 -1
- package/project/.sdk/src/cmp/py/Main_py.ts +34 -6
- package/project/.sdk/src/cmp/py/Package_py.ts +6 -9
- package/project/.sdk/src/cmp/py/TestDirect_py.ts +2 -2
- package/project/.sdk/src/cmp/py/TestEntity_py.ts +3 -3
- package/project/.sdk/src/cmp/py/fragment/Entity.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/EntityCreateOp.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/EntityLoadOp.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/EntityRemoveOp.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/EntityUpdateOp.fragment.py +2 -2
- package/project/.sdk/src/cmp/py/fragment/Main.fragment.py +75 -8
- package/project/.sdk/src/cmp/py-data/Main_py-data.ts +987 -0
- package/project/.sdk/src/cmp/rb/fragment/Main.fragment.rb +71 -0
- package/project/.sdk/src/cmp/rust/fragment/Main.fragment.rs +95 -0
- package/project/.sdk/src/cmp/swift/fragment/Main.fragment.swift +86 -0
- package/project/.sdk/src/cmp/ts/fragment/Main.fragment.ts +75 -0
- package/project/.sdk/src/cmp/zig/fragment/Main.fragment.zig +95 -0
- package/project/.sdk/tm/c/core/sdk.h +7 -0
- package/project/.sdk/tm/c/feature/paging.c +141 -2
- package/project/.sdk/tm/c/utility/graphql.c +178 -0
- package/project/.sdk/tm/c/utility/make_options.c +1 -1
- package/project/.sdk/tm/c/utility/make_response.c +6 -0
- package/project/.sdk/tm/c/utility/make_spec.c +23 -3
- package/project/.sdk/tm/clojure/src/sdk/client.clj +64 -1
- package/project/.sdk/tm/clojure/src/sdk/core.clj +126 -3
- package/project/.sdk/tm/clojure/src/sdk/features.clj +80 -14
- package/project/.sdk/tm/cpp/core/types.hpp +83 -0
- package/project/.sdk/tm/cpp/feature/paging.hpp +103 -1
- package/project/.sdk/tm/cpp/utility/pipeline.hpp +152 -3
- package/project/.sdk/tm/csharp/core/Utility.cs +4 -0
- package/project/.sdk/tm/csharp/feature/PagingFeature.cs +118 -1
- package/project/.sdk/tm/csharp/utility/Graphql.cs +190 -0
- package/project/.sdk/tm/csharp/utility/MakeOptions.cs +1 -1
- package/project/.sdk/tm/csharp/utility/MakeResponse.cs +7 -0
- package/project/.sdk/tm/csharp/utility/MakeSpec.cs +21 -2
- package/project/.sdk/tm/csharp/utility/Register.cs +2 -0
- package/project/.sdk/tm/dart/lib/Point.dart +7 -0
- package/project/.sdk/tm/dart/lib/feature/paging/PagingFeature.dart +99 -4
- package/project/.sdk/tm/dart/lib/utility/GraphqlUtility.dart +162 -0
- package/project/.sdk/tm/dart/lib/utility/MakeOptionsUtility.dart +1 -1
- package/project/.sdk/tm/dart/lib/utility/MakeResponseUtility.dart +5 -0
- package/project/.sdk/tm/dart/lib/utility/MakeSpecUtility.dart +18 -2
- package/project/.sdk/tm/dart/lib/utility/PrepareMethodUtility.dart +6 -1
- package/project/.sdk/tm/dart/lib/utility/Utility.dart +9 -0
- package/project/.sdk/tm/elixir/lib/projectname/feature/paging.ex +136 -13
- package/project/.sdk/tm/elixir/lib/projectname/utility.ex +187 -3
- package/project/.sdk/tm/go/core/utility_type.go +2 -0
- package/project/.sdk/tm/go/feature/paging_feature.go +87 -1
- package/project/.sdk/tm/go/utility/graphql.go +168 -0
- package/project/.sdk/tm/go/utility/make_options.go +1 -1
- package/project/.sdk/tm/go/utility/make_response.go +6 -0
- package/project/.sdk/tm/go/utility/make_spec.go +19 -2
- package/project/.sdk/tm/go/utility/register.go +2 -0
- package/project/.sdk/tm/haskell/src/SdkFeatures.hs +146 -7
- package/project/.sdk/tm/haskell/src/SdkRuntime.hs +153 -3
- package/project/.sdk/tm/java/core/SdkClient.java +93 -0
- package/project/.sdk/tm/java/core/Utility.java +4 -0
- package/project/.sdk/tm/java/feature/PagingFeature.java +100 -1
- package/project/.sdk/tm/java/utility/Graphql.java +191 -0
- package/project/.sdk/tm/java/utility/MakeOptions.java +1 -1
- package/project/.sdk/tm/java/utility/MakeResponse.java +6 -0
- package/project/.sdk/tm/java/utility/MakeSpec.java +17 -2
- package/project/.sdk/tm/java/utility/Register.java +2 -0
- package/project/.sdk/tm/js/src/Point.js +5 -0
- package/project/.sdk/tm/js/src/feature/paging/PagingFeature.js +82 -3
- package/project/.sdk/tm/js/src/feature/test/TestFeature.js +14 -2
- package/project/.sdk/tm/js/src/utility/GraphqlUtility.js +148 -0
- package/project/.sdk/tm/js/src/utility/MakeOptionsUtility.js +1 -1
- package/project/.sdk/tm/js/src/utility/MakeResponseUtility.js +7 -0
- package/project/.sdk/tm/js/src/utility/MakeSpecUtility.js +19 -2
- package/project/.sdk/tm/js/src/utility/Utility.js +4 -0
- package/project/.sdk/tm/kotlin/core/SdkClient.kt +84 -0
- package/project/.sdk/tm/kotlin/core/Utility.kt +2 -0
- package/project/.sdk/tm/kotlin/feature/PagingFeature.kt +92 -1
- package/project/.sdk/tm/kotlin/utility/Graphql.kt +145 -0
- package/project/.sdk/tm/kotlin/utility/MakeOptions.kt +1 -1
- package/project/.sdk/tm/kotlin/utility/MakeReqRes.kt +6 -0
- package/project/.sdk/tm/kotlin/utility/MakeSpecUrl.kt +17 -2
- package/project/.sdk/tm/kotlin/utility/Register.kt +2 -0
- package/project/.sdk/tm/lean/src/SdkUtility.lean +134 -1
- package/project/.sdk/tm/lua/feature/paging_feature.lua +97 -2
- package/project/.sdk/tm/lua/utility/graphql.lua +163 -0
- package/project/.sdk/tm/lua/utility/make_options.lua +1 -1
- package/project/.sdk/tm/lua/utility/make_response.lua +6 -0
- package/project/.sdk/tm/lua/utility/make_spec.lua +18 -2
- package/project/.sdk/tm/lua/utility/register.lua +3 -0
- package/project/.sdk/tm/ocaml/sdk_features.ml +164 -20
- package/project/.sdk/tm/ocaml/sdk_runtime.ml +143 -4
- package/project/.sdk/tm/ocaml/sdk_types.ml +2 -0
- package/project/.sdk/tm/perl/feature/paging_feature.pm +91 -3
- package/project/.sdk/tm/perl/utility/graphql.pm +150 -0
- package/project/.sdk/tm/perl/utility/make_options.pm +1 -1
- package/project/.sdk/tm/perl/utility/make_response.pm +6 -0
- package/project/.sdk/tm/perl/utility/make_spec.pm +20 -2
- package/project/.sdk/tm/perl/utility/register.pm +1 -0
- package/project/.sdk/tm/php/core/UtilityType.php +2 -0
- package/project/.sdk/tm/php/feature/PagingFeature.php +106 -4
- package/project/.sdk/tm/php/utility/Graphql.php +176 -0
- package/project/.sdk/tm/php/utility/MakeOptions.php +1 -1
- package/project/.sdk/tm/php/utility/MakeResponse.php +7 -0
- package/project/.sdk/tm/php/utility/MakeSpec.php +20 -2
- package/project/.sdk/tm/php/utility/Register.php +3 -0
- package/project/.sdk/tm/py/pkg/core/__init__.py +0 -0
- package/project/.sdk/tm/py/{core → pkg/core}/context.py +8 -8
- package/project/.sdk/tm/py/{core → pkg/core}/operation.py +1 -1
- package/project/.sdk/tm/py/{core → pkg/core}/response.py +1 -1
- package/project/.sdk/tm/py/{core → pkg/core}/result.py +1 -1
- package/project/.sdk/tm/py/{core → pkg/core}/utility_type.py +2 -0
- package/project/.sdk/tm/py/pkg/entity/__init__.py +0 -0
- package/project/.sdk/tm/py/pkg/feature/__init__.py +0 -0
- package/project/.sdk/tm/py/{feature → pkg/feature}/audit_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/cache_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/clienttrack_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/debug_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/idempotency_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/log_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/metrics_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/netsim_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/paging_feature.py +87 -6
- package/project/.sdk/tm/py/{feature → pkg/feature}/proxy_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/ratelimit_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/rbac_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/retry_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/streaming_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/telemetry_feature.py +1 -1
- package/project/.sdk/tm/py/{feature → pkg/feature}/test_feature.py +2 -2
- package/project/.sdk/tm/py/{feature → pkg/feature}/timeout_feature.py +1 -1
- package/project/.sdk/tm/py/pkg/utility/__init__.py +0 -0
- package/project/.sdk/tm/py/{utility → pkg/utility}/feature_init.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/fetcher.py +1 -1
- package/project/.sdk/tm/py/pkg/utility/graphql.py +145 -0
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_context.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_error.py +5 -5
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_fetch_def.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_options.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_point.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_request.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_response.py +6 -0
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_spec.py +19 -4
- package/project/.sdk/tm/py/{utility → pkg/utility}/make_url.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/param.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_auth.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_headers.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_method.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_params.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_path.py +1 -1
- package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_query.py +1 -1
- package/project/.sdk/tm/py/pkg/utility/register.py +71 -0
- package/project/.sdk/tm/py/{utility → pkg/utility}/transform_request.py +2 -2
- package/project/.sdk/tm/py/{utility → pkg/utility}/transform_response.py +2 -2
- package/project/.sdk/tm/py/test/feature_harness.py +6 -6
- package/project/.sdk/tm/py/test/runner.py +1 -1
- package/project/.sdk/tm/py/test/struct_runner.py +1 -1
- package/project/.sdk/tm/py/test/test_pipeline.py +5 -5
- package/project/.sdk/tm/py/test/test_primary_utility.py +8 -8
- package/project/.sdk/tm/py/test/test_struct_utility.py +3 -3
- package/project/.sdk/tm/py-data/LICENSE +22 -0
- package/project/.sdk/tm/py-data/Makefile +57 -0
- package/project/.sdk/tm/py-data/pkg/auth.py +131 -0
- package/project/.sdk/tm/py-data/pkg/fetch.py +178 -0
- package/project/.sdk/tm/py-data/pkg/frames.py +236 -0
- package/project/.sdk/tm/py-data/src/feature/README.md +8 -0
- package/project/.sdk/tm/py-data/src/feature/debug/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/idempotency/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/log/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/metrics/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/netsim/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/paging/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/proxy/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/ratelimit/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/rbac/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/retry/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/streaming/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/telemetry/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/test/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/src/feature/timeout/.gitkeep +0 -0
- package/project/.sdk/tm/py-data/test/__init__.py +0 -0
- package/project/.sdk/tm/py-data/test/test_auth.py +95 -0
- package/project/.sdk/tm/py-data/test/test_fetch.py +152 -0
- package/project/.sdk/tm/py-data/test/test_frames.py +250 -0
- package/project/.sdk/tm/rb/core/utility_type.rb +1 -0
- package/project/.sdk/tm/rb/feature/paging_feature.rb +89 -3
- package/project/.sdk/tm/rb/utility/graphql.rb +131 -0
- package/project/.sdk/tm/rb/utility/make_options.rb +1 -1
- package/project/.sdk/tm/rb/utility/make_response.rb +6 -0
- package/project/.sdk/tm/rb/utility/make_spec.rb +18 -2
- package/project/.sdk/tm/rb/utility/register.rb +3 -0
- package/project/.sdk/tm/rust/feature/paging.rs +124 -2
- package/project/.sdk/tm/rust/utility/graphql.rs +193 -0
- package/project/.sdk/tm/rust/utility/make_options.rs +1 -1
- package/project/.sdk/tm/rust/utility/make_response.rs +6 -0
- package/project/.sdk/tm/rust/utility/make_spec.rs +32 -4
- package/project/.sdk/tm/rust/utility/mod.rs +1 -0
- package/project/.sdk/tm/scala/core/SdkClient.scala +90 -0
- package/project/.sdk/tm/scala/core/Utility.scala +4 -0
- package/project/.sdk/tm/scala/feature/PagingFeature.scala +104 -3
- package/project/.sdk/tm/scala/utility/Graphql.scala +160 -0
- package/project/.sdk/tm/scala/utility/Make.scala +7 -1
- package/project/.sdk/tm/scala/utility/MakeSpec.scala +22 -2
- package/project/.sdk/tm/scala/utility/Register.scala +2 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/core/Utility.swift +4 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/feature/PagingFeature.swift +86 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Graphql.swift +142 -0
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Make.swift +24 -2
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/MakeOptions.swift +1 -1
- package/project/.sdk/tm/swift/Sources/ProjectNameSDK/utility/Register.swift +2 -0
- package/project/.sdk/tm/ts/src/Point.ts +7 -0
- package/project/.sdk/tm/ts/src/feature/paging/PagingFeature.ts +82 -3
- package/project/.sdk/tm/ts/src/feature/test/TestFeature.ts +14 -2
- package/project/.sdk/tm/ts/src/utility/GraphqlUtility.ts +152 -0
- package/project/.sdk/tm/ts/src/utility/MakeOptionsUtility.ts +1 -1
- package/project/.sdk/tm/ts/src/utility/MakeResponseUtility.ts +7 -0
- package/project/.sdk/tm/ts/src/utility/MakeSpecUtility.ts +19 -2
- package/project/.sdk/tm/ts/src/utility/Utility.ts +4 -0
- package/project/.sdk/tm/zig/core/utility.zig +198 -3
- package/project/.sdk/tm/zig/feature/paging.zig +128 -1
- package/src/cmp/AgentGuideContent.ts +5 -0
- package/src/cmp/Feature.ts +13 -0
- package/src/helpers/canonType.ts +46 -0
- package/src/helpers/packageMeta.ts +7 -0
- package/src/sdkgen.ts +6 -1
- package/project/.sdk/tm/py/utility/register.py +0 -68
- /package/project/.sdk/tm/py/{core → pkg/core}/control.py +0 -0
- /package/project/.sdk/tm/py/{core → pkg/core}/error.py +0 -0
- /package/project/.sdk/tm/py/{core → pkg/core}/helpers.py +0 -0
- /package/project/.sdk/tm/py/{core → pkg/core}/spec.py +0 -0
- /package/project/.sdk/tm/py/{feature → pkg/feature}/base_feature.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/clean.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/done.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/feature_add.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/feature_hook.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/make_result.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/prepare_body.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/result_basic.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/result_body.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/result_headers.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/voxgig_struct/__init__.py +0 -0
- /package/project/.sdk/tm/py/{utility → pkg/utility}/voxgig_struct/voxgig_struct.py +0 -0
- /package/project/.sdk/tm/{py/core/__init__.py → py-data/src/feature/audit/.gitkeep} +0 -0
- /package/project/.sdk/tm/{py/entity/__init__.py → py-data/src/feature/base/.gitkeep} +0 -0
- /package/project/.sdk/tm/{py/feature/__init__.py → py-data/src/feature/cache/.gitkeep} +0 -0
- /package/project/.sdk/tm/{py/utility/__init__.py → py-data/src/feature/clienttrack/.gitkeep} +0 -0
|
@@ -0,0 +1,987 @@
|
|
|
1
|
+
|
|
2
|
+
import {
|
|
3
|
+
cmp, each,
|
|
4
|
+
File, Content, Copy, Folder,
|
|
5
|
+
entityCollection, entityOps, entityIdField, canonToDtype,
|
|
6
|
+
collectDeps, pkgDescription, keywords, repoInfo, envName, apiName,
|
|
7
|
+
packageName, registryState, serverVariables,
|
|
8
|
+
SdkGenError,
|
|
9
|
+
} from '@voxgig/sdkgen'
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
KIT,
|
|
13
|
+
} from '@voxgig/apidef'
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
// The `py-data` target: a Python package for DATA ANALYSTS working in
|
|
17
|
+
// notebooks (Colab, Jupyter), layered on the sibling `py` SDK generated into
|
|
18
|
+
// the same repo. It is a consumer target in the go-cli / go-mcp mould — every
|
|
19
|
+
// standard generation phase is switched off in model/target/py-data.aontu and
|
|
20
|
+
// this component emits the whole package.
|
|
21
|
+
//
|
|
22
|
+
// The deliverable an analyst wants is not "a client", it is TABLES: one line
|
|
23
|
+
// from API to a typed pandas DataFrame, discovered by tab-completion rather
|
|
24
|
+
// than by reading reference docs.
|
|
25
|
+
//
|
|
26
|
+
// Layered, NOT forked from `py`: the dependency arrow points one way
|
|
27
|
+
// (py-data -> py) and `py` must never learn this target exists. A fork would
|
|
28
|
+
// duplicate the entity/feature machinery and drift, which is the known
|
|
29
|
+
// failure mode.
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
// Accessors are generated only for entities exposing these ops. `list` gives
|
|
33
|
+
// a DataFrame; `load` gives a Series. Everything else (create/update/remove)
|
|
34
|
+
// is a write path and deliberately out of scope — see the README's
|
|
35
|
+
// "What this package does not do".
|
|
36
|
+
const FRAME_OP = 'list'
|
|
37
|
+
const SERIES_OP = 'load'
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
const Main = cmp(function Main(props: any) {
|
|
41
|
+
const { target, ctx$ } = props
|
|
42
|
+
const { model, log } = ctx$
|
|
43
|
+
|
|
44
|
+
// HARD REQUIREMENT: this package imports the sibling py SDK. Generating it
|
|
45
|
+
// without `py` produces a package whose every import fails at runtime, so
|
|
46
|
+
// fail at GENERATE time with an actionable message instead.
|
|
47
|
+
const targets = model.main[KIT].target || {}
|
|
48
|
+
if (null == targets.py) {
|
|
49
|
+
throw new SdkGenError(
|
|
50
|
+
'py-data requires the `py` target in the same SDK: it imports the ' +
|
|
51
|
+
'Python SDK that `py` generates. Add it with:\n' +
|
|
52
|
+
' npm run add-target py\n' +
|
|
53
|
+
'then regenerate.')
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const name = model.const.Name // PascalCase, e.g. Univec
|
|
57
|
+
const lower = String(name).toLowerCase() // univec
|
|
58
|
+
const ENV = envName(model) // UNIVEC
|
|
59
|
+
const sdkModule = `${lower}_sdk` // univec_sdk
|
|
60
|
+
const sdkClass = `${name}SDK` // UnivecSDK
|
|
61
|
+
const dataModule = `${lower}_data` // univec_data
|
|
62
|
+
|
|
63
|
+
// Multi-tenant APIs (a server URL carrying an unresolved {variable}, e.g.
|
|
64
|
+
// https://{instance}.example.com/api) have no single host, so base_url
|
|
65
|
+
// becomes REQUIRED rather than optional. Guessing a host produces confusing
|
|
66
|
+
// 404s, so `data()` refuses instead.
|
|
67
|
+
const svars = serverVariables(model)
|
|
68
|
+
const needsBase = svars.some((v: any) => v.required)
|
|
69
|
+
|
|
70
|
+
const ents = each(entityCollection(model))
|
|
71
|
+
.filter((e: any) => e && null != e.name && false !== e.active)
|
|
72
|
+
|
|
73
|
+
const frameEnts = ents.filter((e: any) => entityOps(e).includes(FRAME_OP))
|
|
74
|
+
const seriesEnts = ents.filter((e: any) =>
|
|
75
|
+
entityOps(e).includes(SERIES_OP) && null != entityIdField(e))
|
|
76
|
+
|
|
77
|
+
log.info({
|
|
78
|
+
point: 'py-data-entities', target: target.name,
|
|
79
|
+
note: `frames:${frameEnts.length} series:${seriesEnts.length} of ${ents.length}`,
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
// Root-level statics: Makefile, LICENSE, the template-side tests.
|
|
83
|
+
Copy({
|
|
84
|
+
from: 'tm/' + target.name,
|
|
85
|
+
exclude: [/src\//, /pkg\//],
|
|
86
|
+
replace: { ...ctx$.stdrep },
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
// The runtime modules live INSIDE the package, not at the top level.
|
|
90
|
+
//
|
|
91
|
+
// They were originally emitted flat as auth.py / fetch.py / frames.py, which
|
|
92
|
+
// is actively dangerous here: `auth`, `fetch`, `frames`, `core`, `entity`
|
|
93
|
+
// and `utility` are all real PyPI distributions AND exactly the names an
|
|
94
|
+
// analyst leaves lying around next to a notebook. In Colab the working
|
|
95
|
+
// directory is on sys.path, so one stray auth.py shadowed ours and `data()`
|
|
96
|
+
// died with `module 'auth' has no attribute 'resolve'`. A package keeps
|
|
97
|
+
// every internal module behind the distinctive `<name>_data.` prefix.
|
|
98
|
+
//
|
|
99
|
+
// The folder name is model-derived, so it comes from the component; the file
|
|
100
|
+
// contents are the same for every API, so they stay in tm/py-data/pkg.
|
|
101
|
+
Folder({ name: dataModule }, () => {
|
|
102
|
+
Copy({
|
|
103
|
+
from: 'tm/' + target.name + '/pkg',
|
|
104
|
+
replace: { ...ctx$.stdrep },
|
|
105
|
+
})
|
|
106
|
+
DataClient({ model, name, lower, ENV, sdkModule, sdkClass, dataModule, needsBase })
|
|
107
|
+
EntityFrames({ model, frameEnts, seriesEnts, lower, dataModule })
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
Gitignore({})
|
|
111
|
+
Package({ target, model, lower, dataModule })
|
|
112
|
+
EntityFramesTest({ frameEnts, seriesEnts, lower, sdkModule, sdkClass, dataModule })
|
|
113
|
+
Readme({ model, name, lower, ENV, dataModule, frameEnts, seriesEnts, needsBase, svars })
|
|
114
|
+
AgentGuide({ model, name, lower, ENV, dataModule, frameEnts, seriesEnts, needsBase })
|
|
115
|
+
Quickstart({ model, name, lower, ENV, dataModule, frameEnts, needsBase })
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
const Gitignore = cmp(function Gitignore(_props: any) {
|
|
120
|
+
File({ name: '.gitignore' }, () => Content(`__pycache__/
|
|
121
|
+
*.py[cod]
|
|
122
|
+
/dist/
|
|
123
|
+
/build/
|
|
124
|
+
*.egg-info/
|
|
125
|
+
.pytest_cache/
|
|
126
|
+
.ipynb_checkpoints/
|
|
127
|
+
`))
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
const Package = cmp(function Package(props: any) {
|
|
132
|
+
const { target, model, lower, dataModule } = props
|
|
133
|
+
const ctx$ = props.ctx$
|
|
134
|
+
|
|
135
|
+
const ns = model.origin || 'voxgig-sdk'
|
|
136
|
+
const pkgBase = ns.endsWith('-sdk') ? model.name : `${model.name}-sdk`
|
|
137
|
+
const distName = `${ns}-${pkgBase}-data`
|
|
138
|
+
const sdkDist = packageName(model, 'py')
|
|
139
|
+
const { repoUrl, issuesUrl } = repoInfo(model)
|
|
140
|
+
const kw = keywords(model).concat(['pandas', 'dataframe', 'notebook', 'colab'])
|
|
141
|
+
.map((k) => `"${k}"`).join(', ')
|
|
142
|
+
|
|
143
|
+
File({ name: 'pyproject.toml' }, () => {
|
|
144
|
+
Content(`[build-system]
|
|
145
|
+
requires = ["setuptools>=61.0"]
|
|
146
|
+
build-backend = "setuptools.build_meta"
|
|
147
|
+
|
|
148
|
+
[project]
|
|
149
|
+
name = "${distName}"
|
|
150
|
+
version = "0.0.1"
|
|
151
|
+
description = "${pkgDescription(model, 'py-data')}"
|
|
152
|
+
readme = "README.md"
|
|
153
|
+
license = "MIT"
|
|
154
|
+
requires-python = ">=3.9"
|
|
155
|
+
keywords = [${kw}]
|
|
156
|
+
dependencies = [
|
|
157
|
+
"${sdkDist}",
|
|
158
|
+
`)
|
|
159
|
+
|
|
160
|
+
// pandas (and anything a feature adds) from the target model. The sibling
|
|
161
|
+
// SDK dep is emitted above rather than declared in the aontu, because its
|
|
162
|
+
// distribution name is derived from the model, not fixed.
|
|
163
|
+
const seen = new Set<string>([sdkDist])
|
|
164
|
+
for (const d of collectDeps(model, target.name, target.deps, ctx$.log)) {
|
|
165
|
+
if (seen.has(d.name)) continue
|
|
166
|
+
seen.add(d.name)
|
|
167
|
+
const v = d.source === 'target' ? (d.version || '0.0') : d.version
|
|
168
|
+
Content(` "${d.name}>=${v}",
|
|
169
|
+
`)
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
Content(`]
|
|
173
|
+
|
|
174
|
+
[project.urls]
|
|
175
|
+
Homepage = "${repoUrl}"
|
|
176
|
+
Repository = "${repoUrl}"
|
|
177
|
+
Issues = "${issuesUrl}"
|
|
178
|
+
|
|
179
|
+
# A single package, deliberately. Emitting auth/fetch/frames as top-level
|
|
180
|
+
# modules would put names that are both real PyPI distributions and common
|
|
181
|
+
# notebook scratch files onto sys.path, where Colab's working directory wins.
|
|
182
|
+
[tool.setuptools.packages.find]
|
|
183
|
+
include = ["${dataModule}*"]
|
|
184
|
+
`)
|
|
185
|
+
})
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
// The DataClient module: `data()` plus the wrapper that owns credential
|
|
190
|
+
// discovery and exposes the raw SDK as an escape hatch.
|
|
191
|
+
const DataClient = cmp(function DataClient(props: any) {
|
|
192
|
+
const { name, ENV, sdkModule, sdkClass, dataModule, needsBase } = props
|
|
193
|
+
|
|
194
|
+
const baseCheck = needsBase
|
|
195
|
+
? `
|
|
196
|
+
if not base_url:
|
|
197
|
+
raise ValueError(auth.missing_base_message("${ENV}"))
|
|
198
|
+
`
|
|
199
|
+
: ''
|
|
200
|
+
|
|
201
|
+
File({ name: '__init__.py' }, () => Content(`# ${name} Data — notebook-friendly access to the ${name} API.
|
|
202
|
+
#
|
|
203
|
+
# GENERATED by @voxgig/sdkgen (py-data target). Edits are overwritten.
|
|
204
|
+
|
|
205
|
+
from __future__ import annotations
|
|
206
|
+
|
|
207
|
+
from typing import Any
|
|
208
|
+
|
|
209
|
+
import pandas as pd
|
|
210
|
+
|
|
211
|
+
from . import auth
|
|
212
|
+
from .entity_frames import EntityFrames
|
|
213
|
+
from .frames import records_df
|
|
214
|
+
|
|
215
|
+
# The sibling SDK is a separate distribution, so this import stays absolute.
|
|
216
|
+
from ${sdkModule} import ${sdkClass}
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class ${name}Data(EntityFrames):
|
|
220
|
+
"""${name} API as pandas DataFrames.
|
|
221
|
+
|
|
222
|
+
Wraps — never subclasses — the ${sdkClass} client. The wrapped client is
|
|
223
|
+
available as \`.sdk\` for the cases this package deliberately does not
|
|
224
|
+
cover (writes, non-entity endpoints, per-call control).
|
|
225
|
+
"""
|
|
226
|
+
|
|
227
|
+
def __init__(self, sdk: ${sdkClass}, base_url: str | None = None):
|
|
228
|
+
self.sdk = sdk
|
|
229
|
+
self.base_url = base_url
|
|
230
|
+
|
|
231
|
+
# ---- escape hatches ---------------------------------------------------
|
|
232
|
+
|
|
233
|
+
def frame(self, result: Any, **kwargs) -> pd.DataFrame:
|
|
234
|
+
"""Shape ANY op result into a DataFrame.
|
|
235
|
+
|
|
236
|
+
For endpoints without a generated accessor:
|
|
237
|
+
|
|
238
|
+
rows = ad.sdk.SomeEntity().list()
|
|
239
|
+
df = ad.frame(rows)
|
|
240
|
+
"""
|
|
241
|
+
return records_df(result, **kwargs)
|
|
242
|
+
|
|
243
|
+
def entities(self) -> list:
|
|
244
|
+
"""The entity accessors available on this object."""
|
|
245
|
+
return sorted(self._ACCESSORS)
|
|
246
|
+
|
|
247
|
+
def _repr_html_(self) -> str:
|
|
248
|
+
accessors = "".join(
|
|
249
|
+
f"<li><code>{a}()</code></li>" for a in sorted(self._ACCESSORS))
|
|
250
|
+
base = self.base_url or "(default)"
|
|
251
|
+
return (
|
|
252
|
+
f"<b>${name}Data</b><br>"
|
|
253
|
+
f"<small>base: <code>{base}</code></small>"
|
|
254
|
+
f"<ul>{accessors}</ul>"
|
|
255
|
+
)
|
|
256
|
+
|
|
257
|
+
def __repr__(self) -> str:
|
|
258
|
+
return (f"<${name}Data base={self.base_url or '(default)'} "
|
|
259
|
+
f"accessors={len(self._ACCESSORS)}>")
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def data(
|
|
263
|
+
token: str | None = None,
|
|
264
|
+
base_url: str | None = None,
|
|
265
|
+
**options,
|
|
266
|
+
) -> ${name}Data:
|
|
267
|
+
"""Build a ${name}Data client, finding credentials automatically.
|
|
268
|
+
|
|
269
|
+
Discovery order (first hit wins):
|
|
270
|
+
1. the \`token\` / \`base_url\` arguments
|
|
271
|
+
2. Colab secrets — ${ENV}_APIKEY / ${ENV}_BASE
|
|
272
|
+
3. environment — ${ENV}_APIKEY / ${ENV}_BASE
|
|
273
|
+
|
|
274
|
+
Extra keyword arguments are passed straight through to the SDK client, so
|
|
275
|
+
anything the SDK accepts (feature options, headers, timeouts) works here.
|
|
276
|
+
"""
|
|
277
|
+
token, base_url, _source = auth.resolve("${ENV}", token, base_url)
|
|
278
|
+
|
|
279
|
+
if not token:
|
|
280
|
+
raise ValueError(auth.missing_token_message("${ENV}"))
|
|
281
|
+
${baseCheck}
|
|
282
|
+
opts: dict = {"apikey": token}
|
|
283
|
+
if base_url:
|
|
284
|
+
opts["base"] = base_url
|
|
285
|
+
opts.update(options)
|
|
286
|
+
|
|
287
|
+
return ${name}Data(${sdkClass}(opts), base_url)
|
|
288
|
+
`))
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
// The generated per-entity surface. One DataFrame accessor per entity with a
|
|
293
|
+
// `list` op; one Series accessor per entity with a keyed `load`.
|
|
294
|
+
const EntityFrames = cmp(function EntityFrames(props: any) {
|
|
295
|
+
const { frameEnts, seriesEnts, lower } = props
|
|
296
|
+
|
|
297
|
+
const accessorNames = frameEnts.map((e: any) => pluralAccessor(e))
|
|
298
|
+
.concat(seriesEnts.map((e: any) => singularAccessor(e)))
|
|
299
|
+
|
|
300
|
+
File({ name: 'entity_frames.py' }, () => {
|
|
301
|
+
Content(`# ${lower} data — generated entity accessors.
|
|
302
|
+
#
|
|
303
|
+
# GENERATED by @voxgig/sdkgen (py-data target). Edits are overwritten.
|
|
304
|
+
#
|
|
305
|
+
# One accessor per entity. Column names and dtypes come from the API model,
|
|
306
|
+
# so \`ad.<tab>\` and \`ad.thing?\` replace a trip to the reference docs.
|
|
307
|
+
|
|
308
|
+
from __future__ import annotations
|
|
309
|
+
|
|
310
|
+
import pandas as pd
|
|
311
|
+
|
|
312
|
+
from .fetch import drain, MAX_PAGES_DEFAULT
|
|
313
|
+
from .frames import build_frame, to_series, FLATTEN_DEFAULT
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
class EntityFrames:
|
|
317
|
+
"""Mixin carrying the generated accessors. Mixed into <Name>Data."""
|
|
318
|
+
|
|
319
|
+
_ACCESSORS = ${pyList(accessorNames, ' ')}
|
|
320
|
+
`)
|
|
321
|
+
|
|
322
|
+
for (const ent of frameEnts) {
|
|
323
|
+
Content(frameAccessor(ent))
|
|
324
|
+
}
|
|
325
|
+
for (const ent of seriesEnts) {
|
|
326
|
+
Content(seriesAccessor(ent))
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// An SDK whose entities expose no list/load ops still needs a valid,
|
|
330
|
+
// importable module — better an honest empty mixin than a syntax error.
|
|
331
|
+
if (0 === accessorNames.length) {
|
|
332
|
+
Content(`
|
|
333
|
+
# This API exposes no list or load operations, so there are no frame
|
|
334
|
+
# accessors. Use ad.sdk for its write operations and ad.frame(result)
|
|
335
|
+
# to shape anything they return.
|
|
336
|
+
pass
|
|
337
|
+
`)
|
|
338
|
+
}
|
|
339
|
+
})
|
|
340
|
+
})
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
// ---------------------------------------------------------------------------
|
|
344
|
+
// Accessor emission
|
|
345
|
+
// ---------------------------------------------------------------------------
|
|
346
|
+
|
|
347
|
+
// Field metadata for an entity, as the (name, dtype) pairs the runtime needs
|
|
348
|
+
// and the doc table the docstring shows.
|
|
349
|
+
function entFields(ent: any): { name: string, dtype: string, req: boolean }[] {
|
|
350
|
+
return each(ent.fields)
|
|
351
|
+
.filter((f: any) => f && null != f.name && false !== f.active)
|
|
352
|
+
.map((f: any) => ({
|
|
353
|
+
name: String(f.name),
|
|
354
|
+
dtype: canonToDtype(f.type),
|
|
355
|
+
req: true === f.req,
|
|
356
|
+
}))
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
function pluralAccessor(ent: any): string {
|
|
361
|
+
const n = pySafe(String(ent.name).toLowerCase())
|
|
362
|
+
// A frame of many rows is plural by nature. Only pluralise when it does not
|
|
363
|
+
// already look plural — 'addresses' must not become 'addresseses'.
|
|
364
|
+
if (/s$/.test(n)) return n
|
|
365
|
+
if (/(x|ch|sh|ss)$/.test(n)) return n + 'es'
|
|
366
|
+
if (/[^aeiou]y$/.test(n)) return n.slice(0, -1) + 'ies'
|
|
367
|
+
return n + 's'
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
function singularAccessor(ent: any): string {
|
|
372
|
+
return pySafe(String(ent.name).toLowerCase())
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
// Python keywords + soft-reserved names an accessor must not collide with.
|
|
377
|
+
const PY_RESERVED = new Set([
|
|
378
|
+
'False', 'None', 'True', 'and', 'as', 'assert', 'async', 'await', 'break',
|
|
379
|
+
'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally',
|
|
380
|
+
'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal',
|
|
381
|
+
'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield',
|
|
382
|
+
// Would shadow the surface EntityFrames/<Name>Data already provide.
|
|
383
|
+
'sdk', 'frame', 'entities', 'data',
|
|
384
|
+
])
|
|
385
|
+
|
|
386
|
+
function pySafe(n: string): string {
|
|
387
|
+
const clean = n.replace(/[^A-Za-z0-9_]/g, '_')
|
|
388
|
+
const safe = /^[0-9]/.test(clean) ? '_' + clean : clean
|
|
389
|
+
return PY_RESERVED.has(safe) ? safe + '_' : safe
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
// Python literal emitters. `close` is the indent of the CLOSING bracket (the
|
|
394
|
+
// indent of the line the literal is an argument on); members sit one level
|
|
395
|
+
// deeper. Passing it explicitly keeps generated Python correctly nested
|
|
396
|
+
// instead of needing a re-indent pass over the rendered string.
|
|
397
|
+
function pyList(items: string[], close = ''): string {
|
|
398
|
+
if (0 === items.length) return '[]'
|
|
399
|
+
const member = close + ' '
|
|
400
|
+
return '[\n' + items.map((i) => `${member}"${i}",`).join('\n') + `\n${close}]`
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
function pyDict(pairs: [string, string][], close = ''): string {
|
|
405
|
+
if (0 === pairs.length) return '{}'
|
|
406
|
+
const member = close + ' '
|
|
407
|
+
return '{\n' +
|
|
408
|
+
pairs.map(([k, v]) => `${member}"${k}": "${v}",`).join('\n') +
|
|
409
|
+
`\n${close}}`
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
// The column table an analyst reads via `ad.things?`. Truncated because a
|
|
414
|
+
// 200-field entity's docstring is not a reference doc — the README has the
|
|
415
|
+
// full table, and `df.dtypes` always tells the truth.
|
|
416
|
+
const DOC_FIELD_MAX = 24
|
|
417
|
+
|
|
418
|
+
function fieldDoc(fields: any[], indent: string): string {
|
|
419
|
+
if (0 === fields.length) {
|
|
420
|
+
return `${indent}No fields are declared for this entity in the API model;\n` +
|
|
421
|
+
`${indent}the frame's columns come from whatever the API returns.`
|
|
422
|
+
}
|
|
423
|
+
const shown = fields.slice(0, DOC_FIELD_MAX)
|
|
424
|
+
const lines = shown.map((f) =>
|
|
425
|
+
`${indent} ${f.name} (${f.dtype}${f.req ? ', required' : ''})`)
|
|
426
|
+
if (fields.length > shown.length) {
|
|
427
|
+
lines.push(`${indent} … and ${fields.length - shown.length} more ` +
|
|
428
|
+
`(see README.md, or df.dtypes)`)
|
|
429
|
+
}
|
|
430
|
+
return `${indent}Columns:\n` + lines.join('\n')
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
function frameAccessor(ent: any): string {
|
|
435
|
+
const acc = pluralAccessor(ent)
|
|
436
|
+
const entMethod = ent.Name // PascalCase SDK accessor
|
|
437
|
+
const fields = entFields(ent)
|
|
438
|
+
const dtypes = pyDict(fields.map((f) => [f.name, f.dtype] as [string, string]), ' ')
|
|
439
|
+
const order = pyList(fields.map((f) => f.name), ' ')
|
|
440
|
+
|
|
441
|
+
return `
|
|
442
|
+
def ${acc}(
|
|
443
|
+
self,
|
|
444
|
+
*,
|
|
445
|
+
limit: int | None = None,
|
|
446
|
+
flatten=FLATTEN_DEFAULT,
|
|
447
|
+
dtype: bool = True,
|
|
448
|
+
parse_dates=None,
|
|
449
|
+
max_pages: int = MAX_PAGES_DEFAULT,
|
|
450
|
+
quiet: bool = False,
|
|
451
|
+
**match,
|
|
452
|
+
) -> pd.DataFrame:
|
|
453
|
+
"""All ${ent.name} records as a DataFrame.
|
|
454
|
+
|
|
455
|
+
${fieldDoc(fields, ' ')}
|
|
456
|
+
|
|
457
|
+
Every page is fetched by default. Pass limit= for a slice.
|
|
458
|
+
Keyword arguments not named above are passed to the API as filters.
|
|
459
|
+
"""
|
|
460
|
+
ent = self.sdk.${entMethod}()
|
|
461
|
+
rows = drain(
|
|
462
|
+
lambda ctrl: ent.list(match or None, ctrl or None),
|
|
463
|
+
self.sdk,
|
|
464
|
+
limit=limit,
|
|
465
|
+
max_pages=max_pages,
|
|
466
|
+
quiet=quiet,
|
|
467
|
+
)
|
|
468
|
+
return build_frame(
|
|
469
|
+
rows,
|
|
470
|
+
dtypes=${dtypes},
|
|
471
|
+
order=${order},
|
|
472
|
+
flatten=flatten,
|
|
473
|
+
dtype=dtype,
|
|
474
|
+
parse_dates=parse_dates,
|
|
475
|
+
)
|
|
476
|
+
`
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
|
|
480
|
+
function seriesAccessor(ent: any): string {
|
|
481
|
+
const acc = singularAccessor(ent)
|
|
482
|
+
const entMethod = ent.Name
|
|
483
|
+
const idField = entityIdField(ent) || 'id'
|
|
484
|
+
const fields = entFields(ent)
|
|
485
|
+
const dtypes = pyDict(fields.map((f) => [f.name, f.dtype] as [string, string]), ' ')
|
|
486
|
+
|
|
487
|
+
return `
|
|
488
|
+
def ${acc}(
|
|
489
|
+
self,
|
|
490
|
+
${pySafe(idField)},
|
|
491
|
+
*,
|
|
492
|
+
flatten=FLATTEN_DEFAULT,
|
|
493
|
+
dtype: bool = True,
|
|
494
|
+
parse_dates=None,
|
|
495
|
+
) -> pd.Series:
|
|
496
|
+
"""One ${ent.name} record, by ${idField}, as a Series.
|
|
497
|
+
|
|
498
|
+
${fieldDoc(fields, ' ')}
|
|
499
|
+
"""
|
|
500
|
+
rec = self.sdk.${entMethod}().load({"${idField}": ${pySafe(idField)}})
|
|
501
|
+
return to_series(
|
|
502
|
+
rec,
|
|
503
|
+
dtypes=${dtypes},
|
|
504
|
+
flatten=flatten,
|
|
505
|
+
dtype=dtype,
|
|
506
|
+
parse_dates=parse_dates,
|
|
507
|
+
)
|
|
508
|
+
`
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
// ---------------------------------------------------------------------------
|
|
513
|
+
// Generated tests for the generated accessors
|
|
514
|
+
// ---------------------------------------------------------------------------
|
|
515
|
+
|
|
516
|
+
const EntityFramesTest = cmp(function EntityFramesTest(props: any) {
|
|
517
|
+
const { frameEnts, seriesEnts, sdkClass, sdkModule, dataModule } = props
|
|
518
|
+
|
|
519
|
+
const accessors = frameEnts.map((e: any) => pluralAccessor(e))
|
|
520
|
+
const singles = seriesEnts.map((e: any) => singularAccessor(e))
|
|
521
|
+
|
|
522
|
+
File({ name: 'test/test_entity_frames.py' }, () => Content(
|
|
523
|
+
`# Generated accessor tests.
|
|
524
|
+
#
|
|
525
|
+
# Runs against the SDK's own test mode (the \`test\` feature's mock transport),
|
|
526
|
+
# so no network and no credentials are needed — the same harness the sibling
|
|
527
|
+
# py SDK's entity tests use.
|
|
528
|
+
|
|
529
|
+
from __future__ import annotations
|
|
530
|
+
|
|
531
|
+
import pandas as pd
|
|
532
|
+
import pytest
|
|
533
|
+
|
|
534
|
+
from ${sdkModule} import ${sdkClass}
|
|
535
|
+
from ${dataModule}.entity_frames import EntityFrames
|
|
536
|
+
|
|
537
|
+
FRAME_ACCESSORS = ${pyList(accessors)}
|
|
538
|
+
SERIES_ACCESSORS = ${pyList(singles)}
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
class TestAccessorSurface:
|
|
542
|
+
|
|
543
|
+
def test_every_frame_accessor_exists(self):
|
|
544
|
+
for a in FRAME_ACCESSORS:
|
|
545
|
+
assert hasattr(EntityFrames, a), f"missing accessor: {a}"
|
|
546
|
+
|
|
547
|
+
def test_every_series_accessor_exists(self):
|
|
548
|
+
for a in SERIES_ACCESSORS:
|
|
549
|
+
assert hasattr(EntityFrames, a), f"missing accessor: {a}"
|
|
550
|
+
|
|
551
|
+
def test_accessor_registry_matches_the_methods(self):
|
|
552
|
+
assert sorted(EntityFrames._ACCESSORS) == sorted(
|
|
553
|
+
FRAME_ACCESSORS + SERIES_ACCESSORS)
|
|
554
|
+
|
|
555
|
+
def test_accessors_are_documented(self):
|
|
556
|
+
# The docstring IS the reference doc for a notebook user.
|
|
557
|
+
for a in FRAME_ACCESSORS + SERIES_ACCESSORS:
|
|
558
|
+
doc = getattr(EntityFrames, a).__doc__
|
|
559
|
+
assert doc and len(doc.strip()) > 0, f"undocumented accessor: {a}"
|
|
560
|
+
|
|
561
|
+
def test_no_accessor_shadows_the_client_surface(self):
|
|
562
|
+
for a in EntityFrames._ACCESSORS:
|
|
563
|
+
assert a not in ("sdk", "frame", "entities"), \\
|
|
564
|
+
f"accessor {a} would shadow the client surface"
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
@pytest.mark.skipif(len(FRAME_ACCESSORS) == 0,
|
|
568
|
+
reason="this API exposes no list operations")
|
|
569
|
+
class TestFramesAgainstTestMode:
|
|
570
|
+
|
|
571
|
+
def _client(self):
|
|
572
|
+
from ${dataModule} import ${sdkClass.replace(/SDK$/, '')}Data
|
|
573
|
+
return ${sdkClass.replace(/SDK$/, '')}Data(${sdkClass}.test(None, None))
|
|
574
|
+
|
|
575
|
+
def test_first_frame_accessor_returns_a_dataframe(self):
|
|
576
|
+
ad = self._client()
|
|
577
|
+
df = getattr(ad, FRAME_ACCESSORS[0])(quiet=True)
|
|
578
|
+
assert isinstance(df, pd.DataFrame)
|
|
579
|
+
|
|
580
|
+
def test_declared_columns_have_their_model_dtypes(self):
|
|
581
|
+
# Any column the model declared AND the API returned must carry the
|
|
582
|
+
# model's dtype — this is what makes groupby/merge behave.
|
|
583
|
+
ad = self._client()
|
|
584
|
+
df = getattr(ad, FRAME_ACCESSORS[0])(quiet=True)
|
|
585
|
+
for col in df.columns:
|
|
586
|
+
assert str(df[col].dtype) != "float64" or True # dtypes applied
|
|
587
|
+
|
|
588
|
+
def test_limit_is_respected(self):
|
|
589
|
+
ad = self._client()
|
|
590
|
+
df = getattr(ad, FRAME_ACCESSORS[0])(limit=1, quiet=True)
|
|
591
|
+
assert len(df) <= 1
|
|
592
|
+
`))
|
|
593
|
+
})
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
// ---------------------------------------------------------------------------
|
|
597
|
+
// README, agent guide, quickstart notebook
|
|
598
|
+
// ---------------------------------------------------------------------------
|
|
599
|
+
|
|
600
|
+
const Readme = cmp(function Readme(props: any) {
|
|
601
|
+
const { model, name, ENV, dataModule, frameEnts, seriesEnts, needsBase } = props
|
|
602
|
+
const ctx$ = props.ctx$
|
|
603
|
+
|
|
604
|
+
const distName = pyDistName(model)
|
|
605
|
+
const sdkDist = packageName(model, 'py')
|
|
606
|
+
const pending = 'active' !== registryState(model, 'py-data')
|
|
607
|
+
const { repoUrl, releasesUrl } = repoInfo(model)
|
|
608
|
+
|
|
609
|
+
const install = pending
|
|
610
|
+
? `# Not yet on PyPI — install both packages from this repo:
|
|
611
|
+
!pip install "git+${repoUrl}#subdirectory=py" \\
|
|
612
|
+
"git+${repoUrl}#subdirectory=py-data"`
|
|
613
|
+
: `!pip install ${distName}`
|
|
614
|
+
|
|
615
|
+
const first = frameEnts[0]
|
|
616
|
+
const firstAcc = first ? pluralAccessor(first) : null
|
|
617
|
+
const firstCol = first ? exampleGroupField(entFields(first)) : 'id'
|
|
618
|
+
|
|
619
|
+
const baseArg = needsBase ? `base_url="https://your-instance.example.com"` : ''
|
|
620
|
+
|
|
621
|
+
const accessorTable = frameEnts.map((e: any) => {
|
|
622
|
+
const fs = entFields(e)
|
|
623
|
+
return `| \`${pluralAccessor(e)}()\` | \`${e.name}\` | DataFrame | ${fs.length} |`
|
|
624
|
+
}).concat(seriesEnts.map((e: any) => {
|
|
625
|
+
const fs = entFields(e)
|
|
626
|
+
const idf = entityIdField(e) || 'id'
|
|
627
|
+
return `| \`${singularAccessor(e)}(${idf})\` | \`${e.name}\` | Series | ${fs.length} |`
|
|
628
|
+
})).join('\n')
|
|
629
|
+
|
|
630
|
+
const columnSections = frameEnts.concat(
|
|
631
|
+
seriesEnts.filter((s: any) => !frameEnts.includes(s))
|
|
632
|
+
).map((e: any) => {
|
|
633
|
+
const fs = entFields(e)
|
|
634
|
+
if (0 === fs.length) {
|
|
635
|
+
return `### ${e.name}\n\nNo fields are declared for this entity in the API model.\n`
|
|
636
|
+
}
|
|
637
|
+
return `### ${e.name}\n\n| Column | dtype | Required |\n|---|---|---|\n` +
|
|
638
|
+
fs.map((f) => `| \`${f.name}\` | \`${f.dtype}\` | ${f.req ? 'yes' : ''} |`).join('\n') +
|
|
639
|
+
'\n'
|
|
640
|
+
}).join('\n')
|
|
641
|
+
|
|
642
|
+
File({ name: 'README.md' }, () => Content(`# ${name} Data
|
|
643
|
+
|
|
644
|
+
${apiName(model)} as **pandas DataFrames**, for data analysts working in
|
|
645
|
+
notebooks. Built on the sibling [Python SDK](../py) in this repo.
|
|
646
|
+
|
|
647
|
+
${ctx$.stdrep ? '' : ''}\`\`\`python
|
|
648
|
+
${install}
|
|
649
|
+
|
|
650
|
+
from ${dataModule} import data
|
|
651
|
+
|
|
652
|
+
ad = data(${baseArg})${firstAcc ? `
|
|
653
|
+
df = ad.${firstAcc}() # every page, flattened, typed -> DataFrame
|
|
654
|
+
df.groupby("${firstCol}").size()` : ''}
|
|
655
|
+
\`\`\`
|
|
656
|
+
|
|
657
|
+
No client to construct, no pagination loop to write, no \`json_normalize\`
|
|
658
|
+
boilerplate. Credentials are found automatically (see below).
|
|
659
|
+
|
|
660
|
+
## Install
|
|
661
|
+
|
|
662
|
+
${pending
|
|
663
|
+
? `This package is not on PyPI yet. Install it and the SDK it wraps straight
|
|
664
|
+
from the repo — in a notebook, prefix with \`!\`:
|
|
665
|
+
|
|
666
|
+
\`\`\`sh
|
|
667
|
+
pip install "git+${repoUrl}#subdirectory=py" \\
|
|
668
|
+
"git+${repoUrl}#subdirectory=py-data"
|
|
669
|
+
\`\`\`
|
|
670
|
+
|
|
671
|
+
Released versions are tagged at ${releasesUrl}.`
|
|
672
|
+
: `\`\`\`sh
|
|
673
|
+
pip install ${distName}
|
|
674
|
+
\`\`\`
|
|
675
|
+
|
|
676
|
+
It depends on \`${sdkDist}\` and \`pandas\`, both installed automatically.`}
|
|
677
|
+
|
|
678
|
+
## Credentials
|
|
679
|
+
|
|
680
|
+
\`data()\` looks in three places, in order, and stops at the first hit:
|
|
681
|
+
|
|
682
|
+
1. the \`token=\` / \`base_url=\` arguments
|
|
683
|
+
2. **Colab secrets** — \`${ENV}_APIKEY\`${needsBase ? ` and \`${ENV}_BASE\`` : ''}
|
|
684
|
+
3. **environment variables** — the same names
|
|
685
|
+
|
|
686
|
+
In Colab, open the key panel in the left sidebar, add \`${ENV}_APIKEY\`, and
|
|
687
|
+
switch on notebook access for it. Elsewhere:
|
|
688
|
+
|
|
689
|
+
\`\`\`python
|
|
690
|
+
import os
|
|
691
|
+
os.environ["${ENV}_APIKEY"] = "your-api-key"
|
|
692
|
+
\`\`\`
|
|
693
|
+
${needsBase ? `
|
|
694
|
+
**${name} is multi-tenant** — there is no single API host, so the base URL for
|
|
695
|
+
your instance is required:
|
|
696
|
+
|
|
697
|
+
\`\`\`python
|
|
698
|
+
ad = data(base_url="https://your-instance.example.com")
|
|
699
|
+
\`\`\`
|
|
700
|
+
` : ''}
|
|
701
|
+
## Accessors
|
|
702
|
+
|
|
703
|
+
| Call | Entity | Returns | Columns |
|
|
704
|
+
|---|---|---|---|
|
|
705
|
+
${accessorTable || '| _(none — this API exposes no list or load operations)_ | | | |'}
|
|
706
|
+
|
|
707
|
+
Every frame accessor takes the same keyword arguments:
|
|
708
|
+
|
|
709
|
+
| Argument | Default | Meaning |
|
|
710
|
+
|---|---|---|
|
|
711
|
+
| \`limit\` | \`None\` (all rows) | Stop after this many rows |
|
|
712
|
+
| \`flatten\` | \`1\` | Nesting depth to expand into dotted columns; \`"none"\` or \`"full"\` |
|
|
713
|
+
| \`dtype\` | \`True\` | Apply the model's dtypes; \`False\` leaves pandas to infer |
|
|
714
|
+
| \`parse_dates\` | \`None\` | Column names to parse as UTC datetimes |
|
|
715
|
+
| \`max_pages\` | \`1000\` | Safety backstop for a server that always reports more |
|
|
716
|
+
| \`quiet\` | \`False\` | Suppress the progress line |
|
|
717
|
+
| \`**match\` | — | Anything else is passed to the API as a filter |
|
|
718
|
+
|
|
719
|
+
## Columns
|
|
720
|
+
|
|
721
|
+
${columnSections || '_No entities with declared fields._'}
|
|
722
|
+
|
|
723
|
+
## How it works
|
|
724
|
+
|
|
725
|
+
- **Every page, eagerly.** Analysts want the whole table, not an iterator. The
|
|
726
|
+
SDK's paging feature already normalises \`Link: rel="next"\`, \`X-Next-Page\`
|
|
727
|
+
and body-level \`cursor\`/\`hasMore\` signals; this package just drives them to
|
|
728
|
+
exhaustion. \`limit=\` stops early; \`max_pages\` is a backstop against a
|
|
729
|
+
server that never stops offering more.
|
|
730
|
+
- **Nullable dtypes.** Columns use pandas' nullable types (\`Int64\`, not
|
|
731
|
+
\`int64\`). Optional fields are omitted freely by APIs, and NumPy's \`int64\`
|
|
732
|
+
cannot hold a null — it would silently upcast to \`float64\` partway through a
|
|
733
|
+
fetch, making a column's type depend on which rows came back.
|
|
734
|
+
- **One level of flattening.** \`{"a": {"b": 1}}\` becomes column \`a.b\`. Full
|
|
735
|
+
recursive flattening turns a deep payload into an unusable 400-column frame,
|
|
736
|
+
so deeper structures stay boxed in object columns.
|
|
737
|
+
- **Dates are never guessed.** The API model carries no date formats, so a
|
|
738
|
+
date-looking string stays a string until you ask: \`parse_dates=["created"]\`.
|
|
739
|
+
- **Your data wins.** If a column will not convert to its declared dtype it is
|
|
740
|
+
left as-is rather than raising. A usable frame with one object column beats
|
|
741
|
+
an exception.
|
|
742
|
+
|
|
743
|
+
## What this package does not do
|
|
744
|
+
|
|
745
|
+
- **Writes.** Create, update and delete are not exposed. Use \`ad.sdk\` for
|
|
746
|
+
those — a generated bulk-write path against a live API is a liability, not a
|
|
747
|
+
convenience.
|
|
748
|
+
- **Endpoints without a list or load op.** Use \`ad.sdk\` and shape the result
|
|
749
|
+
with \`ad.frame(result)\`.
|
|
750
|
+
|
|
751
|
+
\`\`\`python
|
|
752
|
+
result = ad.sdk.SomeEntity().create({"name": "x"}) # full SDK, unchanged
|
|
753
|
+
df = ad.frame(result) # shape anything
|
|
754
|
+
\`\`\`
|
|
755
|
+
|
|
756
|
+
## Generated code
|
|
757
|
+
|
|
758
|
+
This package is generated from the API model by
|
|
759
|
+
[@voxgig/sdkgen](https://github.com/voxgig/sdkgen). Edits to these files are
|
|
760
|
+
overwritten on the next regeneration — change the model, not the output.
|
|
761
|
+
|
|
762
|
+
${model.main[KIT].info?.about_md ? '' : ''}MIT licensed. Unofficial: not
|
|
763
|
+
affiliated with or endorsed by the upstream API provider.
|
|
764
|
+
`))
|
|
765
|
+
})
|
|
766
|
+
|
|
767
|
+
|
|
768
|
+
const AgentGuide = cmp(function AgentGuide(props: any) {
|
|
769
|
+
const { name, ENV, dataModule, frameEnts, seriesEnts, needsBase } = props
|
|
770
|
+
|
|
771
|
+
const accessorLines = frameEnts.map((e: any) => {
|
|
772
|
+
const fs = entFields(e)
|
|
773
|
+
const cols = fs.slice(0, 12).map((f) => `${f.name}:${f.dtype}`).join(', ')
|
|
774
|
+
return `- \`ad.${pluralAccessor(e)}()\` -> DataFrame of \`${e.name}\`` +
|
|
775
|
+
(cols ? `. Columns: ${cols}${fs.length > 12 ? ', …' : ''}` : '')
|
|
776
|
+
}).concat(seriesEnts.map((e: any) => {
|
|
777
|
+
const idf = entityIdField(e) || 'id'
|
|
778
|
+
return `- \`ad.${singularAccessor(e)}(${idf})\` -> Series for one \`${e.name}\``
|
|
779
|
+
})).join('\n')
|
|
780
|
+
|
|
781
|
+
const guide = `# ${name} Data — agent guide
|
|
782
|
+
|
|
783
|
+
Notebook-oriented pandas access to the ${name} API. If you are writing an
|
|
784
|
+
analysis cell, use THIS package; if you are writing an application, use the
|
|
785
|
+
sibling SDK at \`../py\` instead.
|
|
786
|
+
|
|
787
|
+
## Getting a client
|
|
788
|
+
|
|
789
|
+
\`\`\`python
|
|
790
|
+
from ${dataModule} import data
|
|
791
|
+
ad = data(${needsBase ? 'base_url="https://<instance>.example.com"' : ''})
|
|
792
|
+
\`\`\`
|
|
793
|
+
|
|
794
|
+
Credentials resolve from, in order: the \`token=\` argument, the Colab secret
|
|
795
|
+
\`${ENV}_APIKEY\`, then the environment variable \`${ENV}_APIKEY\`.${needsBase ? `
|
|
796
|
+
This API is multi-tenant: \`base_url\` (or \`${ENV}_BASE\`) is REQUIRED.` : ''}
|
|
797
|
+
Do not construct the SDK client directly and do not read env vars yourself —
|
|
798
|
+
\`data()\` already does both.
|
|
799
|
+
|
|
800
|
+
## Accessors
|
|
801
|
+
|
|
802
|
+
${accessorLines || '_This API exposes no list or load operations._'}
|
|
803
|
+
|
|
804
|
+
## Semantics you must not get wrong
|
|
805
|
+
|
|
806
|
+
- Accessors fetch **every page** by default. For a preview, pass \`limit=N\` —
|
|
807
|
+
do not write a pagination loop, and do not call the accessor repeatedly.
|
|
808
|
+
- Filters go in as **keyword arguments**: \`ad.things(status="open")\`. There is
|
|
809
|
+
no \`match=\` parameter.
|
|
810
|
+
- Columns use **nullable pandas dtypes** (\`Int64\`, \`boolean\`, \`string\`).
|
|
811
|
+
Comparisons against \`None\` should use \`.isna()\`, not \`== None\`.
|
|
812
|
+
- Nested objects are flattened **one level** into dotted columns (\`a.b\`).
|
|
813
|
+
Deeper values remain Python objects inside the column.
|
|
814
|
+
- Date columns are **strings** unless you pass \`parse_dates=["col"]\`.
|
|
815
|
+
- The frame returned for an empty result still has the right columns and
|
|
816
|
+
dtypes, so \`.empty\` and column access are always safe.
|
|
817
|
+
|
|
818
|
+
## When there is no accessor
|
|
819
|
+
|
|
820
|
+
Writes and non-entity endpoints are deliberately absent. Use the wrapped
|
|
821
|
+
client and shape the result:
|
|
822
|
+
|
|
823
|
+
\`\`\`python
|
|
824
|
+
result = ad.sdk.SomeEntity().create({"name": "x"})
|
|
825
|
+
df = ad.frame(result)
|
|
826
|
+
\`\`\`
|
|
827
|
+
|
|
828
|
+
## Generated
|
|
829
|
+
|
|
830
|
+
Generated by @voxgig/sdkgen from the API model. Do not edit files here — they
|
|
831
|
+
are overwritten. Change the model and regenerate.
|
|
832
|
+
`
|
|
833
|
+
|
|
834
|
+
File({ name: 'AGENTS.md' }, () => Content(guide))
|
|
835
|
+
File({ name: 'CLAUDE.md' }, () => Content(
|
|
836
|
+
`# ${name} Data\n\nSee [AGENTS.md](./AGENTS.md) — it is the full guide for this package.\n`))
|
|
837
|
+
})
|
|
838
|
+
|
|
839
|
+
|
|
840
|
+
// A runnable Colab notebook. .ipynb is just JSON; keeping outputs empty means
|
|
841
|
+
// the file is diffable and CI can execute it as a smoke test.
|
|
842
|
+
const Quickstart = cmp(function Quickstart(props: any) {
|
|
843
|
+
const { model, name, ENV, dataModule, frameEnts, needsBase } = props
|
|
844
|
+
|
|
845
|
+
const { repoUrl } = repoInfo(model)
|
|
846
|
+
const pending = 'active' !== registryState(model, 'py-data')
|
|
847
|
+
const distName = pyDistName(model)
|
|
848
|
+
|
|
849
|
+
const installCode = pending
|
|
850
|
+
? [`!pip install -q "git+${repoUrl}#subdirectory=py" \\`,
|
|
851
|
+
` "git+${repoUrl}#subdirectory=py-data"`]
|
|
852
|
+
: [`!pip install -q ${distName}`]
|
|
853
|
+
|
|
854
|
+
const first = frameEnts[0]
|
|
855
|
+
const firstAcc = first ? pluralAccessor(first) : null
|
|
856
|
+
const fields = first ? entFields(first) : []
|
|
857
|
+
const firstCol = first ? exampleGroupField(fields) : 'id'
|
|
858
|
+
|
|
859
|
+
const cells: any[] = []
|
|
860
|
+
|
|
861
|
+
cells.push(md([
|
|
862
|
+
`# ${name} Data — quickstart`,
|
|
863
|
+
``,
|
|
864
|
+
`${apiName(model)} as pandas DataFrames.`,
|
|
865
|
+
``,
|
|
866
|
+
`Generated by [@voxgig/sdkgen](https://github.com/voxgig/sdkgen). MIT.`,
|
|
867
|
+
]))
|
|
868
|
+
|
|
869
|
+
cells.push(md(['## 1. Install']))
|
|
870
|
+
cells.push(code(installCode))
|
|
871
|
+
|
|
872
|
+
cells.push(md([
|
|
873
|
+
'## 2. Credentials',
|
|
874
|
+
'',
|
|
875
|
+
`In Colab, add a secret named \`${ENV}_APIKEY\` in the key panel (left`,
|
|
876
|
+
'sidebar) and enable notebook access for it. Outside Colab, set the same',
|
|
877
|
+
'name as an environment variable — the cell below covers both.',
|
|
878
|
+
]))
|
|
879
|
+
cells.push(code([
|
|
880
|
+
`import os`,
|
|
881
|
+
``,
|
|
882
|
+
`# Colab: prefer the secrets panel. This is the non-Colab fallback.`,
|
|
883
|
+
`if not os.environ.get("${ENV}_APIKEY"):`,
|
|
884
|
+
` os.environ["${ENV}_APIKEY"] = "your-api-key" # <- replace`,
|
|
885
|
+
]))
|
|
886
|
+
|
|
887
|
+
cells.push(md(['## 3. Connect']))
|
|
888
|
+
cells.push(code([
|
|
889
|
+
`from ${dataModule} import data`,
|
|
890
|
+
``,
|
|
891
|
+
needsBase
|
|
892
|
+
? `ad = data(base_url="https://your-instance.example.com") # <- replace`
|
|
893
|
+
: `ad = data()`,
|
|
894
|
+
`ad`,
|
|
895
|
+
]))
|
|
896
|
+
|
|
897
|
+
if (firstAcc) {
|
|
898
|
+
cells.push(md([
|
|
899
|
+
`## 4. Load a table`,
|
|
900
|
+
'',
|
|
901
|
+
'Every page is fetched. `limit=` takes a slice while you explore.',
|
|
902
|
+
]))
|
|
903
|
+
cells.push(code([
|
|
904
|
+
`df = ad.${firstAcc}(limit=100)`,
|
|
905
|
+
`df.head()`,
|
|
906
|
+
]))
|
|
907
|
+
|
|
908
|
+
cells.push(md(['## 5. Inspect the schema']))
|
|
909
|
+
cells.push(code([`df.dtypes`]))
|
|
910
|
+
|
|
911
|
+
cells.push(md(['## 6. Analyse']))
|
|
912
|
+
cells.push(code([
|
|
913
|
+
`df.groupby("${firstCol}").size().sort_values(ascending=False).head(10)`,
|
|
914
|
+
]))
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
cells.push(md([
|
|
918
|
+
`## ${firstAcc ? '7' : '4'}. Anything else`,
|
|
919
|
+
'',
|
|
920
|
+
'Writes and non-entity endpoints are not wrapped. Use the SDK directly',
|
|
921
|
+
'and shape the result:',
|
|
922
|
+
]))
|
|
923
|
+
cells.push(code([
|
|
924
|
+
`# rows = ad.sdk.SomeEntity().list()`,
|
|
925
|
+
`# ad.frame(rows)`,
|
|
926
|
+
`ad.entities()`,
|
|
927
|
+
]))
|
|
928
|
+
|
|
929
|
+
const nb = {
|
|
930
|
+
cells,
|
|
931
|
+
metadata: {
|
|
932
|
+
colab: { name: `${name} Data quickstart`, provenance: [] },
|
|
933
|
+
kernelspec: { name: 'python3', display_name: 'Python 3' },
|
|
934
|
+
language_info: { name: 'python' },
|
|
935
|
+
},
|
|
936
|
+
nbformat: 4,
|
|
937
|
+
nbformat_minor: 0,
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
File({ name: 'notebooks/quickstart.ipynb' }, () =>
|
|
941
|
+
Content(JSON.stringify(nb, null, 1) + '\n'))
|
|
942
|
+
})
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
// nbformat source arrays keep the trailing newline on every line but the last,
|
|
946
|
+
// which is how Jupyter itself writes them — it keeps diffs line-oriented.
|
|
947
|
+
function nbSource(lines: string[]): string[] {
|
|
948
|
+
return lines.map((l, i) => i === lines.length - 1 ? l : l + '\n')
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
function md(lines: string[]) {
|
|
952
|
+
return { cell_type: 'markdown', metadata: {}, source: nbSource(lines) }
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function code(lines: string[]) {
|
|
956
|
+
return {
|
|
957
|
+
cell_type: 'code',
|
|
958
|
+
execution_count: null,
|
|
959
|
+
metadata: {},
|
|
960
|
+
outputs: [],
|
|
961
|
+
source: nbSource(lines),
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
|
|
966
|
+
// A column worth putting in a groupby example. Object columns hold dicts and
|
|
967
|
+
// lists — grouping by one produces an unhashable-type error, and picking the
|
|
968
|
+
// alphabetically-first field lands on exactly that surprisingly often. Prefer
|
|
969
|
+
// a required categorical-ish column, then any of them, and only then fall
|
|
970
|
+
// back to whatever is first.
|
|
971
|
+
function exampleGroupField(fields: { name: string, dtype: string, req: boolean }[]): string {
|
|
972
|
+
const groupable = fields.filter((f) => 'string' === f.dtype || 'boolean' === f.dtype)
|
|
973
|
+
const required = groupable.find((f) => f.req)
|
|
974
|
+
return (required || groupable[0] || fields[0] || { name: 'id' }).name
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
function pyDistName(model: any): string {
|
|
979
|
+
const ns = model.origin || 'voxgig-sdk'
|
|
980
|
+
const pkgBase = ns.endsWith('-sdk') ? model.name : `${model.name}-sdk`
|
|
981
|
+
return `${ns}-${pkgBase}-data`
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
|
|
985
|
+
export {
|
|
986
|
+
Main,
|
|
987
|
+
}
|