geniespec 0.1.2.dev5__tar.gz → 0.1.2.dev6__tar.gz
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.
- {geniespec-0.1.2.dev5 → geniespec-0.1.2.dev6}/PKG-INFO +25 -1
- {geniespec-0.1.2.dev5 → geniespec-0.1.2.dev6}/README.md +24 -0
- {geniespec-0.1.2.dev5 → geniespec-0.1.2.dev6}/pyproject.toml +1 -1
- {geniespec-0.1.2.dev5 → geniespec-0.1.2.dev6}/pyproject.toml.orig +1 -1
- {geniespec-0.1.2.dev5 → geniespec-0.1.2.dev6}/src/geniespec/api_schema.yaml +32 -0
- {geniespec-0.1.2.dev5 → geniespec-0.1.2.dev6}/src/geniespec/genie.py +12 -1
- {geniespec-0.1.2.dev5 → geniespec-0.1.2.dev6}/src/geniespec/languages.py +70 -25
- {geniespec-0.1.2.dev5 → geniespec-0.1.2.dev6}/src/geniespec/renderable_types.py +8 -1
- {geniespec-0.1.2.dev5 → geniespec-0.1.2.dev6}/src/geniespec/templates/python-server/{service}/v{version}/api.py.j2 +8 -3
- {geniespec-0.1.2.dev5 → geniespec-0.1.2.dev6}/src/geniespec/templates/python-server/{service}/v{version}/endpoints.py.j2 +12 -6
- {geniespec-0.1.2.dev5 → geniespec-0.1.2.dev6}/src/geniespec/templates/python-server/{service}/v{version}/exceptions.py.j2 +11 -2
- {geniespec-0.1.2.dev5 → geniespec-0.1.2.dev6}/src/geniespec/templates/python-server/{service}/v{version}/internal.py.j2 +9 -3
- {geniespec-0.1.2.dev5 → geniespec-0.1.2.dev6}/src/geniespec/templates/python-server/{service}/v{version}/resources.py.j2 +9 -3
- {geniespec-0.1.2.dev5 → geniespec-0.1.2.dev6}/src/geniespec/templates/typescript-client/{service}/v{version}/client.ts.j2 +6 -0
- {geniespec-0.1.2.dev5 → geniespec-0.1.2.dev6}/src/geniespec/templates/typescript-client/{service}/v{version}/endpoints.ts.j2 +3 -1
- {geniespec-0.1.2.dev5 → geniespec-0.1.2.dev6}/src/geniespec/templates/typescript-client/{service}/v{version}/index.ts.j2 +2 -0
- {geniespec-0.1.2.dev5 → geniespec-0.1.2.dev6}/src/geniespec/templates/typescript-client/{service}/v{version}/resources.ts.j2 +5 -4
- {geniespec-0.1.2.dev5 → geniespec-0.1.2.dev6}/src/geniespec/types.py +8 -0
- {geniespec-0.1.2.dev5 → geniespec-0.1.2.dev6}/src/geniespec/__init__.py +0 -0
- {geniespec-0.1.2.dev5 → geniespec-0.1.2.dev6}/src/geniespec/cli.py +0 -0
- {geniespec-0.1.2.dev5 → geniespec-0.1.2.dev6}/src/geniespec/templates/python-server/{service}/__init__.py.j2 +0 -0
- {geniespec-0.1.2.dev5 → geniespec-0.1.2.dev6}/src/geniespec/templates/python-server/{service}/v{version}/__init__.py.j2 +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: geniespec
|
|
3
|
-
Version: 0.1.2.
|
|
3
|
+
Version: 0.1.2.dev6
|
|
4
4
|
Summary: Generate API code from an api.yaml spec
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Requires-Dist: jinja2>=3.1.6
|
|
@@ -93,6 +93,30 @@ transitions:
|
|
|
93
93
|
|
|
94
94
|
A rate limit needs at least one of `perMinute`, `perFiveMinutes`, `perHour` and `perDay`. `keyBy` is `user` (the default) or `ip`. Requests over any window are rejected with a 429. Limits keyed by `user` count per authenticated user, so a transition using one needs `authentication`. `python-server` writes each rate limit as a `RATE_LIMIT_<NAME>` kiba-core `RateLimitConfig` and passes it to `route`; keying by `ip` needs kiba-core's `OriginIpMiddleware`. `typescript-client` ignores rate limits.
|
|
95
95
|
|
|
96
|
+
## API docs
|
|
97
|
+
|
|
98
|
+
Transitions can set `operationId`, `summary`, `description`, `tags` and `extensions`, and attributes, request fields and response fields can set a `description`:
|
|
99
|
+
|
|
100
|
+
```yaml
|
|
101
|
+
resources:
|
|
102
|
+
- singleName: Member
|
|
103
|
+
collectionName: Members
|
|
104
|
+
attributes:
|
|
105
|
+
- name: member-id
|
|
106
|
+
type: Uuid
|
|
107
|
+
isPrimary: true
|
|
108
|
+
description: The member's id.
|
|
109
|
+
transitions:
|
|
110
|
+
- action: GET
|
|
111
|
+
operationId: getMember
|
|
112
|
+
summary: Get a member
|
|
113
|
+
description: Returns one member.
|
|
114
|
+
tags: [Members]
|
|
115
|
+
extensions: {deprecated: true}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
`python-server` passes the transition fields to kiba-core's `route`, whose `OpenApiSchemaGenerator` documents every route with an `operationId` (and skips the rest); `extensions` reach `OpenApiExtension`s as `metadata['extensions']`. Field descriptions become pydantic `Field(description=...)`, so they show up in the request and response schemas. Each `operationId` must be unique, so a transition that generates more than one route (for example `GET` on a resource with unique attributes) can't set one. `typescript-client` ignores these fields.
|
|
119
|
+
|
|
96
120
|
# Development
|
|
97
121
|
|
|
98
122
|
```
|
|
@@ -79,6 +79,30 @@ transitions:
|
|
|
79
79
|
|
|
80
80
|
A rate limit needs at least one of `perMinute`, `perFiveMinutes`, `perHour` and `perDay`. `keyBy` is `user` (the default) or `ip`. Requests over any window are rejected with a 429. Limits keyed by `user` count per authenticated user, so a transition using one needs `authentication`. `python-server` writes each rate limit as a `RATE_LIMIT_<NAME>` kiba-core `RateLimitConfig` and passes it to `route`; keying by `ip` needs kiba-core's `OriginIpMiddleware`. `typescript-client` ignores rate limits.
|
|
81
81
|
|
|
82
|
+
## API docs
|
|
83
|
+
|
|
84
|
+
Transitions can set `operationId`, `summary`, `description`, `tags` and `extensions`, and attributes, request fields and response fields can set a `description`:
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
resources:
|
|
88
|
+
- singleName: Member
|
|
89
|
+
collectionName: Members
|
|
90
|
+
attributes:
|
|
91
|
+
- name: member-id
|
|
92
|
+
type: Uuid
|
|
93
|
+
isPrimary: true
|
|
94
|
+
description: The member's id.
|
|
95
|
+
transitions:
|
|
96
|
+
- action: GET
|
|
97
|
+
operationId: getMember
|
|
98
|
+
summary: Get a member
|
|
99
|
+
description: Returns one member.
|
|
100
|
+
tags: [Members]
|
|
101
|
+
extensions: {deprecated: true}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`python-server` passes the transition fields to kiba-core's `route`, whose `OpenApiSchemaGenerator` documents every route with an `operationId` (and skips the rest); `extensions` reach `OpenApiExtension`s as `metadata['extensions']`. Field descriptions become pydantic `Field(description=...)`, so they show up in the request and response schemas. Each `operationId` must be unique, so a transition that generates more than one route (for example `GET` on a resource with unique attributes) can't set one. `typescript-client` ignores these fields.
|
|
105
|
+
|
|
82
106
|
# Development
|
|
83
107
|
|
|
84
108
|
```
|
|
@@ -158,6 +158,8 @@ definitions:
|
|
|
158
158
|
type: boolean
|
|
159
159
|
isDeprecated:
|
|
160
160
|
type: boolean
|
|
161
|
+
description:
|
|
162
|
+
type: string
|
|
161
163
|
defaultValue:
|
|
162
164
|
type:
|
|
163
165
|
- string
|
|
@@ -242,6 +244,19 @@ definitions:
|
|
|
242
244
|
- json
|
|
243
245
|
- stream
|
|
244
246
|
- redirect
|
|
247
|
+
operationId:
|
|
248
|
+
type: string
|
|
249
|
+
pattern: '^[A-Za-z][A-Za-z0-9_]*$'
|
|
250
|
+
summary:
|
|
251
|
+
type: string
|
|
252
|
+
description:
|
|
253
|
+
type: string
|
|
254
|
+
tags:
|
|
255
|
+
type: array
|
|
256
|
+
items:
|
|
257
|
+
type: string
|
|
258
|
+
extensions:
|
|
259
|
+
type: object
|
|
245
260
|
Transition:
|
|
246
261
|
type: object
|
|
247
262
|
additionalProperties: false
|
|
@@ -285,6 +300,19 @@ definitions:
|
|
|
285
300
|
- collection
|
|
286
301
|
isIndirectAction:
|
|
287
302
|
type: boolean
|
|
303
|
+
operationId:
|
|
304
|
+
type: string
|
|
305
|
+
pattern: '^[A-Za-z][A-Za-z0-9_]*$'
|
|
306
|
+
summary:
|
|
307
|
+
type: string
|
|
308
|
+
description:
|
|
309
|
+
type: string
|
|
310
|
+
tags:
|
|
311
|
+
type: array
|
|
312
|
+
items:
|
|
313
|
+
type: string
|
|
314
|
+
extensions:
|
|
315
|
+
type: object
|
|
288
316
|
RootTransition:
|
|
289
317
|
type: object
|
|
290
318
|
allOf:
|
|
@@ -328,6 +356,8 @@ definitions:
|
|
|
328
356
|
- 'null'
|
|
329
357
|
isDeprecated:
|
|
330
358
|
type: boolean
|
|
359
|
+
description:
|
|
360
|
+
type: string
|
|
331
361
|
maxValue:
|
|
332
362
|
type:
|
|
333
363
|
- string
|
|
@@ -374,6 +404,8 @@ definitions:
|
|
|
374
404
|
type: boolean
|
|
375
405
|
isList:
|
|
376
406
|
type: boolean
|
|
407
|
+
description:
|
|
408
|
+
type: string
|
|
377
409
|
Exception:
|
|
378
410
|
type: object
|
|
379
411
|
additionalProperties: false
|
|
@@ -199,6 +199,7 @@ class Renderer:
|
|
|
199
199
|
isRequired=requestField.isRequired,
|
|
200
200
|
isDeprecated=requestField.isDeprecated,
|
|
201
201
|
source=requestField.source,
|
|
202
|
+
description=requestField.description,
|
|
202
203
|
)
|
|
203
204
|
|
|
204
205
|
def _response_field_to_renderable_field(self, responseField: ResponseField, knownTypeNames: dict[str, str]) -> RenderableField:
|
|
@@ -214,6 +215,7 @@ class Renderer:
|
|
|
214
215
|
mappingValueTypes=responseField.mappingValueTypes,
|
|
215
216
|
enumValues=responseField.enumValues,
|
|
216
217
|
),
|
|
218
|
+
description=responseField.description,
|
|
217
219
|
)
|
|
218
220
|
|
|
219
221
|
def _attribute_to_renderable_attribute(self, attribute: Attribute, knownTypeNames: dict[str, str], isNullable: bool | None = None) -> RenderableAttribute:
|
|
@@ -236,6 +238,7 @@ class Renderer:
|
|
|
236
238
|
isAccessibleIndependently=attribute.isAccessibleIndependently,
|
|
237
239
|
isSettable=attribute.isSettable,
|
|
238
240
|
isNullable=attribute.isNullable,
|
|
241
|
+
description=attribute.description,
|
|
239
242
|
)
|
|
240
243
|
|
|
241
244
|
def _attribute_to_renderable_parameter(self, attribute: Attribute, isRequired: bool, knownTypeNames: dict[str, str], shouldUseDefault: bool = True) -> RenderableParameter:
|
|
@@ -254,8 +257,13 @@ class Renderer:
|
|
|
254
257
|
defaultValue=attribute.defaultValue if shouldUseDefault else None,
|
|
255
258
|
isRequired=isRequired,
|
|
256
259
|
isDeprecated=attribute.isDeprecated,
|
|
260
|
+
description=attribute.description,
|
|
257
261
|
)
|
|
258
262
|
|
|
263
|
+
@staticmethod
|
|
264
|
+
def _operation_documentation(transition: BaseTransition) -> dict[str, object]:
|
|
265
|
+
return {'operationId': transition.operationId, 'summary': transition.summary, 'description': transition.description, 'tags': transition.tags, 'extensions': transition.extensions}
|
|
266
|
+
|
|
259
267
|
@staticmethod
|
|
260
268
|
def _resource_full_name(resource: Resource, name: str) -> str:
|
|
261
269
|
return f'{resource.parentResource or ""}{name}'
|
|
@@ -458,7 +466,7 @@ class Renderer:
|
|
|
458
466
|
knownTypeNames=knownTypeNames,
|
|
459
467
|
defaultAuthentication=api.defaultAuthentication,
|
|
460
468
|
defaultRateLimit=api.defaultRateLimit,
|
|
461
|
-
)
|
|
469
|
+
).model_copy(update=self._operation_documentation(transition=transition))
|
|
462
470
|
for transition in api.transitions
|
|
463
471
|
]
|
|
464
472
|
renderableExceptions = [
|
|
@@ -564,6 +572,7 @@ class Renderer:
|
|
|
564
572
|
for endpoint in customEndpoints:
|
|
565
573
|
customEndpoint = RenderableEndpoint(components=[*endpoint.components, ActionEndpointComponent(actionName=RenderableString.from_separated_string(string=transition.action, separator='-'))])
|
|
566
574
|
renderableApi.operations.append(self._action_operation(transition=transition, endpoint=customEndpoint, knownTypeNames=knownTypeNames, defaultAuthentication=api.defaultAuthentication, defaultRateLimit=api.defaultRateLimit, isIndirectAction=transition.isIndirectAction))
|
|
575
|
+
renderableApi.operations[operationCount:] = [operation.model_copy(update=self._operation_documentation(transition=transition)) for operation in renderableApi.operations[operationCount:]]
|
|
567
576
|
if len(renderableApi.operations) == operationCount:
|
|
568
577
|
uncoveredTransitions.append(f'{renderableResource.singleName.pascalCaseString} {transition.action}')
|
|
569
578
|
if uncoveredTransitions:
|
|
@@ -573,6 +582,8 @@ class Renderer:
|
|
|
573
582
|
rateLimitsByName = {rateLimit.name.kebabCaseString: rateLimit for rateLimit in renderableApi.rateLimits}
|
|
574
583
|
operationErrors = [error for operation in renderableApi.operations for error in self._operation_errors(operation=operation, rateLimitsByName=rateLimitsByName)]
|
|
575
584
|
operationErrors += self._file_errors(resources=renderableResources, exceptions=renderableExceptions)
|
|
585
|
+
operationIdCounts = collections.Counter(operation.operationId for operation in renderableApi.operations if operation.operationId is not None)
|
|
586
|
+
operationErrors += [f'Duplicate operationId: {operationId}' for operationId, count in operationIdCounts.items() if count > 1]
|
|
576
587
|
if operationErrors:
|
|
577
588
|
raise SemanticValidationException(message='\n'.join(operationErrors))
|
|
578
589
|
renderableApi.resources.extend(renderableResources)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import collections
|
|
2
2
|
import keyword
|
|
3
3
|
import re
|
|
4
|
+
import typing
|
|
4
5
|
|
|
5
6
|
from core.exceptions import KibaException
|
|
6
7
|
|
|
@@ -8,6 +9,7 @@ from geniespec.renderable_types import ActionEndpointComponent
|
|
|
8
9
|
from geniespec.renderable_types import AttributeEndpointComponent
|
|
9
10
|
from geniespec.renderable_types import MappingRenderableType
|
|
10
11
|
from geniespec.renderable_types import RenderableApi
|
|
12
|
+
from geniespec.renderable_types import RenderableAttribute
|
|
11
13
|
from geniespec.renderable_types import RenderableEndpointComponent
|
|
12
14
|
from geniespec.renderable_types import RenderableEnum
|
|
13
15
|
from geniespec.renderable_types import RenderableException
|
|
@@ -405,6 +407,43 @@ class PythonLanguageDefinition(BaseLanguageDefinition):
|
|
|
405
407
|
def requires_optional_field_default(self, api: RenderableApi) -> bool:
|
|
406
408
|
return any(not self.is_undefined_parameter(operation=operation, parameter=parameter) and not parameter.isRequired and not parameter.fieldType.isNullable for operation in api.operations for parameter in self.request_parameters(operation=operation))
|
|
407
409
|
|
|
410
|
+
def endpoints_require_field(self, api: RenderableApi) -> bool:
|
|
411
|
+
hasRequestDescriptions = any(parameter.description is not None for operation in api.operations for parameter in self.request_parameters(operation=operation))
|
|
412
|
+
hasResponseDescriptions = any(field.description is not None for operation in api.operations for field in operation.responseFields or [])
|
|
413
|
+
return self.requires_optional_field_default(api=api) or hasRequestDescriptions or hasResponseDescriptions
|
|
414
|
+
|
|
415
|
+
@staticmethod
|
|
416
|
+
def resources_require_field(api: RenderableApi) -> bool:
|
|
417
|
+
return any(attribute.description is not None for resource in api.resources for attribute in resource.attributes)
|
|
418
|
+
|
|
419
|
+
@staticmethod
|
|
420
|
+
def _field_default(default: str | None, description: str | None, fieldFunction: str = 'Field', shouldUseField: bool = False) -> str:
|
|
421
|
+
if description is None and not shouldUseField:
|
|
422
|
+
return '' if default is None else f' = {default}'
|
|
423
|
+
arguments = ([] if default is None else [f'default={default}']) + ([] if description is None else [f'description={description!r}'])
|
|
424
|
+
return f' = {fieldFunction}({", ".join(arguments)})'
|
|
425
|
+
|
|
426
|
+
def request_field_declaration(self, operation: RenderableOperation, parameter: RenderableParameter, apiVersion: Version) -> str:
|
|
427
|
+
typeName = self.type(renderableType=parameter.fieldType, apiVersion=apiVersion)
|
|
428
|
+
if self.is_undefined_parameter(operation=operation, parameter=parameter):
|
|
429
|
+
return f'UpdateValue[{typeName}]{self._field_default(default="UNDEFINED", description=parameter.description)}'
|
|
430
|
+
if parameter.isRequired:
|
|
431
|
+
return f'{typeName}{self._field_default(default=None, description=parameter.description)}'
|
|
432
|
+
if parameter.fieldType.isNullable:
|
|
433
|
+
return f'{typeName}{self._field_default(default="None", description=parameter.description)}'
|
|
434
|
+
return f'{typeName}{self._field_default(default=f"typing.cast({typeName}, None)", description=parameter.description, shouldUseField=True)}'
|
|
435
|
+
|
|
436
|
+
def response_field_declaration(self, field: RenderableField, apiVersion: Version) -> str:
|
|
437
|
+
return f'{self.type(renderableType=field.fieldType, apiVersion=apiVersion)}{self._field_default(default=None, description=field.description)}'
|
|
438
|
+
|
|
439
|
+
def attribute_declaration(self, attribute: RenderableAttribute, apiVersion: Version) -> str:
|
|
440
|
+
return f'{self.type(renderableType=attribute.fieldType, apiVersion=apiVersion, isLocalReference=True)}{self._field_default(default=None, description=attribute.description, fieldFunction="pydantic.Field")}'
|
|
441
|
+
|
|
442
|
+
@staticmethod
|
|
443
|
+
def route_documentation_arguments(operation: RenderableOperation) -> str:
|
|
444
|
+
arguments = {'operationId': operation.operationId, 'summary': operation.summary, 'description': operation.description, 'tags': operation.tags or None, 'extensions': operation.extensions or None}
|
|
445
|
+
return ''.join(f', {name}={value!r}' for name, value in arguments.items() if value is not None)
|
|
446
|
+
|
|
408
447
|
def requires_streaming(self, api: RenderableApi) -> bool:
|
|
409
448
|
return any(self.is_streaming(operation=operation) for operation in api.operations)
|
|
410
449
|
|
|
@@ -426,25 +465,32 @@ class PythonLanguageDefinition(BaseLanguageDefinition):
|
|
|
426
465
|
return parameterName if self.requires_parameter_preprocessing(operation=operation, parameter=parameter) else f'request.data.{parameterName}'
|
|
427
466
|
|
|
428
467
|
@staticmethod
|
|
429
|
-
def
|
|
430
|
-
if isinstance(renderableType, MappingRenderableType):
|
|
431
|
-
return PythonLanguageDefinition._contains_json(renderableType=renderableType.keyType) or PythonLanguageDefinition._contains_json(renderableType=renderableType.valueType)
|
|
432
|
-
return renderableType.rawType == PrimitiveTypes.JSON
|
|
433
|
-
|
|
434
|
-
@staticmethod
|
|
435
|
-
def _contains_uuid(renderableType: RenderableType) -> bool:
|
|
468
|
+
def _type_parts(renderableType: RenderableType) -> list[RenderableType]:
|
|
436
469
|
if isinstance(renderableType, MappingRenderableType):
|
|
437
|
-
return PythonLanguageDefinition.
|
|
438
|
-
return renderableType
|
|
439
|
-
|
|
440
|
-
def
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
470
|
+
return [renderableType, *PythonLanguageDefinition._type_parts(renderableType=renderableType.keyType), *PythonLanguageDefinition._type_parts(renderableType=renderableType.valueType)]
|
|
471
|
+
return [renderableType]
|
|
472
|
+
|
|
473
|
+
def _module_field_types(self, api: RenderableApi, module: str) -> list[RenderableType]:
|
|
474
|
+
responseFieldTypes = [field.fieldType for operation in api.operations for field in operation.responseFields or []]
|
|
475
|
+
if module == 'resources':
|
|
476
|
+
return [attribute.fieldType for resource in api.resources for attribute in resource.attributes]
|
|
477
|
+
if module == 'endpoints':
|
|
478
|
+
return [parameter.fieldType for operation in api.operations for parameter in self.request_parameters(operation=operation)] + responseFieldTypes
|
|
479
|
+
if module == 'internal':
|
|
480
|
+
return [parameter.fieldType for operation in api.operations for parameter in operation.parameters] + responseFieldTypes
|
|
481
|
+
if module == 'exceptions':
|
|
482
|
+
return [field.fieldType for exception in api.exceptions for field in exception.fields]
|
|
483
|
+
raise KibaException(message=f'Unknown module: {module}')
|
|
484
|
+
|
|
485
|
+
def module_requires(self, api: RenderableApi, module: str, requirement: str) -> bool:
|
|
486
|
+
predicates: dict[str, typing.Callable[[RenderableType], bool]] = {
|
|
487
|
+
'collections': lambda part: part.isList or isinstance(part, MappingRenderableType),
|
|
488
|
+
'datetime': lambda part: part.rawType in (PrimitiveTypes.DATETIME, PrimitiveTypes.DATE),
|
|
489
|
+
'json': lambda part: part.rawType == PrimitiveTypes.JSON,
|
|
490
|
+
'resources': lambda part: part.className is not None,
|
|
491
|
+
'uuid': lambda part: part.rawType == PrimitiveTypes.UUID,
|
|
492
|
+
}
|
|
493
|
+
return any(predicates[requirement](part) for fieldType in self._module_field_types(api=api, module=module) for part in self._type_parts(renderableType=fieldType))
|
|
448
494
|
|
|
449
495
|
def resources_require_typing(self, api: RenderableApi) -> bool:
|
|
450
496
|
return len(api.enums) > 0 or len(api.oneOfs) > 0 or any(self.is_inline_enum(renderableType=attribute.fieldType) for resource in api.resources for attribute in resource.attributes)
|
|
@@ -459,13 +505,6 @@ class PythonLanguageDefinition(BaseLanguageDefinition):
|
|
|
459
505
|
def requires_uploaded_file(self, api: RenderableApi) -> bool:
|
|
460
506
|
return any(self.is_file_operation(operation=operation) for operation in api.operations)
|
|
461
507
|
|
|
462
|
-
def operations_require_json(self, api: RenderableApi) -> bool:
|
|
463
|
-
fieldTypes = [parameter.fieldType for operation in api.operations for parameter in operation.parameters] + [field.fieldType for operation in api.operations for field in operation.responseFields or []]
|
|
464
|
-
return any(self._contains_json(renderableType=fieldType) for fieldType in fieldTypes)
|
|
465
|
-
|
|
466
|
-
def exceptions_require_json(self, api: RenderableApi) -> bool:
|
|
467
|
-
return any(self._contains_json(renderableType=field.fieldType) for exception in api.exceptions for field in exception.fields)
|
|
468
|
-
|
|
469
508
|
def route_ordered_operations(self, api: RenderableApi) -> list[RenderableOperation]:
|
|
470
509
|
return sorted(api.operations, key=lambda operation: tuple(segment.startswith('{') for segment in self.endpoint(operation=operation).split('/')))
|
|
471
510
|
|
|
@@ -672,6 +711,12 @@ class TypeScriptLanguageDefinition(BaseLanguageDefinition):
|
|
|
672
711
|
def requires_request_data(self, api: RenderableApi) -> bool:
|
|
673
712
|
return any(self.body_parameters(operation=operation) and not self.is_file_operation(operation=operation) for operation in self.client_operations(api=api))
|
|
674
713
|
|
|
714
|
+
def requires_rest_method(self, api: RenderableApi) -> bool:
|
|
715
|
+
return any(not self.is_file_operation(operation=operation) for operation in self.client_operations(api=api))
|
|
716
|
+
|
|
717
|
+
def has_endpoint_classes(self, api: RenderableApi) -> bool:
|
|
718
|
+
return len(self.body_parameter_types(api=api)) > 0 or len(self.response_field_types(api=api)) > 0
|
|
719
|
+
|
|
675
720
|
def form_value(self, renderableType: RenderableType, value: str) -> str:
|
|
676
721
|
if renderableType.rawType == RenderableRawTypes.FILE:
|
|
677
722
|
return value
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
|
|
5
|
+
import pydantic
|
|
5
6
|
from core.exceptions import KibaException
|
|
6
7
|
from pydantic import BaseModel
|
|
7
8
|
|
|
@@ -128,6 +129,7 @@ class RenderableField(BaseModel):
|
|
|
128
129
|
name: RenderableString
|
|
129
130
|
fieldType: RenderableType
|
|
130
131
|
defaultValue: str | int | float | bool | None = None
|
|
132
|
+
description: str | None = None
|
|
131
133
|
|
|
132
134
|
|
|
133
135
|
class RenderableAttribute(RenderableField):
|
|
@@ -191,7 +193,7 @@ class RenderableEndpoint(BaseModel):
|
|
|
191
193
|
@property
|
|
192
194
|
def urlParameters(self) -> list[RenderableParameter]: # noqa: N802
|
|
193
195
|
return [
|
|
194
|
-
RenderableParameter(name=component.attribute.name, fieldType=component.attribute.fieldType, isRequired=True, isDeprecated=False)
|
|
196
|
+
RenderableParameter(name=component.attribute.name, fieldType=component.attribute.fieldType, isRequired=True, isDeprecated=False, description=component.attribute.description)
|
|
195
197
|
for component in self.components
|
|
196
198
|
if isinstance(component, ResourceEndpointComponent) and component.attribute is not None
|
|
197
199
|
]
|
|
@@ -215,6 +217,11 @@ class RenderableOperation(BaseModel):
|
|
|
215
217
|
rateLimit: str | None = None
|
|
216
218
|
isIndirectAction: bool = False
|
|
217
219
|
responseType: str = ResponseTypes.JSON
|
|
220
|
+
operationId: str | None = None
|
|
221
|
+
summary: str | None = None
|
|
222
|
+
description: str | None = None
|
|
223
|
+
tags: list[str] = []
|
|
224
|
+
extensions: dict[str, pydantic.JsonValue] = {}
|
|
218
225
|
|
|
219
226
|
@property
|
|
220
227
|
def urlParameters(self) -> list[RenderableParameter]: # noqa: N802
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
# ruff: noqa: ARG001
|
|
3
3
|
{% if target.requires_streaming(api=api) %}
|
|
4
4
|
import collections.abc
|
|
5
|
-
{% endif %}
|
|
6
|
-
import datetime
|
|
7
5
|
|
|
6
|
+
{% endif %}
|
|
7
|
+
{% if api.operations %}
|
|
8
8
|
from core.api.api_request import KibaApiRequest
|
|
9
9
|
from core.api.route import route
|
|
10
|
+
{% endif %}
|
|
10
11
|
from core.api.route_auth import RouteAuthResolver
|
|
11
12
|
{% if api.rateLimits %}
|
|
12
13
|
from core.api.route_metadata import RateLimitConfig
|
|
@@ -14,10 +15,14 @@ from core.api.route_metadata import RateLimitConfig
|
|
|
14
15
|
{% if target.requires_redirect(api=api) %}
|
|
15
16
|
from core.exceptions import FoundRedirectException
|
|
16
17
|
{% endif %}
|
|
18
|
+
{% if api.operations %}
|
|
17
19
|
from core.http.rest_method import RestMethod
|
|
20
|
+
{% endif %}
|
|
18
21
|
from starlette.routing import BaseRoute
|
|
22
|
+
{% if api.operations %}
|
|
19
23
|
from starlette.routing import Route
|
|
20
24
|
from . import endpoints
|
|
25
|
+
{% endif %}
|
|
21
26
|
from .internal import {{ target.internal_class_name(api=api) }}
|
|
22
27
|
{% for rateLimit in api.rateLimits %}
|
|
23
28
|
{{ target.rate_limit_constant_name(name=rateLimit.name.kebabCaseString) }}: RateLimitConfig = {{ target.rate_limit_config(rateLimit=rateLimit) }}
|
|
@@ -30,7 +35,7 @@ def create_routes(internal: {{ target.internal_class_name(api=api) }}, {% if tar
|
|
|
30
35
|
{% set rate_limit = target.route_rate_limit(operation=operation) %}
|
|
31
36
|
{% set class_name = target.operation_class_name(operation=operation) %}
|
|
32
37
|
{% set result_name = target.identifier(name=operation.responseFields[0].name) if operation.responseFields else 'result' %}
|
|
33
|
-
@route(requestType=endpoints.{{ class_name }}Request, responseType=endpoints.{{ class_name }}Response{% if target.is_streaming(operation=operation) %}, isStreaming=True{% endif %}{% if auth is not none %}, authResolver=authResolver, auth='{{ auth }}'{% endif %}{% if rate_limit is not none %}, rateLimit={{ target.rate_limit_constant_name(name=rate_limit) }}{% endif %})
|
|
38
|
+
@route(requestType=endpoints.{{ class_name }}Request, responseType=endpoints.{{ class_name }}Response{% if target.is_streaming(operation=operation) %}, isStreaming=True{% endif %}{% if auth is not none %}, authResolver=authResolver, auth='{{ auth }}'{% endif %}{% if rate_limit is not none %}, rateLimit={{ target.rate_limit_constant_name(name=rate_limit) }}{% endif %}{{ target.route_documentation_arguments(operation=operation) }})
|
|
34
39
|
async def {{ target.operation_name(operation=operation) }}(request: KibaApiRequest[endpoints.{{ class_name }}Request]) -> {% if target.is_streaming(operation=operation) %}collections.abc.AsyncIterator[endpoints.{{ class_name }}Response]{% else %}endpoints.{{ class_name }}Response{% endif %}:
|
|
35
40
|
{% for parameter in target.request_parameters(operation=operation) %}
|
|
36
41
|
{% set param_name = target.identifier(name=parameter.name) %}
|
|
@@ -1,19 +1,23 @@
|
|
|
1
1
|
{{ autogenerated_notice }}
|
|
2
|
+
{% if target.module_requires(api=api, module='endpoints', requirement='collections') %}
|
|
2
3
|
import collections.abc
|
|
4
|
+
{% endif %}
|
|
5
|
+
{% if target.module_requires(api=api, module='endpoints', requirement='datetime') %}
|
|
3
6
|
import datetime
|
|
7
|
+
{% endif %}
|
|
4
8
|
{% if target.requires_optional_field_default(api=api) or target.operations_require_typing(api=api) %}
|
|
5
9
|
import typing
|
|
6
10
|
{% endif %}
|
|
7
|
-
{% if target.
|
|
11
|
+
{% if target.module_requires(api=api, module='endpoints', requirement='uuid') %}
|
|
8
12
|
import uuid
|
|
9
13
|
{% endif %}
|
|
10
14
|
|
|
11
|
-
{% if target.
|
|
15
|
+
{% if target.module_requires(api=api, module='endpoints', requirement='json') %}
|
|
12
16
|
import pydantic
|
|
13
17
|
{% endif %}
|
|
14
|
-
{% if target.
|
|
18
|
+
{% if target.endpoints_require_field(api=api) %}
|
|
15
19
|
from pydantic import BaseModel, Field
|
|
16
|
-
{%
|
|
20
|
+
{% elif api.operations %}
|
|
17
21
|
from pydantic import BaseModel
|
|
18
22
|
{% endif %}
|
|
19
23
|
{% if target.requires_undefined(api=api) %}
|
|
@@ -22,21 +26,23 @@ from core.util.typing_util import UNDEFINED, UpdateValue
|
|
|
22
26
|
{% if target.requires_uploaded_file(api=api) %}
|
|
23
27
|
from core.api.uploaded_file import UploadedFile
|
|
24
28
|
{% endif %}
|
|
29
|
+
{% if target.module_requires(api=api, module='endpoints', requirement='resources') %}
|
|
25
30
|
|
|
26
31
|
from . import resources
|
|
32
|
+
{% endif %}
|
|
27
33
|
|
|
28
34
|
{% for operation in api.operations %}
|
|
29
35
|
|
|
30
36
|
class {{ target.operation_class_name(operation=operation) }}Request(BaseModel):
|
|
31
37
|
{% for parameter in target.request_parameters(operation=operation) %}
|
|
32
|
-
{{ target.identifier(name=parameter.name) }}: {
|
|
38
|
+
{{ target.identifier(name=parameter.name) }}: {{ target.request_field_declaration(operation=operation, parameter=parameter, apiVersion=api.version) }}
|
|
33
39
|
{% else %}
|
|
34
40
|
pass
|
|
35
41
|
{% endfor %}
|
|
36
42
|
|
|
37
43
|
class {{ target.operation_class_name(operation=operation) }}Response(BaseModel):
|
|
38
44
|
{% if operation.responseFields and operation.responseFields | length > 0 %}
|
|
39
|
-
{{ target.identifier(name=operation.responseFields[0].name) }}: {{ target.
|
|
45
|
+
{{ target.identifier(name=operation.responseFields[0].name) }}: {{ target.response_field_declaration(field=operation.responseFields[0], apiVersion=api.version) }}
|
|
40
46
|
{% else %}
|
|
41
47
|
pass
|
|
42
48
|
{% endif %}
|
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
{{ autogenerated_notice }}
|
|
2
2
|
from __future__ import annotations
|
|
3
|
+
{% if target.module_requires(api=api, module='exceptions', requirement='collections') %}
|
|
3
4
|
import collections.abc
|
|
5
|
+
{% endif %}
|
|
6
|
+
{% if target.module_requires(api=api, module='exceptions', requirement='datetime') %}
|
|
7
|
+
import datetime
|
|
8
|
+
{% endif %}
|
|
4
9
|
import typing
|
|
5
|
-
{% if target.
|
|
10
|
+
{% if target.module_requires(api=api, module='exceptions', requirement='uuid') %}
|
|
6
11
|
import uuid
|
|
7
12
|
{% endif %}
|
|
8
|
-
{% if target.
|
|
13
|
+
{% if target.module_requires(api=api, module='exceptions', requirement='json') %}
|
|
9
14
|
import pydantic
|
|
10
15
|
{% endif %}
|
|
16
|
+
{% if api.exceptions %}
|
|
11
17
|
from core import exceptions
|
|
12
18
|
from core.util.typing_util import Json
|
|
19
|
+
{% endif %}
|
|
20
|
+
{% if target.module_requires(api=api, module='exceptions', requirement='resources') %}
|
|
13
21
|
from . import resources
|
|
22
|
+
{% endif %}
|
|
14
23
|
|
|
15
24
|
|
|
16
25
|
class KibaExceptionTypedDict(typing.TypedDict):
|
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
{{ autogenerated_notice }}
|
|
2
2
|
import abc
|
|
3
|
-
|
|
3
|
+
{% if target.module_requires(api=api, module='internal', requirement='collections') or target.requires_streaming(api=api) %}
|
|
4
4
|
import collections.abc
|
|
5
|
+
{% endif %}
|
|
6
|
+
{% if target.module_requires(api=api, module='internal', requirement='datetime') %}
|
|
7
|
+
import datetime
|
|
8
|
+
{% endif %}
|
|
5
9
|
{% if target.operations_require_typing(api=api) %}
|
|
6
10
|
import typing
|
|
7
11
|
{% endif %}
|
|
8
|
-
{% if target.
|
|
12
|
+
{% if target.module_requires(api=api, module='internal', requirement='uuid') %}
|
|
9
13
|
import uuid
|
|
10
14
|
{% endif %}
|
|
11
15
|
|
|
12
|
-
{% if target.
|
|
16
|
+
{% if target.module_requires(api=api, module='internal', requirement='json') %}
|
|
13
17
|
import pydantic
|
|
14
18
|
|
|
15
19
|
{% endif %}
|
|
20
|
+
{% if target.module_requires(api=api, module='internal', requirement='resources') %}
|
|
16
21
|
from . import resources
|
|
22
|
+
{% endif %}
|
|
17
23
|
{% if target.requires_undefined(api=api) %}
|
|
18
24
|
from core.util.typing_util import UNDEFINED, UpdateValue
|
|
19
25
|
{% endif %}
|
|
@@ -1,19 +1,25 @@
|
|
|
1
1
|
{{ autogenerated_notice }}
|
|
2
2
|
from __future__ import annotations
|
|
3
3
|
|
|
4
|
+
{% if target.module_requires(api=api, module='resources', requirement='collections') %}
|
|
4
5
|
import collections.abc
|
|
6
|
+
{% endif %}
|
|
7
|
+
{% if target.module_requires(api=api, module='resources', requirement='datetime') %}
|
|
5
8
|
import datetime
|
|
9
|
+
{% endif %}
|
|
6
10
|
{% if target.resources_require_typing(api=api) %}
|
|
7
11
|
import typing
|
|
8
12
|
{% endif %}
|
|
9
|
-
{% if target.
|
|
13
|
+
{% if target.module_requires(api=api, module='resources', requirement='uuid') %}
|
|
10
14
|
import uuid
|
|
11
15
|
{% endif %}
|
|
12
16
|
|
|
13
|
-
{% if target.
|
|
17
|
+
{% if target.module_requires(api=api, module='resources', requirement='json') or api.oneOfs or target.resources_require_field(api=api) %}
|
|
14
18
|
import pydantic
|
|
15
19
|
{% endif %}
|
|
20
|
+
{% if api.resources %}
|
|
16
21
|
from pydantic import BaseModel
|
|
22
|
+
{% endif %}
|
|
17
23
|
|
|
18
24
|
{% for enum in api.enums %}
|
|
19
25
|
{{ target.enum_class_name(enum=enum, apiVersion=api.version) }} = {{ target.enum_literal(values=enum.values) }}
|
|
@@ -28,7 +34,7 @@ class {{ target.resource_class_name(resource=resource, apiVersion=api.version) }
|
|
|
28
34
|
pass
|
|
29
35
|
{% endif %}
|
|
30
36
|
{% for attribute in resource.attributes %}
|
|
31
|
-
{{ target.identifier(name=attribute.name) }}: {{ target.
|
|
37
|
+
{{ target.identifier(name=attribute.name) }}: {{ target.attribute_declaration(attribute=attribute, apiVersion=api.version) }}
|
|
32
38
|
{% endfor %}
|
|
33
39
|
|
|
34
40
|
{% endfor %}
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
{{ autogenerated_notice }}
|
|
2
|
+
{% if target.requires_rest_method(api=api) %}
|
|
2
3
|
import { RestMethod, ServiceClient } from '@kibalabs/core';
|
|
4
|
+
{% else %}
|
|
5
|
+
import { ServiceClient } from '@kibalabs/core';
|
|
6
|
+
{% endif %}
|
|
7
|
+
{% if target.has_endpoint_classes(api=api) %}
|
|
3
8
|
|
|
4
9
|
import * as Endpoints from './endpoints';
|
|
10
|
+
{% endif %}
|
|
5
11
|
{% if target.references_resources(renderableTypes=target.response_field_types(api=api)) %}
|
|
6
12
|
import * as Resources from './resources';
|
|
7
13
|
{% endif %}
|
|
@@ -4,10 +4,12 @@
|
|
|
4
4
|
{% set coreImports = [] %}
|
|
5
5
|
{% if target.requires_date_parsing(renderableTypes=responseTypes) %}{% set _ = coreImports.append('dateFromString') %}{% endif %}
|
|
6
6
|
{% if target.requires_date_parsing(renderableTypes=bodyTypes) %}{% set _ = coreImports.append('dateToString') %}{% endif %}
|
|
7
|
-
{% set _ = coreImports.append('RawObject') %}
|
|
7
|
+
{% if target.requires_request_data(api=api) or responseTypes %}{% set _ = coreImports.append('RawObject') %}{% endif %}
|
|
8
8
|
{% if target.requires_request_data(api=api) %}{% set _ = coreImports.append('RequestData') %}{% endif %}
|
|
9
9
|
{% if responseTypes %}{% set _ = coreImports.append('ResponseData') %}{% endif %}
|
|
10
|
+
{% if coreImports %}
|
|
10
11
|
import { {{ coreImports | join(', ') }} } from '@kibalabs/core';
|
|
12
|
+
{% endif %}
|
|
11
13
|
{% if target.references_resources(renderableTypes=responseTypes + bodyTypes) %}
|
|
12
14
|
|
|
13
15
|
import * as Resources from './resources';
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{{ autogenerated_notice }}
|
|
2
|
-
{%
|
|
3
|
-
|
|
4
|
-
{%
|
|
5
|
-
|
|
2
|
+
{% set coreImports = [] %}
|
|
3
|
+
{% if target.requires_date_parsing(renderableTypes=target.resource_field_types(api=api)) %}{% set _ = coreImports.extend(['dateFromString', 'dateToString']) %}{% endif %}
|
|
4
|
+
{% if api.resources or api.oneOfs %}{% set _ = coreImports.append('RawObject') %}{% endif %}
|
|
5
|
+
{% if coreImports %}
|
|
6
|
+
import { {{ coreImports | join(', ') }} } from '@kibalabs/core';
|
|
6
7
|
{% endif %}
|
|
7
8
|
|
|
8
9
|
export type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };
|
|
@@ -85,6 +85,7 @@ class Attribute(BaseModel):
|
|
|
85
85
|
isSettable: bool = False
|
|
86
86
|
isAccessibleIndependently: bool = False
|
|
87
87
|
defaultValue: str | int | float | bool | None = None
|
|
88
|
+
description: str | None = None
|
|
88
89
|
|
|
89
90
|
|
|
90
91
|
class ResponseField(BaseModel):
|
|
@@ -96,6 +97,7 @@ class ResponseField(BaseModel):
|
|
|
96
97
|
enumValues: list[str] = []
|
|
97
98
|
isNullable: bool = False
|
|
98
99
|
isList: bool = False
|
|
100
|
+
description: str | None = None
|
|
99
101
|
|
|
100
102
|
|
|
101
103
|
class RequestField(BaseModel):
|
|
@@ -111,6 +113,7 @@ class RequestField(BaseModel):
|
|
|
111
113
|
maxValue: str | int | float | None = None
|
|
112
114
|
minValue: str | int | float | None = None
|
|
113
115
|
source: str = FieldSources.REQUEST
|
|
116
|
+
description: str | None = None
|
|
114
117
|
|
|
115
118
|
|
|
116
119
|
class BaseTransition(BaseModel):
|
|
@@ -121,6 +124,11 @@ class BaseTransition(BaseModel):
|
|
|
121
124
|
authentication: str | None = None
|
|
122
125
|
rateLimit: str | None = None
|
|
123
126
|
responseType: str = ResponseTypes.JSON
|
|
127
|
+
operationId: str | None = None
|
|
128
|
+
summary: str | None = None
|
|
129
|
+
description: str | None = None
|
|
130
|
+
tags: list[str] = []
|
|
131
|
+
extensions: dict[str, pydantic.JsonValue] = {}
|
|
124
132
|
|
|
125
133
|
|
|
126
134
|
class Transition(BaseTransition):
|
|
File without changes
|
|
File without changes
|
|
File without changes
|