From 12b88cd19be97a29991878841e7b391dec8edc15 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Sat, 29 Mar 2025 14:22:34 +0100 Subject: [PATCH] feat(classgen): convert only certain attributes --- pkgs/clan-cli/clan_cli/inventory/classes.py | 4 +- pkgs/clan-cli/clan_cli/inventory/update.sh | 2 +- pkgs/clan-cli/default.nix | 2 +- pkgs/clan-cli/flake-module.nix | 6 +-- pkgs/clan-cli/shell.nix | 2 +- pkgs/classgen/main.py | 42 +++++++++++++++------ 6 files changed, 39 insertions(+), 19 deletions(-) diff --git a/pkgs/clan-cli/clan_cli/inventory/classes.py b/pkgs/clan-cli/clan_cli/inventory/classes.py index 33dcae521..3ed9d3342 100644 --- a/pkgs/clan-cli/clan_cli/inventory/classes.py +++ b/pkgs/clan-cli/clan_cli/inventory/classes.py @@ -31,6 +31,6 @@ Service = dict[str, Any] class Inventory(TypedDict): machines: NotRequired[dict[str, Machine]] 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]] - tags: NotRequired[dict[str, list[str]]] + tags: NotRequired[dict[str, Any]] diff --git a/pkgs/clan-cli/clan_cli/inventory/update.sh b/pkgs/clan-cli/clan_cli/inventory/update.sh index b62b8430e..2b0dbe50f 100755 --- a/pkgs/clan-cli/clan_cli/inventory/update.sh +++ b/pkgs/clan-cli/clan_cli/inventory/update.sh @@ -5,4 +5,4 @@ set -euo pipefail jsonSchema=$(nix build .#schemas.inventory-schema-abstract --print-out-paths)/schema.json SCRIPT_DIR=$(dirname "$0") 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" diff --git a/pkgs/clan-cli/default.nix b/pkgs/clan-cli/default.nix index a3214cb2d..d58a0cce5 100644 --- a/pkgs/clan-cli/default.nix +++ b/pkgs/clan-cli/default.nix @@ -60,7 +60,7 @@ let ln -sf ${nixpkgs'} $out/clan_cli/nixpkgs 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 diff --git a/pkgs/clan-cli/flake-module.nix b/pkgs/clan-cli/flake-module.nix index 3bf583647..c0e3b9e9e 100644 --- a/pkgs/clan-cli/flake-module.nix +++ b/pkgs/clan-cli/flake-module.nix @@ -169,7 +169,7 @@ ]; 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 mkdir -p $out @@ -188,7 +188,7 @@ ]; 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 # Retrieve python API Typescript types python api.py > $out/API.json @@ -214,7 +214,7 @@ classFile = "classes.py"; }; 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 file2=b_classes.py diff --git a/pkgs/clan-cli/shell.nix b/pkgs/clan-cli/shell.nix index 384b88d1a..bdace6f79 100644 --- a/pkgs/clan-cli/shell.nix +++ b/pkgs/clan-cli/shell.nix @@ -46,6 +46,6 @@ mkShell { # Generate classes.py from inventory schema # 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 ''; } diff --git a/pkgs/classgen/main.py b/pkgs/classgen/main.py index 1ed59203e..76bb115f0 100644 --- a/pkgs/classgen/main.py +++ b/pkgs/classgen/main.py @@ -54,7 +54,11 @@ def map_json_type( known_classes = set() 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( @@ -193,19 +197,32 @@ def get_field_def( # 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", {}) required_fields = [] fields_with_default = [] nested_classes: list[str] = [] - if stop_at and class_name == stop_at: - # Skip generating classes below the stop_at property - return f"{class_name} = dict[str, Any]" + + # if We are at the top level, and the attribute name is in shallow + # 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(): + # If we are at the top level, and the attribute name is not explicitly included we only do shallow 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) union_variants = prop_info.get("oneOf", []) 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: 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) @@ -259,7 +278,9 @@ def generate_dataclass(schema: dict[str, Any], class_name: str = root_class) -> field_types = {nested_class_name} if nested_class_name not in known_classes: 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) else: @@ -324,6 +345,8 @@ def generate_dataclass(schema: dict[str, Any], class_name: str = root_class) -> ) required_fields.append(field_def) + # breakpoint() + fields_str = "\n ".join(required_fields + fields_with_default) 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: print(f"Converting {args.input} to {args.output}") - if args.stop_at: - global stop_at - stop_at = args.stop_at dataclass_code = "" with args.input.open() as f: schema = json.load(f) - dataclass_code = generate_dataclass(schema) + dataclass_code = generate_dataclass(schema, []) with args.output.open("w") as f: f.write(