feat(classgen): convert only certain attributes
This commit is contained in:
@@ -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]]
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user