@apollo-deploy/tesseract 0.9.1 → 1.2.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/dist/adapters/csharp/index.d.ts +12 -0
- package/dist/adapters/csharp/index.d.ts.map +1 -0
- package/dist/adapters/csharp/index.js +99 -0
- package/dist/adapters/csharp/index.js.map +1 -0
- package/dist/adapters/go/index.d.ts +12 -0
- package/dist/adapters/go/index.d.ts.map +1 -0
- package/dist/adapters/go/index.js +135 -0
- package/dist/adapters/go/index.js.map +1 -0
- package/dist/adapters/kotlin/index.d.ts +13 -0
- package/dist/adapters/kotlin/index.d.ts.map +1 -0
- package/dist/adapters/kotlin/index.js +188 -0
- package/dist/adapters/kotlin/index.js.map +1 -0
- package/dist/adapters/php/index.d.ts +20 -0
- package/dist/adapters/php/index.d.ts.map +1 -0
- package/dist/adapters/php/index.js +123 -0
- package/dist/adapters/php/index.js.map +1 -0
- package/dist/adapters/python/index.d.ts +12 -0
- package/dist/adapters/python/index.d.ts.map +1 -0
- package/dist/adapters/python/index.js +145 -0
- package/dist/adapters/python/index.js.map +1 -0
- package/dist/adapters/ruby/index.d.ts +12 -0
- package/dist/adapters/ruby/index.d.ts.map +1 -0
- package/dist/adapters/ruby/index.js +86 -0
- package/dist/adapters/ruby/index.js.map +1 -0
- package/dist/adapters/rust/index.d.ts +13 -0
- package/dist/adapters/rust/index.d.ts.map +1 -0
- package/dist/adapters/rust/index.js +213 -0
- package/dist/adapters/rust/index.js.map +1 -0
- package/dist/adapters/typescript/emitter/emitSchema.d.ts +1 -1
- package/dist/adapters/typescript/emitter/emitSchema.d.ts.map +1 -1
- package/dist/adapters/typescript/emitter/emitSchema.js +1 -1
- package/dist/adapters/typescript/emitter/emitSchema.js.map +1 -1
- package/dist/adapters/typescript/emitter/emitTypeScriptModels.d.ts +2 -2
- package/dist/adapters/typescript/emitter/emitTypeScriptModels.d.ts.map +1 -1
- package/dist/adapters/typescript/emitter/emitTypeScriptModels.js +5 -3
- package/dist/adapters/typescript/emitter/emitTypeScriptModels.js.map +1 -1
- package/dist/adapters/typescript/emitter/importCollector.d.ts +1 -1
- package/dist/adapters/typescript/emitter/importCollector.d.ts.map +1 -1
- package/dist/adapters/typescript/index.d.ts.map +1 -1
- package/dist/adapters/typescript/index.js +43 -2
- package/dist/adapters/typescript/index.js.map +1 -1
- package/dist/cli.js +2 -1
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/pipeline/intake.d.ts.map +1 -1
- package/dist/pipeline/intake.js +32 -21
- package/dist/pipeline/intake.js.map +1 -1
- package/dist/types/ir.d.ts +1 -1
- package/dist/types/ir.d.ts.map +1 -1
- package/dist/types/sdk-module.d.ts +22 -2
- package/dist/types/sdk-module.d.ts.map +1 -1
- package/dist/types/sdk-module.js +1 -1
- package/package.json +1 -1
- package/templates/csharp/client.hbs +95 -0
- package/templates/csharp/csproj.hbs +18 -0
- package/templates/csharp/domain.hbs +183 -0
- package/templates/csharp/errors.hbs +45 -0
- package/templates/csharp/readme.hbs +76 -0
- package/templates/csharp/transport.hbs +291 -0
- package/templates/csharp/types.hbs +104 -0
- package/templates/go/client.hbs +158 -0
- package/templates/go/domain.hbs +89 -0
- package/templates/go/errors.hbs +38 -0
- package/templates/go/go-mod.hbs +6 -0
- package/templates/go/readme.hbs +73 -0
- package/templates/go/transport.hbs +379 -0
- package/templates/go/types.hbs +80 -0
- package/templates/go/utils.hbs +45 -0
- package/templates/kotlin/build-gradle.hbs +151 -0
- package/templates/kotlin/client.hbs +57 -0
- package/templates/kotlin/domain.hbs +73 -0
- package/templates/kotlin/errors.hbs +42 -0
- package/templates/kotlin/gradle-properties.hbs +2 -0
- package/templates/kotlin/readme.hbs +133 -0
- package/templates/kotlin/settings-gradle.hbs +1 -0
- package/templates/kotlin/transport.hbs +246 -0
- package/templates/kotlin/types.hbs +100 -0
- package/templates/php/client.hbs +74 -0
- package/templates/php/composer.hbs +19 -0
- package/templates/php/domain.hbs +173 -0
- package/templates/php/errors.hbs +131 -0
- package/templates/php/readme.hbs +89 -0
- package/templates/php/transport.hbs +285 -0
- package/templates/php/types.hbs +95 -0
- package/templates/python/client.hbs +102 -0
- package/templates/python/domain.hbs +139 -0
- package/templates/python/index.hbs +20 -0
- package/templates/python/pyproject.hbs +26 -0
- package/templates/python/readme.hbs +64 -0
- package/templates/python/resources-init.hbs +11 -0
- package/templates/python/transport.hbs +191 -0
- package/templates/python/types-errors.hbs +75 -0
- package/templates/python/types.hbs +79 -0
- package/templates/ruby/Gemfile.hbs +5 -0
- package/templates/ruby/client.hbs +86 -0
- package/templates/ruby/domain.hbs +110 -0
- package/templates/ruby/errors.hbs +150 -0
- package/templates/ruby/gemspec.hbs +30 -0
- package/templates/ruby/index.hbs +54 -0
- package/templates/ruby/readme.hbs +113 -0
- package/templates/ruby/transport.hbs +177 -0
- package/templates/ruby/types.hbs +126 -0
- package/templates/ruby/version.hbs +5 -0
- package/templates/rust/api-mod.hbs +3 -0
- package/templates/rust/cargo.hbs +22 -0
- package/templates/rust/client.hbs +114 -0
- package/templates/rust/domain.hbs +94 -0
- package/templates/rust/error.hbs +74 -0
- package/templates/rust/lib.hbs +85 -0
- package/templates/rust/readme.hbs +77 -0
- package/templates/rust/transport.hbs +287 -0
- package/templates/rust/types.hbs +142 -0
|
@@ -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,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
|
+
)
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"""Error classes for {{moduleName}}."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Dict, Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class SDKError(Exception):
|
|
7
|
+
"""Structured error thrown by all SDK operations."""
|
|
8
|
+
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
message: str,
|
|
12
|
+
status: int,
|
|
13
|
+
code: str,
|
|
14
|
+
request_id: Optional[str] = None,
|
|
15
|
+
timestamp: Optional[str] = None,
|
|
16
|
+
path: Optional[str] = None,
|
|
17
|
+
method: Optional[str] = None,
|
|
18
|
+
hint: Optional[str] = None,
|
|
19
|
+
details: Optional[Any] = None,
|
|
20
|
+
):
|
|
21
|
+
super().__init__(message)
|
|
22
|
+
self.message = message
|
|
23
|
+
self.status = status
|
|
24
|
+
self.code = code
|
|
25
|
+
self.request_id = request_id
|
|
26
|
+
self.timestamp = timestamp
|
|
27
|
+
self.path = path
|
|
28
|
+
self.method = method
|
|
29
|
+
self.hint = hint
|
|
30
|
+
self.details = details
|
|
31
|
+
|
|
32
|
+
def is_rate_limited(self) -> bool:
|
|
33
|
+
return self.code == "rate_limit_exceeded"
|
|
34
|
+
|
|
35
|
+
def is_auth_error(self) -> bool:
|
|
36
|
+
return self.code in ("unauthorized", "credential_expired", "credential_revoked")
|
|
37
|
+
|
|
38
|
+
def is_forbidden(self) -> bool:
|
|
39
|
+
return self.code == "forbidden"
|
|
40
|
+
|
|
41
|
+
def is_not_found(self) -> bool:
|
|
42
|
+
return self.code == "not_found"
|
|
43
|
+
|
|
44
|
+
def is_validation_error(self) -> bool:
|
|
45
|
+
return self.code in (
|
|
46
|
+
"validation_failed",
|
|
47
|
+
"request_validation_failed",
|
|
48
|
+
"bad_request",
|
|
49
|
+
"unprocessable_entity",
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
def is_server_error(self) -> bool:
|
|
53
|
+
return self.status >= 500
|
|
54
|
+
|
|
55
|
+
def is_client_error(self) -> bool:
|
|
56
|
+
return 400 <= self.status < 500
|
|
57
|
+
|
|
58
|
+
def is_retryable(self) -> bool:
|
|
59
|
+
return (
|
|
60
|
+
self.code in ("network_error", "gateway_timeout")
|
|
61
|
+
or self.status == 429
|
|
62
|
+
or self.status >= 500
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
66
|
+
return {
|
|
67
|
+
"message": self.message,
|
|
68
|
+
"status": self.status,
|
|
69
|
+
"code": self.code,
|
|
70
|
+
"request_id": self.request_id,
|
|
71
|
+
"timestamp": self.timestamp,
|
|
72
|
+
"path": self.path,
|
|
73
|
+
"method": self.method,
|
|
74
|
+
"hint": self.hint,
|
|
75
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""Generated type definitions for {{moduleName}}."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, List, Dict, Any, Literal, Union
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# -- Pagination --------------------------------------------------
|
|
9
|
+
|
|
10
|
+
class PaginationQuery(BaseModel):
|
|
11
|
+
page: Optional[int] = Field(None, description="Page number (1-indexed)")
|
|
12
|
+
limit: Optional[int] = Field(None, description="Number of items per page")
|
|
13
|
+
sort_by: Optional[str] = Field(None, description="Column name to sort by")
|
|
14
|
+
sort_order: Optional[Literal["asc", "desc"]] = Field(None, description="Sort direction")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class PageMeta(BaseModel):
|
|
18
|
+
size: int
|
|
19
|
+
total_pages: int
|
|
20
|
+
has_more: bool
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Page(BaseModel):
|
|
24
|
+
data: List[Any]
|
|
25
|
+
page: PageMeta
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# -- Common Responses --------------------------------------------
|
|
29
|
+
|
|
30
|
+
class SuccessResponse(BaseModel):
|
|
31
|
+
success: bool
|
|
32
|
+
message: Optional[str] = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class MessageResponse(BaseModel):
|
|
36
|
+
message: str
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ApiErrorEnvelope(BaseModel):
|
|
40
|
+
error: str
|
|
41
|
+
message: str
|
|
42
|
+
status: int
|
|
43
|
+
request_id: Optional[str] = None
|
|
44
|
+
timestamp: Optional[str] = None
|
|
45
|
+
path: Optional[str] = None
|
|
46
|
+
method: Optional[str] = None
|
|
47
|
+
hint: Optional[str] = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# -- Generated Schemas -------------------------------------------
|
|
51
|
+
|
|
52
|
+
{{#each schemas}}
|
|
53
|
+
{{#if (eq ownership.kind "external")}}
|
|
54
|
+
{{else}}
|
|
55
|
+
{{#if isEnum}}
|
|
56
|
+
class {{name}}(str):
|
|
57
|
+
"""{{#if description}}{{description}}{{else}}Enum for {{name}}{{/if}}"""
|
|
58
|
+
{{#each enumValues}}
|
|
59
|
+
{{pascalCase this}} = "{{this}}"
|
|
60
|
+
{{/each}}
|
|
61
|
+
|
|
62
|
+
{{else if isUnionType}}
|
|
63
|
+
{{name}} = Union[{{#each unionMembers}}{{this}}{{#unless @last}}, {{/unless}}{{/each}}]
|
|
64
|
+
"""{{#if description}}{{description}}{{else}}Union type for {{name}}{{/if}}"""
|
|
65
|
+
|
|
66
|
+
{{else if isTypeAlias}}
|
|
67
|
+
{{name}} = {{additionalPropertiesType}}
|
|
68
|
+
"""{{#if description}}{{description}}{{else}}Type alias for {{name}}{{/if}}"""
|
|
69
|
+
|
|
70
|
+
{{else}}
|
|
71
|
+
class {{name}}(BaseModel):
|
|
72
|
+
"""{{#if description}}{{description}}{{else}}Schema for {{name}}{{/if}}"""
|
|
73
|
+
{{#each properties}}
|
|
74
|
+
{{name}}: {{#if nullable}}Optional[{{/if}}{{pythonType type}}{{#if nullable}}] = None{{else}}{{#unless required}} = None{{/unless}}{{/if}}
|
|
75
|
+
{{/each}}
|
|
76
|
+
|
|
77
|
+
{{/if}}
|
|
78
|
+
{{/if}}
|
|
79
|
+
{{/each}}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "transport"
|
|
4
|
+
{{#each groups}}
|
|
5
|
+
require_relative "resources/{{fileName}}"
|
|
6
|
+
{{/each}}
|
|
7
|
+
|
|
8
|
+
module {{pascalCase meta.clientName}}
|
|
9
|
+
# Configuration for the {{meta.title}} SDK client.
|
|
10
|
+
class Config
|
|
11
|
+
# @return [String] the base URL for API requests
|
|
12
|
+
attr_accessor :base_url
|
|
13
|
+
# @return [Integer] request timeout in seconds
|
|
14
|
+
attr_accessor :timeout
|
|
15
|
+
# @return [Hash] retry configuration
|
|
16
|
+
attr_accessor :retries
|
|
17
|
+
# @return [Hash] default headers sent with every request
|
|
18
|
+
attr_accessor :default_headers
|
|
19
|
+
{{#each securitySchemes}}
|
|
20
|
+
# @return [String, nil] credential for {{name}} security scheme
|
|
21
|
+
attr_accessor :{{camelCase name}}
|
|
22
|
+
{{/each}}
|
|
23
|
+
|
|
24
|
+
# @param base_url [String] the base URL for API requests
|
|
25
|
+
# @param timeout [Integer] request timeout in seconds (default: 15)
|
|
26
|
+
# @param retries [Hash] retry configuration
|
|
27
|
+
# @param default_headers [Hash] headers sent with every request
|
|
28
|
+
{{#each securitySchemes}}
|
|
29
|
+
# @param {{camelCase name}} [String, nil] credential for {{name}}
|
|
30
|
+
{{/each}}
|
|
31
|
+
def initialize(
|
|
32
|
+
base_url: '{{meta.baseUrl}}',
|
|
33
|
+
timeout: 15,
|
|
34
|
+
retries: { attempts: 3, backoff: 0.4, jitter: true, max_backoff: 30.0 },
|
|
35
|
+
default_headers: {},
|
|
36
|
+
{{#each securitySchemes}}
|
|
37
|
+
{{camelCase name}}: nil,
|
|
38
|
+
{{/each}}
|
|
39
|
+
**_
|
|
40
|
+
)
|
|
41
|
+
@base_url = base_url
|
|
42
|
+
@timeout = timeout
|
|
43
|
+
@retries = retries
|
|
44
|
+
@default_headers = default_headers
|
|
45
|
+
{{#each securitySchemes}}
|
|
46
|
+
@{{camelCase name}} = {{camelCase name}}
|
|
47
|
+
{{/each}}
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Main client for the {{meta.title}} API.
|
|
52
|
+
#
|
|
53
|
+
# Provides access to all API domains as instance methods.
|
|
54
|
+
class Client
|
|
55
|
+
{{#each groups}}
|
|
56
|
+
# @return [{{pascalCase ../meta.clientName}}::{{pascalCase name}}]
|
|
57
|
+
attr_reader :{{name}}
|
|
58
|
+
{{/each}}
|
|
59
|
+
|
|
60
|
+
# @return [{{pascalCase meta.clientName}}::Config] the client configuration
|
|
61
|
+
attr_reader :config
|
|
62
|
+
|
|
63
|
+
# @return [{{pascalCase meta.clientName}}::Transport] the underlying HTTP transport
|
|
64
|
+
attr_reader :transport
|
|
65
|
+
|
|
66
|
+
# Create a new API client.
|
|
67
|
+
#
|
|
68
|
+
# @param config [{{pascalCase meta.clientName}}::Config, Hash, nil]
|
|
69
|
+
# Either a Config instance or keyword arguments matching Config#initialize.
|
|
70
|
+
def initialize(config = nil)
|
|
71
|
+
@config = if config.is_a?(Config)
|
|
72
|
+
config
|
|
73
|
+
elsif config.is_a?(Hash)
|
|
74
|
+
Config.new(**config)
|
|
75
|
+
else
|
|
76
|
+
Config.new
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
@transport = Transport.new(@config)
|
|
80
|
+
|
|
81
|
+
{{#each groups}}
|
|
82
|
+
@{{name}} = {{pascalCase ../meta.clientName}}::{{pascalCase name}}.new(@transport)
|
|
83
|
+
{{/each}}
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|