From b0c40717a9253ab6b26d8b8eaa3b43866143d697 Mon Sep 17 00:00:00 2001 From: Fabrice Normandin Date: Thu, 8 Feb 2024 12:35:24 -0500 Subject: [PATCH] Update example, fix descriptions for top-level DC Signed-off-by: Fabrice Normandin --- examples/config_files/.schemas/.gitignore | 1 - .../config_files/.schemas/Bob_schema.json | 13 ++++ .../config_files/.schemas/Nested_schema.json | 35 ++++++++++ examples/config_files/bob_with_schema.yaml | 2 + .../{nested.yaml => nested_with_schema.yaml} | 0 examples/config_files/schema_example.py | 10 ++- .../helpers/serialization/yaml_schema.py | 68 +++++++++++-------- 7 files changed, 98 insertions(+), 31 deletions(-) delete mode 100644 examples/config_files/.schemas/.gitignore create mode 100644 examples/config_files/.schemas/Bob_schema.json create mode 100644 examples/config_files/.schemas/Nested_schema.json create mode 100644 examples/config_files/bob_with_schema.yaml rename examples/config_files/{nested.yaml => nested_with_schema.yaml} (100%) diff --git a/examples/config_files/.schemas/.gitignore b/examples/config_files/.schemas/.gitignore deleted file mode 100644 index cc40a400..00000000 --- a/examples/config_files/.schemas/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*_schema.json diff --git a/examples/config_files/.schemas/Bob_schema.json b/examples/config_files/.schemas/Bob_schema.json new file mode 100644 index 00000000..32e525a1 --- /dev/null +++ b/examples/config_files/.schemas/Bob_schema.json @@ -0,0 +1,13 @@ +{ + "properties": { + "foo": { + "default": 123, + "title": "Foo", + "type": "integer", + "description": "A very important field." + } + }, + "title": "Bob", + "type": "object", + "description": "Some docstring." +} diff --git a/examples/config_files/.schemas/Nested_schema.json b/examples/config_files/.schemas/Nested_schema.json new file mode 100644 index 00000000..6bb14e42 --- /dev/null +++ b/examples/config_files/.schemas/Nested_schema.json @@ -0,0 +1,35 @@ +{ + "$defs": { + "Bob": { + "properties": { + "foo": { + "default": 123, + "title": "Foo", + "type": "integer", + "description": "A very important field." + } + }, + "title": "Bob", + "type": "object", + "description": "Some docstring." + } + }, + "properties": { + "bob": { + "$ref": "#/$defs/Bob", + "description": "bobobobo." + }, + "other_field": { + "title": "Other Field", + "type": "string", + "description": "This is a docstring for the other field." + } + }, + "required": [ + "bob", + "other_field" + ], + "title": "Nested", + "type": "object", + "description": "Some docstring of the 'Nested' class." +} diff --git a/examples/config_files/bob_with_schema.yaml b/examples/config_files/bob_with_schema.yaml new file mode 100644 index 00000000..f7c95f80 --- /dev/null +++ b/examples/config_files/bob_with_schema.yaml @@ -0,0 +1,2 @@ +# yaml-language-server: $schema=.schemas/Bob_schema.json +foo: 222 diff --git a/examples/config_files/nested.yaml b/examples/config_files/nested_with_schema.yaml similarity index 100% rename from examples/config_files/nested.yaml rename to examples/config_files/nested_with_schema.yaml diff --git a/examples/config_files/schema_example.py b/examples/config_files/schema_example.py index 8fcf0238..67931acd 100644 --- a/examples/config_files/schema_example.py +++ b/examples/config_files/schema_example.py @@ -1,7 +1,6 @@ from dataclasses import dataclass from pathlib import Path -from simple_parsing.helpers.serialization.serializable import load_yaml from simple_parsing.helpers.serialization.yaml_schema import save_yaml_with_schema @@ -15,6 +14,8 @@ class Bob: @dataclass class Nested: + """Some docstring of the 'Nested' class.""" + bob: Bob # inline comment for field `bob` of class `Nested` """bobobobo.""" @@ -23,7 +24,12 @@ class Nested: if __name__ == "__main__": + save_yaml_with_schema( + Bob(foo=222), + Path(__file__).parent / "bob_with_schema.yaml", + ) + save_yaml_with_schema( Nested(bob=Bob(foo=222), other_field="babab"), - Path(__file__).parent / "nested.yaml", + Path(__file__).parent / "nested_with_schema.yaml", ) diff --git a/simple_parsing/helpers/serialization/yaml_schema.py b/simple_parsing/helpers/serialization/yaml_schema.py index dfadcadc..62b8c606 100644 --- a/simple_parsing/helpers/serialization/yaml_schema.py +++ b/simple_parsing/helpers/serialization/yaml_schema.py @@ -41,7 +41,7 @@ def save_yaml_with_schema( generated_schemas_dir = path.parent / ".schemas" generated_schemas_dir.mkdir(exist_ok=True, parents=True) schema_file = generated_schemas_dir / dc_schema_filename - schema_file.write_text(json.dumps(json_schema, indent=2)) + schema_file.write_text(json.dumps(json_schema, indent=2) + "\n") if repo_root: repo_root, _ = _try_make_relative(repo_root, relative_to=Path.cwd()) @@ -103,9 +103,9 @@ def save_yaml_with_schema_in_vscode_settings( _write_gitignore_file_for_schemas(generated_schemas_dir) schema_file = generated_schemas_dir / dc_schema_filename - schema_file.write_text(json.dumps(json_schema, indent=2)) + schema_file.write_text(json.dumps(json_schema, indent=2) + "\n") - # Alternatively: we can also use a setting in the VsCode editor to associate a schema file with + # We can use a setting in the VsCode editor to associate a schema file with # a list of config files. vscode_dir = repo_root / ".vscode" @@ -119,7 +119,9 @@ def save_yaml_with_schema_in_vscode_settings( logger.error("Unable to load the vscode settings file!") raise - yaml_schemas: dict[str, str | list[str]] = vscode_settings.setdefault("yaml.schemas", {}) + yaml_schemas_setting: dict[str, str | list[str]] = vscode_settings.setdefault( + "yaml.schemas", {} + ) schema_key = str(schema_file.relative_to(repo_root)) try: @@ -127,13 +129,13 @@ def save_yaml_with_schema_in_vscode_settings( except ValueError: path_to_add = str(path) - files_associated_with_schema: str | list[str] = yaml_schemas.get(schema_key, []) + files_associated_with_schema: str | list[str] = yaml_schemas_setting.get(schema_key, []) if isinstance(files_associated_with_schema, str): existing_value = files_associated_with_schema files_associated_with_schema = sorted(set([existing_value, path_to_add])) else: files_associated_with_schema = sorted(set(files_associated_with_schema + [path_to_add])) - yaml_schemas[schema_key] = files_associated_with_schema + yaml_schemas_setting[schema_key] = files_associated_with_schema vscode_settings_file.write_text(json.dumps(vscode_settings, indent=2)) return schema_file @@ -173,6 +175,16 @@ def _has_default_dataclass_docstring(dc_type: type[Dataclass]) -> bool: return bool(docstring) and docstring.startswith(f"{dc_type.__name__}(") +def _get_dc_type_with_name(dataclass_name: str) -> type[Dataclass] | None: + # Get the dataclass type has this classname. + frame = inspect.currentframe() + assert frame + for frame_info in inspect.getouterframes(frame): + if is_dataclass_type(definition_dc_type := frame_info.frame.f_globals.get(dataclass_name)): + return definition_dc_type + return None + + def _update_schema_with_descriptions( dc: Dataclass, json_schema: PossiblyNestedDict[str, str | list[str]], @@ -181,29 +193,29 @@ def _update_schema_with_descriptions( if not inplace: json_schema = copy.deepcopy(json_schema) - definitions = json_schema["$defs"] - assert isinstance(definitions, dict) - for classname, definition in definitions.items(): - if classname == type(dc).__name__: - definition_dc_type = type(dc) - else: - # Get the dataclass type has this classname. - frame = inspect.currentframe() - assert frame - outer_frames = inspect.getouterframes(frame) - for frame in outer_frames: - if classname in frame.frame.f_globals and is_dataclass_type( - definition_dc_type := frame.frame.f_globals[classname] - ): - break + if "$defs" in json_schema: + definitions = json_schema["$defs"] + assert isinstance(definitions, dict) + for classname, definition in definitions.items(): + if classname == type(dc).__name__: + definition_dc_type = type(dc) else: - logger.debug( - f"Unable to find the dataclass type for {classname} in the caller globals." - ) - continue - - assert isinstance(definition, dict) - _update_definition_in_schema_using_dc(definition, dc_type=definition_dc_type) + # Get the dataclass type has this classname. + frame = inspect.currentframe() + assert frame + definition_dc_type = _get_dc_type_with_name(classname) + if not definition_dc_type: + logger.debug( + f"Unable to find the dataclass type for {classname} in the caller globals." + f"Not adding descriptions for this dataclass." + ) + continue + + assert isinstance(definition, dict) + _update_definition_in_schema_using_dc(definition, dc_type=definition_dc_type) + + if "properties" in json_schema: + _update_definition_in_schema_using_dc(json_schema, dc_type=type(dc)) return json_schema