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.
@@ -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
File without changes
File without changes
File without changes
@@ -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