or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

compilation.mddecorators.mdfunction-creation.mdindex.mdpartial.mdsignature-utils.mdwrapping.md

decorators.mddocs/

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

```