Skip to content

Commit a158041

Browse files
feat: Add --ignore-enum-constraints option (#2694)
* feat: Add --ignore-enum-constraints option to bypass enum generation * docs: update CLI reference documentation 🤖 Generated by GitHub Actions * feat: Add test for --ignore-enum-constraints option and generated output * docs: update CLI reference documentation 🤖 Generated by GitHub Actions * docs: update CLI reference documentation 🤖 Generated by GitHub Actions --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent 6a29f22 commit a158041

13 files changed

Lines changed: 251 additions & 9 deletions

File tree

docs/cli-reference/index.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ This documentation is auto-generated from test cases.
99
| Category | Options | Description |
1010
|----------|---------|-------------|
1111
| 📁 [Base Options](base-options.md) | 5 | Input/output configuration |
12-
| 🔧 [Typing Customization](typing-customization.md) | 16 | Type annotation and import behavior |
12+
| 🔧 [Typing Customization](typing-customization.md) | 17 | Type annotation and import behavior |
1313
| 🏷️ [Field Customization](field-customization.md) | 20 | Field naming and docstring behavior |
1414
| 🏗️ [Model Customization](model-customization.md) | 26 | Model generation behavior |
1515
| 🎨 [Template Customization](template-customization.md) | 16 | Output formatting and custom rendering |
@@ -92,6 +92,7 @@ This documentation is auto-generated from test cases.
9292

9393
### I {#i}
9494

95+
- [`--ignore-enum-constraints`](typing-customization.md#ignore-enum-constraints)
9596
- [`--ignore-pyproject`](general-options.md#ignore-pyproject)
9697
- [`--include-path-parameters`](openapi-only-options.md#include-path-parameters)
9798
- [`--input`](base-options.md#input)

docs/cli-reference/quick-reference.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ datamodel-codegen [OPTIONS]
2929
| [`--allof-merge-mode`](typing-customization.md#allof-merge-mode) | Merge constraints from root model references in allOf schemas. |
3030
| [`--disable-future-imports`](typing-customization.md#disable-future-imports) | Prevent automatic addition of __future__ imports in generated code. |
3131
| [`--enum-field-as-literal`](typing-customization.md#enum-field-as-literal) | Convert all enum fields to Literal types instead of Enum classes. |
32+
| [`--ignore-enum-constraints`](typing-customization.md#ignore-enum-constraints) | Ignore enum constraints and use base string type instead of Enum classes. |
3233
| [`--no-use-specialized-enum`](typing-customization.md#no-use-specialized-enum) | Disable specialized Enum classes for Python 3.11+ code generation. |
3334
| [`--output-datetime-class`](typing-customization.md#output-datetime-class) | Specify datetime class type for date-time schema fields. |
3435
| [`--strict-types`](typing-customization.md#strict-types) | Enable strict type validation for specified Python types. |
@@ -210,6 +211,7 @@ All options sorted alphabetically:
210211
- [`--http-headers`](general-options.md#http-headers) - Fetch schema from URL with custom HTTP headers.
211212
- [`--http-ignore-tls`](general-options.md#http-ignore-tls) - Disable TLS certificate verification for HTTPS requests.
212213
- [`--http-query-parameters`](general-options.md#http-query-parameters) - Add query parameters to HTTP requests for remote schemas.
214+
- [`--ignore-enum-constraints`](typing-customization.md#ignore-enum-constraints) - Ignore enum constraints and use base string type instead of ...
213215
- [`--ignore-pyproject`](general-options.md#ignore-pyproject) - Ignore pyproject.toml configuration file.
214216
- [`--include-path-parameters`](openapi-only-options.md#include-path-parameters) - Include OpenAPI path parameters in generated parameter model...
215217
- [`--input`](base-options.md#input) - Specify the input schema file path.

docs/cli-reference/typing-customization.md

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
| [`--allof-merge-mode`](#allof-merge-mode) | Merge constraints from root model references in allOf schema... |
88
| [`--disable-future-imports`](#disable-future-imports) | Prevent automatic addition of __future__ imports in generate... |
99
| [`--enum-field-as-literal`](#enum-field-as-literal) | Convert all enum fields to Literal types instead of Enum cla... |
10+
| [`--ignore-enum-constraints`](#ignore-enum-constraints) | Ignore enum constraints and use base string type instead of ... |
1011
| [`--no-use-specialized-enum`](#no-use-specialized-enum) | Disable specialized Enum classes for Python 3.11+ code gener... |
1112
| [`--output-datetime-class`](#output-datetime-class) | Specify datetime class type for date-time schema fields. |
1213
| [`--strict-types`](#strict-types) | Enable strict type validation for specified Python types. |
@@ -1191,6 +1192,135 @@ of Enum classes for all enumerations.
11911192

11921193
---
11931194

1195+
## `--ignore-enum-constraints` {#ignore-enum-constraints}
1196+
1197+
Ignore enum constraints and use base string type instead of Enum classes.
1198+
1199+
The `--ignore-enum-constraints` flag ignores enum constraints and uses
1200+
the base type (str) instead of generating Enum classes. This is useful
1201+
when you need flexibility in the values a field can accept beyond the
1202+
defined enum members.
1203+
1204+
!!! tip "Usage"
1205+
1206+
```bash
1207+
datamodel-codegen --input schema.json --ignore-enum-constraints # (1)!
1208+
```
1209+
1210+
1. :material-arrow-left: `--ignore-enum-constraints` - the option documented here
1211+
1212+
??? example "Examples"
1213+
1214+
**Input Schema:**
1215+
1216+
```graphql
1217+
"Employee shift status"
1218+
enum EmployeeShiftStatus {
1219+
"not on shift"
1220+
NOT_ON_SHIFT
1221+
"on shift"
1222+
ON_SHIFT
1223+
}
1224+
1225+
enum Color {
1226+
RED
1227+
GREEN
1228+
BLUE
1229+
}
1230+
1231+
enum EnumWithOneField {
1232+
FIELD
1233+
}
1234+
```
1235+
1236+
**Output:**
1237+
1238+
=== "With Option"
1239+
1240+
```python
1241+
# generated by datamodel-codegen:
1242+
# filename: enums.graphql
1243+
# timestamp: 2019-07-26T00:00:00+00:00
1244+
1245+
from __future__ import annotations
1246+
1247+
from pydantic import BaseModel
1248+
from typing_extensions import TypeAlias
1249+
1250+
Boolean: TypeAlias = bool
1251+
"""
1252+
The `Boolean` scalar type represents `true` or `false`.
1253+
"""
1254+
1255+
1256+
String: TypeAlias = str
1257+
"""
1258+
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.
1259+
"""
1260+
1261+
1262+
class Color(BaseModel):
1263+
__root__: str
1264+
1265+
1266+
class EmployeeShiftStatus(BaseModel):
1267+
"""
1268+
Employee shift status
1269+
"""
1270+
1271+
__root__: str
1272+
1273+
1274+
class EnumWithOneField(BaseModel):
1275+
__root__: str
1276+
```
1277+
1278+
=== "Without Option"
1279+
1280+
```python
1281+
# generated by datamodel-codegen:
1282+
# filename: enums.graphql
1283+
# timestamp: 2019-07-26T00:00:00+00:00
1284+
1285+
from __future__ import annotations
1286+
1287+
from enum import Enum
1288+
1289+
from typing_extensions import TypeAlias
1290+
1291+
Boolean: TypeAlias = bool
1292+
"""
1293+
The `Boolean` scalar type represents `true` or `false`.
1294+
"""
1295+
1296+
1297+
String: TypeAlias = str
1298+
"""
1299+
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.
1300+
"""
1301+
1302+
1303+
class Color(Enum):
1304+
BLUE = 'BLUE'
1305+
GREEN = 'GREEN'
1306+
RED = 'RED'
1307+
1308+
1309+
class EmployeeShiftStatus(Enum):
1310+
"""
1311+
Employee shift status
1312+
"""
1313+
1314+
NOT_ON_SHIFT = 'NOT_ON_SHIFT'
1315+
ON_SHIFT = 'ON_SHIFT'
1316+
1317+
1318+
class EnumWithOneField(Enum):
1319+
FIELD = 'FIELD'
1320+
```
1321+
1322+
---
1323+
11941324
## `--no-use-specialized-enum` {#no-use-specialized-enum}
11951325

11961326
Disable specialized Enum classes for Python 3.11+ code generation.

src/datamodel_code_generator/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,7 @@ def generate( # noqa: PLR0912, PLR0913, PLR0914, PLR0915
417417
shared_module_name: str = DEFAULT_SHARED_MODULE_NAME,
418418
encoding: str = "utf-8",
419419
enum_field_as_literal: LiteralType | None = None,
420+
ignore_enum_constraints: bool = False,
420421
use_one_literal_as_default: bool = False,
421422
use_enum_values_in_discriminator: bool = False,
422423
set_default_enum_member: bool = False,
@@ -663,6 +664,7 @@ def get_header_and_first_line(csv_file: IO[str]) -> dict[str, Any]:
663664
enum_field_as_literal=enum_field_as_literal
664665
if enum_field_as_literal is not None
665666
else (LiteralType.All if output_model_type == DataModelType.TypingTypedDict else None),
667+
ignore_enum_constraints=ignore_enum_constraints,
666668
use_one_literal_as_default=use_one_literal_as_default,
667669
use_enum_values_in_discriminator=use_enum_values_in_discriminator,
668670
set_default_enum_member=True

src/datamodel_code_generator/__main__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,7 @@ def validate_all_exports_collision_strategy(cls, values: dict[str, Any]) -> dict
405405
shared_module_name: str = DEFAULT_SHARED_MODULE_NAME
406406
encoding: str = DEFAULT_ENCODING
407407
enum_field_as_literal: Optional[LiteralType] = None # noqa: UP045
408+
ignore_enum_constraints: bool = False
408409
use_one_literal_as_default: bool = False
409410
use_enum_values_in_discriminator: bool = False
410411
set_default_enum_member: bool = False
@@ -708,6 +709,7 @@ def run_generate_from_config( # noqa: PLR0913, PLR0917
708709
shared_module_name=config.shared_module_name,
709710
encoding=config.encoding,
710711
enum_field_as_literal=config.enum_field_as_literal,
712+
ignore_enum_constraints=config.ignore_enum_constraints,
711713
use_one_literal_as_default=config.use_one_literal_as_default,
712714
use_enum_values_in_discriminator=config.use_enum_values_in_discriminator,
713715
set_default_enum_member=config.set_default_enum_member,

src/datamodel_code_generator/arguments.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -348,6 +348,12 @@ def start_section(self, heading: str | None) -> None:
348348
choices=[lt.value for lt in LiteralType],
349349
default=None,
350350
)
351+
typing_options.add_argument(
352+
"--ignore-enum-constraints",
353+
help="Ignore enum constraints and use the base type (e.g., str, int) instead of generating Enum classes",
354+
action="store_true",
355+
default=None,
356+
)
351357
typing_options.add_argument(
352358
"--field-constraints",
353359
help="Use field constraints and not con* annotations",

src/datamodel_code_generator/cli_options.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ class CLIOptionMeta:
142142
"--use-type-alias": CLIOptionMeta(name="--use-type-alias", category=OptionCategory.TYPING),
143143
"--strict-types": CLIOptionMeta(name="--strict-types", category=OptionCategory.TYPING),
144144
"--enum-field-as-literal": CLIOptionMeta(name="--enum-field-as-literal", category=OptionCategory.TYPING),
145+
"--ignore-enum-constraints": CLIOptionMeta(name="--ignore-enum-constraints", category=OptionCategory.TYPING),
145146
"--disable-future-imports": CLIOptionMeta(name="--disable-future-imports", category=OptionCategory.TYPING),
146147
"--use-pendulum": CLIOptionMeta(name="--use-pendulum", category=OptionCategory.TYPING),
147148
"--output-datetime-class": CLIOptionMeta(name="--output-datetime-class", category=OptionCategory.TYPING),

src/datamodel_code_generator/parser/base.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,7 @@ def __init__( # noqa: PLR0913, PLR0915
683683
shared_module_name: str = DEFAULT_SHARED_MODULE_NAME,
684684
encoding: str = "utf-8",
685685
enum_field_as_literal: LiteralType | None = None,
686+
ignore_enum_constraints: bool = False,
686687
set_default_enum_member: bool = False,
687688
use_subclass_enum: bool = False,
688689
use_specialized_enum: bool = True,
@@ -783,6 +784,7 @@ def __init__( # noqa: PLR0913, PLR0915
783784
self.shared_module_name: str = shared_module_name
784785
self.encoding: str = encoding
785786
self.enum_field_as_literal: LiteralType | None = enum_field_as_literal
787+
self.ignore_enum_constraints: bool = ignore_enum_constraints
786788
self.set_default_enum_member: bool = set_default_enum_member
787789
self.use_subclass_enum: bool = use_subclass_enum
788790
self.use_specialized_enum: bool = use_specialized_enum

src/datamodel_code_generator/parser/graphql.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ def __init__( # noqa: PLR0913
134134
shared_module_name: str = DEFAULT_SHARED_MODULE_NAME,
135135
encoding: str = "utf-8",
136136
enum_field_as_literal: LiteralType | None = None,
137+
ignore_enum_constraints: bool = False,
137138
set_default_enum_member: bool = False,
138139
use_subclass_enum: bool = False,
139140
use_specialized_enum: bool = True,
@@ -227,6 +228,7 @@ def __init__( # noqa: PLR0913
227228
shared_module_name=shared_module_name,
228229
encoding=encoding,
229230
enum_field_as_literal=enum_field_as_literal,
231+
ignore_enum_constraints=ignore_enum_constraints,
230232
use_one_literal_as_default=use_one_literal_as_default,
231233
use_enum_values_in_discriminator=use_enum_values_in_discriminator,
232234
set_default_enum_member=set_default_enum_member,
@@ -414,10 +416,32 @@ def should_parse_enum_as_literal(self, obj: graphql.GraphQLEnumType) -> bool:
414416

415417
def parse_enum(self, enum_object: graphql.GraphQLEnumType) -> None:
416418
"""Parse a GraphQL enum type and add it to results."""
419+
if self.ignore_enum_constraints:
420+
return self.parse_enum_as_str_type(enum_object)
417421
if self.should_parse_enum_as_literal(enum_object):
418422
return self.parse_enum_as_literal(enum_object)
419423
return self.parse_enum_as_enum_class(enum_object)
420424

425+
def parse_enum_as_str_type(self, enum_object: graphql.GraphQLEnumType) -> None:
426+
"""Parse enum as a str type alias when ignoring enum constraints."""
427+
data_type = self.data_type_manager.get_data_type(Types.string)
428+
data_model_type = self._create_data_model(
429+
model_type=self.data_model_root_type,
430+
reference=self.references[enum_object.name],
431+
fields=[
432+
self.data_model_field_type(
433+
required=True,
434+
data_type=data_type,
435+
)
436+
],
437+
custom_base_class=self.base_class,
438+
custom_template_dir=self.custom_template_dir,
439+
extra_template_data=self.extra_template_data,
440+
path=self.current_source_path,
441+
description=enum_object.description,
442+
)
443+
self.results.append(data_model_type)
444+
421445
def parse_enum_as_literal(self, enum_object: graphql.GraphQLEnumType) -> None:
422446
"""Parse enum values as a Literal type."""
423447
data_type = self.data_type(literals=list(enum_object.values.keys()))

src/datamodel_code_generator/parser/jsonschema.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,7 @@ def __init__( # noqa: PLR0913
544544
shared_module_name: str = DEFAULT_SHARED_MODULE_NAME,
545545
encoding: str = "utf-8",
546546
enum_field_as_literal: LiteralType | None = None,
547+
ignore_enum_constraints: bool = False,
547548
use_one_literal_as_default: bool = False,
548549
use_enum_values_in_discriminator: bool = False,
549550
set_default_enum_member: bool = False,
@@ -638,6 +639,7 @@ def __init__( # noqa: PLR0913
638639
shared_module_name=shared_module_name,
639640
encoding=encoding,
640641
enum_field_as_literal=enum_field_as_literal,
642+
ignore_enum_constraints=ignore_enum_constraints,
641643
use_one_literal_as_default=use_one_literal_as_default,
642644
use_enum_values_in_discriminator=use_enum_values_in_discriminator,
643645
set_default_enum_member=set_default_enum_member,
@@ -852,7 +854,15 @@ def _create_synthetic_enum_obj(
852854
def is_constraints_field(self, obj: JsonSchemaObject) -> bool:
853855
"""Check if a field should include constraints."""
854856
return obj.is_array or (
855-
self.field_constraints and not (obj.ref or obj.anyOf or obj.oneOf or obj.allOf or obj.is_object or obj.enum)
857+
self.field_constraints
858+
and not (
859+
obj.ref
860+
or obj.anyOf
861+
or obj.oneOf
862+
or obj.allOf
863+
or obj.is_object
864+
or (obj.enum and not self.ignore_enum_constraints)
865+
)
856866
)
857867

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

1503-
def _is_root_model_schema(self, obj: JsonSchemaObject) -> bool: # noqa: PLR6301
1513+
def _is_root_model_schema(self, obj: JsonSchemaObject) -> bool:
15041514
"""Check if schema represents a root model (primitive type with constraints).
15051515
15061516
Based on parse_raw_obj() else branch conditions. Returns True when
@@ -1516,7 +1526,7 @@ def _is_root_model_schema(self, obj: JsonSchemaObject) -> bool: # noqa: PLR6301
15161526
return False
15171527
if obj.type == "object":
15181528
return False
1519-
return not obj.enum
1529+
return not obj.enum or self.ignore_enum_constraints
15201530

15211531
def _handle_allof_root_model_with_constraints( # noqa: PLR0911, PLR0912
15221532
self,
@@ -2328,7 +2338,7 @@ def parse_item( # noqa: PLR0911, PLR0912
23282338
return self.data_type_manager.get_data_type(
23292339
Types.object,
23302340
)
2331-
if item.enum:
2341+
if item.enum and not self.ignore_enum_constraints:
23322342
if self.should_parse_enum_as_literal(item):
23332343
return self.parse_enum_as_literal(item)
23342344
return self.parse_enum(name, item, get_special_path("enum", path), singular_name=singular_name)
@@ -2401,7 +2411,7 @@ def parse_array_fields(
24012411
data_types.append(self.parse_all_of(name, obj, get_special_path("allOf", path)))
24022412
elif obj.is_object:
24032413
data_types.append(self.parse_object(name, obj, get_special_path("object", path)))
2404-
if obj.enum:
2414+
if obj.enum and not self.ignore_enum_constraints:
24052415
data_types.append(self.parse_enum(name, obj, get_special_path("enum", path)))
24062416
return self.data_model_field_type(
24072417
data_type=self.data_type(data_types=data_types),
@@ -2510,7 +2520,7 @@ def parse_root_type( # noqa: PLR0912
25102520
data_type = data_types[0]
25112521
elif obj.patternProperties:
25122522
data_type = self.parse_pattern_properties(name, obj.patternProperties, path)
2513-
elif obj.enum:
2523+
elif obj.enum and not self.ignore_enum_constraints:
25142524
if self.should_parse_enum_as_literal(obj):
25152525
data_type = self.parse_enum_as_literal(obj)
25162526
else: # pragma: no cover
@@ -3000,7 +3010,7 @@ def parse_obj( # noqa: PLR0912
30003010
self.parse_root_type(name, obj, path)
30013011
elif obj.type == "object":
30023012
self.parse_object(name, obj, path)
3003-
elif obj.enum and not self.should_parse_enum_as_literal(obj):
3013+
elif obj.enum and not self.ignore_enum_constraints and not self.should_parse_enum_as_literal(obj):
30043014
self.parse_enum(name, obj, path)
30053015
else:
30063016
self.parse_root_type(name, obj, path)

0 commit comments

Comments
 (0)