@typespec/http-client-python 0.7.1 → 0.8.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 (58) hide show
  1. package/dist/emitter/code-model.js +6 -6
  2. package/dist/emitter/code-model.js.map +1 -1
  3. package/dist/emitter/emitter.d.ts.map +1 -1
  4. package/dist/emitter/emitter.js +57 -54
  5. package/dist/emitter/emitter.js.map +1 -1
  6. package/dist/emitter/external-process.js +3 -3
  7. package/dist/emitter/external-process.js.map +1 -1
  8. package/dist/emitter/http.d.ts.map +1 -1
  9. package/dist/emitter/http.js +81 -23
  10. package/dist/emitter/http.js.map +1 -1
  11. package/dist/emitter/lib.d.ts +37 -1
  12. package/dist/emitter/lib.d.ts.map +1 -1
  13. package/dist/emitter/lib.js +24 -0
  14. package/dist/emitter/lib.js.map +1 -1
  15. package/dist/emitter/types.js +3 -3
  16. package/dist/emitter/types.js.map +1 -1
  17. package/dist/emitter/utils.d.ts +4 -2
  18. package/dist/emitter/utils.d.ts.map +1 -1
  19. package/dist/emitter/utils.js +21 -2
  20. package/dist/emitter/utils.js.map +1 -1
  21. package/emitter/src/code-model.ts +8 -8
  22. package/emitter/src/emitter.ts +57 -53
  23. package/emitter/src/external-process.ts +3 -3
  24. package/emitter/src/http.ts +113 -26
  25. package/emitter/src/lib.ts +24 -0
  26. package/emitter/src/types.ts +3 -3
  27. package/emitter/src/utils.ts +36 -1
  28. package/emitter/temp/tsconfig.tsbuildinfo +1 -1
  29. package/eng/scripts/setup/__pycache__/venvtools.cpython-38.pyc +0 -0
  30. package/eng/scripts/setup/run_tsp.py +2 -2
  31. package/generator/build/lib/pygen/__init__.py +3 -3
  32. package/generator/build/lib/pygen/black.py +2 -2
  33. package/generator/build/lib/pygen/codegen/__init__.py +5 -5
  34. package/generator/build/lib/pygen/codegen/models/paging_operation.py +11 -2
  35. package/generator/build/lib/pygen/codegen/models/parameter.py +2 -1
  36. package/generator/build/lib/pygen/codegen/models/request_builder_parameter.py +2 -0
  37. package/generator/build/lib/pygen/codegen/models/response.py +1 -1
  38. package/generator/build/lib/pygen/codegen/serializers/builder_serializer.py +47 -25
  39. package/generator/build/lib/pygen/preprocess/__init__.py +10 -12
  40. package/generator/build/lib/pygen/preprocess/python_mappings.py +1 -1
  41. package/generator/build/lib/pygen/utils.py +5 -5
  42. package/generator/component-detection-pip-report.json +7 -6
  43. package/generator/dist/pygen-0.1.0-py3-none-any.whl +0 -0
  44. package/generator/pygen/__init__.py +3 -3
  45. package/generator/pygen/black.py +2 -2
  46. package/generator/pygen/codegen/__init__.py +5 -5
  47. package/generator/pygen/codegen/models/paging_operation.py +11 -2
  48. package/generator/pygen/codegen/models/parameter.py +2 -1
  49. package/generator/pygen/codegen/models/request_builder_parameter.py +2 -0
  50. package/generator/pygen/codegen/models/response.py +1 -1
  51. package/generator/pygen/codegen/serializers/builder_serializer.py +47 -25
  52. package/generator/pygen/preprocess/__init__.py +10 -12
  53. package/generator/pygen/preprocess/python_mappings.py +1 -1
  54. package/generator/pygen/utils.py +5 -5
  55. package/generator/test/generic_mock_api_tests/asynctests/test_payload_pageable_async.py +75 -0
  56. package/generator/test/generic_mock_api_tests/test_payload_pageable.py +54 -0
  57. package/package.json +1 -1
  58. package/generator/test/unbranded/mock_api_tests/cadl-ranch-config.yaml +0 -27
@@ -1,6 +1,7 @@
1
+ import { NoTarget } from "@typespec/compiler";
2
+
1
3
  import {
2
4
  SdkBasicServiceMethod,
3
- SdkBodyModelPropertyType,
4
5
  SdkBodyParameter,
5
6
  SdkClientType,
6
7
  SdkHeaderParameter,
@@ -10,16 +11,16 @@ import {
10
11
  SdkHttpResponse,
11
12
  SdkLroPagingServiceMethod,
12
13
  SdkLroServiceMethod,
14
+ SdkModelPropertyType,
13
15
  SdkPagingServiceMethod,
14
16
  SdkPathParameter,
15
17
  SdkQueryParameter,
16
18
  SdkServiceMethod,
17
19
  SdkServiceResponseHeader,
18
- SdkType,
19
20
  UsageFlags,
20
21
  } from "@azure-tools/typespec-client-generator-core";
21
22
  import { HttpStatusCodeRange } from "@typespec/http";
22
- import { PythonSdkContext } from "./lib.js";
23
+ import { PythonSdkContext, reportDiagnostic } from "./lib.js";
23
24
  import { KnownTypes, getType } from "./types.js";
24
25
  import {
25
26
  camelToSnakeCase,
@@ -29,6 +30,7 @@ import {
29
30
  getImplementation,
30
31
  isAbstract,
31
32
  isAzureCoreErrorResponse,
33
+ isContinuationToken,
32
34
  } from "./utils.js";
33
35
 
34
36
  function isContentTypeParameter(parameter: SdkHeaderParameter) {
@@ -96,6 +98,88 @@ function addLroInformation(
96
98
  };
97
99
  }
98
100
 
101
+ function getWireNameFromPropertySegments(segments: SdkModelPropertyType[]): string | undefined {
102
+ if (segments[0].kind === "property") {
103
+ return segments
104
+ .filter((s) => s.kind === "property")
105
+ .map((s) => s.serializationOptions.json?.name ?? "")
106
+ .join(".");
107
+ }
108
+
109
+ return undefined;
110
+ }
111
+
112
+ function getWireNameWithDiagnostics(
113
+ context: PythonSdkContext<SdkHttpOperation>,
114
+ segments: SdkModelPropertyType[] | undefined,
115
+ code: "invalid-paging-items" | "invalid-next-link" | "invalid-lro-result",
116
+ method?: SdkServiceMethod<SdkHttpOperation>,
117
+ ): string | undefined {
118
+ if (segments && segments.length > 0) {
119
+ const result = getWireNameFromPropertySegments(segments);
120
+ if (result) {
121
+ return result;
122
+ }
123
+ const operationId = method ? method.name : "";
124
+ reportDiagnostic(context.program, {
125
+ code: code,
126
+ target: NoTarget,
127
+ format: { operationId: operationId },
128
+ });
129
+ }
130
+
131
+ return undefined;
132
+ }
133
+
134
+ function buildContinuationToken(
135
+ context: PythonSdkContext<SdkHttpOperation>,
136
+ method: SdkPagingServiceMethod<SdkHttpOperation> | SdkLroPagingServiceMethod<SdkHttpOperation>,
137
+ segments: SdkModelPropertyType[],
138
+ input: boolean = true,
139
+ ): Record<string, any> {
140
+ if (segments[0].kind === "property") {
141
+ const wireName = getWireNameFromPropertySegments(segments);
142
+ if (wireName) {
143
+ return { wireName, location: "body" };
144
+ }
145
+ } else if (input) {
146
+ for (const parameter of method.operation.parameters) {
147
+ if (isContinuationToken(parameter, method)) {
148
+ return { wireName: parameter.serializedName, location: parameter.kind };
149
+ }
150
+ }
151
+ } else {
152
+ for (const response of method.operation.responses) {
153
+ for (const header of response.headers) {
154
+ if (isContinuationToken(header, method, false)) {
155
+ return { wireName: header.serializedName, location: "header" };
156
+ }
157
+ }
158
+ }
159
+ }
160
+ reportDiagnostic(context.program, {
161
+ code: "invalid-continuation-token",
162
+ target: NoTarget,
163
+ format: { operationId: method.name, direction: input ? "request" : "response" },
164
+ });
165
+ return {};
166
+ }
167
+
168
+ function buildAllContinuationToken(
169
+ context: PythonSdkContext<SdkHttpOperation>,
170
+ method: SdkPagingServiceMethod<SdkHttpOperation> | SdkLroPagingServiceMethod<SdkHttpOperation>,
171
+ ): Record<string, any> {
172
+ const parameterSegments = method.pagingMetadata.continuationTokenParameterSegments ?? [];
173
+ const responseSegments = method.pagingMetadata.continuationTokenResponseSegments ?? [];
174
+ if (parameterSegments.length > 0 && responseSegments.length > 0) {
175
+ return {
176
+ input: buildContinuationToken(context, method, parameterSegments),
177
+ output: buildContinuationToken(context, method, responseSegments, false),
178
+ };
179
+ }
180
+ return {};
181
+ }
182
+
99
183
  function addPagingInformation(
100
184
  context: PythonSdkContext<SdkHttpOperation>,
101
185
  rootClient: SdkClientType<SdkHttpOperation>,
@@ -109,13 +193,17 @@ function addPagingInformation(
109
193
  }
110
194
  const itemType = getType(context, method.response.type!);
111
195
  const base = emitHttpOperation(context, rootClient, operationGroupName, method.operation, method);
112
- const itemName = getPropertyWireName(
113
- method.operation.responses[0].type,
114
- method.response.resultPath,
196
+ const itemName = getWireNameWithDiagnostics(
197
+ context,
198
+ method.response.resultSegments,
199
+ "invalid-paging-items",
200
+ method,
115
201
  );
116
- const continuationTokenName = getPropertyWireName(
117
- method.operation.responses[0].type,
118
- method.nextLinkPath,
202
+ const nextLinkName = getWireNameWithDiagnostics(
203
+ context,
204
+ method.pagingMetadata.nextLinkSegments,
205
+ "invalid-next-link",
206
+ method,
119
207
  );
120
208
  base.responses.forEach((resp: Record<string, any>) => {
121
209
  resp.type = itemType;
@@ -126,23 +214,14 @@ function addPagingInformation(
126
214
  discriminator: "paging",
127
215
  exposeStreamKeyword: false,
128
216
  itemName,
129
- continuationTokenName,
217
+ nextLinkName,
130
218
  itemType,
131
219
  description: method.doc ?? "",
132
220
  summary: method.summary,
221
+ continuationToken: buildAllContinuationToken(context, method),
133
222
  };
134
223
  }
135
224
 
136
- function getPropertyWireName(type: SdkType | undefined, path?: string) {
137
- if (!path || !type || type.kind !== "model") return path;
138
- for (const property of type.properties) {
139
- if (property.name === path) {
140
- return (property as SdkBodyModelPropertyType).serializedName;
141
- }
142
- }
143
- return path;
144
- }
145
-
146
225
  export function emitLroHttpMethod(
147
226
  context: PythonSdkContext<SdkHttpOperation>,
148
227
  rootClient: SdkClientType<SdkHttpOperation>,
@@ -192,7 +271,7 @@ function emitHttpOperation(
192
271
  const result = {
193
272
  url: operation.path,
194
273
  method: operation.verb.toUpperCase(),
195
- parameters: emitHttpParameters(context, rootClient, operation),
274
+ parameters: emitHttpParameters(context, rootClient, operation, method),
196
275
  bodyParameter: emitHttpBodyParameter(context, operation.bodyParam),
197
276
  responses,
198
277
  exceptions,
@@ -277,8 +356,9 @@ function emitHttpPathParameter(
277
356
  function emitHttpHeaderParameter(
278
357
  context: PythonSdkContext<SdkHttpOperation>,
279
358
  parameter: SdkHeaderParameter,
359
+ method: SdkServiceMethod<SdkHttpOperation>,
280
360
  ): Record<string, any> {
281
- const base = emitParamBase(context, parameter);
361
+ const base = emitParamBase(context, parameter, method);
282
362
  const [delimiter, explode] = getDelimiterAndExplode(parameter);
283
363
  let clientDefaultValue = parameter.clientDefaultValue;
284
364
  if (isContentTypeParameter(parameter)) {
@@ -302,8 +382,9 @@ function emitHttpHeaderParameter(
302
382
  function emitHttpQueryParameter(
303
383
  context: PythonSdkContext<SdkHttpOperation>,
304
384
  parameter: SdkQueryParameter,
385
+ method: SdkServiceMethod<SdkHttpOperation>,
305
386
  ): Record<string, any> {
306
- const base = emitParamBase(context, parameter);
387
+ const base = emitParamBase(context, parameter, method);
307
388
  const [delimiter, explode] = getDelimiterAndExplode(parameter);
308
389
  return {
309
390
  ...base,
@@ -320,15 +401,16 @@ function emitHttpParameters(
320
401
  context: PythonSdkContext<SdkHttpOperation>,
321
402
  rootClient: SdkClientType<SdkHttpOperation>,
322
403
  operation: SdkHttpOperation,
404
+ method: SdkServiceMethod<SdkHttpOperation>,
323
405
  ): Record<string, any>[] {
324
406
  const parameters: Record<string, any>[] = [...context.__endpointPathParameters];
325
407
  for (const parameter of operation.parameters) {
326
408
  switch (parameter.kind) {
327
409
  case "header":
328
- parameters.push(emitHttpHeaderParameter(context, parameter));
410
+ parameters.push(emitHttpHeaderParameter(context, parameter, method));
329
411
  break;
330
412
  case "query":
331
- parameters.push(emitHttpQueryParameter(context, parameter));
413
+ parameters.push(emitHttpQueryParameter(context, parameter, method));
332
414
  break;
333
415
  case "path":
334
416
  parameters.push(emitHttpPathParameter(context, parameter));
@@ -387,7 +469,12 @@ function emitHttpResponse(
387
469
  type,
388
470
  contentTypes: response.contentTypes,
389
471
  defaultContentType: response.defaultContentType ?? "application/json",
390
- resultProperty: method?.response.resultPath,
472
+ resultProperty: getWireNameWithDiagnostics(
473
+ context,
474
+ method?.response.resultSegments,
475
+ "invalid-lro-result",
476
+ method,
477
+ ),
391
478
  };
392
479
  }
393
480
 
@@ -83,6 +83,30 @@ const libDef = {
83
83
  default: "Can't generate Python SDK since no client defined in typespec file.",
84
84
  },
85
85
  },
86
+ "invalid-paging-items": {
87
+ severity: "warning",
88
+ messages: {
89
+ default: paramMessage`No valid paging items for operation '${"operationId"}'.`,
90
+ },
91
+ },
92
+ "invalid-next-link": {
93
+ severity: "warning",
94
+ messages: {
95
+ default: paramMessage`No valid next link for operation '${"operationId"}'.`,
96
+ },
97
+ },
98
+ "invalid-lro-result": {
99
+ severity: "warning",
100
+ messages: {
101
+ default: paramMessage`No valid LRO result for operation '${"operationId"}'.`,
102
+ },
103
+ },
104
+ "invalid-continuation-token": {
105
+ severity: "warning",
106
+ messages: {
107
+ default: paramMessage`No valid continuation token in '${"direction"}' for operation '${"operationId"}'.`,
108
+ },
109
+ },
86
110
  },
87
111
  emitter: {
88
112
  options: EmitterOptionsSchema as JSONSchemaType<PythonEmitterOptions>,
@@ -294,7 +294,7 @@ function emitModel<TServiceOperation extends SdkServiceOperation>(
294
294
  usage: type.usage,
295
295
  isXml: type.usage & UsageFlags.Xml ? true : false,
296
296
  xmlMetadata: getXmlMetadata(type),
297
- clientNamespace: getClientNamespace(context, type.clientNamespace),
297
+ clientNamespace: getClientNamespace(context, type.namespace),
298
298
  };
299
299
 
300
300
  typesMap.set(type, newValue);
@@ -364,7 +364,7 @@ function emitEnum<TServiceOperation extends SdkServiceOperation>(
364
364
  values,
365
365
  xmlMetadata: {},
366
366
  crossLanguageDefinitionId: type.crossLanguageDefinitionId,
367
- clientNamespace: getClientNamespace(context, type.clientNamespace),
367
+ clientNamespace: getClientNamespace(context, type.namespace),
368
368
  };
369
369
  for (const value of type.values) {
370
370
  newValue.values.push(emitEnumMember(value, newValue));
@@ -482,7 +482,7 @@ function emitUnion<TServiceOperation extends SdkServiceOperation>(
482
482
  type: "combined",
483
483
  types: type.variantTypes.map((x) => getType(context, x)),
484
484
  xmlMetadata: {},
485
- clientNamespace: getClientNamespace(context, type.clientNamespace),
485
+ clientNamespace: getClientNamespace(context, type.namespace),
486
486
  });
487
487
  }
488
488
 
@@ -1,4 +1,5 @@
1
1
  import {
2
+ InitializedByFlags,
2
3
  SdkHeaderParameter,
3
4
  SdkHttpParameter,
4
5
  SdkMethod,
@@ -7,6 +8,7 @@ import {
7
8
  SdkQueryParameter,
8
9
  SdkServiceMethod,
9
10
  SdkServiceOperation,
11
+ SdkServiceResponseHeader,
10
12
  SdkType,
11
13
  } from "@azure-tools/typespec-client-generator-core";
12
14
  import { getNamespaceFullName } from "@typespec/compiler";
@@ -150,6 +152,7 @@ type ParamBase = {
150
152
  inOverload: boolean;
151
153
  isApiVersion: boolean;
152
154
  type: Record<string, any>;
155
+ isContinuationToken: boolean;
153
156
  };
154
157
 
155
158
  export function getAddedOn<TServiceOperation extends SdkServiceOperation>(
@@ -160,15 +163,46 @@ export function getAddedOn<TServiceOperation extends SdkServiceOperation>(
160
163
  // if type is added in the first version of the client, we do not need to add the versioning info
161
164
  if (
162
165
  type.apiVersions[0] ===
163
- context.sdkPackage.clients.find((c) => c.initialization.access === "public")?.apiVersions[0]
166
+ context.sdkPackage.clients.find(
167
+ (c) => c.clientInitialization.initializedBy | InitializedByFlags.Individually,
168
+ )?.apiVersions[0]
164
169
  )
165
170
  return undefined;
166
171
  return type.apiVersions[0];
167
172
  }
168
173
 
174
+ export function isContinuationToken<TServiceOperation extends SdkServiceOperation>(
175
+ parameter: SdkParameter | SdkHttpParameter | SdkServiceResponseHeader,
176
+ method?: SdkServiceMethod<TServiceOperation>,
177
+ input: boolean = true,
178
+ ): boolean {
179
+ const parameterSegments =
180
+ method && method.kind === "paging"
181
+ ? method.pagingMetadata.continuationTokenParameterSegments
182
+ : undefined;
183
+ const responseSegments =
184
+ method && method.kind === "paging"
185
+ ? method.pagingMetadata.continuationTokenResponseSegments
186
+ : undefined;
187
+ if (!parameterSegments || !responseSegments) return false;
188
+ if (input) {
189
+ return Boolean(
190
+ parameterSegments &&
191
+ parameterSegments.length > 0 &&
192
+ (parameter.kind === "header" || parameter.kind === "query" || parameter.kind === "body") &&
193
+ parameterSegments.at(-1) === parameter.correspondingMethodParams.at(-1),
194
+ );
195
+ }
196
+
197
+ return Boolean(
198
+ responseSegments && responseSegments.length > 0 && responseSegments.at(-1) === parameter,
199
+ );
200
+ }
201
+
169
202
  export function emitParamBase<TServiceOperation extends SdkServiceOperation>(
170
203
  context: PythonSdkContext<TServiceOperation>,
171
204
  parameter: SdkParameter | SdkHttpParameter,
205
+ method?: SdkServiceMethod<TServiceOperation>,
172
206
  ): ParamBase {
173
207
  let type = getType(context, parameter.type);
174
208
  if (parameter.isApiVersionParam) {
@@ -187,6 +221,7 @@ export function emitParamBase<TServiceOperation extends SdkServiceOperation>(
187
221
  clientName: camelToSnakeCase(parameter.name),
188
222
  inOverload: false,
189
223
  isApiVersion: parameter.isApiVersionParam,
224
+ isContinuationToken: isContinuationToken(parameter, method),
190
225
  type,
191
226
  };
192
227
  }