Skip to content

Commit 74597a0

Browse files
authored
Add test for Literal in describe_model (#101)
* test: check Literal handling in describe_model * Show Literal values in model description * Document Literal support
1 parent 0e5658d commit 74597a0

File tree

6 files changed

+45
-10
lines changed

6 files changed

+45
-10
lines changed

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ AI agents can now:
9090
Wrap your existing APIs with semantic understanding:
9191

9292
```python
93+
from typing import Literal
9394
from enrichmcp import EnrichMCP, EnrichModel, Relationship
9495
from pydantic import Field
9596

@@ -101,7 +102,9 @@ class Customer(EnrichModel):
101102

102103
id: int = Field(description="Unique customer ID")
103104
email: str = Field(description="Primary contact email")
104-
tier: str = Field(description="Subscription tier: free, pro, enterprise")
105+
tier: Literal["free", "pro", "enterprise"] = Field(
106+
description="Subscription tier"
107+
)
105108

106109
# Define navigable relationships
107110
orders: list["Order"] = Relationship(description="Customer's purchase history")
@@ -113,7 +116,9 @@ class Order(EnrichModel):
113116
id: int = Field(description="Order ID")
114117
customer_id: int = Field(description="Associated customer")
115118
total: float = Field(description="Order total in USD")
116-
status: str = Field(description="Order status: pending, shipped, delivered")
119+
status: Literal["pending", "shipped", "delivered"] = Field(
120+
description="Order status"
121+
)
117122

118123
customer: Customer = Relationship(description="Customer who placed this order")
119124

@@ -242,6 +247,7 @@ class Order(EnrichModel):
242247
email: EmailStr = Field(description="Customer email")
243248
status: Literal["pending", "shipped", "delivered"]
244249
```
250+
`describe_model()` will list these allowed values so agents know the valid options.
245251

246252
### ✏️ Mutability & CRUD
247253

docs/api/app.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ Generate a comprehensive description of the data model. This is used internally
117117

118118
**Returns:**
119119
A formatted string containing all entities, fields, and relationships.
120+
If a field is annotated with `typing.Literal`, the allowed values are shown in the output.
120121

121122
## Built-in Resources
122123

docs/concepts.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ async def create_customer(
245245
# All inputs are validated before reaching your code
246246
...
247247
```
248+
The `describe_model()` output will list these allowed values.
248249

249250
## Context and Lifespan Management
250251

src/enrichmcp/app.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88
from collections.abc import Callable
99
from typing import (
1010
Any,
11+
Literal,
1112
Protocol,
1213
TypeVar,
1314
cast,
15+
get_args,
16+
get_origin,
1417
runtime_checkable,
1518
)
1619

@@ -259,9 +262,14 @@ def describe_model(self) -> str:
259262
# Get field type and description
260263
field_type = "Any" # Default type if annotation is None
261264
if field.annotation is not None:
262-
field_type = str(field.annotation) # Always safe fallback
263-
if hasattr(field.annotation, "__name__"):
264-
field_type = field.annotation.__name__
265+
annotation = field.annotation
266+
if get_origin(annotation) is Literal:
267+
values = ", ".join(repr(v) for v in get_args(annotation))
268+
field_type = f"Literal[{values}]"
269+
else:
270+
field_type = str(annotation) # Always safe fallback
271+
if hasattr(annotation, "__name__"):
272+
field_type = annotation.__name__
265273
field_desc = field.description
266274
extra = getattr(field, "json_schema_extra", None)
267275
if extra is None:

src/enrichmcp/entity.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"""
66

77
from collections.abc import Callable
8-
from typing import Any, Literal, cast
8+
from typing import Any, Literal, cast, get_args, get_origin
99

1010
from pydantic import BaseModel, ConfigDict
1111
from pydantic.main import IncEx
@@ -107,9 +107,14 @@ def describe(self) -> str:
107107
# Get field type and description
108108
field_type = "Any" # Default type if annotation is None
109109
if field.annotation is not None:
110-
field_type = str(field.annotation) # Always safe fallback
111-
if hasattr(field.annotation, "__name__"):
112-
field_type = field.annotation.__name__
110+
annotation = field.annotation
111+
if get_origin(annotation) is Literal:
112+
values = ", ".join(repr(v) for v in get_args(annotation))
113+
field_type = f"Literal[{values}]"
114+
else:
115+
field_type = str(annotation) # Always safe fallback
116+
if hasattr(annotation, "__name__"):
117+
field_type = annotation.__name__
113118
field_desc = field.description
114119

115120
extra = getattr(field, "json_schema_extra", None)

tests/test_model_description.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Any
1+
from typing import Any, Literal
22

33
from pydantic import Field
44

@@ -132,3 +132,17 @@ class Article(EnrichModel):
132132
assert "**categories**" in description and "Article categories" in description
133133

134134
assert "- **author** → Relationship: Article author" in description
135+
136+
137+
def test_describe_model_with_literal_type():
138+
"""Test describe_model with Literal field types."""
139+
app = EnrichMCP("Enum API", description="A model with Literal fields")
140+
141+
@app.entity(description="Entity using Literal")
142+
class Item(EnrichModel):
143+
status: Literal["pending", "complete"] = Field(description="Item status")
144+
145+
description = app.describe_model()
146+
147+
assert "## Item" in description
148+
assert "- **status** (Literal['pending', 'complete']): Item status" in description

0 commit comments

Comments
 (0)