or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

decorators.mdfunction-wrappers.mdindex.mdpatching.mdproxy-objects.mdutilities.md

function-wrappers.mddocs/

0

# Function Wrappers

1

2

Specialized wrappers for functions that handle method binding, descriptor protocol, and function-specific behavior with proper signature preservation. These wrappers are designed specifically for wrapping functions and methods while maintaining their introspection capabilities and binding behavior.

3

4

## Capabilities

5

6

### FunctionWrapper

7

8

A specialized wrapper for functions that properly handles method binding, the descriptor protocol, and function-specific behavior. This is the core wrapper used by the `@decorator` function and provides the foundation for creating robust function decorators.

9

10

```python { .api }

11

class FunctionWrapper(ObjectProxy):

12

def __init__(self, wrapped, wrapper, enabled=None):

13

"""

14

Create a function wrapper with proper binding support.

15

16

Args:

17

wrapped: The function to wrap

18

wrapper: Wrapper function with signature (wrapped, instance, args, kwargs) -> result

19

enabled: Boolean or callable to enable/disable wrapper (optional)

20

"""

21

22

@property

23

def _self_wrapper(self):

24

"""The wrapper function."""

25

26

@property

27

def _self_enabled(self):

28

"""Enable/disable flag or callable."""

29

30

@property

31

def _self_instance(self):

32

"""Instance the function is bound to (if any)."""

33

34

@property

35

def _self_binding(self):

36

"""Type of binding ('function', 'method', 'classmethod', etc.)."""

37

38

@property

39

def _self_parent(self):

40

"""Parent wrapper (for bound functions)."""

41

42

def __get__(self, instance, owner):

43

"""

44

Descriptor protocol for method binding.

45

46

Args:

47

instance: Instance object (None for class access)

48

owner: Owner class

49

50

Returns:

51

BoundFunctionWrapper or self

52

"""

53

54

def __set_name__(self, owner, name):

55

"""

56

Support for descriptor naming (Python 3.6+).

57

58

Args:

59

owner: Owner class

60

name: Attribute name

61

"""

62

63

def __call__(self, *args, **kwargs):

64

"""

65

Call the wrapped function through the wrapper.

66

67

Args:

68

*args: Positional arguments

69

**kwargs: Keyword arguments

70

71

Returns:

72

Result from wrapper function

73

"""

74

75

def __instancecheck__(self, instance):

76

"""Support for isinstance() checks."""

77

78

def __subclasscheck__(self, subclass):

79

"""Support for issubclass() checks."""

80

```

81

82

**Usage Example:**

83

84

```python

85

import wrapt

86

87

def logging_wrapper(wrapped, instance, args, kwargs):

88

print(f"Calling {wrapped.__name__} with args={args}, kwargs={kwargs}")

89

if instance is not None:

90

print(f"Instance: {instance}")

91

result = wrapped(*args, **kwargs)

92

print(f"Result: {result}")

93

return result

94

95

# Wrap a function

96

def add(a, b):

97

return a + b

98

99

wrapped_add = wrapt.FunctionWrapper(add, logging_wrapper)

100

result = wrapped_add(2, 3) # Logs call details

101

102

# Wrap a method

103

class Calculator:

104

def multiply(self, a, b):

105

return a * b

106

107

Calculator.multiply = wrapt.FunctionWrapper(Calculator.multiply, logging_wrapper)

108

calc = Calculator()

109

result = calc.multiply(4, 5) # Logs with instance information

110

```

111

112

### BoundFunctionWrapper

113

114

Created automatically when a FunctionWrapper is accessed as a method. Handles the complexities of method calls, instance binding, and argument handling. You typically don't create these directly.

115

116

```python { .api }

117

class BoundFunctionWrapper:

118

"""

119

Wrapper for bound functions (methods). Created automatically by

120

FunctionWrapper.__get__() when accessing wrapped methods on instances.

121

"""

122

123

def __call__(self, *args, **kwargs):

124

"""

125

Call the bound method with proper instance binding.

126

127

Args:

128

*args: Positional arguments

129

**kwargs: Keyword arguments

130

131

Returns:

132

Result from wrapper function

133

"""

134

```

135

136

**Example of automatic binding:**

137

138

```python

139

import wrapt

140

141

def method_wrapper(wrapped, instance, args, kwargs):

142

print(f"Method {wrapped.__name__} called on {type(instance).__name__}")

143

return wrapped(*args, **kwargs)

144

145

class MyClass:

146

def __init__(self, name):

147

self.name = name

148

149

def greet(self, message):

150

return f"{self.name}: {message}"

151

152

# Wrap the method

153

MyClass.greet = wrapt.FunctionWrapper(MyClass.greet, method_wrapper)

154

155

# Create instance and call method

156

obj = MyClass("Alice")

157

result = obj.greet("Hello!") # Creates BoundFunctionWrapper automatically

158

# Prints: "Method greet called on MyClass"

159

# Returns: "Alice: Hello!"

160

```

161

162

## Advanced Usage

163

164

### Conditional Wrappers

165

166

Use the `enabled` parameter to conditionally enable/disable wrapping:

167

168

```python

169

import wrapt

170

import os

171

172

def debug_wrapper(wrapped, instance, args, kwargs):

173

print(f"DEBUG: Calling {wrapped.__name__}")

174

return wrapped(*args, **kwargs)

175

176

# Enable only when DEBUG environment variable is set

177

debug_enabled = lambda: os.environ.get('DEBUG') == '1'

178

179

@wrapt.FunctionWrapper(enabled=debug_enabled)

180

def my_function():

181

return "result"

182

183

# Wrapper only executes if DEBUG=1 is set

184

result = my_function()

185

```

186

187

### Wrapper Chains

188

189

FunctionWrapper supports chaining multiple wrappers:

190

191

```python

192

import wrapt

193

194

def timing_wrapper(wrapped, instance, args, kwargs):

195

import time

196

start = time.time()

197

result = wrapped(*args, **kwargs)

198

print(f"Time: {time.time() - start:.4f}s")

199

return result

200

201

def logging_wrapper(wrapped, instance, args, kwargs):

202

print(f"Calling: {wrapped.__name__}")

203

result = wrapped(*args, **kwargs)

204

print(f"Result: {result}")

205

return result

206

207

def slow_function():

208

import time

209

time.sleep(0.1)

210

return "done"

211

212

# Chain wrappers (inner wrapper applied first)

213

wrapped_once = wrapt.FunctionWrapper(slow_function, timing_wrapper)

214

wrapped_twice = wrapt.FunctionWrapper(wrapped_once, logging_wrapper)

215

216

wrapped_twice() # Logs then times the function

217

```

218

219

### Custom Wrapper Classes

220

221

Extend FunctionWrapper for specialized behavior:

222

223

```python

224

import wrapt

225

226

class CachingWrapper(wrapt.FunctionWrapper):

227

def __init__(self, wrapped, wrapper):

228

super().__init__(wrapped, wrapper)

229

self._self_cache = {}

230

231

def __call__(self, *args, **kwargs):

232

# Create cache key from arguments

233

key = (args, tuple(sorted(kwargs.items())))

234

235

if key in self._self_cache:

236

print(f"Cache hit for {self.__wrapped__.__name__}")

237

return self._self_cache[key]

238

239

result = super().__call__(*args, **kwargs)

240

self._self_cache[key] = result

241

return result

242

243

def identity_wrapper(wrapped, instance, args, kwargs):

244

return wrapped(*args, **kwargs)

245

246

def expensive_function(n):

247

import time

248

time.sleep(0.5) # Simulate expensive operation

249

return n * 2

250

251

cached_function = CachingWrapper(expensive_function, identity_wrapper)

252

print(cached_function(5)) # Slow first call

253

print(cached_function(5)) # Fast cached call

254

```

255

256

## Descriptor Protocol

257

258

FunctionWrapper fully implements the descriptor protocol, making it work correctly with class methods, static methods, and properties:

259

260

```python

261

import wrapt

262

263

def method_wrapper(wrapped, instance, args, kwargs):

264

print(f"Wrapper called with instance: {instance}")

265

return wrapped(*args, **kwargs)

266

267

class MyClass:

268

@wrapt.FunctionWrapper(method_wrapper)

269

def instance_method(self):

270

return "instance"

271

272

@classmethod

273

@wrapt.FunctionWrapper(method_wrapper)

274

def class_method(cls):

275

return "class"

276

277

@staticmethod

278

@wrapt.FunctionWrapper(method_wrapper)

279

def static_method():

280

return "static"

281

282

obj = MyClass()

283

obj.instance_method() # instance != None

284

MyClass.class_method() # instance = MyClass

285

MyClass.static_method() # instance = None

286

```

287

288

## Error Handling

289

290

FunctionWrapper preserves exception information and stack traces:

291

292

```python

293

import wrapt

294

295

def error_wrapper(wrapped, instance, args, kwargs):

296

try:

297

return wrapped(*args, **kwargs)

298

except Exception as e:

299

print(f"Exception in {wrapped.__name__}: {e}")

300

raise # Re-raise preserving original traceback

301

302

def failing_function():

303

raise ValueError("Something went wrong")

304

305

wrapped_function = wrapt.FunctionWrapper(failing_function, error_wrapper)

306

307

try:

308

wrapped_function()

309

except ValueError as e:

310

# Original traceback is preserved

311

import traceback

312

traceback.print_exc()

313

```