@sdk-it/cli 0.22.0 → 0.23.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/index.js CHANGED
@@ -1,13 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // packages/cli/src/lib/cli.ts
4
- import { Command as Command3, program } from "commander";
4
+ import { Command as Command5, program } from "commander";
5
5
 
6
- // packages/cli/src/lib/langs/dart.ts
6
+ // packages/cli/src/lib/apiref.ts
7
7
  import { Command } from "commander";
8
- import { execFile, execSync } from "node:child_process";
9
- import { generate } from "@sdk-it/dart";
10
- import { augmentSpec, loadSpec } from "@sdk-it/spec";
8
+ import { execa } from "execa";
11
9
 
12
10
  // packages/cli/src/lib/options.ts
13
11
  import { Option } from "commander";
@@ -23,8 +21,24 @@ function shellEnv(name) {
23
21
  return process.platform === "win32" ? `%${name}%` : `$${name}`;
24
22
  }
25
23
 
24
+ // packages/cli/src/lib/apiref.ts
25
+ var apiref_default = new Command("apiref").description("Generate APIREF").addOption(specOption.makeOptionMandatory(true)).addOption(outputOption.makeOptionMandatory(true)).action(async (options) => {
26
+ await execa("nx", ["run", "apiref:build"], {
27
+ stdio: "inherit",
28
+ extendEnv: true,
29
+ env: {
30
+ VITE_SPEC: options.spec,
31
+ VITE_SDK_IT_OUTPUT: options.output
32
+ }
33
+ });
34
+ });
35
+
26
36
  // packages/cli/src/lib/langs/dart.ts
27
- var dart_default = new Command("dart").description("Generate Dart SDK").addOption(specOption.makeOptionMandatory(true)).addOption(outputOption.makeOptionMandatory(true)).option("-l, --language <language>", "Programming language for the SDK").option("-n, --name <name>", "Name of the generated client", "Client").option("-v, --verbose", "Verbose output", false).action(async (options) => {
37
+ import { Command as Command2 } from "commander";
38
+ import { execFile, execSync } from "node:child_process";
39
+ import { generate } from "@sdk-it/dart";
40
+ import { augmentSpec, loadSpec } from "@sdk-it/spec";
41
+ var dart_default = new Command2("dart").description("Generate Dart SDK").addOption(specOption.makeOptionMandatory(true)).addOption(outputOption.makeOptionMandatory(true)).option("-l, --language <language>", "Programming language for the SDK").option("-n, --name <name>", "Name of the generated client", "Client").option("-v, --verbose", "Verbose output", false).action(async (options) => {
28
42
  const spec = augmentSpec({ spec: await loadSpec(options.spec) }, true);
29
43
  await generate(spec, {
30
44
  output: options.output,
@@ -46,18 +60,2021 @@ var dart_default = new Command("dart").description("Generate Dart SDK").addOptio
46
60
  });
47
61
  });
48
62
 
63
+ // packages/cli/src/lib/langs/python.ts
64
+ import { Command as Command3 } from "commander";
65
+ import { execFile as execFile2, execSync as execSync2 } from "node:child_process";
66
+
67
+ // packages/python/dist/index.js
68
+ import { readdir } from "node:fs/promises";
69
+ import { join } from "node:path";
70
+ import { snakecase as snakecase2 } from "stringcase";
71
+ import { isEmpty, isRef as isRef2, pascalcase as pascalcase2 } from "@sdk-it/core";
72
+ import {
73
+ createWriterProxy,
74
+ writeFiles
75
+ } from "@sdk-it/core/file-system.js";
76
+ import {
77
+ augmentSpec as augmentSpec2,
78
+ cleanFiles,
79
+ forEachOperation,
80
+ isSuccessStatusCode,
81
+ parseJsonContentType,
82
+ readWriteMetadata
83
+ } from "@sdk-it/spec";
84
+ import { snakecase } from "stringcase";
85
+ import { isRef, notRef, parseRef, pascalcase } from "@sdk-it/core";
86
+ import { isPrimitiveSchema } from "@sdk-it/spec";
87
+ var dispatcher_default = `"""HTTP dispatcher for making API requests."""
88
+
89
+ import asyncio
90
+ import logging
91
+ from typing import Any, Dict, List, Optional, Union
92
+ from urllib.parse import urljoin, urlparse
93
+
94
+ import httpx
95
+ from pydantic import BaseModel
96
+
97
+ from .interceptors import Interceptor
98
+ from .responses import ApiResponse, ErrorResponse
99
+
100
+
101
+ class RequestConfig(BaseModel):
102
+ """Configuration for an HTTP request."""
103
+
104
+ method: str
105
+ url: str
106
+ headers: Optional[Dict[str, str]] = None
107
+ params: Optional[Dict[str, Any]] = None
108
+ json_data: Optional[Dict[str, Any]] = None
109
+ form_data: Optional[Dict[str, Any]] = None
110
+ files: Optional[Dict[str, Any]] = None
111
+ timeout: Optional[Union[float, httpx.Timeout]] = None
112
+
113
+ class Config:
114
+ """Pydantic configuration."""
115
+ arbitrary_types_allowed = True
116
+
117
+
118
+ class Dispatcher:
119
+ """HTTP client dispatcher with interceptor support."""
120
+
121
+ def __init__(
122
+ self,
123
+ interceptors: Optional[List[Interceptor]] = None,
124
+ client: Optional[httpx.AsyncClient] = None,
125
+ timeout: Optional[Union[float, httpx.Timeout]] = None
126
+ ):
127
+ """Initialize the dispatcher.
128
+
129
+ Args:
130
+ interceptors: List of interceptors to apply to requests/responses
131
+ client: Custom httpx.AsyncClient instance (creates default if None)
132
+ timeout: Default timeout for requests
133
+ """
134
+ self.interceptors = interceptors or []
135
+ self.client = client or httpx.AsyncClient(timeout=timeout)
136
+ self.logger = logging.getLogger(__name__)
137
+
138
+ async def __aenter__(self):
139
+ """Async context manager entry."""
140
+ return self
141
+
142
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
143
+ """Async context manager exit."""
144
+ await self.client.aclose()
145
+
146
+ async def request(self, config: RequestConfig) -> httpx.Response:
147
+ """Execute an HTTP request with interceptor processing.
148
+
149
+ Args:
150
+ config: Request configuration
151
+
152
+ Returns:
153
+ HTTP response after processing through interceptors
154
+
155
+ Raises:
156
+ httpx.HTTPError: For HTTP-related errors
157
+ ValueError: For invalid request configuration
158
+ """
159
+ # Process request interceptors
160
+ processed_config = config
161
+ for interceptor in self.interceptors:
162
+ processed_config = await interceptor.process_request(processed_config)
163
+
164
+ # Prepare request arguments
165
+ request_kwargs = self._prepare_request_kwargs(processed_config)
166
+
167
+ try:
168
+ # Execute request
169
+ response = await self.client.request(**request_kwargs)
170
+
171
+ # Process response interceptors (in reverse order)
172
+ for interceptor in reversed(self.interceptors):
173
+ response = await interceptor.process_response(response)
174
+
175
+ return response
176
+
177
+ except httpx.RequestError as e:
178
+ self.logger.error(f"Request failed: {e}")
179
+ raise
180
+ except Exception as e:
181
+ self.logger.error(f"Unexpected error during request: {e}")
182
+ raise
183
+
184
+ def _prepare_request_kwargs(self, config: RequestConfig) -> Dict[str, Any]:
185
+ """Prepare keyword arguments for httpx request.
186
+
187
+ Args:
188
+ config: Request configuration
189
+
190
+ Returns:
191
+ Dictionary of kwargs for httpx.request
192
+
193
+ Raises:
194
+ ValueError: If request configuration is invalid
195
+ """
196
+ if not config.method:
197
+ raise ValueError("Request method cannot be empty")
198
+
199
+ if not config.url:
200
+ raise ValueError("Request URL cannot be empty")
201
+
202
+ request_kwargs = {
203
+ 'method': config.method.upper(),
204
+ 'url': config.url,
205
+ 'headers': config.headers or {},
206
+ 'params': config.params,
207
+ 'timeout': config.timeout,
208
+ }
209
+
210
+ # Handle different content types
211
+ content_type_set = False
212
+
213
+ if config.json_data is not None:
214
+ request_kwargs['json'] = config.json_data
215
+ if 'Content-Type' not in request_kwargs['headers']:
216
+ request_kwargs['headers']['Content-Type'] = 'application/json'
217
+ content_type_set = True
218
+
219
+ elif config.form_data is not None:
220
+ request_kwargs['data'] = config.form_data
221
+ if 'Content-Type' not in request_kwargs['headers']:
222
+ request_kwargs['headers']['Content-Type'] = 'application/x-www-form-urlencoded'
223
+ content_type_set = True
224
+
225
+ elif config.files is not None:
226
+ request_kwargs['files'] = config.files
227
+ # Don't set Content-Type for multipart/form-data - httpx will handle it automatically
228
+ content_type_set = True
229
+
230
+ # Validate that only one content type is set
231
+ content_fields = [config.json_data, config.form_data, config.files]
232
+ non_none_count = sum(1 for field in content_fields if field is not None)
233
+
234
+ if non_none_count > 1:
235
+ raise ValueError(
236
+ "Only one of json_data, form_data, or files can be set in a single request"
237
+ )
238
+
239
+ return request_kwargs
240
+
241
+ async def json(self, config: RequestConfig) -> httpx.Response:
242
+ """Make a JSON request.
243
+
244
+ Args:
245
+ config: Request configuration
246
+
247
+ Returns:
248
+ HTTP response
249
+ """
250
+ return await self.request(config)
251
+
252
+ async def form(self, config: RequestConfig) -> httpx.Response:
253
+ """Make a form-encoded request.
254
+
255
+ Args:
256
+ config: Request configuration
257
+
258
+ Returns:
259
+ HTTP response
260
+ """
261
+ return await self.request(config)
262
+
263
+ async def multipart(self, config: RequestConfig) -> httpx.Response:
264
+ """Make a multipart/form-data request.
265
+
266
+ Args:
267
+ config: Request configuration
268
+
269
+ Returns:
270
+ HTTP response
271
+ """
272
+ return await self.request(config)
273
+
274
+ async def close(self):
275
+ """Close the HTTP client and clean up resources."""
276
+ await self.client.aclose()
277
+
278
+
279
+ class Receiver:
280
+ """Response processor with interceptor support."""
281
+
282
+ def __init__(
283
+ self,
284
+ interceptors: Optional[List[Interceptor]] = None,
285
+ logger: Optional[logging.Logger] = None
286
+ ):
287
+ """Initialize the receiver.
288
+
289
+ Args:
290
+ interceptors: List of interceptors to apply to responses
291
+ logger: Custom logger instance
292
+ """
293
+ self.interceptors = interceptors or []
294
+ self.logger = logger or logging.getLogger(__name__)
295
+
296
+ async def json(
297
+ self,
298
+ response: httpx.Response,
299
+ success_model: Optional[type] = None,
300
+ error_model: Optional[type] = None
301
+ ) -> Any:
302
+ """Process a JSON response.
303
+
304
+ Args:
305
+ response: HTTP response to process
306
+ success_model: Pydantic model for successful responses
307
+ error_model: Pydantic model for error responses
308
+
309
+ Returns:
310
+ Parsed response data, optionally as model instances
311
+
312
+ Raises:
313
+ ErrorResponse: For HTTP error status codes
314
+ ValueError: For response parsing errors
315
+ """
316
+ # Process response interceptors
317
+ processed_response = response
318
+ for interceptor in self.interceptors:
319
+ processed_response = await interceptor.process_response(processed_response)
320
+
321
+ # Handle different status codes
322
+ if 200 <= processed_response.status_code < 300:
323
+ return await self._handle_success_response(
324
+ processed_response, success_model
325
+ )
326
+ else:
327
+ await self._handle_error_response(
328
+ processed_response, error_model
329
+ )
330
+
331
+ async def _handle_success_response(
332
+ self,
333
+ response: httpx.Response,
334
+ success_model: Optional[type] = None
335
+ ) -> Any:
336
+ """Handle successful response.
337
+
338
+ Args:
339
+ response: HTTP response
340
+ success_model: Pydantic model for successful responses
341
+
342
+ Returns:
343
+ Parsed response data
344
+
345
+ Raises:
346
+ ValueError: For parsing errors
347
+ """
348
+ if not response.content:
349
+ return None
350
+
351
+ try:
352
+ data = response.json()
353
+
354
+ if success_model:
355
+ if isinstance(data, list):
356
+ return [success_model(**item) for item in data]
357
+ else:
358
+ return success_model(**data)
359
+
360
+ return data
361
+
362
+ except Exception as e:
363
+ self.logger.error(f"Failed to parse success response: {e}")
364
+ raise ValueError(f"Failed to parse response: {e}")
365
+
366
+ async def _handle_error_response(
367
+ self,
368
+ response: httpx.Response,
369
+ error_model: Optional[type] = None
370
+ ) -> None:
371
+ """Handle error response.
372
+
373
+ Args:
374
+ response: HTTP response
375
+ error_model: Pydantic model for error responses
376
+
377
+ Raises:
378
+ ErrorResponse: Always raises with error details
379
+ """
380
+ error_data = {}
381
+
382
+ if response.content:
383
+ try:
384
+ error_data = response.json()
385
+ except Exception:
386
+ # Fallback to text content if JSON parsing fails
387
+ error_data = {'message': response.text}
388
+
389
+ if error_model:
390
+ try:
391
+ error = error_model(**error_data)
392
+ raise ErrorResponse(error, response.status_code, dict(response.headers))
393
+ except Exception as e:
394
+ self.logger.warning(f"Failed to parse error with model {error_model}: {e}")
395
+
396
+ raise ErrorResponse(error_data, response.status_code, dict(response.headers))
397
+
398
+ async def stream(self, response: httpx.Response) -> httpx.Response:
399
+ """Return streaming response as-is.
400
+
401
+ Args:
402
+ response: HTTP response
403
+
404
+ Returns:
405
+ The unmodified streaming response
406
+ """
407
+ return response
408
+
409
+ async def text(self, response: httpx.Response) -> str:
410
+ """Get response as text.
411
+
412
+ Args:
413
+ response: HTTP response
414
+
415
+ Returns:
416
+ Response body as text
417
+
418
+ Raises:
419
+ ErrorResponse: For HTTP error status codes
420
+ """
421
+ # Process response interceptors
422
+ processed_response = response
423
+ for interceptor in self.interceptors:
424
+ processed_response = await interceptor.process_response(processed_response)
425
+
426
+ if 200 <= processed_response.status_code < 300:
427
+ return processed_response.text
428
+ else:
429
+ error_data = {'message': processed_response.text}
430
+ raise ErrorResponse(error_data, processed_response.status_code, dict(processed_response.headers))
431
+
432
+ async def bytes(self, response: httpx.Response) -> bytes:
433
+ """Get response as bytes.
434
+
435
+ Args:
436
+ response: HTTP response
437
+
438
+ Returns:
439
+ Response body as bytes
440
+
441
+ Raises:
442
+ ErrorResponse: For HTTP error status codes
443
+ """
444
+ # Process response interceptors
445
+ processed_response = response
446
+ for interceptor in self.interceptors:
447
+ processed_response = await interceptor.process_response(processed_response)
448
+
449
+ if 200 <= processed_response.status_code < 300:
450
+ return processed_response.content
451
+ else:
452
+ error_data = {'message': 'Binary response error'}
453
+ raise ErrorResponse(error_data, processed_response.status_code, dict(processed_response.headers))
454
+
455
+
456
+ # Convenience functions for common use cases
457
+ async def quick_request(
458
+ method: str,
459
+ url: str,
460
+ interceptors: Optional[List[Interceptor]] = None,
461
+ **kwargs
462
+ ) -> httpx.Response:
463
+ """Make a quick HTTP request with interceptors.
464
+
465
+ Args:
466
+ method: HTTP method
467
+ url: Request URL
468
+ interceptors: List of interceptors to apply
469
+ **kwargs: Additional request configuration
470
+
471
+ Returns:
472
+ HTTP response
473
+ """
474
+ config = RequestConfig(method=method, url=url, **kwargs)
475
+
476
+ async with Dispatcher(interceptors=interceptors) as dispatcher:
477
+ return await dispatcher.request(config)
478
+
479
+
480
+ async def quick_json_request(
481
+ method: str,
482
+ url: str,
483
+ json_data: Optional[Dict[str, Any]] = None,
484
+ interceptors: Optional[List[Interceptor]] = None,
485
+ success_model: Optional[type] = None,
486
+ error_model: Optional[type] = None,
487
+ **kwargs
488
+ ) -> Any:
489
+ """Make a quick JSON HTTP request with interceptors.
490
+
491
+ Args:
492
+ method: HTTP method
493
+ url: Request URL
494
+ json_data: JSON data to send
495
+ interceptors: List of interceptors to apply
496
+ success_model: Pydantic model for successful responses
497
+ error_model: Pydantic model for error responses
498
+ **kwargs: Additional request configuration
499
+
500
+ Returns:
501
+ Parsed JSON response
502
+ """
503
+ config = RequestConfig(method=method, url=url, json_data=json_data, **kwargs)
504
+
505
+ async with Dispatcher(interceptors=interceptors) as dispatcher:
506
+ response = await dispatcher.request(config)
507
+ receiver = Receiver(interceptors=interceptors)
508
+ return await receiver.json(response, success_model, error_model)
509
+ `;
510
+ var interceptors_default = `"""HTTP interceptors for request/response processing."""
511
+
512
+ import asyncio
513
+ import logging
514
+ import time
515
+ from abc import ABC, abstractmethod
516
+ from typing import Dict, Optional, List, Any, Union
517
+ from urllib.parse import urljoin
518
+
519
+ import httpx
520
+
521
+ from .dispatcher import RequestConfig
522
+
523
+
524
+ class Interceptor(ABC):
525
+ """Base class for HTTP interceptors."""
526
+
527
+ @abstractmethod
528
+ async def process_request(self, config: RequestConfig) -> RequestConfig:
529
+ """Process an outgoing request.
530
+
531
+ Args:
532
+ config: The request configuration to process
533
+
534
+ Returns:
535
+ The modified request configuration
536
+ """
537
+ pass
538
+
539
+ @abstractmethod
540
+ async def process_response(self, response: httpx.Response) -> httpx.Response:
541
+ """Process an incoming response.
542
+
543
+ Args:
544
+ response: The HTTP response to process
545
+
546
+ Returns:
547
+ The processed response
548
+ """
549
+ pass
550
+
551
+
552
+ class BaseUrlInterceptor(Interceptor):
553
+ """Interceptor that prepends base URL to relative URLs."""
554
+
555
+ def __init__(self, base_url: str):
556
+ """Initialize the base URL interceptor.
557
+
558
+ Args:
559
+ base_url: The base URL to prepend to relative URLs
560
+ """
561
+ self.base_url = base_url.rstrip('/')
562
+
563
+ async def process_request(self, config: RequestConfig) -> RequestConfig:
564
+ """Prepend base URL if the request URL is relative.
565
+
566
+ Args:
567
+ config: The request configuration
568
+
569
+ Returns:
570
+ The modified request configuration with absolute URL
571
+ """
572
+ if not config.url.startswith(('http://', 'https://')):
573
+ # Use urljoin for proper URL joining, ensuring single slash
574
+ config.url = urljoin(self.base_url + '/', config.url.lstrip('/'))
575
+ return config
576
+
577
+ async def process_response(self, response: httpx.Response) -> httpx.Response:
578
+ """Pass through response unchanged.
579
+
580
+ Args:
581
+ response: The HTTP response
582
+
583
+ Returns:
584
+ The unmodified response
585
+ """
586
+ return response
587
+
588
+
589
+ class LoggingInterceptor(Interceptor):
590
+ """Interceptor that logs requests and responses using Python's logging module."""
591
+
592
+ def __init__(
593
+ self,
594
+ enabled: bool = True,
595
+ logger: Optional[logging.Logger] = None,
596
+ log_level: int = logging.INFO,
597
+ include_headers: bool = True,
598
+ include_sensitive_headers: bool = False
599
+ ):
600
+ """Initialize the logging interceptor.
601
+
602
+ Args:
603
+ enabled: Whether logging is enabled
604
+ logger: Custom logger instance (creates default if None)
605
+ log_level: Logging level to use
606
+ include_headers: Whether to log request/response headers
607
+ include_sensitive_headers: Whether to log sensitive headers like Authorization
608
+ """
609
+ self.enabled = enabled
610
+ self.logger = logger or logging.getLogger(__name__)
611
+ self.log_level = log_level
612
+ self.include_headers = include_headers
613
+ self.include_sensitive_headers = include_sensitive_headers
614
+ self._sensitive_headers = {'authorization', 'x-api-key', 'cookie', 'set-cookie'}
615
+
616
+ async def process_request(self, config: RequestConfig) -> RequestConfig:
617
+ """Log outgoing request.
618
+
619
+ Args:
620
+ config: The request configuration
621
+
622
+ Returns:
623
+ The unmodified request configuration
624
+ """
625
+ if not self.enabled:
626
+ return config
627
+
628
+ self.logger.log(self.log_level, f"\u2192 {config.method.upper()} {config.url}")
629
+
630
+ if self.include_headers and config.headers:
631
+ for key, value in config.headers.items():
632
+ if (key.lower() in self._sensitive_headers and
633
+ not self.include_sensitive_headers):
634
+ self.logger.log(self.log_level, f" {key}: [REDACTED]")
635
+ else:
636
+ self.logger.log(self.log_level, f" {key}: {value}")
637
+
638
+ return config
639
+
640
+ async def process_response(self, response: httpx.Response) -> httpx.Response:
641
+ """Log incoming response.
642
+
643
+ Args:
644
+ response: The HTTP response
645
+
646
+ Returns:
647
+ The unmodified response
648
+ """
649
+ if not self.enabled:
650
+ return response
651
+
652
+ status_icon = "\u2713" if 200 <= response.status_code < 300 else "\u2717"
653
+ self.logger.log(
654
+ self.log_level,
655
+ f"\u2190 {status_icon} {response.status_code} {response.reason_phrase or ''}"
656
+ )
657
+
658
+ if self.include_headers and response.headers:
659
+ for key, value in response.headers.items():
660
+ if (key.lower() in self._sensitive_headers and
661
+ not self.include_sensitive_headers):
662
+ self.logger.log(self.log_level, f" {key}: [REDACTED]")
663
+ else:
664
+ self.logger.log(self.log_level, f" {key}: {value}")
665
+
666
+ return response
667
+
668
+
669
+ class AuthInterceptor(Interceptor):
670
+ """Interceptor that adds authentication headers."""
671
+
672
+ def __init__(
673
+ self,
674
+ token: Optional[str] = None,
675
+ api_key: Optional[str] = None,
676
+ api_key_header: str = 'X-API-Key',
677
+ auth_type: str = 'Bearer'
678
+ ):
679
+ """Initialize the authentication interceptor.
680
+
681
+ Args:
682
+ token: Bearer token for Authorization header
683
+ api_key: API key value
684
+ api_key_header: Header name for API key
685
+ auth_type: Type of authentication (Bearer, Basic, etc.)
686
+ """
687
+ self.token = token
688
+ self.api_key = api_key
689
+ self.api_key_header = api_key_header
690
+ self.auth_type = auth_type
691
+
692
+ async def process_request(self, config: RequestConfig) -> RequestConfig:
693
+ """Add authentication headers.
694
+
695
+ Args:
696
+ config: The request configuration
697
+
698
+ Returns:
699
+ The modified request configuration with auth headers
700
+ """
701
+ if config.headers is None:
702
+ config.headers = {}
703
+
704
+ if self.token:
705
+ config.headers['Authorization'] = f'{self.auth_type} {self.token}'
706
+ elif self.api_key:
707
+ config.headers[self.api_key_header] = self.api_key
708
+
709
+ return config
710
+
711
+ async def process_response(self, response: httpx.Response) -> httpx.Response:
712
+ """Pass through response unchanged.
713
+
714
+ Args:
715
+ response: The HTTP response
716
+
717
+ Returns:
718
+ The unmodified response
719
+ """
720
+ return response
721
+
722
+
723
+ class RetryInterceptor(Interceptor):
724
+ """Interceptor that retries failed requests with exponential backoff."""
725
+
726
+ def __init__(
727
+ self,
728
+ max_retries: int = 3,
729
+ retry_delay: float = 1.0,
730
+ backoff_factor: float = 2.0,
731
+ retry_on_status: Optional[List[int]] = None,
732
+ retry_on_exceptions: Optional[List[type]] = None
733
+ ):
734
+ """Initialize the retry interceptor.
735
+
736
+ Args:
737
+ max_retries: Maximum number of retry attempts
738
+ retry_delay: Initial delay between retries in seconds
739
+ backoff_factor: Exponential backoff multiplier
740
+ retry_on_status: HTTP status codes that should trigger retries
741
+ retry_on_exceptions: Exception types that should trigger retries
742
+ """
743
+ self.max_retries = max_retries
744
+ self.retry_delay = retry_delay
745
+ self.backoff_factor = backoff_factor
746
+ self.retry_on_status = retry_on_status or [500, 502, 503, 504, 408, 429]
747
+ self.retry_on_exceptions = retry_on_exceptions or [
748
+ httpx.TimeoutException,
749
+ httpx.ConnectError,
750
+ httpx.RemoteProtocolError
751
+ ]
752
+ self._original_request_func = None
753
+ self.logger = logging.getLogger(__name__)
754
+
755
+ async def process_request(self, config: RequestConfig) -> RequestConfig:
756
+ """Store original request for potential retries.
757
+
758
+ Args:
759
+ config: The request configuration
760
+
761
+ Returns:
762
+ The unmodified request configuration
763
+ """
764
+ # Store the original config for retries
765
+ self._original_config = config.model_copy() if hasattr(config, 'model_copy') else config
766
+ return config
767
+
768
+ async def process_response(self, response: httpx.Response) -> httpx.Response:
769
+ """Check if response needs retry and handle accordingly.
770
+
771
+ Args:
772
+ response: The HTTP response
773
+
774
+ Returns:
775
+ The response (possibly after retries)
776
+ """
777
+ # For retry logic to work properly, it needs to be integrated at the dispatcher level
778
+ # This is a simplified version that just passes through
779
+ # In a full implementation, the retry logic would need access to the original request method
780
+ return response
781
+
782
+ async def execute_with_retry(self, request_func, *args, **kwargs) -> httpx.Response:
783
+ """Execute a request function with retry logic.
784
+
785
+ Args:
786
+ request_func: Function that executes the HTTP request
787
+ *args: Arguments to pass to request_func
788
+ **kwargs: Keyword arguments to pass to request_func
789
+
790
+ Returns:
791
+ The HTTP response after potential retries
792
+
793
+ Raises:
794
+ The last exception encountered if all retries fail
795
+ """
796
+ last_exception = None
797
+
798
+ for attempt in range(self.max_retries + 1):
799
+ try:
800
+ response = await request_func(*args, **kwargs)
801
+
802
+ # Check if response status requires retry
803
+ if response.status_code not in self.retry_on_status:
804
+ return response
805
+
806
+ if attempt == self.max_retries:
807
+ self.logger.warning(
808
+ f"Max retries ({self.max_retries}) reached for request. "
809
+ f"Final status: {response.status_code}"
810
+ )
811
+ return response
812
+
813
+ # Wait before retry
814
+ delay = self.retry_delay * (self.backoff_factor ** attempt)
815
+ self.logger.info(
816
+ f"Retrying request (attempt {attempt + 1}/{self.max_retries + 1}) "
817
+ f"after {delay:.2f}s due to status {response.status_code}"
818
+ )
819
+ await asyncio.sleep(delay)
820
+
821
+ except Exception as e:
822
+ # Check if exception type requires retry
823
+ if not any(isinstance(e, exc_type) for exc_type in self.retry_on_exceptions):
824
+ raise e
825
+
826
+ last_exception = e
827
+
828
+ if attempt == self.max_retries:
829
+ self.logger.error(
830
+ f"Max retries ({self.max_retries}) reached. "
831
+ f"Final exception: {type(e).__name__}: {e}"
832
+ )
833
+ raise e
834
+
835
+ # Wait before retry
836
+ delay = self.retry_delay * (self.backoff_factor ** attempt)
837
+ self.logger.info(
838
+ f"Retrying request (attempt {attempt + 1}/{self.max_retries + 1}) "
839
+ f"after {delay:.2f}s due to {type(e).__name__}: {e}"
840
+ )
841
+ await asyncio.sleep(delay)
842
+
843
+
844
+ class UserAgentInterceptor(Interceptor):
845
+ """Interceptor that adds a User-Agent header."""
846
+
847
+ def __init__(self, user_agent: str):
848
+ """Initialize the User-Agent interceptor.
849
+
850
+ Args:
851
+ user_agent: The User-Agent string to set
852
+ """
853
+ self.user_agent = user_agent
854
+
855
+ async def process_request(self, config: RequestConfig) -> RequestConfig:
856
+ """Add User-Agent header if not already present.
857
+
858
+ Args:
859
+ config: The request configuration
860
+
861
+ Returns:
862
+ The modified request configuration with User-Agent header
863
+ """
864
+ if config.headers is None:
865
+ config.headers = {}
866
+
867
+ # Only set User-Agent if not already present (case-insensitive check)
868
+ has_user_agent = any(
869
+ key.lower() == 'user-agent'
870
+ for key in config.headers.keys()
871
+ )
872
+
873
+ if not has_user_agent:
874
+ config.headers['User-Agent'] = self.user_agent
875
+
876
+ return config
877
+
878
+ async def process_response(self, response: httpx.Response) -> httpx.Response:
879
+ """Pass through response unchanged.
880
+
881
+ Args:
882
+ response: The HTTP response
883
+
884
+ Returns:
885
+ The unmodified response
886
+ """
887
+ return response
888
+
889
+
890
+ class TimeoutInterceptor(Interceptor):
891
+ """Interceptor that sets request timeouts."""
892
+
893
+ def __init__(self, timeout: Union[float, httpx.Timeout]):
894
+ """Initialize the timeout interceptor.
895
+
896
+ Args:
897
+ timeout: Timeout value in seconds or httpx.Timeout object
898
+ """
899
+ self.timeout = timeout
900
+
901
+ async def process_request(self, config: RequestConfig) -> RequestConfig:
902
+ """Set timeout for the request.
903
+
904
+ Args:
905
+ config: The request configuration
906
+
907
+ Returns:
908
+ The modified request configuration with timeout
909
+ """
910
+ if config.timeout is None:
911
+ config.timeout = self.timeout
912
+ return config
913
+
914
+ async def process_response(self, response: httpx.Response) -> httpx.Response:
915
+ """Pass through response unchanged.
916
+
917
+ Args:
918
+ response: The HTTP response
919
+
920
+ Returns:
921
+ The unmodified response
922
+ """
923
+ return response
924
+
925
+
926
+ class RateLimitInterceptor(Interceptor):
927
+ """Interceptor that implements client-side rate limiting."""
928
+
929
+ def __init__(self, max_requests: int, time_window: float = 60.0):
930
+ """Initialize the rate limit interceptor.
931
+
932
+ Args:
933
+ max_requests: Maximum number of requests allowed in the time window
934
+ time_window: Time window in seconds
935
+ """
936
+ self.max_requests = max_requests
937
+ self.time_window = time_window
938
+ self.requests = []
939
+ self._lock = asyncio.Lock()
940
+
941
+ async def process_request(self, config: RequestConfig) -> RequestConfig:
942
+ """Apply rate limiting before request.
943
+
944
+ Args:
945
+ config: The request configuration
946
+
947
+ Returns:
948
+ The unmodified request configuration
949
+ """
950
+ async with self._lock:
951
+ now = time.time()
952
+
953
+ # Remove requests outside the time window
954
+ self.requests = [req_time for req_time in self.requests
955
+ if now - req_time < self.time_window]
956
+
957
+ # Check if we've exceeded the rate limit
958
+ if len(self.requests) >= self.max_requests:
959
+ # Calculate how long to wait
960
+ oldest_request = min(self.requests)
961
+ wait_time = self.time_window - (now - oldest_request)
962
+
963
+ if wait_time > 0:
964
+ await asyncio.sleep(wait_time)
965
+
966
+ # Record this request
967
+ self.requests.append(now)
968
+
969
+ return config
970
+
971
+ async def process_response(self, response: httpx.Response) -> httpx.Response:
972
+ """Pass through response unchanged.
973
+
974
+ Args:
975
+ response: The HTTP response
976
+
977
+ Returns:
978
+ The unmodified response
979
+ """
980
+ return response
981
+
982
+
983
+ # Factory functions for convenient interceptor creation
984
+ def create_base_url_interceptor(base_url: str) -> BaseUrlInterceptor:
985
+ """Create a BaseUrlInterceptor instance.
986
+
987
+ Args:
988
+ base_url: The base URL to prepend to relative URLs
989
+
990
+ Returns:
991
+ Configured BaseUrlInterceptor instance
992
+ """
993
+ return BaseUrlInterceptor(base_url)
994
+
995
+
996
+ def create_logging_interceptor(
997
+ enabled: bool = True,
998
+ log_level: int = logging.INFO,
999
+ include_headers: bool = True,
1000
+ include_sensitive_headers: bool = False
1001
+ ) -> LoggingInterceptor:
1002
+ """Create a LoggingInterceptor instance.
1003
+
1004
+ Args:
1005
+ enabled: Whether logging is enabled
1006
+ log_level: Logging level to use
1007
+ include_headers: Whether to log headers
1008
+ include_sensitive_headers: Whether to log sensitive headers
1009
+
1010
+ Returns:
1011
+ Configured LoggingInterceptor instance
1012
+ """
1013
+ return LoggingInterceptor(
1014
+ enabled=enabled,
1015
+ log_level=log_level,
1016
+ include_headers=include_headers,
1017
+ include_sensitive_headers=include_sensitive_headers
1018
+ )
1019
+
1020
+
1021
+ def create_auth_interceptor(
1022
+ token: Optional[str] = None,
1023
+ api_key: Optional[str] = None,
1024
+ api_key_header: str = 'X-API-Key',
1025
+ auth_type: str = 'Bearer'
1026
+ ) -> AuthInterceptor:
1027
+ """Create an AuthInterceptor instance.
1028
+
1029
+ Args:
1030
+ token: Bearer token for Authorization header
1031
+ api_key: API key value
1032
+ api_key_header: Header name for API key
1033
+ auth_type: Type of authentication
1034
+
1035
+ Returns:
1036
+ Configured AuthInterceptor instance
1037
+ """
1038
+ return AuthInterceptor(
1039
+ token=token,
1040
+ api_key=api_key,
1041
+ api_key_header=api_key_header,
1042
+ auth_type=auth_type
1043
+ )
1044
+
1045
+
1046
+ def create_retry_interceptor(
1047
+ max_retries: int = 3,
1048
+ retry_delay: float = 1.0,
1049
+ backoff_factor: float = 2.0,
1050
+ retry_on_status: Optional[List[int]] = None
1051
+ ) -> RetryInterceptor:
1052
+ """Create a RetryInterceptor instance.
1053
+
1054
+ Args:
1055
+ max_retries: Maximum number of retry attempts
1056
+ retry_delay: Initial delay between retries in seconds
1057
+ backoff_factor: Exponential backoff multiplier
1058
+ retry_on_status: HTTP status codes that should trigger retries
1059
+
1060
+ Returns:
1061
+ Configured RetryInterceptor instance
1062
+ """
1063
+ return RetryInterceptor(
1064
+ max_retries=max_retries,
1065
+ retry_delay=retry_delay,
1066
+ backoff_factor=backoff_factor,
1067
+ retry_on_status=retry_on_status
1068
+ )
1069
+
1070
+
1071
+ def create_user_agent_interceptor(user_agent: str) -> UserAgentInterceptor:
1072
+ """Create a UserAgentInterceptor instance.
1073
+
1074
+ Args:
1075
+ user_agent: The User-Agent string to set
1076
+
1077
+ Returns:
1078
+ Configured UserAgentInterceptor instance
1079
+ """
1080
+ return UserAgentInterceptor(user_agent)
1081
+ `;
1082
+ var responses_default = `"""HTTP response models and exceptions."""
1083
+
1084
+ from typing import Any, Dict, Optional, Union
1085
+
1086
+ import httpx
1087
+ from pydantic import BaseModel
1088
+
1089
+
1090
+ class ApiResponse(BaseModel):
1091
+ """Base class for API responses."""
1092
+
1093
+ status_code: int
1094
+ headers: Dict[str, str]
1095
+ data: Any
1096
+
1097
+ class Config:
1098
+ """Pydantic configuration."""
1099
+ arbitrary_types_allowed = True
1100
+
1101
+
1102
+ class SuccessResponse(ApiResponse):
1103
+ """Represents a successful API response."""
1104
+
1105
+ def __init__(self, data: Any, status_code: int = 200, headers: Optional[Dict[str, str]] = None):
1106
+ """Initialize success response.
1107
+
1108
+ Args:
1109
+ data: Response data
1110
+ status_code: HTTP status code
1111
+ headers: Response headers
1112
+ """
1113
+ super().__init__(
1114
+ status_code=status_code,
1115
+ headers=headers or {},
1116
+ data=data
1117
+ )
1118
+
1119
+
1120
+ class ErrorResponse(Exception):
1121
+ """Exception raised for HTTP error responses."""
1122
+
1123
+ def __init__(
1124
+ self,
1125
+ data: Any,
1126
+ status_code: int,
1127
+ headers: Optional[Dict[str, str]] = None,
1128
+ message: Optional[str] = None
1129
+ ):
1130
+ """Initialize error response.
1131
+
1132
+ Args:
1133
+ data: Error response data
1134
+ status_code: HTTP status code
1135
+ headers: Response headers
1136
+ message: Custom error message
1137
+ """
1138
+ self.data = data
1139
+ self.status_code = status_code
1140
+ self.headers = headers or {}
1141
+ self.message = message or f"HTTP {status_code} Error"
1142
+
1143
+ super().__init__(self.message)
1144
+
1145
+ def __str__(self) -> str:
1146
+ """String representation of the error."""
1147
+ return f"ErrorResponse(status_code={self.status_code}, message='{self.message}')"
1148
+
1149
+ def __repr__(self) -> str:
1150
+ """Detailed string representation of the error."""
1151
+ return (
1152
+ f"ErrorResponse(status_code={self.status_code}, "
1153
+ f"message='{self.message}', data={self.data})"
1154
+ )
1155
+
1156
+
1157
+ class TimeoutError(ErrorResponse):
1158
+ """Exception raised for request timeouts."""
1159
+
1160
+ def __init__(self, message: str = "Request timed out"):
1161
+ """Initialize timeout error.
1162
+
1163
+ Args:
1164
+ message: Error message
1165
+ """
1166
+ super().__init__(
1167
+ data={'error': 'timeout'},
1168
+ status_code=408,
1169
+ message=message
1170
+ )
1171
+
1172
+
1173
+ class ConnectionError(ErrorResponse):
1174
+ """Exception raised for connection errors."""
1175
+
1176
+ def __init__(self, message: str = "Connection failed"):
1177
+ """Initialize connection error.
1178
+
1179
+ Args:
1180
+ message: Error message
1181
+ """
1182
+ super().__init__(
1183
+ data={'error': 'connection'},
1184
+ status_code=503,
1185
+ message=message
1186
+ )
1187
+
1188
+
1189
+ class BadRequestError(ErrorResponse):
1190
+ """Exception raised for 400 Bad Request errors."""
1191
+
1192
+ def __init__(self, data: Any = None, message: str = "Bad Request"):
1193
+ """Initialize bad request error.
1194
+
1195
+ Args:
1196
+ data: Error data
1197
+ message: Error message
1198
+ """
1199
+ super().__init__(
1200
+ data=data or {'error': 'bad_request'},
1201
+ status_code=400,
1202
+ message=message
1203
+ )
1204
+
1205
+
1206
+ class UnauthorizedError(ErrorResponse):
1207
+ """Exception raised for 401 Unauthorized errors."""
1208
+
1209
+ def __init__(self, data: Any = None, message: str = "Unauthorized"):
1210
+ """Initialize unauthorized error.
1211
+
1212
+ Args:
1213
+ data: Error data
1214
+ message: Error message
1215
+ """
1216
+ super().__init__(
1217
+ data=data or {'error': 'unauthorized'},
1218
+ status_code=401,
1219
+ message=message
1220
+ )
1221
+
1222
+
1223
+ class ForbiddenError(ErrorResponse):
1224
+ """Exception raised for 403 Forbidden errors."""
1225
+
1226
+ def __init__(self, data: Any = None, message: str = "Forbidden"):
1227
+ """Initialize forbidden error.
1228
+
1229
+ Args:
1230
+ data: Error data
1231
+ message: Error message
1232
+ """
1233
+ super().__init__(
1234
+ data=data or {'error': 'forbidden'},
1235
+ status_code=403,
1236
+ message=message
1237
+ )
1238
+
1239
+
1240
+ class NotFoundError(ErrorResponse):
1241
+ """Exception raised for 404 Not Found errors."""
1242
+
1243
+ def __init__(self, data: Any = None, message: str = "Not Found"):
1244
+ """Initialize not found error.
1245
+
1246
+ Args:
1247
+ data: Error data
1248
+ message: Error message
1249
+ """
1250
+ super().__init__(
1251
+ data=data or {'error': 'not_found'},
1252
+ status_code=404,
1253
+ message=message
1254
+ )
1255
+
1256
+
1257
+ class InternalServerError(ErrorResponse):
1258
+ """Exception raised for 500 Internal Server Error."""
1259
+
1260
+ def __init__(self, data: Any = None, message: str = "Internal Server Error"):
1261
+ """Initialize internal server error.
1262
+
1263
+ Args:
1264
+ data: Error data
1265
+ message: Error message
1266
+ """
1267
+ super().__init__(
1268
+ data=data or {'error': 'internal_server_error'},
1269
+ status_code=500,
1270
+ message=message
1271
+ )
1272
+
1273
+
1274
+ def create_error_from_response(response: httpx.Response) -> ErrorResponse:
1275
+ """Create appropriate error exception from HTTP response.
1276
+
1277
+ Args:
1278
+ response: HTTP response
1279
+
1280
+ Returns:
1281
+ Appropriate error exception
1282
+ """
1283
+ status_code = response.status_code
1284
+ headers = dict(response.headers)
1285
+
1286
+ # Try to parse error data
1287
+ try:
1288
+ data = response.json()
1289
+ except Exception:
1290
+ data = {'message': response.text}
1291
+
1292
+ # Create specific error types based on status code
1293
+ error_classes = {
1294
+ 400: BadRequestError,
1295
+ 401: UnauthorizedError,
1296
+ 403: ForbiddenError,
1297
+ 404: NotFoundError,
1298
+ 500: InternalServerError,
1299
+ }
1300
+
1301
+ error_class = error_classes.get(status_code, ErrorResponse)
1302
+
1303
+ if error_class == ErrorResponse:
1304
+ return ErrorResponse(data, status_code, headers)
1305
+ else:
1306
+ return error_class(data)
1307
+ `;
1308
+ function coerceObject(schema) {
1309
+ schema = structuredClone(schema);
1310
+ if (schema["x-properties"]) {
1311
+ schema.properties = {
1312
+ ...schema.properties ?? {},
1313
+ ...schema["x-properties"] ?? {}
1314
+ };
1315
+ }
1316
+ if (schema["x-required"]) {
1317
+ schema.required = Array.from(
1318
+ /* @__PURE__ */ new Set([
1319
+ ...Array.isArray(schema.required) ? schema.required : [],
1320
+ ...schema["x-required"] || []
1321
+ ])
1322
+ );
1323
+ }
1324
+ return schema;
1325
+ }
1326
+ var PythonEmitter = class {
1327
+ #spec;
1328
+ #emitHandler;
1329
+ #emitHistory = /* @__PURE__ */ new Set();
1330
+ #typeCache = /* @__PURE__ */ new Map();
1331
+ // Cache for resolved types
1332
+ #emit(name, content, schema) {
1333
+ if (this.#emitHistory.has(content)) {
1334
+ return;
1335
+ }
1336
+ this.#emitHistory.add(content);
1337
+ this.#emitHandler?.(name, content, schema);
1338
+ }
1339
+ constructor(spec) {
1340
+ this.#spec = spec;
1341
+ }
1342
+ onEmit(emit) {
1343
+ this.#emitHandler = emit;
1344
+ }
1345
+ #formatFieldName(name) {
1346
+ let fieldName = snakecase(name);
1347
+ const reservedKeywords = [
1348
+ "class",
1349
+ "def",
1350
+ "if",
1351
+ "else",
1352
+ "elif",
1353
+ "while",
1354
+ "for",
1355
+ "try",
1356
+ "except",
1357
+ "finally",
1358
+ "with",
1359
+ "as",
1360
+ "import",
1361
+ "from",
1362
+ "global",
1363
+ "nonlocal",
1364
+ "lambda",
1365
+ "yield",
1366
+ "return",
1367
+ "pass",
1368
+ "break",
1369
+ "continue",
1370
+ "True",
1371
+ "False",
1372
+ "None",
1373
+ "and",
1374
+ "or",
1375
+ "not",
1376
+ "in",
1377
+ "is"
1378
+ ];
1379
+ if (reservedKeywords.includes(fieldName)) {
1380
+ fieldName = `${fieldName}_`;
1381
+ }
1382
+ return fieldName;
1383
+ }
1384
+ #ref(ref) {
1385
+ const cacheKey = ref.$ref;
1386
+ if (this.#typeCache.has(cacheKey)) {
1387
+ return this.#typeCache.get(cacheKey);
1388
+ }
1389
+ const refInfo = parseRef(ref.$ref);
1390
+ const refName = refInfo.model;
1391
+ const className = pascalcase(refName);
1392
+ const result = {
1393
+ type: className,
1394
+ content: "",
1395
+ use: className,
1396
+ fromJson: `${className}.parse_obj`,
1397
+ simple: false
1398
+ };
1399
+ this.#typeCache.set(cacheKey, result);
1400
+ return result;
1401
+ }
1402
+ #oneOf(variants, context) {
1403
+ const variantTypes = variants.map((variant) => this.handle(variant, context)).map((result) => result.type || "Any").filter((type, index, arr) => arr.indexOf(type) === index);
1404
+ if (variantTypes.length === 0) {
1405
+ return {
1406
+ type: "Any",
1407
+ content: "",
1408
+ use: "Any",
1409
+ fromJson: "Any",
1410
+ simple: true
1411
+ };
1412
+ }
1413
+ if (variantTypes.length === 1) {
1414
+ return {
1415
+ type: variantTypes[0],
1416
+ content: "",
1417
+ use: variantTypes[0],
1418
+ fromJson: variantTypes[0],
1419
+ simple: true
1420
+ };
1421
+ }
1422
+ const unionType = `Union[${variantTypes.join(", ")}]`;
1423
+ return {
1424
+ type: unionType,
1425
+ content: "",
1426
+ use: unionType,
1427
+ fromJson: unionType,
1428
+ simple: true
1429
+ };
1430
+ }
1431
+ #object(className, schema, context) {
1432
+ const { properties = {}, required = [] } = coerceObject(schema);
1433
+ const fields = [];
1434
+ let baseClass = "BaseModel";
1435
+ if (schema.allOf) {
1436
+ const bases = schema.allOf.filter(notRef).map((s) => this.handle(s, context)).filter((result) => result.type).map((result) => result.type);
1437
+ if (bases.length > 0 && bases[0]) {
1438
+ baseClass = bases[0];
1439
+ }
1440
+ }
1441
+ for (const [propName, propSchema] of Object.entries(properties)) {
1442
+ if (isRef(propSchema)) {
1443
+ const refResult = this.#ref(propSchema);
1444
+ const refInfo = parseRef(propSchema.$ref);
1445
+ const refName = refInfo.model;
1446
+ const pythonType = pascalcase(refName);
1447
+ const fieldName = this.#formatFieldName(propName);
1448
+ const isRequired = required.includes(propName);
1449
+ const fieldType = isRequired ? pythonType : `Optional[${pythonType}]`;
1450
+ const defaultValue = isRequired ? "" : " = None";
1451
+ fields.push(` ${fieldName}: ${fieldType}${defaultValue}`);
1452
+ } else {
1453
+ const result = this.handle(propSchema, { ...context, name: propName });
1454
+ const fieldName = this.#formatFieldName(propName);
1455
+ const isRequired = required.includes(propName);
1456
+ let fieldType = result.type || "Any";
1457
+ if (!isRequired) {
1458
+ fieldType = `Optional[${fieldType}]`;
1459
+ }
1460
+ const defaultValue = isRequired ? "" : " = None";
1461
+ let fieldDef = ` ${fieldName}: ${fieldType}${defaultValue}`;
1462
+ if (fieldName !== propName) {
1463
+ fieldDef = ` ${fieldName}: ${fieldType} = Field(alias='${propName}'${defaultValue ? ", default=None" : ""})`;
1464
+ }
1465
+ if (propSchema.description) {
1466
+ fieldDef += ` # ${propSchema.description}`;
1467
+ }
1468
+ fields.push(fieldDef);
1469
+ }
1470
+ }
1471
+ if (schema.oneOf || schema.anyOf) {
1472
+ const unionResult = this.#oneOf(
1473
+ schema.oneOf || schema.anyOf || [],
1474
+ context
1475
+ );
1476
+ fields.push(` value: ${unionResult.type}`);
1477
+ }
1478
+ if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
1479
+ const addlResult = this.handle(schema.additionalProperties, context);
1480
+ fields.push(
1481
+ ` additional_properties: Optional[Dict[str, ${addlResult.type || "Any"}]] = None`
1482
+ );
1483
+ }
1484
+ const docstring = schema.description ? ` """${schema.description}"""
1485
+ ` : "";
1486
+ let requestConfigMethod = "";
1487
+ if (schema["x-inputname"]) {
1488
+ requestConfigMethod = `
1489
+ def to_request_config(self, config: RequestConfig) -> RequestConfig:
1490
+ """Convert this input model to request configuration."""
1491
+ # Handle path parameters
1492
+ path_params = {}
1493
+ for key, value in self.dict(exclude_none=True).items():
1494
+ if key in config.url:
1495
+ path_params[key] = str(value)
1496
+ config.url = config.url.replace(f'{{{key}}}', str(value))
1497
+
1498
+ # Handle query parameters
1499
+ query_params = {k: v for k, v in self.dict(exclude_none=True).items()
1500
+ if k not in path_params}
1501
+ if query_params:
1502
+ config.params = query_params
1503
+
1504
+ return config
1505
+ `;
1506
+ }
1507
+ const content = `class ${className}(${baseClass}):
1508
+ ${docstring}${fields.length > 0 ? fields.join("\n") : " pass"}${requestConfigMethod}
1509
+ `;
1510
+ this.#emit(className, content, schema);
1511
+ return {
1512
+ type: className,
1513
+ content,
1514
+ use: className,
1515
+ fromJson: `${className}.parse_obj`,
1516
+ simple: false
1517
+ };
1518
+ }
1519
+ #primitive(schema, context) {
1520
+ const { type, format } = schema;
1521
+ const nullable = schema.nullable;
1522
+ let pythonType = "Any";
1523
+ switch (type) {
1524
+ case "string":
1525
+ if (format === "date-time") {
1526
+ pythonType = "datetime";
1527
+ } else if (format === "date") {
1528
+ pythonType = "date";
1529
+ } else if (format === "uuid") {
1530
+ pythonType = "UUID";
1531
+ } else if (format === "binary" || format === "byte") {
1532
+ pythonType = "bytes";
1533
+ } else {
1534
+ pythonType = "str";
1535
+ }
1536
+ break;
1537
+ case "integer":
1538
+ if (format === "int64") {
1539
+ pythonType = "int";
1540
+ } else {
1541
+ pythonType = "int";
1542
+ }
1543
+ break;
1544
+ case "number":
1545
+ pythonType = "float";
1546
+ break;
1547
+ case "boolean":
1548
+ pythonType = "bool";
1549
+ break;
1550
+ default:
1551
+ pythonType = "Any";
1552
+ }
1553
+ if (nullable) {
1554
+ pythonType = `Optional[${pythonType}]`;
1555
+ }
1556
+ return {
1557
+ type: pythonType,
1558
+ content: "",
1559
+ use: pythonType,
1560
+ fromJson: pythonType,
1561
+ simple: true,
1562
+ nullable
1563
+ };
1564
+ }
1565
+ #array(schema, context) {
1566
+ const itemsSchema = schema.items;
1567
+ if (!itemsSchema) {
1568
+ return {
1569
+ type: "List[Any]",
1570
+ content: "",
1571
+ use: "List[Any]",
1572
+ fromJson: "list",
1573
+ simple: true
1574
+ };
1575
+ }
1576
+ const itemsResult = this.handle(itemsSchema, context);
1577
+ const listType = `List[${itemsResult.type || "Any"}]`;
1578
+ return {
1579
+ type: listType,
1580
+ content: itemsResult.content,
1581
+ use: listType,
1582
+ fromJson: `List[${itemsResult.fromJson || itemsResult.type}]`,
1583
+ simple: true
1584
+ };
1585
+ }
1586
+ #enum(schema, context) {
1587
+ const { enum: enumValues } = schema;
1588
+ if (!enumValues || enumValues.length === 0) {
1589
+ return this.#primitive(schema, context);
1590
+ }
1591
+ if (!context.name) {
1592
+ throw new Error("Enum schemas must have a name in context");
1593
+ }
1594
+ const className = pascalcase(context.name);
1595
+ const enumItems = enumValues.map((value, index) => {
1596
+ const name = typeof value === "string" ? value.toUpperCase().replace(/[^A-Z0-9]/g, "_") : `VALUE_${index}`;
1597
+ const pythonValue = typeof value === "string" ? `'${value}'` : String(value);
1598
+ return ` ${name} = ${pythonValue}`;
1599
+ });
1600
+ const content = `class ${className}(Enum):
1601
+ """Enumeration for ${context.name}."""
1602
+ ${enumItems.join("\n")}
1603
+ `;
1604
+ this.#emit(className, content, schema);
1605
+ return {
1606
+ type: className,
1607
+ content,
1608
+ use: className,
1609
+ fromJson: className,
1610
+ simple: false
1611
+ };
1612
+ }
1613
+ #const(schema, context) {
1614
+ const { const: constValue } = schema;
1615
+ if (typeof constValue === "string") {
1616
+ return {
1617
+ type: `Literal['${constValue}']`,
1618
+ content: "",
1619
+ use: `Literal['${constValue}']`,
1620
+ fromJson: `'${constValue}'`,
1621
+ simple: true,
1622
+ literal: constValue
1623
+ };
1624
+ }
1625
+ return {
1626
+ type: `Literal[${JSON.stringify(constValue)}]`,
1627
+ content: "",
1628
+ use: `Literal[${JSON.stringify(constValue)}]`,
1629
+ fromJson: JSON.stringify(constValue),
1630
+ simple: true,
1631
+ literal: constValue
1632
+ };
1633
+ }
1634
+ handle(schema, context = {}) {
1635
+ if (isRef(schema)) {
1636
+ return this.#ref(schema);
1637
+ }
1638
+ if ("const" in schema && schema.const !== void 0) {
1639
+ return this.#const(schema, context);
1640
+ }
1641
+ if (schema.enum) {
1642
+ return this.#enum(schema, context);
1643
+ }
1644
+ if (schema.type === "array") {
1645
+ return this.#array(schema, context);
1646
+ }
1647
+ if (schema.oneOf || schema.anyOf) {
1648
+ return this.#oneOf(schema.oneOf || schema.anyOf || [], context);
1649
+ }
1650
+ if (schema.type === "object" || schema.properties || schema.allOf || schema.oneOf || schema.anyOf) {
1651
+ if (!context.name) {
1652
+ throw new Error("Object schemas must have a name in context");
1653
+ }
1654
+ const className = pascalcase(context.name);
1655
+ return this.#object(className, schema, context);
1656
+ }
1657
+ if (isPrimitiveSchema(schema)) {
1658
+ return this.#primitive(schema, context);
1659
+ }
1660
+ return {
1661
+ type: "Any",
1662
+ content: "",
1663
+ use: "Any",
1664
+ fromJson: "Any",
1665
+ simple: true
1666
+ };
1667
+ }
1668
+ };
1669
+ async function generate2(openapi, settings) {
1670
+ const spec = augmentSpec2({ spec: openapi }, true);
1671
+ const clientName = settings.name || "Client";
1672
+ const output = settings.output;
1673
+ const { writer, files: writtenFiles } = createWriterProxy(
1674
+ settings.writer ?? writeFiles,
1675
+ settings.output
1676
+ );
1677
+ settings.writer = writer;
1678
+ settings.readFolder ??= async (folder) => {
1679
+ const files = await readdir(folder, { withFileTypes: true });
1680
+ return files.map((file) => ({
1681
+ fileName: file.name,
1682
+ filePath: join(file.parentPath, file.name),
1683
+ isFolder: file.isDirectory()
1684
+ }));
1685
+ };
1686
+ const groups = {};
1687
+ forEachOperation(spec, (entry, operation) => {
1688
+ console.log(`Processing ${entry.method} ${entry.path}`);
1689
+ const group = groups[entry.groupName] ??= {
1690
+ className: `${pascalcase2(entry.groupName)}Api`,
1691
+ methods: []
1692
+ };
1693
+ const input = toInputs(spec, { entry, operation });
1694
+ const response = toOutput(spec, operation);
1695
+ const methodName = snakecase2(
1696
+ operation.operationId || `${entry.method}_${entry.path.replace(/[^a-zA-Z0-9]/g, "_")}`
1697
+ );
1698
+ const returnType = response ? response.returnType : "httpx.Response";
1699
+ const docstring = operation.summary || operation.description ? ` """${operation.summary || operation.description}"""` : "";
1700
+ group.methods.push(`
1701
+ async def ${methodName}(self${input.haveInput ? `, input_data: ${input.inputName}` : ""}) -> ${returnType}:
1702
+ ${docstring}
1703
+ config = RequestConfig(
1704
+ method='${entry.method.toUpperCase()}',
1705
+ url='${entry.path}',
1706
+ )
1707
+
1708
+ ${input.haveInput ? "config = input_data.to_request_config(config)" : ""}
1709
+
1710
+ response = await self.dispatcher.${input.contentType}(config)
1711
+ ${response ? `return await self.receiver.json(response, ${response.successModel || "None"}, ${response.errorModel || "None"})` : "return response"}
1712
+ `);
1713
+ });
1714
+ const emitter = new PythonEmitter(spec);
1715
+ const models = await serializeModels(spec, emitter);
1716
+ const apiClasses = Object.entries(groups).reduce(
1717
+ (acc, [name, { className, methods }]) => {
1718
+ const fileName = `api/${snakecase2(name)}_api.py`;
1719
+ const imports = [
1720
+ "from typing import Optional",
1721
+ "import httpx",
1722
+ "",
1723
+ "from ..http.dispatcher import Dispatcher, RequestConfig",
1724
+ "from ..http.responses import Receiver",
1725
+ "from ..inputs import *",
1726
+ "from ..outputs import *",
1727
+ "from ..models import *",
1728
+ ""
1729
+ ].join("\n");
1730
+ acc[fileName] = `${imports}
1731
+ class ${className}:
1732
+ """API client for ${name} operations."""
1733
+
1734
+ def __init__(self, dispatcher: Dispatcher, receiver: Receiver):
1735
+ self.dispatcher = dispatcher
1736
+ self.receiver = receiver
1737
+ ${methods.join("\n")}
1738
+ `;
1739
+ return acc;
1740
+ },
1741
+ {}
1742
+ );
1743
+ const apiImports = Object.keys(groups).map(
1744
+ (name) => `from .api.${snakecase2(name)}_api import ${pascalcase2(name)}Api`
1745
+ ).join("\\n");
1746
+ const apiProperties = Object.keys(groups).map(
1747
+ (name) => ` self.${snakecase2(name)} = ${pascalcase2(name)}Api(dispatcher, receiver)`
1748
+ ).join("\\n");
1749
+ const clientCode = `"""Main API client."""
1750
+
1751
+ from typing import Optional, List
1752
+ import httpx
1753
+
1754
+ ${apiImports}
1755
+ from .http.dispatcher import Dispatcher, RequestConfig
1756
+ from .http.responses import Receiver
1757
+ from .http.interceptors import (
1758
+ Interceptor,
1759
+ BaseUrlInterceptor,
1760
+ LoggingInterceptor,
1761
+ AuthInterceptor,
1762
+ UserAgentInterceptor,
1763
+ )
1764
+
1765
+
1766
+ class ${clientName}:
1767
+ """Main API client for the SDK."""
1768
+
1769
+ def __init__(
1770
+ self,
1771
+ base_url: str,
1772
+ token: Optional[str] = None,
1773
+ api_key: Optional[str] = None,
1774
+ api_key_header: str = 'X-API-Key',
1775
+ enable_logging: bool = False,
1776
+ user_agent: Optional[str] = None,
1777
+ custom_interceptors: Optional[List[Interceptor]] = None,
1778
+ ):
1779
+ """
1780
+ Initialize the API client.
1781
+
1782
+ Args:
1783
+ base_url: Base URL for the API
1784
+ token: Bearer token for authentication
1785
+ api_key: API key for authentication
1786
+ api_key_header: Header name for API key authentication
1787
+ enable_logging: Enable request/response logging
1788
+ user_agent: Custom User-Agent header
1789
+ custom_interceptors: Additional custom interceptors
1790
+ """
1791
+ self.base_url = base_url
1792
+
1793
+ # Build interceptor chain
1794
+ interceptors = []
1795
+
1796
+ # Base URL interceptor (always first)
1797
+ interceptors.append(BaseUrlInterceptor(base_url))
1798
+
1799
+ # Authentication interceptor
1800
+ if token or api_key:
1801
+ interceptors.append(AuthInterceptor(token=token, api_key=api_key, api_key_header=api_key_header))
1802
+
1803
+ # User agent interceptor
1804
+ if user_agent:
1805
+ interceptors.append(UserAgentInterceptor(user_agent))
1806
+
1807
+ # Logging interceptor
1808
+ if enable_logging:
1809
+ interceptors.append(LoggingInterceptor())
1810
+
1811
+ # Custom interceptors
1812
+ if custom_interceptors:
1813
+ interceptors.extend(custom_interceptors)
1814
+
1815
+ # Initialize dispatcher and receiver
1816
+ self.dispatcher = Dispatcher(interceptors)
1817
+ self.receiver = Receiver(interceptors)
1818
+
1819
+ # Initialize API clients
1820
+ ${apiProperties}
1821
+
1822
+ async def __aenter__(self):
1823
+ return self
1824
+
1825
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
1826
+ await self.close()
1827
+
1828
+ async def close(self):
1829
+ """Close the HTTP client."""
1830
+ await self.dispatcher.close()
1831
+ `;
1832
+ await settings.writer(output, {
1833
+ ...models,
1834
+ ...apiClasses,
1835
+ "client.py": clientCode,
1836
+ "http/dispatcher.py": dispatcher_default,
1837
+ "http/interceptors.py": interceptors_default,
1838
+ "http/responses.py": responses_default,
1839
+ "__init__.py": `"""SDK package."""
1840
+
1841
+ from .client import ${clientName}
1842
+
1843
+ __all__ = ['${clientName}']
1844
+ `
1845
+ });
1846
+ if (settings.mode === "full") {
1847
+ const requirements = `# HTTP client
1848
+ httpx>=0.24.0,<1.0.0
1849
+
1850
+ # Data validation and serialization
1851
+ pydantic>=2.0.0,<3.0.0
1852
+
1853
+ # Enhanced type hints
1854
+ typing-extensions>=4.0.0
1855
+
1856
+ # Optional: For better datetime handling
1857
+ python-dateutil>=2.8.0
1858
+ `;
1859
+ await settings.writer(output, {
1860
+ "requirements.txt": requirements
1861
+ });
1862
+ }
1863
+ const metadata = await readWriteMetadata(
1864
+ settings.output,
1865
+ Array.from(writtenFiles)
1866
+ );
1867
+ if (settings.cleanup !== false && writtenFiles.size > 0) {
1868
+ await cleanFiles(metadata.content, settings.output, [
1869
+ "/__init__.py",
1870
+ "requirements.txt",
1871
+ "/metadata.json"
1872
+ ]);
1873
+ }
1874
+ await settings.writer(output, {
1875
+ "models/__init__.py": await generateModuleInit(
1876
+ join(output, "models"),
1877
+ settings.readFolder
1878
+ ),
1879
+ "inputs/__init__.py": await generateModuleInit(
1880
+ join(output, "inputs"),
1881
+ settings.readFolder
1882
+ ),
1883
+ "outputs/__init__.py": await generateModuleInit(
1884
+ join(output, "outputs"),
1885
+ settings.readFolder
1886
+ ),
1887
+ "api/__init__.py": await generateModuleInit(
1888
+ join(output, "api"),
1889
+ settings.readFolder
1890
+ ),
1891
+ "http/__init__.py": `"""HTTP utilities."""
1892
+
1893
+ from .dispatcher import Dispatcher, RequestConfig
1894
+ from .interceptors import *
1895
+ from .responses import *
1896
+
1897
+ __all__ = [
1898
+ 'Dispatcher',
1899
+ 'RequestConfig',
1900
+ 'ApiResponse',
1901
+ 'ErrorResponse',
1902
+ 'Interceptor',
1903
+ 'BaseUrlInterceptor',
1904
+ 'LoggingInterceptor',
1905
+ 'AuthInterceptor',
1906
+ ]
1907
+ `
1908
+ });
1909
+ if (settings.formatCode) {
1910
+ await settings.formatCode({ output: settings.output });
1911
+ }
1912
+ }
1913
+ async function generateModuleInit(folder, readFolder) {
1914
+ try {
1915
+ const files = await readFolder(folder);
1916
+ const pyFiles = files.filter(
1917
+ (file) => file.fileName.endsWith(".py") && file.fileName !== "__init__.py"
1918
+ ).map((file) => file.fileName.replace(".py", ""));
1919
+ if (pyFiles.length === 0) {
1920
+ return '"""Package module."""\n';
1921
+ }
1922
+ const imports = pyFiles.map((name) => `from .${name} import *`).join("\n");
1923
+ return `"""Package module."""
1924
+
1925
+ ${imports}
1926
+ `;
1927
+ } catch {
1928
+ return '"""Package module."""\\n';
1929
+ }
1930
+ }
1931
+ function toInputs(spec, { entry, operation }) {
1932
+ const inputName = entry.inputName || "Input";
1933
+ const haveInput = !isEmpty(operation.parameters) || !isEmpty(operation.requestBody);
1934
+ let contentType = "json";
1935
+ if (operation.requestBody && !isRef2(operation.requestBody)) {
1936
+ const content = operation.requestBody.content;
1937
+ if (content) {
1938
+ const contentTypes = Object.keys(content);
1939
+ if (contentTypes.some((type) => type.includes("multipart"))) {
1940
+ contentType = "multipart";
1941
+ } else if (contentTypes.some((type) => type.includes("form"))) {
1942
+ contentType = "form";
1943
+ }
1944
+ }
1945
+ }
1946
+ return {
1947
+ inputName,
1948
+ haveInput,
1949
+ contentType
1950
+ };
1951
+ }
1952
+ function toOutput(spec, operation) {
1953
+ if (!operation.responses) {
1954
+ return null;
1955
+ }
1956
+ const successResponse = Object.entries(operation.responses).find(
1957
+ ([code]) => isSuccessStatusCode(Number(code))
1958
+ );
1959
+ if (!successResponse) {
1960
+ return null;
1961
+ }
1962
+ const [statusCode, response] = successResponse;
1963
+ if (isRef2(response)) {
1964
+ return null;
1965
+ }
1966
+ const content = response.content;
1967
+ if (!content) {
1968
+ return { returnType: "None", successModel: null, errorModel: null };
1969
+ }
1970
+ const jsonContent = Object.entries(content).find(
1971
+ ([type]) => parseJsonContentType(type)
1972
+ );
1973
+ if (!jsonContent) {
1974
+ return {
1975
+ returnType: "httpx.Response",
1976
+ successModel: null,
1977
+ errorModel: null
1978
+ };
1979
+ }
1980
+ const [, mediaType] = jsonContent;
1981
+ const schema = mediaType.schema;
1982
+ if (!schema || isRef2(schema)) {
1983
+ return { returnType: "Any", successModel: null, errorModel: null };
1984
+ }
1985
+ const emitter = new PythonEmitter(spec);
1986
+ const result = emitter.handle(schema, {});
1987
+ return {
1988
+ returnType: result.type || "Any",
1989
+ successModel: result.type,
1990
+ errorModel: null
1991
+ // TODO: Handle error models
1992
+ };
1993
+ }
1994
+ async function serializeModels(spec, emitter) {
1995
+ const models = {};
1996
+ const standardImports = [
1997
+ "from typing import Any, Dict, List, Optional, Union, Literal",
1998
+ "from pydantic import BaseModel, Field",
1999
+ "from datetime import datetime, date",
2000
+ "from uuid import UUID",
2001
+ "from enum import Enum"
2002
+ ].join("\n");
2003
+ emitter.onEmit((name, content, schema) => {
2004
+ const fullContent = `${standardImports}
2005
+ ${schema["x-inputname"] ? "from ..http.dispatcher import RequestConfig" : ""}
2006
+
2007
+
2008
+ ${content}`;
2009
+ if (schema["x-inputname"]) {
2010
+ models[`inputs/${snakecase2(name)}.py`] = fullContent;
2011
+ } else if (schema["x-response-name"]) {
2012
+ models[`outputs/${snakecase2(name)}.py`] = fullContent;
2013
+ } else {
2014
+ models[`models/${snakecase2(name)}.py`] = fullContent;
2015
+ }
2016
+ });
2017
+ if (spec.components?.schemas) {
2018
+ for (const [name, schema] of Object.entries(spec.components.schemas)) {
2019
+ if (!isRef2(schema)) {
2020
+ emitter.handle(schema, { name });
2021
+ }
2022
+ }
2023
+ }
2024
+ return models;
2025
+ }
2026
+
2027
+ // packages/cli/src/lib/langs/python.ts
2028
+ import { augmentSpec as augmentSpec3, loadSpec as loadSpec2 } from "@sdk-it/spec";
2029
+ var python_default = new Command3("python").description("Generate Python SDK").addOption(specOption.makeOptionMandatory(true)).addOption(outputOption.makeOptionMandatory(true)).option("-l, --language <language>", "Programming language for the SDK").option("-n, --name <n>", "Name of the generated client", "Client").option("-v, --verbose", "Verbose output", false).option("--formatter <formatter>", "Formatter to use for the generated code").action(async (options) => {
2030
+ const spec = augmentSpec3({ spec: await loadSpec2(options.spec) }, true);
2031
+ await generate2(spec, {
2032
+ output: options.output,
2033
+ mode: options.mode || "full",
2034
+ name: options.name,
2035
+ formatCode: ({ output }) => {
2036
+ if (options.formatter) {
2037
+ const [command2, ...args] = options.formatter.split(" ");
2038
+ execFile2(command2, args, {
2039
+ env: { ...process.env, SDK_IT_OUTPUT: output }
2040
+ });
2041
+ } else {
2042
+ try {
2043
+ execSync2(`black ${shellEnv("SDK_IT_OUTPUT")}`, {
2044
+ env: { ...process.env, SDK_IT_OUTPUT: output },
2045
+ stdio: options.verbose ? "inherit" : "pipe"
2046
+ });
2047
+ } catch {
2048
+ try {
2049
+ execSync2(`ruff format ${shellEnv("SDK_IT_OUTPUT")}`, {
2050
+ env: { ...process.env, SDK_IT_OUTPUT: output },
2051
+ stdio: options.verbose ? "inherit" : "pipe"
2052
+ });
2053
+ } catch {
2054
+ if (options.verbose) {
2055
+ console.warn(
2056
+ "No Python formatter found (black or ruff). Skipping formatting."
2057
+ );
2058
+ }
2059
+ }
2060
+ }
2061
+ }
2062
+ }
2063
+ });
2064
+ });
2065
+
49
2066
  // packages/cli/src/lib/langs/typescript.ts
50
- import { Command as Command2, Option as Option2 } from "commander";
2067
+ import { Command as Command4, Option as Option2 } from "commander";
51
2068
  import { publish } from "libnpmpublish";
52
- import { execFile as execFile2, execSync as execSync2, spawnSync } from "node:child_process";
2069
+ import { execFile as execFile3, execSync as execSync3, spawnSync } from "node:child_process";
53
2070
  import { readFile } from "node:fs/promises";
54
2071
  import { tmpdir } from "node:os";
55
- import { join } from "node:path";
2072
+ import { join as join2 } from "node:path";
56
2073
  import getAuthToken from "registry-auth-token";
57
- import { writeFiles } from "@sdk-it/core/file-system.js";
58
- import { augmentSpec as augmentSpec2, loadSpec as loadSpec2 } from "@sdk-it/spec";
59
- import { generate as generate2 } from "@sdk-it/typescript";
60
- var command = new Command2("typescript").alias("ts").description("Generate TypeScript SDK").addOption(specOption.makeOptionMandatory(true)).addOption(outputOption.makeOptionMandatory(false)).option(
2074
+ import { writeFiles as writeFiles2 } from "@sdk-it/core/file-system.js";
2075
+ import { augmentSpec as augmentSpec4, loadSpec as loadSpec3 } from "@sdk-it/spec";
2076
+ import { generate as generate3 } from "@sdk-it/typescript";
2077
+ var command = new Command4("typescript").alias("ts").description("Generate TypeScript SDK").addOption(specOption.makeOptionMandatory(true)).addOption(outputOption.makeOptionMandatory(false)).option(
61
2078
  "--useTsExtension [value]",
62
2079
  "Use .ts extension for generated files",
63
2080
  (value) => value === "false" ? false : true,
@@ -94,9 +2111,9 @@ var command = new Command2("typescript").alias("ts").description("Generate TypeS
94
2111
  });
95
2112
  return;
96
2113
  }
97
- const spec = augmentSpec2(
2114
+ const spec = augmentSpec4(
98
2115
  {
99
- spec: await loadSpec2(options.spec),
2116
+ spec: await loadSpec3(options.spec),
100
2117
  responses: { flattenErrorResponses: true }
101
2118
  },
102
2119
  false
@@ -112,8 +2129,8 @@ var command = new Command2("typescript").alias("ts").description("Generate TypeS
112
2129
  }
113
2130
  });
114
2131
  async function emitLocal(spec, options) {
115
- await generate2(spec, {
116
- writer: writeFiles,
2132
+ await generate3(spec, {
2133
+ writer: writeFiles2,
117
2134
  output: options.output,
118
2135
  mode: options.mode || "minimal",
119
2136
  name: options.name,
@@ -127,7 +2144,7 @@ async function emitLocal(spec, options) {
127
2144
  formatCode: ({ env, output }) => {
128
2145
  if (options.formatter) {
129
2146
  const [command2, ...args] = options.formatter.split(" ");
130
- execFile2(command2, args, {
2147
+ execFile3(command2, args, {
131
2148
  env: { ...env, SDK_IT_OUTPUT: output }
132
2149
  });
133
2150
  } else if (options.defaultFormatter) {
@@ -144,7 +2161,7 @@ async function emitLocal(spec, options) {
144
2161
  });
145
2162
  if (options.install && options.mode === "full") {
146
2163
  console.log("Installing dependencies...");
147
- execSync2("npm install", {
2164
+ execSync3("npm install", {
148
2165
  cwd: options.output,
149
2166
  stdio: options.verbose ? "inherit" : "pipe"
150
2167
  });
@@ -153,7 +2170,7 @@ async function emitLocal(spec, options) {
153
2170
  async function emitRemote(spec, options) {
154
2171
  const registry = options.publish === "npm" ? "https://registry.npmjs.org/" : options.publish === "github" ? "https://npm.pkg.github.com/" : options.publish;
155
2172
  console.log("Publishing to registry:", registry);
156
- const path = join(tmpdir(), crypto.randomUUID());
2173
+ const path = join2(tmpdir(), crypto.randomUUID());
157
2174
  await emitLocal(spec, {
158
2175
  ...options,
159
2176
  output: path,
@@ -161,7 +2178,7 @@ async function emitRemote(spec, options) {
161
2178
  mode: "full"
162
2179
  });
163
2180
  const manifest = JSON.parse(
164
- await readFile(join(path, "package.json"), "utf-8")
2181
+ await readFile(join2(path, "package.json"), "utf-8")
165
2182
  );
166
2183
  const registryUrl = new URL(registry);
167
2184
  const npmrc = process.env.NPM_TOKEN ? {
@@ -176,9 +2193,9 @@ async function emitRemote(spec, options) {
176
2193
  "No npm auth token found in .npmrc or environment. please provide NPM_TOKEN."
177
2194
  );
178
2195
  }
179
- const packResult = execSync2("npm pack --pack-destination .", { cwd: path });
2196
+ const packResult = execSync3("npm pack --pack-destination .", { cwd: path });
180
2197
  const [tgzName] = packResult.toString().trim().split("\n");
181
- await publish(manifest, await readFile(join(path, tgzName)), {
2198
+ await publish(manifest, await readFile(join2(path, tgzName)), {
182
2199
  registry,
183
2200
  defaultTag: "latest",
184
2201
  forceAuth: {
@@ -191,9 +2208,9 @@ async function emitRemote(spec, options) {
191
2208
  var typescript_default = command;
192
2209
 
193
2210
  // packages/cli/src/lib/cli.ts
194
- var generate3 = new Command3("generate").addCommand(typescript_default).addCommand(dart_default);
195
- var cli = program.description(`CLI tool to interact with SDK-IT.`).addCommand(generate3, { isDefault: true }).addCommand(
196
- new Command3("_internal").action(() => {
2211
+ var generate4 = new Command5("generate").addCommand(typescript_default).addCommand(python_default).addCommand(dart_default).addCommand(apiref_default);
2212
+ var cli = program.description(`CLI tool to interact with SDK-IT.`).addCommand(generate4, { isDefault: true }).addCommand(
2213
+ new Command5("_internal").action(() => {
197
2214
  }),
198
2215
  { hidden: true }
199
2216
  ).parse(process.argv);