From 57ff3a0e00c9c1997adc593948867addebf8480a Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Sat, 4 Apr 2026 16:33:22 +0900 Subject: [PATCH 01/11] Support JSON files for mapping options --- docs/cli-reference/model-customization.md | 2 + docs/cli-reference/typing-customization.md | 2 + src/datamodel_code_generator/arguments.py | 26 ++++++- tests/main/jsonschema/test_main_jsonschema.py | 75 ++++++++++++++++++- 4 files changed, 100 insertions(+), 5 deletions(-) diff --git a/docs/cli-reference/model-customization.md b/docs/cli-reference/model-customization.md index c9fb7d048..e0ce2d726 100644 --- a/docs/cli-reference/model-customization.md +++ b/docs/cli-reference/model-customization.md @@ -927,6 +927,8 @@ You can specify either a single base class as a string, or multiple base classes - Single: `{"Person": "custom.bases.PersonBase"}` - Multiple: `{"User": ["mixins.AuditMixin", "mixins.TimestampMixin"]}` +You can pass the mapping either inline as JSON or as a path to a JSON file. + When using multiple base classes, the specified classes are used directly without adding `BaseModel`. Ensure your mixins inherit from `BaseModel` if needed. diff --git a/docs/cli-reference/typing-customization.md b/docs/cli-reference/typing-customization.md index cbb022e87..4ae0f5111 100644 --- a/docs/cli-reference/typing-customization.md +++ b/docs/cli-reference/typing-customization.md @@ -1535,6 +1535,8 @@ Override enum/literal generation per-field via JSON mapping. The `--enum-field-as-literal-map` option allows per-field control over whether to generate Literal types or Enum classes. Overrides `--enum-field-as-literal`. +You can pass the mapping either inline as JSON or as a path to a JSON file. + !!! tip "Usage" ```bash diff --git a/src/datamodel_code_generator/arguments.py b/src/datamodel_code_generator/arguments.py index acf2af3cc..2a4c26e71 100644 --- a/src/datamodel_code_generator/arguments.py +++ b/src/datamodel_code_generator/arguments.py @@ -85,6 +85,24 @@ def _external_ref_mapping(value: str) -> str: return value +def _json_value_or_file(value: str) -> object: + """Parse a JSON value or load it from a JSON file path.""" + path = Path(value).expanduser() + if path.is_file(): + try: + json_input = path.read_text(encoding=DEFAULT_ENCODING) + except (OSError, UnicodeDecodeError) as e: + msg = f"Unable to read JSON file {value!r}: {e}" + raise ArgumentTypeError(msg) from e + else: + json_input = value + try: + return json.loads(json_input) + except json.JSONDecodeError as e: + msg = f"Invalid JSON: {e}" + raise ArgumentTypeError(msg) from e + + class SortingHelpFormatter(RawDescriptionHelpFormatter): """Help formatter that sorts arguments, adds color to section headers, and preserves epilog formatting.""" @@ -505,10 +523,10 @@ def start_section(self, heading: str | None) -> None: ) typing_options.add_argument( "--base-class-map", - help="Model-specific base class mapping (JSON). " + help="Model-specific base class mapping (JSON or JSON file path). " 'Example: \'{"MyModel": "custom.BaseA", "OtherModel": "custom.BaseB"}\'. ' "Priority: base-class-map > customBasePath (in schema) > base-class.", - type=json.loads, + type=_json_value_or_file, default=None, ) typing_options.add_argument( @@ -522,11 +540,11 @@ def start_section(self, heading: str | None) -> None: ) typing_options.add_argument( "--enum-field-as-literal-map", - help="Per-field override for enum/literal generation. " + help="Per-field override for enum/literal generation (JSON or JSON file path). " "Format: JSON object mapping field names to 'literal' or 'enum'. " 'Example: \'{"status": "literal", "priority": "enum"}\'. ' "Overrides --enum-field-as-literal for matched fields.", - type=json.loads, + type=_json_value_or_file, default=None, ) typing_options.add_argument( diff --git a/tests/main/jsonschema/test_main_jsonschema.py b/tests/main/jsonschema/test_main_jsonschema.py index a3de51b6a..2cba5ba0f 100644 --- a/tests/main/jsonschema/test_main_jsonschema.py +++ b/tests/main/jsonschema/test_main_jsonschema.py @@ -2981,6 +2981,8 @@ def test_main_jsonschema_custom_base_path(output_file: Path) -> None: - Single: `{"Person": "custom.bases.PersonBase"}` - Multiple: `{"User": ["mixins.AuditMixin", "mixins.TimestampMixin"]}` +You can pass the mapping either inline as JSON or as a path to a JSON file. + When using multiple base classes, the specified classes are used directly without adding `BaseModel`. Ensure your mixins inherit from `BaseModel` if needed.""", input_schema="jsonschema/base_class_map.json", @@ -3010,6 +3012,23 @@ def test_main_jsonschema_base_class_map(output_file: Path) -> None: ) +def test_main_jsonschema_base_class_map_from_file(output_file: Path, tmp_path: Path) -> None: + """Test base_class_map loaded from a JSON file.""" + mapping_path = tmp_path / "base_class_map.json" + mapping_path.write_text( + json.dumps({"Person": "custom.bases.PersonBase", "Animal": "custom.bases.AnimalBase"}), + encoding="utf-8", + ) + run_main_and_assert( + input_path=JSON_SCHEMA_DATA_PATH / "base_class_map.json", + output_path=output_file, + input_file_type="jsonschema", + assert_func=assert_file_content, + expected_file="base_class_map.py", + extra_args=["--base-class-map", str(mapping_path)], + ) + + def test_main_jsonschema_custom_base_paths_list(output_file: Path) -> None: """Test customBasePath with list of base classes.""" run_main_and_assert( @@ -4527,7 +4546,9 @@ def test_main_typed_dict_no_closed(output_file: Path) -> None: option_description="""Override enum/literal generation per-field via JSON mapping. The `--enum-field-as-literal-map` option allows per-field control over whether -to generate Literal types or Enum classes. Overrides `--enum-field-as-literal`.""", +to generate Literal types or Enum classes. Overrides `--enum-field-as-literal`. + +You can pass the mapping either inline as JSON or as a path to a JSON file.""", input_schema="jsonschema/enum_field_as_literal_map.json", cli_args=["--enum-field-as-literal-map", '{"status": "literal"}'], golden_output="jsonschema/enum_field_as_literal_map.py", @@ -4551,6 +4572,20 @@ def test_main_enum_field_as_literal_map(output_file: Path) -> None: ) +def test_main_enum_field_as_literal_map_from_file(output_file: Path, tmp_path: Path) -> None: + """Test enum_field_as_literal_map loaded from a JSON file.""" + mapping_path = tmp_path / "enum_field_as_literal_map.json" + mapping_path.write_text(json.dumps({"status": "literal"}), encoding="utf-8") + run_main_and_assert( + input_path=JSON_SCHEMA_DATA_PATH / "enum_field_as_literal_map.json", + output_path=output_file, + input_file_type=None, + assert_func=assert_file_content, + expected_file="enum_field_as_literal_map.py", + extra_args=["--enum-field-as-literal-map", str(mapping_path)], + ) + + def test_main_enum_field_as_literal_map_override_global(output_file: Path) -> None: """Test --enum-field-as-literal-map overrides global --enum-field-as-literal.""" run_main_and_assert( @@ -4568,6 +4603,44 @@ def test_main_enum_field_as_literal_map_override_global(output_file: Path) -> No ) +def test_main_enum_field_as_literal_map_invalid_json_file(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: + """Test invalid JSON file passed to --enum-field-as-literal-map.""" + mapping_path = tmp_path / "enum_field_as_literal_map.json" + mapping_path.write_text("{invalid json}", encoding="utf-8") + with pytest.raises(SystemExit) as exc_info: + run_main_with_args([ + "--input", + str(JSON_SCHEMA_DATA_PATH / "enum_field_as_literal_map.json"), + "--output", + str(tmp_path / "output.py"), + "--enum-field-as-literal-map", + str(mapping_path), + ]) + assert exc_info.value.code == 2 + captured = capsys.readouterr() + assert "Invalid JSON:" in captured.err + + +def test_main_enum_field_as_literal_map_invalid_encoding_json_file( + tmp_path: Path, capsys: pytest.CaptureFixture[str] +) -> None: + """Test invalid-encoding JSON file passed to --enum-field-as-literal-map.""" + mapping_path = tmp_path / "enum_field_as_literal_map.json" + mapping_path.write_bytes(b"\x80") + with pytest.raises(SystemExit) as exc_info: + run_main_with_args([ + "--input", + str(JSON_SCHEMA_DATA_PATH / "enum_field_as_literal_map.json"), + "--output", + str(tmp_path / "output.py"), + "--enum-field-as-literal-map", + str(mapping_path), + ]) + assert exc_info.value.code == 2 + captured = capsys.readouterr() + assert "Unable to read JSON file" in captured.err + + def test_main_x_enum_field_as_literal(output_file: Path) -> None: """Test x-enum-field-as-literal schema extension for per-field control.""" run_main_and_assert( From ee8d6e850bb81451bcbee48d52a180fcd3738b3a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 4 Apr 2026 07:34:14 +0000 Subject: [PATCH 02/11] docs: update llms.txt files Generated by GitHub Actions --- docs/llms-full.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/llms-full.txt b/docs/llms-full.txt index f4f4f9baf..c00c59194 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -2156,6 +2156,8 @@ You can specify either a single base class as a string, or multiple base classes - Single: `{"Person": "custom.bases.PersonBase"}` - Multiple: `{"User": ["mixins.AuditMixin", "mixins.TimestampMixin"]}` +You can pass the mapping either inline as JSON or as a path to a JSON file. + When using multiple base classes, the specified classes are used directly without adding `BaseModel`. Ensure your mixins inherit from `BaseModel` if needed. @@ -12717,6 +12719,8 @@ Override enum/literal generation per-field via JSON mapping. The `--enum-field-as-literal-map` option allows per-field control over whether to generate Literal types or Enum classes. Overrides `--enum-field-as-literal`. +You can pass the mapping either inline as JSON or as a path to a JSON file. + !!! tip "Usage" ```bash From f779d687f45f7b43eab6735bf308cb810356e2eb Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Sat, 4 Apr 2026 16:46:20 +0900 Subject: [PATCH 03/11] Address PR review feedback --- docs/cli-reference/typing-customization.md | 2 + src/datamodel_code_generator/arguments.py | 8 +++- tests/main/jsonschema/test_main_jsonschema.py | 44 +++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/docs/cli-reference/typing-customization.md b/docs/cli-reference/typing-customization.md index 4ae0f5111..a886425e0 100644 --- a/docs/cli-reference/typing-customization.md +++ b/docs/cli-reference/typing-customization.md @@ -1541,9 +1541,11 @@ You can pass the mapping either inline as JSON or as a path to a JSON file. ```bash datamodel-codegen --input schema.json --enum-field-as-literal-map "{"status": "literal"}" # (1)! + datamodel-codegen --input schema.json --enum-field-as-literal-map ./enum-map.json # (2)! ``` 1. :material-arrow-left: `--enum-field-as-literal-map` - the option documented here + 2. :material-arrow-left: JSON file path containing the mapping ??? example "Examples" diff --git a/src/datamodel_code_generator/arguments.py b/src/datamodel_code_generator/arguments.py index 2a4c26e71..263277129 100644 --- a/src/datamodel_code_generator/arguments.py +++ b/src/datamodel_code_generator/arguments.py @@ -85,7 +85,7 @@ def _external_ref_mapping(value: str) -> str: return value -def _json_value_or_file(value: str) -> object: +def _json_value_or_file(value: str) -> dict[str, object]: """Parse a JSON value or load it from a JSON file path.""" path = Path(value).expanduser() if path.is_file(): @@ -97,10 +97,14 @@ def _json_value_or_file(value: str) -> object: else: json_input = value try: - return json.loads(json_input) + result = json.loads(json_input) except json.JSONDecodeError as e: msg = f"Invalid JSON: {e}" raise ArgumentTypeError(msg) from e + if not isinstance(result, dict): + msg = f"Expected a JSON object, got {type(result).__name__}" + raise ArgumentTypeError(msg) + return result class SortingHelpFormatter(RawDescriptionHelpFormatter): diff --git a/tests/main/jsonschema/test_main_jsonschema.py b/tests/main/jsonschema/test_main_jsonschema.py index 2cba5ba0f..77346924f 100644 --- a/tests/main/jsonschema/test_main_jsonschema.py +++ b/tests/main/jsonschema/test_main_jsonschema.py @@ -3029,6 +3029,50 @@ def test_main_jsonschema_base_class_map_from_file(output_file: Path, tmp_path: P ) +def test_main_jsonschema_base_class_map_from_file_invalid_json( + tmp_path: Path, capsys: pytest.CaptureFixture[str] +) -> None: + """Test invalid JSON file passed to --base-class-map.""" + mapping_path = tmp_path / "base_class_map.json" + mapping_path.write_text("{invalid json}", encoding="utf-8") + with pytest.raises(SystemExit) as exc_info: + run_main_with_args([ + "--input", + str(JSON_SCHEMA_DATA_PATH / "base_class_map.json"), + "--output", + str(tmp_path / "output.py"), + "--input-file-type", + "jsonschema", + "--base-class-map", + str(mapping_path), + ]) + assert exc_info.value.code == 2 + captured = capsys.readouterr() + assert "Invalid JSON:" in captured.err + + +def test_main_jsonschema_base_class_map_from_file_invalid_encoding( + tmp_path: Path, capsys: pytest.CaptureFixture[str] +) -> None: + """Test invalid-encoding JSON file passed to --base-class-map.""" + mapping_path = tmp_path / "base_class_map.json" + mapping_path.write_bytes(b"\x80") + with pytest.raises(SystemExit) as exc_info: + run_main_with_args([ + "--input", + str(JSON_SCHEMA_DATA_PATH / "base_class_map.json"), + "--output", + str(tmp_path / "output.py"), + "--input-file-type", + "jsonschema", + "--base-class-map", + str(mapping_path), + ]) + assert exc_info.value.code == 2 + captured = capsys.readouterr() + assert "Unable to read JSON file" in captured.err + + def test_main_jsonschema_custom_base_paths_list(output_file: Path) -> None: """Test customBasePath with list of base classes.""" run_main_and_assert( From dfab12e168e2ab260bb8846ccd5cd9d632ccc330 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 4 Apr 2026 07:47:19 +0000 Subject: [PATCH 04/11] docs: update llms.txt files Generated by GitHub Actions --- docs/llms-full.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/llms-full.txt b/docs/llms-full.txt index c00c59194..8a15c03d9 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -12725,9 +12725,11 @@ You can pass the mapping either inline as JSON or as a path to a JSON file. ```bash datamodel-codegen --input schema.json --enum-field-as-literal-map "{"status": "literal"}" # (1)! + datamodel-codegen --input schema.json --enum-field-as-literal-map ./enum-map.json # (2)! ``` 1. :material-arrow-left: `--enum-field-as-literal-map` - the option documented here + 2. :material-arrow-left: JSON file path containing the mapping ??? example "Examples" From 124e9f3a7d5408f260d680062a77724505c936b8 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 4 Apr 2026 07:47:44 +0000 Subject: [PATCH 05/11] docs: update CLI reference documentation and prompt data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated by GitHub Actions --- docs/cli-reference/typing-customization.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/cli-reference/typing-customization.md b/docs/cli-reference/typing-customization.md index a886425e0..4ae0f5111 100644 --- a/docs/cli-reference/typing-customization.md +++ b/docs/cli-reference/typing-customization.md @@ -1541,11 +1541,9 @@ You can pass the mapping either inline as JSON or as a path to a JSON file. ```bash datamodel-codegen --input schema.json --enum-field-as-literal-map "{"status": "literal"}" # (1)! - datamodel-codegen --input schema.json --enum-field-as-literal-map ./enum-map.json # (2)! ``` 1. :material-arrow-left: `--enum-field-as-literal-map` - the option documented here - 2. :material-arrow-left: JSON file path containing the mapping ??? example "Examples" From 884636d3d69c7648fe45929bdf8c583e417ad017 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 4 Apr 2026 07:48:03 +0000 Subject: [PATCH 06/11] docs: update llms.txt files Generated by GitHub Actions --- docs/llms-full.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/llms-full.txt b/docs/llms-full.txt index 8a15c03d9..c00c59194 100644 --- a/docs/llms-full.txt +++ b/docs/llms-full.txt @@ -12725,11 +12725,9 @@ You can pass the mapping either inline as JSON or as a path to a JSON file. ```bash datamodel-codegen --input schema.json --enum-field-as-literal-map "{"status": "literal"}" # (1)! - datamodel-codegen --input schema.json --enum-field-as-literal-map ./enum-map.json # (2)! ``` 1. :material-arrow-left: `--enum-field-as-literal-map` - the option documented here - 2. :material-arrow-left: JSON file path containing the mapping ??? example "Examples" From 831b5b8d5fc0b153cac34a305116ec193fc31e2e Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Sat, 4 Apr 2026 16:53:32 +0900 Subject: [PATCH 07/11] Add mapping coverage test --- tests/main/jsonschema/test_main_jsonschema.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/tests/main/jsonschema/test_main_jsonschema.py b/tests/main/jsonschema/test_main_jsonschema.py index 77346924f..8bdc67156 100644 --- a/tests/main/jsonschema/test_main_jsonschema.py +++ b/tests/main/jsonschema/test_main_jsonschema.py @@ -3073,6 +3073,26 @@ def test_main_jsonschema_base_class_map_from_file_invalid_encoding( assert "Unable to read JSON file" in captured.err +def test_main_jsonschema_base_class_map_inline_requires_json_object( + tmp_path: Path, capsys: pytest.CaptureFixture[str] +) -> None: + """Test non-object JSON passed to --base-class-map.""" + with pytest.raises(SystemExit) as exc_info: + run_main_with_args([ + "--input", + str(JSON_SCHEMA_DATA_PATH / "base_class_map.json"), + "--output", + str(tmp_path / "output.py"), + "--input-file-type", + "jsonschema", + "--base-class-map", + '["custom.bases.PersonBase"]', + ]) + assert exc_info.value.code == 2 + captured = capsys.readouterr() + assert "Expected a JSON object" in captured.err + + def test_main_jsonschema_custom_base_paths_list(output_file: Path) -> None: """Test customBasePath with list of base classes.""" run_main_and_assert( From d04179fdf5154a1f66a0c146f0a4a88666aef5ce Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Sat, 4 Apr 2026 16:58:58 +0900 Subject: [PATCH 08/11] Add enum map object validation test --- tests/main/jsonschema/test_main_jsonschema.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/main/jsonschema/test_main_jsonschema.py b/tests/main/jsonschema/test_main_jsonschema.py index 8bdc67156..5cc68431a 100644 --- a/tests/main/jsonschema/test_main_jsonschema.py +++ b/tests/main/jsonschema/test_main_jsonschema.py @@ -4705,6 +4705,24 @@ def test_main_enum_field_as_literal_map_invalid_encoding_json_file( assert "Unable to read JSON file" in captured.err +def test_main_enum_field_as_literal_map_inline_requires_json_object( + tmp_path: Path, capsys: pytest.CaptureFixture[str] +) -> None: + """Test non-object JSON passed to --enum-field-as-literal-map.""" + with pytest.raises(SystemExit) as exc_info: + run_main_with_args([ + "--input", + str(JSON_SCHEMA_DATA_PATH / "enum_field_as_literal_map.json"), + "--output", + str(tmp_path / "output.py"), + "--enum-field-as-literal-map", + '["literal"]', + ]) + assert exc_info.value.code == 2 + captured = capsys.readouterr() + assert "Expected a JSON object" in captured.err + + def test_main_x_enum_field_as_literal(output_file: Path) -> None: """Test x-enum-field-as-literal schema extension for per-field control.""" run_main_and_assert( From 91bf203c207de7e7ad6dad32cd51ffa80225060c Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Sat, 4 Apr 2026 17:33:44 +0900 Subject: [PATCH 09/11] Extract CLI error test helper --- tests/main/jsonschema/test_main_jsonschema.py | 82 +++++++++++-------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/tests/main/jsonschema/test_main_jsonschema.py b/tests/main/jsonschema/test_main_jsonschema.py index 5cc68431a..76d12f4f1 100644 --- a/tests/main/jsonschema/test_main_jsonschema.py +++ b/tests/main/jsonschema/test_main_jsonschema.py @@ -52,6 +52,16 @@ FixtureRequest = pytest.FixtureRequest +def _assert_run_main_with_args_error( + args: list[str], capsys: pytest.CaptureFixture[str], expected_error: str +) -> None: + with pytest.raises(SystemExit) as exc_info: + run_main_with_args(args) + assert exc_info.value.code == 2 + captured = capsys.readouterr() + assert expected_error in captured.err + + def _install_test_my_app(base_dir: Path, monkeypatch: pytest.MonkeyPatch) -> None: package_dir = base_dir / "my_app" package_dir.mkdir() @@ -3035,8 +3045,8 @@ def test_main_jsonschema_base_class_map_from_file_invalid_json( """Test invalid JSON file passed to --base-class-map.""" mapping_path = tmp_path / "base_class_map.json" mapping_path.write_text("{invalid json}", encoding="utf-8") - with pytest.raises(SystemExit) as exc_info: - run_main_with_args([ + _assert_run_main_with_args_error( + [ "--input", str(JSON_SCHEMA_DATA_PATH / "base_class_map.json"), "--output", @@ -3045,10 +3055,10 @@ def test_main_jsonschema_base_class_map_from_file_invalid_json( "jsonschema", "--base-class-map", str(mapping_path), - ]) - assert exc_info.value.code == 2 - captured = capsys.readouterr() - assert "Invalid JSON:" in captured.err + ], + capsys, + "Invalid JSON:", + ) def test_main_jsonschema_base_class_map_from_file_invalid_encoding( @@ -3057,8 +3067,8 @@ def test_main_jsonschema_base_class_map_from_file_invalid_encoding( """Test invalid-encoding JSON file passed to --base-class-map.""" mapping_path = tmp_path / "base_class_map.json" mapping_path.write_bytes(b"\x80") - with pytest.raises(SystemExit) as exc_info: - run_main_with_args([ + _assert_run_main_with_args_error( + [ "--input", str(JSON_SCHEMA_DATA_PATH / "base_class_map.json"), "--output", @@ -3067,18 +3077,18 @@ def test_main_jsonschema_base_class_map_from_file_invalid_encoding( "jsonschema", "--base-class-map", str(mapping_path), - ]) - assert exc_info.value.code == 2 - captured = capsys.readouterr() - assert "Unable to read JSON file" in captured.err + ], + capsys, + "Unable to read JSON file", + ) def test_main_jsonschema_base_class_map_inline_requires_json_object( tmp_path: Path, capsys: pytest.CaptureFixture[str] ) -> None: """Test non-object JSON passed to --base-class-map.""" - with pytest.raises(SystemExit) as exc_info: - run_main_with_args([ + _assert_run_main_with_args_error( + [ "--input", str(JSON_SCHEMA_DATA_PATH / "base_class_map.json"), "--output", @@ -3087,10 +3097,10 @@ def test_main_jsonschema_base_class_map_inline_requires_json_object( "jsonschema", "--base-class-map", '["custom.bases.PersonBase"]', - ]) - assert exc_info.value.code == 2 - captured = capsys.readouterr() - assert "Expected a JSON object" in captured.err + ], + capsys, + "Expected a JSON object", + ) def test_main_jsonschema_custom_base_paths_list(output_file: Path) -> None: @@ -4671,18 +4681,18 @@ def test_main_enum_field_as_literal_map_invalid_json_file(tmp_path: Path, capsys """Test invalid JSON file passed to --enum-field-as-literal-map.""" mapping_path = tmp_path / "enum_field_as_literal_map.json" mapping_path.write_text("{invalid json}", encoding="utf-8") - with pytest.raises(SystemExit) as exc_info: - run_main_with_args([ + _assert_run_main_with_args_error( + [ "--input", str(JSON_SCHEMA_DATA_PATH / "enum_field_as_literal_map.json"), "--output", str(tmp_path / "output.py"), "--enum-field-as-literal-map", str(mapping_path), - ]) - assert exc_info.value.code == 2 - captured = capsys.readouterr() - assert "Invalid JSON:" in captured.err + ], + capsys, + "Invalid JSON:", + ) def test_main_enum_field_as_literal_map_invalid_encoding_json_file( @@ -4691,36 +4701,36 @@ def test_main_enum_field_as_literal_map_invalid_encoding_json_file( """Test invalid-encoding JSON file passed to --enum-field-as-literal-map.""" mapping_path = tmp_path / "enum_field_as_literal_map.json" mapping_path.write_bytes(b"\x80") - with pytest.raises(SystemExit) as exc_info: - run_main_with_args([ + _assert_run_main_with_args_error( + [ "--input", str(JSON_SCHEMA_DATA_PATH / "enum_field_as_literal_map.json"), "--output", str(tmp_path / "output.py"), "--enum-field-as-literal-map", str(mapping_path), - ]) - assert exc_info.value.code == 2 - captured = capsys.readouterr() - assert "Unable to read JSON file" in captured.err + ], + capsys, + "Unable to read JSON file", + ) def test_main_enum_field_as_literal_map_inline_requires_json_object( tmp_path: Path, capsys: pytest.CaptureFixture[str] ) -> None: """Test non-object JSON passed to --enum-field-as-literal-map.""" - with pytest.raises(SystemExit) as exc_info: - run_main_with_args([ + _assert_run_main_with_args_error( + [ "--input", str(JSON_SCHEMA_DATA_PATH / "enum_field_as_literal_map.json"), "--output", str(tmp_path / "output.py"), "--enum-field-as-literal-map", '["literal"]', - ]) - assert exc_info.value.code == 2 - captured = capsys.readouterr() - assert "Expected a JSON object" in captured.err + ], + capsys, + "Expected a JSON object", + ) def test_main_x_enum_field_as_literal(output_file: Path) -> None: From ed498a7df27f11364213e7b6450a7765e76c05fc Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Sat, 4 Apr 2026 17:54:05 +0900 Subject: [PATCH 10/11] Rename CLI error test helper --- tests/main/jsonschema/test_main_jsonschema.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/main/jsonschema/test_main_jsonschema.py b/tests/main/jsonschema/test_main_jsonschema.py index 76d12f4f1..1af742edf 100644 --- a/tests/main/jsonschema/test_main_jsonschema.py +++ b/tests/main/jsonschema/test_main_jsonschema.py @@ -52,7 +52,7 @@ FixtureRequest = pytest.FixtureRequest -def _assert_run_main_with_args_error( +def assert_run_main_with_args_error( args: list[str], capsys: pytest.CaptureFixture[str], expected_error: str ) -> None: with pytest.raises(SystemExit) as exc_info: @@ -3045,7 +3045,7 @@ def test_main_jsonschema_base_class_map_from_file_invalid_json( """Test invalid JSON file passed to --base-class-map.""" mapping_path = tmp_path / "base_class_map.json" mapping_path.write_text("{invalid json}", encoding="utf-8") - _assert_run_main_with_args_error( + assert_run_main_with_args_error( [ "--input", str(JSON_SCHEMA_DATA_PATH / "base_class_map.json"), @@ -3067,7 +3067,7 @@ def test_main_jsonschema_base_class_map_from_file_invalid_encoding( """Test invalid-encoding JSON file passed to --base-class-map.""" mapping_path = tmp_path / "base_class_map.json" mapping_path.write_bytes(b"\x80") - _assert_run_main_with_args_error( + assert_run_main_with_args_error( [ "--input", str(JSON_SCHEMA_DATA_PATH / "base_class_map.json"), @@ -3087,7 +3087,7 @@ def test_main_jsonschema_base_class_map_inline_requires_json_object( tmp_path: Path, capsys: pytest.CaptureFixture[str] ) -> None: """Test non-object JSON passed to --base-class-map.""" - _assert_run_main_with_args_error( + assert_run_main_with_args_error( [ "--input", str(JSON_SCHEMA_DATA_PATH / "base_class_map.json"), @@ -4681,7 +4681,7 @@ def test_main_enum_field_as_literal_map_invalid_json_file(tmp_path: Path, capsys """Test invalid JSON file passed to --enum-field-as-literal-map.""" mapping_path = tmp_path / "enum_field_as_literal_map.json" mapping_path.write_text("{invalid json}", encoding="utf-8") - _assert_run_main_with_args_error( + assert_run_main_with_args_error( [ "--input", str(JSON_SCHEMA_DATA_PATH / "enum_field_as_literal_map.json"), @@ -4701,7 +4701,7 @@ def test_main_enum_field_as_literal_map_invalid_encoding_json_file( """Test invalid-encoding JSON file passed to --enum-field-as-literal-map.""" mapping_path = tmp_path / "enum_field_as_literal_map.json" mapping_path.write_bytes(b"\x80") - _assert_run_main_with_args_error( + assert_run_main_with_args_error( [ "--input", str(JSON_SCHEMA_DATA_PATH / "enum_field_as_literal_map.json"), @@ -4719,7 +4719,7 @@ def test_main_enum_field_as_literal_map_inline_requires_json_object( tmp_path: Path, capsys: pytest.CaptureFixture[str] ) -> None: """Test non-object JSON passed to --enum-field-as-literal-map.""" - _assert_run_main_with_args_error( + assert_run_main_with_args_error( [ "--input", str(JSON_SCHEMA_DATA_PATH / "enum_field_as_literal_map.json"), From 990893f7aea854a4ea61a13fd347b4230a05e6a7 Mon Sep 17 00:00:00 2001 From: Koudai Aono Date: Sat, 4 Apr 2026 17:57:23 +0900 Subject: [PATCH 11/11] Add helper docstring --- tests/main/jsonschema/test_main_jsonschema.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/main/jsonschema/test_main_jsonschema.py b/tests/main/jsonschema/test_main_jsonschema.py index 1af742edf..f811b78df 100644 --- a/tests/main/jsonschema/test_main_jsonschema.py +++ b/tests/main/jsonschema/test_main_jsonschema.py @@ -52,9 +52,8 @@ FixtureRequest = pytest.FixtureRequest -def assert_run_main_with_args_error( - args: list[str], capsys: pytest.CaptureFixture[str], expected_error: str -) -> None: +def assert_run_main_with_args_error(args: list[str], capsys: pytest.CaptureFixture[str], expected_error: str) -> None: + """Assert that running the CLI exits with code 2 and emits the expected error.""" with pytest.raises(SystemExit) as exc_info: run_main_with_args(args) assert exc_info.value.code == 2