Repository: pydantic/pydantic

Issue:
`model_json_schema` fails with empty enum
### Initial Checks

- [X] I confirm that I'm using Pydantic V2

### Description

When a field is annotated as an enum and that enum has no members then `model_json_schema` fails.
MRE and traceback below.
In pydantic v1 this produces `{"title": "MyModel", "type": "object", "properties": {"e": {"title": "E", "enum": []}}, "required": ["e"]}` (using `schema_json`).

```python
from pydantic import BaseModel
from enum import Enum


class MyEnum(Enum):
    pass


class MyModel(BaseModel):
    e: MyEnum


print(MyModel.model_json_schema())
```

```
Traceback (most recent call last):
  File "scratch_308.py", line 13, in <module>
    print(MyModel.model_json_schema())
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/main.py", line 385, in model_json_schema
    return model_json_schema(
           ^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/json_schema.py", line 2153, in model_json_schema
    return schema_generator_instance.generate(cls.__pydantic_core_schema__, mode=mode)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/json_schema.py", line 410, in generate
    json_schema: JsonSchemaValue = self.generate_inner(schema)
                                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/json_schema.py", line 549, in generate_inner
    json_schema = current_handler(schema)
                  ^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/_internal/_schema_generation_shared.py", line 36, in __call__
    return self.handler(__core_schema)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/json_schema.py", line 523, in new_handler_func
    json_schema = js_modify_function(schema_or_field, current_handler)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/main.py", line 601, in __get_pydantic_json_schema__
    return __handler(__core_schema)
           ^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/_internal/_schema_generation_shared.py", line 36, in __call__
    return self.handler(__core_schema)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/json_schema.py", line 523, in new_handler_func
    json_schema = js_modify_function(schema_or_field, current_handler)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 212, in modify_model_json_schema
    json_schema = handler(schema_or_field)
                  ^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/_internal/_schema_generation_shared.py", line 36, in __call__
    return self.handler(__core_schema)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/json_schema.py", line 506, in handler_func
    json_schema = generate_for_schema_type(schema_or_field)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/json_schema.py", line 1318, in model_schema
    json_schema = self.generate_inner(schema['schema'])
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/json_schema.py", line 549, in generate_inner
    json_schema = current_handler(schema)
                  ^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/_internal/_schema_generation_shared.py", line 36, in __call__
    return self.handler(__core_schema)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/json_schema.py", line 506, in handler_func
    json_schema = generate_for_schema_type(schema_or_field)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/json_schema.py", line 1410, in model_fields_schema
    json_schema = self._named_required_fields_schema(named_required_fields)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/json_schema.py", line 1221, in _named_required_fields_schema
    field_json_schema = self.generate_inner(field).copy()
                        ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/json_schema.py", line 549, in generate_inner
    json_schema = current_handler(schema)
                  ^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/_internal/_schema_generation_shared.py", line 36, in __call__
    return self.handler(__core_schema)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/json_schema.py", line 541, in new_handler_func
    json_schema = js_modify_function(schema_or_field, current_handler)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/_internal/_generate_schema.py", line 963, in json_schema_update_func
    json_schema = {**handler(schema), **json_schema_updates}
                     ^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/_internal/_schema_generation_shared.py", line 36, in __call__
    return self.handler(__core_schema)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/json_schema.py", line 506, in handler_func
    json_schema = generate_for_schema_type(schema_or_field)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/json_schema.py", line 1289, in model_field_schema
    return self.generate_inner(schema['schema'])
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/json_schema.py", line 549, in generate_inner
    json_schema = current_handler(schema)
                  ^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/_internal/_schema_generation_shared.py", line 36, in __call__
    return self.handler(__core_schema)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/json_schema.py", line 506, in handler_func
    json_schema = generate_for_schema_type(schema_or_field)
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/json_schema.py", line 766, in is_instance_schema
    return self.handle_invalid_for_json_schema(schema, f'core_schema.IsInstanceSchema ({schema["cls"]})')
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File ".../lib/python3.11/site-packages/pydantic/json_schema.py", line 2069, in handle_invalid_for_json_schema
    raise PydanticInvalidForJsonSchema(f'Cannot generate a JsonSchema for {error_info}')
pydantic.errors.PydanticInvalidForJsonSchema: Cannot generate a JsonSchema for core_schema.IsInstanceSchema (<enum 'MyEnum'>)

For further information visit https://errors.pydantic.dev/2.4/u/invalid-for-json-schema
```

### Example Code

~~ see above

### Python, Pydantic & OS Version

```Text
pydantic version: 2.4.2
        pydantic-core version: 2.10.1
          pydantic-core build: profile=release pgo=false
                 install path: .../lib/python3.11/site-packages/pydantic
               python version: 3.11.4 (main, Jul  5 2023, 08:40:20) [Clang 14.0.6 ]
                     platform: macOS-13.6-arm64-arm-64bit
             related packages: mypy-1.6.1 email-validator-2.0.0.post2 typing_extensions-4.8.0 pydantic-settings-2.0.3 fastapi-0.103.2
```



Discussion:
Perhaps a more practical example is like below, where you would practically provide an empty dict for `e`. Fails similarly

```python
from pydantic import BaseModel
from enum import Enum


class MyEnum(Enum):
    pass


class MyModel(BaseModel):
    e: dict[MyEnum, str]


print(MyModel.model_json_schema())
```
I'm really curious, what's the goal here? Do you have empty enums? Do you actually want the only valid value for `e` to be `{}` for some reason?
> Do you have empty enums? Do you actually want the only valid value for `e` to be `{}` for some reason?

Yes and yes. In my case the empty enums are there as part of a pattern and just happen to be empty. Perhaps things could be structured "better" to make that not a thing but 🤷

Like uhh here's a contrived example


```python
from pydantic import BaseModel
from enum import StrEnum


class GoodJapaneseCars(StrEnum):
    ...


class GoodSouthAfricanCars(StrEnum):
    pass  # Well maybe they'll make some in the future :)


class MyModel(BaseModel):
    japanese_cars: list[GoodJapaneseCars]
    south_african_cars: list[GoodSouthAfricanCars]


print(MyModel.model_json_schema())
```

Seems an intended behavior based on https://github.com/pydantic/pydantic/blob/4dd40aec7397418e37b1029a0496b486adecbb7c/pydantic/_internal/_std_types_schema.py#L62-L67

But, let's wait for @dmontagu to make it more clear.
> In pydantic v1 this produces `{"title": "MyModel", "type": "object", "properties": {"e": {"title": "E", "enum": []}}, "required": ["e"]}` (using `schema_json`).

If I put that into https://www.jsonschemavalidator.net/ I get an error: "Enum array must have at least one value"

But this seems to do the trick: `"allOf": [{"type": "string"}, {"type": "integer"}]`
> If I put that into https://www.jsonschemavalidator.net/ I get an error: "Enum array must have at least one value"


Seems an empty enum is okay per this spec (which [pydantic is compliant with](https://github.com/pydantic/pydantic/blob/5be53ce941dccd0f8f84e9cee81a6a1871f24dae/docs/concepts/json_schema.md?plain=true#L12-L14)), granted it [should](https://www.rfc-editor.org/rfc/rfc2119.txt) have at least one element. Seems a bit of a grey area
https://json-schema.org/draft/2020-12/json-schema-validation#name-enum

> This array SHOULD have at least one element.
If the issue is just generating a JSON schema, I'm sure we can make that work for enums with no cases. I'm assuming we don't need to actually implement anything different for validation (given there are no cases that it could be validated into). Since right now it just errors, it seems fine to me to make it not error and generate some best-effort JSON schema.

If you know what you want the JSON schema to be let me know otherwise I will try to just make it consistent with the existing JSON schemas and just have zero cases.
It seems risky to produce a schema that's technically spec-compliant but might be refused by a fussy validator.
> If the issue is just generating a JSON schema, I'm sure we can make that work for enums with no cases

Yeah this is the core issue for me - only reason I noticed this issue was because of fastapi docs failing to load. I'm not fussed about exactly what schema gets generated 
Are you able to control the schema generation? If so you can do this in the meantime:

```python
from pydantic.json_schema import GenerateJsonSchema


class MyGen(GenerateJsonSchema):
    def is_instance_schema(self, *_):
        return {"enum": []}


print(MyModel.model_json_schema(schema_generator=MyGen))
```
Cheers I'll have a play with that 

Resolving patch:
diff --git a/pydantic/_internal/_std_types_schema.py b/pydantic/_internal/_std_types_schema.py
--- a/pydantic/_internal/_std_types_schema.py
+++ b/pydantic/_internal/_std_types_schema.py
@@ -59,13 +59,27 @@ def __get_pydantic_json_schema__(self, schema: CoreSchema, handler: GetJsonSchem
 def get_enum_core_schema(enum_type: type[Enum], config: ConfigDict) -> CoreSchema:
     cases: list[Any] = list(enum_type.__members__.values())
 
+    enum_ref = get_type_ref(enum_type)
+    description = None if not enum_type.__doc__ else inspect.cleandoc(enum_type.__doc__)
+    if description == 'An enumeration.':  # This is the default value provided by enum.EnumMeta.__new__; don't use it
+        description = None
+    updates = {'title': enum_type.__name__, 'description': description}
+    updates = {k: v for k, v in updates.items() if v is not None}
+
+    def get_json_schema(_, handler: GetJsonSchemaHandler) -> JsonSchemaValue:
+        json_schema = handler(core_schema.literal_schema([x.value for x in cases], ref=enum_ref))
+        original_schema = handler.resolve_ref_schema(json_schema)
+        update_json_schema(original_schema, updates)
+        return json_schema
+
     if not cases:
         # Use an isinstance check for enums with no cases.
-        # This won't work with serialization or JSON schema, but that's okay -- the most important
-        # use case for this is creating typevar bounds for generics that should be restricted to enums.
-        # This is more consistent than it might seem at first, since you can only subclass enum.Enum
-        # (or subclasses of enum.Enum) if all parent classes have no cases.
-        return core_schema.is_instance_schema(enum_type)
+        # The most important use case for this is creating TypeVar bounds for generics that should
+        # be restricted to enums. This is more consistent than it might seem at first, since you can only
+        # subclass enum.Enum (or subclasses of enum.Enum) if all parent classes have no cases.
+        # We use the get_json_schema function when an Enum subclass has been declared with no cases
+        # so that we can still generate a valid json schema.
+        return core_schema.is_instance_schema(enum_type, metadata={'pydantic_js_functions': [get_json_schema]})
 
     use_enum_values = config.get('use_enum_values', False)
 
@@ -84,19 +98,6 @@ def to_enum(__input_value: Any) -> Enum:
             # The type: ignore on the next line is to ignore the requirement of LiteralString
             raise PydanticCustomError('enum', f'Input should be {expected}', {'expected': expected})  # type: ignore
 
-    enum_ref = get_type_ref(enum_type)
-    description = None if not enum_type.__doc__ else inspect.cleandoc(enum_type.__doc__)
-    if description == 'An enumeration.':  # This is the default value provided by enum.EnumMeta.__new__; don't use it
-        description = None
-    updates = {'title': enum_type.__name__, 'description': description}
-    updates = {k: v for k, v in updates.items() if v is not None}
-
-    def get_json_schema(_, handler: GetJsonSchemaHandler) -> JsonSchemaValue:
-        json_schema = handler(core_schema.literal_schema([x.value for x in cases], ref=enum_ref))
-        original_schema = handler.resolve_ref_schema(json_schema)
-        update_json_schema(original_schema, updates)
-        return json_schema
-
     strict_python_schema = core_schema.is_instance_schema(enum_type)
     if use_enum_values:
         strict_python_schema = core_schema.chain_schema(


Test patch:
diff --git a/tests/test_types.py b/tests/test_types.py
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -1621,12 +1621,6 @@ class MyEnum(Enum):
         }
     ]
 
-    with pytest.raises(
-        PydanticInvalidForJsonSchema,
-        match=re.escape("Cannot generate a JsonSchema for core_schema.IsInstanceSchema (<enum 'Enum'>)"),
-    ):
-        Model.model_json_schema()
-
 
 def test_int_enum_type():
     class Model(BaseModel):
@@ -1655,12 +1649,6 @@ class MyIntEnum(IntEnum):
         }
     ]
 
-    with pytest.raises(
-        PydanticInvalidForJsonSchema,
-        match=re.escape("Cannot generate a JsonSchema for core_schema.IsInstanceSchema (<enum 'IntEnum'>)"),
-    ):
-        Model.model_json_schema()
-
 
 @pytest.mark.parametrize('enum_base,strict', [(Enum, False), (IntEnum, False), (IntEnum, True)])
 def test_enum_from_json(enum_base, strict):
@@ -1723,6 +1711,17 @@ class User(BaseModel):
         User(demo_strict=0, demo_not_strict=1)
 
 
+def test_enum_with_no_cases() -> None:
+    class MyEnum(Enum):
+        pass
+
+    class MyModel(BaseModel):
+        e: MyEnum
+
+    json_schema = MyModel.model_json_schema()
+    assert json_schema['properties']['e']['enum'] == []
+
+
 @pytest.mark.parametrize(
     'kwargs,type_',
     [