cddl2py 0.1.0 → 0.1.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,KAAK,UAAU,EAGlB,MAAM,MAAM,CAAA;AAKb,MAAM,WAAW,gBAAgB;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAA;CACrB;AAgBD,wBAAgB,SAAS,CAAE,WAAW,EAAE,UAAU,EAAE,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAuBxF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAIH,KAAK,UAAU,EAGlB,MAAM,MAAM,CAAA;AAKb,MAAM,WAAW,gBAAgB;IAC7B,QAAQ,CAAC,EAAE,OAAO,CAAA;CACrB;AAkBD,wBAAgB,SAAS,CAAE,WAAW,EAAE,UAAU,EAAE,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAuBxF"}
package/build/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { isCDDLArray, isGroup, isNamedGroupReference, isLiteralWithValue, isNativeTypeWithOperator, isUnNamedProperty, isPropertyReference, isRange, isVariable, pascalCase } from 'cddl';
2
2
  import { snakeCase } from './utils.js';
3
3
  import { pkg, NATIVE_TYPE_MAP } from './constants.js';
4
+ const STRING_RECORD_KEY_TYPES = new Set(['str', 'text', 'tstr']);
4
5
  export function transform(assignments, options) {
5
6
  const ctx = {
6
7
  pydantic: options?.pydantic ?? false,
@@ -88,6 +89,7 @@ function generateGroup(group, ctx) {
88
89
  return comments + generateGroupWithChoices(name, properties, ctx);
89
90
  }
90
91
  const props = properties;
92
+ const extraItemsType = getExtraItemsType(props, ctx);
91
93
  if (props.length === 1) {
92
94
  const prop = props[0];
93
95
  const propType = Array.isArray(prop.Type) ? prop.Type : [prop.Type];
@@ -101,7 +103,7 @@ function generateGroup(group, ctx) {
101
103
  }
102
104
  }
103
105
  const mixins = props.filter(isUnNamedProperty);
104
- const ownProps = props.filter(p => !isUnNamedProperty(p));
106
+ const ownProps = props.filter(p => !isUnNamedProperty(p) && !isExtensibleRecordProperty(p));
105
107
  const simpleMixinBases = [];
106
108
  const unionMixinGroups = [];
107
109
  for (const mixin of mixins) {
@@ -137,9 +139,9 @@ function generateGroup(group, ctx) {
137
139
  }
138
140
  }
139
141
  if (unionMixinGroups.length > 0) {
140
- return comments + generateGroupWithUnionMixins(name, simpleMixinBases, unionMixinGroups, ownProps, ctx);
142
+ return comments + generateGroupWithUnionMixins(name, simpleMixinBases, unionMixinGroups, ownProps, extraItemsType, ctx);
141
143
  }
142
- return comments + generateClass(name, simpleMixinBases, ownProps, ctx);
144
+ return comments + generateClass(name, simpleMixinBases, ownProps, ctx, extraItemsType);
143
145
  }
144
146
  function generateGroupWithChoices(name, properties, ctx) {
145
147
  const blocks = [];
@@ -203,7 +205,7 @@ function generateGroupWithChoices(name, properties, ctx) {
203
205
  }
204
206
  return blocks.join('\n\n');
205
207
  }
206
- function generateGroupWithUnionMixins(name, simpleBases, unionGroups, ownProps, ctx) {
208
+ function generateGroupWithUnionMixins(name, simpleBases, unionGroups, ownProps, extraItemsType, ctx) {
207
209
  if (ownProps.length === 0 && simpleBases.length === 0) {
208
210
  const allTypes = unionGroups.flat();
209
211
  if (allTypes.length === 1) {
@@ -218,7 +220,7 @@ function generateGroupWithUnionMixins(name, simpleBases, unionGroups, ownProps,
218
220
  const unionTypes = unionGroups[0];
219
221
  if (ownProps.length > 0) {
220
222
  const baseName = `_${name}Fields`;
221
- blocks.push(generateClass(baseName, [], ownProps, ctx));
223
+ blocks.push(generateClass(baseName, [], ownProps, ctx, extraItemsType));
222
224
  for (let i = 0; i < unionTypes.length; i++) {
223
225
  const variantName = `_${name}Variant${i}`;
224
226
  variantNames.push(variantName);
@@ -231,7 +233,7 @@ function generateGroupWithUnionMixins(name, simpleBases, unionGroups, ownProps,
231
233
  const variantName = `_${name}Variant${i}`;
232
234
  variantNames.push(variantName);
233
235
  const bases = [unionTypes[i], ...simpleBases];
234
- blocks.push(generateClass(variantName, bases, [], ctx));
236
+ blocks.push(generateClass(variantName, bases, [], ctx, extraItemsType));
235
237
  }
236
238
  }
237
239
  }
@@ -292,7 +294,7 @@ function generateArrayAssignment(arr, ctx) {
292
294
  // ---------------------------------------------------------------------------
293
295
  // Class generation (TypedDict or Pydantic BaseModel)
294
296
  // ---------------------------------------------------------------------------
295
- function generateClass(name, bases, props, ctx) {
297
+ function generateClass(name, bases, props, ctx, extraItemsType) {
296
298
  const lines = [];
297
299
  let classDecl;
298
300
  if (ctx.pydantic) {
@@ -308,16 +310,22 @@ function generateClass(name, bases, props, ctx) {
308
310
  else {
309
311
  ctx.typingExtensionsImports.add('TypedDict');
310
312
  const typedDictBases = bases.filter((base) => isModelCompatibleBase(base, ctx));
311
- if (typedDictBases.length > 0) {
312
- classDecl = `class ${name}(${typedDictBases.join(', ')}):`;
313
- }
314
- else {
315
- classDecl = `class ${name}(TypedDict):`;
316
- }
313
+ const baseList = typedDictBases.length > 0 ? typedDictBases.join(', ') : 'TypedDict';
314
+ classDecl = extraItemsType
315
+ ? `class ${name}(${baseList}, extra_items=${extraItemsType}):`
316
+ : `class ${name}(${baseList}):`;
317
317
  }
318
318
  lines.push(classDecl);
319
+ if (ctx.pydantic && extraItemsType) {
320
+ ctx.pydanticImports.add('ConfigDict');
321
+ ctx.pydanticImports.add('Field');
322
+ lines.push(` __pydantic_extra__: dict[str, ${extraItemsType}] = Field(init=False)`);
323
+ lines.push(` model_config = ConfigDict(extra='allow')`);
324
+ }
319
325
  if (props.length === 0) {
320
- lines.push(' pass');
326
+ if (lines.length === 1) {
327
+ lines.push(' pass');
328
+ }
321
329
  return lines.join('\n');
322
330
  }
323
331
  for (const prop of props) {
@@ -385,6 +393,29 @@ function generateField(prop, ctx) {
385
393
  }
386
394
  return ` ${propName}: ${typeStr}${commentSuffix}`;
387
395
  }
396
+ function isExtensibleRecordProperty(prop) {
397
+ return !isUnNamedProperty(prop) &&
398
+ prop.Occurrence.m === Infinity &&
399
+ !prop.HasCut &&
400
+ STRING_RECORD_KEY_TYPES.has(prop.Name);
401
+ }
402
+ function getExtraItemsType(props, ctx) {
403
+ const types = props
404
+ .filter(isExtensibleRecordProperty)
405
+ .flatMap((prop) => {
406
+ const cddlTypes = Array.isArray(prop.Type) ? prop.Type : [prop.Type];
407
+ return cddlTypes.map((type) => resolveType(type, ctx));
408
+ });
409
+ if (types.length === 0) {
410
+ return;
411
+ }
412
+ const uniqueTypes = [...new Set(types)];
413
+ if (uniqueTypes.length === 1) {
414
+ return uniqueTypes[0];
415
+ }
416
+ ctx.typingImports.add('Union');
417
+ return `Union[${uniqueTypes.join(', ')}]`;
418
+ }
388
419
  // ---------------------------------------------------------------------------
389
420
  // Type resolution
390
421
  // ---------------------------------------------------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cddl2py",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "A Node.js package that can generate Python type definitions (with optional Pydantic support) based on a CDDL file",
5
5
  "author": "Christian Bromann <mail@bromann.dev>",
6
6
  "license": "MIT",
@@ -34,7 +34,7 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "yargs": "^18.0.0",
37
- "cddl": "0.19.0"
37
+ "cddl": "0.19.2"
38
38
  },
39
39
  "scripts": {
40
40
  "release": "release-it --config .release-it.ts --VV",