@apollo-deploy/tesseract 0.9.1 → 1.0.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.
Files changed (110) hide show
  1. package/dist/adapters/csharp/index.d.ts +12 -0
  2. package/dist/adapters/csharp/index.d.ts.map +1 -0
  3. package/dist/adapters/csharp/index.js +99 -0
  4. package/dist/adapters/csharp/index.js.map +1 -0
  5. package/dist/adapters/go/index.d.ts +12 -0
  6. package/dist/adapters/go/index.d.ts.map +1 -0
  7. package/dist/adapters/go/index.js +135 -0
  8. package/dist/adapters/go/index.js.map +1 -0
  9. package/dist/adapters/kotlin/index.d.ts +13 -0
  10. package/dist/adapters/kotlin/index.d.ts.map +1 -0
  11. package/dist/adapters/kotlin/index.js +186 -0
  12. package/dist/adapters/kotlin/index.js.map +1 -0
  13. package/dist/adapters/php/index.d.ts +20 -0
  14. package/dist/adapters/php/index.d.ts.map +1 -0
  15. package/dist/adapters/php/index.js +123 -0
  16. package/dist/adapters/php/index.js.map +1 -0
  17. package/dist/adapters/python/index.d.ts +12 -0
  18. package/dist/adapters/python/index.d.ts.map +1 -0
  19. package/dist/adapters/python/index.js +145 -0
  20. package/dist/adapters/python/index.js.map +1 -0
  21. package/dist/adapters/ruby/index.d.ts +12 -0
  22. package/dist/adapters/ruby/index.d.ts.map +1 -0
  23. package/dist/adapters/ruby/index.js +86 -0
  24. package/dist/adapters/ruby/index.js.map +1 -0
  25. package/dist/adapters/rust/index.d.ts +13 -0
  26. package/dist/adapters/rust/index.d.ts.map +1 -0
  27. package/dist/adapters/rust/index.js +213 -0
  28. package/dist/adapters/rust/index.js.map +1 -0
  29. package/dist/adapters/typescript/emitter/emitSchema.d.ts +1 -1
  30. package/dist/adapters/typescript/emitter/emitSchema.d.ts.map +1 -1
  31. package/dist/adapters/typescript/emitter/emitSchema.js +1 -1
  32. package/dist/adapters/typescript/emitter/emitSchema.js.map +1 -1
  33. package/dist/adapters/typescript/emitter/emitTypeScriptModels.d.ts +2 -2
  34. package/dist/adapters/typescript/emitter/emitTypeScriptModels.d.ts.map +1 -1
  35. package/dist/adapters/typescript/emitter/emitTypeScriptModels.js +2 -2
  36. package/dist/adapters/typescript/emitter/emitTypeScriptModels.js.map +1 -1
  37. package/dist/adapters/typescript/emitter/importCollector.d.ts +1 -1
  38. package/dist/adapters/typescript/emitter/importCollector.d.ts.map +1 -1
  39. package/dist/cli.js +2 -1
  40. package/dist/cli.js.map +1 -1
  41. package/dist/index.d.ts.map +1 -1
  42. package/dist/index.js +14 -0
  43. package/dist/index.js.map +1 -1
  44. package/dist/pipeline/intake.js +23 -16
  45. package/dist/pipeline/intake.js.map +1 -1
  46. package/dist/types/ir.d.ts +1 -1
  47. package/dist/types/ir.d.ts.map +1 -1
  48. package/dist/types/sdk-module.d.ts +22 -2
  49. package/dist/types/sdk-module.d.ts.map +1 -1
  50. package/dist/types/sdk-module.js +1 -1
  51. package/package.json +1 -1
  52. package/templates/csharp/client.hbs +95 -0
  53. package/templates/csharp/csproj.hbs +18 -0
  54. package/templates/csharp/domain.hbs +183 -0
  55. package/templates/csharp/errors.hbs +45 -0
  56. package/templates/csharp/readme.hbs +76 -0
  57. package/templates/csharp/transport.hbs +291 -0
  58. package/templates/csharp/types.hbs +104 -0
  59. package/templates/go/client.hbs +158 -0
  60. package/templates/go/domain.hbs +89 -0
  61. package/templates/go/errors.hbs +38 -0
  62. package/templates/go/go-mod.hbs +6 -0
  63. package/templates/go/readme.hbs +73 -0
  64. package/templates/go/transport.hbs +379 -0
  65. package/templates/go/types.hbs +80 -0
  66. package/templates/go/utils.hbs +45 -0
  67. package/templates/kotlin/build-gradle.hbs +41 -0
  68. package/templates/kotlin/client.hbs +57 -0
  69. package/templates/kotlin/domain.hbs +73 -0
  70. package/templates/kotlin/errors.hbs +42 -0
  71. package/templates/kotlin/gradle-properties.hbs +2 -0
  72. package/templates/kotlin/readme.hbs +105 -0
  73. package/templates/kotlin/settings-gradle.hbs +1 -0
  74. package/templates/kotlin/transport.hbs +246 -0
  75. package/templates/kotlin/types.hbs +100 -0
  76. package/templates/php/client.hbs +74 -0
  77. package/templates/php/composer.hbs +19 -0
  78. package/templates/php/domain.hbs +173 -0
  79. package/templates/php/errors.hbs +131 -0
  80. package/templates/php/readme.hbs +89 -0
  81. package/templates/php/transport.hbs +285 -0
  82. package/templates/php/types.hbs +95 -0
  83. package/templates/python/client.hbs +102 -0
  84. package/templates/python/domain.hbs +139 -0
  85. package/templates/python/index.hbs +20 -0
  86. package/templates/python/pyproject.hbs +26 -0
  87. package/templates/python/readme.hbs +64 -0
  88. package/templates/python/resources-init.hbs +11 -0
  89. package/templates/python/transport.hbs +191 -0
  90. package/templates/python/types-errors.hbs +75 -0
  91. package/templates/python/types.hbs +79 -0
  92. package/templates/ruby/Gemfile.hbs +5 -0
  93. package/templates/ruby/client.hbs +86 -0
  94. package/templates/ruby/domain.hbs +110 -0
  95. package/templates/ruby/errors.hbs +150 -0
  96. package/templates/ruby/gemspec.hbs +30 -0
  97. package/templates/ruby/index.hbs +54 -0
  98. package/templates/ruby/readme.hbs +113 -0
  99. package/templates/ruby/transport.hbs +177 -0
  100. package/templates/ruby/types.hbs +126 -0
  101. package/templates/ruby/version.hbs +5 -0
  102. package/templates/rust/api-mod.hbs +3 -0
  103. package/templates/rust/cargo.hbs +22 -0
  104. package/templates/rust/client.hbs +114 -0
  105. package/templates/rust/domain.hbs +94 -0
  106. package/templates/rust/error.hbs +74 -0
  107. package/templates/rust/lib.hbs +85 -0
  108. package/templates/rust/readme.hbs +77 -0
  109. package/templates/rust/transport.hbs +287 -0
  110. package/templates/rust/types.hbs +142 -0
@@ -0,0 +1,95 @@
1
+ <?php
2
+
3
+ /**
4
+ * @generated
5
+ * This file was automatically generated by Tesseract.
6
+ * DO NOT MODIFY THIS FILE DIRECTLY.
7
+ */
8
+
9
+ declare(strict_types=1);
10
+
11
+ namespace {{namespace}};
12
+
13
+ {{#each schemas}}
14
+ {{#if isEnum}}
15
+ /**
16
+ {{#if description}}
17
+ * {{description}}
18
+ *
19
+ {{/if}}
20
+ * @generated
21
+ */
22
+ enum {{name}}: {{#each properties}}{{#if @first}}{{type}}{{/if}}{{/each}}
23
+ {
24
+ {{#each enumValues}}
25
+ case {{this}} = '{{this}}';
26
+ {{/each}}
27
+ }
28
+
29
+ {{else if isTypeAlias}}
30
+ {{!-- PHP type aliases are not direct; they are used as docblock hints --}}
31
+ {{!-- Skipping type alias: {{name}} --}}
32
+
33
+ {{else if isUnionType}}
34
+ {{!-- PHP union types are handled via properties, not standalone declarations --}}
35
+ {{!-- Skipping union type: {{name}} --}}
36
+
37
+ {{else}}
38
+ /**
39
+ {{#if description}}
40
+ * {{description}}
41
+ *
42
+ {{/if}}
43
+ * @generated
44
+ */
45
+ class {{name}}{{#if extends}} extends {{extends}}{{/if}}
46
+ {
47
+ {{#each properties}}
48
+ /**
49
+ * @var {{tsType type nullable}}
50
+ */
51
+ public {{#if nullable}}?{{/if}}{{type}} ${{name}}{{#if defaultValue}} = {{{json defaultValue}}}{{/if}};
52
+
53
+ {{/each}}
54
+
55
+ /**
56
+ * @param array<string, mixed> $data
57
+ */
58
+ public static function fromArray(array $data): self
59
+ {
60
+ $instance = new self();
61
+
62
+ {{#each properties}}
63
+ {{#if (eq type "string")}}
64
+ $instance->{{name}} = isset($data['{{name}}']) ? (string) $data['{{name}}'] : {{#if required}}throw new \InvalidArgumentException('Missing required field: {{name}}'){{else}}null{{/if}};
65
+ {{else if (eq type "int")}}
66
+ $instance->{{name}} = isset($data['{{name}}']) ? (int) $data['{{name}}'] : {{#if required}}throw new \InvalidArgumentException('Missing required field: {{name}}'){{else}}null{{/if}};
67
+ {{else if (eq type "float")}}
68
+ $instance->{{name}} = isset($data['{{name}}']) ? (float) $data['{{name}}'] : {{#if required}}throw new \InvalidArgumentException('Missing required field: {{name}}'){{else}}null{{/if}};
69
+ {{else if (eq type "bool")}}
70
+ $instance->{{name}} = isset($data['{{name}}']) ? (bool) $data['{{name}}'] : {{#if required}}throw new \InvalidArgumentException('Missing required field: {{name}}'){{else}}null{{/if}};
71
+ {{else if (eq type "array")}}
72
+ $instance->{{name}} = isset($data['{{name}}']) ? (array) $data['{{name}}'] : {{#if required}}[]{{else}}null{{/if}};
73
+ {{else}}
74
+ $instance->{{name}} = $data['{{name}}'] ?? {{#if required}}throw new \InvalidArgumentException('Missing required field: {{name}}'){{else}}null{{/if}};
75
+ {{/if}}
76
+
77
+ {{/each}}
78
+ return $instance;
79
+ }
80
+
81
+ /**
82
+ * @return array<string, mixed>
83
+ */
84
+ public function toArray(): array
85
+ {
86
+ return [
87
+ {{#each properties}}
88
+ '{{name}}' => $this->{{name}},
89
+ {{/each}}
90
+ ];
91
+ }
92
+ }
93
+
94
+ {{/if}}
95
+ {{/each}}
@@ -0,0 +1,102 @@
1
+ """{{title}} SDK Client"""
2
+
3
+ from typing import Optional, Dict, Any, List, Callable, Union
4
+ import httpx
5
+ from .transport import create_transport, SDKTransport, RequestOptions
6
+ {{#each groups}}
7
+ from .resources.{{fileName}} import {{interfaceName}}
8
+ {{/each}}
9
+
10
+ {{#if hasSSE}}
11
+ # SSE support requires the sseclient-py package:
12
+ # pip install sseclient-py>=1.8.0
13
+ {{/if}}
14
+
15
+ class {{clientName}}ClientConfig:
16
+ """Configuration for {{title}} client."""
17
+
18
+ def __init__(
19
+ self,
20
+ base_url: str = "{{baseUrl}}",
21
+ {{#each securitySchemes}}
22
+ {{#if (eq type "apiKey")}}
23
+ {{configKey}}: Optional[str] = None,
24
+ {{/if}}
25
+ {{#if (and (eq type "http") (eq scheme "bearer"))}}
26
+ {{configKey}}: Optional[str] = None,
27
+ {{/if}}
28
+ {{#if (eq type "oauth2")}}
29
+ {{configKey}}: Optional[str] = None,
30
+ {{/if}}
31
+ {{#if (eq type "openIdConnect")}}
32
+ {{configKey}}: Optional[str] = None,
33
+ {{/if}}
34
+ {{/each}}
35
+ timeout_ms: int = 15000,
36
+ retries: int = 3,
37
+ default_headers: Optional[Dict[str, str]] = None,
38
+ on_request: Optional[Callable] = None,
39
+ on_response: Optional[Callable] = None,
40
+ on_error: Optional[Callable] = None,
41
+ ):
42
+ self.base_url = base_url
43
+ {{#each securitySchemes}}
44
+ self.{{configKey}} = {{configKey}}
45
+ {{/each}}
46
+ self.timeout_ms = timeout_ms
47
+ self.retries = retries
48
+ self.default_headers = default_headers or {}
49
+ self.on_request = on_request
50
+ self.on_response = on_response
51
+ self.on_error = on_error
52
+
53
+
54
+ class {{clientName}}Client:
55
+ """{{title}} SDK Client"""
56
+
57
+ def __init__(self, config: Optional[{{clientName}}ClientConfig] = None):
58
+ self.config = config or {{clientName}}ClientConfig()
59
+ self._transport = create_transport(
60
+ base_url=self.config.base_url,
61
+ {{#each securitySchemes}}
62
+ {{configKey}}=self.config.{{configKey}},
63
+ {{/each}}
64
+ timeout_ms=self.config.timeout_ms,
65
+ retries=self.config.retries,
66
+ default_headers=self.config.default_headers,
67
+ on_request=self.config.on_request,
68
+ on_response=self.config.on_response,
69
+ on_error=self.config.on_error,
70
+ )
71
+ {{#each groups}}
72
+ self.{{snakeCase name}} = {{interfaceName}}(self._transport)
73
+ {{/each}}
74
+
75
+
76
+ def create_{{snakeCase clientName}}_client(
77
+ base_url: str = "{{baseUrl}}",
78
+ {{#each securitySchemes}}
79
+ {{#if (eq type "apiKey")}}
80
+ {{configKey}}: Optional[str] = None,
81
+ {{/if}}
82
+ {{#if (and (eq type "http") (eq scheme "bearer"))}}
83
+ {{configKey}}: Optional[str] = None,
84
+ {{/if}}
85
+ {{#if (eq type "oauth2")}}
86
+ {{configKey}}: Optional[str] = None,
87
+ {{/if}}
88
+ {{#if (eq type "openIdConnect")}}
89
+ {{configKey}}: Optional[str] = None,
90
+ {{/if}}
91
+ {{/each}}
92
+ **kwargs,
93
+ ) -> {{clientName}}Client:
94
+ """Create a new {{title}} SDK client."""
95
+ config = {{clientName}}ClientConfig(
96
+ base_url=base_url,
97
+ {{#each securitySchemes}}
98
+ {{configKey}}={{configKey}},
99
+ {{/each}}
100
+ **kwargs,
101
+ )
102
+ return {{clientName}}Client(config)
@@ -0,0 +1,139 @@
1
+ """{{interfaceName}} operations."""
2
+
3
+ from typing import Optional, Dict, Any, List
4
+ {{#if imports.length}}
5
+ from ..types import {{#each imports}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}
6
+ {{/if}}
7
+ from ..transport import SDKTransport, RequestOptions
8
+ from ..errors import SDKError
9
+
10
+
11
+ class {{interfaceName}}:
12
+ """{{#if description}}{{description}}{{else}}{{interfaceName}} API operations{{/if}}"""
13
+
14
+ def __init__(self, transport: SDKTransport):
15
+ self._transport = transport
16
+
17
+ {{#each operations}}
18
+ {{#if isEventStream}}
19
+ def {{snakeCase name}}(
20
+ self,
21
+ {{#each pathParams}}
22
+ {{name}}: str,
23
+ {{/each}}
24
+ {{#if (and queryType (not queryParams.length))}}
25
+ *,
26
+ query: Optional[Dict[str, Any]] = None,
27
+ {{/if}}
28
+ {{#if queryParams.length}}
29
+ *,
30
+ {{#each queryParams}}
31
+ {{name}}: Optional[{{pythonType type}}] = None,
32
+ {{/each}}
33
+ {{/if}}
34
+ options: Optional[RequestOptions] = None,
35
+ ):
36
+ """{{#if summary}}{{summary}}{{#if description}} - {{description}}{{/if}}{{else if description}}{{description}}{{/if}} (SSE stream)
37
+ """
38
+ {{#if deprecated}}
39
+ # @deprecated {{deprecationMessage}}
40
+ {{/if}}
41
+ # Build path with parameter substitution
42
+ _path = "{{path}}"
43
+ {{#each pathParams}}
44
+ _path = _path.replace("{" + "{{originalName}}" + "}", str({{name}}))
45
+ {{/each}}
46
+
47
+ # Build query parameters
48
+ _query: Dict[str, Any] = {}
49
+ {{#each queryParams}}
50
+ if query is not None and hasattr(query, "{{name}}") and getattr(query, "{{name}}") is not None:
51
+ _query["{{originalName}}"] = getattr(query, "{{name}}")
52
+ {{/each}}
53
+
54
+ response = self._transport.request(
55
+ method="{{httpMethod}}",
56
+ path=_path,
57
+ query=_query if _query else None,
58
+ options=options,
59
+ )
60
+ return response.json()
61
+
62
+ {{else}}
63
+ def {{snakeCase name}}(
64
+ self,
65
+ {{#each pathParams}}
66
+ {{name}}: str,
67
+ {{/each}}
68
+ {{#if requestBody}}
69
+ body: {{pythonType requestBody.type}},
70
+ {{/if}}
71
+ {{#if (and queryType (not queryParams.length))}}
72
+ *,
73
+ query: Optional[Dict[str, Any]] = None,
74
+ {{/if}}
75
+ {{#if queryParams.length}}
76
+ *,
77
+ {{#each queryParams}}
78
+ {{name}}: Optional[{{pythonType type}}] = None,
79
+ {{/each}}
80
+ {{/if}}
81
+ {{#if headerType}}
82
+ headers: Optional[{{pythonType headerType}}] = None,
83
+ {{/if}}
84
+ options: Optional[RequestOptions] = None,
85
+ ) -> {{pythonType responseType}}:
86
+ """{{#if summary}}{{summary}}{{#if description}} - {{description}}{{/if}}{{else if description}}{{description}}{{/if}}
87
+ """
88
+ {{#if deprecated}}
89
+ # @deprecated {{deprecationMessage}}
90
+ {{/if}}
91
+ # Build path with parameter substitution
92
+ _path = "{{path}}"
93
+ {{#each pathParams}}
94
+ _path = _path.replace("{" + "{{originalName}}" + "}", str({{name}}))
95
+ {{/each}}
96
+
97
+ # Build query parameters
98
+ _query: Dict[str, Any] = {}
99
+ {{#each queryParams}}
100
+ if {{name}} is not None:
101
+ _query["{{originalName}}"] = {{name}}
102
+ {{/each}}
103
+ {{#if queryType}}
104
+ if query is not None:
105
+ _items = query.__dict__.items() if hasattr(query, "__dict__") else []
106
+ for k, v in _items:
107
+ if v is not None:
108
+ _query[k] = v
109
+ {{/if}}
110
+
111
+ # Build headers
112
+ _headers: Dict[str, str] = {}
113
+ {{#each headerParams}}
114
+ if headers is not None and hasattr(headers, "{{name}}") and getattr(headers, "{{name}}") is not None:
115
+ _headers["{{originalName}}"] = str(getattr(headers, "{{name}}"))
116
+ {{/each}}
117
+
118
+ {{#if (hasRequestBody this)}}
119
+ _content_type = "{{requestBody.contentType}}"
120
+ {{/if}}
121
+
122
+ response = self._transport.request(
123
+ method="{{httpMethod}}",
124
+ path=_path,
125
+ {{#if (or queryParams.length queryType)}}
126
+ query=_query if _query else None,
127
+ {{/if}}
128
+ {{#if (hasRequestBody this)}}
129
+ body=body,
130
+ content_type=_content_type,
131
+ {{/if}}
132
+ {{#if (or headerParams.length (hasRequestBody this))}}
133
+ options=RequestOptions(headers=_headers if _headers else None),
134
+ {{/if}}
135
+ )
136
+ return response.json()
137
+
138
+ {{/if}}
139
+ {{/each}}
@@ -0,0 +1,20 @@
1
+ """
2
+ {{title}} Python SDK
3
+ """
4
+
5
+ from .client import create_{{snakeCase clientName}}_client, {{clientName}}Client
6
+ from .errors import SDKError
7
+ from .resources import (
8
+ {{#each groups}}
9
+ {{interfaceName}},
10
+ {{/each}}
11
+ )
12
+
13
+ __all__ = [
14
+ "create_{{snakeCase clientName}}_client",
15
+ "{{clientName}}Client",
16
+ "SDKError",
17
+ {{#each groups}}
18
+ "{{interfaceName}}",
19
+ {{/each}}
20
+ ]
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "{{packageName}}"
7
+ version = "{{#if packageVersion}}{{packageVersion}}{{else}}{{version}}{{/if}}"
8
+ description = "{{#if description}}{{description}}{{else}}Python SDK for {{title}}{{/if}}"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = {text = "MIT"}
12
+ dependencies = [
13
+ "httpx>=0.27.0",
14
+ "pydantic>=2.0.0",
15
+ "python-dateutil>=2.8.0",
16
+ ]
17
+
18
+ [project.optional-dependencies]
19
+ dev = [
20
+ "pytest>=8.0",
21
+ "pytest-asyncio>=0.24.0",
22
+ "mypy>=1.0",
23
+ ]
24
+
25
+ [tool.setuptools.packages.find]
26
+ include = ["{{moduleName}}*"]
@@ -0,0 +1,64 @@
1
+ # {{title}} Python SDK
2
+
3
+ {{#if description}}
4
+ {{description}}
5
+ {{/if}}
6
+
7
+ > Auto-generated by Tesseract.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ pip install {{packageName}}
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```python
18
+ from {{moduleName}} import create_{{snakeCase clientName}}_client
19
+
20
+ client = create_{{snakeCase clientName}}_client(
21
+ base_url="{{baseUrl}}",
22
+ {{#each securitySchemes}}
23
+ {{#if (eq type "apiKey")}}
24
+ {{configKey}}="your-api-key",
25
+ {{/if}}
26
+ {{#if (and (eq type "http") (eq scheme "bearer"))}}
27
+ {{configKey}}="your-bearer-token",
28
+ {{/if}}
29
+ {{/each}}
30
+ )
31
+
32
+ {{#with groups.[0]}}
33
+ # Example: {{interfaceName}}
34
+ result = client.{{snakeCase name}}.{{snakeCase operations.[0].name}}()
35
+ {{/with}}
36
+ ```
37
+
38
+ ## API Reference
39
+
40
+ {{#each groups}}
41
+ ### {{interfaceName}}
42
+
43
+ {{#if description}}
44
+ {{description}}
45
+ {{/if}}
46
+
47
+ | Method | HTTP | Path | Description |
48
+ |--------|------|------|-------------|
49
+ {{#each operations}}
50
+ | `{{name}}` | {{httpMethod}} | `{{path}}` | {{#if description}}{{description}}{{else}}-{{/if}} |
51
+ {{/each}}
52
+
53
+ {{/each}}
54
+
55
+ ## Error Handling
56
+
57
+ ```python
58
+ from {{moduleName}}.errors import SDKError
59
+
60
+ try:
61
+ result = client.some_api.some_method()
62
+ except SDKError as e:
63
+ print(f"Error {e.status}: {e.message} ({e.code})")
64
+ ```
@@ -0,0 +1,11 @@
1
+ """API resource modules for {{moduleName}}."""
2
+
3
+ {{#each groups}}
4
+ from .{{fileName}} import {{interfaceName}}
5
+ {{/each}}
6
+
7
+ __all__ = [
8
+ {{#each groups}}
9
+ "{{interfaceName}}",
10
+ {{/each}}
11
+ ]
@@ -0,0 +1,191 @@
1
+ """HTTP transport layer for {{moduleName}}."""
2
+
3
+ import time
4
+ import json
5
+ import random
6
+ from typing import Optional, Dict, Any, Callable
7
+ from urllib.parse import urlencode
8
+ import httpx
9
+ from .errors import SDKError
10
+
11
+
12
+ class RequestOptions:
13
+ """Per-request options."""
14
+
15
+ def __init__(
16
+ self,
17
+ timeout_ms: Optional[int] = None,
18
+ headers: Optional[Dict[str, str]] = None,
19
+ signal: Optional[Any] = None,
20
+ ):
21
+ self.timeout_ms = timeout_ms
22
+ self.headers = headers or {}
23
+ self.signal = signal
24
+
25
+
26
+ class SDKTransport:
27
+ """HTTP transport with retry, auth, and error handling."""
28
+
29
+ def __init__(
30
+ self,
31
+ base_url: str,
32
+ {{#each securitySchemes}}
33
+ {{configKey}}: Optional[str] = None,
34
+ {{/each}}
35
+ timeout_ms: int = 15000,
36
+ retries: int = 3,
37
+ default_headers: Optional[Dict[str, str]] = None,
38
+ on_request: Optional[Callable] = None,
39
+ on_response: Optional[Callable] = None,
40
+ on_error: Optional[Callable] = None,
41
+ ):
42
+ self.base_url = base_url.rstrip("/")
43
+ {{#each securitySchemes}}
44
+ self._{{configKey}} = {{configKey}}
45
+ {{/each}}
46
+ self.timeout_ms = timeout_ms
47
+ self.max_retries = retries
48
+ self.default_headers = default_headers or {}
49
+ self.on_request = on_request
50
+ self.on_response = on_response
51
+ self.on_error = on_error
52
+ self._client = httpx.Client(timeout=timeout_ms / 1000.0)
53
+
54
+ def _build_headers(self, extra_headers: Optional[Dict[str, str]] = None) -> Dict[str, str]:
55
+ headers = {**self.default_headers}
56
+ {{#each securitySchemes}}
57
+ {{#if (eq type "apiKey")}}
58
+ if self._{{configKey}}:
59
+ headers["{{paramName}}"] = self._{{configKey}}
60
+ {{/if}}
61
+ {{#if (and (eq type "http") (eq scheme "bearer"))}}
62
+ if self._{{configKey}}:
63
+ headers["Authorization"] = "Bearer " + self._{{configKey}}
64
+ {{/if}}
65
+ {{#if (eq type "oauth2")}}
66
+ if self._{{configKey}}:
67
+ headers["Authorization"] = "Bearer " + self._{{configKey}}
68
+ {{/if}}
69
+ {{#if (eq type "openIdConnect")}}
70
+ if self._{{configKey}}:
71
+ headers["Authorization"] = "Bearer " + self._{{configKey}}
72
+ {{/if}}
73
+ {{/each}}
74
+ if extra_headers:
75
+ headers.update(extra_headers)
76
+ return headers
77
+
78
+ def request(
79
+ self,
80
+ method: str,
81
+ path: str,
82
+ query: Optional[Dict[str, Any]] = None,
83
+ body: Optional[Any] = None,
84
+ content_type: str = "application/json",
85
+ options: Optional[RequestOptions] = None,
86
+ ) -> httpx.Response:
87
+ """Execute an HTTP request with retry logic."""
88
+ url = self.base_url + path
89
+ headers = self._build_headers(options.headers if options else None)
90
+ timeout = (options.timeout_ms if options and options.timeout_ms else self.timeout_ms) / 1000.0
91
+
92
+ if query:
93
+ filtered = {k: v for k, v in query.items() if v is not None}
94
+ if filtered:
95
+ url += "?" + urlencode(filtered)
96
+
97
+ data = None
98
+ if body is not None:
99
+ if content_type == "application/json":
100
+ data = json.dumps(body, default=str)
101
+ headers["Content-Type"] = "application/json"
102
+ else:
103
+ data = body
104
+
105
+ last_error = None
106
+ for attempt in range(self.max_retries + 1):
107
+ try:
108
+ if self.on_request:
109
+ self.on_request(method, url, attempt)
110
+
111
+ response = self._client.request(
112
+ method=method,
113
+ url=url,
114
+ headers=headers,
115
+ content=data,
116
+ timeout=timeout,
117
+ )
118
+
119
+ if self.on_response:
120
+ self.on_response(response)
121
+
122
+ if response.status_code >= 400:
123
+ self._handle_error_response(response)
124
+
125
+ return response
126
+
127
+ except httpx.TimeoutException as e:
128
+ last_error = SDKError("Request timed out: " + str(e), 0, "timeout")
129
+ except httpx.NetworkError as e:
130
+ last_error = SDKError("Network error: " + str(e), 0, "network_error")
131
+ except SDKError:
132
+ raise
133
+ except Exception as e:
134
+ last_error = SDKError("Request failed: " + str(e), 0, "unknown_error")
135
+
136
+ if attempt < self.max_retries:
137
+ delay = min(1000 * (2 ** attempt) + random.uniform(0, 500), 30000) / 1000.0
138
+ time.sleep(delay)
139
+
140
+ if self.on_error and last_error:
141
+ self.on_error(last_error)
142
+ raise last_error
143
+
144
+ def _handle_error_response(self, response: httpx.Response) -> None:
145
+ try:
146
+ body = response.json()
147
+ error_code = body.get("error", "unknown_error")
148
+ message = body.get("message", response.text)
149
+ request_id = body.get("request_id")
150
+ raise SDKError(message, response.status_code, error_code, request_id)
151
+ except (json.JSONDecodeError, KeyError):
152
+ raise SDKError(
153
+ "HTTP " + str(response.status_code) + ": " + str(response.text)[:200],
154
+ response.status_code,
155
+ "http_error",
156
+ )
157
+
158
+ def close(self):
159
+ self._client.close()
160
+
161
+ def __enter__(self):
162
+ return self
163
+
164
+ def __exit__(self, *args):
165
+ self.close()
166
+
167
+
168
+ def create_transport(
169
+ base_url: str,
170
+ {{#each securitySchemes}}
171
+ {{configKey}}: Optional[str] = None,
172
+ {{/each}}
173
+ timeout_ms: int = 15000,
174
+ retries: int = 3,
175
+ default_headers: Optional[Dict[str, str]] = None,
176
+ on_request: Optional[Callable] = None,
177
+ on_response: Optional[Callable] = None,
178
+ on_error: Optional[Callable] = None,
179
+ ) -> SDKTransport:
180
+ return SDKTransport(
181
+ base_url=base_url,
182
+ {{#each securitySchemes}}
183
+ {{configKey}}={{configKey}},
184
+ {{/each}}
185
+ timeout_ms=timeout_ms,
186
+ retries=retries,
187
+ default_headers=default_headers,
188
+ on_request=on_request,
189
+ on_response=on_response,
190
+ on_error=on_error,
191
+ )