@lovrozagar/honey 0.6.5 → 0.7.0
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/README.md +514 -192
- package/bin/honey.js +25 -0
- package/dist/accepts.d.ts +7 -1
- package/dist/accepts.d.ts.map +1 -1
- package/dist/accepts.js +126 -0
- package/dist/accepts.js.map +1 -0
- package/dist/body-kind.d.ts +26 -0
- package/dist/body-kind.d.ts.map +1 -0
- package/dist/body-kind.js +70 -0
- package/dist/body-kind.js.map +1 -0
- package/dist/body-limit.d.ts +5 -1
- package/dist/body-limit.d.ts.map +1 -1
- package/dist/body-limit.js +126 -0
- package/dist/body-limit.js.map +1 -0
- package/dist/build/index.d.ts +19 -3
- package/dist/build/index.d.ts.map +1 -1
- package/dist/build/index.js +173 -0
- package/dist/build/index.js.map +1 -0
- package/dist/cli-args.d.ts +33 -0
- package/dist/cli-args.d.ts.map +1 -0
- package/dist/cli-args.js +102 -0
- package/dist/cli-args.js.map +1 -0
- package/dist/cli-go/auth.go +26 -0
- package/dist/cli-go/config.go +192 -0
- package/dist/cli-go/errors.go +75 -0
- package/dist/cli-go/multipart.go +79 -0
- package/dist/cli-go/output.go +163 -0
- package/dist/cli-go/root.go +30 -0
- package/dist/cli-go/stream.go +88 -0
- package/dist/cli-go/version.go +5 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +209 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/defaults.js +48 -0
- package/dist/client/defaults.js.map +1 -0
- package/dist/client/error.d.ts +35 -0
- package/dist/client/error.d.ts.map +1 -1
- package/dist/client/error.js +71 -0
- package/dist/client/error.js.map +1 -0
- package/dist/client/http.d.ts +67 -8
- package/dist/client/http.d.ts.map +1 -1
- package/dist/client/http.js +571 -0
- package/dist/client/http.js.map +1 -0
- package/dist/client/index.d.ts +3 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +95 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/path.d.ts +26 -0
- package/dist/client/path.d.ts.map +1 -0
- package/dist/client/path.js +163 -0
- package/dist/client/path.js.map +1 -0
- package/dist/client/redirect-policy.d.ts +12 -0
- package/dist/client/redirect-policy.d.ts.map +1 -0
- package/dist/client/redirect-policy.js +20 -0
- package/dist/client/redirect-policy.js.map +1 -0
- package/dist/client/sdk.d.ts +36 -0
- package/dist/client/sdk.d.ts.map +1 -1
- package/dist/client/sdk.js +292 -0
- package/dist/client/sdk.js.map +1 -0
- package/dist/client/sse.d.ts +13 -0
- package/dist/client/sse.d.ts.map +1 -1
- package/dist/client/sse.js +129 -0
- package/dist/client/sse.js.map +1 -0
- package/dist/client/types.js +2 -0
- package/dist/client/types.js.map +1 -0
- package/dist/client/ws.d.ts +9 -2
- package/dist/client/ws.d.ts.map +1 -1
- package/dist/client/ws.js +112 -0
- package/dist/client/ws.js.map +1 -0
- package/dist/client-go/errors.go +236 -0
- package/dist/client-go/export.go +44 -0
- package/dist/client-go/invalidation.go +371 -0
- package/dist/client-go/nullable.go +62 -0
- package/dist/client-go/realtime.go +354 -0
- package/dist/client-go/result.go +20 -0
- package/dist/client-go/runtime.go +975 -0
- package/dist/client-go/sse.go +159 -0
- package/dist/client-go/transport.go +495 -0
- package/dist/client-go/ws.go +128 -0
- package/dist/client-info.d.ts +36 -0
- package/dist/client-info.d.ts.map +1 -0
- package/dist/client-info.js +53 -0
- package/dist/client-info.js.map +1 -0
- package/dist/client-mcp/README.md +10 -0
- package/dist/client-mcp/server.ts +221 -0
- package/dist/client-mcp/types.ts +19 -0
- package/dist/client-python/_errors.py +115 -0
- package/dist/client-python/_invalidation.py +494 -0
- package/dist/client-python/_realtime.py +179 -0
- package/dist/client-python/_runtime.py +860 -0
- package/dist/client-python/_sse.py +155 -0
- package/dist/client-python/_transport.py +366 -0
- package/dist/client-python/_ws.py +159 -0
- package/dist/client-python/py.typed +0 -0
- package/dist/client-rust/errors.rs +220 -0
- package/dist/client-rust/invalidation.rs +412 -0
- package/dist/client-rust/invalidation_sync.rs +93 -0
- package/dist/client-rust/nullable.rs +17 -0
- package/dist/client-rust/realtime.rs +702 -0
- package/dist/client-rust/result.rs +22 -0
- package/dist/client-rust/runtime.rs +980 -0
- package/dist/client-rust/runtime_sync.rs +484 -0
- package/dist/client-rust/sse.rs +188 -0
- package/dist/client-rust/ws.rs +167 -0
- package/dist/codegen-go-cli.d.ts.map +1 -1
- package/dist/codegen-go-cli.js +964 -0
- package/dist/codegen-go-cli.js.map +1 -0
- package/dist/codegen-go.d.ts +15 -3
- package/dist/codegen-go.d.ts.map +1 -1
- package/dist/codegen-go.js +872 -0
- package/dist/codegen-go.js.map +1 -0
- package/dist/codegen-hash.js +9 -0
- package/dist/codegen-hash.js.map +1 -0
- package/dist/codegen-ir.d.ts +69 -2
- package/dist/codegen-ir.d.ts.map +1 -1
- package/dist/codegen-ir.js +661 -0
- package/dist/codegen-ir.js.map +1 -0
- package/dist/codegen-lang.d.ts +65 -0
- package/dist/codegen-lang.d.ts.map +1 -0
- package/dist/codegen-lang.js +526 -0
- package/dist/codegen-lang.js.map +1 -0
- package/dist/codegen-load.d.ts +31 -0
- package/dist/codegen-load.d.ts.map +1 -0
- package/dist/codegen-load.js +138 -0
- package/dist/codegen-load.js.map +1 -0
- package/dist/codegen-loaders.js +55 -0
- package/dist/codegen-loaders.js.map +1 -0
- package/dist/codegen-mcp.d.ts.map +1 -1
- package/dist/codegen-mcp.js +368 -0
- package/dist/codegen-mcp.js.map +1 -0
- package/dist/codegen-python.d.ts +13 -1
- package/dist/codegen-python.d.ts.map +1 -1
- package/dist/codegen-python.js +828 -0
- package/dist/codegen-python.js.map +1 -0
- package/dist/codegen-route-tree-intern.d.ts +15 -5
- package/dist/codegen-route-tree-intern.d.ts.map +1 -1
- package/dist/codegen-route-tree-intern.js +221 -0
- package/dist/codegen-route-tree-intern.js.map +1 -0
- package/dist/codegen-rust.d.ts.map +1 -1
- package/dist/codegen-rust.js +1293 -0
- package/dist/codegen-rust.js.map +1 -0
- package/dist/codegen-sanitize.d.ts.map +1 -1
- package/dist/codegen-sanitize.js +56 -0
- package/dist/codegen-sanitize.js.map +1 -0
- package/dist/codegen-schema-naming.d.ts +5 -0
- package/dist/codegen-schema-naming.d.ts.map +1 -1
- package/dist/codegen-schema-naming.js +150 -0
- package/dist/codegen-schema-naming.js.map +1 -0
- package/dist/codegen-sdk-model.d.ts +71 -0
- package/dist/codegen-sdk-model.d.ts.map +1 -0
- package/dist/codegen-sdk-model.js +179 -0
- package/dist/codegen-sdk-model.js.map +1 -0
- package/dist/codegen.d.ts +41 -23
- package/dist/codegen.d.ts.map +1 -1
- package/dist/codegen.js +4610 -0
- package/dist/codegen.js.map +1 -0
- package/dist/context.d.ts +36 -6
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +242 -0
- package/dist/context.js.map +1 -0
- package/dist/cookie-sign.d.ts +16 -2
- package/dist/cookie-sign.d.ts.map +1 -1
- package/dist/cookie-sign.js +104 -0
- package/dist/cookie-sign.js.map +1 -0
- package/dist/cookie.d.ts +12 -0
- package/dist/cookie.d.ts.map +1 -1
- package/dist/cookie.js +138 -0
- package/dist/cookie.js.map +1 -0
- package/dist/cors.d.ts +1 -0
- package/dist/cors.d.ts.map +1 -1
- package/dist/cors.js +87 -0
- package/dist/cors.js.map +1 -0
- package/dist/crypto.d.ts +6 -1
- package/dist/crypto.d.ts.map +1 -1
- package/dist/crypto.js +38 -0
- package/dist/crypto.js.map +1 -0
- package/dist/csrf.d.ts +19 -0
- package/dist/csrf.d.ts.map +1 -1
- package/dist/csrf.js +79 -0
- package/dist/csrf.js.map +1 -0
- package/dist/curl-logger.d.ts +14 -3
- package/dist/curl-logger.d.ts.map +1 -1
- package/dist/curl-logger.js +199 -0
- package/dist/curl-logger.js.map +1 -0
- package/dist/detect-runtime.js +13 -0
- package/dist/detect-runtime.js.map +1 -0
- package/dist/dict.d.ts +11 -0
- package/dist/dict.d.ts.map +1 -0
- package/dist/dict.js +13 -0
- package/dist/dict.js.map +1 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +44 -0
- package/dist/error.js.map +1 -0
- package/dist/errors.js +39 -0
- package/dist/errors.js.map +1 -0
- package/dist/etag.d.ts +2 -0
- package/dist/etag.d.ts.map +1 -1
- package/dist/etag.js +82 -0
- package/dist/etag.js.map +1 -0
- package/dist/feature-detect.d.ts +25 -0
- package/dist/feature-detect.d.ts.map +1 -0
- package/dist/feature-detect.js +144 -0
- package/dist/feature-detect.js.map +1 -0
- package/dist/feature-load.js +36 -0
- package/dist/feature-load.js.map +1 -0
- package/dist/feature-slots.d.ts +44 -0
- package/dist/feature-slots.d.ts.map +1 -0
- package/dist/feature-slots.js +48 -0
- package/dist/feature-slots.js.map +1 -0
- package/dist/fetch-request.d.ts +10 -0
- package/dist/fetch-request.d.ts.map +1 -0
- package/dist/fetch-request.js +13 -0
- package/dist/fetch-request.js.map +1 -0
- package/dist/gen-process.d.ts +15 -0
- package/dist/gen-process.d.ts.map +1 -0
- package/dist/gen-process.js +43 -0
- package/dist/gen-process.js.map +1 -0
- package/dist/gen-write.d.ts +19 -0
- package/dist/gen-write.d.ts.map +1 -0
- package/dist/gen-write.js +120 -0
- package/dist/gen-write.js.map +1 -0
- package/dist/glob.d.ts +21 -0
- package/dist/glob.d.ts.map +1 -0
- package/dist/glob.js +115 -0
- package/dist/glob.js.map +1 -0
- package/dist/go-type-emitter.d.ts +18 -4
- package/dist/go-type-emitter.d.ts.map +1 -1
- package/dist/go-type-emitter.js +594 -0
- package/dist/go-type-emitter.js.map +1 -0
- package/dist/honey-response.d.ts.map +1 -1
- package/dist/honey-response.js +338 -0
- package/dist/honey-response.js.map +1 -0
- package/dist/i18n-slot.d.ts +8 -0
- package/dist/i18n-slot.d.ts.map +1 -1
- package/dist/i18n-slot.js +14 -0
- package/dist/i18n-slot.js.map +1 -0
- package/dist/i18n.d.ts +3 -0
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +250 -0
- package/dist/i18n.js.map +1 -0
- package/dist/index.d.ts +410 -149
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2324 -0
- package/dist/index.js.map +1 -0
- package/dist/init.d.ts +3 -5
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +140 -0
- package/dist/init.js.map +1 -0
- package/dist/input.js +4 -0
- package/dist/input.js.map +1 -0
- package/dist/invalidate-check.d.ts +11 -0
- package/dist/invalidate-check.d.ts.map +1 -1
- package/dist/invalidate-check.js +163 -0
- package/dist/invalidate-check.js.map +1 -0
- package/dist/invoke-user.d.ts +35 -0
- package/dist/invoke-user.d.ts.map +1 -0
- package/dist/invoke-user.js +73 -0
- package/dist/invoke-user.js.map +1 -0
- package/dist/ip-restrict.d.ts +13 -1
- package/dist/ip-restrict.d.ts.map +1 -1
- package/dist/ip-restrict.js +60 -0
- package/dist/ip-restrict.js.map +1 -0
- package/dist/ip.d.ts +39 -0
- package/dist/ip.d.ts.map +1 -0
- package/dist/ip.js +209 -0
- package/dist/ip.js.map +1 -0
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +113 -0
- package/dist/logger.js.map +1 -0
- package/dist/media-type.d.ts +30 -0
- package/dist/media-type.d.ts.map +1 -0
- package/dist/media-type.js +97 -0
- package/dist/media-type.js.map +1 -0
- package/dist/meta-spec-merge.d.ts +32 -0
- package/dist/meta-spec-merge.d.ts.map +1 -0
- package/dist/meta-spec-merge.js +68 -0
- package/dist/meta-spec-merge.js.map +1 -0
- package/dist/meta-spec.d.ts +16 -3
- package/dist/meta-spec.d.ts.map +1 -1
- package/dist/meta-spec.js +535 -0
- package/dist/meta-spec.js.map +1 -0
- package/dist/middleware.d.ts +23 -6
- package/dist/middleware.d.ts.map +1 -1
- package/dist/middleware.js +253 -0
- package/dist/middleware.js.map +1 -0
- package/dist/node-request.d.ts +57 -12
- package/dist/node-request.d.ts.map +1 -1
- package/dist/node-request.js +407 -0
- package/dist/node-request.js.map +1 -0
- package/dist/node.d.ts +20 -3
- package/dist/node.d.ts.map +1 -1
- package/dist/node.js +396 -0
- package/dist/node.js.map +1 -0
- package/dist/openapi/collect.d.ts +6 -3
- package/dist/openapi/collect.d.ts.map +1 -1
- package/dist/openapi/collect.js +30 -0
- package/dist/openapi/collect.js.map +1 -0
- package/dist/openapi/docs-page.d.ts +42 -0
- package/dist/openapi/docs-page.d.ts.map +1 -0
- package/dist/openapi/docs-page.js +80 -0
- package/dist/openapi/docs-page.js.map +1 -0
- package/dist/openapi/document.d.ts +4 -0
- package/dist/openapi/document.d.ts.map +1 -1
- package/dist/openapi/document.js +785 -0
- package/dist/openapi/document.js.map +1 -0
- package/dist/openapi/json-schema-slot.d.ts +7 -0
- package/dist/openapi/json-schema-slot.d.ts.map +1 -1
- package/dist/openapi/json-schema-slot.js +38 -0
- package/dist/openapi/json-schema-slot.js.map +1 -0
- package/dist/openapi/register.d.ts.map +1 -1
- package/dist/openapi/register.js +20 -0
- package/dist/openapi/register.js.map +1 -0
- package/dist/openapi/scalar.d.ts +3 -1
- package/dist/openapi/scalar.d.ts.map +1 -1
- package/dist/openapi/scalar.js +21 -0
- package/dist/openapi/scalar.js.map +1 -0
- package/dist/openapi/serve.d.ts +9 -0
- package/dist/openapi/serve.d.ts.map +1 -0
- package/dist/openapi/serve.js +36 -0
- package/dist/openapi/serve.js.map +1 -0
- package/dist/openapi/spec-factory.d.ts +30 -1
- package/dist/openapi/spec-factory.d.ts.map +1 -1
- package/dist/openapi/spec-factory.js +52 -0
- package/dist/openapi/spec-factory.js.map +1 -0
- package/dist/openapi/spec.d.ts +14 -5
- package/dist/openapi/spec.d.ts.map +1 -1
- package/dist/openapi/spec.js +50 -0
- package/dist/openapi/spec.js.map +1 -0
- package/dist/openapi/swagger.d.ts +3 -1
- package/dist/openapi/swagger.d.ts.map +1 -1
- package/dist/openapi/swagger.js +22 -0
- package/dist/openapi/swagger.js.map +1 -0
- package/dist/pattern.d.ts +71 -0
- package/dist/pattern.d.ts.map +1 -0
- package/dist/pattern.js +239 -0
- package/dist/pattern.js.map +1 -0
- package/dist/peer.d.ts +17 -0
- package/dist/peer.d.ts.map +1 -0
- package/dist/peer.js +50 -0
- package/dist/peer.js.map +1 -0
- package/dist/plugin.d.ts +67 -16
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +649 -0
- package/dist/plugin.js.map +1 -0
- package/dist/powered-by.d.ts.map +1 -1
- package/dist/powered-by.js +11 -0
- package/dist/powered-by.js.map +1 -0
- package/dist/pretty-json.d.ts.map +1 -1
- package/dist/pretty-json.js +47 -0
- package/dist/pretty-json.js.map +1 -0
- package/dist/producer-stream.d.ts +47 -0
- package/dist/producer-stream.d.ts.map +1 -0
- package/dist/producer-stream.js +267 -0
- package/dist/producer-stream.js.map +1 -0
- package/dist/proxy.d.ts +47 -8
- package/dist/proxy.d.ts.map +1 -1
- package/dist/proxy.js +274 -0
- package/dist/proxy.js.map +1 -0
- package/dist/python-type-emitter.d.ts +14 -1
- package/dist/python-type-emitter.d.ts.map +1 -1
- package/dist/python-type-emitter.js +157 -0
- package/dist/python-type-emitter.js.map +1 -0
- package/dist/realtime/bus.d.ts +17 -1
- package/dist/realtime/bus.d.ts.map +1 -1
- package/dist/realtime/bus.js +112 -0
- package/dist/realtime/bus.js.map +1 -0
- package/dist/realtime/register.d.ts +3 -0
- package/dist/realtime/register.d.ts.map +1 -0
- package/dist/realtime/register.js +10 -0
- package/dist/realtime/register.js.map +1 -0
- package/dist/realtime/route.d.ts +65 -26
- package/dist/realtime/route.d.ts.map +1 -1
- package/dist/realtime/route.js +72 -0
- package/dist/realtime/route.js.map +1 -0
- package/dist/realtime/server.d.ts +43 -0
- package/dist/realtime/server.d.ts.map +1 -0
- package/dist/realtime/server.js +255 -0
- package/dist/realtime/server.js.map +1 -0
- package/dist/request-id.d.ts +7 -0
- package/dist/request-id.d.ts.map +1 -1
- package/dist/request-id.js +16 -0
- package/dist/request-id.js.map +1 -0
- package/dist/request-limits.d.ts +3 -0
- package/dist/request-limits.d.ts.map +1 -0
- package/dist/request-limits.js +3 -0
- package/dist/request-limits.js.map +1 -0
- package/dist/request-path.d.ts +33 -0
- package/dist/request-path.d.ts.map +1 -0
- package/dist/request-path.js +178 -0
- package/dist/request-path.js.map +1 -0
- package/dist/request-to-curl.d.ts +7 -1
- package/dist/request-to-curl.d.ts.map +1 -1
- package/dist/request-to-curl.js +48 -0
- package/dist/request-to-curl.js.map +1 -0
- package/dist/response.d.ts +25 -2
- package/dist/response.d.ts.map +1 -1
- package/dist/response.js +313 -0
- package/dist/response.js.map +1 -0
- package/dist/rust-type-emitter.d.ts +14 -2
- package/dist/rust-type-emitter.d.ts.map +1 -1
- package/dist/rust-type-emitter.js +463 -0
- package/dist/rust-type-emitter.js.map +1 -0
- package/dist/secure-headers.d.ts.map +1 -1
- package/dist/secure-headers.js +53 -0
- package/dist/secure-headers.js.map +1 -0
- package/dist/serve-register.d.ts +4 -0
- package/dist/serve-register.d.ts.map +1 -1
- package/dist/serve-register.js +9 -0
- package/dist/serve-register.js.map +1 -0
- package/dist/serve-slot.d.ts +3 -0
- package/dist/serve-slot.d.ts.map +1 -1
- package/dist/serve-slot.js +52 -0
- package/dist/serve-slot.js.map +1 -0
- package/dist/serve.d.ts +18 -1
- package/dist/serve.d.ts.map +1 -1
- package/dist/serve.js +157 -0
- package/dist/serve.js.map +1 -0
- package/dist/server-timing.d.ts.map +1 -1
- package/dist/server-timing.js +51 -0
- package/dist/server-timing.js.map +1 -0
- package/dist/static.d.ts +9 -0
- package/dist/static.d.ts.map +1 -1
- package/dist/static.js +102 -0
- package/dist/static.js.map +1 -0
- package/dist/telemetry/otel.d.ts +2 -0
- package/dist/telemetry/otel.d.ts.map +1 -1
- package/dist/telemetry/otel.js +135 -0
- package/dist/telemetry/otel.js.map +1 -0
- package/dist/testing.d.ts +12 -0
- package/dist/testing.d.ts.map +1 -1
- package/dist/testing.js +124 -0
- package/dist/testing.js.map +1 -0
- package/dist/timeout.d.ts +6 -0
- package/dist/timeout.d.ts.map +1 -1
- package/dist/timeout.js +36 -0
- package/dist/timeout.js.map +1 -0
- package/dist/tree.d.ts +133 -36
- package/dist/tree.d.ts.map +1 -1
- package/dist/tree.js +437 -0
- package/dist/tree.js.map +1 -0
- package/dist/trust.d.ts +21 -0
- package/dist/trust.d.ts.map +1 -0
- package/dist/trust.js +100 -0
- package/dist/trust.js.map +1 -0
- package/dist/ts-type-emitter.d.ts.map +1 -1
- package/dist/ts-type-emitter.js +150 -0
- package/dist/ts-type-emitter.js.map +1 -0
- package/dist/type-emitter.d.ts +16 -2
- package/dist/type-emitter.d.ts.map +1 -1
- package/dist/type-emitter.js +729 -0
- package/dist/type-emitter.js.map +1 -0
- package/dist/type-extractor.d.ts +8 -4
- package/dist/type-extractor.d.ts.map +1 -1
- package/dist/type-extractor.js +776 -0
- package/dist/type-extractor.js.map +1 -0
- package/dist/types.d.ts +7 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +96 -0
- package/dist/types.js.map +1 -0
- package/dist/validation.d.ts +14 -2
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +335 -0
- package/dist/validation.js.map +1 -0
- package/dist/with-headers.d.ts +15 -0
- package/dist/with-headers.d.ts.map +1 -0
- package/dist/with-headers.js +45 -0
- package/dist/with-headers.js.map +1 -0
- package/dist/ws/bun.d.ts +11 -5
- package/dist/ws/bun.d.ts.map +1 -1
- package/dist/ws/bun.js +82 -0
- package/dist/ws/bun.js.map +1 -0
- package/dist/ws/cloudflare.d.ts +25 -3
- package/dist/ws/cloudflare.d.ts.map +1 -1
- package/dist/ws/cloudflare.js +117 -0
- package/dist/ws/cloudflare.js.map +1 -0
- package/dist/ws/deno.d.ts +4 -4
- package/dist/ws/deno.d.ts.map +1 -1
- package/dist/ws/deno.js +133 -0
- package/dist/ws/deno.js.map +1 -0
- package/dist/ws/node.d.ts +20 -3
- package/dist/ws/node.d.ts.map +1 -1
- package/dist/ws/node.js +173 -0
- package/dist/ws/node.js.map +1 -0
- package/dist/ws/session.d.ts +11 -0
- package/dist/ws/session.d.ts.map +1 -0
- package/dist/ws/session.js +60 -0
- package/dist/ws/session.js.map +1 -0
- package/dist/ws/shared.d.ts +65 -0
- package/dist/ws/shared.d.ts.map +1 -0
- package/dist/ws/shared.js +103 -0
- package/dist/ws/shared.js.map +1 -0
- package/dist/ws-origin.d.ts +20 -0
- package/dist/ws-origin.d.ts.map +1 -0
- package/dist/ws-origin.js +41 -0
- package/dist/ws-origin.js.map +1 -0
- package/dist/yaml.d.ts +15 -1
- package/dist/yaml.d.ts.map +1 -1
- package/dist/yaml.js +101 -0
- package/dist/yaml.js.map +1 -0
- package/docs/invalidate-spec.md +13 -1
- package/docs/meta-spec.md +36 -12
- package/docs/packaging.md +43 -24
- package/docs/regression-matrix/README.md +32 -0
- package/docs/regression-matrix/review.md +44 -0
- package/docs/regression-matrix/ws1-3.md +97 -0
- package/docs/regression-matrix/ws4-8.md +107 -0
- package/docs/regression-matrix/ws9-12.md +266 -0
- package/docs/sdk.md +102 -52
- package/package.json +183 -52
- package/src/accepts.ts +91 -33
- package/src/body-kind.ts +67 -0
- package/src/body-limit.ts +102 -87
- package/src/build/index.ts +104 -54
- package/src/cli-args.ts +123 -0
- package/src/cli-go/auth.go +12 -1
- package/src/cli-go/config.go +29 -2
- package/src/cli-go/errors.go +28 -10
- package/src/cli-go/multipart.go +51 -27
- package/src/cli-go/output.go +58 -4
- package/src/cli-go/root.go +1 -1
- package/src/cli-go/version.go +2 -2
- package/src/cli.ts +154 -148
- package/src/client/error.ts +56 -1
- package/src/client/http.ts +479 -256
- package/src/client/index.ts +55 -17
- package/src/client/path.ts +169 -0
- package/src/client/redirect-policy.ts +19 -0
- package/src/client/sdk.ts +180 -101
- package/src/client/sse.ts +92 -66
- package/src/client/ws.ts +67 -31
- package/src/client-go/errors.go +41 -12
- package/src/client-go/export.go +44 -0
- package/src/client-go/invalidation.go +52 -54
- package/src/client-go/nullable.go +62 -0
- package/src/client-go/realtime.go +147 -110
- package/src/client-go/runtime.go +646 -88
- package/src/client-go/sse.go +104 -94
- package/src/client-go/transport.go +67 -27
- package/src/client-go/ws.go +49 -25
- package/src/client-info.ts +79 -0
- package/src/client-mcp/server.ts +133 -3
- package/src/client-python/_invalidation.py +207 -291
- package/src/client-python/_realtime.py +63 -40
- package/src/client-python/_runtime.py +501 -119
- package/src/client-python/_sse.py +130 -74
- package/src/client-python/_transport.py +37 -9
- package/src/client-python/_ws.py +50 -22
- package/src/client-rust/errors.rs +21 -1
- package/src/client-rust/invalidation.rs +202 -231
- package/src/client-rust/invalidation_sync.rs +16 -229
- package/src/client-rust/nullable.rs +17 -0
- package/src/client-rust/realtime.rs +217 -217
- package/src/client-rust/runtime.rs +602 -177
- package/src/client-rust/runtime_sync.rs +169 -145
- package/src/client-rust/sse.rs +142 -121
- package/src/client-rust/ws.rs +61 -37
- package/src/codegen-go-cli.ts +607 -728
- package/src/codegen-go.ts +710 -770
- package/src/codegen-ir.ts +381 -75
- package/src/codegen-lang.ts +517 -0
- package/src/codegen-load.ts +150 -0
- package/src/codegen-mcp.ts +207 -108
- package/src/codegen-python.ts +684 -816
- package/src/codegen-route-tree-intern.ts +51 -25
- package/src/codegen-rust.ts +946 -1240
- package/src/codegen-sanitize.ts +0 -4
- package/src/codegen-schema-naming.ts +19 -6
- package/src/codegen-sdk-model.ts +228 -0
- package/src/codegen.ts +1488 -1140
- package/src/context.ts +130 -11
- package/src/cookie-sign.ts +85 -16
- package/src/cookie.ts +66 -11
- package/src/cors.ts +55 -54
- package/src/crypto.ts +27 -5
- package/src/csrf.ts +57 -25
- package/src/curl-logger.ts +155 -114
- package/src/dict.ts +12 -0
- package/src/error.ts +4 -2
- package/src/etag.ts +58 -34
- package/src/feature-detect.ts +147 -0
- package/src/feature-slots.ts +70 -0
- package/src/fetch-request.ts +13 -0
- package/src/gen-process.ts +42 -0
- package/src/gen-write.ts +127 -0
- package/src/glob.ts +109 -0
- package/src/go-type-emitter.ts +406 -155
- package/src/honey-response.ts +36 -9
- package/src/i18n-slot.ts +10 -0
- package/src/i18n.ts +98 -23
- package/src/index.ts +1893 -1761
- package/src/init.ts +40 -28
- package/src/invalidate-check.ts +42 -4
- package/src/invoke-user.ts +95 -0
- package/src/ip-restrict.ts +47 -161
- package/src/ip.ts +194 -0
- package/src/logger.ts +65 -36
- package/src/media-type.ts +104 -0
- package/src/meta-spec-merge.ts +80 -0
- package/src/meta-spec.ts +0 -0
- package/src/middleware.ts +112 -12
- package/src/node-request.ts +242 -52
- package/src/node.ts +318 -75
- package/src/openapi/collect.ts +19 -44
- package/src/openapi/docs-page.ts +89 -0
- package/src/openapi/document.ts +503 -224
- package/src/openapi/json-schema-slot.ts +40 -0
- package/src/openapi/register.ts +7 -2
- package/src/openapi/scalar.ts +11 -10
- package/src/openapi/serve.ts +39 -0
- package/src/openapi/spec-factory.ts +50 -1
- package/src/openapi/spec.ts +53 -30
- package/src/openapi/swagger.ts +12 -11
- package/src/pattern.ts +245 -0
- package/src/peer.ts +53 -0
- package/src/plugin.ts +270 -185
- package/src/powered-by.ts +2 -2
- package/src/pretty-json.ts +25 -9
- package/src/producer-stream.ts +296 -0
- package/src/proxy.ts +271 -55
- package/src/python-type-emitter.ts +68 -56
- package/src/realtime/bus.ts +56 -9
- package/src/realtime/register.ts +13 -0
- package/src/realtime/route.ts +129 -80
- package/src/realtime/server.ts +298 -0
- package/src/request-id.ts +13 -3
- package/src/request-limits.ts +2 -0
- package/src/request-path.ts +165 -0
- package/src/request-to-curl.ts +28 -7
- package/src/response.ts +143 -103
- package/src/rust-type-emitter.ts +265 -170
- package/src/secure-headers.ts +8 -4
- package/src/serve-register.ts +4 -0
- package/src/serve-slot.ts +38 -0
- package/src/serve.ts +113 -25
- package/src/server-timing.ts +11 -7
- package/src/static.ts +85 -32
- package/src/telemetry/otel.ts +29 -7
- package/src/testing.ts +76 -14
- package/src/timeout.ts +15 -7
- package/src/tree.ts +405 -333
- package/src/trust.ts +117 -0
- package/src/ts-type-emitter.ts +18 -12
- package/src/type-emitter.ts +227 -89
- package/src/type-extractor.ts +562 -424
- package/src/types.ts +8 -0
- package/src/validation.ts +221 -96
- package/src/with-headers.ts +45 -0
- package/src/ws/bun.ts +53 -15
- package/src/ws/cloudflare.ts +81 -19
- package/src/ws/deno.ts +124 -36
- package/src/ws/node.ts +173 -79
- package/src/ws/session.ts +73 -0
- package/src/ws/shared.ts +140 -0
- package/src/ws-origin.ts +53 -0
- package/src/yaml.ts +45 -22
- package/dist/client-mcp/server.d.ts +0 -10
- package/dist/client-mcp/server.d.ts.map +0 -1
- package/dist/client-mcp/types.d.ts +0 -16
- package/dist/client-mcp/types.d.ts.map +0 -1
- package/dist/cloudflare/realtime.d.ts +0 -30
- package/dist/cloudflare/realtime.d.ts.map +0 -1
- package/dist/realtime/buffer.d.ts +0 -18
- package/dist/realtime/buffer.d.ts.map +0 -1
- package/dist/realtime/longpoll.d.ts +0 -23
- package/dist/realtime/longpoll.d.ts.map +0 -1
- package/dist/realtime/protocol.d.ts +0 -50
- package/dist/realtime/protocol.d.ts.map +0 -1
- package/dist/realtime/runtime.d.ts +0 -74
- package/dist/realtime/runtime.d.ts.map +0 -1
- package/src/cloudflare/realtime.ts +0 -234
- package/src/realtime/buffer.ts +0 -101
- package/src/realtime/longpoll.ts +0 -148
- package/src/realtime/protocol.ts +0 -128
- package/src/realtime/runtime.ts +0 -427
package/src/codegen-python.ts
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
/** Python SDK code generator.
|
|
1
|
+
/** Python SDK code generator.
|
|
2
|
+
*
|
|
3
|
+
* Input is the SDK request model (codegen-sdk-model.ts). Identifiers come from per-scope
|
|
4
|
+
* NameScopes (module, class, method) and every spec string reaches source through pyString,
|
|
5
|
+
* docstring or comment writers (codegen-lang.ts).
|
|
6
|
+
*/
|
|
2
7
|
|
|
3
8
|
import { readFileSync } from "node:fs"
|
|
4
|
-
import {
|
|
5
|
-
import { methodsOf, namespacesOf
|
|
6
|
-
import type { IRNamespace } from "./codegen-ir.ts"
|
|
7
|
-
import {
|
|
9
|
+
import type { OpenApiSpecInput } from "./codegen.ts"
|
|
10
|
+
import { methodsOf, namespacesOf } from "./codegen-ir.ts"
|
|
11
|
+
import type { IRNamespace, IRSchema } from "./codegen-ir.ts"
|
|
12
|
+
import { NameScope, cmpCodeUnit, pyClassName, pyDocLines, pyIdentifier, pyString } from "./codegen-lang.ts"
|
|
13
|
+
import { buildSdkModel } from "./codegen-sdk-model.ts"
|
|
14
|
+
import type { SdkModel, SdkOp } from "./codegen-sdk-model.ts"
|
|
15
|
+
import { irToPythonNamed, isPyAttributeKey, pyIdent } from "./python-type-emitter.ts"
|
|
16
|
+
import type { PyTypeNames } from "./python-type-emitter.ts"
|
|
8
17
|
|
|
9
18
|
interface PySDKOptions {
|
|
10
19
|
name?: string
|
|
@@ -25,33 +34,6 @@ interface PySDKResult {
|
|
|
25
34
|
serviceMap: Record<string, ServiceMapEntry>
|
|
26
35
|
}
|
|
27
36
|
|
|
28
|
-
interface OperationInfo {
|
|
29
|
-
operationId: string
|
|
30
|
-
method: string
|
|
31
|
-
path: string
|
|
32
|
-
summary?: string
|
|
33
|
-
description?: string
|
|
34
|
-
pathParams: string[]
|
|
35
|
-
queryParams: ParamInfo[]
|
|
36
|
-
bodySchema: Record<string, unknown> | undefined
|
|
37
|
-
bodyRequired: boolean
|
|
38
|
-
bodyIsStream: boolean
|
|
39
|
-
successType: string
|
|
40
|
-
errorStatuses: number[]
|
|
41
|
-
isSSE: boolean
|
|
42
|
-
isWS: boolean
|
|
43
|
-
isRealtime: boolean
|
|
44
|
-
isIdempotent: boolean
|
|
45
|
-
invalidate: string[]
|
|
46
|
-
parameters: Array<{ in: string; name: string; required: boolean; schema: Record<string, unknown> }>
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
interface ParamInfo {
|
|
50
|
-
name: string
|
|
51
|
-
required: boolean
|
|
52
|
-
schema: Record<string, unknown>
|
|
53
|
-
}
|
|
54
|
-
|
|
55
37
|
const PYTHON_FUTURE = "from __future__ import annotations"
|
|
56
38
|
const STATIC_FILE_NAMES = [
|
|
57
39
|
"_runtime.py",
|
|
@@ -63,11 +45,6 @@ const STATIC_FILE_NAMES = [
|
|
|
63
45
|
"_realtime.py",
|
|
64
46
|
] as const
|
|
65
47
|
const RUNTIME_TEMPLATE_CACHE = new Map<string, string>()
|
|
66
|
-
const WS_EXTENSION = "x-websocket"
|
|
67
|
-
const REALTIME_EXTENSION = "x-realtime"
|
|
68
|
-
const INVALIDATE_EXTENSION = "x-invalidate"
|
|
69
|
-
const IDEMPOTENCY_EXTENSION = "x-idempotency-key"
|
|
70
|
-
const HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head", "options"] as const
|
|
71
48
|
|
|
72
49
|
const ERROR_NAMES: Record<number, string> = {
|
|
73
50
|
400: "BadRequestError",
|
|
@@ -83,6 +60,69 @@ const ERROR_NAMES: Record<number, string> = {
|
|
|
83
60
|
504: "GatewayTimeoutError",
|
|
84
61
|
}
|
|
85
62
|
|
|
63
|
+
const ERROR_CLASSES = [
|
|
64
|
+
"APIError",
|
|
65
|
+
"APIStatusError",
|
|
66
|
+
"BadGatewayError",
|
|
67
|
+
"BadRequestError",
|
|
68
|
+
"ConflictError",
|
|
69
|
+
"ForbiddenError",
|
|
70
|
+
"GatewayTimeoutError",
|
|
71
|
+
"InternalServerError",
|
|
72
|
+
"NotFoundError",
|
|
73
|
+
"RateLimitError",
|
|
74
|
+
"ServiceUnavailableError",
|
|
75
|
+
"UnauthorizedError",
|
|
76
|
+
"UnprocessableEntityError",
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
const TRANSPORT_EXPORTS = [
|
|
80
|
+
"LongpollAdapter",
|
|
81
|
+
"SseAdapter",
|
|
82
|
+
"TransportAdapter",
|
|
83
|
+
"TransportConn",
|
|
84
|
+
"TransportKind",
|
|
85
|
+
"TransportOpts",
|
|
86
|
+
"WsAdapter",
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
const REALTIME_EXPORTS = ["ConnectionState", "ResumableConnection"]
|
|
90
|
+
|
|
91
|
+
/** Names a generated module-level type must not take: package exports, typing and builtins. */
|
|
92
|
+
const PY_MODULE_RESERVED = [
|
|
93
|
+
"AsyncSDK",
|
|
94
|
+
"SDK",
|
|
95
|
+
...ERROR_CLASSES,
|
|
96
|
+
...TRANSPORT_EXPORTS,
|
|
97
|
+
...REALTIME_EXPORTS,
|
|
98
|
+
"ClientConfig",
|
|
99
|
+
"InvalidationConfig",
|
|
100
|
+
"SDKResult",
|
|
101
|
+
"SSEEvent",
|
|
102
|
+
"PathParamError",
|
|
103
|
+
"FileInput",
|
|
104
|
+
"Any",
|
|
105
|
+
"AsyncIterator",
|
|
106
|
+
"Literal",
|
|
107
|
+
"NotRequired",
|
|
108
|
+
"TypeAlias",
|
|
109
|
+
"TypedDict",
|
|
110
|
+
"bool",
|
|
111
|
+
"bytes",
|
|
112
|
+
"dict",
|
|
113
|
+
"float",
|
|
114
|
+
"int",
|
|
115
|
+
"list",
|
|
116
|
+
"object",
|
|
117
|
+
"str",
|
|
118
|
+
"tuple",
|
|
119
|
+
"type",
|
|
120
|
+
"httpx",
|
|
121
|
+
"asyncio",
|
|
122
|
+
"threading",
|
|
123
|
+
"uuid",
|
|
124
|
+
]
|
|
125
|
+
|
|
86
126
|
export function loadPythonRuntimeTemplates(): Map<string, string> {
|
|
87
127
|
if (RUNTIME_TEMPLATE_CACHE.size > 0) return RUNTIME_TEMPLATE_CACHE
|
|
88
128
|
for (const name of STATIC_FILE_NAMES) {
|
|
@@ -93,845 +133,702 @@ export function loadPythonRuntimeTemplates(): Map<string, string> {
|
|
|
93
133
|
return RUNTIME_TEMPLATE_CACHE
|
|
94
134
|
}
|
|
95
135
|
|
|
96
|
-
|
|
97
|
-
spec: Record<string, unknown>,
|
|
98
|
-
schema: Record<string, unknown> | undefined,
|
|
99
|
-
): Record<string, unknown> | undefined {
|
|
100
|
-
if (!schema) return undefined
|
|
101
|
-
if (!schema.$ref) return schema
|
|
102
|
-
const ref = schema.$ref as string
|
|
103
|
-
const parts = ref.replace(/^#\//, "").split("/")
|
|
104
|
-
let cur: unknown = spec
|
|
105
|
-
for (const part of parts) {
|
|
106
|
-
cur = (cur as Record<string, unknown>)[part]
|
|
107
|
-
}
|
|
108
|
-
return cur as Record<string, unknown> | undefined
|
|
109
|
-
}
|
|
136
|
+
/* ── types ── */
|
|
110
137
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (code === "204") return "None"
|
|
119
|
-
const content = resp.content as Record<string, Record<string, unknown>> | undefined
|
|
120
|
-
if (!content) continue
|
|
121
|
-
const jsonContent = content["application/json"]
|
|
122
|
-
if (!jsonContent) continue
|
|
123
|
-
const schema = resolveSchema(spec, jsonContent.schema as Record<string, unknown> | undefined)
|
|
124
|
-
if (!schema) continue
|
|
125
|
-
const rawRef = (jsonContent.schema as Record<string, unknown> | undefined)?.$ref as string | undefined
|
|
126
|
-
if (rawRef) {
|
|
127
|
-
const parts = rawRef.split("/")
|
|
128
|
-
return parts[parts.length - 1]
|
|
129
|
-
}
|
|
130
|
-
return jsonSchemaToPy(schema, 0)
|
|
131
|
-
}
|
|
132
|
-
return "None"
|
|
138
|
+
type PyNames = {
|
|
139
|
+
module: NameScope
|
|
140
|
+
types: PyTypeNames
|
|
141
|
+
/** class name → source, for every emitted type (schemas first, then hoisted). */
|
|
142
|
+
decls: Map<string, string>
|
|
143
|
+
declOrder: string[]
|
|
144
|
+
schemaNames: Map<string, string>
|
|
133
145
|
}
|
|
134
146
|
|
|
135
|
-
function
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
.
|
|
140
|
-
|
|
141
|
-
|
|
147
|
+
function typedDictSource(name: string, ir: Extract<IRSchema, { kind: "object" }>, names: PyTypeNames): string {
|
|
148
|
+
const fields = ir.fields.slice().sort((a, b) => cmpCodeUnit(a.name, b.name))
|
|
149
|
+
const rendered = fields.map((f) => {
|
|
150
|
+
const t = irToPythonNamed(f.schema, names, name, f.name)
|
|
151
|
+
return { annotation: f.required ? t : `NotRequired[${t}]`, field: f }
|
|
152
|
+
})
|
|
153
|
+
if (fields.every((f) => isPyAttributeKey(f.name))) {
|
|
154
|
+
const l = [`class ${name}(TypedDict, total=False):`]
|
|
155
|
+
for (const r of rendered) l.push(` ${r.field.name}: ${r.annotation}`)
|
|
156
|
+
if (rendered.length === 0) l.push(` pass`)
|
|
157
|
+
return l.join("\n")
|
|
158
|
+
}
|
|
159
|
+
/* functional syntax: any JSON key is allowed; annotations are strings, so forward refs work */
|
|
160
|
+
const l = [`${name} = TypedDict(`, ` ${pyString(name)},`, ` {`]
|
|
161
|
+
for (const r of rendered) l.push(` ${pyString(r.field.name)}: ${pyString(r.annotation)},`)
|
|
162
|
+
l.push(` },`, ` total=False,`, `)`)
|
|
163
|
+
return l.join("\n")
|
|
142
164
|
}
|
|
143
165
|
|
|
144
|
-
function
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
const
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (requestBody) {
|
|
171
|
-
const content = requestBody.content as Record<string, Record<string, unknown>> | undefined
|
|
172
|
-
const jsonContent = content?.["application/json"]
|
|
173
|
-
const octetContent = content?.["application/octet-stream"]
|
|
174
|
-
if (octetContent) {
|
|
175
|
-
bodyIsStream = true
|
|
176
|
-
} else if (jsonContent) {
|
|
177
|
-
const rawSchema = jsonContent.schema as Record<string, unknown> | undefined
|
|
178
|
-
bodySchema = resolveSchema(spec, rawSchema)
|
|
179
|
-
if (rawSchema?.$ref) {
|
|
180
|
-
const refParts = (rawSchema.$ref as string).split("/")
|
|
181
|
-
bodySchema = { $ref: rawSchema.$ref, _refName: refParts[refParts.length - 1] }
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
}
|
|
166
|
+
function buildPyNames(model: SdkModel): PyNames {
|
|
167
|
+
const module = new NameScope(PY_MODULE_RESERVED)
|
|
168
|
+
const schemaNames = new Map<string, string>()
|
|
169
|
+
for (const name of model.schemaNames) {
|
|
170
|
+
const base = pyClassName(name)
|
|
171
|
+
schemaNames.set(name, module.has(base) ? module.claim(`${base}Model`) : module.claim(base))
|
|
172
|
+
}
|
|
173
|
+
const decls = new Map<string, string>()
|
|
174
|
+
const declOrder: string[] = []
|
|
175
|
+
const hoistKeys = new Map<string, string>()
|
|
176
|
+
const names: PyNames = { declOrder, decls, module, schemaNames, types: undefined as unknown as PyTypeNames }
|
|
177
|
+
names.types = {
|
|
178
|
+
hoist: (key, base, schema) => {
|
|
179
|
+
const hit = hoistKeys.get(key)
|
|
180
|
+
if (hit) return hit
|
|
181
|
+
const name = module.claim(pyClassName(base))
|
|
182
|
+
hoistKeys.set(key, name)
|
|
183
|
+
decls.set(name, "")
|
|
184
|
+
declOrder.push(name)
|
|
185
|
+
decls.set(name, typedDictSource(name, schema, names.types))
|
|
186
|
+
return name
|
|
187
|
+
},
|
|
188
|
+
ref: (name) => schemaNames.get(name) ?? pyClassName(name),
|
|
189
|
+
}
|
|
190
|
+
return names
|
|
191
|
+
}
|
|
185
192
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
isSSE: !isRealtime && isSSEOperation(operation),
|
|
198
|
-
isWS: !isRealtime && operation[WS_EXTENSION] === true,
|
|
199
|
-
method: httpMethod.toUpperCase(),
|
|
200
|
-
operationId,
|
|
201
|
-
parameters: rawParams as Array<{
|
|
202
|
-
in: string
|
|
203
|
-
name: string
|
|
204
|
-
required: boolean
|
|
205
|
-
schema: Record<string, unknown>
|
|
206
|
-
}>,
|
|
207
|
-
path,
|
|
208
|
-
pathParams,
|
|
209
|
-
queryParams,
|
|
210
|
-
successType: getSuccessType(spec, operation),
|
|
211
|
-
summary: operation.summary as string | undefined,
|
|
212
|
-
})
|
|
193
|
+
function buildPyTypes(model: SdkModel, names: PyNames): string {
|
|
194
|
+
const body: string[] = []
|
|
195
|
+
for (const name of model.schemaNames) {
|
|
196
|
+
const ir = model.ir.schemas[name]
|
|
197
|
+
const pyName = names.types.ref(name)
|
|
198
|
+
if (ir.kind === "object" && ir.fields.length > 0) {
|
|
199
|
+
body.push(typedDictSource(pyName, ir, names.types))
|
|
200
|
+
} else {
|
|
201
|
+
/* string alias: lazy, so the order of definitions does not matter */
|
|
202
|
+
const t = irToPythonNamed(ir, names.types, pyName, "")
|
|
203
|
+
body.push(`${pyName}: TypeAlias = ${pyString(t)}`)
|
|
213
204
|
}
|
|
205
|
+
body.push("")
|
|
214
206
|
}
|
|
215
|
-
|
|
216
|
-
return ops
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function nestedClassName(path: string[]): string {
|
|
220
|
-
const pascal = path
|
|
221
|
-
.map((seg) => {
|
|
222
|
-
const safe = pyIdent(seg)
|
|
223
|
-
return safe.charAt(0).toUpperCase() + safe.slice(1)
|
|
224
|
-
})
|
|
225
|
-
.join("")
|
|
226
|
-
return `_${pascal}Resource`
|
|
207
|
+
return body.join("\n")
|
|
227
208
|
}
|
|
228
209
|
|
|
229
|
-
function
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
for (const op of ops) {
|
|
240
|
-
opsByOpId.set(op.operationId, op)
|
|
241
|
-
if (!op.operationId.includes(".")) {
|
|
242
|
-
topLevel.push(op)
|
|
243
|
-
}
|
|
210
|
+
function typesFile(schemaSource: string, names: PyNames): string {
|
|
211
|
+
const hoisted = names.declOrder.map((n) => `${names.decls.get(n)}\n`).join("\n")
|
|
212
|
+
const bodyText = `${schemaSource}\n${hoisted}`
|
|
213
|
+
const typing = ["Any", "Literal", "NotRequired", "TypeAlias", "TypedDict"].filter((sym) =>
|
|
214
|
+
new RegExp(`\\b${sym}\\b`).test(bodyText),
|
|
215
|
+
)
|
|
216
|
+
const fileInput = /\bFileInput\b/.test(bodyText)
|
|
217
|
+
/* string aliases mention `Any`/`Literal` only inside quotes; keep them importable for checkers */
|
|
218
|
+
if (/TypeAlias = /.test(bodyText)) {
|
|
219
|
+
for (const sym of ["Any", "Literal"]) if (!typing.includes(sym)) typing.push(sym)
|
|
244
220
|
}
|
|
245
|
-
|
|
246
|
-
|
|
221
|
+
const l: string[] = []
|
|
222
|
+
l.push("# ruff: noqa: E501")
|
|
223
|
+
l.push(PYTHON_FUTURE)
|
|
224
|
+
l.push("")
|
|
225
|
+
if (typing.length > 0) l.push(`from typing import ${typing.sort(cmpCodeUnit).join(", ")}`)
|
|
226
|
+
if (fileInput) l.push(`from ._runtime import FileInput`)
|
|
227
|
+
if (typing.length > 0 || fileInput) l.push("")
|
|
228
|
+
l.push(bodyText)
|
|
229
|
+
const exported = [...names.schemaNames.values(), ...names.declOrder]
|
|
230
|
+
if (exported.length === 0) l.push("__all__: list[str] = []")
|
|
231
|
+
else l.push(`__all__ = [${exported.map(pyString).join(", ")}]`)
|
|
232
|
+
l.push("")
|
|
233
|
+
return l.join("\n")
|
|
247
234
|
}
|
|
248
235
|
|
|
249
|
-
|
|
250
|
-
const safe = name.replace(/[^a-zA-Z0-9_]/g, "_").replace(/^([0-9])/, "_$1")
|
|
251
|
-
return pyIdent(safe)
|
|
252
|
-
}
|
|
236
|
+
/* ── client ── */
|
|
253
237
|
|
|
254
|
-
|
|
255
|
-
if (!bodySchema) return "Any"
|
|
256
|
-
const refName = bodySchema._refName as string | undefined
|
|
257
|
-
if (refName) return refName
|
|
258
|
-
return "Any"
|
|
259
|
-
}
|
|
238
|
+
type PyParam = { local: string; wire: string }
|
|
260
239
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const actionPart = segments[segments.length - 1] ?? op.operationId
|
|
272
|
-
/* convert camelCase to snake_case for Python naming convention */
|
|
273
|
-
const snakeAction = actionPart.replace(/([A-Z])/g, (m) => `_${m.toLowerCase()}`).replace(/^_/, "")
|
|
274
|
-
const ident = pyIdent(snakeAction)
|
|
275
|
-
/* realtime method always named `connect<X>` — follow camelCase convention used elsewhere
|
|
276
|
-
* in the emitted SDK (connectWs, createUser). If operationId already begins with "connect",
|
|
277
|
-
* use verbatim; otherwise prepend "connect" + capitalize first char of action. */
|
|
278
|
-
const alreadyConnectPrefixed = /^connect([A-Z_]|$)/.test(ident)
|
|
279
|
-
const realtimeName = alreadyConnectPrefixed ? ident : `connect${ident.charAt(0).toUpperCase()}${ident.slice(1)}`
|
|
280
|
-
const methodName = op.isRealtime ? realtimeName : ident
|
|
281
|
-
|
|
282
|
-
const posArgs: string[] = ["self"]
|
|
283
|
-
|
|
284
|
-
for (const pp of op.pathParams) {
|
|
285
|
-
posArgs.push(`${pyParamName(pp)}: str`)
|
|
286
|
-
}
|
|
240
|
+
type PyOpPlan = {
|
|
241
|
+
op: SdkOp
|
|
242
|
+
methodName: string
|
|
243
|
+
pathParams: PyParam[]
|
|
244
|
+
queryParams: Array<PyParam & { required: boolean; type: string }>
|
|
245
|
+
headerParams: Array<PyParam & { required: boolean; type: string }>
|
|
246
|
+
body?: { local: string; type: string; required: boolean }
|
|
247
|
+
multipart?: { files: string[]; listFiles: string[] }
|
|
248
|
+
returnType: string
|
|
249
|
+
}
|
|
287
250
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
251
|
+
/** kwargs the generated methods always take; params must not reuse them. */
|
|
252
|
+
/** What client.py may import from the runtime modules; each name only when the bodies use it. */
|
|
253
|
+
const PY_CLIENT_IMPORTS: ReadonlyArray<readonly [string, readonly string[]]> = [
|
|
254
|
+
[
|
|
255
|
+
"._runtime",
|
|
256
|
+
[
|
|
257
|
+
"ClientConfig",
|
|
258
|
+
"SDKResult",
|
|
259
|
+
"_AuthState",
|
|
260
|
+
"_build_headers",
|
|
261
|
+
"_build_url",
|
|
262
|
+
"_do_request_async",
|
|
263
|
+
"_do_request_sync",
|
|
264
|
+
"_expand_path",
|
|
265
|
+
"_form_content",
|
|
266
|
+
"_format_value",
|
|
267
|
+
"_has_header",
|
|
268
|
+
"_multipart_parts",
|
|
269
|
+
"_open_stream",
|
|
270
|
+
"_parse_body",
|
|
271
|
+
"_raise_for_status",
|
|
272
|
+
"_set_header",
|
|
273
|
+
"_to_ws_url",
|
|
274
|
+
],
|
|
275
|
+
],
|
|
276
|
+
["._invalidation", ["_StaleTracker", "_StaleTrackerSync"]],
|
|
277
|
+
["._sse", ["SSEEvent", "parse_sse_stream"]],
|
|
278
|
+
["._ws", ["_TypedWebSocket"]],
|
|
279
|
+
["._transport", ["LongpollAdapter", "SseAdapter", "TransportOpts", "WsAdapter"]],
|
|
280
|
+
["._realtime", ["ResumableConnection"]],
|
|
281
|
+
]
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Module-scope names a generated method body evaluates at runtime (calls and attribute access, not
|
|
285
|
+
* annotations, which `from __future__ import annotations` never evaluates). A parameter local must
|
|
286
|
+
* never shadow one: a path param named `uuid` would turn `str(uuid.uuid4())` into an
|
|
287
|
+
* AttributeError. Underscore names cannot collide, since parameter locals never start with `_`.
|
|
288
|
+
*/
|
|
289
|
+
export const PY_BODY_GLOBALS: readonly string[] = [
|
|
290
|
+
"httpx",
|
|
291
|
+
"str",
|
|
292
|
+
"threading",
|
|
293
|
+
"uuid",
|
|
294
|
+
...PY_CLIENT_IMPORTS.flatMap(([, syms]) => syms.filter((n) => !n.startsWith("_"))),
|
|
295
|
+
]
|
|
296
|
+
|
|
297
|
+
const PY_METHOD_RESERVED = [
|
|
298
|
+
"self",
|
|
299
|
+
"timeout",
|
|
300
|
+
"headers",
|
|
301
|
+
"cancel_token",
|
|
302
|
+
"idempotency_key",
|
|
303
|
+
"last_event_id",
|
|
304
|
+
"protocols",
|
|
305
|
+
"reconnect_token",
|
|
306
|
+
...PY_BODY_GLOBALS,
|
|
307
|
+
]
|
|
308
|
+
|
|
309
|
+
function planPyOp(op: SdkOp, methodName: string, names: PyNames, model: SdkModel): PyOpPlan {
|
|
310
|
+
const locals = new NameScope(PY_METHOD_RESERVED)
|
|
311
|
+
const typeParent = pyClassName(op.id)
|
|
312
|
+
const plan: PyOpPlan = { headerParams: [], methodName, op, pathParams: [], queryParams: [], returnType: "None" }
|
|
313
|
+
const b = op.body
|
|
314
|
+
const hasBodyArg = b !== undefined && op.stream !== "ws" && op.stream !== "realtime"
|
|
315
|
+
const bodyLocal = hasBodyArg ? locals.claim("body") : ""
|
|
316
|
+
for (const p of op.pathParams) plan.pathParams.push({ local: locals.claim(pyIdentifier(p.name)), wire: p.name })
|
|
317
|
+
if (op.stream !== "realtime") {
|
|
318
|
+
for (const q of op.query) {
|
|
319
|
+
if (op.stream === "sse" && /^last[-_]?event[-_]?id$/i.test(q.name)) continue
|
|
320
|
+
plan.queryParams.push({
|
|
321
|
+
local: locals.claim(pyIdentifier(q.name)),
|
|
322
|
+
required: q.required === true,
|
|
323
|
+
type: irToPythonNamed(q.schema, names.types, typeParent, q.name),
|
|
324
|
+
wire: q.name,
|
|
325
|
+
})
|
|
326
|
+
}
|
|
327
|
+
for (const h of op.headers) {
|
|
328
|
+
plan.headerParams.push({
|
|
329
|
+
local: locals.claim(pyIdentifier(h.name)),
|
|
330
|
+
required: h.required === true,
|
|
331
|
+
type: irToPythonNamed(h.schema, names.types, typeParent, h.name),
|
|
332
|
+
wire: h.name,
|
|
333
|
+
})
|
|
302
334
|
}
|
|
303
|
-
lines.push(` ) -> ResumableConnection:`)
|
|
304
|
-
return lines
|
|
305
335
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
336
|
+
if (hasBodyArg && b) {
|
|
337
|
+
let type: string
|
|
338
|
+
switch (b.kind) {
|
|
339
|
+
case "json":
|
|
340
|
+
case "form":
|
|
341
|
+
type = irToPythonNamed(b.schema, names.types, typeParent, "Body")
|
|
342
|
+
break
|
|
343
|
+
case "binary":
|
|
344
|
+
type = "bytes | AsyncIterator[bytes]"
|
|
345
|
+
break
|
|
346
|
+
case "text":
|
|
347
|
+
type = "str"
|
|
348
|
+
break
|
|
349
|
+
case "multipart": {
|
|
350
|
+
const files: string[] = []
|
|
351
|
+
const listFiles: string[] = []
|
|
352
|
+
const fields = b.parts.map((part) => {
|
|
353
|
+
const schema = part.schema ? model.resolve(part.schema) : ({ kind: "unknown" } as IRSchema)
|
|
354
|
+
if (part.type === "file") {
|
|
355
|
+
if (schema.kind === "array") listFiles.push(part.name)
|
|
356
|
+
else files.push(part.name)
|
|
357
|
+
return {
|
|
358
|
+
name: part.name,
|
|
359
|
+
required: false,
|
|
360
|
+
schema: { kind: "unknown" } as IRSchema,
|
|
361
|
+
file: true,
|
|
362
|
+
list: schema.kind === "array",
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return {
|
|
366
|
+
file: false,
|
|
367
|
+
list: false,
|
|
368
|
+
name: part.name,
|
|
369
|
+
required: false,
|
|
370
|
+
schema: part.schema ?? ({ kind: "unknown" } as IRSchema),
|
|
371
|
+
}
|
|
372
|
+
})
|
|
373
|
+
const objectIR: Extract<IRSchema, { kind: "object" }> = {
|
|
374
|
+
fields: fields.map((f) => ({ name: f.name, required: f.required, schema: f.schema })),
|
|
375
|
+
kind: "object",
|
|
376
|
+
}
|
|
377
|
+
type = names.types.hoist(`${op.id}.multipart`, `${typeParent}Body`, objectIR)
|
|
378
|
+
/* file parts: re-render with the FileInput alias instead of Any */
|
|
379
|
+
const decl = names.decls.get(type) ?? ""
|
|
380
|
+
let patched = decl
|
|
381
|
+
for (const f of fields.filter((x) => x.file)) {
|
|
382
|
+
const key = pyString(f.name)
|
|
383
|
+
const t = f.list ? "list[FileInput]" : "FileInput"
|
|
384
|
+
patched = patched
|
|
385
|
+
.replace(` ${f.name}: NotRequired[Any]`, ` ${f.name}: NotRequired[${t}]`)
|
|
386
|
+
.replace(` ${key}: "NotRequired[Any]",`, ` ${key}: ${pyString(`NotRequired[${t}]`)},`)
|
|
387
|
+
}
|
|
388
|
+
names.decls.set(type, patched)
|
|
389
|
+
plan.multipart = { files, listFiles }
|
|
390
|
+
break
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
plan.body = { local: bodyLocal, required: b.required, type }
|
|
311
394
|
}
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
395
|
+
if (op.stream === null) {
|
|
396
|
+
const s = op.success
|
|
397
|
+
if (s.kind === "json")
|
|
398
|
+
plan.returnType = s.schema ? irToPythonNamed(s.schema, names.types, typeParent, "Response") : "Any"
|
|
399
|
+
else if (s.kind === "text") plan.returnType = "str"
|
|
400
|
+
else if (s.kind === "binary") plan.returnType = "bytes"
|
|
401
|
+
else plan.returnType = "None"
|
|
317
402
|
}
|
|
403
|
+
return plan
|
|
404
|
+
}
|
|
318
405
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
406
|
+
function signature(plan: PyOpPlan, throwOnError: boolean, isAsync: boolean): string[] {
|
|
407
|
+
const { op } = plan
|
|
408
|
+
const defKw = op.stream === "ws" || op.stream === "realtime" || !isAsync ? "def" : "async def"
|
|
409
|
+
const pos: string[] = ["self", ...plan.pathParams.map((p) => `${p.local}: str`)]
|
|
410
|
+
const kw: string[] = []
|
|
322
411
|
|
|
323
|
-
if (op.
|
|
324
|
-
|
|
412
|
+
if (op.stream === "realtime") {
|
|
413
|
+
kw.push(
|
|
414
|
+
`reconnect_token: str | None = None`,
|
|
415
|
+
`last_event_id: str | None = None`,
|
|
416
|
+
`headers: dict[str, str] | None = None`,
|
|
417
|
+
)
|
|
418
|
+
return emitSignature(defKw, plan.methodName, pos, kw, "ResumableConnection")
|
|
325
419
|
}
|
|
326
420
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
if (qp.required) {
|
|
331
|
-
posArgs.push(`${pname}: ${pyType}`)
|
|
332
|
-
} else {
|
|
333
|
-
/* skip last-event-id as already handled above */
|
|
334
|
-
if (op.isSSE && (qp.name === "last-event-id" || qp.name === "last_event_id")) continue
|
|
335
|
-
kwArgs.push(`${pname}: ${pyType} | None = None`)
|
|
336
|
-
}
|
|
421
|
+
if (plan.body) {
|
|
422
|
+
if (plan.body.required) pos.push(`${plan.body.local}: ${plan.body.type}`)
|
|
423
|
+
else kw.push(`${plan.body.local}: ${optional(plan.body.type)} = None`)
|
|
337
424
|
}
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
if (op.isIdempotent) kwArgs.push(`idempotency_key: str | None = None`)
|
|
342
|
-
kwArgs.push(`timeout: float | None = None`)
|
|
343
|
-
kwArgs.push(`headers: dict[str, str] | None = None`)
|
|
344
|
-
kwArgs.push(`cancel_token: "threading.Event | None" = None`)
|
|
425
|
+
for (const q of [...plan.queryParams, ...plan.headerParams]) {
|
|
426
|
+
if (q.required) pos.push(`${q.local}: ${q.type}`)
|
|
427
|
+
else kw.push(`${q.local}: ${optional(q.type)} = None`)
|
|
345
428
|
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
returnType = op.successType
|
|
354
|
-
} else {
|
|
355
|
-
returnType = `SDKResult[${op.successType}]`
|
|
429
|
+
if (op.stream === "sse") kw.push(`last_event_id: str | None = None`, `headers: dict[str, str] | None = None`)
|
|
430
|
+
if (op.stream === "ws") {
|
|
431
|
+
kw.push(
|
|
432
|
+
`protocols: list[str] | None = None`,
|
|
433
|
+
`reconnect_token: str | None = None`,
|
|
434
|
+
`headers: dict[str, str] | None = None`,
|
|
435
|
+
)
|
|
356
436
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
const posLine = posArgs.join(", ")
|
|
362
|
-
lines.push(` ${defKw} ${methodName}(${posLine},`)
|
|
363
|
-
lines.push(` *,`)
|
|
364
|
-
for (let i = 0; i < kwArgs.length; i++) {
|
|
365
|
-
const comma = i < kwArgs.length - 1 ? "," : ""
|
|
366
|
-
lines.push(` ${kwArgs[i]}${comma}`)
|
|
367
|
-
}
|
|
368
|
-
lines.push(` ) -> ${returnType}:`)
|
|
437
|
+
if (op.stream === null) {
|
|
438
|
+
if (op.idempotent) kw.push(`idempotency_key: str | None = None`)
|
|
439
|
+
kw.push(`timeout: float | None = None`, `headers: dict[str, str] | None = None`)
|
|
440
|
+
kw.push(`cancel_token: "threading.Event | None" = None`)
|
|
369
441
|
}
|
|
370
442
|
|
|
371
|
-
|
|
443
|
+
let ret: string
|
|
444
|
+
if (op.stream === "sse") ret = "AsyncIterator[SSEEvent]"
|
|
445
|
+
else if (op.stream === "ws") ret = "_TypedWebSocket"
|
|
446
|
+
else if (throwOnError) ret = plan.returnType
|
|
447
|
+
else ret = `SDKResult[${plan.returnType}]`
|
|
448
|
+
return emitSignature(defKw, plan.methodName, pos, kw, ret)
|
|
372
449
|
}
|
|
373
450
|
|
|
374
|
-
function
|
|
375
|
-
|
|
376
|
-
const text = op.summary ?? op.description ?? ""
|
|
377
|
-
if (!text && op.errorStatuses.length === 0) return lines
|
|
378
|
-
|
|
379
|
-
if (op.errorStatuses.length === 0) {
|
|
380
|
-
/* single-line docstring when no Raises section */
|
|
381
|
-
lines.push(` """${text}"""`)
|
|
382
|
-
return lines
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
lines.push(` """${text}`)
|
|
386
|
-
lines.push(``)
|
|
387
|
-
lines.push(` Raises:`)
|
|
388
|
-
for (const status of op.errorStatuses) {
|
|
389
|
-
const name = ERROR_NAMES[status] ?? "APIStatusError"
|
|
390
|
-
lines.push(` ${name}: HTTP ${status}`)
|
|
391
|
-
}
|
|
392
|
-
lines.push(` """`)
|
|
393
|
-
return lines
|
|
451
|
+
function optional(t: string): string {
|
|
452
|
+
return t.endsWith("| None") ? t : `${t} | None`
|
|
394
453
|
}
|
|
395
454
|
|
|
396
|
-
function
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
return ` url = _build_url(${configRef}.base_url, "${op.path}")`
|
|
455
|
+
function emitSignature(defKw: string, name: string, pos: string[], kw: string[], ret: string): string[] {
|
|
456
|
+
if (kw.length === 0) return [` ${defKw} ${name}(${pos.join(", ")}) -> ${ret}:`]
|
|
457
|
+
const l = [` ${defKw} ${name}(${pos.join(", ")},`, ` *,`]
|
|
458
|
+
kw.forEach((k, i) => l.push(` ${k}${i < kw.length - 1 ? "," : ""}`))
|
|
459
|
+
l.push(` ) -> ${ret}:`)
|
|
460
|
+
return l
|
|
403
461
|
}
|
|
404
462
|
|
|
405
|
-
function
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
463
|
+
function docstring(op: SdkOp): string[] {
|
|
464
|
+
const text = op.summary || op.description
|
|
465
|
+
const lines = text ? pyDocLines(text) : []
|
|
466
|
+
if (lines.length === 0 && op.errorStatuses.length === 0) return []
|
|
467
|
+
const out: string[] = []
|
|
468
|
+
if (op.errorStatuses.length === 0 && lines.length === 1) return [` """${lines[0]}"""`]
|
|
469
|
+
out.push(` """${lines[0] ?? ""}`)
|
|
470
|
+
for (const line of lines.slice(1)) out.push(line === "" ? "" : ` ${line}`)
|
|
471
|
+
if (op.errorStatuses.length > 0) {
|
|
472
|
+
out.push(``)
|
|
473
|
+
out.push(` Raises:`)
|
|
474
|
+
for (const status of op.errorStatuses)
|
|
475
|
+
out.push(` ${ERROR_NAMES[status] ?? "APIStatusError"}: HTTP ${status}`)
|
|
476
|
+
}
|
|
477
|
+
out.push(` """`)
|
|
478
|
+
return out
|
|
415
479
|
}
|
|
416
480
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
if (op.pathParams.length === 0) return "None"
|
|
422
|
-
const entries = op.pathParams.map((p) => `"${p}": ${pyParamName(p)}`)
|
|
423
|
-
return `{${entries.join(", ")}}`
|
|
481
|
+
function pathExpr(plan: PyOpPlan): string {
|
|
482
|
+
if (plan.pathParams.length === 0) return pyString(plan.op.path)
|
|
483
|
+
const dict = plan.pathParams.map((p) => `${pyString(p.wire)}: ${p.local}`).join(", ")
|
|
484
|
+
return `_expand_path(${pyString(plan.op.path)}, {${dict}})`
|
|
424
485
|
}
|
|
425
486
|
|
|
426
|
-
function
|
|
427
|
-
|
|
428
|
-
return `
|
|
487
|
+
function paramsDict(plan: PyOpPlan): string {
|
|
488
|
+
const entries = plan.queryParams.map((q) => `${pyString(q.wire)}: ${q.local}`)
|
|
489
|
+
return `{${entries.join(", ")}}`
|
|
429
490
|
}
|
|
430
491
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
` break`,
|
|
442
|
-
` if _idem_existing is None:`,
|
|
443
|
-
` _headers["Idempotency-Key"] = idempotency_key if idempotency_key is not None else str(uuid.uuid4())`,
|
|
444
|
-
]
|
|
492
|
+
function headerLines(plan: PyOpPlan, configRef: string, authRef: string, base: string): string[] {
|
|
493
|
+
const l: string[] = []
|
|
494
|
+
l.push(` _extra: dict[str, str] = ${base}`)
|
|
495
|
+
for (const h of plan.headerParams) {
|
|
496
|
+
l.push(` if ${h.local} is not None:`)
|
|
497
|
+
l.push(` _extra[${pyString(h.wire)}] = _format_value(${h.local})`)
|
|
498
|
+
}
|
|
499
|
+
l.push(` _extra.update(headers or {})`)
|
|
500
|
+
l.push(` _headers = _build_headers(${configRef}, extra=_extra, bearer_token=${authRef}.token)`)
|
|
501
|
+
return l
|
|
445
502
|
}
|
|
446
503
|
|
|
447
|
-
function
|
|
448
|
-
const
|
|
449
|
-
const
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
504
|
+
function bodyArgs(plan: PyOpPlan, l: string[]): string {
|
|
505
|
+
const b = plan.op.body
|
|
506
|
+
const local = plan.body?.local
|
|
507
|
+
if (!b || !local) return ""
|
|
508
|
+
switch (b.kind) {
|
|
509
|
+
case "json":
|
|
510
|
+
return `json=${local}`
|
|
511
|
+
case "form":
|
|
512
|
+
l.push(` _headers.setdefault("Content-Type", "application/x-www-form-urlencoded")`)
|
|
513
|
+
return `content=_form_content(${local})`
|
|
514
|
+
case "text":
|
|
515
|
+
l.push(` _set_header(_headers, "Content-Type", ${pyString(b.contentType)})`)
|
|
516
|
+
return `content=${local}.encode() if ${local} is not None else None`
|
|
517
|
+
case "binary":
|
|
518
|
+
l.push(` _set_header(_headers, "Content-Type", ${pyString(b.contentType)})`)
|
|
519
|
+
return `content=${local}`
|
|
520
|
+
case "multipart": {
|
|
521
|
+
const files = plan.multipart?.files ?? []
|
|
522
|
+
const listFiles = plan.multipart?.listFiles ?? []
|
|
523
|
+
l.push(
|
|
524
|
+
` _data, _files = _multipart_parts(${local}, files=[${files.map(pyString).join(", ")}], list_files=[${listFiles.map(pyString).join(", ")}])`,
|
|
525
|
+
)
|
|
526
|
+
return `data=_data, files=_files`
|
|
527
|
+
}
|
|
463
528
|
}
|
|
529
|
+
}
|
|
464
530
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
531
|
+
function methodBody(plan: PyOpPlan, throwOnError: boolean, isAsync: boolean, isTopLevel: boolean): string[] {
|
|
532
|
+
const l: string[] = []
|
|
533
|
+
const { op } = plan
|
|
534
|
+
const self = isTopLevel ? "self" : "self._client"
|
|
535
|
+
const configRef = `${self}._config`
|
|
536
|
+
const authRef = `${self}._auth`
|
|
537
|
+
const clientRef = isAsync ? `${self}._async_client` : `${self}._sync_client`
|
|
538
|
+
const trackerRef = `${self}._stale_tracker`
|
|
539
|
+
const aw = isAsync ? "await " : ""
|
|
540
|
+
|
|
541
|
+
if (op.stream === "realtime") {
|
|
542
|
+
l.push(` _url = _build_url(${configRef}.base_url, ${pathExpr(plan)})`)
|
|
543
|
+
l.push(` _opts = TransportOpts(`)
|
|
544
|
+
l.push(` reconnect_token=reconnect_token,`)
|
|
545
|
+
l.push(` last_event_id=last_event_id,`)
|
|
546
|
+
l.push(` headers=_build_headers(${configRef}, extra=headers, bearer_token=${authRef}.token),`)
|
|
547
|
+
l.push(` )`)
|
|
548
|
+
l.push(` _transports: list[Any] = [WsAdapter(), SseAdapter(), LongpollAdapter()]`)
|
|
549
|
+
l.push(` return ResumableConnection(_url, _transports, _opts)`)
|
|
550
|
+
return l
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
l.push(` _path = ${pathExpr(plan)}`)
|
|
554
|
+
|
|
555
|
+
if (op.stream === "ws") {
|
|
556
|
+
l.push(` _params: dict[str, Any] = ${paramsDict(plan)}`)
|
|
557
|
+
l.push(` if reconnect_token is not None:`)
|
|
558
|
+
l.push(` _params["reconnect_token"] = reconnect_token`)
|
|
559
|
+
l.push(...headerLines(plan, configRef, authRef, "{}"))
|
|
560
|
+
l.push(` _url = _build_url(${configRef}.base_url, _path, _params)`)
|
|
561
|
+
l.push(` _ws_url = _to_ws_url(_url)`)
|
|
562
|
+
l.push(` return _TypedWebSocket(_ws_url, protocols=protocols, extra_headers=_headers)`)
|
|
563
|
+
return l
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
if (op.stream === "sse") {
|
|
567
|
+
l.push(` _params: dict[str, Any] = ${paramsDict(plan)}`)
|
|
568
|
+
l.push(...headerLines(plan, configRef, authRef, `{"Accept": "text/event-stream"}`))
|
|
569
|
+
l.push(` if last_event_id is not None:`)
|
|
570
|
+
l.push(` _headers["Last-Event-ID"] = last_event_id`)
|
|
571
|
+
const args = bodyArgs(plan, l)
|
|
572
|
+
l.push(` _url = _build_url(${configRef}.base_url, _path, _params)`)
|
|
573
|
+
l.push(
|
|
574
|
+
` async with _open_stream(${clientRef}, ${configRef}, ${pyString(op.method)}, _url, _headers${args ? `, ${args}` : ""}) as _response:`,
|
|
575
|
+
)
|
|
576
|
+
l.push(` async for _event in parse_sse_stream(_response):`)
|
|
577
|
+
l.push(` yield _event`)
|
|
578
|
+
return l
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
l.push(` _params: dict[str, Any] = ${paramsDict(plan)}`)
|
|
582
|
+
l.push(...headerLines(plan, configRef, authRef, "{}"))
|
|
583
|
+
if (op.idempotent) {
|
|
584
|
+
/* explicit header (any casing) > idempotency_key kwarg > uuid4 */
|
|
585
|
+
l.push(` if not _has_header(_headers, "Idempotency-Key"):`)
|
|
586
|
+
l.push(
|
|
587
|
+
` _headers["Idempotency-Key"] = idempotency_key if idempotency_key is not None else str(uuid.uuid4())`,
|
|
475
588
|
)
|
|
476
|
-
} else {
|
|
477
|
-
lines.push(` _headers = _build_headers(${configRef}, extra=headers)`)
|
|
478
589
|
}
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
590
|
+
const args = bodyArgs(plan, l)
|
|
591
|
+
const dictEntries = plan.pathParams.map((p) => `${pyString(p.wire)}: ${p.local}`)
|
|
592
|
+
l.push(
|
|
593
|
+
` _path_params: dict[str, str] | None = ${dictEntries.length > 0 ? `{${dictEntries.join(", ")}}` : "None"}`,
|
|
594
|
+
)
|
|
595
|
+
l.push(` _selector = ${pyString(`${op.method} `)} + _path`)
|
|
596
|
+
l.push(` _request_meta = ${aw}${trackerRef}.build_request_meta(_selector, _path, ${pyString(op.method)})`)
|
|
597
|
+
l.push(` _url = _build_url(${configRef}.base_url, _path, _params)`)
|
|
598
|
+
l.push(` _response = ${aw}${isAsync ? "_do_request_async" : "_do_request_sync"}(`)
|
|
599
|
+
l.push(` ${clientRef}, ${configRef}, ${pyString(op.method)}, _url,`)
|
|
600
|
+
l.push(` headers=_headers,${args ? ` ${args},` : ""} timeout=timeout,`)
|
|
601
|
+
l.push(` cancel_token=cancel_token, operation=${pyString(`${op.method} ${op.path}`)},`)
|
|
602
|
+
l.push(` request_meta=_request_meta, auth=${authRef},`)
|
|
603
|
+
l.push(` )`)
|
|
604
|
+
l.push(` if 200 <= _response.status_code < 300:`)
|
|
605
|
+
const targets = op.invalidates.map(pyString).join(", ")
|
|
606
|
+
l.push(` ${aw}${trackerRef}.mark_stale([${targets}], _path_params, _selector)`)
|
|
607
|
+
l.push(` if _request_meta is not None and _request_meta.is_stale:`)
|
|
608
|
+
l.push(
|
|
609
|
+
` ${aw}${trackerRef}.clear_stale(_selector, _path, ${pyString(op.method)}, _request_meta.seq_snapshot)`,
|
|
610
|
+
)
|
|
611
|
+
const kind = op.success.kind
|
|
612
|
+
if (throwOnError) {
|
|
613
|
+
l.push(` _raise_for_status(_response)`)
|
|
614
|
+
l.push(` return _parse_body(_response, ${pyString(kind)})`)
|
|
487
615
|
} else {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
lines.push(` _params["reconnect_token"] = reconnect_token`)
|
|
496
|
-
}
|
|
497
|
-
lines.push(` if _params:`)
|
|
498
|
-
lines.push(` from urllib.parse import urlencode`)
|
|
499
|
-
lines.push(` ws_url = f"{ws_url}?{urlencode({k: v for k, v in _params.items() if v is not None})}"`)
|
|
500
|
-
lines.push(` return _TypedWebSocket(ws_url, protocols=protocols, extra_headers=_headers)`)
|
|
501
|
-
return lines
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
if (op.isSSE) {
|
|
505
|
-
lines.push(` async with ${asyncClientRef}.stream(`)
|
|
506
|
-
lines.push(` "${op.method}", url, headers=_headers,`)
|
|
507
|
-
lines.push(` params={k: v for k, v in _params.items() if v is not None} or None,`)
|
|
508
|
-
lines.push(` ) as _response:`)
|
|
509
|
-
lines.push(` _raise_for_status(_response)`)
|
|
510
|
-
lines.push(` async for _event in parse_sse_stream(_response):`)
|
|
511
|
-
lines.push(` yield _event`)
|
|
512
|
-
return lines
|
|
616
|
+
l.push(` if not 200 <= _response.status_code < 300:`)
|
|
617
|
+
l.push(
|
|
618
|
+
` return SDKResult(data=None, error=_parse_body(_response), status=_response.status_code, response=_response)`,
|
|
619
|
+
)
|
|
620
|
+
l.push(
|
|
621
|
+
` return SDKResult(data=_parse_body(_response, ${pyString(kind)}), error=None, status=_response.status_code, response=_response)`,
|
|
622
|
+
)
|
|
513
623
|
}
|
|
514
|
-
|
|
515
|
-
const pathParamsDict = buildPathParamsDict(op)
|
|
516
|
-
const invalidateLiteral = buildInvalidateListLiteral(op)
|
|
517
|
-
lines.push(` _path_params: dict[str, str] | None = ${pathParamsDict}`)
|
|
518
|
-
lines.push(` _concrete_path = interpolate_path("${op.path}", _path_params)`)
|
|
519
|
-
lines.push(` _concrete_selector = "${op.method} " + _concrete_path`)
|
|
520
|
-
lines.push(` _request_meta = await ${trackerRef}.build_request_meta(`)
|
|
521
|
-
lines.push(` _concrete_selector, _concrete_path, "${op.method}",`)
|
|
522
|
-
lines.push(` )`)
|
|
523
|
-
|
|
524
|
-
let bodyArg: string
|
|
525
|
-
if (op.bodyIsStream) bodyArg = "json=None, content=body"
|
|
526
|
-
else if (op.bodySchema) bodyArg = "json=body"
|
|
527
|
-
else bodyArg = "json=None"
|
|
528
|
-
lines.push(` _response = await _do_request_async(`)
|
|
529
|
-
lines.push(` ${asyncClientRef}, ${configRef}, "${op.method}", url,`)
|
|
530
|
-
lines.push(` headers=_headers, ${bodyArg}, params=_params, timeout=timeout,`)
|
|
531
|
-
lines.push(` cancel_token=cancel_token, operation="${op.method} ${op.path}",`)
|
|
532
|
-
lines.push(` request_meta=_request_meta,`)
|
|
533
|
-
lines.push(` )`)
|
|
534
|
-
lines.push(` if _response.status_code < 400:`)
|
|
535
|
-
lines.push(` await ${trackerRef}.mark_stale(`)
|
|
536
|
-
lines.push(` ${invalidateLiteral}, _path_params, _concrete_selector,`)
|
|
537
|
-
lines.push(` )`)
|
|
538
|
-
lines.push(` if _request_meta is not None and _request_meta.is_stale:`)
|
|
539
|
-
lines.push(` await ${trackerRef}.clear_stale(`)
|
|
540
|
-
lines.push(` _concrete_selector, _concrete_path, "${op.method}",`)
|
|
541
|
-
lines.push(` _request_meta.seq_snapshot,`)
|
|
542
|
-
lines.push(` )`)
|
|
543
|
-
lines.push(...buildResponseLines(throwOnError))
|
|
544
|
-
|
|
545
|
-
return lines
|
|
624
|
+
return l
|
|
546
625
|
}
|
|
547
626
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
lines.push(` _headers = _build_headers(${configRef}, extra=headers)`)
|
|
556
|
-
lines.push(...buildIdempotencyLines(op))
|
|
557
|
-
|
|
558
|
-
const qpNames = op.queryParams.map((qp) => `"${qp.name}": ${pyParamName(qp.name)}`)
|
|
559
|
-
if (qpNames.length > 0) {
|
|
560
|
-
lines.push(` _params: dict[str, Any] = {${qpNames.join(", ")}}`)
|
|
561
|
-
} else {
|
|
562
|
-
lines.push(` _params: dict[str, Any] = {}`)
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
const pathParamsDict = buildPathParamsDict(op)
|
|
566
|
-
const invalidateLiteral = buildInvalidateListLiteral(op)
|
|
567
|
-
lines.push(` _path_params: dict[str, str] | None = ${pathParamsDict}`)
|
|
568
|
-
lines.push(` _concrete_path = interpolate_path("${op.path}", _path_params)`)
|
|
569
|
-
lines.push(` _concrete_selector = "${op.method} " + _concrete_path`)
|
|
570
|
-
lines.push(` _request_meta = ${trackerRef}.build_request_meta(`)
|
|
571
|
-
lines.push(` _concrete_selector, _concrete_path, "${op.method}",`)
|
|
572
|
-
lines.push(` )`)
|
|
573
|
-
|
|
574
|
-
const jsonArg = op.bodySchema ? "json=body" : "json=None"
|
|
575
|
-
lines.push(` _response = _do_request_sync(`)
|
|
576
|
-
lines.push(` ${syncClientRef}, ${configRef}, "${op.method}", url,`)
|
|
577
|
-
lines.push(` headers=_headers, ${jsonArg}, params=_params, timeout=timeout,`)
|
|
578
|
-
lines.push(` cancel_token=cancel_token, operation="${op.method} ${op.path}",`)
|
|
579
|
-
lines.push(` request_meta=_request_meta,`)
|
|
580
|
-
lines.push(` )`)
|
|
581
|
-
lines.push(` if _response.status_code < 400:`)
|
|
582
|
-
lines.push(` ${trackerRef}.mark_stale(`)
|
|
583
|
-
lines.push(` ${invalidateLiteral}, _path_params, _concrete_selector,`)
|
|
584
|
-
lines.push(` )`)
|
|
585
|
-
lines.push(` if _request_meta is not None and _request_meta.is_stale:`)
|
|
586
|
-
lines.push(` ${trackerRef}.clear_stale(`)
|
|
587
|
-
lines.push(` _concrete_selector, _concrete_path, "${op.method}",`)
|
|
588
|
-
lines.push(` _request_meta.seq_snapshot,`)
|
|
589
|
-
lines.push(` )`)
|
|
590
|
-
lines.push(...buildResponseLines(throwOnError))
|
|
591
|
-
|
|
592
|
-
return lines
|
|
627
|
+
type PyClassNode = {
|
|
628
|
+
asyncName: string
|
|
629
|
+
syncName?: string
|
|
630
|
+
path: string[]
|
|
631
|
+
ns: IRNamespace
|
|
632
|
+
children: Array<{ attr: string; node: PyClassNode }>
|
|
633
|
+
plans: PyOpPlan[]
|
|
593
634
|
}
|
|
594
635
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
const schemas = (spec.components as Record<string, unknown> | undefined)?.schemas as
|
|
599
|
-
| Record<string, Record<string, unknown>>
|
|
600
|
-
| undefined
|
|
601
|
-
|
|
602
|
-
const body: string[] = []
|
|
603
|
-
const allNames: string[] = []
|
|
604
|
-
|
|
605
|
-
if (schemas) {
|
|
606
|
-
for (const [name, schema] of Object.entries(schemas).sort(([a], [b]) => a.localeCompare(b))) {
|
|
607
|
-
allNames.push(name)
|
|
608
|
-
const props = schema.properties as Record<string, Record<string, unknown>> | undefined
|
|
609
|
-
const required = new Set((schema.required as string[] | undefined) ?? [])
|
|
610
|
-
|
|
611
|
-
if (props && Object.keys(props).length > 0) {
|
|
612
|
-
body.push(`class ${name}(TypedDict, total=False):`)
|
|
613
|
-
for (const [key, val] of Object.entries(props).sort(([a], [b]) => a.localeCompare(b))) {
|
|
614
|
-
const pyType = jsonSchemaToPy(val, 0)
|
|
615
|
-
if (required.has(key)) {
|
|
616
|
-
body.push(` ${key}: ${pyType}`)
|
|
617
|
-
} else {
|
|
618
|
-
body.push(` ${key}: NotRequired[${pyType}]`)
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
body.push("")
|
|
622
|
-
} else if (schema.type === "string" && schema.format === "uuid") {
|
|
623
|
-
body.push(`${name} = NewType("${name}", str)`)
|
|
624
|
-
body.push("")
|
|
625
|
-
} else {
|
|
626
|
-
body.push(`${name} = NewType("${name}", Any) # type: ignore[misc]`)
|
|
627
|
-
body.push("")
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
const bodyText = body.join("\n")
|
|
633
|
-
const typingImports = new Set<string>(["TypedDict", "Literal", "NotRequired", "NewType"])
|
|
634
|
-
if (!bodyText.includes("NewType(")) typingImports.delete("NewType")
|
|
635
|
-
if (!/\bAny\b/.test(bodyText)) typingImports.delete("Any")
|
|
636
|
-
else typingImports.add("Any")
|
|
637
|
-
if (!/\bLiteral\b/.test(bodyText)) typingImports.delete("Literal")
|
|
638
|
-
if (!/\bNotRequired\b/.test(bodyText)) typingImports.delete("NotRequired")
|
|
639
|
-
if (!/\bTypedDict\b/.test(bodyText)) typingImports.delete("TypedDict")
|
|
636
|
+
function isSyncCapable(op: SdkOp): boolean {
|
|
637
|
+
return op.stream === null && op.body?.kind !== "binary"
|
|
638
|
+
}
|
|
640
639
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
l.push("")
|
|
645
|
-
if (typingImports.size > 0) {
|
|
646
|
-
const sorted = [...typingImports].sort()
|
|
647
|
-
l.push(`from typing import ${sorted.join(", ")}`)
|
|
648
|
-
l.push("")
|
|
649
|
-
}
|
|
650
|
-
if (bodyText.length > 0) {
|
|
651
|
-
l.push(bodyText)
|
|
652
|
-
}
|
|
640
|
+
function hasSyncContent(node: PyClassNode): boolean {
|
|
641
|
+
return node.plans.some((p) => isSyncCapable(p.op)) || node.children.some((c) => hasSyncContent(c.node))
|
|
642
|
+
}
|
|
653
643
|
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
644
|
+
function trackerInit(cls: string): string[] {
|
|
645
|
+
return [
|
|
646
|
+
" _inv = config.invalidation",
|
|
647
|
+
` self._stale_tracker = ${cls}(`,
|
|
648
|
+
" stale_time=_inv.stale_time if _inv is not None else 0.0,",
|
|
649
|
+
" stale_max_entries=(",
|
|
650
|
+
" _inv.stale_max_entries if _inv is not None else 1000",
|
|
651
|
+
" ),",
|
|
652
|
+
" max_sources_per_target=(",
|
|
653
|
+
" _inv.max_sources_per_target if _inv is not None else 16",
|
|
654
|
+
" ),",
|
|
655
|
+
" )",
|
|
656
|
+
]
|
|
660
657
|
}
|
|
661
658
|
|
|
662
659
|
function buildPyClient(
|
|
663
|
-
|
|
660
|
+
model: SdkModel,
|
|
661
|
+
names: PyNames,
|
|
664
662
|
options: PySDKOptions,
|
|
665
663
|
serviceMap: Record<string, ServiceMapEntry>,
|
|
666
664
|
): string {
|
|
667
665
|
const throwOnError = options.throwOnError !== false
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
666
|
+
function planNs(ns: IRNamespace, path: string[], reserved: string[]): PyClassNode {
|
|
667
|
+
const members = new NameScope(reserved)
|
|
668
|
+
const node: PyClassNode = {
|
|
669
|
+
asyncName: path.length === 0 ? "AsyncSDK" : names.module.claim(`_${path.map(pyClassName).join("")}Resource`),
|
|
670
|
+
children: [],
|
|
671
|
+
ns,
|
|
672
|
+
path,
|
|
673
|
+
plans: [],
|
|
674
|
+
}
|
|
675
|
+
if (path.length > 0) node.syncName = names.module.claim(`_Sync${path.map(pyClassName).join("")}Resource`)
|
|
676
|
+
for (const [seg, childNs] of namespacesOf(ns)) {
|
|
677
|
+
const attr = members.claim(pyIdent(seg))
|
|
678
|
+
node.children.push({ attr, node: planNs(childNs, [...path, seg], ["_client"]) })
|
|
679
|
+
}
|
|
680
|
+
for (const [seg, irOp] of methodsOf(ns)) {
|
|
681
|
+
const op = model.opsById.get(irOp.id)
|
|
682
|
+
if (!op) continue
|
|
683
|
+
let base = pyIdentifier(seg)
|
|
684
|
+
if (op.stream === "realtime") {
|
|
685
|
+
/* realtime methods keep the `connectX` name they always had */
|
|
686
|
+
const ident = pyIdentifier(seg)
|
|
687
|
+
base = /^connect(_|$)/.test(ident) ? ident : `connect${ident.charAt(0).toUpperCase()}${ident.slice(1)}`
|
|
688
|
+
}
|
|
689
|
+
node.plans.push(planPyOp(op, members.claim(base), names, model))
|
|
690
|
+
}
|
|
691
|
+
return node
|
|
692
|
+
}
|
|
693
|
+
const root = planNs(
|
|
694
|
+
model.ir.tree,
|
|
695
|
+
[],
|
|
696
|
+
["_config", "_auth", "_async_client", "_sync_client", "_stale_tracker", "is_stale", "aclose", "close"],
|
|
697
|
+
)
|
|
674
698
|
|
|
675
699
|
const body: string[] = []
|
|
676
|
-
|
|
677
700
|
const invEntries = Object.entries(serviceMap).filter(([, v]) => v.invalidate && v.invalidate.length > 0)
|
|
678
|
-
|
|
679
701
|
body.push("_INVALIDATION_MAP: dict[str, dict[str, Any]] = {")
|
|
680
702
|
for (const [opId, entry] of invEntries) {
|
|
681
|
-
|
|
682
|
-
body.push(`
|
|
683
|
-
body.push(` "
|
|
684
|
-
body.push(` "
|
|
685
|
-
body.push(` "invalidate": [${invItems}],`)
|
|
703
|
+
body.push(` ${pyString(opId)}: {`)
|
|
704
|
+
body.push(` "method": ${pyString(entry.method)},`)
|
|
705
|
+
body.push(` "path": ${pyString(entry.path)},`)
|
|
706
|
+
body.push(` "invalidate": [${(entry.invalidate ?? []).map(pyString).join(", ")}],`)
|
|
686
707
|
body.push(` },`)
|
|
687
708
|
}
|
|
688
709
|
body.push("}")
|
|
689
710
|
body.push("")
|
|
690
711
|
|
|
691
|
-
/*
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
for (const
|
|
695
|
-
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
if (path.length === 0) return
|
|
699
|
-
|
|
700
|
-
const className = nestedClassName(path)
|
|
701
|
-
const parentType = path.length === 1 ? '"AsyncSDK"' : nestedClassName(path.slice(0, -1))
|
|
702
|
-
body.push(`class ${className}:`)
|
|
703
|
-
body.push(` def __init__(self, _client: ${parentType}) -> None:`)
|
|
712
|
+
/* children before parents so class names resolve at definition time */
|
|
713
|
+
/* every resource holds the root SDK: nested resources reach config and clients directly */
|
|
714
|
+
function emitAsync(node: PyClassNode): void {
|
|
715
|
+
for (const c of node.children) emitAsync(c.node)
|
|
716
|
+
if (node.path.length === 0) return
|
|
717
|
+
body.push(`class ${node.asyncName}:`)
|
|
718
|
+
body.push(` def __init__(self, _client: "AsyncSDK") -> None:`)
|
|
704
719
|
body.push(` self._client = _client`)
|
|
705
|
-
|
|
706
|
-
for (const [seg] of namespacesOf(ns)) {
|
|
707
|
-
const childClass = nestedClassName([...path, seg])
|
|
708
|
-
body.push(` self.${pyIdent(seg)} = ${childClass}(self)`)
|
|
709
|
-
}
|
|
720
|
+
for (const c of node.children) body.push(` self.${c.attr} = ${c.node.asyncName}(_client)`)
|
|
710
721
|
body.push("")
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
body.push(...buildMethodSignature(opInfo, throwOnError, true))
|
|
716
|
-
body.push(...buildMethodDocstring(opInfo))
|
|
717
|
-
body.push(...buildAsyncMethodBody(opInfo, throwOnError))
|
|
722
|
+
for (const plan of node.plans) {
|
|
723
|
+
body.push(...signature(plan, throwOnError, true))
|
|
724
|
+
body.push(...docstring(plan.op))
|
|
725
|
+
body.push(...methodBody(plan, throwOnError, true, false))
|
|
718
726
|
body.push("")
|
|
719
727
|
}
|
|
720
728
|
body.push("")
|
|
721
729
|
}
|
|
722
730
|
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
if (path.length === 0) return
|
|
730
|
-
|
|
731
|
-
const syncMethods = methodsOf(ns).filter(([, op]) => {
|
|
732
|
-
const opInfo = opsByOpId.get(op.id)
|
|
733
|
-
return opInfo && !opInfo.isSSE && !opInfo.isWS && !opInfo.isRealtime && !opInfo.bodyIsStream
|
|
734
|
-
})
|
|
735
|
-
/* check if any sub-namespace has sync methods */
|
|
736
|
-
const hasAnySyncContent = syncMethods.length > 0 || namespacesOf(ns).length > 0
|
|
737
|
-
if (!hasAnySyncContent) return
|
|
738
|
-
|
|
739
|
-
const className = nestedClassName(path)
|
|
740
|
-
const syncClassName = `_Sync${className.slice(1)}`
|
|
741
|
-
const parentType = path.length === 1 ? '"SDK"' : `_Sync${nestedClassName(path.slice(0, -1)).slice(1)}`
|
|
742
|
-
body.push(`class ${syncClassName}:`)
|
|
743
|
-
body.push(` def __init__(self, _client: ${parentType}) -> None:`)
|
|
731
|
+
function emitSync(node: PyClassNode): void {
|
|
732
|
+
for (const c of node.children) emitSync(c.node)
|
|
733
|
+
if (node.path.length === 0 || !hasSyncContent(node)) return
|
|
734
|
+
body.push(`class ${node.syncName}:`)
|
|
735
|
+
body.push(` def __init__(self, _client: "SDK") -> None:`)
|
|
744
736
|
body.push(` self._client = _client`)
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
const childClass = `_Sync${nestedClassName([...path, seg]).slice(1)}`
|
|
748
|
-
const childSyncMethods = methodsOf(childNs).filter(([, op]) => {
|
|
749
|
-
const opInfo = opsByOpId.get(op.id)
|
|
750
|
-
return opInfo && !opInfo.isSSE && !opInfo.isWS && !opInfo.isRealtime && !opInfo.bodyIsStream
|
|
751
|
-
})
|
|
752
|
-
if (childSyncMethods.length > 0 || namespacesOf(childNs).length > 0) {
|
|
753
|
-
body.push(` self.${pyIdent(seg)} = ${childClass}(self)`)
|
|
754
|
-
}
|
|
737
|
+
for (const c of node.children) {
|
|
738
|
+
if (hasSyncContent(c.node)) body.push(` self.${c.attr} = ${c.node.syncName}(_client)`)
|
|
755
739
|
}
|
|
756
740
|
body.push("")
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
body.push(...buildMethodSignature(opInfo, throwOnError, false))
|
|
762
|
-
body.push(...buildMethodDocstring(opInfo))
|
|
763
|
-
body.push(...buildSyncMethodBody(opInfo, throwOnError))
|
|
741
|
+
for (const plan of node.plans.filter((p) => isSyncCapable(p.op))) {
|
|
742
|
+
body.push(...signature(plan, throwOnError, false))
|
|
743
|
+
body.push(...docstring(plan.op))
|
|
744
|
+
body.push(...methodBody(plan, throwOnError, false, false))
|
|
764
745
|
body.push("")
|
|
765
746
|
}
|
|
766
747
|
body.push("")
|
|
767
748
|
}
|
|
768
749
|
|
|
769
|
-
|
|
770
|
-
|
|
750
|
+
emitAsync(root)
|
|
751
|
+
emitSync(root)
|
|
771
752
|
|
|
772
753
|
body.push("class SDK:")
|
|
773
754
|
body.push(" def __init__(self, config: ClientConfig) -> None:")
|
|
774
755
|
body.push(" self._config = config")
|
|
756
|
+
body.push(" self._auth = _AuthState(config.bearer_token)")
|
|
775
757
|
body.push(" self._sync_client = httpx.Client(")
|
|
776
758
|
body.push(" transport=config.sync_transport,")
|
|
777
759
|
body.push(" )")
|
|
778
|
-
body.push("
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
body.push(" stale_max_entries=(")
|
|
782
|
-
body.push(" _inv.stale_max_entries if _inv is not None else 1000")
|
|
783
|
-
body.push(" ),")
|
|
784
|
-
body.push(" max_sources_per_target=(")
|
|
785
|
-
body.push(" _inv.max_sources_per_target if _inv is not None else 16")
|
|
786
|
-
body.push(" ),")
|
|
787
|
-
body.push(" )")
|
|
788
|
-
for (const [seg] of namespacesOf(tree)) {
|
|
789
|
-
const className = `_Sync${nestedClassName([seg]).slice(1)}`
|
|
790
|
-
body.push(` self.${pyIdent(seg)} = ${className}(self)`)
|
|
760
|
+
body.push(...trackerInit("_StaleTrackerSync"))
|
|
761
|
+
for (const c of root.children) {
|
|
762
|
+
if (hasSyncContent(c.node)) body.push(` self.${c.attr} = ${c.node.syncName}(self)`)
|
|
791
763
|
}
|
|
792
764
|
body.push("")
|
|
765
|
+
body.push(" def close(self) -> None:")
|
|
766
|
+
body.push(' """Closes the underlying HTTP client."""')
|
|
767
|
+
body.push(" self._sync_client.close()")
|
|
768
|
+
body.push("")
|
|
793
769
|
body.push(" def is_stale(self, method: str, path: str) -> bool:")
|
|
794
770
|
body.push(' """Returns True when (method, path) sits inside an active stale window."""')
|
|
795
771
|
body.push(" return self._stale_tracker.is_stale(method, path)")
|
|
796
772
|
body.push("")
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
body.push(...
|
|
801
|
-
body.push(...buildMethodDocstring(op))
|
|
802
|
-
const bodyLines = buildSyncMethodBody(op, throwOnError, true)
|
|
803
|
-
body.push(...bodyLines)
|
|
773
|
+
for (const plan of root.plans.filter((p) => isSyncCapable(p.op))) {
|
|
774
|
+
body.push(...signature(plan, throwOnError, false))
|
|
775
|
+
body.push(...docstring(plan.op))
|
|
776
|
+
body.push(...methodBody(plan, throwOnError, false, true))
|
|
804
777
|
body.push("")
|
|
805
778
|
}
|
|
806
|
-
|
|
807
779
|
body.push("")
|
|
808
780
|
|
|
809
781
|
body.push("class AsyncSDK:")
|
|
810
782
|
body.push(" def __init__(self, config: ClientConfig) -> None:")
|
|
811
783
|
body.push(" self._config = config")
|
|
784
|
+
body.push(" self._auth = _AuthState(config.bearer_token)")
|
|
812
785
|
body.push(" self._async_client = httpx.AsyncClient(")
|
|
813
786
|
body.push(" transport=config.transport,")
|
|
814
787
|
body.push(" )")
|
|
815
|
-
body.push("
|
|
816
|
-
body.push(
|
|
817
|
-
body.push("
|
|
818
|
-
body.push("
|
|
819
|
-
body.push("
|
|
820
|
-
body.push("
|
|
821
|
-
body.push(" max_sources_per_target=(")
|
|
822
|
-
body.push(" _inv.max_sources_per_target if _inv is not None else 16")
|
|
823
|
-
body.push(" ),")
|
|
824
|
-
body.push(" )")
|
|
825
|
-
for (const [seg] of namespacesOf(tree)) {
|
|
826
|
-
const className = nestedClassName([seg])
|
|
827
|
-
body.push(` self.${pyIdent(seg)} = ${className}(self)`)
|
|
828
|
-
}
|
|
788
|
+
body.push(...trackerInit("_StaleTracker"))
|
|
789
|
+
for (const c of root.children) body.push(` self.${c.attr} = ${c.node.asyncName}(self)`)
|
|
790
|
+
body.push("")
|
|
791
|
+
body.push(" async def aclose(self) -> None:")
|
|
792
|
+
body.push(' """Closes the underlying HTTP client."""')
|
|
793
|
+
body.push(" await self._async_client.aclose()")
|
|
829
794
|
body.push("")
|
|
830
795
|
body.push(" async def is_stale(self, method: str, path: str) -> bool:")
|
|
831
796
|
body.push(' """Returns True when (method, path) sits inside an active stale window."""')
|
|
832
797
|
body.push(" return await self._stale_tracker.is_stale(method, path)")
|
|
833
798
|
body.push("")
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
body.push(...
|
|
837
|
-
body.push(...
|
|
838
|
-
const bodyLines = buildAsyncMethodBody(op, throwOnError, true)
|
|
839
|
-
body.push(...bodyLines)
|
|
799
|
+
for (const plan of root.plans) {
|
|
800
|
+
body.push(...signature(plan, throwOnError, true))
|
|
801
|
+
body.push(...docstring(plan.op))
|
|
802
|
+
body.push(...methodBody(plan, throwOnError, true, true))
|
|
840
803
|
body.push("")
|
|
841
804
|
}
|
|
842
805
|
|
|
843
|
-
|
|
806
|
+
return body.join("\n")
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
function clientFile(bodyText: string, names: PyNames): string {
|
|
810
|
+
const uses = (sym: string) => new RegExp(`(?<![\\w.])${sym.replace(/[.]/g, "\\.")}\\b`).test(bodyText)
|
|
844
811
|
const imports: string[] = []
|
|
845
|
-
if (/\basyncio\b/.test(bodyText)) imports.push("import asyncio")
|
|
846
812
|
if (/\bthreading\b/.test(bodyText)) imports.push("import threading")
|
|
847
813
|
if (/\buuid\.uuid4\b/.test(bodyText)) imports.push("import uuid")
|
|
848
814
|
imports.push("import httpx")
|
|
849
|
-
if (/\bquote\b/.test(bodyText)) imports.push("from urllib.parse import quote")
|
|
850
|
-
|
|
851
|
-
const typingUsed: string[] = []
|
|
852
|
-
if (/\bAny\b/.test(bodyText)) typingUsed.push("Any")
|
|
853
|
-
if (/\bAsyncIterator\b/.test(bodyText)) typingUsed.push("AsyncIterator")
|
|
854
|
-
if (/\bLiteral\b/.test(bodyText)) typingUsed.push("Literal")
|
|
855
|
-
if (/\bNotRequired\b/.test(bodyText)) typingUsed.push("NotRequired")
|
|
856
|
-
if (/\bTypedDict\b/.test(bodyText)) typingUsed.push("TypedDict")
|
|
857
|
-
if (typingUsed.length > 0) imports.push(`from typing import ${typingUsed.join(", ")}`)
|
|
858
|
-
|
|
859
|
-
const runtimeUsed: string[] = []
|
|
860
|
-
for (const sym of [
|
|
861
|
-
"ClientConfig",
|
|
862
|
-
"InvalidationConfig",
|
|
863
|
-
"SDKResult",
|
|
864
|
-
"_build_headers",
|
|
865
|
-
"_build_url",
|
|
866
|
-
"_do_request_async",
|
|
867
|
-
"_do_request_sync",
|
|
868
|
-
"_parse_body",
|
|
869
|
-
"_raise_for_status",
|
|
870
|
-
]) {
|
|
871
|
-
if (new RegExp(`\\b${sym}\\b`).test(bodyText)) runtimeUsed.push(sym)
|
|
872
|
-
}
|
|
873
|
-
if (runtimeUsed.length > 0) {
|
|
874
|
-
const line = `from ._runtime import ${runtimeUsed.join(", ")}`
|
|
875
|
-
if (line.length <= 88) {
|
|
876
|
-
imports.push(line)
|
|
877
|
-
} else {
|
|
878
|
-
imports.push("from ._runtime import (")
|
|
879
|
-
for (const sym of runtimeUsed) imports.push(` ${sym},`)
|
|
880
|
-
imports.push(")")
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
const invalidationUsed: string[] = []
|
|
885
|
-
for (const sym of ["_StaleTracker", "_StaleTrackerSync", "interpolate_path"]) {
|
|
886
|
-
if (new RegExp(`\\b${sym}\\b`).test(bodyText)) invalidationUsed.push(sym)
|
|
887
|
-
}
|
|
888
|
-
if (invalidationUsed.length > 0) {
|
|
889
|
-
imports.push(`from ._invalidation import ${invalidationUsed.join(", ")}`)
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
/* APIStatusError only appears in docstring Raises sections, never as a code symbol */
|
|
893
|
-
const errorsUsed: string[] = []
|
|
894
|
-
for (const sym of ["APIError", "_STATUS_ERROR_MAP"]) {
|
|
895
|
-
if (new RegExp(`\\b${sym}\\b`).test(bodyText)) errorsUsed.push(sym)
|
|
896
|
-
}
|
|
897
|
-
if (errorsUsed.length > 0) {
|
|
898
|
-
imports.push(`from ._errors import ${errorsUsed.join(", ")}`)
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
const sseUsed: string[] = []
|
|
902
|
-
for (const sym of ["SSEEvent", "parse_sse_stream"]) {
|
|
903
|
-
if (new RegExp(`\\b${sym}\\b`).test(bodyText)) sseUsed.push(sym)
|
|
904
|
-
}
|
|
905
|
-
if (sseUsed.length > 0) {
|
|
906
|
-
imports.push(`from ._sse import ${sseUsed.join(", ")}`)
|
|
907
|
-
}
|
|
908
815
|
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
const transportUsed: string[] = []
|
|
914
|
-
for (const sym of ["LongpollAdapter", "SseAdapter", "TransportOpts", "WsAdapter"]) {
|
|
915
|
-
if (new RegExp(`\\b${sym}\\b`).test(bodyText)) transportUsed.push(sym)
|
|
916
|
-
}
|
|
917
|
-
if (transportUsed.length > 0) {
|
|
918
|
-
imports.push(`from ._transport import ${transportUsed.join(", ")}`)
|
|
919
|
-
}
|
|
816
|
+
const typingUsed = ["Any", "AsyncIterator", "Literal", "NotRequired", "TypedDict"].filter(uses)
|
|
817
|
+
if (typingUsed.length > 0) imports.push(`from typing import ${typingUsed.join(", ")}`)
|
|
920
818
|
|
|
921
|
-
|
|
922
|
-
|
|
819
|
+
for (const [mod, syms] of PY_CLIENT_IMPORTS) {
|
|
820
|
+
const used = syms.filter(uses)
|
|
821
|
+
if (used.length === 0) continue
|
|
822
|
+
imports.push(`from ${mod} import (`)
|
|
823
|
+
for (const s of used) imports.push(` ${s},`)
|
|
824
|
+
imports.push(")")
|
|
923
825
|
}
|
|
924
826
|
|
|
925
|
-
const
|
|
926
|
-
if (
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
} else {
|
|
931
|
-
imports.push("from .types import (")
|
|
932
|
-
for (const name of usedSchemas) imports.push(` ${name},`)
|
|
933
|
-
imports.push(")")
|
|
934
|
-
}
|
|
827
|
+
const typeNames = [...names.schemaNames.values(), ...names.declOrder].filter(uses).sort(cmpCodeUnit)
|
|
828
|
+
if (typeNames.length > 0) {
|
|
829
|
+
imports.push("from .types import (")
|
|
830
|
+
for (const n of typeNames) imports.push(` ${n},`)
|
|
831
|
+
imports.push(")")
|
|
935
832
|
}
|
|
936
833
|
|
|
937
834
|
const out: string[] = []
|
|
@@ -941,89 +838,59 @@ function buildPyClient(
|
|
|
941
838
|
out.push(...imports)
|
|
942
839
|
out.push("")
|
|
943
840
|
out.push(bodyText)
|
|
841
|
+
out.push("")
|
|
944
842
|
return out.join("\n")
|
|
945
843
|
}
|
|
946
844
|
|
|
947
|
-
function buildPyInit(
|
|
948
|
-
const
|
|
949
|
-
"APIError",
|
|
950
|
-
"APIStatusError",
|
|
951
|
-
"BadGatewayError",
|
|
952
|
-
"BadRequestError",
|
|
953
|
-
"ConflictError",
|
|
954
|
-
"ForbiddenError",
|
|
955
|
-
"GatewayTimeoutError",
|
|
956
|
-
"InternalServerError",
|
|
957
|
-
"NotFoundError",
|
|
958
|
-
"RateLimitError",
|
|
959
|
-
"ServiceUnavailableError",
|
|
960
|
-
"UnauthorizedError",
|
|
961
|
-
"UnprocessableEntityError",
|
|
962
|
-
]
|
|
963
|
-
|
|
964
|
-
const transportExports = [
|
|
965
|
-
"LongpollAdapter",
|
|
966
|
-
"SseAdapter",
|
|
967
|
-
"TransportAdapter",
|
|
968
|
-
"TransportConn",
|
|
969
|
-
"TransportKind",
|
|
970
|
-
"TransportOpts",
|
|
971
|
-
"WsAdapter",
|
|
972
|
-
]
|
|
973
|
-
|
|
974
|
-
const realtimeExports = ["ConnectionState", "ResumableConnection"]
|
|
845
|
+
function buildPyInit(names: PyNames): string {
|
|
846
|
+
const typeNames = [...names.schemaNames.values()].sort(cmpCodeUnit)
|
|
975
847
|
|
|
976
848
|
const l: string[] = []
|
|
977
849
|
l.push(PYTHON_FUTURE)
|
|
978
850
|
l.push("")
|
|
979
851
|
l.push("from .client import AsyncSDK, SDK")
|
|
980
852
|
l.push("from ._errors import (")
|
|
981
|
-
for (const name of
|
|
982
|
-
l.push(` ${name},`)
|
|
983
|
-
}
|
|
853
|
+
for (const name of ERROR_CLASSES) l.push(` ${name},`)
|
|
984
854
|
l.push(")")
|
|
855
|
+
l.push("from ._runtime import ClientConfig, InvalidationConfig, PathParamError")
|
|
985
856
|
l.push("from ._transport import (")
|
|
986
|
-
for (const name of
|
|
987
|
-
l.push(` ${name},`)
|
|
988
|
-
}
|
|
857
|
+
for (const name of TRANSPORT_EXPORTS) l.push(` ${name},`)
|
|
989
858
|
l.push(")")
|
|
990
859
|
l.push("from ._realtime import (")
|
|
991
|
-
for (const name of
|
|
992
|
-
l.push(` ${name},`)
|
|
993
|
-
}
|
|
860
|
+
for (const name of REALTIME_EXPORTS) l.push(` ${name},`)
|
|
994
861
|
l.push(")")
|
|
995
|
-
|
|
996
|
-
const schemas = (spec.components as Record<string, unknown> | undefined)?.schemas as
|
|
997
|
-
| Record<string, Record<string, unknown>>
|
|
998
|
-
| undefined
|
|
999
|
-
const schemaNames = schemas ? Object.keys(schemas).sort() : []
|
|
1000
|
-
if (schemaNames.length > 0) {
|
|
862
|
+
if (typeNames.length > 0) {
|
|
1001
863
|
l.push("from .types import (")
|
|
1002
|
-
for (const name of
|
|
1003
|
-
l.push(` ${name},`)
|
|
1004
|
-
}
|
|
864
|
+
for (const name of typeNames) l.push(` ${name},`)
|
|
1005
865
|
l.push(")")
|
|
1006
866
|
}
|
|
1007
867
|
|
|
1008
868
|
l.push("")
|
|
1009
|
-
const allExports = [
|
|
869
|
+
const allExports = [
|
|
870
|
+
"AsyncSDK",
|
|
871
|
+
"SDK",
|
|
872
|
+
...ERROR_CLASSES,
|
|
873
|
+
"ClientConfig",
|
|
874
|
+
"InvalidationConfig",
|
|
875
|
+
"PathParamError",
|
|
876
|
+
...TRANSPORT_EXPORTS,
|
|
877
|
+
...REALTIME_EXPORTS,
|
|
878
|
+
...typeNames,
|
|
879
|
+
]
|
|
1010
880
|
l.push("__all__ = [")
|
|
1011
|
-
for (const name of allExports) {
|
|
1012
|
-
l.push(` "${name}",`)
|
|
1013
|
-
}
|
|
881
|
+
for (const name of allExports) l.push(` ${pyString(name)},`)
|
|
1014
882
|
l.push("]")
|
|
1015
|
-
|
|
883
|
+
l.push("")
|
|
1016
884
|
return l.join("\n")
|
|
1017
885
|
}
|
|
1018
886
|
|
|
1019
|
-
function buildServiceMap(
|
|
1020
|
-
const
|
|
1021
|
-
const
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
invalidate: op.invalidate.length > 0 ? op.invalidate : undefined,
|
|
887
|
+
function buildServiceMap(model: SdkModel): Record<string, ServiceMapEntry> {
|
|
888
|
+
const result: Record<string, ServiceMapEntry> = Object.create(null)
|
|
889
|
+
for (const op of model.ops) {
|
|
890
|
+
result[op.id] = {
|
|
891
|
+
invalidate: op.invalidates.length > 0 ? op.invalidates : undefined,
|
|
1025
892
|
method: op.method,
|
|
1026
|
-
operationId: op.
|
|
893
|
+
operationId: op.id,
|
|
1027
894
|
path: op.path,
|
|
1028
895
|
}
|
|
1029
896
|
}
|
|
@@ -1032,17 +899,18 @@ function buildServiceMap(spec: Record<string, unknown>): Record<string, ServiceM
|
|
|
1032
899
|
|
|
1033
900
|
export function generatePythonSDK(spec: Record<string, unknown>, options: PySDKOptions = {}): PySDKResult {
|
|
1034
901
|
const templates = loadPythonRuntimeTemplates()
|
|
1035
|
-
const
|
|
902
|
+
const model = buildSdkModel(spec as unknown as OpenApiSpecInput)
|
|
903
|
+
const serviceMap = buildServiceMap(model)
|
|
904
|
+
const names = buildPyNames(model)
|
|
1036
905
|
|
|
1037
906
|
const files: Record<string, string> = {}
|
|
907
|
+
for (const [name, content] of templates) files[name] = content
|
|
1038
908
|
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
files["
|
|
1044
|
-
files["client.py"] = buildPyClient(spec, options, serviceMap)
|
|
1045
|
-
files["__init__.py"] = buildPyInit(spec)
|
|
909
|
+
/* schema types claim their names first; the client then hoists into the same table */
|
|
910
|
+
const schemaSource = buildPyTypes(model, names)
|
|
911
|
+
files["client.py"] = clientFile(buildPyClient(model, names, options, serviceMap), names)
|
|
912
|
+
files["types.py"] = typesFile(schemaSource, names)
|
|
913
|
+
files["__init__.py"] = buildPyInit(names)
|
|
1046
914
|
|
|
1047
915
|
return { files, serviceMap }
|
|
1048
916
|
}
|