@typespec/http-client-python 0.35.1 → 0.37.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/README.md +6 -0
  2. package/dist/emitter/code-model.d.ts +1 -1
  3. package/dist/emitter/code-model.d.ts.map +1 -1
  4. package/dist/emitter/code-model.js.map +1 -1
  5. package/dist/emitter/emitter.d.ts +2 -2
  6. package/dist/emitter/emitter.d.ts.map +1 -1
  7. package/dist/emitter/emitter.js +24 -3
  8. package/dist/emitter/emitter.js.map +1 -1
  9. package/dist/emitter/external-process.d.ts +1 -15
  10. package/dist/emitter/external-process.d.ts.map +1 -1
  11. package/dist/emitter/external-process.js +1 -17
  12. package/dist/emitter/external-process.js.map +1 -1
  13. package/dist/emitter/http.d.ts +45 -2
  14. package/dist/emitter/http.d.ts.map +1 -1
  15. package/dist/emitter/http.js +97 -1
  16. package/dist/emitter/http.js.map +1 -1
  17. package/dist/emitter/index.d.ts +2 -1
  18. package/dist/emitter/index.d.ts.map +1 -1
  19. package/dist/emitter/index.js.map +1 -1
  20. package/dist/emitter/lib.d.ts +3 -2
  21. package/dist/emitter/lib.d.ts.map +1 -1
  22. package/dist/emitter/lib.js +6 -1
  23. package/dist/emitter/lib.js.map +1 -1
  24. package/dist/emitter/node-runner.browser.d.ts +1 -1
  25. package/dist/emitter/node-runner.browser.d.ts.map +1 -1
  26. package/dist/emitter/node-runner.browser.js.map +1 -1
  27. package/dist/emitter/node-runner.d.ts +3 -3
  28. package/dist/emitter/node-runner.d.ts.map +1 -1
  29. package/dist/emitter/node-runner.js.map +1 -1
  30. package/dist/emitter/system-requirements.d.ts.map +1 -1
  31. package/dist/emitter/system-requirements.js.map +1 -1
  32. package/dist/emitter/types.d.ts +4 -4
  33. package/dist/emitter/types.d.ts.map +1 -1
  34. package/dist/emitter/types.js +15 -3
  35. package/dist/emitter/types.js.map +1 -1
  36. package/dist/emitter/utils.d.ts +2 -2
  37. package/dist/emitter/utils.d.ts.map +1 -1
  38. package/dist/emitter/utils.js +1 -1
  39. package/dist/emitter/utils.js.map +1 -1
  40. package/dist/emitter/yaml-utils.d.ts +19 -0
  41. package/dist/emitter/yaml-utils.d.ts.map +1 -0
  42. package/dist/emitter/yaml-utils.js +22 -0
  43. package/dist/emitter/yaml-utils.js.map +1 -0
  44. package/emitter/src/code-model.ts +4 -2
  45. package/emitter/src/emitter.ts +31 -7
  46. package/emitter/src/external-process.ts +3 -19
  47. package/emitter/src/http.ts +159 -5
  48. package/emitter/src/index.ts +2 -1
  49. package/emitter/src/lib.ts +11 -6
  50. package/emitter/src/node-runner.browser.ts +2 -1
  51. package/emitter/src/node-runner.ts +6 -3
  52. package/emitter/src/system-requirements.ts +2 -1
  53. package/emitter/src/types.ts +23 -8
  54. package/emitter/src/utils.ts +5 -4
  55. package/emitter/src/yaml-utils.ts +22 -0
  56. package/emitter/temp/tsconfig.tsbuildinfo +1 -1
  57. package/emitter/test/browser-pyodide.test.ts +25 -0
  58. package/emitter/test/streaming.test.ts +155 -0
  59. package/emitter/test/types.test.ts +72 -0
  60. package/emitter/test/{external-process.test.ts → yaml-utils.test.ts} +2 -2
  61. package/eng/scripts/ci/dev_requirements.txt +1 -1
  62. package/eng/scripts/ci/regenerate-common.ts +1 -1
  63. package/eng/scripts/ci/regenerate.ts +1 -2
  64. package/eng/scripts/ci/run-tests.ts +2 -1
  65. package/eng/scripts/ci/run_pylint.py +1 -0
  66. package/eng/scripts/setup/__pycache__/package_manager.cpython-311.pyc +0 -0
  67. package/eng/scripts/setup/__pycache__/venvtools.cpython-311.pyc +0 -0
  68. package/eng/scripts/setup/system-requirements.ts +2 -1
  69. package/generator/build/lib/pygen/__init__.py +24 -5
  70. package/generator/build/lib/pygen/codegen/models/__init__.py +3 -1
  71. package/generator/build/lib/pygen/codegen/models/code_model.py +29 -9
  72. package/generator/build/lib/pygen/codegen/models/enum_type.py +21 -9
  73. package/generator/build/lib/pygen/codegen/models/list_type.py +1 -0
  74. package/generator/build/lib/pygen/codegen/models/model_type.py +1 -3
  75. package/generator/build/lib/pygen/codegen/models/operation.py +13 -3
  76. package/generator/build/lib/pygen/codegen/models/parameter.py +3 -1
  77. package/generator/build/lib/pygen/codegen/models/response.py +152 -8
  78. package/generator/build/lib/pygen/codegen/models/utils.py +14 -0
  79. package/generator/build/lib/pygen/codegen/serializers/__init__.py +13 -6
  80. package/generator/build/lib/pygen/codegen/serializers/builder_serializer.py +219 -69
  81. package/generator/build/lib/pygen/codegen/serializers/client_serializer.py +13 -6
  82. package/generator/build/lib/pygen/codegen/serializers/general_serializer.py +4 -0
  83. package/generator/build/lib/pygen/codegen/serializers/import_serializer.py +27 -0
  84. package/generator/build/lib/pygen/codegen/serializers/model_serializer.py +4 -1
  85. package/generator/build/lib/pygen/codegen/serializers/types_serializer.py +44 -12
  86. package/generator/build/lib/pygen/codegen/templates/streaming_base.py.jinja2 +767 -0
  87. package/generator/build/lib/pygen/preprocess/__init__.py +70 -17
  88. package/generator/build/lib/pygen/utils.py +15 -0
  89. package/generator/component-detection-pip-report.json +9 -9
  90. package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
  91. package/generator/pygen/__init__.py +24 -5
  92. package/generator/pygen/codegen/models/__init__.py +3 -1
  93. package/generator/pygen/codegen/models/code_model.py +29 -9
  94. package/generator/pygen/codegen/models/enum_type.py +21 -9
  95. package/generator/pygen/codegen/models/list_type.py +1 -0
  96. package/generator/pygen/codegen/models/model_type.py +1 -3
  97. package/generator/pygen/codegen/models/operation.py +13 -3
  98. package/generator/pygen/codegen/models/parameter.py +3 -1
  99. package/generator/pygen/codegen/models/response.py +152 -8
  100. package/generator/pygen/codegen/models/utils.py +14 -0
  101. package/generator/pygen/codegen/serializers/__init__.py +13 -6
  102. package/generator/pygen/codegen/serializers/builder_serializer.py +219 -69
  103. package/generator/pygen/codegen/serializers/client_serializer.py +13 -6
  104. package/generator/pygen/codegen/serializers/general_serializer.py +4 -0
  105. package/generator/pygen/codegen/serializers/import_serializer.py +27 -0
  106. package/generator/pygen/codegen/serializers/model_serializer.py +4 -1
  107. package/generator/pygen/codegen/serializers/types_serializer.py +44 -12
  108. package/generator/pygen/codegen/templates/streaming_base.py.jinja2 +767 -0
  109. package/generator/pygen/preprocess/__init__.py +70 -17
  110. package/generator/pygen/utils.py +15 -0
  111. package/generator/pygen.egg-info/PKG-INFO +1 -1
  112. package/generator/pygen.egg-info/SOURCES.txt +1 -0
  113. package/generator/pygen.egg-info/requires.txt +1 -1
  114. package/generator/setup.py +1 -1
  115. package/package.json +33 -33
@@ -1,7 +1,6 @@
1
1
  import { getNamespaceFullName, NoTarget } from "@typespec/compiler";
2
2
 
3
- import {
4
- getHttpOperationParameter,
3
+ import type {
5
4
  SdkBasicServiceMethod,
6
5
  SdkBodyParameter,
7
6
  SdkClientType,
@@ -19,11 +18,13 @@ import {
19
18
  SdkQueryParameter,
20
19
  SdkServiceMethod,
21
20
  SdkServiceResponseHeader,
21
+ SdkSseEventMetadata,
22
22
  SdkType,
23
- UsageFlags,
24
23
  } from "@azure-tools/typespec-client-generator-core";
25
- import { HttpStatusCodeRange } from "@typespec/http";
26
- import { PythonSdkContext, reportDiagnostic } from "./lib.js";
24
+ import { getHttpOperationParameter, UsageFlags } from "@azure-tools/typespec-client-generator-core";
25
+ import type { HttpStatusCodeRange } from "@typespec/http";
26
+ import type { PythonSdkContext } from "./lib.js";
27
+ import { reportDiagnostic } from "./lib.js";
27
28
  import { getType, KnownTypes } from "./types.js";
28
29
  import {
29
30
  emitParamBase,
@@ -42,6 +43,158 @@ export enum ReferredByOperationTypes {
42
43
  NonPagingOnly = 2,
43
44
  }
44
45
 
46
+ type StructuredStreamKind = "jsonl" | "sse";
47
+ type EmittedType = ReturnType<typeof getType>;
48
+
49
+ interface StructuredStreamEvent {
50
+ eventType: string | undefined;
51
+ /**
52
+ * Payload type for this one SSE event. For an event envelope, this is the type of the property
53
+ * marked `@Events.data`. Together with {@link eventType} these form the
54
+ * runtime dispatch table (wire event name -> model to deserialize) inside the generated
55
+ * `_callback`. This is a narrower type than {@link StructuredStreamingInfo.itemType}.
56
+ */
57
+ payloadType: EmittedType;
58
+ /**
59
+ * True when this event is a `@terminalEvent` that carries a payload (a named / model event,
60
+ * not a bare string-constant sentinel). Such events are deserialized and yielded like any
61
+ * other event, and iteration stops immediately after one is yielded. Contrast with
62
+ * {@link StructuredStreamingInfo.terminalEvent}, the sentinel that stops without yielding.
63
+ */
64
+ isTerminal?: boolean;
65
+ /** Content type of the payload, not the enclosing event. */
66
+ payloadContentType?: string;
67
+ }
68
+
69
+ interface StructuredStreamingInfo {
70
+ kind: StructuredStreamKind;
71
+ /**
72
+ * The aggregate stream element type used for the `Stream[T]` / `AsyncStream[T]` return
73
+ * annotation (a single type expression). For homogeneous JSONL this is the one model; for
74
+ * heterogeneous SSE this is the union of every event payload.
75
+ *
76
+ * Note the deliberate overlap with the per-event {@link StructuredStreamEvent.payloadType}: for
77
+ * heterogeneous SSE this union is exactly the sum of the `events[]` payload types. Both are
78
+ * carried because the union alone cannot recover the wire-name -> member mapping needed for
79
+ * dispatch, and the events list alone is not a single valid type expression for the annotation.
80
+ */
81
+ itemType: EmittedType;
82
+ events?: StructuredStreamEvent[];
83
+ /**
84
+ * A bare string-constant `@terminalEvent` with no event name (e.g. `"[DONE]"`). Iteration
85
+ * stops when an event's `data` equals this value, and the sentinel is NOT yielded. Named /
86
+ * model terminal events are carried in {@link events} with `isTerminal: true` instead.
87
+ */
88
+ terminalEvent?: string;
89
+ }
90
+
91
+ /** Whether pygen can deserialize the stream item type. */
92
+ export function isStructuredStreamType(type: SdkType): boolean {
93
+ switch (type.kind) {
94
+ case "model":
95
+ case "union":
96
+ return true;
97
+ case "nullable":
98
+ return isStructuredStreamType(type.type);
99
+ default:
100
+ return false;
101
+ }
102
+ }
103
+
104
+ export function getStructuredStreamKind(
105
+ response: SdkHttpResponse | SdkHttpErrorResponse,
106
+ ): StructuredStreamKind | undefined {
107
+ if (response.sseMetadata) return "sse";
108
+
109
+ const contentTypes = response.streamMetadata?.contentTypes ?? response.contentTypes ?? [];
110
+ for (const contentType of contentTypes) {
111
+ const mediaType = contentType.split(";", 1)[0].trim().toLowerCase();
112
+ if (mediaType === "text/event-stream") return "sse";
113
+ if (mediaType === "application/jsonl") return "jsonl";
114
+ }
115
+ return undefined;
116
+ }
117
+
118
+ function getStringConstantValue(type: SdkType): string | undefined {
119
+ if (type.kind === "nullable") return getStringConstantValue(type.type);
120
+ return type.kind === "constant" && typeof type.value === "string" ? type.value : undefined;
121
+ }
122
+
123
+ /**
124
+ * Split the SSE events into the runtime dispatch table and a bare string-constant sentinel.
125
+ *
126
+ * A `@terminalEvent` comes in two shapes:
127
+ * * a nameless string constant (e.g. `"[DONE]"`) -> a pure sentinel: iteration stops when an
128
+ * event's `data` equals this value and the event is NOT yielded. Returned as `terminalEvent`.
129
+ * * a named / model event (e.g. `error`, `response.completed`) -> carries a payload the consumer
130
+ * needs, so it is deserialized and yielded like any other event, then iteration stops. Returned
131
+ * in `events` with `isTerminal: true`.
132
+ *
133
+ * `toPayloadType` maps event payloads to emitted types; it is injected so this partitioning stays
134
+ * a pure function that can be unit-tested without a full emitter context.
135
+ */
136
+ export function partitionSSEEvents(
137
+ events: readonly SdkSseEventMetadata[],
138
+ toPayloadType: (type: SdkType) => EmittedType,
139
+ ): { events: StructuredStreamEvent[]; terminalEvent?: string } {
140
+ const dispatch: StructuredStreamEvent[] = [];
141
+ let terminalEvent: string | undefined;
142
+ for (const event of events) {
143
+ if (event.isTerminalEvent) {
144
+ const sentinelValue =
145
+ event.eventType === undefined
146
+ ? (getStringConstantValue(event.payloadType) ?? getStringConstantValue(event.type))
147
+ : undefined;
148
+ if (sentinelValue !== undefined) {
149
+ // Keep the first sentinel; no current spec defines more than one.
150
+ terminalEvent ??= sentinelValue;
151
+ continue;
152
+ }
153
+ dispatch.push({
154
+ eventType: event.eventType,
155
+ payloadType: toPayloadType(event.payloadType),
156
+ isTerminal: true,
157
+ payloadContentType: event.payloadContentType,
158
+ });
159
+ continue;
160
+ }
161
+ dispatch.push({
162
+ eventType: event.eventType,
163
+ payloadType: toPayloadType(event.payloadType),
164
+ payloadContentType: event.payloadContentType,
165
+ });
166
+ }
167
+ return terminalEvent !== undefined ? { events: dispatch, terminalEvent } : { events: dispatch };
168
+ }
169
+
170
+ function emitStructuredStreamingInfo(
171
+ context: PythonSdkContext,
172
+ response: SdkHttpResponse | SdkHttpErrorResponse,
173
+ ): StructuredStreamingInfo | undefined {
174
+ const streamMetadata = response.streamMetadata;
175
+ if (!streamMetadata || !isStructuredStreamType(streamMetadata.streamType)) return undefined;
176
+
177
+ const kind = getStructuredStreamKind(response);
178
+ if (!kind) return undefined;
179
+
180
+ const streaming: StructuredStreamingInfo = {
181
+ kind,
182
+ itemType: getType(context, streamMetadata.streamType),
183
+ };
184
+ if (kind !== "sse") return streaming;
185
+
186
+ const sseMetadata = response.sseMetadata;
187
+ if (!sseMetadata || sseMetadata.events.length === 0) return undefined;
188
+
189
+ const { events, terminalEvent } = partitionSSEEvents(sseMetadata.events, (type) =>
190
+ getType(context, type),
191
+ );
192
+ if (events.length > 0) streaming.events = events;
193
+ if (terminalEvent !== undefined) streaming.terminalEvent = terminalEvent;
194
+
195
+ return streaming;
196
+ }
197
+
45
198
  function isEtagType(type: SdkType): boolean {
46
199
  if (type.kind === "nullable") return isEtagType(type.type);
47
200
  const raw = type.__raw;
@@ -682,6 +835,7 @@ function emitHttpResponse(
682
835
  "invalid-lro-result",
683
836
  method,
684
837
  ),
838
+ streaming: isException ? undefined : emitStructuredStreamingInfo(context, response),
685
839
  };
686
840
  }
687
841
 
@@ -1,2 +1,3 @@
1
1
  export * from "./emitter.js";
2
- export { $lib, PythonEmitterOptions, PythonEmitterOptionsSchema } from "./lib.js";
2
+ export { $lib, PythonEmitterOptionsSchema } from "./lib.js";
3
+ export type { PythonEmitterOptions } from "./lib.js";
@@ -1,9 +1,7 @@
1
- import {
2
- SdkContext,
3
- SdkType,
4
- UnbrandedSdkEmitterOptions,
5
- } from "@azure-tools/typespec-client-generator-core";
6
- import { createTypeSpecLibrary, JSONSchemaType, paramMessage } from "@typespec/compiler";
1
+ import type { SdkContext, SdkType } from "@azure-tools/typespec-client-generator-core";
2
+ import { UnbrandedSdkEmitterOptions } from "@azure-tools/typespec-client-generator-core";
3
+ import type { JSONSchemaType } from "@typespec/compiler";
4
+ import { createTypeSpecLibrary, paramMessage } from "@typespec/compiler";
7
5
 
8
6
  export interface PythonEmitterOptions {
9
7
  "api-version"?: string;
@@ -24,6 +22,7 @@ export interface PythonEmitterOptions {
24
22
  "head-as-boolean"?: boolean;
25
23
  "use-pyodide"?: boolean;
26
24
  "keep-setup-py"?: boolean;
25
+ "generate-typeddict"?: boolean;
27
26
  "keep-pyproject-fields"?: {
28
27
  authors?: boolean;
29
28
  description?: boolean;
@@ -111,6 +110,12 @@ export const PythonEmitterOptionsSchema: JSONSchemaType<PythonEmitterOptions> =
111
110
  description:
112
111
  "Whether to keep the existing `setup.py` when `generate-packaging-files` is `true`. If set to `false` and by default, `pyproject.toml` will be generated instead. To generate `setup.py`, use `basic-setup-py`.",
113
112
  },
113
+ "generate-typeddict": {
114
+ type: "boolean",
115
+ nullable: true,
116
+ description:
117
+ "Whether to add TypedDict typing for JSON dictionary input in `models-mode: dpg`, instead of accepting only generic JSON. This enriches the typing on the existing overloads rather than adding another request-body overload. Defaults to `true`.",
118
+ },
114
119
  "keep-pyproject-fields": {
115
120
  type: "object",
116
121
  nullable: true,
@@ -6,7 +6,8 @@
6
6
  import type { EmitContext } from "@typespec/compiler";
7
7
  import { NoTarget } from "@typespec/compiler";
8
8
  import type { PyodideInterface } from "pyodide";
9
- import { PythonEmitterOptions, reportDiagnostic } from "./lib.js";
9
+ import type { PythonEmitterOptions } from "./lib.js";
10
+ import { reportDiagnostic } from "./lib.js";
10
11
 
11
12
  export interface RunNodeEmitArgs {
12
13
  context: EmitContext<PythonEmitterOptions>;
@@ -5,16 +5,19 @@
5
5
  // `node-runner.browser.ts` stub is swapped in via the `"browser"` field in
6
6
  // `package.json` when bundling with `platform: "browser"`.
7
7
 
8
- import { EmitContext, NoTarget } from "@typespec/compiler";
8
+ import type { EmitContext } from "@typespec/compiler";
9
+ import { NoTarget } from "@typespec/compiler";
9
10
  import { execSync } from "child_process";
10
11
  import fs from "fs";
11
12
  import os from "os";
12
13
  import path, { dirname } from "path";
13
- import { loadPyodide, PyodideInterface } from "pyodide";
14
+ import type { PyodideInterface } from "pyodide";
15
+ import { loadPyodide } from "pyodide";
14
16
  import { fileURLToPath } from "url";
15
17
  import { blackExcludeDirs, PYGEN_WHEEL_FILENAME } from "./constants.js";
16
18
  import { saveCodeModelAsYaml } from "./external-process.js";
17
- import { PythonEmitterOptions, reportDiagnostic } from "./lib.js";
19
+ import type { PythonEmitterOptions } from "./lib.js";
20
+ import { reportDiagnostic } from "./lib.js";
18
21
  import { runPython3 } from "./run-python3.js";
19
22
  import { quoteShellArg } from "./utils.js";
20
23
 
@@ -1,4 +1,5 @@
1
- import { ChildProcess, spawn, SpawnOptions } from "child_process";
1
+ import type { ChildProcess, SpawnOptions } from "child_process";
2
+ import { spawn } from "child_process";
2
3
  import { coerce, satisfies } from "semver";
3
4
 
4
5
  /*
@@ -1,5 +1,4 @@
1
- import {
2
- isHttpMetadata,
1
+ import type {
3
2
  SdkArrayType,
4
3
  SdkBuiltInType,
5
4
  SdkConstantType,
@@ -14,12 +13,14 @@ import {
14
13
  SdkModelType,
15
14
  SdkType,
16
15
  SdkUnionType,
17
- UsageFlags,
18
16
  } from "@azure-tools/typespec-client-generator-core";
19
- import { getEncode, Type } from "@typespec/compiler";
20
- import { HttpAuth, Visibility } from "@typespec/http";
17
+ import { isHttpMetadata, UsageFlags } from "@azure-tools/typespec-client-generator-core";
18
+ import type { Type } from "@typespec/compiler";
19
+ import { getEncode } from "@typespec/compiler";
20
+ import type { HttpAuth } from "@typespec/http";
21
+ import { Visibility } from "@typespec/http";
21
22
  import { dump } from "js-yaml";
22
- import { PythonSdkContext } from "./lib.js";
23
+ import type { PythonSdkContext } from "./lib.js";
23
24
  import {
24
25
  camelToSnakeCase,
25
26
  emitParamBase,
@@ -473,10 +474,17 @@ const sdkScalarKindToPythonKind: Record<string, string> = {
473
474
  azureLocation: "string",
474
475
  };
475
476
 
477
+ const supportedPythonEncodings = new Map<string, ReadonlySet<string>>([
478
+ ["boolean", new Set(["string"])],
479
+ ["integer", new Set(["string"])],
480
+ ["bytes", new Set(["base64", "base64url"])],
481
+ ]);
482
+
476
483
  function emitBuiltInType(
477
484
  context: PythonSdkContext,
478
485
  type: SdkBuiltInType | SdkDurationType | SdkDateTimeType,
479
486
  ): Record<string, any> {
487
+ const pythonType = sdkScalarKindToPythonKind[type.kind] || type.kind;
480
488
  if (type.encode) {
481
489
  if (type.kind === "duration") {
482
490
  if (type.encode === "ISO8601") {
@@ -508,7 +516,14 @@ function emitBuiltInType(
508
516
  }
509
517
  }
510
518
 
511
- // fallback to wire type for unknown or unsupported encode
519
+ if (supportedPythonEncodings.get(pythonType)?.has(type.encode)) {
520
+ return getSimpleTypeResult(context, {
521
+ type: pythonType,
522
+ encode: type.encode,
523
+ });
524
+ }
525
+
526
+ // Python cannot apply unknown/custom encodings, so expose the wire type instead of silently ignoring the encoding.
512
527
  if ("wireType" in type && type.wireType !== undefined) {
513
528
  return getSimpleTypeResult(context, {
514
529
  type: sdkScalarKindToPythonKind[type.wireType.kind] || type.wireType.kind,
@@ -518,7 +533,7 @@ function emitBuiltInType(
518
533
  }
519
534
 
520
535
  return getSimpleTypeResult(context, {
521
- type: sdkScalarKindToPythonKind[type.kind] || type.kind, // TODO: switch to kind
536
+ type: pythonType,
522
537
  encode: type.encode,
523
538
  });
524
539
  }
@@ -1,5 +1,4 @@
1
- import {
2
- InitializedByFlags,
1
+ import type {
3
2
  SdkCredentialParameter,
4
3
  SdkEndpointParameter,
5
4
  SdkHeaderParameter,
@@ -13,9 +12,11 @@ import {
13
12
  SdkServiceResponseHeader,
14
13
  SdkType,
15
14
  } from "@azure-tools/typespec-client-generator-core";
15
+ import { InitializedByFlags } from "@azure-tools/typespec-client-generator-core";
16
16
  import { getNamespaceFullName } from "@typespec/compiler";
17
- import { marked, Token } from "marked";
18
- import { PythonSdkContext } from "./lib.js";
17
+ import type { Token } from "marked";
18
+ import { marked } from "marked";
19
+ import type { PythonSdkContext } from "./lib.js";
19
20
  import { getSimpleTypeResult, getType } from "./types.js";
20
21
 
21
22
  function IsFullyUpperCase(identifier: string, maxUppercasePreserve: number) {
@@ -0,0 +1,22 @@
1
+ import jsyaml from "js-yaml";
2
+
3
+ /**
4
+ * Serialize the given codemodel to a YAML string.
5
+ *
6
+ * The generated YAML is consumed by the Python generator, which parses it with
7
+ * PyYAML (YAML 1.1). js-yaml, on the other hand, dumps using YAML 1.2 rules, so
8
+ * plain scalars such as `2020_01_01` (a snake-cased enum member name) are left
9
+ * unquoted because YAML 1.2 does not treat underscores as integer separators.
10
+ * PyYAML would then read `2020_01_01` back as the integer `20200101`, corrupting
11
+ * string values (e.g. enum member names, descriptions). Forcing every string
12
+ * scalar to be quoted guarantees that PyYAML round-trips them as strings.
13
+ *
14
+ * This module must remain browser-safe (no Node built-ins): `emitter.ts` calls
15
+ * this function on the browser/Pyodide path, and esbuild bundles it into the
16
+ * playground bundle with `platform: "browser"`.
17
+ * @param codemodel Codemodel to serialize
18
+ * @return the YAML representation of the codemodel.
19
+ */
20
+ export function dumpCodeModelToYaml(codemodel: unknown): string {
21
+ return jsyaml.dump(codemodel, { forceQuotes: true, quotingType: '"' });
22
+ }