Skip to content
This repository was archived by the owner on Jan 19, 2025. It is now read-only.

Commit e5e8916

Browse files
authored
feat: None as default value (#422)
* feat: None as default value * fix: errors shown in IDE only * style: apply automatic fixes of linters * test: some tests using strings/none as default values Co-authored-by: lars-reimann <[email protected]>
1 parent 2c7f368 commit e5e8916

File tree

12 files changed

+137
-96
lines changed

12 files changed

+137
-96
lines changed

api-editor/client/src/features/annotatedPackageData/model/InferableAnnotation.ts

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,27 @@ import {
1616

1717
const dataPathPrefix = 'com.larsreimann.api_editor.model.';
1818

19-
const getDefaultValueTypeSuffix = (type: DefaultType) => {
19+
const convertDefaultValue = (type: DefaultType, value: DefaultValue) => {
2020
switch (type) {
2121
case 'string':
22-
return 'DefaultString';
22+
return {
23+
type: `${dataPathPrefix}DefaultString`,
24+
value,
25+
};
2326
case 'boolean':
24-
return 'DefaultBoolean';
27+
return {
28+
type: `${dataPathPrefix}DefaultBoolean`,
29+
value,
30+
};
2531
case 'number':
26-
return 'DefaultNumber';
32+
return {
33+
type: `${dataPathPrefix}DefaultNumber`,
34+
value,
35+
};
36+
case 'none':
37+
return {
38+
type: `${dataPathPrefix}DefaultNone`,
39+
};
2740
}
2841
};
2942

@@ -36,16 +49,14 @@ export class InferableAnnotation {
3649
}
3750

3851
export class InferableAttributeAnnotation extends InferableAnnotation {
39-
readonly defaultValue: { type: string; value: DefaultValue };
52+
readonly defaultValue: { type: string; value?: DefaultValue };
4053

4154
constructor(attributeAnnotation: AttributeAnnotation) {
4255
super(dataPathPrefix + 'AttributeAnnotation');
43-
this.defaultValue = {
44-
type:
45-
dataPathPrefix +
46-
getDefaultValueTypeSuffix(attributeAnnotation.defaultType),
47-
value: attributeAnnotation.defaultValue,
48-
};
56+
this.defaultValue = convertDefaultValue(
57+
attributeAnnotation.defaultType,
58+
attributeAnnotation.defaultValue,
59+
);
4960
}
5061
}
5162

@@ -78,19 +89,19 @@ export class InferableCalledAfterAnnotation extends InferableAnnotation {
7889
this.calledAfterName = calledAfterAnnotation.calledAfterName;
7990
}
8091
}
92+
8193
export class InferableConstantAnnotation extends InferableAnnotation {
82-
readonly defaultValue: { type: string; value: DefaultValue };
94+
readonly defaultValue: { type: string; value?: DefaultValue };
8395

8496
constructor(constantAnnotation: ConstantAnnotation) {
8597
super(dataPathPrefix + 'ConstantAnnotation');
86-
this.defaultValue = {
87-
type:
88-
dataPathPrefix +
89-
getDefaultValueTypeSuffix(constantAnnotation.defaultType),
90-
value: constantAnnotation.defaultValue,
91-
};
98+
this.defaultValue = convertDefaultValue(
99+
constantAnnotation.defaultType,
100+
constantAnnotation.defaultValue,
101+
);
92102
}
93103
}
104+
94105
export class InferableGroupAnnotation extends InferableAnnotation {
95106
readonly groupName: string;
96107
readonly parameters: string[];
@@ -101,6 +112,7 @@ export class InferableGroupAnnotation extends InferableAnnotation {
101112
this.parameters = groupAnnotation.parameters;
102113
}
103114
}
115+
104116
export class InferableEnumAnnotation extends InferableAnnotation {
105117
readonly enumName: string;
106118
readonly pairs: EnumPair[];
@@ -111,6 +123,7 @@ export class InferableEnumAnnotation extends InferableAnnotation {
111123
this.pairs = enumAnnotation.pairs;
112124
}
113125
}
126+
114127
export class InferableMoveAnnotation extends InferableAnnotation {
115128
readonly destination: string;
116129

@@ -119,24 +132,25 @@ export class InferableMoveAnnotation extends InferableAnnotation {
119132
this.destination = moveAnnotation.destination;
120133
}
121134
}
135+
122136
export class InferableOptionalAnnotation extends InferableAnnotation {
123-
readonly defaultValue: { type: string; value: DefaultValue };
137+
readonly defaultValue: { type: string; value?: DefaultValue };
124138

125139
constructor(optionalAnnotation: OptionalAnnotation) {
126140
super(dataPathPrefix + 'OptionalAnnotation');
127-
this.defaultValue = {
128-
type:
129-
dataPathPrefix +
130-
getDefaultValueTypeSuffix(optionalAnnotation.defaultType),
131-
value: optionalAnnotation.defaultValue,
132-
};
141+
this.defaultValue = convertDefaultValue(
142+
optionalAnnotation.defaultType,
143+
optionalAnnotation.defaultValue,
144+
);
133145
}
134146
}
147+
135148
export class InferablePureAnnotation extends InferableAnnotation {
136149
constructor() {
137150
super(dataPathPrefix + 'PureAnnotation');
138151
}
139152
}
153+
140154
export class InferableRenameAnnotation extends InferableAnnotation {
141155
readonly newName: string;
142156

@@ -145,11 +159,13 @@ export class InferableRenameAnnotation extends InferableAnnotation {
145159
this.newName = renameAnnotation.newName;
146160
}
147161
}
162+
148163
export class InferableRequiredAnnotation extends InferableAnnotation {
149164
constructor() {
150165
super(dataPathPrefix + 'RequiredAnnotation');
151166
}
152167
}
168+
153169
export class InferableUnusedAnnotation extends InferableAnnotation {
154170
constructor() {
155171
super(dataPathPrefix + 'UnusedAnnotation');

api-editor/client/src/features/annotations/AnnotationView.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ const valueToString = (value: DefaultValue, type: DefaultType): string => {
205205
return String(value);
206206
case 'boolean':
207207
return value === true ? 'True' : 'False';
208+
case 'none':
209+
return 'None';
208210
}
209211
};
210212

api-editor/client/src/features/annotations/annotationSlice.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ export interface AttributeAnnotation {
6060
readonly defaultValue: DefaultValue;
6161
}
6262

63-
export type DefaultType = 'string' | 'number' | 'boolean';
64-
export type DefaultValue = string | number | boolean;
63+
export type DefaultType = 'string' | 'number' | 'boolean' | 'none';
64+
export type DefaultValue = string | number | boolean | null;
6565

6666
export interface BoundaryAnnotation {
6767
/**

api-editor/client/src/features/annotations/forms/TypeValueForm.tsx

Lines changed: 41 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ const TypeValueForm: React.FC<TypeValueFormProps> = function ({
8686
let toUpsert = { ...data };
8787
if (data.defaultType === 'boolean') {
8888
toUpsert = { ...data, defaultValue: data.defaultValue === 'true' };
89+
} else if (data.defaultType === 'none') {
90+
toUpsert = { ...data, defaultValue: null };
8991
}
9092
onUpsertAnnotation(toUpsert);
9193
dispatch(hideAnnotationForms());
@@ -116,49 +118,52 @@ const TypeValueForm: React.FC<TypeValueFormProps> = function ({
116118
<Radio value="string">String</Radio>
117119
<Radio value="number">Number</Radio>
118120
<Radio value="boolean">Boolean</Radio>
121+
<Radio value="none">None</Radio>
119122
</Stack>
120123
</RadioGroup>
121124

122-
<FormControl isInvalid={Boolean(errors?.defaultValue)}>
123-
<FormLabel>
124-
Default value for &quot;{target.name}&quot;:
125-
</FormLabel>
126-
{watchDefaultType === 'string' && (
127-
<Input
128-
{...register('defaultValue', {
129-
required: 'This is required.',
130-
})}
131-
/>
132-
)}
133-
{watchDefaultType === 'number' && (
134-
<NumberInput>
135-
<NumberInputField
125+
{watchDefaultType !== 'none' && (
126+
<FormControl isInvalid={Boolean(errors?.defaultValue)}>
127+
<FormLabel>
128+
Default value for &quot;{target.name}&quot;:
129+
</FormLabel>
130+
{watchDefaultType === 'string' && (
131+
<Input
136132
{...register('defaultValue', {
137133
required: 'This is required.',
138-
pattern: numberPattern,
139134
})}
140135
/>
141-
<NumberInputStepper>
142-
<NumberIncrementStepper />
143-
<NumberDecrementStepper />
144-
</NumberInputStepper>
145-
</NumberInput>
146-
)}
147-
{watchDefaultType === 'boolean' && (
148-
<Select
149-
{...register('defaultValue', {
150-
required: 'This is required.',
151-
pattern: booleanPattern,
152-
})}
153-
>
154-
<option value="true">True</option>
155-
<option value="false">False</option>
156-
</Select>
157-
)}
158-
<FormErrorMessage>
159-
<FormErrorIcon /> {errors.defaultValue?.message}
160-
</FormErrorMessage>
161-
</FormControl>
136+
)}
137+
{watchDefaultType === 'number' && (
138+
<NumberInput>
139+
<NumberInputField
140+
{...register('defaultValue', {
141+
required: 'This is required.',
142+
pattern: numberPattern,
143+
})}
144+
/>
145+
<NumberInputStepper>
146+
<NumberIncrementStepper />
147+
<NumberDecrementStepper />
148+
</NumberInputStepper>
149+
</NumberInput>
150+
)}
151+
{watchDefaultType === 'boolean' && (
152+
<Select
153+
{...register('defaultValue', {
154+
required: 'This is required.',
155+
pattern: booleanPattern,
156+
})}
157+
>
158+
<option value="true">True</option>
159+
<option value="false">False</option>
160+
</Select>
161+
)}
162+
<FormErrorMessage>
163+
<FormErrorIcon /> {errors.defaultValue?.message}
164+
</FormErrorMessage>
165+
</FormControl>
166+
)}
162167
</AnnotationForm>
163168
);
164169
};

api-editor/server/src/main/kotlin/com/larsreimann/api_editor/codegen/PythonCodeGenerator.kt

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import com.larsreimann.api_editor.mutable_model.PythonInt
2424
import com.larsreimann.api_editor.mutable_model.PythonMemberAccess
2525
import com.larsreimann.api_editor.mutable_model.PythonModule
2626
import com.larsreimann.api_editor.mutable_model.PythonNamedType
27+
import com.larsreimann.api_editor.mutable_model.PythonNone
2728
import com.larsreimann.api_editor.mutable_model.PythonParameter
2829
import com.larsreimann.api_editor.mutable_model.PythonReference
2930
import com.larsreimann.api_editor.mutable_model.PythonString
@@ -101,7 +102,7 @@ private fun String.parentQualifiedName(): String {
101102
return substring(0, separationPosition)
102103
}
103104

104-
internal fun PythonAttribute.toPythonCode() = buildString {
105+
fun PythonAttribute.toPythonCode() = buildString {
105106
append("self.$name")
106107
type?.toPythonCodeOrNull()?.let {
107108
append(": $it")
@@ -111,7 +112,7 @@ internal fun PythonAttribute.toPythonCode() = buildString {
111112
}
112113
}
113114

114-
internal fun PythonClass.toPythonCode() = buildString {
115+
fun PythonClass.toPythonCode() = buildString {
115116
val constructorString = constructor?.toPythonCode() ?: ""
116117
val methodsString = methods.joinToString("\n\n") { it.toPythonCode() }
117118

@@ -130,7 +131,7 @@ internal fun PythonClass.toPythonCode() = buildString {
130131
}
131132
}
132133

133-
internal fun PythonConstructor.toPythonCode() = buildString {
134+
fun PythonConstructor.toPythonCode() = buildString {
134135
val parametersString = parameters.toPythonCode()
135136
val boundariesString = parameters
136137
.mapNotNull { it.boundary?.toPythonCode(it.name) }
@@ -164,7 +165,7 @@ internal fun PythonConstructor.toPythonCode() = buildString {
164165
}
165166
}
166167

167-
internal fun PythonEnum.toPythonCode() = buildString {
168+
fun PythonEnum.toPythonCode() = buildString {
168169
appendLine("class $name(Enum):")
169170
appendIndented {
170171
if (instances.isEmpty()) {
@@ -180,11 +181,11 @@ internal fun PythonEnum.toPythonCode() = buildString {
180181
}
181182
}
182183

183-
internal fun PythonEnumInstance.toPythonCode(): String {
184+
fun PythonEnumInstance.toPythonCode(): String {
184185
return "$name = ${value!!.toPythonCode()}"
185186
}
186187

187-
internal fun PythonFunction.toPythonCode() = buildString {
188+
fun PythonFunction.toPythonCode() = buildString {
188189
val parametersString = parameters.toPythonCode()
189190
val boundariesString = parameters
190191
.mapNotNull { it.boundary?.toPythonCode(it.name) }
@@ -211,7 +212,7 @@ internal fun PythonFunction.toPythonCode() = buildString {
211212
}
212213
}
213214

214-
internal fun List<PythonParameter>.toPythonCode(): String {
215+
fun List<PythonParameter>.toPythonCode(): String {
215216
val assignedByToParameter = this@toPythonCode.groupBy { it.assignedBy }
216217
val implicitParametersString = assignedByToParameter[IMPLICIT]
217218
?.joinToString { it.toPythonCode() }
@@ -246,7 +247,7 @@ internal fun List<PythonParameter>.toPythonCode(): String {
246247
.joinToString()
247248
}
248249

249-
internal fun PythonParameter.toPythonCode() = buildString {
250+
fun PythonParameter.toPythonCode() = buildString {
250251
val typeStringOrNull = type.toPythonCodeOrNull()
251252

252253
append(name)
@@ -262,13 +263,14 @@ internal fun PythonParameter.toPythonCode() = buildString {
262263
* Expressions
263264
* ********************************************************************************************************************/
264265

265-
internal fun PythonExpression.toPythonCode(): String {
266+
fun PythonExpression.toPythonCode(): String {
266267
return when (this) {
267268
is PythonBoolean -> value.toString().replaceFirstChar { it.uppercase() }
268269
is PythonCall -> "${receiver!!.toPythonCode()}(${arguments.joinToString { it.toPythonCode() }})"
269270
is PythonFloat -> value.toString()
270271
is PythonInt -> value.toString()
271272
is PythonMemberAccess -> "${receiver!!.toPythonCode()}.${member!!.toPythonCode()}"
273+
is PythonNone -> "None"
272274
is PythonReference -> declaration!!.name
273275
is PythonString -> "'$value'"
274276
is PythonStringifiedExpression -> string
@@ -279,7 +281,7 @@ internal fun PythonExpression.toPythonCode(): String {
279281
* Types
280282
* ********************************************************************************************************************/
281283

282-
internal fun PythonType?.toPythonCodeOrNull(): String? {
284+
fun PythonType?.toPythonCodeOrNull(): String? {
283285
return when (this) {
284286
is PythonNamedType -> this.declaration?.name
285287
is PythonStringifiedType -> {
@@ -299,14 +301,14 @@ internal fun PythonType?.toPythonCodeOrNull(): String? {
299301
* Other
300302
* ********************************************************************************************************************/
301303

302-
internal fun PythonArgument.toPythonCode() = buildString {
304+
fun PythonArgument.toPythonCode() = buildString {
303305
if (name != null) {
304306
append("$name=")
305307
}
306308
append(value!!.toPythonCode())
307309
}
308310

309-
internal fun Boundary.toPythonCode(parameterName: String) = buildString {
311+
fun Boundary.toPythonCode(parameterName: String) = buildString {
310312
if (isDiscrete) {
311313
appendLine("if not (isinstance($parameterName, int) or (isinstance($parameterName, float) and $parameterName.is_integer())):")
312314
appendIndented("raise ValueError(f'$parameterName needs to be an integer, but {$parameterName} was assigned.')")

0 commit comments

Comments
 (0)