or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

collections.mdcore-utilities.mdextended.mdindex.mdmetaprogramming.mdnetworking.mdparallel.mdsystem-integration.mdtesting.mdxml-html.md

metaprogramming.mddocs/

0

# Metaprogramming Tools

1

2

Advanced metaclasses, function signature manipulation, delegation patterns, and dynamic code generation utilities for building flexible and extensible APIs. The meta module provides powerful tools for creating sophisticated object-oriented designs and dynamic programming patterns.

3

4

## Capabilities

5

6

### Metaclasses for Enhanced Object Creation

7

8

Specialized metaclasses that modify class creation and object initialization behavior.

9

10

```python { .api }

11

class FixSigMeta(type):

12

"""

13

Metaclass that fixes function signatures on classes overriding __new__.

14

15

Automatically sets __signature__ attribute based on __init__ method

16

to ensure proper introspection and IDE support for classes that

17

override __new__ but define parameters in __init__.

18

19

Usage:

20

class MyClass(metaclass=FixSigMeta):

21

def __init__(self, param1, param2): ...

22

def __new__(cls, *args, **kwargs): ...

23

24

# MyClass.__signature__ automatically reflects __init__ signature

25

"""

26

27

def __new__(cls, name, bases, dict): ...

28

29

class PrePostInitMeta(FixSigMeta):

30

"""

31

Metaclass that calls optional __pre_init__ and __post_init__ methods.

32

33

Automatically calls __pre_init__ before __init__ and __post_init__

34

after __init__ if these methods are defined. Provides hooks for

35

setup and cleanup logic around object initialization.

36

37

Execution order:

38

1. __new__ called

39

2. __pre_init__ called (if defined)

40

3. __init__ called

41

4. __post_init__ called (if defined)

42

43

Usage:

44

class MyClass(metaclass=PrePostInitMeta):

45

def __pre_init__(self, *args, **kwargs): ...

46

def __init__(self, *args, **kwargs): ...

47

def __post_init__(self, *args, **kwargs): ...

48

"""

49

50

def __call__(cls, *args, **kwargs): ...

51

52

class AutoInit(metaclass=PrePostInitMeta):

53

"""

54

Base class that automatically calls super().__init__ in __pre_init__.

55

56

Eliminates need for subclasses to remember to call super().__init__()

57

by automatically handling parent class initialization.

58

59

Usage:

60

class Parent(AutoInit):

61

def __init__(self, x): self.x = x

62

63

class Child(Parent):

64

def __init__(self, x, y): # No need to call super().__init__

65

self.y = y

66

"""

67

68

def __pre_init__(self, *args, **kwargs): ...

69

70

class NewChkMeta(FixSigMeta):

71

"""

72

Metaclass to avoid recreating objects passed to constructor.

73

74

If an object of the same class is passed to the constructor

75

with no additional arguments, returns the object unchanged

76

rather than creating a new instance.

77

78

Usage:

79

class MyClass(metaclass=NewChkMeta): pass

80

81

obj1 = MyClass()

82

obj2 = MyClass(obj1) # Returns obj1, not a new instance

83

obj3 = MyClass(obj1, extra_arg=True) # Creates new instance

84

"""

85

86

def __call__(cls, x=None, *args, **kwargs): ...

87

88

class BypassNewMeta(FixSigMeta):

89

"""

90

Metaclass that casts objects to class type instead of creating new instances.

91

92

If object is of _bypass_type, changes its __class__ to the target class

93

rather than creating a new instance. Useful for wrapping existing objects.

94

95

Class can define:

96

- _bypass_type: Type to cast from

97

- _new_meta: Custom creation method

98

99

Usage:

100

class StringWrapper(metaclass=BypassNewMeta):

101

_bypass_type = str

102

def upper_method(self): return self.upper()

103

104

wrapped = StringWrapper("hello") # Casts str to StringWrapper

105

print(wrapped.upper_method()) # "HELLO"

106

"""

107

108

def __call__(cls, x=None, *args, **kwargs): ...

109

```

110

111

### Function Signature Manipulation

112

113

Tools for analyzing, modifying, and working with function signatures dynamically.

114

115

```python { .api }

116

def test_sig(f, expected_signature):

117

"""

118

Test that function has expected signature string.

119

120

Utility for testing that function signatures match expected format,

121

useful for validating decorator effects and signature modifications.

122

123

Parameters:

124

- f: function to test

125

- expected_signature: str, expected signature format

126

127

Raises:

128

AssertionError: If signatures don't match

129

"""

130

131

def empty2none(param):

132

"""

133

Convert Parameter.empty to None for cleaner handling.

134

135

Helper function to normalize inspect.Parameter.empty values

136

to None for easier processing and comparison.

137

138

Parameters:

139

- param: inspect.Parameter value

140

141

Returns:

142

None if param is Parameter.empty, otherwise param unchanged

143

"""

144

145

def anno_dict(func):

146

"""

147

Get function annotations as dict with empty values normalized.

148

149

Extracts __annotations__ from function and converts Parameter.empty

150

values to None for cleaner processing.

151

152

Parameters:

153

- func: function to get annotations from

154

155

Returns:

156

dict: Annotations with empty values normalized to None

157

"""

158

159

def use_kwargs_dict(keep=False, **kwargs):

160

"""

161

Decorator to replace **kwargs in signature with specific parameters.

162

163

Modifies function signature to show specific keyword parameters

164

instead of generic **kwargs, improving IDE support and documentation.

165

166

Parameters:

167

- keep: bool, whether to keep **kwargs in addition to named params

168

- **kwargs: parameter names and defaults to add to signature

169

170

Returns:

171

Decorator function

172

173

Usage:

174

@use_kwargs_dict(param1=None, param2="default")

175

def func(**kwargs): ...

176

# Signature shows func(*, param1=None, param2='default')

177

"""

178

179

def use_kwargs(names, keep=False):

180

"""

181

Decorator to replace **kwargs with list of parameter names.

182

183

Similar to use_kwargs_dict but takes parameter names as list

184

without default values.

185

186

Parameters:

187

- names: list of str, parameter names to add

188

- keep: bool, whether to keep **kwargs

189

190

Returns:

191

Decorator function

192

"""

193

194

def delegates(to=None, keep=False, but=None):

195

"""

196

Decorator to delegate function parameters from another function.

197

198

Copies parameters from target function to decorated function,

199

enabling parameter forwarding while maintaining proper signatures

200

for IDE support and introspection.

201

202

Parameters:

203

- to: callable, function to delegate parameters from

204

- keep: bool, keep original parameters in addition to delegated ones

205

- but: str|list, parameter names to exclude from delegation

206

207

Returns:

208

Decorator that modifies function signature

209

210

Usage:

211

def target_func(a, b=1, c=2): pass

212

213

@delegates(target_func)

214

def wrapper(**kwargs):

215

return target_func(**kwargs)

216

# wrapper now has signature: wrapper(a, b=1, c=2)

217

"""

218

219

def method(func):

220

"""

221

Convert function to method by adding self parameter.

222

223

Modifies function signature to include 'self' as first parameter,

224

useful for dynamically creating methods.

225

226

Parameters:

227

- func: function to convert to method

228

229

Returns:

230

Function with method signature (including self)

231

"""

232

233

def funcs_kwargs(as_method=False):

234

"""

235

Get kwargs specification for function parameters.

236

237

Analyzes function parameters and returns specification

238

for **kwargs handling and parameter forwarding.

239

240

Parameters:

241

- as_method: bool, treat as method (skip self parameter)

242

243

Returns:

244

dict: Parameter specification for kwargs processing

245

"""

246

```

247

248

### Dynamic Code Generation

249

250

Utilities for generating and modifying code at runtime.

251

252

```python { .api }

253

def _mk_param(name, default=None):

254

"""

255

Create inspect.Parameter for signature construction.

256

257

Helper function to create Parameter objects for building

258

function signatures programmatically.

259

260

Parameters:

261

- name: str, parameter name

262

- default: default value for parameter

263

264

Returns:

265

inspect.Parameter: Configured parameter object

266

"""

267

268

def _rm_self(signature):

269

"""

270

Remove 'self' parameter from function signature.

271

272

Utility to convert method signatures to function signatures

273

by removing the self parameter.

274

275

Parameters:

276

- signature: inspect.Signature with self parameter

277

278

Returns:

279

inspect.Signature: New signature without self parameter

280

"""

281

```

282

283

## Usage Examples

284

285

### Creating Enhanced Classes with Metaclasses

286

287

```python

288

from fastcore.meta import FixSigMeta, PrePostInitMeta, AutoInit

289

290

# Class with proper signature introspection

291

class DataContainer(metaclass=FixSigMeta):

292

def __init__(self, data, transform=None, validate=True):

293

self.data = data

294

self.transform = transform

295

self.validate = validate

296

297

def __new__(cls, *args, **kwargs):

298

# Custom object creation logic

299

instance = super().__new__(cls)

300

return instance

301

302

# Signature is properly reflected for IDE support

303

import inspect

304

print(inspect.signature(DataContainer))

305

# <Signature (data, transform=None, validate=True)>

306

307

# Class with initialization hooks

308

class ConfigurableClass(metaclass=PrePostInitMeta):

309

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

310

print("Setting up configuration...")

311

self.setup_config()

312

313

def __init__(self, name, value):

314

self.name = name

315

self.value = value

316

317

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

318

print("Finalizing setup...")

319

self.validate()

320

321

def setup_config(self):

322

self.config = {"initialized": True}

323

324

def validate(self):

325

assert hasattr(self, 'name')

326

assert hasattr(self, 'value')

327

328

obj = ConfigurableClass("test", 42)

329

# Prints: Setting up configuration...

330

# Prints: Finalizing setup...

331

332

# Simplified inheritance with AutoInit

333

class BaseProcessor(AutoInit):

334

def __init__(self, name):

335

self.name = name

336

self.processed_count = 0

337

338

class AdvancedProcessor(BaseProcessor):

339

def __init__(self, name, algorithm):

340

# No need to call super().__init__() - handled automatically

341

self.algorithm = algorithm

342

self.features = []

343

344

processor = AdvancedProcessor("nlp", "transformer")

345

print(processor.name) # "nlp" - inherited properly

346

```

347

348

### Object Identity and Casting Patterns

349

350

```python

351

from fastcore.meta import NewChkMeta, BypassNewMeta

352

353

# Avoid unnecessary object recreation

354

class SmartContainer(metaclass=NewChkMeta):

355

def __init__(self, data=None):

356

self.data = data or []

357

358

def add(self, item):

359

self.data.append(item)

360

361

container1 = SmartContainer([1, 2, 3])

362

container2 = SmartContainer(container1) # Returns container1, not new instance

363

print(container1 is container2) # True

364

365

container3 = SmartContainer(container1, extra_data=True) # Creates new instance

366

print(container1 is container3) # False

367

368

# Type casting with BypassNewMeta

369

class EnhancedString(str, metaclass=BypassNewMeta):

370

_bypass_type = str

371

372

def word_count(self):

373

return len(self.split())

374

375

def title_case(self):

376

return ' '.join(word.capitalize() for word in self.split())

377

378

# Cast existing string to EnhancedString

379

text = "hello world python programming"

380

enhanced = EnhancedString(text) # Casts str to EnhancedString

381

print(enhanced.word_count()) # 4

382

print(enhanced.title_case()) # "Hello World Python Programming"

383

print(type(enhanced)) # <class 'EnhancedString'>

384

385

# Works with string methods too

386

print(enhanced.upper()) # "HELLO WORLD PYTHON PROGRAMMING"

387

```

388

389

### Function Signature Delegation and Enhancement

390

391

```python

392

from fastcore.meta import delegates, use_kwargs_dict, use_kwargs

393

import matplotlib.pyplot as plt

394

395

# Delegate parameters from matplotlib.pyplot.plot

396

@delegates(plt.plot)

397

def enhanced_plot(*args, title="Enhanced Plot", save_path=None, **kwargs):

398

"""Enhanced plotting with automatic title and save functionality."""

399

fig, ax = plt.subplots()

400

ax.plot(*args, **kwargs)

401

ax.set_title(title)

402

403

if save_path:

404

plt.savefig(save_path)

405

406

return fig, ax

407

408

# Function now has full plt.plot signature plus enhancements

409

import inspect

410

sig = inspect.signature(enhanced_plot)

411

print(sig) # Shows all matplotlib plot parameters

412

413

# Use the enhanced function

414

fig, ax = enhanced_plot([1, 2, 3], [1, 4, 9],

415

linestyle='--', color='red',

416

title="My Data", save_path="plot.png")

417

418

# Replace **kwargs with specific parameters for better IDE support

419

@use_kwargs_dict(method='GET', timeout=30, headers=None)

420

def api_request(url, **kwargs):

421

"""Make API request with specified parameters."""

422

import requests

423

return requests.request(**kwargs, url=url)

424

425

# Signature now shows: api_request(url, *, method='GET', timeout=30, headers=None)

426

427

# Replace **kwargs with parameter names from list

428

@use_kwargs(['host', 'port', 'database', 'username', 'password'])

429

def connect_db(**kwargs):

430

"""Connect to database with specified parameters."""

431

# Connection logic here

432

return f"Connected with {kwargs}"

433

434

# Signature shows all database connection parameters

435

```

436

437

### Advanced Delegation Patterns

438

439

```python

440

from fastcore.meta import delegates, method

441

from functools import partial

442

443

# Complex delegation with exclusions

444

class DataFrameWrapper:

445

def __init__(self, df):

446

self.df = df

447

448

@delegates(lambda self: self.df.groupby, but=['by'])

449

def smart_groupby(self, columns, **kwargs):

450

"""Enhanced groupby with automatic column handling."""

451

if isinstance(columns, str):

452

columns = [columns]

453

return self.df.groupby(columns, **kwargs)

454

455

# Delegate from multiple sources

456

class MLPipeline:

457

@delegates(lambda: sklearn.model_selection.train_test_split,

458

but=['arrays'])

459

@delegates(lambda: sklearn.preprocessing.StandardScaler,

460

keep=True)

461

def prepare_data(self, X, y, scale=True, **kwargs):

462

"""Prepare data with splitting and optional scaling."""

463

from sklearn.model_selection import train_test_split

464

from sklearn.preprocessing import StandardScaler

465

466

# Split data

467

split_kwargs = {k: v for k, v in kwargs.items()

468

if k in ['test_size', 'random_state', 'stratify']}

469

X_train, X_test, y_train, y_test = train_test_split(X, y, **split_kwargs)

470

471

# Optional scaling

472

if scale:

473

scaler_kwargs = {k: v for k, v in kwargs.items()

474

if k in ['copy', 'with_mean', 'with_std']}

475

scaler = StandardScaler(**scaler_kwargs)

476

X_train = scaler.fit_transform(X_train)

477

X_test = scaler.transform(X_test)

478

479

return X_train, X_test, y_train, y_test

480

481

# Dynamic method creation

482

def create_property_method(attr_name):

483

@method

484

def get_property(self):

485

return getattr(self, f"_{attr_name}")

486

487

return get_property

488

489

class DynamicClass:

490

def __init__(self, **kwargs):

491

for key, value in kwargs.items():

492

setattr(self, f"_{key}", value)

493

# Create getter method dynamically

494

setattr(self, f"get_{key}",

495

create_property_method(key).__get__(self, type(self)))

496

497

obj = DynamicClass(name="test", value=42, items=[1, 2, 3])

498

print(obj.get_name()) # "test"

499

print(obj.get_value()) # 42

500

print(obj.get_items()) # [1, 2, 3]

501

```

502

503

### Signature Analysis and Testing

504

505

```python

506

from fastcore.meta import test_sig, anno_dict, empty2none

507

import inspect

508

509

# Test function signatures

510

def sample_function(a: int, b: str = "default", c=None) -> bool:

511

return True

512

513

# Verify signature matches expected format

514

test_sig(sample_function, "(a: int, b: str = 'default', c=None) -> bool")

515

516

# Extract and process annotations

517

annotations = anno_dict(sample_function)

518

print(annotations) # {'a': <class 'int'>, 'b': <class 'str'>, 'c': None, 'return': <class 'bool'>}

519

520

# Signature modification validation

521

def modify_signature(func, new_params):

522

"""Modify function signature and validate result."""

523

sig = inspect.signature(func)

524

params = list(sig.parameters.values())

525

526

# Add new parameters

527

for name, default in new_params.items():

528

param = inspect.Parameter(

529

name,

530

inspect.Parameter.KEYWORD_ONLY,

531

default=default

532

)

533

params.append(param)

534

535

# Create new signature

536

new_sig = sig.replace(parameters=params)

537

func.__signature__ = new_sig

538

return func

539

540

# Test the modification

541

@modify_signature

542

def test_func(x, y=1):

543

pass

544

545

modified = modify_signature(test_func, {'extra1': 'default', 'extra2': None})

546

test_sig(modified, "(x, y=1, *, extra1='default', extra2=None)")

547

548

# Parameter processing

549

def process_parameters(func):

550

"""Analyze function parameters and provide summary."""

551

sig = inspect.signature(func)

552

summary = {

553

'total_params': len(sig.parameters),

554

'required_params': [],

555

'optional_params': [],

556

'annotations': anno_dict(func)

557

}

558

559

for name, param in sig.parameters.items():

560

if param.default is inspect.Parameter.empty:

561

summary['required_params'].append(name)

562

else:

563

summary['optional_params'].append((name, empty2none(param.default)))

564

565

return summary

566

567

def example_func(a, b: int, c="default", d: str = None):

568

pass

569

570

summary = process_parameters(example_func)

571

print(f"Required: {summary['required_params']}") # ['a', 'b']

572

print(f"Optional: {summary['optional_params']}") # [('c', 'default'), ('d', None)]

573

print(f"Annotations: {summary['annotations']}") # {'b': int, 'd': str}

574

```

575

576

### Building Flexible APIs

577

578

```python

579

from fastcore.meta import delegates, use_kwargs_dict, PrePostInitMeta

580

581

# Flexible configuration system

582

class ConfigurableAPI(metaclass=PrePostInitMeta):

583

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

584

self.config = {}

585

self.validators = []

586

587

def __init__(self, base_url, **config):

588

self.base_url = base_url

589

self.config.update(config)

590

591

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

592

self.validate_config()

593

594

def validate_config(self):

595

for validator in self.validators:

596

validator(self.config)

597

598

def add_validator(self, validator_func):

599

self.validators.append(validator_func)

600

601

# API client with delegated requests parameters

602

import requests

603

604

class APIClient(ConfigurableAPI):

605

@delegates(requests.get, but=['url'])

606

def get(self, endpoint, **kwargs):

607

url = f"{self.base_url}/{endpoint.lstrip('/')}"

608

return requests.get(url, **kwargs)

609

610

@delegates(requests.post, but=['url'])

611

def post(self, endpoint, **kwargs):

612

url = f"{self.base_url}/{endpoint.lstrip('/')}"

613

return requests.post(url, **kwargs)

614

615

# Plugin system with dynamic method delegation

616

class PluginSystem:

617

def __init__(self):

618

self.plugins = {}

619

620

def register_plugin(self, name, plugin_class):

621

self.plugins[name] = plugin_class

622

623

# Delegate plugin methods to main system

624

for method_name in dir(plugin_class):

625

if not method_name.startswith('_'):

626

method = getattr(plugin_class, method_name)

627

if callable(method):

628

self.add_delegated_method(name, method_name, method)

629

630

@delegates(lambda: None) # Will be updated dynamically

631

def add_delegated_method(self, plugin_name, method_name, method):

632

def wrapper(*args, **kwargs):

633

plugin_instance = self.plugins[plugin_name]()

634

return getattr(plugin_instance, method_name)(*args, **kwargs)

635

636

# Add method with proper delegation

637

wrapper = delegates(method)(wrapper)

638

setattr(self, f"{plugin_name}_{method_name}", wrapper)

639

640

# Usage example

641

class DatabasePlugin:

642

def connect(self, host='localhost', port=5432, database='mydb'):

643

return f"Connected to {host}:{port}/{database}"

644

645

def query(self, sql, params=None, timeout=30):

646

return f"Executed: {sql}"

647

648

system = PluginSystem()

649

system.register_plugin('db', DatabasePlugin)

650

651

# Now system has db_connect and db_query methods with proper signatures

652

result = system.db_connect(host='production.db', port=5433)

653

query_result = system.db_query("SELECT * FROM users", timeout=60)

654

```