Coverage for mcpgateway / utils / base_models.py: 100%
8 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-02-11 07:10 +0000
« prev ^ index » next coverage.py v7.13.1, created at 2026-02-11 07:10 +0000
1# -*- coding: utf-8 -*-
2"""Base model utilities for MCP Gateway.
4This module provides shared base classes and utilities for Pydantic models
5to avoid circular dependencies between models.py and schemas.py.
7Copyright 2025
8SPDX-License-Identifier: Apache-2.0
9"""
11# Standard
12from typing import Any, Dict
14# Third-Party
15from pydantic import BaseModel, ConfigDict
18def to_camel_case(s: str) -> str:
19 """Convert a string from snake_case to camelCase.
21 Args:
22 s (str): The string to be converted, which is assumed to be in snake_case.
24 Returns:
25 str: The string converted to camelCase.
27 Examples:
28 >>> to_camel_case("hello_world_example")
29 'helloWorldExample'
30 >>> to_camel_case("alreadyCamel")
31 'alreadyCamel'
32 >>> to_camel_case("")
33 ''
34 >>> to_camel_case("single")
35 'single'
36 >>> to_camel_case("_leading_underscore")
37 'LeadingUnderscore'
38 >>> to_camel_case("trailing_underscore_")
39 'trailingUnderscore'
40 >>> to_camel_case("multiple_words_here")
41 'multipleWordsHere'
42 >>> to_camel_case("api_key_value")
43 'apiKeyValue'
44 >>> to_camel_case("user_id")
45 'userId'
46 >>> to_camel_case("created_at")
47 'createdAt'
48 """
49 return "".join(word.capitalize() if i else word for i, word in enumerate(s.split("_")))
52class BaseModelWithConfigDict(BaseModel):
53 """Base model with common configuration for MCP protocol types.
55 This base class provides automatic snake_case → camelCase field name conversion
56 to comply with the MCP specification's JSON naming conventions.
58 Key Features:
59 - **Automatic camelCase conversion**: Field names like `stop_reason` automatically
60 serialize as `stopReason` when FastAPI returns the response (via jsonable_encoder).
61 - **ORM mode**: Can be constructed from SQLAlchemy models (from_attributes=True).
62 - **Flexible input**: Accepts both snake_case and camelCase in input (populate_by_name=True).
63 - **Enum values**: Enums serialize as their values, not names (use_enum_values=True).
65 Usage:
66 Models extending this class will automatically serialize field names to camelCase:
68 >>> class MyModel(BaseModelWithConfigDict):
69 ... my_field: str = "value"
70 ... another_field: int = 42
71 >>>
72 >>> obj = MyModel()
73 >>> obj.model_dump(by_alias=True)
74 {'myField': 'value', 'anotherField': 42}
76 Important:
77 FastAPI's default response serialization uses `by_alias=True`, so models extending
78 this class will automatically use camelCase in JSON responses without any additional
79 code changes. This is critical for MCP spec compliance.
81 Examples:
82 >>> from mcpgateway.utils.base_models import BaseModelWithConfigDict
83 >>> class CreateMessageResult(BaseModelWithConfigDict):
84 ... stop_reason: str = "endTurn"
85 >>>
86 >>> result = CreateMessageResult()
87 >>> # Without by_alias (internal Python usage):
88 >>> result.model_dump()
89 {'stop_reason': 'endTurn'}
90 >>>
91 >>> # With by_alias (FastAPI automatic serialization):
92 >>> result.model_dump(by_alias=True)
93 {'stopReason': 'endTurn'}
94 """
96 model_config = ConfigDict(
97 from_attributes=True,
98 alias_generator=to_camel_case, # Automatic snake_case → camelCase conversion
99 populate_by_name=True,
100 use_enum_values=True,
101 extra="ignore",
102 json_schema_extra={"nullable": True},
103 )
105 def to_dict(self, use_alias: bool = False) -> Dict[str, Any]:
106 """Convert the model instance into a dictionary representation.
108 Args:
109 use_alias (bool): Whether to use aliases for field names (default is False).
110 If True, field names will be converted using the alias generator.
112 Returns:
113 Dict[str, Any]: A dictionary where keys are field names and values are
114 corresponding field values, with any nested models recursively
115 converted to dictionaries.
116 """
117 return self.model_dump(by_alias=use_alias)