pydantic-rpc 0.7.0__py3-none-any.whl → 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pydantic_rpc/__init__.py +12 -4
- pydantic_rpc/core.py +686 -203
- pydantic_rpc/decorators.py +138 -0
- pydantic_rpc/mcp/__init__.py +0 -0
- pydantic_rpc/mcp/converter.py +0 -0
- pydantic_rpc/mcp/exporter.py +0 -0
- pydantic_rpc/options.py +134 -0
- pydantic_rpc/py.typed +0 -0
- {pydantic_rpc-0.7.0.dist-info → pydantic_rpc-0.9.0.dist-info}/METADATA +286 -58
- pydantic_rpc-0.9.0.dist-info/RECORD +12 -0
- pydantic_rpc-0.9.0.dist-info/WHEEL +4 -0
- {pydantic_rpc-0.7.0.dist-info → pydantic_rpc-0.9.0.dist-info}/entry_points.txt +1 -0
- pydantic_rpc-0.7.0.dist-info/RECORD +0 -11
- pydantic_rpc-0.7.0.dist-info/WHEEL +0 -4
- pydantic_rpc-0.7.0.dist-info/licenses/LICENSE +0 -21
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"""Decorators for adding protobuf options to RPC methods."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Callable, Dict, List, Optional, TypeVar
|
|
4
|
+
from functools import wraps
|
|
5
|
+
|
|
6
|
+
from .options import OptionMetadata, OPTION_METADATA_ATTR
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def http_option(
|
|
13
|
+
method: str,
|
|
14
|
+
path: str,
|
|
15
|
+
body: Optional[str] = None,
|
|
16
|
+
response_body: Optional[str] = None,
|
|
17
|
+
additional_bindings: Optional[List[Dict[str, Any]]] = None,
|
|
18
|
+
) -> Callable[[F], F]:
|
|
19
|
+
"""
|
|
20
|
+
Decorator to add google.api.http option to an RPC method.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
method: HTTP method (GET, POST, PUT, DELETE, PATCH)
|
|
24
|
+
path: URL path template (e.g., "/v1/books/{id}")
|
|
25
|
+
body: Request body mapping (e.g., "*" for entire body)
|
|
26
|
+
response_body: Response body mapping (specific field to return)
|
|
27
|
+
additional_bindings: List of additional HTTP bindings
|
|
28
|
+
|
|
29
|
+
Example:
|
|
30
|
+
@http_option(method="GET", path="/v1/books/{id}")
|
|
31
|
+
async def get_book(self, request: GetBookRequest) -> Book:
|
|
32
|
+
...
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def decorator(func: F) -> F:
|
|
36
|
+
# Get or create option metadata
|
|
37
|
+
if not hasattr(func, OPTION_METADATA_ATTR):
|
|
38
|
+
setattr(func, OPTION_METADATA_ATTR, OptionMetadata())
|
|
39
|
+
|
|
40
|
+
metadata: OptionMetadata = getattr(func, OPTION_METADATA_ATTR)
|
|
41
|
+
|
|
42
|
+
# Set HTTP option
|
|
43
|
+
metadata.set_http_option(
|
|
44
|
+
method=method,
|
|
45
|
+
path=path,
|
|
46
|
+
body=body,
|
|
47
|
+
response_body=response_body,
|
|
48
|
+
additional_bindings=additional_bindings,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
@wraps(func)
|
|
52
|
+
def wrapper(*args, **kwargs):
|
|
53
|
+
return func(*args, **kwargs)
|
|
54
|
+
|
|
55
|
+
# Preserve the metadata on the wrapper
|
|
56
|
+
setattr(wrapper, OPTION_METADATA_ATTR, metadata)
|
|
57
|
+
|
|
58
|
+
return wrapper # type: ignore
|
|
59
|
+
|
|
60
|
+
return decorator
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def proto_option(name: str, value: Any) -> Callable[[F], F]:
|
|
64
|
+
"""
|
|
65
|
+
Decorator to add a generic protobuf option to an RPC method.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
name: Option name (e.g., "deprecated", "idempotency_level")
|
|
69
|
+
value: Option value
|
|
70
|
+
|
|
71
|
+
Example:
|
|
72
|
+
@proto_option("deprecated", True)
|
|
73
|
+
@proto_option("idempotency_level", "IDEMPOTENT")
|
|
74
|
+
async def old_method(self, request: Request) -> Response:
|
|
75
|
+
...
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
def decorator(func: F) -> F:
|
|
79
|
+
# Get or create option metadata
|
|
80
|
+
if not hasattr(func, OPTION_METADATA_ATTR):
|
|
81
|
+
setattr(func, OPTION_METADATA_ATTR, OptionMetadata())
|
|
82
|
+
|
|
83
|
+
metadata: OptionMetadata = getattr(func, OPTION_METADATA_ATTR)
|
|
84
|
+
|
|
85
|
+
# Add proto option
|
|
86
|
+
metadata.add_proto_option(name=name, value=value)
|
|
87
|
+
|
|
88
|
+
@wraps(func)
|
|
89
|
+
def wrapper(*args, **kwargs):
|
|
90
|
+
return func(*args, **kwargs)
|
|
91
|
+
|
|
92
|
+
# Preserve the metadata on the wrapper
|
|
93
|
+
setattr(wrapper, OPTION_METADATA_ATTR, metadata)
|
|
94
|
+
|
|
95
|
+
return wrapper # type: ignore
|
|
96
|
+
|
|
97
|
+
return decorator
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def get_method_options(method: Callable) -> Optional[OptionMetadata]:
|
|
101
|
+
"""
|
|
102
|
+
Get option metadata from a method.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
method: The method to get options from
|
|
106
|
+
|
|
107
|
+
Returns:
|
|
108
|
+
OptionMetadata if present, None otherwise
|
|
109
|
+
"""
|
|
110
|
+
return getattr(method, OPTION_METADATA_ATTR, None)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def has_http_option(method: Callable) -> bool:
|
|
114
|
+
"""
|
|
115
|
+
Check if a method has an HTTP option.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
method: The method to check
|
|
119
|
+
|
|
120
|
+
Returns:
|
|
121
|
+
True if the method has an HTTP option, False otherwise
|
|
122
|
+
"""
|
|
123
|
+
metadata = get_method_options(method)
|
|
124
|
+
return metadata is not None and metadata.http_option is not None
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def has_proto_options(method: Callable) -> bool:
|
|
128
|
+
"""
|
|
129
|
+
Check if a method has any proto options.
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
method: The method to check
|
|
133
|
+
|
|
134
|
+
Returns:
|
|
135
|
+
True if the method has proto options, False otherwise
|
|
136
|
+
"""
|
|
137
|
+
metadata = get_method_options(method)
|
|
138
|
+
return metadata is not None and len(metadata.proto_options) > 0
|
pydantic_rpc/mcp/__init__.py
CHANGED
|
File without changes
|
pydantic_rpc/mcp/converter.py
CHANGED
|
File without changes
|
pydantic_rpc/mcp/exporter.py
CHANGED
|
File without changes
|
pydantic_rpc/options.py
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Protocol Buffer options support for pydantic-rpc."""
|
|
2
|
+
|
|
3
|
+
from typing import Any, Dict, List, Optional
|
|
4
|
+
from pydantic import BaseModel, Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class HttpBinding(BaseModel):
|
|
8
|
+
"""HTTP binding configuration for a single HTTP method."""
|
|
9
|
+
|
|
10
|
+
method: str = Field(..., description="HTTP method (get, post, put, delete, patch)")
|
|
11
|
+
path: Optional[str] = Field(None, description="URL path template")
|
|
12
|
+
body: Optional[str] = Field(None, description="Request body mapping")
|
|
13
|
+
response_body: Optional[str] = Field(None, description="Response body mapping")
|
|
14
|
+
|
|
15
|
+
def to_proto_dict(self) -> Dict[str, Any]:
|
|
16
|
+
"""Convert to protobuf option format."""
|
|
17
|
+
result = {}
|
|
18
|
+
if self.path:
|
|
19
|
+
result[self.method.lower()] = self.path
|
|
20
|
+
if self.body:
|
|
21
|
+
result["body"] = self.body
|
|
22
|
+
if self.response_body:
|
|
23
|
+
result["response_body"] = self.response_body
|
|
24
|
+
return result
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class HttpOption(BaseModel):
|
|
28
|
+
"""Google API HTTP option configuration."""
|
|
29
|
+
|
|
30
|
+
method: str = Field(..., description="Primary HTTP method")
|
|
31
|
+
path: str = Field(..., description="Primary URL path template")
|
|
32
|
+
body: Optional[str] = Field(None, description="Request body mapping")
|
|
33
|
+
response_body: Optional[str] = Field(None, description="Response body mapping")
|
|
34
|
+
additional_bindings: List[Dict[str, Any]] = Field(
|
|
35
|
+
default_factory=list, description="Additional HTTP bindings"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
def to_proto_string(self) -> str:
|
|
39
|
+
"""Convert to protobuf option string format."""
|
|
40
|
+
lines = []
|
|
41
|
+
lines.append("option (google.api.http) = {")
|
|
42
|
+
|
|
43
|
+
# Primary binding
|
|
44
|
+
lines.append(f' {self.method.lower()}: "{self.path}"')
|
|
45
|
+
|
|
46
|
+
# Body mapping
|
|
47
|
+
if self.body:
|
|
48
|
+
lines.append(f' body: "{self.body}"')
|
|
49
|
+
|
|
50
|
+
# Response body mapping
|
|
51
|
+
if self.response_body:
|
|
52
|
+
lines.append(f' response_body: "{self.response_body}"')
|
|
53
|
+
|
|
54
|
+
# Additional bindings
|
|
55
|
+
for binding in self.additional_bindings:
|
|
56
|
+
lines.append(" additional_bindings {")
|
|
57
|
+
for key, value in binding.items():
|
|
58
|
+
if key == "body":
|
|
59
|
+
lines.append(f' {key}: "{value}"')
|
|
60
|
+
else:
|
|
61
|
+
lines.append(f' {key}: "{value}"')
|
|
62
|
+
lines.append(" }")
|
|
63
|
+
|
|
64
|
+
lines.append("};")
|
|
65
|
+
return "\n".join(lines)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class ProtoOption(BaseModel):
|
|
69
|
+
"""Generic protocol buffer option."""
|
|
70
|
+
|
|
71
|
+
name: str = Field(..., description="Option name")
|
|
72
|
+
value: Any = Field(..., description="Option value")
|
|
73
|
+
|
|
74
|
+
def to_proto_string(self) -> str:
|
|
75
|
+
"""Convert to protobuf option string format."""
|
|
76
|
+
if isinstance(self.value, bool):
|
|
77
|
+
value_str = "true" if self.value else "false"
|
|
78
|
+
elif isinstance(self.value, str):
|
|
79
|
+
# Check if it's an enum value (no quotes) or string literal (quotes)
|
|
80
|
+
if self.value.isupper() or "_" in self.value:
|
|
81
|
+
# Likely an enum value
|
|
82
|
+
value_str = self.value
|
|
83
|
+
else:
|
|
84
|
+
value_str = f'"{self.value}"'
|
|
85
|
+
else:
|
|
86
|
+
value_str = str(self.value)
|
|
87
|
+
|
|
88
|
+
return f"option {self.name} = {value_str};"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
class OptionMetadata(BaseModel):
|
|
92
|
+
"""Metadata container for method/service options."""
|
|
93
|
+
|
|
94
|
+
http_option: Optional[HttpOption] = None
|
|
95
|
+
proto_options: List[ProtoOption] = Field(default_factory=list)
|
|
96
|
+
|
|
97
|
+
def add_proto_option(self, name: str, value: Any) -> None:
|
|
98
|
+
"""Add a generic proto option."""
|
|
99
|
+
self.proto_options.append(ProtoOption(name=name, value=value))
|
|
100
|
+
|
|
101
|
+
def set_http_option(
|
|
102
|
+
self,
|
|
103
|
+
method: str,
|
|
104
|
+
path: str,
|
|
105
|
+
body: Optional[str] = None,
|
|
106
|
+
response_body: Optional[str] = None,
|
|
107
|
+
additional_bindings: Optional[List[Dict[str, Any]]] = None,
|
|
108
|
+
) -> None:
|
|
109
|
+
"""Set the HTTP option configuration."""
|
|
110
|
+
self.http_option = HttpOption(
|
|
111
|
+
method=method,
|
|
112
|
+
path=path,
|
|
113
|
+
body=body,
|
|
114
|
+
response_body=response_body,
|
|
115
|
+
additional_bindings=additional_bindings or [],
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
def to_proto_strings(self) -> List[str]:
|
|
119
|
+
"""Convert all options to protobuf strings."""
|
|
120
|
+
result = []
|
|
121
|
+
|
|
122
|
+
# Add HTTP option first if present
|
|
123
|
+
if self.http_option:
|
|
124
|
+
result.append(self.http_option.to_proto_string())
|
|
125
|
+
|
|
126
|
+
# Add other proto options
|
|
127
|
+
for option in self.proto_options:
|
|
128
|
+
result.append(option.to_proto_string())
|
|
129
|
+
|
|
130
|
+
return result
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# Option metadata attribute name used on methods
|
|
134
|
+
OPTION_METADATA_ATTR = "__pydantic_rpc_options__"
|
pydantic_rpc/py.typed
CHANGED
|
File without changes
|