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
3 changes: 2 additions & 1 deletion docs/cli-reference/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ This documentation is auto-generated from test cases.
| Category | Options | Description |
|----------|---------|-------------|
| 📁 [Base Options](base-options.md) | 5 | Input/output configuration |
| 🔧 [Typing Customization](typing-customization.md) | 16 | Type annotation and import behavior |
| 🔧 [Typing Customization](typing-customization.md) | 17 | Type annotation and import behavior |
| 🏷️ [Field Customization](field-customization.md) | 20 | Field naming and docstring behavior |
| 🏗️ [Model Customization](model-customization.md) | 26 | Model generation behavior |
| 🎨 [Template Customization](template-customization.md) | 16 | Output formatting and custom rendering |
Expand Down Expand Up @@ -92,6 +92,7 @@ This documentation is auto-generated from test cases.

### I {#i}

- [`--ignore-enum-constraints`](typing-customization.md#ignore-enum-constraints)
- [`--ignore-pyproject`](general-options.md#ignore-pyproject)
- [`--include-path-parameters`](openapi-only-options.md#include-path-parameters)
- [`--input`](base-options.md#input)
Expand Down
2 changes: 2 additions & 0 deletions docs/cli-reference/quick-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ datamodel-codegen [OPTIONS]
| [`--allof-merge-mode`](typing-customization.md#allof-merge-mode) | Merge constraints from root model references in allOf schemas. |
| [`--disable-future-imports`](typing-customization.md#disable-future-imports) | Prevent automatic addition of __future__ imports in generated code. |
| [`--enum-field-as-literal`](typing-customization.md#enum-field-as-literal) | Convert all enum fields to Literal types instead of Enum classes. |
| [`--ignore-enum-constraints`](typing-customization.md#ignore-enum-constraints) | Ignore enum constraints and use base string type instead of Enum classes. |
| [`--no-use-specialized-enum`](typing-customization.md#no-use-specialized-enum) | Disable specialized Enum classes for Python 3.11+ code generation. |
| [`--output-datetime-class`](typing-customization.md#output-datetime-class) | Specify datetime class type for date-time schema fields. |
| [`--strict-types`](typing-customization.md#strict-types) | Enable strict type validation for specified Python types. |
Expand Down Expand Up @@ -210,6 +211,7 @@ All options sorted alphabetically:
- [`--http-headers`](general-options.md#http-headers) - Fetch schema from URL with custom HTTP headers.
- [`--http-ignore-tls`](general-options.md#http-ignore-tls) - Disable TLS certificate verification for HTTPS requests.
- [`--http-query-parameters`](general-options.md#http-query-parameters) - Add query parameters to HTTP requests for remote schemas.
- [`--ignore-enum-constraints`](typing-customization.md#ignore-enum-constraints) - Ignore enum constraints and use base string type instead of ...
- [`--ignore-pyproject`](general-options.md#ignore-pyproject) - Ignore pyproject.toml configuration file.
- [`--include-path-parameters`](openapi-only-options.md#include-path-parameters) - Include OpenAPI path parameters in generated parameter model...
- [`--input`](base-options.md#input) - Specify the input schema file path.
Expand Down
130 changes: 130 additions & 0 deletions docs/cli-reference/typing-customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
| [`--allof-merge-mode`](#allof-merge-mode) | Merge constraints from root model references in allOf schema... |
| [`--disable-future-imports`](#disable-future-imports) | Prevent automatic addition of __future__ imports in generate... |
| [`--enum-field-as-literal`](#enum-field-as-literal) | Convert all enum fields to Literal types instead of Enum cla... |
| [`--ignore-enum-constraints`](#ignore-enum-constraints) | Ignore enum constraints and use base string type instead of ... |
| [`--no-use-specialized-enum`](#no-use-specialized-enum) | Disable specialized Enum classes for Python 3.11+ code gener... |
| [`--output-datetime-class`](#output-datetime-class) | Specify datetime class type for date-time schema fields. |
| [`--strict-types`](#strict-types) | Enable strict type validation for specified Python types. |
Expand Down Expand Up @@ -1191,6 +1192,135 @@ of Enum classes for all enumerations.

---

## `--ignore-enum-constraints` {#ignore-enum-constraints}

Ignore enum constraints and use base string type instead of Enum classes.

The `--ignore-enum-constraints` flag ignores enum constraints and uses
the base type (str) instead of generating Enum classes. This is useful
when you need flexibility in the values a field can accept beyond the
defined enum members.

!!! tip "Usage"

```bash
datamodel-codegen --input schema.json --ignore-enum-constraints # (1)!
```

1. :material-arrow-left: `--ignore-enum-constraints` - the option documented here

??? example "Examples"

**Input Schema:**

```graphql
"Employee shift status"
enum EmployeeShiftStatus {
"not on shift"
NOT_ON_SHIFT
"on shift"
ON_SHIFT
}

enum Color {
RED
GREEN
BLUE
}

enum EnumWithOneField {
FIELD
}
```

**Output:**

=== "With Option"

```python
# generated by datamodel-codegen:
# filename: enums.graphql
# timestamp: 2019-07-26T00:00:00+00:00

from __future__ import annotations

from pydantic import BaseModel
from typing_extensions import TypeAlias

Boolean: TypeAlias = bool
"""
The `Boolean` scalar type represents `true` or `false`.
"""


String: TypeAlias = str
"""
The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.
"""


class Color(BaseModel):
__root__: str


class EmployeeShiftStatus(BaseModel):
"""
Employee shift status
"""

__root__: str


class EnumWithOneField(BaseModel):
__root__: str
```

=== "Without Option"

```python
# generated by datamodel-codegen:
# filename: enums.graphql
# timestamp: 2019-07-26T00:00:00+00:00

from __future__ import annotations

from enum import Enum

from typing_extensions import TypeAlias

Boolean: TypeAlias = bool
"""
The `Boolean` scalar type represents `true` or `false`.
"""


String: TypeAlias = str
"""
The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.
"""


class Color(Enum):
BLUE = 'BLUE'
GREEN = 'GREEN'
RED = 'RED'


class EmployeeShiftStatus(Enum):
"""
Employee shift status
"""

NOT_ON_SHIFT = 'NOT_ON_SHIFT'
ON_SHIFT = 'ON_SHIFT'


class EnumWithOneField(Enum):
FIELD = 'FIELD'
```

---

## `--no-use-specialized-enum` {#no-use-specialized-enum}

Disable specialized Enum classes for Python 3.11+ code generation.
Expand Down
2 changes: 2 additions & 0 deletions src/datamodel_code_generator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,7 @@ def generate( # noqa: PLR0912, PLR0913, PLR0914, PLR0915
shared_module_name: str = DEFAULT_SHARED_MODULE_NAME,
encoding: str = "utf-8",
enum_field_as_literal: LiteralType | None = None,
ignore_enum_constraints: bool = False,
use_one_literal_as_default: bool = False,
use_enum_values_in_discriminator: bool = False,
set_default_enum_member: bool = False,
Expand Down Expand Up @@ -663,6 +664,7 @@ def get_header_and_first_line(csv_file: IO[str]) -> dict[str, Any]:
enum_field_as_literal=enum_field_as_literal
if enum_field_as_literal is not None
else (LiteralType.All if output_model_type == DataModelType.TypingTypedDict else None),
ignore_enum_constraints=ignore_enum_constraints,
use_one_literal_as_default=use_one_literal_as_default,
use_enum_values_in_discriminator=use_enum_values_in_discriminator,
set_default_enum_member=True
Expand Down
2 changes: 2 additions & 0 deletions src/datamodel_code_generator/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,7 @@ def validate_all_exports_collision_strategy(cls, values: dict[str, Any]) -> dict
shared_module_name: str = DEFAULT_SHARED_MODULE_NAME
encoding: str = DEFAULT_ENCODING
enum_field_as_literal: Optional[LiteralType] = None # noqa: UP045
ignore_enum_constraints: bool = False
use_one_literal_as_default: bool = False
use_enum_values_in_discriminator: bool = False
set_default_enum_member: bool = False
Expand Down Expand Up @@ -708,6 +709,7 @@ def run_generate_from_config( # noqa: PLR0913, PLR0917
shared_module_name=config.shared_module_name,
encoding=config.encoding,
enum_field_as_literal=config.enum_field_as_literal,
ignore_enum_constraints=config.ignore_enum_constraints,
use_one_literal_as_default=config.use_one_literal_as_default,
use_enum_values_in_discriminator=config.use_enum_values_in_discriminator,
set_default_enum_member=config.set_default_enum_member,
Expand Down
6 changes: 6 additions & 0 deletions src/datamodel_code_generator/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,12 @@ def start_section(self, heading: str | None) -> None:
choices=[lt.value for lt in LiteralType],
default=None,
)
typing_options.add_argument(
"--ignore-enum-constraints",
help="Ignore enum constraints and use the base type (e.g., str, int) instead of generating Enum classes",
action="store_true",
default=None,
)
typing_options.add_argument(
"--field-constraints",
help="Use field constraints and not con* annotations",
Expand Down
1 change: 1 addition & 0 deletions src/datamodel_code_generator/cli_options.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ class CLIOptionMeta:
"--use-type-alias": CLIOptionMeta(name="--use-type-alias", category=OptionCategory.TYPING),
"--strict-types": CLIOptionMeta(name="--strict-types", category=OptionCategory.TYPING),
"--enum-field-as-literal": CLIOptionMeta(name="--enum-field-as-literal", category=OptionCategory.TYPING),
"--ignore-enum-constraints": CLIOptionMeta(name="--ignore-enum-constraints", category=OptionCategory.TYPING),
"--disable-future-imports": CLIOptionMeta(name="--disable-future-imports", category=OptionCategory.TYPING),
"--use-pendulum": CLIOptionMeta(name="--use-pendulum", category=OptionCategory.TYPING),
"--output-datetime-class": CLIOptionMeta(name="--output-datetime-class", category=OptionCategory.TYPING),
Expand Down
2 changes: 2 additions & 0 deletions src/datamodel_code_generator/parser/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,7 @@ def __init__( # noqa: PLR0913, PLR0915
shared_module_name: str = DEFAULT_SHARED_MODULE_NAME,
encoding: str = "utf-8",
enum_field_as_literal: LiteralType | None = None,
ignore_enum_constraints: bool = False,
set_default_enum_member: bool = False,
use_subclass_enum: bool = False,
use_specialized_enum: bool = True,
Expand Down Expand Up @@ -783,6 +784,7 @@ def __init__( # noqa: PLR0913, PLR0915
self.shared_module_name: str = shared_module_name
self.encoding: str = encoding
self.enum_field_as_literal: LiteralType | None = enum_field_as_literal
self.ignore_enum_constraints: bool = ignore_enum_constraints
self.set_default_enum_member: bool = set_default_enum_member
self.use_subclass_enum: bool = use_subclass_enum
self.use_specialized_enum: bool = use_specialized_enum
Expand Down
24 changes: 24 additions & 0 deletions src/datamodel_code_generator/parser/graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ def __init__( # noqa: PLR0913
shared_module_name: str = DEFAULT_SHARED_MODULE_NAME,
encoding: str = "utf-8",
enum_field_as_literal: LiteralType | None = None,
ignore_enum_constraints: bool = False,
set_default_enum_member: bool = False,
use_subclass_enum: bool = False,
use_specialized_enum: bool = True,
Expand Down Expand Up @@ -227,6 +228,7 @@ def __init__( # noqa: PLR0913
shared_module_name=shared_module_name,
encoding=encoding,
enum_field_as_literal=enum_field_as_literal,
ignore_enum_constraints=ignore_enum_constraints,
use_one_literal_as_default=use_one_literal_as_default,
use_enum_values_in_discriminator=use_enum_values_in_discriminator,
set_default_enum_member=set_default_enum_member,
Expand Down Expand Up @@ -414,10 +416,32 @@ def should_parse_enum_as_literal(self, obj: graphql.GraphQLEnumType) -> bool:

def parse_enum(self, enum_object: graphql.GraphQLEnumType) -> None:
"""Parse a GraphQL enum type and add it to results."""
if self.ignore_enum_constraints:
return self.parse_enum_as_str_type(enum_object)
if self.should_parse_enum_as_literal(enum_object):
return self.parse_enum_as_literal(enum_object)
return self.parse_enum_as_enum_class(enum_object)

def parse_enum_as_str_type(self, enum_object: graphql.GraphQLEnumType) -> None:
"""Parse enum as a str type alias when ignoring enum constraints."""
data_type = self.data_type_manager.get_data_type(Types.string)
data_model_type = self._create_data_model(
model_type=self.data_model_root_type,
reference=self.references[enum_object.name],
fields=[
self.data_model_field_type(
required=True,
data_type=data_type,
)
],
custom_base_class=self.base_class,
custom_template_dir=self.custom_template_dir,
extra_template_data=self.extra_template_data,
path=self.current_source_path,
description=enum_object.description,
)
self.results.append(data_model_type)

def parse_enum_as_literal(self, enum_object: graphql.GraphQLEnumType) -> None:
"""Parse enum values as a Literal type."""
data_type = self.data_type(literals=list(enum_object.values.keys()))
Expand Down
24 changes: 17 additions & 7 deletions src/datamodel_code_generator/parser/jsonschema.py
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,7 @@ def __init__( # noqa: PLR0913
shared_module_name: str = DEFAULT_SHARED_MODULE_NAME,
encoding: str = "utf-8",
enum_field_as_literal: LiteralType | None = None,
ignore_enum_constraints: bool = False,
use_one_literal_as_default: bool = False,
use_enum_values_in_discriminator: bool = False,
set_default_enum_member: bool = False,
Expand Down Expand Up @@ -638,6 +639,7 @@ def __init__( # noqa: PLR0913
shared_module_name=shared_module_name,
encoding=encoding,
enum_field_as_literal=enum_field_as_literal,
ignore_enum_constraints=ignore_enum_constraints,
use_one_literal_as_default=use_one_literal_as_default,
use_enum_values_in_discriminator=use_enum_values_in_discriminator,
set_default_enum_member=set_default_enum_member,
Expand Down Expand Up @@ -852,7 +854,15 @@ def _create_synthetic_enum_obj(
def is_constraints_field(self, obj: JsonSchemaObject) -> bool:
"""Check if a field should include constraints."""
return obj.is_array or (
self.field_constraints and not (obj.ref or obj.anyOf or obj.oneOf or obj.allOf or obj.is_object or obj.enum)
self.field_constraints
and not (
obj.ref
or obj.anyOf
or obj.oneOf
or obj.allOf
or obj.is_object
or (obj.enum and not self.ignore_enum_constraints)
)
)

def _resolve_field_flag(self, obj: JsonSchemaObject, flag: Literal["readOnly", "writeOnly"]) -> bool:
Expand Down Expand Up @@ -1500,7 +1510,7 @@ def _schema_signature(self, prop_schema: JsonSchemaObject | bool) -> str | bool:
return prop_schema
return json.dumps(prop_schema.dict(exclude_unset=True, by_alias=True), sort_keys=True, default=repr)

def _is_root_model_schema(self, obj: JsonSchemaObject) -> bool: # noqa: PLR6301
def _is_root_model_schema(self, obj: JsonSchemaObject) -> bool:
"""Check if schema represents a root model (primitive type with constraints).

Based on parse_raw_obj() else branch conditions. Returns True when
Expand All @@ -1516,7 +1526,7 @@ def _is_root_model_schema(self, obj: JsonSchemaObject) -> bool: # noqa: PLR6301
return False
if obj.type == "object":
return False
return not obj.enum
return not obj.enum or self.ignore_enum_constraints

def _handle_allof_root_model_with_constraints( # noqa: PLR0911, PLR0912
self,
Expand Down Expand Up @@ -2328,7 +2338,7 @@ def parse_item( # noqa: PLR0911, PLR0912
return self.data_type_manager.get_data_type(
Types.object,
)
if item.enum:
if item.enum and not self.ignore_enum_constraints:
if self.should_parse_enum_as_literal(item):
return self.parse_enum_as_literal(item)
return self.parse_enum(name, item, get_special_path("enum", path), singular_name=singular_name)
Expand Down Expand Up @@ -2401,7 +2411,7 @@ def parse_array_fields(
data_types.append(self.parse_all_of(name, obj, get_special_path("allOf", path)))
elif obj.is_object:
data_types.append(self.parse_object(name, obj, get_special_path("object", path)))
if obj.enum:
if obj.enum and not self.ignore_enum_constraints:
data_types.append(self.parse_enum(name, obj, get_special_path("enum", path)))
return self.data_model_field_type(
data_type=self.data_type(data_types=data_types),
Expand Down Expand Up @@ -2510,7 +2520,7 @@ def parse_root_type( # noqa: PLR0912
data_type = data_types[0]
elif obj.patternProperties:
data_type = self.parse_pattern_properties(name, obj.patternProperties, path)
elif obj.enum:
elif obj.enum and not self.ignore_enum_constraints:
if self.should_parse_enum_as_literal(obj):
data_type = self.parse_enum_as_literal(obj)
else: # pragma: no cover
Expand Down Expand Up @@ -3000,7 +3010,7 @@ def parse_obj( # noqa: PLR0912
self.parse_root_type(name, obj, path)
elif obj.type == "object":
self.parse_object(name, obj, path)
elif obj.enum and not self.should_parse_enum_as_literal(obj):
elif obj.enum and not self.ignore_enum_constraints and not self.should_parse_enum_as_literal(obj):
self.parse_enum(name, obj, path)
else:
self.parse_root_type(name, obj, path)
Expand Down
Loading
Loading