From edcc1a5cb28f56a7a18b15b874bf965abe193f96 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Sat, 6 Jul 2024 17:51:46 +0200 Subject: [PATCH] Test: fixup --- pkgs/clan-cli/clan_cli/api/util.py | 12 +++---- pkgs/clan-cli/clan_cli/machines/machines.py | 2 +- .../tests/test_api_dataclass_compat.py | 35 +++++++++++++++---- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/pkgs/clan-cli/clan_cli/api/util.py b/pkgs/clan-cli/clan_cli/api/util.py index 4a49a7a6c..505fab545 100644 --- a/pkgs/clan-cli/clan_cli/api/util.py +++ b/pkgs/clan-cli/clan_cli/api/util.py @@ -128,7 +128,7 @@ def type_to_dict(t: Any, scope: str = "", type_map: dict[TypeVar, type] = {}) -> if origin is None: # Non-generic user-defined or built-in type # TODO: handle custom types - raise JSchemaTypeError("Unhandled Type: ", origin) + raise JSchemaTypeError(f"{scope} Unhandled Type: ", origin) elif origin is Literal: # Handle Literal values for enums in JSON Schema @@ -173,7 +173,7 @@ def type_to_dict(t: Any, scope: str = "", type_map: dict[TypeVar, type] = {}) -> new_map.update(inspect_dataclass_fields(t)) return type_to_dict(origin, scope, new_map) - raise JSchemaTypeError(f"Error api type not yet supported {t!s}") + raise JSchemaTypeError(f"{scope} - Error api type not yet supported {t!s}") elif isinstance(t, type): if t is str: @@ -188,7 +188,7 @@ def type_to_dict(t: Any, scope: str = "", type_map: dict[TypeVar, type] = {}) -> return {"type": "object"} if t is Any: raise JSchemaTypeError( - f"Usage of the Any type is not supported for API functions. In: {scope}" + f"{scope} - Usage of the Any type is not supported for API functions. In: {scope}" ) if t is pathlib.Path: return { @@ -197,13 +197,13 @@ def type_to_dict(t: Any, scope: str = "", type_map: dict[TypeVar, type] = {}) -> } if t is dict: raise JSchemaTypeError( - "Error: generic dict type not supported. Use dict[str, Any] instead" + f"{scope} - Generic 'dict' type not supported. Use dict[str, Any] or any more expressive type." ) # Optional[T] gets internally transformed Union[T,NoneType] if t is NoneType: return {"type": "null"} - raise JSchemaTypeError(f"Error primitive type not supported {t!s}") + raise JSchemaTypeError(f"{scope} - Error primitive type not supported {t!s}") else: - raise JSchemaTypeError(f"Error type not supported {t!s}") + raise JSchemaTypeError(f"{scope} - Error type not supported {t!s}") diff --git a/pkgs/clan-cli/clan_cli/machines/machines.py b/pkgs/clan-cli/clan_cli/machines/machines.py index 877ba6421..6e1974312 100644 --- a/pkgs/clan-cli/clan_cli/machines/machines.py +++ b/pkgs/clan-cli/clan_cli/machines/machines.py @@ -20,7 +20,7 @@ class Machine: name: str flake: FlakeId nix_options: list[str] = field(default_factory=list) - cached_deployment: None | dict = None + cached_deployment: None | dict[str, Any] = None _eval_cache: dict[str, str] = field(default_factory=dict) _build_cache: dict[str, Path] = field(default_factory=dict) diff --git a/pkgs/clan-cli/tests/test_api_dataclass_compat.py b/pkgs/clan-cli/tests/test_api_dataclass_compat.py index 2a5683598..4641da3db 100644 --- a/pkgs/clan-cli/tests/test_api_dataclass_compat.py +++ b/pkgs/clan-cli/tests/test_api_dataclass_compat.py @@ -5,7 +5,7 @@ import sys from dataclasses import is_dataclass from pathlib import Path -from clan_cli.api.util import type_to_dict +from clan_cli.api.util import JSchemaTypeError, type_to_dict from clan_cli.errors import ClanError @@ -100,10 +100,21 @@ def load_dataclass_from_file( if dataclass_type and is_dataclass(dataclass_type): return dataclass_type - return None + + raise ClanError(f"Could not load dataclass {class_name} from file: {file_path}") def test_all_dataclasses() -> None: + """ + This Test ensures that all dataclasses are compatible with the API. + + It will load all dataclasses from the clan_cli directory and + generate a JSON schema for each of them. + + It will fail if any dataclass cannot be converted to JSON schema. + This means the dataclass in its current form is not compatible with the API. + """ + # Excludes: # - API includes Type Generic wrappers, that are not known in the init file. excludes = ["api/__init__.py"] @@ -112,14 +123,24 @@ def test_all_dataclasses() -> None: dataclasses = find_dataclasses_in_directory(cli_path, excludes) for file, dataclass in dataclasses: - print(f"Found dataclass {dataclass} in {file}") - # The parent directory of the clan_cli is the projects root directory + print(f"checking dataclass {dataclass} in file: {file}") try: dclass = load_dataclass_from_file(file, dataclass, str(cli_path.parent)) - json_schema = type_to_dict(dclass, scope=f"FILE {file} {dataclass}") - except Exception as e: + type_to_dict(dclass) + except JSchemaTypeError as e: print(f"Error loading dataclass {dataclass} from {file}: {e}") raise ClanError( - f"Error loading dataclass {dataclass} from {file}: {e}", + f""" +-------------------------------------------------------------------------------- +Error converting dataclass 'class {dataclass}()' from {file} + +Details: + {e} + +Help: +- Converting public fields to PRIVATE by prefixing them with underscore ('_') +- Ensure all private fields are initialized the API wont provide initial values for them. +-------------------------------------------------------------------------------- +""", location=__file__, )