@lovrozagar/honey 0.6.8 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +408 -148
- 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 +819 -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 +1887 -1756
- 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 +253 -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 +603 -423
- 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
|
@@ -0,0 +1,860 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import inspect
|
|
5
|
+
import re
|
|
6
|
+
import threading
|
|
7
|
+
import time
|
|
8
|
+
import unicodedata
|
|
9
|
+
import uuid
|
|
10
|
+
from contextlib import asynccontextmanager
|
|
11
|
+
from dataclasses import dataclass, field
|
|
12
|
+
from decimal import Decimal
|
|
13
|
+
from typing import Any, AsyncIterator, Awaitable, Callable, Generic, TypeVar
|
|
14
|
+
from urllib.parse import quote, urlsplit, urlunsplit
|
|
15
|
+
|
|
16
|
+
import httpx
|
|
17
|
+
|
|
18
|
+
from ._errors import APIStatusError, _STATUS_ERROR_MAP
|
|
19
|
+
from ._invalidation import _RequestMeta
|
|
20
|
+
|
|
21
|
+
T = TypeVar("T")
|
|
22
|
+
|
|
23
|
+
DEFAULT_TIMEOUT = 30.0
|
|
24
|
+
MAX_ERROR_MESSAGE = 512
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class RequestContext:
|
|
29
|
+
method: str
|
|
30
|
+
url: str
|
|
31
|
+
headers: dict[str, str]
|
|
32
|
+
body: Any
|
|
33
|
+
is_retry: bool = False
|
|
34
|
+
selector: str | None = None
|
|
35
|
+
is_stale: bool | None = None
|
|
36
|
+
invalidated_by: list[str] | None = None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class ResponseContext:
|
|
41
|
+
response: httpx.Response
|
|
42
|
+
body: bytes
|
|
43
|
+
parsed: Any
|
|
44
|
+
is_retry: bool = False
|
|
45
|
+
selector: str | None = None
|
|
46
|
+
is_stale: bool | None = None
|
|
47
|
+
invalidated_by: list[str] | None = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class LogEntry:
|
|
52
|
+
level: str
|
|
53
|
+
event: str
|
|
54
|
+
operation: str
|
|
55
|
+
duration_ms: int
|
|
56
|
+
status: int | None = None
|
|
57
|
+
error: str | None = None
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@dataclass(frozen=True)
|
|
61
|
+
class InvalidationConfig:
|
|
62
|
+
stale_time: float = 0.0
|
|
63
|
+
stale_max_entries: int = 1000
|
|
64
|
+
max_sources_per_target: int = 16
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@dataclass(frozen=True)
|
|
68
|
+
class ClientConfig:
|
|
69
|
+
"""SDK configuration.
|
|
70
|
+
|
|
71
|
+
``timeout`` (seconds) bounds a whole regular request; ``None`` disables it.
|
|
72
|
+
Streams (SSE, WebSocket, realtime) are not bounded by it.
|
|
73
|
+
|
|
74
|
+
``on_auth_expired`` may be a coroutine function or a plain function; it
|
|
75
|
+
returns the new token (or ``None`` to give up). The SDK keeps the refreshed
|
|
76
|
+
token for later calls and refreshes once for concurrent 401s.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
base_url: str
|
|
80
|
+
bearer_token: str | None = None
|
|
81
|
+
headers: dict[str, str] = field(default_factory=dict)
|
|
82
|
+
timeout: float | None = DEFAULT_TIMEOUT
|
|
83
|
+
throw_on_error: bool = True
|
|
84
|
+
on_auth_expired: Callable[[], Awaitable[str | None] | str | None] | None = None
|
|
85
|
+
auth_header_name: str | None = None
|
|
86
|
+
auth_header_prefix: str | None = None
|
|
87
|
+
transport: httpx.AsyncBaseTransport | None = None
|
|
88
|
+
sync_transport: httpx.BaseTransport | None = None
|
|
89
|
+
on_request: list[Callable[[RequestContext], Awaitable[None]]] | None = None
|
|
90
|
+
on_response: list[Callable[[ResponseContext], Awaitable[None]]] | None = None
|
|
91
|
+
on_request_sync: list[Callable[[RequestContext], None]] | None = None
|
|
92
|
+
on_response_sync: list[Callable[[ResponseContext], None]] | None = None
|
|
93
|
+
on_log: Callable[[LogEntry], None] | None = None
|
|
94
|
+
invalidation: InvalidationConfig | None = None
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _emit_log(config: ClientConfig, entry: LogEntry) -> None:
|
|
98
|
+
if config.on_log is None:
|
|
99
|
+
return
|
|
100
|
+
try:
|
|
101
|
+
config.on_log(entry)
|
|
102
|
+
except Exception:
|
|
103
|
+
# logger must never break the request path
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _elapsed_ms(start: float) -> int:
|
|
108
|
+
return round((time.monotonic() - start) * 1000)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _err_status(exc: BaseException) -> int | None:
|
|
112
|
+
status = getattr(exc, "status", None)
|
|
113
|
+
return status if isinstance(status, int) else None
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass(frozen=True)
|
|
117
|
+
class SDKResult(Generic[T]):
|
|
118
|
+
data: T | None
|
|
119
|
+
error: Any
|
|
120
|
+
status: int
|
|
121
|
+
response: httpx.Response
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class _AuthState:
|
|
125
|
+
"""Current bearer token plus a refresh guard, shared by one SDK instance."""
|
|
126
|
+
|
|
127
|
+
def __init__(self, token: str | None) -> None:
|
|
128
|
+
self.token = token
|
|
129
|
+
self._async_lock: asyncio.Lock | None = None
|
|
130
|
+
self._thread_lock = threading.Lock()
|
|
131
|
+
|
|
132
|
+
async def refresh(self, config: ClientConfig, rejected: str | None) -> str | None:
|
|
133
|
+
if self._async_lock is None:
|
|
134
|
+
self._async_lock = asyncio.Lock()
|
|
135
|
+
async with self._async_lock:
|
|
136
|
+
if self.token != rejected:
|
|
137
|
+
return self.token
|
|
138
|
+
token = await _call_auth_hook(config)
|
|
139
|
+
if token is not None:
|
|
140
|
+
self.token = token
|
|
141
|
+
return token
|
|
142
|
+
|
|
143
|
+
def refresh_sync(self, config: ClientConfig, rejected: str | None) -> str | None:
|
|
144
|
+
with self._thread_lock:
|
|
145
|
+
if self.token != rejected:
|
|
146
|
+
return self.token
|
|
147
|
+
token = _call_auth_hook_sync(config)
|
|
148
|
+
if token is not None:
|
|
149
|
+
self.token = token
|
|
150
|
+
return token
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
async def _call_auth_hook(config: ClientConfig) -> str | None:
|
|
154
|
+
assert config.on_auth_expired is not None
|
|
155
|
+
result = config.on_auth_expired()
|
|
156
|
+
if inspect.isawaitable(result):
|
|
157
|
+
return await result
|
|
158
|
+
return result
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _call_auth_hook_sync(config: ClientConfig) -> str | None:
|
|
162
|
+
assert config.on_auth_expired is not None
|
|
163
|
+
result = config.on_auth_expired()
|
|
164
|
+
if not inspect.isawaitable(result):
|
|
165
|
+
return result
|
|
166
|
+
try:
|
|
167
|
+
asyncio.get_running_loop()
|
|
168
|
+
except RuntimeError:
|
|
169
|
+
return asyncio.run(_await(result))
|
|
170
|
+
# called from inside a running loop (sync SDK used in async code): run the
|
|
171
|
+
# coroutine on its own loop in a worker thread instead of failing silently
|
|
172
|
+
out: list[Any] = []
|
|
173
|
+
errors: list[BaseException] = []
|
|
174
|
+
|
|
175
|
+
def _worker() -> None:
|
|
176
|
+
try:
|
|
177
|
+
out.append(asyncio.run(_await(result)))
|
|
178
|
+
except BaseException as exc: # re-raised in the caller's thread
|
|
179
|
+
errors.append(exc)
|
|
180
|
+
|
|
181
|
+
thread = threading.Thread(target=_worker)
|
|
182
|
+
thread.start()
|
|
183
|
+
thread.join()
|
|
184
|
+
if errors:
|
|
185
|
+
raise errors[0]
|
|
186
|
+
return out[0]
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
async def _await(value: Awaitable[T]) -> T:
|
|
190
|
+
return await value
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _auth_header_name(config: ClientConfig) -> str:
|
|
194
|
+
return config.auth_header_name or "Authorization"
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _has_header(headers: dict[str, str], name: str) -> bool:
|
|
198
|
+
target = name.lower()
|
|
199
|
+
return any(k.lower() == target for k in headers)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _set_header(headers: dict[str, str], name: str, value: str) -> None:
|
|
203
|
+
"""Set *name* replacing any existing casing of it."""
|
|
204
|
+
target = name.lower()
|
|
205
|
+
for key in [k for k in headers if k.lower() == target]:
|
|
206
|
+
del headers[key]
|
|
207
|
+
headers[name] = value
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _build_headers(
|
|
211
|
+
config: ClientConfig,
|
|
212
|
+
extra: dict[str, str] | None = None,
|
|
213
|
+
bearer_token: str | None = None,
|
|
214
|
+
) -> dict[str, str]:
|
|
215
|
+
"""Config headers, then the auth header, then per-call headers. Merging is
|
|
216
|
+
case-insensitive and later wins, so exactly one of each header is sent."""
|
|
217
|
+
merged: dict[str, str] = {}
|
|
218
|
+
for key, value in config.headers.items():
|
|
219
|
+
_set_header(merged, key, value)
|
|
220
|
+
token = bearer_token if bearer_token is not None else config.bearer_token
|
|
221
|
+
if token:
|
|
222
|
+
prefix = (
|
|
223
|
+
config.auth_header_prefix
|
|
224
|
+
if config.auth_header_prefix is not None
|
|
225
|
+
else "Bearer "
|
|
226
|
+
)
|
|
227
|
+
_set_header(merged, _auth_header_name(config), f"{prefix}{token}")
|
|
228
|
+
for key, value in (extra or {}).items():
|
|
229
|
+
if value is None:
|
|
230
|
+
continue
|
|
231
|
+
_set_header(merged, key, str(value))
|
|
232
|
+
# auto correlation id — config/extra win; on_request hooks may overwrite
|
|
233
|
+
if not _has_header(merged, "x-request-id"):
|
|
234
|
+
merged["x-request-id"] = str(uuid.uuid4())
|
|
235
|
+
return merged
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class PathParamError(ValueError):
|
|
239
|
+
"""A path parameter that is missing, or that a URL parser would collapse
|
|
240
|
+
("", "." or "..")."""
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _escape_segment(value: str) -> str:
|
|
244
|
+
# encodeURIComponent's safe set: every SDK language and the TS client send
|
|
245
|
+
# the same bytes
|
|
246
|
+
return quote(value, safe="!~*'()")
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _escape_query(value: str) -> str:
|
|
250
|
+
# URLSearchParams encoding: space becomes "+"
|
|
251
|
+
return quote(value, safe="*").replace("%20", "+").replace("~", "%7E")
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _encode_segment_value(name: str, value: str) -> str:
|
|
255
|
+
if value in ("", ".", ".."):
|
|
256
|
+
raise PathParamError(
|
|
257
|
+
f"Invalid path param {name!r}: {value!r} is not a path segment"
|
|
258
|
+
)
|
|
259
|
+
return _escape_segment(value)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _expand_path(template: str, params: dict[str, Any]) -> str:
|
|
263
|
+
"""Fill a path template with the TS client's grammar: ``:name`` / ``:name?``
|
|
264
|
+
take a whole segment, a final ``*name`` takes the rest, ``{name}`` may sit
|
|
265
|
+
inside a segment. Values are encoded; "", "." and ".." are rejected."""
|
|
266
|
+
segments = template.split("/")
|
|
267
|
+
out: list[str] = []
|
|
268
|
+
for i, seg in enumerate(segments):
|
|
269
|
+
if len(seg) > 1 and seg[0] == ":":
|
|
270
|
+
optional = seg.endswith("?")
|
|
271
|
+
name = seg[1:-1] if optional else seg[1:]
|
|
272
|
+
if params.get(name) is None:
|
|
273
|
+
if optional:
|
|
274
|
+
continue
|
|
275
|
+
raise PathParamError(f"Missing path param: {name}")
|
|
276
|
+
out.append(_encode_segment_value(name, _format_value(params[name])))
|
|
277
|
+
elif seg[:1] == "*" and i == len(segments) - 1:
|
|
278
|
+
name = seg[1:] or "*"
|
|
279
|
+
if params.get(name) is None:
|
|
280
|
+
if seg == "*":
|
|
281
|
+
out.append("")
|
|
282
|
+
continue
|
|
283
|
+
raise PathParamError(f"Missing path param: {name}")
|
|
284
|
+
value = _format_value(params[name])
|
|
285
|
+
out.append(
|
|
286
|
+
""
|
|
287
|
+
if value == ""
|
|
288
|
+
else "/".join(_encode_segment_value(name, p) for p in value.split("/"))
|
|
289
|
+
)
|
|
290
|
+
else:
|
|
291
|
+
|
|
292
|
+
def _sub(match: re.Match[str]) -> str:
|
|
293
|
+
name = match.group(1)
|
|
294
|
+
if params.get(name) is None:
|
|
295
|
+
raise PathParamError(f"Missing path param: {name}")
|
|
296
|
+
return _encode_segment_value(name, _format_value(params[name]))
|
|
297
|
+
|
|
298
|
+
out.append(_BRACE_RE.sub(_sub, seg))
|
|
299
|
+
return "/".join(out)
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
_BRACE_RE = re.compile(r"\{([^{}/]+)\}")
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def _format_float(value: float) -> str:
|
|
306
|
+
"""JavaScript Number#toString formatting, shared by every SDK language."""
|
|
307
|
+
if value != value:
|
|
308
|
+
return "NaN"
|
|
309
|
+
if value in (float("inf"), float("-inf")):
|
|
310
|
+
return "Infinity" if value > 0 else "-Infinity"
|
|
311
|
+
if value == 0:
|
|
312
|
+
return "0"
|
|
313
|
+
magnitude = abs(value)
|
|
314
|
+
if 1e-6 <= magnitude < 1e21:
|
|
315
|
+
text = format(Decimal(repr(value)), "f")
|
|
316
|
+
if "." in text:
|
|
317
|
+
text = text.rstrip("0").rstrip(".")
|
|
318
|
+
return text
|
|
319
|
+
mantissa, _, exponent = repr(value).partition("e")
|
|
320
|
+
if not exponent:
|
|
321
|
+
mantissa, _, exponent = f"{value:e}".partition("e")
|
|
322
|
+
mantissa = mantissa.rstrip("0").rstrip(".") if "." in mantissa else mantissa
|
|
323
|
+
exp = int(exponent)
|
|
324
|
+
return f"{mantissa}e{'+' if exp > 0 else '-'}{abs(exp)}"
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _format_value(value: Any) -> str:
|
|
328
|
+
if isinstance(value, bool):
|
|
329
|
+
return "true" if value else "false"
|
|
330
|
+
if isinstance(value, int):
|
|
331
|
+
return str(value)
|
|
332
|
+
if isinstance(value, float):
|
|
333
|
+
return _format_float(value)
|
|
334
|
+
if isinstance(value, str):
|
|
335
|
+
return value
|
|
336
|
+
if hasattr(value, "value") and isinstance(getattr(value, "value"), (str, int)):
|
|
337
|
+
return _format_value(value.value)
|
|
338
|
+
import json as _json
|
|
339
|
+
|
|
340
|
+
return _json.dumps(value, separators=(",", ":"))
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _query_pairs(params: dict[str, Any] | None) -> list[tuple[str, str]]:
|
|
344
|
+
"""Flatten params in order: None skipped, lists and tuples repeat the key."""
|
|
345
|
+
pairs: list[tuple[str, str]] = []
|
|
346
|
+
for key, value in (params or {}).items():
|
|
347
|
+
if value is None:
|
|
348
|
+
continue
|
|
349
|
+
if isinstance(value, (list, tuple)):
|
|
350
|
+
pairs.extend((key, _format_value(v)) for v in value if v is not None)
|
|
351
|
+
else:
|
|
352
|
+
pairs.append((key, _format_value(value)))
|
|
353
|
+
return pairs
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
def _encode_query(pairs: list[tuple[str, str]]) -> str:
|
|
357
|
+
return "&".join(f"{_escape_query(k)}={_escape_query(v)}" for k, v in pairs)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _build_url(base_url: str, path: str, params: dict[str, Any] | None = None) -> str:
|
|
361
|
+
"""Join *base_url* and an escaped *path*, keeping the base path and query,
|
|
362
|
+
then append *params* (lists repeat the key)."""
|
|
363
|
+
parts = urlsplit(base_url)
|
|
364
|
+
if not parts.scheme or not parts.netloc:
|
|
365
|
+
raise ValueError(f"base_url {base_url!r} needs a scheme and host")
|
|
366
|
+
if not path.startswith("/"):
|
|
367
|
+
path = "/" + path
|
|
368
|
+
joined_path = parts.path.rstrip("/") + path
|
|
369
|
+
query = parts.query
|
|
370
|
+
extra = _encode_query(_query_pairs(params))
|
|
371
|
+
if extra:
|
|
372
|
+
query = f"{query}&{extra}" if query else extra
|
|
373
|
+
return urlunsplit((parts.scheme, parts.netloc, joined_path, query, ""))
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _append_query(url: str, params: dict[str, Any] | None) -> str:
|
|
377
|
+
extra = _encode_query(_query_pairs(params))
|
|
378
|
+
if not extra:
|
|
379
|
+
return url
|
|
380
|
+
return f"{url}{'&' if '?' in url else '?'}{extra}"
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
# One multipart file: raw bytes, a binary file object, or a
|
|
384
|
+
# (filename, content[, content_type]) tuple.
|
|
385
|
+
FileInput = Any
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def _form_content(body: Any) -> bytes:
|
|
389
|
+
"""application/x-www-form-urlencoded bytes; lists repeat the key."""
|
|
390
|
+
return _encode_query(_query_pairs(dict(body or {}))).encode()
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _multipart_parts(
|
|
394
|
+
body: Any,
|
|
395
|
+
files: list[str],
|
|
396
|
+
list_files: list[str],
|
|
397
|
+
) -> tuple[None, list[tuple[str, Any]]]:
|
|
398
|
+
"""Encode a multipart body as httpx ``files`` entries (``data`` is always ``None``).
|
|
399
|
+
|
|
400
|
+
httpx 0.28 cannot combine a list of ``data`` tuples with ``files`` (a text
|
|
401
|
+
field raises ``TypeError``, and an empty list sends an empty body), so text
|
|
402
|
+
fields go in as ``(None, value)`` parts: same wire format, and repeated keys
|
|
403
|
+
keep their order.
|
|
404
|
+
"""
|
|
405
|
+
out_files: list[tuple[str, Any]] = []
|
|
406
|
+
for key, value in dict(body or {}).items():
|
|
407
|
+
if value is None:
|
|
408
|
+
continue
|
|
409
|
+
if key in files:
|
|
410
|
+
out_files.append((key, value))
|
|
411
|
+
elif key in list_files:
|
|
412
|
+
out_files.extend((key, item) for item in value)
|
|
413
|
+
elif isinstance(value, (list, tuple)):
|
|
414
|
+
out_files.extend((key, (None, _format_value(v))) for v in value)
|
|
415
|
+
else:
|
|
416
|
+
out_files.append((key, (None, _format_value(value))))
|
|
417
|
+
return None, out_files
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def _body_args(
|
|
421
|
+
content: Any,
|
|
422
|
+
data: Any,
|
|
423
|
+
files: Any,
|
|
424
|
+
headers: dict[str, str],
|
|
425
|
+
) -> tuple[Any, Any, Any, dict[str, str]]:
|
|
426
|
+
"""httpx sends ``files=[]`` as no body at all. A multipart operation called
|
|
427
|
+
with no fields still sends a multipart body: the empty one, a lone closing
|
|
428
|
+
boundary."""
|
|
429
|
+
if files is not None and len(files) == 0 and content is None and not data:
|
|
430
|
+
boundary = uuid.uuid4().hex
|
|
431
|
+
out = dict(headers)
|
|
432
|
+
_set_header(out, "Content-Type", f"multipart/form-data; boundary={boundary}")
|
|
433
|
+
return f"--{boundary}--\r\n".encode(), None, None, out
|
|
434
|
+
return content, data, files, headers
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def _to_ws_url(url: str) -> str:
|
|
438
|
+
"""Swap only the scheme; a URL inside the query string is untouched."""
|
|
439
|
+
if url.startswith("https://"):
|
|
440
|
+
return "wss://" + url[len("https://") :]
|
|
441
|
+
if url.startswith("http://"):
|
|
442
|
+
return "ws://" + url[len("http://") :]
|
|
443
|
+
return url
|
|
444
|
+
|
|
445
|
+
|
|
446
|
+
def _is_json_media(content_type: str) -> bool:
|
|
447
|
+
essence = content_type.split(";", 1)[0].strip().lower()
|
|
448
|
+
return essence == "application/json" or essence.endswith("+json")
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
def _parse_body(response: httpx.Response, kind: str = "json") -> Any:
|
|
452
|
+
"""Decode a response per the operation's declared success kind.
|
|
453
|
+
|
|
454
|
+
``binary`` returns bytes, ``text`` returns str, ``none`` returns None; JSON
|
|
455
|
+
(including ``+json`` media types) is decoded, anything else is returned as
|
|
456
|
+
text.
|
|
457
|
+
"""
|
|
458
|
+
if kind == "binary":
|
|
459
|
+
return response.content
|
|
460
|
+
if kind == "none":
|
|
461
|
+
return None
|
|
462
|
+
content_type = response.headers.get("content-type", "")
|
|
463
|
+
if kind == "text":
|
|
464
|
+
return response.text
|
|
465
|
+
if _is_json_media(content_type):
|
|
466
|
+
if not response.content:
|
|
467
|
+
return None
|
|
468
|
+
try:
|
|
469
|
+
return response.json()
|
|
470
|
+
except Exception:
|
|
471
|
+
return None
|
|
472
|
+
if not response.content:
|
|
473
|
+
return None
|
|
474
|
+
return response.text
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def _clean_message(text: str) -> str:
|
|
478
|
+
cleaned = "".join(
|
|
479
|
+
ch for ch in text if ch in " " or unicodedata.category(ch)[0] != "C"
|
|
480
|
+
)
|
|
481
|
+
if len(cleaned) > MAX_ERROR_MESSAGE:
|
|
482
|
+
cleaned = cleaned[:MAX_ERROR_MESSAGE] + "…"
|
|
483
|
+
return cleaned
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def _raise_for_status(response: httpx.Response, body: Any = None) -> None:
|
|
487
|
+
"""Raise the typed error for any non-2xx status. A 3xx counts: the SDK does
|
|
488
|
+
not follow redirects, so a redirect is not a successful response."""
|
|
489
|
+
status = response.status_code
|
|
490
|
+
if 200 <= status < 300:
|
|
491
|
+
return
|
|
492
|
+
parsed = body if body is not None else _parse_body(response)
|
|
493
|
+
raw = response.content
|
|
494
|
+
msg_val = parsed.get("message") if isinstance(parsed, dict) else None
|
|
495
|
+
if isinstance(msg_val, str):
|
|
496
|
+
message = msg_val
|
|
497
|
+
elif isinstance(parsed, str):
|
|
498
|
+
message = parsed
|
|
499
|
+
elif parsed is not None:
|
|
500
|
+
message = str(parsed)
|
|
501
|
+
else:
|
|
502
|
+
message = f"HTTP {status}"
|
|
503
|
+
error_cls = _STATUS_ERROR_MAP.get(status, APIStatusError)
|
|
504
|
+
raise error_cls(
|
|
505
|
+
status=status,
|
|
506
|
+
body=raw,
|
|
507
|
+
data=parsed,
|
|
508
|
+
response=response,
|
|
509
|
+
message=_clean_message(message) or f"HTTP {status}",
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def _apply_meta_to_request_ctx(ctx: RequestContext, meta: _RequestMeta | None) -> None:
|
|
514
|
+
if meta is None:
|
|
515
|
+
return
|
|
516
|
+
ctx.selector = meta.selector
|
|
517
|
+
ctx.is_stale = meta.is_stale
|
|
518
|
+
ctx.invalidated_by = meta.invalidated_by
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
def _apply_meta_to_response_ctx(
|
|
522
|
+
ctx: ResponseContext,
|
|
523
|
+
meta: _RequestMeta | None,
|
|
524
|
+
) -> None:
|
|
525
|
+
if meta is None:
|
|
526
|
+
return
|
|
527
|
+
ctx.selector = meta.selector
|
|
528
|
+
ctx.is_stale = meta.is_stale
|
|
529
|
+
ctx.invalidated_by = meta.invalidated_by
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def _replayable(content: Any) -> bool:
|
|
533
|
+
"""A body can be sent again after an auth refresh unless it is a one-shot stream."""
|
|
534
|
+
return content is None or isinstance(content, (bytes, bytearray, str))
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def _timeout(value: float | None) -> httpx.Timeout:
|
|
538
|
+
return httpx.Timeout(value)
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
async def _do_request_async(
|
|
542
|
+
client: httpx.AsyncClient,
|
|
543
|
+
config: ClientConfig,
|
|
544
|
+
method: str,
|
|
545
|
+
url: str,
|
|
546
|
+
headers: dict[str, str],
|
|
547
|
+
json: Any = None,
|
|
548
|
+
content: Any = None,
|
|
549
|
+
params: dict[str, Any] | None = None,
|
|
550
|
+
timeout: float | None = None,
|
|
551
|
+
cancel_token: threading.Event | None = None,
|
|
552
|
+
operation: str = "",
|
|
553
|
+
request_meta: _RequestMeta | None = None,
|
|
554
|
+
data: Any = None,
|
|
555
|
+
files: Any = None,
|
|
556
|
+
auth: _AuthState | None = None,
|
|
557
|
+
) -> httpx.Response:
|
|
558
|
+
if cancel_token is not None and cancel_token.is_set():
|
|
559
|
+
raise asyncio.CancelledError()
|
|
560
|
+
timeout_val = timeout if timeout is not None else config.timeout
|
|
561
|
+
start = time.monotonic()
|
|
562
|
+
url = _append_query(url, params)
|
|
563
|
+
|
|
564
|
+
req_ctx = RequestContext(method=method, url=url, headers=dict(headers), body=json)
|
|
565
|
+
_apply_meta_to_request_ctx(req_ctx, request_meta)
|
|
566
|
+
if config.on_request:
|
|
567
|
+
for hook in config.on_request:
|
|
568
|
+
await hook(req_ctx)
|
|
569
|
+
|
|
570
|
+
_emit_log(
|
|
571
|
+
config,
|
|
572
|
+
LogEntry(
|
|
573
|
+
level="debug", event="request_start", operation=operation, duration_ms=0
|
|
574
|
+
),
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
async def _send(ctx: RequestContext) -> httpx.Response:
|
|
578
|
+
b_content, b_data, b_files, b_headers = _body_args(
|
|
579
|
+
content, data, files, ctx.headers
|
|
580
|
+
)
|
|
581
|
+
return await client.request(
|
|
582
|
+
ctx.method,
|
|
583
|
+
ctx.url,
|
|
584
|
+
headers=b_headers,
|
|
585
|
+
json=ctx.body
|
|
586
|
+
if content is None and data is None and files is None
|
|
587
|
+
else None,
|
|
588
|
+
content=b_content,
|
|
589
|
+
data=b_data,
|
|
590
|
+
files=b_files,
|
|
591
|
+
timeout=_timeout(timeout_val),
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
async def _observe(response: httpx.Response, is_retry: bool) -> None:
|
|
595
|
+
if config.on_response:
|
|
596
|
+
raw = response.content
|
|
597
|
+
parsed = _parse_body(response)
|
|
598
|
+
resp_ctx = ResponseContext(
|
|
599
|
+
response=response, body=raw, parsed=parsed, is_retry=is_retry
|
|
600
|
+
)
|
|
601
|
+
_apply_meta_to_response_ctx(resp_ctx, request_meta)
|
|
602
|
+
for hook in config.on_response:
|
|
603
|
+
await hook(resp_ctx)
|
|
604
|
+
|
|
605
|
+
try:
|
|
606
|
+
response = await _send(req_ctx)
|
|
607
|
+
await _observe(response, False)
|
|
608
|
+
|
|
609
|
+
if (
|
|
610
|
+
response.status_code == 401
|
|
611
|
+
and config.on_auth_expired is not None
|
|
612
|
+
and _replayable(content)
|
|
613
|
+
):
|
|
614
|
+
rejected = auth.token if auth is not None else config.bearer_token
|
|
615
|
+
new_token = (
|
|
616
|
+
await auth.refresh(config, rejected)
|
|
617
|
+
if auth is not None
|
|
618
|
+
else await _call_auth_hook(config)
|
|
619
|
+
)
|
|
620
|
+
if new_token is not None:
|
|
621
|
+
current_auth_name = _auth_header_name(config).lower()
|
|
622
|
+
extra_no_auth = {
|
|
623
|
+
k: v
|
|
624
|
+
for k, v in req_ctx.headers.items()
|
|
625
|
+
if k.lower() != current_auth_name
|
|
626
|
+
}
|
|
627
|
+
retry_ctx = RequestContext(
|
|
628
|
+
method=req_ctx.method,
|
|
629
|
+
url=req_ctx.url,
|
|
630
|
+
headers=_build_headers(
|
|
631
|
+
config, extra=extra_no_auth or None, bearer_token=new_token
|
|
632
|
+
),
|
|
633
|
+
body=req_ctx.body,
|
|
634
|
+
is_retry=True,
|
|
635
|
+
)
|
|
636
|
+
_apply_meta_to_request_ctx(retry_ctx, request_meta)
|
|
637
|
+
if config.on_request:
|
|
638
|
+
for hook in config.on_request:
|
|
639
|
+
await hook(retry_ctx)
|
|
640
|
+
response = await _send(retry_ctx)
|
|
641
|
+
await _observe(response, True)
|
|
642
|
+
|
|
643
|
+
_emit_log(
|
|
644
|
+
config,
|
|
645
|
+
LogEntry(
|
|
646
|
+
level="warn" if response.status_code >= 400 else "info",
|
|
647
|
+
event="response_received",
|
|
648
|
+
operation=operation,
|
|
649
|
+
duration_ms=_elapsed_ms(start),
|
|
650
|
+
status=response.status_code,
|
|
651
|
+
),
|
|
652
|
+
)
|
|
653
|
+
return response
|
|
654
|
+
except BaseException as exc:
|
|
655
|
+
_emit_log(
|
|
656
|
+
config,
|
|
657
|
+
LogEntry(
|
|
658
|
+
level="error",
|
|
659
|
+
event="error",
|
|
660
|
+
operation=operation,
|
|
661
|
+
duration_ms=_elapsed_ms(start),
|
|
662
|
+
status=_err_status(exc),
|
|
663
|
+
error=repr(exc),
|
|
664
|
+
),
|
|
665
|
+
)
|
|
666
|
+
raise
|
|
667
|
+
|
|
668
|
+
|
|
669
|
+
def _do_request_sync(
|
|
670
|
+
client: httpx.Client,
|
|
671
|
+
config: ClientConfig,
|
|
672
|
+
method: str,
|
|
673
|
+
url: str,
|
|
674
|
+
headers: dict[str, str],
|
|
675
|
+
json: Any = None,
|
|
676
|
+
content: Any = None,
|
|
677
|
+
params: dict[str, Any] | None = None,
|
|
678
|
+
timeout: float | None = None,
|
|
679
|
+
cancel_token: threading.Event | None = None,
|
|
680
|
+
operation: str = "",
|
|
681
|
+
request_meta: _RequestMeta | None = None,
|
|
682
|
+
data: Any = None,
|
|
683
|
+
files: Any = None,
|
|
684
|
+
auth: _AuthState | None = None,
|
|
685
|
+
) -> httpx.Response:
|
|
686
|
+
if cancel_token is not None and cancel_token.is_set():
|
|
687
|
+
raise asyncio.CancelledError()
|
|
688
|
+
timeout_val = timeout if timeout is not None else config.timeout
|
|
689
|
+
start = time.monotonic()
|
|
690
|
+
url = _append_query(url, params)
|
|
691
|
+
|
|
692
|
+
req_ctx = RequestContext(method=method, url=url, headers=dict(headers), body=json)
|
|
693
|
+
_apply_meta_to_request_ctx(req_ctx, request_meta)
|
|
694
|
+
if config.on_request_sync:
|
|
695
|
+
for hook in config.on_request_sync:
|
|
696
|
+
hook(req_ctx)
|
|
697
|
+
|
|
698
|
+
_emit_log(
|
|
699
|
+
config,
|
|
700
|
+
LogEntry(
|
|
701
|
+
level="debug", event="request_start", operation=operation, duration_ms=0
|
|
702
|
+
),
|
|
703
|
+
)
|
|
704
|
+
|
|
705
|
+
def _send(ctx: RequestContext) -> httpx.Response:
|
|
706
|
+
b_content, b_data, b_files, b_headers = _body_args(
|
|
707
|
+
content, data, files, ctx.headers
|
|
708
|
+
)
|
|
709
|
+
return client.request(
|
|
710
|
+
ctx.method,
|
|
711
|
+
ctx.url,
|
|
712
|
+
headers=b_headers,
|
|
713
|
+
json=ctx.body
|
|
714
|
+
if content is None and data is None and files is None
|
|
715
|
+
else None,
|
|
716
|
+
content=b_content,
|
|
717
|
+
data=b_data,
|
|
718
|
+
files=b_files,
|
|
719
|
+
timeout=_timeout(timeout_val),
|
|
720
|
+
)
|
|
721
|
+
|
|
722
|
+
def _observe(response: httpx.Response, is_retry: bool) -> None:
|
|
723
|
+
if config.on_response_sync:
|
|
724
|
+
raw = response.content
|
|
725
|
+
parsed = _parse_body(response)
|
|
726
|
+
resp_ctx = ResponseContext(
|
|
727
|
+
response=response, body=raw, parsed=parsed, is_retry=is_retry
|
|
728
|
+
)
|
|
729
|
+
_apply_meta_to_response_ctx(resp_ctx, request_meta)
|
|
730
|
+
for hook in config.on_response_sync:
|
|
731
|
+
hook(resp_ctx)
|
|
732
|
+
|
|
733
|
+
try:
|
|
734
|
+
response = _send(req_ctx)
|
|
735
|
+
_observe(response, False)
|
|
736
|
+
|
|
737
|
+
if (
|
|
738
|
+
response.status_code == 401
|
|
739
|
+
and config.on_auth_expired is not None
|
|
740
|
+
and _replayable(content)
|
|
741
|
+
):
|
|
742
|
+
rejected = auth.token if auth is not None else config.bearer_token
|
|
743
|
+
new_token = (
|
|
744
|
+
auth.refresh_sync(config, rejected)
|
|
745
|
+
if auth is not None
|
|
746
|
+
else _call_auth_hook_sync(config)
|
|
747
|
+
)
|
|
748
|
+
if new_token is not None:
|
|
749
|
+
current_auth_name = _auth_header_name(config).lower()
|
|
750
|
+
extra_no_auth = {
|
|
751
|
+
k: v
|
|
752
|
+
for k, v in req_ctx.headers.items()
|
|
753
|
+
if k.lower() != current_auth_name
|
|
754
|
+
}
|
|
755
|
+
retry_ctx = RequestContext(
|
|
756
|
+
method=req_ctx.method,
|
|
757
|
+
url=req_ctx.url,
|
|
758
|
+
headers=_build_headers(
|
|
759
|
+
config, extra=extra_no_auth or None, bearer_token=new_token
|
|
760
|
+
),
|
|
761
|
+
body=req_ctx.body,
|
|
762
|
+
is_retry=True,
|
|
763
|
+
)
|
|
764
|
+
_apply_meta_to_request_ctx(retry_ctx, request_meta)
|
|
765
|
+
if config.on_request_sync:
|
|
766
|
+
for hook in config.on_request_sync:
|
|
767
|
+
hook(retry_ctx)
|
|
768
|
+
response = _send(retry_ctx)
|
|
769
|
+
_observe(response, True)
|
|
770
|
+
|
|
771
|
+
_emit_log(
|
|
772
|
+
config,
|
|
773
|
+
LogEntry(
|
|
774
|
+
level="warn" if response.status_code >= 400 else "info",
|
|
775
|
+
event="response_received",
|
|
776
|
+
operation=operation,
|
|
777
|
+
duration_ms=_elapsed_ms(start),
|
|
778
|
+
status=response.status_code,
|
|
779
|
+
),
|
|
780
|
+
)
|
|
781
|
+
return response
|
|
782
|
+
except BaseException as exc:
|
|
783
|
+
_emit_log(
|
|
784
|
+
config,
|
|
785
|
+
LogEntry(
|
|
786
|
+
level="error",
|
|
787
|
+
event="error",
|
|
788
|
+
operation=operation,
|
|
789
|
+
duration_ms=_elapsed_ms(start),
|
|
790
|
+
status=_err_status(exc),
|
|
791
|
+
error=repr(exc),
|
|
792
|
+
),
|
|
793
|
+
)
|
|
794
|
+
raise
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
@asynccontextmanager
|
|
798
|
+
async def _open_stream(
|
|
799
|
+
client: httpx.AsyncClient,
|
|
800
|
+
config: ClientConfig,
|
|
801
|
+
method: str,
|
|
802
|
+
url: str,
|
|
803
|
+
headers: dict[str, str],
|
|
804
|
+
json: Any = None,
|
|
805
|
+
content: Any = None,
|
|
806
|
+
data: Any = None,
|
|
807
|
+
files: Any = None,
|
|
808
|
+
) -> AsyncIterator[httpx.Response]:
|
|
809
|
+
"""Open a streaming response through the request hooks. The read timeout is
|
|
810
|
+
disabled (a stream may idle), connect/write/pool use the config timeout. The
|
|
811
|
+
response is closed on every exit path, including a raised status error."""
|
|
812
|
+
req_ctx = RequestContext(method=method, url=url, headers=dict(headers), body=json)
|
|
813
|
+
if config.on_request:
|
|
814
|
+
for hook in config.on_request:
|
|
815
|
+
await hook(req_ctx)
|
|
816
|
+
timeout = httpx.Timeout(config.timeout, read=None)
|
|
817
|
+
b_content, b_data, b_files, b_headers = _body_args(
|
|
818
|
+
content, data, files, req_ctx.headers
|
|
819
|
+
)
|
|
820
|
+
request = client.build_request(
|
|
821
|
+
req_ctx.method,
|
|
822
|
+
req_ctx.url,
|
|
823
|
+
headers=b_headers,
|
|
824
|
+
json=req_ctx.body
|
|
825
|
+
if content is None and data is None and files is None
|
|
826
|
+
else None,
|
|
827
|
+
content=b_content,
|
|
828
|
+
data=b_data,
|
|
829
|
+
files=b_files,
|
|
830
|
+
timeout=timeout,
|
|
831
|
+
)
|
|
832
|
+
response = await client.send(request, stream=True)
|
|
833
|
+
try:
|
|
834
|
+
if not 200 <= response.status_code < 300:
|
|
835
|
+
await response.aread()
|
|
836
|
+
_raise_for_status(response)
|
|
837
|
+
yield response
|
|
838
|
+
finally:
|
|
839
|
+
await response.aclose()
|
|
840
|
+
|
|
841
|
+
|
|
842
|
+
__all__ = [
|
|
843
|
+
"ClientConfig",
|
|
844
|
+
"FileInput",
|
|
845
|
+
"InvalidationConfig",
|
|
846
|
+
"LogEntry",
|
|
847
|
+
"PathParamError",
|
|
848
|
+
"RequestContext",
|
|
849
|
+
"ResponseContext",
|
|
850
|
+
"SDKResult",
|
|
851
|
+
"_AuthState",
|
|
852
|
+
"_build_headers",
|
|
853
|
+
"_build_url",
|
|
854
|
+
"_do_request_async",
|
|
855
|
+
"_do_request_sync",
|
|
856
|
+
"_expand_path",
|
|
857
|
+
"_open_stream",
|
|
858
|
+
"_parse_body",
|
|
859
|
+
"_raise_for_status",
|
|
860
|
+
]
|