Merge pull request 'Classgen: export field type definitions' (#3715) from hsjobeki/clan-core:inventory-services-1 into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3715
This commit is contained in:
hsjobeki
2025-05-20 12:20:07 +00:00
4 changed files with 49 additions and 31 deletions

View File

@@ -67,6 +67,9 @@
"*/bin/clan-app" "*/bin/clan-app"
"*/bin/clan-config" "*/bin/clan-config"
]; ];
treefmt.settings.formatter.ruff-format.excludes = [
"*/clan_lib/nix_models/*"
];
treefmt.settings.formatter.shellcheck.includes = [ treefmt.settings.formatter.shellcheck.includes = [
"scripts/pre-commit" "scripts/pre-commit"
]; ];

View File

@@ -1,19 +0,0 @@
import { QueryClient } from "@tanstack/solid-query";
import { ApiEnvelope, callApi } from ".";
import { Schema as Inventory } from "@/api/Inventory";
export async function get_inventory(client: QueryClient, base_path: string) {
const data = await client.ensureQueryData({
queryKey: [base_path, "inventory"],
queryFn: () => {
console.log("Refreshing inventory");
return callApi("get_inventory", {
flake: { identifier: base_path },
}) as Promise<ApiEnvelope<Inventory>>;
},
revalidateIfStale: true,
staleTime: 60 * 1000,
});
return data;
}

View File

@@ -11,6 +11,9 @@ from typing import Any, Literal, NotRequired, TypedDict
class MachineDeploy(TypedDict): class MachineDeploy(TypedDict):
targetHost: NotRequired[str] targetHost: NotRequired[str]
MachineDeployTargethostType = NotRequired[str]
class Machine(TypedDict): class Machine(TypedDict):
deploy: NotRequired[MachineDeploy] deploy: NotRequired[MachineDeploy]
@@ -20,12 +23,25 @@ class Machine(TypedDict):
name: NotRequired[str] name: NotRequired[str]
tags: NotRequired[list[str]] tags: NotRequired[list[str]]
MachineDeployType = NotRequired[MachineDeploy]
MachineDescriptionType = NotRequired[str]
MachineIconType = NotRequired[str]
MachineMachineclassType = NotRequired[Literal["nixos", "darwin"]]
MachineNameType = NotRequired[str]
MachineTagsType = NotRequired[list[str]]
class Meta(TypedDict): class Meta(TypedDict):
name: str name: str
description: NotRequired[str] description: NotRequired[str]
icon: NotRequired[str] icon: NotRequired[str]
MetaNameType = str
MetaDescriptionType = NotRequired[str]
MetaIconType = NotRequired[str]
Service = dict[str, Any] Service = dict[str, Any]
@@ -35,3 +51,10 @@ class Inventory(TypedDict):
modules: NotRequired[dict[str, Any]] modules: NotRequired[dict[str, Any]]
services: NotRequired[dict[str, Service]] services: NotRequired[dict[str, Service]]
tags: NotRequired[dict[str, Any]] tags: NotRequired[dict[str, Any]]
InventoryMachinesType = NotRequired[dict[str, Machine]]
InventoryMetaType = NotRequired[Meta]
InventoryModulesType = NotRequired[dict[str, Any]]
InventoryServicesType = NotRequired[dict[str, Service]]
InventoryTagsType = NotRequired[dict[str, Any]]

View File

@@ -65,8 +65,8 @@ def field_def_from_default_type(
field_name: str, field_name: str,
field_types: set[str], field_types: set[str],
class_name: str, class_name: str,
finalize_field: Callable[..., str], finalize_field: Callable[..., tuple[str, str]],
) -> str | None: ) -> tuple[str, str] | None:
if "dict" in str(field_types): if "dict" in str(field_types):
return finalize_field( return finalize_field(
field_types=field_types, field_types=field_types,
@@ -127,8 +127,8 @@ def field_def_from_default_value(
field_name: str, field_name: str,
field_types: set[str], field_types: set[str],
nested_class_name: str, nested_class_name: str,
finalize_field: Callable[..., str], finalize_field: Callable[..., tuple[str, str]],
) -> str | None: ) -> tuple[str, str] | None:
# default_value = prop_info.get("default") # default_value = prop_info.get("default")
if default_value is None: if default_value is None:
return finalize_field( return finalize_field(
@@ -184,7 +184,7 @@ def get_field_def(
default: str | None = None, default: str | None = None,
default_factory: str | None = None, default_factory: str | None = None,
type_appendix: str = "", type_appendix: str = "",
) -> str: ) -> tuple[str, str]:
if "None" in field_types or default or default_factory: if "None" in field_types or default or default_factory:
if "None" in field_types: if "None" in field_types:
field_types.remove("None") field_types.remove("None")
@@ -193,7 +193,7 @@ def get_field_def(
else: else:
serialised_types = " | ".join(field_types) + type_appendix serialised_types = " | ".join(field_types) + type_appendix
return f"{field_name}: {serialised_types}" return (field_name, serialised_types)
# Recursive function to generate dataclasses from JSON schema # Recursive function to generate dataclasses from JSON schema
@@ -204,8 +204,8 @@ def generate_dataclass(
) -> str: ) -> str:
properties = schema.get("properties", {}) properties = schema.get("properties", {})
required_fields = [] required_fields: list[tuple[str, str]] = []
fields_with_default = [] fields_with_default: list[tuple[str, str]] = []
nested_classes: list[str] = [] nested_classes: list[str] = []
# if We are at the top level, and the attribute name is in shallow # if We are at the top level, and the attribute name is in shallow
@@ -218,7 +218,7 @@ def generate_dataclass(
field_name = prop.replace("-", "_") field_name = prop.replace("-", "_")
if len(attr_path) == 0 and prop not in attrs: if len(attr_path) == 0 and prop not in attrs:
field_def = f"{field_name}: NotRequired[dict[str, Any]]" field_def = field_name, "NotRequired[dict[str, Any]]"
fields_with_default.append(field_def) fields_with_default.append(field_def)
# breakpoint() # breakpoint()
continue continue
@@ -345,9 +345,18 @@ def generate_dataclass(
) )
required_fields.append(field_def) required_fields.append(field_def)
# breakpoint() # Join field name with type to form a complete field declaration
# e.g. "name: str"
fields_str = "\n ".join(required_fields + fields_with_default) all_field_declarations = [
f"{n}: {t}" for n, t in (required_fields + fields_with_default)
]
hoisted_types: str = "\n".join(
[
f"{class_name}{n.capitalize()}Type = {x}"
for n, x in (required_fields + fields_with_default)
]
)
fields_str = "\n ".join(all_field_declarations)
nested_classes_str = "\n\n".join(nested_classes) nested_classes_str = "\n\n".join(nested_classes)
class_def = f"\nclass {class_name}(TypedDict):\n" class_def = f"\nclass {class_name}(TypedDict):\n"
@@ -356,6 +365,8 @@ def generate_dataclass(
else: else:
class_def += f" {fields_str}" class_def += f" {fields_str}"
class_def += f"\n\n{hoisted_types}\n"
return f"{nested_classes_str}\n\n{class_def}" if nested_classes_str else class_def return f"{nested_classes_str}\n\n{class_def}" if nested_classes_str else class_def