feat(classgen): convert only certain attributes

This commit is contained in:
Johannes Kirschbauer
2025-03-29 14:22:34 +01:00
parent 16644309b4
commit 12b88cd19b
6 changed files with 39 additions and 19 deletions

View File

@@ -31,6 +31,6 @@ Service = dict[str, Any]
class Inventory(TypedDict): class Inventory(TypedDict):
machines: NotRequired[dict[str, Machine]] machines: NotRequired[dict[str, Machine]]
meta: NotRequired[Meta] meta: NotRequired[Meta]
modules: NotRequired[dict[str, int | bool | None | str | dict[str, Any] | float | list[Any]]] modules: NotRequired[dict[str, Any]]
services: NotRequired[dict[str, Service]] services: NotRequired[dict[str, Service]]
tags: NotRequired[dict[str, list[str]]] tags: NotRequired[dict[str, Any]]

View File

@@ -5,4 +5,4 @@ set -euo pipefail
jsonSchema=$(nix build .#schemas.inventory-schema-abstract --print-out-paths)/schema.json jsonSchema=$(nix build .#schemas.inventory-schema-abstract --print-out-paths)/schema.json
SCRIPT_DIR=$(dirname "$0") SCRIPT_DIR=$(dirname "$0")
cd "$SCRIPT_DIR" cd "$SCRIPT_DIR"
nix run .#classgen -- "$jsonSchema" "../../../clan-cli/clan_cli/inventory/classes.py" --stop-at "Service" nix run .#classgen -- "$jsonSchema" "../../../clan-cli/clan_cli/inventory/classes.py"

View File

@@ -60,7 +60,7 @@ let
ln -sf ${nixpkgs'} $out/clan_cli/nixpkgs ln -sf ${nixpkgs'} $out/clan_cli/nixpkgs
cp -r ${../../templates} $out/clan_cli/templates cp -r ${../../templates} $out/clan_cli/templates
${classgen}/bin/classgen ${inventory-schema-abstract}/schema.json $out/clan_cli/inventory/classes.py --stop-at "Service" ${classgen}/bin/classgen ${inventory-schema-abstract}/schema.json $out/clan_cli/inventory/classes.py
''; '';
# Create a custom nixpkgs for use within the project # Create a custom nixpkgs for use within the project

View File

@@ -169,7 +169,7 @@
]; ];
installPhase = '' installPhase = ''
${self'.packages.classgen}/bin/classgen ${self'.legacyPackages.schemas.inventory-schema-abstract}/schema.json ./clan_cli/inventory/classes.py --stop-at "Service" ${self'.packages.classgen}/bin/classgen ${self'.legacyPackages.schemas.inventory-schema-abstract}/schema.json ./clan_cli/inventory/classes.py
python docs.py reference python docs.py reference
mkdir -p $out mkdir -p $out
@@ -188,7 +188,7 @@
]; ];
installPhase = '' installPhase = ''
${self'.packages.classgen}/bin/classgen ${self'.legacyPackages.schemas.inventory-schema-abstract}/schema.json ./clan_cli/inventory/classes.py --stop-at "Service" ${self'.packages.classgen}/bin/classgen ${self'.legacyPackages.schemas.inventory-schema-abstract}/schema.json ./clan_cli/inventory/classes.py
mkdir -p $out mkdir -p $out
# Retrieve python API Typescript types # Retrieve python API Typescript types
python api.py > $out/API.json python api.py > $out/API.json
@@ -214,7 +214,7 @@
classFile = "classes.py"; classFile = "classes.py";
}; };
installPhase = '' installPhase = ''
${self'.packages.classgen}/bin/classgen ${self'.legacyPackages.schemas.inventory-schema-abstract}/schema.json b_classes.py --stop-at "Service" ${self'.packages.classgen}/bin/classgen ${self'.legacyPackages.schemas.inventory-schema-abstract}/schema.json b_classes.py
file1=$classFile file1=$classFile
file2=b_classes.py file2=b_classes.py

View File

@@ -46,6 +46,6 @@ mkShell {
# Generate classes.py from inventory schema # Generate classes.py from inventory schema
# This file is in .gitignore # This file is in .gitignore
${self'.packages.classgen}/bin/classgen ${self'.legacyPackages.schemas.inventory-schema-abstract}/schema.json $PKG_ROOT/clan_cli/inventory/classes.py --stop-at "Service" ${self'.packages.classgen}/bin/classgen ${self'.legacyPackages.schemas.inventory-schema-abstract}/schema.json $PKG_ROOT/clan_cli/inventory/classes.py
''; '';
} }

View File

@@ -54,7 +54,11 @@ def map_json_type(
known_classes = set() known_classes = set()
root_class = "Inventory" root_class = "Inventory"
stop_at = None # TODO: make this configurable
# For now this only includes static top-level attributes of the inventory.
attrs = ["machines", "meta", "services"]
static: dict[str, str] = {"Service": "dict[str, Any]"}
def field_def_from_default_type( def field_def_from_default_type(
@@ -193,19 +197,32 @@ def get_field_def(
# Recursive function to generate dataclasses from JSON schema # Recursive function to generate dataclasses from JSON schema
def generate_dataclass(schema: dict[str, Any], class_name: str = root_class) -> str: def generate_dataclass(
schema: dict[str, Any],
attr_path: list[str],
class_name: str = root_class,
) -> str:
properties = schema.get("properties", {}) properties = schema.get("properties", {})
required_fields = [] required_fields = []
fields_with_default = [] fields_with_default = []
nested_classes: list[str] = [] nested_classes: list[str] = []
if stop_at and class_name == stop_at:
# Skip generating classes below the stop_at property # if We are at the top level, and the attribute name is in shallow
return f"{class_name} = dict[str, Any]" # return f"{class_name} = dict[str, Any]"
if class_name in static:
return f"{class_name} = {static[class_name]}"
for prop, prop_info in properties.items(): for prop, prop_info in properties.items():
# If we are at the top level, and the attribute name is not explicitly included we only do shallow
field_name = prop.replace("-", "_") field_name = prop.replace("-", "_")
if len(attr_path) == 0 and prop not in attrs:
field_def = f"{field_name}: NotRequired[dict[str, Any]]"
fields_with_default.append(field_def)
# breakpoint()
continue
prop_type = prop_info.get("type", None) prop_type = prop_info.get("type", None)
union_variants = prop_info.get("oneOf", []) union_variants = prop_info.get("oneOf", [])
enum_variants = prop_info.get("enum", []) enum_variants = prop_info.get("enum", [])
@@ -243,7 +260,9 @@ def generate_dataclass(schema: dict[str, Any], class_name: str = root_class) ->
if nested_class_name not in known_classes: if nested_class_name not in known_classes:
nested_classes.append( nested_classes.append(
generate_dataclass(inner_type, nested_class_name) generate_dataclass(
inner_type, [*attr_path, prop], nested_class_name
)
) )
known_classes.add(nested_class_name) known_classes.add(nested_class_name)
@@ -259,7 +278,9 @@ def generate_dataclass(schema: dict[str, Any], class_name: str = root_class) ->
field_types = {nested_class_name} field_types = {nested_class_name}
if nested_class_name not in known_classes: if nested_class_name not in known_classes:
nested_classes.append( nested_classes.append(
generate_dataclass(prop_info, nested_class_name) generate_dataclass(
prop_info, [*attr_path, prop], nested_class_name
)
) )
known_classes.add(nested_class_name) known_classes.add(nested_class_name)
else: else:
@@ -324,6 +345,8 @@ def generate_dataclass(schema: dict[str, Any], class_name: str = root_class) ->
) )
required_fields.append(field_def) required_fields.append(field_def)
# breakpoint()
fields_str = "\n ".join(required_fields + fields_with_default) fields_str = "\n ".join(required_fields + fields_with_default)
nested_classes_str = "\n\n".join(nested_classes) nested_classes_str = "\n\n".join(nested_classes)
@@ -338,14 +361,11 @@ def generate_dataclass(schema: dict[str, Any], class_name: str = root_class) ->
def run_gen(args: argparse.Namespace) -> None: def run_gen(args: argparse.Namespace) -> None:
print(f"Converting {args.input} to {args.output}") print(f"Converting {args.input} to {args.output}")
if args.stop_at:
global stop_at
stop_at = args.stop_at
dataclass_code = "" dataclass_code = ""
with args.input.open() as f: with args.input.open() as f:
schema = json.load(f) schema = json.load(f)
dataclass_code = generate_dataclass(schema) dataclass_code = generate_dataclass(schema, [])
with args.output.open("w") as f: with args.output.open("w") as f:
f.write( f.write(