Skip to content

Commit 4bdf96d

Browse files
committed
Add API docstrings to generated Python code
1 parent 8394b5a commit 4bdf96d

79 files changed

Lines changed: 8333 additions & 12 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

codegen/layouts/partials/abstract-route-class.hbs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
class {{className}}(abc.ABC):
2+
{{#if docstring}}
3+
"""{{{docstring}}}"""
4+
{{/if}}
25
{{#if showPass}}
36
pass
47
{{/if}}
@@ -13,5 +16,6 @@ class {{className}}(abc.ABC):
1316

1417
@abc.abstractmethod
1518
def {{name}}(self,{{#if hasParams}} *,{{/if}} {{signatureParams}}) -> {{returnType}}:
19+
"""{{{docstring}}}"""
1620
raise NotImplementedError()
1721
{{/each}}

codegen/layouts/partials/resource-dataclass.hbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
@dataclass
22
class {{className}}:
3+
"""{{{docstring}}}"""
34
{{#each properties}}
45
{{safeName}}: {{type}}
56
{{/each}}

codegen/layouts/partials/route-method.hbs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
def {{name}}(self,{{#if hasParams}} *,{{/if}} {{signatureParams}}) -> {{returnType}}:
2+
"""{{{docstring}}}"""
23
json_payload = {}
34

45
{{#each params}}

codegen/layouts/route.hbs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ from ..modules.action_attempts import resolve_action_attempt
1616

1717

1818
class {{className}}({{abstractClassName}}):
19+
{{#if docstring}}
20+
"""{{{docstring}}}"""
21+
{{/if}}
1922
def __init__(self, client: SeamHttpClient, defaults: Dict[str, Any]):
2023
self.client = client
2124
self.defaults = defaults

codegen/lib/class-model.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,20 @@
44
export interface ClassMethodParameter {
55
name: string
66
type: string
7+
description: string
8+
isDeprecated: boolean
9+
deprecationMessage: string
710
position?: number | undefined
811
required?: boolean | undefined
912
}
1013

1114
export interface ClassMethod {
1215
methodName: string
1316
path: string
17+
description: string
18+
responseDescription: string
19+
isDeprecated: boolean
20+
deprecationMessage: string
1421
parameters: ClassMethodParameter[]
1522
returnPath: string[]
1623
returnResource: string
@@ -24,6 +31,7 @@ export interface ChildClassIdentifier {
2431
export interface ClassModel {
2532
name: string
2633
namespace: string
34+
isDeprecated: boolean
2735
methods: ClassMethod[]
2836
childClassIdentifiers: ChildClassIdentifier[]
2937
}

codegen/lib/layouts/resources.ts

Lines changed: 80 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ const toSafeIdentifier = (name: string): string =>
5656
export interface ResourceLayoutContext {
5757
className: string
5858
moduleName: string
59+
docstring: string
5960
properties: Array<{
6061
name: string
6162
safeName: string
@@ -64,6 +65,44 @@ export interface ResourceLayoutContext {
6465
}>
6566
}
6667

68+
const cleanDoc = (value: string): string =>
69+
value.trim().replaceAll('"""', '\\"\\"\\"')
70+
71+
const createResourceDocstring = (
72+
description: string,
73+
isDeprecated: boolean,
74+
deprecationMessage: string,
75+
properties: Property[],
76+
): string => {
77+
const lines = [cleanDoc(description)]
78+
for (const property of properties) {
79+
const deprecated = property.isDeprecated
80+
? `Deprecated${property.deprecationMessage === '' ? '.' : `: ${cleanDoc(property.deprecationMessage)}`}`
81+
: ''
82+
lines.push(
83+
'',
84+
`:ivar ${toSafeIdentifier(property.name)}: ${[
85+
deprecated,
86+
cleanDoc(property.description),
87+
]
88+
.filter(Boolean)
89+
.join(' ')}`,
90+
`:vartype ${toSafeIdentifier(property.name)}: ${mapPropertyToPythonType(property)}`,
91+
)
92+
}
93+
if (isDeprecated) {
94+
lines.push(
95+
'',
96+
'.. deprecated::',
97+
` ${cleanDoc(deprecationMessage) || 'This resource is deprecated.'}`,
98+
)
99+
}
100+
return lines
101+
.filter((line, index) => line !== '' || index !== 0)
102+
.join('\n')
103+
.replaceAll('\n', '\n ')
104+
}
105+
67106
export interface ResourcesIndexLayoutContext {
68107
resources: Array<{ className: string; moduleName: string }>
69108
}
@@ -84,29 +123,61 @@ const mergeResourceProperties = (resources: Resource[]): Property[] => {
84123
export const getResourceLayoutContexts = (
85124
blueprint: Blueprint,
86125
): ResourceLayoutContext[] => {
87-
const models = new Map<string, Property[]>()
126+
const models = new Map<
127+
string,
128+
{
129+
properties: Property[]
130+
description: string
131+
isDeprecated: boolean
132+
deprecationMessage: string
133+
}
134+
>()
88135

89136
for (const resource of blueprint.resources) {
90-
models.set(resource.resourceType, resource.properties)
137+
models.set(resource.resourceType, resource)
91138
}
92139

93140
// The event and action attempt variants merge into a single dataclass with
94141
// the union of the variant properties, overriding the base resource schema.
95-
models.set(
96-
'action_attempt',
97-
mergeResourceProperties(blueprint.actionAttempts),
98-
)
99-
models.set('event', mergeResourceProperties(blueprint.events))
142+
const actionAttemptModel = models.get('action_attempt')
143+
models.set('action_attempt', {
144+
properties: mergeResourceProperties(blueprint.actionAttempts),
145+
description:
146+
actionAttemptModel?.description ??
147+
'An attempt to perform an action in the Seam API.',
148+
isDeprecated: actionAttemptModel?.isDeprecated ?? false,
149+
deprecationMessage: actionAttemptModel?.deprecationMessage ?? '',
150+
})
151+
const eventModel = models.get('event')
152+
models.set('event', {
153+
properties: mergeResourceProperties(blueprint.events),
154+
description: eventModel?.description ?? 'An event emitted by the Seam API.',
155+
isDeprecated: eventModel?.isDeprecated ?? false,
156+
deprecationMessage: eventModel?.deprecationMessage ?? '',
157+
})
100158

101159
if (blueprint.pagination != null) {
102-
models.set('pagination', blueprint.pagination.properties)
160+
models.set('pagination', {
161+
properties: blueprint.pagination.properties,
162+
description: blueprint.pagination.description,
163+
isDeprecated: false,
164+
deprecationMessage: '',
165+
})
103166
}
104167

105168
return [...models.entries()]
106-
.map(([name, properties]) => {
169+
.map(([name, model]) => {
170+
const { properties, description, isDeprecated, deprecationMessage } =
171+
model
107172
const className = pascalCase(convertCustomResourceName(name))
108173
return {
109174
className,
175+
docstring: createResourceDocstring(
176+
description,
177+
isDeprecated,
178+
deprecationMessage,
179+
properties,
180+
),
110181
// Derived from the class name rather than the resource type so the
111182
// module always matches the dataclass it exports (e.g. the "event"
112183
// resource becomes SeamEvent in seam_event.py).

codegen/lib/layouts/route.ts

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
export interface MethodLayoutContext {
1313
name: string
1414
path: string
15+
docstring: string
1516
hasParams: boolean
1617
signatureParams: string
1718
params: Array<{ name: string }>
@@ -25,19 +26,22 @@ export interface MethodLayoutContext {
2526

2627
export interface AbstractClassLayoutContext {
2728
className: string
29+
docstring: string
2830
showPass: boolean
2931
childProperties: Array<{ namespace: string; abstractClassName: string }>
3032
methods: Array<{
3133
name: string
3234
hasParams: boolean
3335
signatureParams: string
3436
returnType: string
37+
docstring: string
3538
}>
3639
}
3740

3841
export interface RouteLayoutContext {
3942
className: string
4043
abstractClassName: string
44+
docstring: string
4145
abstractClass: AbstractClassLayoutContext
4246
resourceImportList: string
4347
childClasses: Array<{
@@ -53,6 +57,57 @@ export interface RouteLayoutContext {
5357
const waitForActionAttemptParameter =
5458
'wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]] = None'
5559

60+
const cleanDoc = (value: string): string =>
61+
value.trim().replaceAll('"""', '\\"\\"\\"')
62+
63+
const indentDoc = (value: string, spaces: number): string =>
64+
value.replaceAll('\n', `\n${' '.repeat(spaces)}`)
65+
66+
const methodDocstring = (
67+
method: ClassMethod,
68+
sortedParameters: ClassMethod['parameters'],
69+
): string => {
70+
const lines = [cleanDoc(method.description)]
71+
72+
for (const parameter of sortedParameters) {
73+
const deprecated = parameter.isDeprecated
74+
? `Deprecated${parameter.deprecationMessage === '' ? '.' : `: ${cleanDoc(parameter.deprecationMessage)}`}`
75+
: ''
76+
const description = cleanDoc(parameter.description)
77+
lines.push(
78+
'',
79+
`:param ${parameter.name}: ${[deprecated, description].filter(Boolean).join(' ')}`,
80+
`:type ${parameter.name}: ${parameter.type}`,
81+
)
82+
}
83+
84+
if (method.returnResource === 'ActionAttempt') {
85+
lines.push(
86+
'',
87+
':param wait_for_action_attempt: Whether, and for how long, to wait for the action attempt to finish.',
88+
':type wait_for_action_attempt: Optional[Union[bool, Dict[str, float]]]',
89+
)
90+
}
91+
92+
if (method.returnResource !== 'None') {
93+
lines.push(
94+
'',
95+
`:returns: ${cleanDoc(method.responseDescription)}`,
96+
`:rtype: ${method.returnResource}`,
97+
)
98+
}
99+
100+
if (method.isDeprecated) {
101+
lines.push(
102+
'',
103+
'.. deprecated::',
104+
` ${cleanDoc(method.deprecationMessage) || 'This method is deprecated.'}`,
105+
)
106+
}
107+
108+
return lines.filter((line, index) => line !== '' || index !== 0).join('\n')
109+
}
110+
56111
export const getMethodLayoutContext = (
57112
method: ClassMethod,
58113
): MethodLayoutContext => {
@@ -83,6 +138,7 @@ export const getMethodLayoutContext = (
83138
return {
84139
name: methodName,
85140
path,
141+
docstring: indentDoc(methodDocstring(method, sortedParameters), 8),
86142
hasParams,
87143
signatureParams,
88144
params: sortedParameters.map(({ name }) => ({ name })),
@@ -110,22 +166,27 @@ export const setRouteLayoutContext = (cls: ClassModel): RouteLayoutContext => {
110166
)
111167

112168
const abstractClassName = `Abstract${cls.name}`
169+
const classDocstring = cls.isDeprecated
170+
? indentDoc('.. deprecated::\n This route is deprecated.', 4)
171+
: ''
113172

114173
return {
115174
className: cls.name,
116175
abstractClassName,
176+
docstring: classDocstring,
117177
abstractClass: {
118178
className: abstractClassName,
179+
docstring: classDocstring,
119180
showPass:
120181
cls.methods.length === 0 && cls.childClassIdentifiers.length === 0,
121182
childProperties: cls.childClassIdentifiers.map((i) => ({
122183
namespace: i.namespace,
123184
abstractClassName: `Abstract${i.className}`,
124185
})),
125186
methods: cls.methods.map((method) => {
126-
const { name, hasParams, signatureParams, returnType } =
187+
const { name, hasParams, signatureParams, returnType, docstring } =
127188
getMethodLayoutContext(method)
128-
return { name, hasParams, signatureParams, returnType }
189+
return { name, hasParams, signatureParams, returnType, docstring }
129190
}),
130191
},
131192
resourceImportList: resourceClasses.join(','),

codegen/lib/routes.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ export const routes = (
4242
// Namespaces group routes without endpoints of their own (e.g. /acs) but
4343
// still produce a route class so their child classes are reachable.
4444
const classEntries = [...blueprint.namespaces, ...blueprint.routes]
45-
.map(({ path, parentPath }) => ({ path, parentPath }))
45+
.map(({ path, parentPath, isDeprecated }) => ({
46+
path,
47+
parentPath,
48+
isDeprecated,
49+
}))
4650
.sort((a, b) => (a.path < b.path ? -1 : 1))
4751

4852
const classMap = new Map<string, ClassModel>()
@@ -55,6 +59,7 @@ export const routes = (
5559
classMap.set(className, {
5660
name: className,
5761
namespace,
62+
isDeprecated: entry.isDeprecated,
5863
methods: [],
5964
childClassIdentifiers: classEntries
6065
.filter((child) => child.parentPath === entry.path)
@@ -84,9 +89,16 @@ export const routes = (
8489
cls.methods.push({
8590
methodName: endpoint.name,
8691
path: endpoint.path,
92+
description: endpoint.description,
93+
responseDescription: endpoint.response.description,
94+
isDeprecated: endpoint.isDeprecated,
95+
deprecationMessage: endpoint.deprecationMessage,
8796
parameters: endpoint.request.parameters.map((parameter) => ({
8897
name: parameter.name,
8998
type: mapParameterToPythonType(parameter),
99+
description: parameter.description,
100+
isDeprecated: parameter.isDeprecated,
101+
deprecationMessage: parameter.deprecationMessage,
90102
position: parameter.name === idParameterName ? 0 : undefined,
91103
required: parameter.isRequired,
92104
})),

0 commit comments

Comments
 (0)