0
# Signature-based Decorators
1
2
Decorators for modifying function signatures while preserving metadata and introspection capabilities. These decorators provide enhanced alternatives to standard Python decorators with precise signature control and comprehensive metadata management.
3
4
```python
5
from typing import Union, Optional, Callable, Any
6
from inspect import Signature
7
```
8
9
## Capabilities
10
11
### Signature Modification Decorator
12
13
Decorator that changes function signatures, equivalent to `create_function` but applied as a decorator for cleaner syntax.
14
15
```python { .api }
16
def with_signature(func_signature: Union[str, Signature, None],
17
func_name: Optional[str] = None,
18
inject_as_first_arg: bool = False,
19
add_source: bool = True,
20
add_impl: bool = True,
21
doc: Optional[str] = None,
22
qualname: Optional[str] = None,
23
co_name: Optional[str] = None,
24
module_name: Optional[str] = None,
25
**attrs: Any) -> Callable[[Callable], Callable]:
26
"""
27
Decorator to change function signature.
28
29
Parameters:
30
- func_signature: Union[str, Signature, None]
31
New signature specification. Can be:
32
- String without 'def': "foo(a, b: int, *args, **kwargs)"
33
- Signature object from inspect.signature()
34
- None for metadata-only changes (no signature modification)
35
- func_name: Optional[str], default None
36
Override for __name__ and __qualname__ attributes
37
- inject_as_first_arg: bool, default False
38
If True, inject created function as first positional argument
39
- add_source: bool, default True
40
Add __source__ attribute containing generated function source
41
- add_impl: bool, default True
42
Add __func_impl__ attribute pointing to original function
43
- doc: Optional[str], default None
44
Docstring for generated function. Defaults to original function's doc
45
- qualname: Optional[str], default None
46
Qualified name for generated function
47
- co_name: Optional[str], default None
48
Name for compiled code object
49
- module_name: Optional[str], default None
50
Module name for generated function
51
- **attrs: Any
52
Additional attributes to set on generated function
53
54
Returns:
55
Callable[[Callable], Callable]: Decorator function that takes a callable and returns a callable
56
57
Note:
58
When func_signature=None, only metadata changes are applied without
59
creating a wrapper. In this case, add_source, add_impl, and
60
inject_as_first_arg should not be used.
61
"""
62
```
63
64
### Usage Examples
65
66
#### Basic Signature Modification
67
68
```python
69
from makefun import with_signature
70
71
@with_signature("greet(name: str, age: int = 25)")
72
def hello(name, age):
73
return f"Hello {name}, you are {age} years old"
74
75
# Function now has the specified signature
76
print(hello("Alice")) # "Hello Alice, you are 25 years old"
77
print(hello("Bob", 30)) # "Hello Bob, you are 30 years old"
78
79
# Signature is properly reflected in introspection
80
from inspect import signature
81
print(signature(hello)) # (name: str, age: int = 25)
82
```
83
84
#### Signature from Another Function
85
86
```python
87
from makefun import with_signature
88
from inspect import signature
89
90
def template_func(x: int, y: str, z: float = 1.0) -> str:
91
pass
92
93
@with_signature(signature(template_func))
94
def my_func(x, y, z):
95
return f"x={x}, y={y}, z={z}"
96
97
print(my_func(42, "hello")) # "x=42, y=hello, z=1.0"
98
```
99
100
#### Metadata-Only Changes
101
102
```python
103
from makefun import with_signature
104
105
@with_signature(None,
106
func_name="enhanced_processor",
107
doc="Enhanced data processing function",
108
module_name="data_utils",
109
version="2.0")
110
def process_data(data):
111
"""Original docstring"""
112
return f"Processing {len(data)} items"
113
114
print(process_data.__name__) # "enhanced_processor"
115
print(process_data.__doc__) # "Enhanced data processing function"
116
print(process_data.__module__) # "data_utils"
117
print(process_data.version) # "2.0"
118
```
119
120
#### Complex Signature Examples
121
122
```python
123
from makefun import with_signature
124
125
# Keyword-only parameters
126
@with_signature("analyze(data: list, *, method: str, verbose: bool = False)")
127
def data_analyzer(data, method, verbose):
128
result = f"Analyzing {len(data)} items using {method}"
129
if verbose:
130
result += " (verbose mode)"
131
return result
132
133
print(data_analyzer([1, 2, 3], method="statistical"))
134
135
# Positional-only parameters (Python 3.8+)
136
@with_signature("compute(x: float, y: float, /, mode: str = 'fast') -> float")
137
def calculator(x, y, mode):
138
if mode == "fast":
139
return x + y
140
else:
141
return x * y + y * x
142
143
print(calculator(2.5, 3.5)) # 6.0
144
145
# Variable arguments
146
@with_signature("handler(*args, **kwargs) -> dict")
147
def request_handler(*args, **kwargs):
148
return {"args": args, "kwargs": kwargs}
149
150
print(request_handler(1, 2, 3, name="test", value=42))
151
```
152
153
#### Function Name from Signature
154
155
```python
156
from makefun import with_signature
157
158
# Function name in signature overrides original name
159
@with_signature("custom_name(value: str) -> str")
160
def original_name(value):
161
return f"Processed: {value}"
162
163
print(original_name.__name__) # "custom_name"
164
print(original_name("test")) # "Processed: test"
165
```
166
167
#### Advanced Metadata Control
168
169
```python
170
from makefun import with_signature
171
172
@with_signature("api_call(endpoint: str, data: dict = None, timeout: int = 30)",
173
func_name="make_api_call",
174
doc="Make an HTTP API call with timeout and error handling",
175
qualname="APIClient.make_api_call",
176
module_name="api_client",
177
add_source=True,
178
add_impl=True,
179
api_version="v1.2",
180
deprecated=False)
181
def http_request(endpoint, data, timeout):
182
return f"Calling {endpoint} with data={data}, timeout={timeout}"
183
184
# All metadata is properly set
185
print(http_request.__name__) # "make_api_call"
186
print(http_request.__qualname__) # "APIClient.make_api_call"
187
print(http_request.__module__) # "api_client"
188
print(http_request.api_version) # "v1.2"
189
print(http_request.deprecated) # False
190
191
# Source code is available
192
print(http_request.__source__) # Generated function source
193
print(http_request.__func_impl__) # Original function reference
194
```
195
196
#### Generator and Async Function Support
197
198
```python
199
from makefun import with_signature
200
import asyncio
201
202
@with_signature("generate_numbers(start: int, end: int, step: int = 1)")
203
def number_generator(start, end, step):
204
for i in range(start, end, step):
205
yield f"Number: {i}"
206
207
# Generator signature is preserved
208
for num in number_generator(1, 5):
209
print(num)
210
211
@with_signature("fetch_data(url: str, headers: dict = None) -> dict")
212
async def async_fetcher(url, headers):
213
await asyncio.sleep(0.1) # Simulate network call
214
return {"url": url, "headers": headers or {}, "status": "success"}
215
216
# Async function signature is preserved
217
result = asyncio.run(async_fetcher("https://api.example.com"))
218
print(result)
219
```
220
221
#### Error Handling
222
223
```python
224
from makefun import with_signature
225
226
# Invalid usage: metadata-only mode with add_source=False conflicts
227
try:
228
@with_signature(None, add_source=False, add_impl=False, inject_as_first_arg=True)
229
def invalid_func():
230
pass
231
except ValueError as e:
232
print(f"Configuration error: {e}")
233
234
# Invalid signature format
235
try:
236
@with_signature("invalid syntax here")
237
def bad_signature():
238
pass
239
except SyntaxError as e:
240
print(f"Signature error: {e}")
241
```
242
243
### Comparison with create_function
244
245
The `@with_signature` decorator is equivalent to `create_function` but provides cleaner syntax:
246
247
```python
248
from makefun import with_signature, create_function
249
250
# These are equivalent:
251
252
# Using decorator
253
@with_signature("func(x: int, y: str) -> str")
254
def my_func1(x, y):
255
return f"{x}: {y}"
256
257
# Using create_function
258
def my_func2_impl(x, y):
259
return f"{x}: {y}"
260
261
my_func2 = create_function("func(x: int, y: str) -> str", my_func2_impl)
262
```
263
264
### Integration with Type Checkers
265
266
The generated functions work properly with static type checkers like mypy:
267
268
```python
269
from makefun import with_signature
270
from typing import List, Optional
271
272
@with_signature("process_items(items: List[str], filter_empty: bool = True) -> List[str]")
273
def item_processor(items, filter_empty):
274
if filter_empty:
275
return [item for item in items if item.strip()]
276
return items
277
278
# Type checker understands the signature
279
result: List[str] = item_processor(["hello", "", "world"]) # Type-safe
280
```