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

wrapping.mddocs/

0

# Enhanced Wrapping

1

2

Advanced function wrapping capabilities that extend `functools.wraps` with signature modification, parameter addition/removal, and better introspection support. These utilities provide precise control over wrapper function signatures while maintaining proper metadata inheritance.

3

4

```python

5

from typing import Union, Optional, Callable, Iterable, Any

6

from inspect import Signature, Parameter

7

```

8

9

## Capabilities

10

11

### Enhanced Function Wrapping

12

13

Enhanced alternative to `functools.wraps` with signature modification capabilities and better introspection support.

14

15

```python { .api }

16

def wraps(wrapped_fun: Callable,

17

new_sig: Optional[Union[str, Signature]] = None,

18

prepend_args: Optional[Union[str, Parameter, Iterable[Union[str, Parameter]]]] = None,

19

append_args: Optional[Union[str, Parameter, Iterable[Union[str, Parameter]]]] = None,

20

remove_args: Optional[Union[str, Iterable[str]]] = None,

21

func_name: Optional[str] = None,

22

co_name: Optional[str] = None,

23

inject_as_first_arg: bool = False,

24

add_source: bool = True,

25

add_impl: bool = True,

26

doc: Optional[str] = None,

27

qualname: Optional[str] = None,

28

module_name: Optional[str] = None,

29

**attrs: Any) -> Callable[[Callable], Callable]:

30

"""

31

Enhanced functools.wraps with signature modification capabilities.

32

33

Parameters:

34

- wrapped_fun: Callable

35

Function to wrap, used as default source for metadata

36

- new_sig: Optional[Union[str, Signature]], default None

37

Complete replacement signature. Cannot be used with other signature modifications

38

- prepend_args: Optional[Union[str, Parameter, Iterable[Union[str, Parameter]]]], default None

39

Arguments to add at the beginning of wrapped_fun's signature

40

- append_args: Optional[Union[str, Parameter, Iterable[Union[str, Parameter]]]], default None

41

Arguments to add at the end of wrapped_fun's signature

42

- remove_args: Optional[Union[str, Iterable[str]]], default None

43

Argument names to remove from wrapped_fun's signature

44

- func_name: Optional[str], default None

45

Override function name (defaults to wrapped_fun.__name__)

46

- co_name: Optional[str], default None

47

Name for compiled code object

48

- inject_as_first_arg: bool, default False

49

Inject created function as first positional argument to wrapper

50

- add_source: bool, default True

51

Add __source__ attribute with generated function source

52

- add_impl: bool, default True

53

Add __func_impl__ attribute pointing to wrapper implementation

54

- doc: Optional[str], default None

55

Docstring override (defaults to wrapped_fun.__doc__)

56

- qualname: Optional[str], default None

57

Qualified name override (defaults to wrapped_fun.__qualname__)

58

- module_name: Optional[str], default None

59

Module name override (defaults to wrapped_fun.__module__)

60

- **attrs: Any

61

Additional attributes to set (wrapped_fun.__dict__ is automatically copied)

62

63

Returns:

64

Callable[[Callable], Callable]: Decorator function that takes a callable and returns a callable

65

66

Note:

67

Sets __wrapped__ attribute for PEP 362 compliance.

68

If signature is modified, sets __signature__ attribute.

69

"""

70

```

71

72

### Direct Wrapper Creation

73

74

Function that directly creates wrapper without requiring decorator syntax.

75

76

```python { .api }

77

def create_wrapper(wrapped: Callable,

78

wrapper: Callable,

79

new_sig: Optional[Union[str, Signature]] = None,

80

prepend_args: Optional[Union[str, Parameter, Iterable[Union[str, Parameter]]]] = None,

81

append_args: Optional[Union[str, Parameter, Iterable[Union[str, Parameter]]]] = None,

82

remove_args: Optional[Union[str, Iterable[str]]] = None,

83

func_name: Optional[str] = None,

84

inject_as_first_arg: bool = False,

85

add_source: bool = True,

86

add_impl: bool = True,

87

doc: Optional[str] = None,

88

qualname: Optional[str] = None,

89

co_name: Optional[str] = None,

90

module_name: Optional[str] = None,

91

**attrs: Any) -> Callable:

92

"""

93

Creates signature-preserving wrapper function.

94

Equivalent to wraps(wrapped, **kwargs)(wrapper).

95

96

Parameters: Same as wraps() function

97

98

Returns:

99

Callable: Generated wrapper function with preserved/modified signature

100

"""

101

```

102

103

### Usage Examples

104

105

#### Basic Function Wrapping

106

107

```python

108

from makefun import wraps

109

import time

110

111

def target_function(x: int, y: str = "default") -> str:

112

"""Original function that does important work."""

113

return f"Result: {x}, {y}"

114

115

@wraps(target_function)

116

def wrapper(x, y):

117

print(f"Calling with x={x}, y={y}")

118

result = target_function(x, y)

119

print(f"Got result: {result}")

120

return result

121

122

# Wrapper preserves original signature and metadata

123

print(wrapper.__name__) # "target_function"

124

print(wrapper.__doc__) # "Original function that does important work."

125

print(wrapper(42)) # Calls with proper signature

126

```

127

128

#### Signature Modification - Adding Parameters

129

130

```python

131

from makefun import wraps

132

from inspect import signature

133

134

def original_func(data: list) -> int:

135

"""Process a list of data."""

136

return len(data)

137

138

@wraps(original_func, prepend_args="verbose: bool = False", append_args="log_level: str = 'INFO'")

139

def enhanced_wrapper(verbose, data, log_level):

140

if verbose:

141

print(f"Processing {len(data)} items at {log_level} level")

142

result = original_func(data)

143

if verbose:

144

print(f"Processed {result} items")

145

return result

146

147

print(signature(enhanced_wrapper)) # (verbose: bool = False, data: list, log_level: str = 'INFO') -> int

148

print(enhanced_wrapper([1, 2, 3])) # 3 (non-verbose)

149

print(enhanced_wrapper(True, [1, 2, 3, 4], "DEBUG")) # Verbose output + 4

150

```

151

152

#### Signature Modification - Removing Parameters

153

154

```python

155

from makefun import wraps

156

157

def complex_function(data: list, mode: str, debug: bool = False, timeout: int = 30) -> dict:

158

"""Complex function with many parameters."""

159

return {

160

"data_size": len(data),

161

"mode": mode,

162

"debug": debug,

163

"timeout": timeout

164

}

165

166

@wraps(complex_function, remove_args=["debug", "timeout"])

167

def simplified_wrapper(data, mode):

168

# Always use specific values for removed parameters

169

return complex_function(data, mode, debug=True, timeout=60)

170

171

# Simplified signature: (data: list, mode: str) -> dict

172

print(simplified_wrapper([1, 2, 3], "fast"))

173

```

174

175

#### Complete Signature Replacement

176

177

```python

178

from makefun import wraps

179

180

def legacy_function(a, b, c=None):

181

"""Legacy function with old-style signature."""

182

return f"Legacy: a={a}, b={b}, c={c}"

183

184

@wraps(legacy_function, new_sig="modern_api(data: dict, options: dict = None) -> str")

185

def modernized_wrapper(data, options):

186

# Transform modern parameters to legacy format

187

a = data.get("value1", 0)

188

b = data.get("value2", "")

189

c = options.get("extra") if options else None

190

return legacy_function(a, b, c)

191

192

# New signature: (data: dict, options: dict = None) -> str

193

result = modernized_wrapper({"value1": 42, "value2": "hello"}, {"extra": "world"})

194

print(result) # "Legacy: a=42, b=hello, c=world"

195

```

196

197

#### Timing Decorator Example

198

199

```python

200

from makefun import wraps

201

import time

202

import functools

203

204

def timing_decorator(func):

205

@wraps(func)

206

def wrapper(*args, **kwargs):

207

start_time = time.time()

208

result = func(*args, **kwargs)

209

end_time = time.time()

210

print(f"{func.__name__} took {end_time - start_time:.4f} seconds")

211

return result

212

return wrapper

213

214

@timing_decorator

215

def slow_computation(n: int) -> int:

216

"""Compute sum of squares up to n."""

217

return sum(i * i for i in range(n))

218

219

# Wrapper preserves original signature and metadata

220

print(slow_computation.__name__) # "slow_computation"

221

print(slow_computation.__doc__) # "Compute sum of squares up to n."

222

result = slow_computation(1000) # Prints timing info

223

```

224

225

#### Caching with Parameter Modification

226

227

```python

228

from makefun import wraps

229

import functools

230

231

def cached_function(x: int, y: int, use_cache: bool = True) -> int:

232

"""Expensive computation."""

233

print(f"Computing {x} + {y}")

234

return x + y

235

236

@wraps(cached_function, remove_args="use_cache")

237

@functools.lru_cache(maxsize=128)

238

def cached_wrapper(x, y):

239

# Always use caching, remove cache control parameter

240

return cached_function(x, y, use_cache=True)

241

242

# Simplified signature without use_cache parameter

243

print(cached_wrapper(5, 3)) # Computes and caches

244

print(cached_wrapper(5, 3)) # Uses cache

245

```

246

247

#### Validation Decorator with Parameter Addition

248

249

```python

250

from makefun import wraps

251

252

def unsafe_divide(x: float, y: float) -> float:

253

"""Divide x by y."""

254

return x / y

255

256

@wraps(unsafe_divide, append_args="validate: bool = True")

257

def safe_wrapper(x, y, validate):

258

if validate and y == 0:

259

raise ValueError("Division by zero not allowed")

260

if validate and not isinstance(x, (int, float)) or not isinstance(y, (int, float)):

261

raise TypeError("Arguments must be numeric")

262

return unsafe_divide(x, y)

263

264

# Enhanced signature: (x: float, y: float, validate: bool = True) -> float

265

print(safe_wrapper(10.0, 2.0)) # 5.0

266

print(safe_wrapper(10.0, 2.0, False)) # 5.0 (no validation)

267

```

268

269

#### Multiple Parameter Modifications

270

271

```python

272

from makefun import wraps

273

from typing import List, Optional

274

275

def basic_processor(items: List[str]) -> List[str]:

276

"""Basic item processing."""

277

return [item.upper() for item in items]

278

279

@wraps(basic_processor,

280

prepend_args="log_prefix: str = '[PROC]'",

281

append_args=["max_items: int = 100", "filter_empty: bool = True"])

282

def enhanced_processor(log_prefix, items, max_items, filter_empty):

283

print(f"{log_prefix} Processing up to {max_items} items")

284

285

if filter_empty:

286

items = [item for item in items if item.strip()]

287

288

items = items[:max_items]

289

result = basic_processor(items)

290

291

print(f"{log_prefix} Processed {len(result)} items")

292

return result

293

294

# New signature: (log_prefix: str = '[PROC]', items: List[str], max_items: int = 100, filter_empty: bool = True) -> List[str]

295

result = enhanced_processor(items=["hello", "", "world", "test"])

296

```

297

298

#### Error Handling

299

300

```python

301

from makefun import wraps

302

303

def target_func(x: int) -> int:

304

return x * 2

305

306

# Cannot combine new_sig with other signature modifications

307

try:

308

@wraps(target_func, new_sig="(y: int)", prepend_args="z: int")

309

def invalid_wrapper(z, y):

310

return target_func(y)

311

except ValueError as e:

312

print(f"Configuration error: {e}")

313

314

# Invalid parameter name to remove

315

try:

316

@wraps(target_func, remove_args="nonexistent_param")

317

def another_wrapper(x):

318

return target_func(x)

319

except KeyError as e:

320

print(f"Parameter error: {e}")

321

```

322

323

### Direct Wrapper Creation

324

325

Alternative to decorator syntax for programmatic wrapper creation:

326

327

```python

328

from makefun import create_wrapper

329

330

def original(x: int, y: str) -> str:

331

return f"{x}: {y}"

332

333

def my_wrapper(x, y):

334

print(f"Wrapper called with {x}, {y}")

335

return original(x, y)

336

337

# Create wrapper directly

338

wrapped_func = create_wrapper(original, my_wrapper)

339

340

# Equivalent to:

341

# @wraps(original)

342

# def wrapped_func(x, y):

343

# print(f"Wrapper called with {x}, {y}")

344

# return original(x, y)

345

346

print(wrapped_func(42, "hello"))

347

```

348

349

### Comparison with functools.wraps

350

351

Key advantages over `functools.wraps`:

352

353

1. **Signature Validation**: Arguments are validated against the signature before entering wrapper

354

2. **Signature Modification**: Can add, remove, or completely replace function signatures

355

3. **Better Introspection**: Maintains proper `inspect.signature()` support

356

4. **Keyword Arguments**: Arguments passed as keywords when possible for better flexibility

357

5. **PEP 362 Compliance**: Proper `__wrapped__` and `__signature__` attributes

358

359

```python

360

import functools

361

from makefun import wraps

362

363

def original(x: int, y: str = "default") -> str:

364

return f"{x}: {y}"

365

366

# functools.wraps: basic wrapping

367

@functools.wraps(original)

368

def basic_wrapper(*args, **kwargs):

369

return original(*args, **kwargs)

370

371

# makefun.wraps: enhanced wrapping with signature preservation

372

@wraps(original)

373

def enhanced_wrapper(x, y):

374

return original(x, y)

375

376

# enhanced_wrapper validates arguments before calling wrapper

377

# enhanced_wrapper receives arguments as keywords when possible

378

# enhanced_wrapper maintains exact signature introspection

379

```