Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -120,23 +120,34 @@ jobs:
pattern: .coverage.*
merge-multiple: true
- name: Combine and report coverage
id: coverage
run: tox run -e coverage --skip-uv-sync --skip-pkg-install
continue-on-error: true
env:
UV_PYTHON_PREFERENCE: only-managed
- name: Show uncovered lines on failure
if: steps.coverage.outcome == 'failure'
run: |
echo "::error::Coverage check failed. Lines not covered:"
COVERAGE_FILE=.tox/.coverage .tox/coverage/bin/coverage report --show-missing --fail-under=0 | grep -v "100%"
- name: Upload HTML report
if: always()
uses: actions/upload-artifact@v4
with:
name: html-report
path: .tox/htmlcov
- name: Upload coverage to Codecov
if: success() || failure()
if: always()
uses: codecov/codecov-action@v5
with:
flags: unittests
files: .tox/coverage.xml
fail_ci_if_error: true
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
- name: Fail if coverage check failed
if: steps.coverage.outcome == 'failure'
run: exit 1

check:
name: ${{ matrix.tox_env }}
Expand Down
2 changes: 1 addition & 1 deletion docs/cli-reference/field-customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -3912,7 +3912,7 @@ This is useful when schemas have descriptive titles that should be preserved.
class ProcessingStatusUnionTitle(BaseModel):
__root__: (
ProcessingStatusDetail | ExtendedProcessingTask | ProcessingStatusTitle
) = Field(..., title='Processing Status Union Title')
) = Field('COMPLETED', title='Processing Status Union Title')


class ProcessingTaskTitle(BaseModel):
Expand Down
2 changes: 1 addition & 1 deletion docs/llms-full.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11680,7 +11680,7 @@ This is useful when schemas have descriptive titles that should be preserved.
class ProcessingStatusUnionTitle(BaseModel):
__root__: (
ProcessingStatusDetail | ExtendedProcessingTask | ProcessingStatusTitle
) = Field(..., title='Processing Status Union Title')
) = Field('COMPLETED', title='Processing Status Union Title')


class ProcessingTaskTitle(BaseModel):
Expand Down
4 changes: 2 additions & 2 deletions src/datamodel_code_generator/model/pydantic/base_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ def __str__(self) -> str: # noqa: PLR0912
elif isinstance(discriminator, dict): # pragma: no cover
data["discriminator"] = discriminator["propertyName"]

if self.required and not self.has_default:
if self.required:
default_factory = None
elif self.default is not UNDEFINED and self.default is not None and "default_factory" not in data:
default_factory = self._get_default_as_pydantic_model()
Expand Down Expand Up @@ -249,7 +249,7 @@ def __str__(self) -> str: # noqa: PLR0912

if self.use_annotated:
field_arguments = self._process_annotated_field_arguments(field_arguments)
elif self.required and not default_factory:
elif self.required:
field_arguments = ["...", *field_arguments]
elif not default_factory:
default_repr = repr_set_sorted(self.default) if isinstance(self.default, set) else repr(self.default)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class {{ class_name }}({{ base_class }}):{% if comment is defined %} # {{ comme
{%- else %}
__root__: {{ field.type_hint }}
{%- endif %}
{%- if not field.has_default_factory_in_field and not ((field.required and not field.has_default) or (field.represented_default == 'None' and field.strip_default_none))
{%- if not field.has_default_factory_in_field and not (field.required or (field.represented_default == 'None' and field.strip_default_none))
%} = {{ field.represented_default }}
{%- endif -%}
{%- endif %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class {{ class_name }}({{ base_class }}{%- if fields -%}[{{get_type_hint(fields,
{%- else %}
root: {{ field.type_hint }}
{%- endif %}
{%- if not field.has_default_factory_in_field and not ((field.required and not field.has_default) or (field.represented_default == 'None' and field.strip_default_none))
{%- if not field.has_default_factory_in_field and not (field.required or (field.represented_default == 'None' and field.strip_default_none))
%} = {{ field.represented_default }}
{%- endif -%}
{%- endif %}
Expand Down
69 changes: 35 additions & 34 deletions src/datamodel_code_generator/parser/jsonschema.py
Original file line number Diff line number Diff line change
Expand Up @@ -903,7 +903,7 @@ def _create_synthetic_enum_obj(
title=original.title,
description=original.description,
x_enum_varnames=final_varnames,
default=original.default if original.has_default else None,
**({"default": original.default} if original.has_default else {}),
)

def is_constraints_field(self, obj: JsonSchemaObject) -> bool:
Expand Down Expand Up @@ -1528,22 +1528,6 @@ def _apply_title_as_name(self, name: str, obj: JsonSchemaObject) -> str:
return sanitize_module_name(obj.title, treat_dot_as_module=self.treat_dot_as_module)
return name

def _should_field_be_required(
self,
*,
in_required_list: bool = True,
has_default: bool = False,
is_nullable: bool = False,
) -> bool:
"""Determine if a field should be marked as required."""
if self.force_optional_for_required_fields:
return False
if self.apply_default_values_for_required_fields and has_default: # pragma: no cover
return False
if is_nullable:
return False
return in_required_list

def _deep_merge(self, dict1: dict[Any, Any], dict2: dict[Any, Any]) -> dict[Any, Any]:
"""Deep merge two dictionaries, combining nested dicts and lists."""
result = dict1.copy()
Expand Down Expand Up @@ -3054,7 +3038,7 @@ def parse_list_item(
for index, item in enumerate(target_items)
]

def parse_array_fields( # noqa: PLR0912
def parse_array_fields( # noqa: PLR0912, PLR0915
self,
name: str,
obj: JsonSchemaObject,
Expand All @@ -3069,12 +3053,17 @@ def parse_array_fields( # noqa: PLR0912
required: bool = False
nullable: Optional[bool] = None # noqa: UP045
else:
required = not (obj.has_default and self.apply_default_values_for_required_fields)
required = not obj.has_default
if self.strict_nullable:
nullable = obj.nullable if obj.has_default or required else True
else:
required = not obj.nullable and required
nullable = None
if obj.nullable:
nullable = True
elif obj.has_default:
nullable = False
else:
nullable = None
is_tuple = False
suppress_item_constraints = False
if isinstance(obj.items, JsonSchemaObject):
Expand Down Expand Up @@ -3259,10 +3248,27 @@ def parse_root_type( # noqa: PLR0912, PLR0914, PLR0915
data_type = self.data_type_manager.get_data_type(
Types.any,
)
required = self._should_field_be_required(
has_default=obj.has_default,
is_nullable=bool(obj.nullable),
)
is_type_alias = self.data_model_root_type.IS_ALIAS
if self.force_optional_for_required_fields:
required = False
nullable = None
has_default_override = True
default_value = obj.default if obj.has_default else None
elif obj.nullable:
required = False
nullable = True
has_default_override = True
default_value = obj.default if obj.has_default else None
elif obj.has_default and not is_type_alias:
required = False
nullable = False
has_default_override = True
default_value = obj.default
else:
required = True
nullable = None
has_default_override = obj.has_default
default_value = obj.default if obj.has_default else UNDEFINED
name = self._apply_title_as_name(name, obj)
if not reference:
reference = self.model_resolver.add(path, name, loaded=True, class_name=True)
Expand All @@ -3276,20 +3282,18 @@ def parse_root_type( # noqa: PLR0912, PLR0914, PLR0915
fields=[
self.data_model_field_type(
data_type=data_type,
default=obj.default,
default=default_value,
required=required,
constraints=constraints,
nullable=obj.nullable
if self.strict_nullable and obj.nullable is not None
else (False if self.strict_nullable and obj.has_default else None),
nullable=nullable,
strip_default_none=self.strip_default_none,
extras=self.get_field_extras(obj),
use_annotated=self.use_annotated,
use_field_description=self.use_field_description,
use_field_description_example=self.use_field_description_example,
use_inline_field_description=self.use_inline_field_description,
original_name=None,
has_default=obj.has_default,
has_default=has_default_override,
)
],
custom_base_class=self._resolve_base_class(name, obj.custom_base_path),
Expand All @@ -3298,7 +3302,7 @@ def parse_root_type( # noqa: PLR0912, PLR0914, PLR0915
path=self.current_source_path,
nullable=obj.type_has_null,
treat_dot_as_module=self.treat_dot_as_module,
default=obj.default if obj.has_default else UNDEFINED,
default=default_value if has_default_override else UNDEFINED,
)
self.results.append(data_model_root_type)
return self.data_type(reference=reference)
Expand Down Expand Up @@ -3326,10 +3330,7 @@ def _parse_multiple_types_with_properties(
)

is_nullable = obj.nullable or obj.type_has_null
required = self._should_field_be_required(
has_default=obj.has_default,
is_nullable=bool(is_nullable),
)
required = not (self.force_optional_for_required_fields or is_nullable)

reference = self.model_resolver.add(path, name, loaded=True, class_name=True)
self._set_schema_metadata(reference.path, obj)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class ExtendedProcessingTasksTitle(BaseModel):
class ProcessingStatusUnionTitle(BaseModel):
__root__: (
ProcessingStatusDetail | ExtendedProcessingTask | ProcessingStatusTitle
) = Field(..., title='Processing Status Union Title')
) = Field('COMPLETED', title='Processing Status Union Title')


class ProcessingTaskTitle(BaseModel):
Expand Down
Loading