or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

dotnet.mdexpression-factory.mdgo.mdindex.mdjava.mdjavascript-typescript.mdparameter-types.mdpython.mdruby.md

python.mddocs/

0

# Python Implementation

1

2

Pythonic implementation of Cucumber Expressions with clean APIs, built-in parameter types, and full feature compatibility for Python BDD testing frameworks.

3

4

## Package Information

5

6

- **Package Name**: `cucumber-expressions`

7

- **Language**: Python 3.7+

8

- **Installation**: `pip install cucumber-expressions`

9

- **Module**: `cucumber_expressions`

10

11

## Core Imports

12

13

```python

14

from cucumber_expressions.expression import CucumberExpression

15

from cucumber_expressions.parameter_type import ParameterType

16

from cucumber_expressions.parameter_type_registry import ParameterTypeRegistry

17

from cucumber_expressions.expression_generator import CucumberExpressionGenerator

18

from cucumber_expressions.argument import Argument

19

```

20

21

## Capabilities

22

23

### Expression Creation and Matching

24

25

Create and match Cucumber expressions with Pythonic API patterns and type conversion.

26

27

```python { .api }

28

class CucumberExpression:

29

"""Main class for parsing and matching Cucumber expressions"""

30

31

def __init__(self, expression: str, parameter_type_registry: ParameterTypeRegistry):

32

"""

33

Create a new Cucumber expression

34

35

Args:

36

expression: The Cucumber expression string

37

parameter_type_registry: Registry containing parameter types

38

"""

39

...

40

41

def match(self, text: str) -> Optional[List[Argument]]:

42

"""

43

Match text against this expression and extract arguments

44

45

Args:

46

text: Text to match against the expression

47

48

Returns:

49

List of matched arguments or None if no match

50

"""

51

...

52

53

@property

54

def source(self) -> str:

55

"""The original expression string"""

56

...

57

58

@property

59

def regexp(self) -> str:

60

"""The compiled regular expression pattern"""

61

...

62

```

63

64

**Usage Examples:**

65

66

```python

67

from cucumber_expressions.expression import CucumberExpression

68

from cucumber_expressions.parameter_type_registry import ParameterTypeRegistry

69

70

# Create registry with built-in parameter types

71

registry = ParameterTypeRegistry()

72

73

# Simple integer parameter

74

expr1 = CucumberExpression('I have {int} cucumbers', registry)

75

args1 = expr1.match('I have 42 cucumbers')

76

if args1:

77

print(args1[0].value) # 42 (int)

78

79

# Multiple parameters

80

expr2 = CucumberExpression('I have {int} {word} and {float} {word}', registry)

81

args2 = expr2.match('I have 42 cucumbers and 3.5 apples')

82

if args2:

83

print(args2[0].value) # 42 (int)

84

print(args2[1].value) # "cucumbers" (str)

85

print(args2[2].value) # 3.5 (float)

86

print(args2[3].value) # "apples" (str)

87

88

# Optional text

89

expr3 = CucumberExpression('I have {int} cucumber(s)', registry)

90

print(expr3.match('I have 1 cucumber')) # matches

91

print(expr3.match('I have 5 cucumbers')) # matches

92

93

# Alternative text

94

expr4 = CucumberExpression('I put it in my belly/stomach', registry)

95

print(expr4.match('I put it in my belly')) # matches

96

print(expr4.match('I put it in my stomach')) # matches

97

98

# String parameters (removes quotes)

99

expr5 = CucumberExpression('I say {string}', registry)

100

args5 = expr5.match('I say "hello world"')

101

if args5:

102

print(args5[0].value) # "hello world" (without quotes)

103

```

104

105

### Parameter Type Definition

106

107

Define custom parameter types with regex patterns and transformation functions using Pythonic patterns.

108

109

```python { .api }

110

class ParameterType:

111

"""Defines a parameter type with name, patterns, and transformer"""

112

113

def __init__(

114

self,

115

name: Optional[str],

116

regexp: Union[List[str], str, List[Pattern], Pattern],

117

type: type,

118

transformer: Optional[Callable] = None,

119

use_for_snippets: bool = True,

120

prefer_for_regexp_match: bool = False

121

):

122

"""

123

Create a new parameter type

124

125

Args:

126

name: Name used in expressions (e.g., 'color' for {color})

127

regexp: Regular expression patterns to match

128

type: Python type for the result

129

transformer: Function to transform matched strings to result type

130

use_for_snippets: Whether to use this type for snippet generation

131

prefer_for_regexp_match: Whether to prefer this type in regexp matches

132

"""

133

...

134

135

@staticmethod

136

def compare(pt1: 'ParameterType', pt2: 'ParameterType') -> int:

137

"""Compare two parameter types for sorting"""

138

...

139

140

@staticmethod

141

def _is_valid_parameter_type_name(type_name: str) -> bool:

142

"""Test if a parameter type name is valid"""

143

...

144

145

def transform(self, group_values: List[str]) -> Any:

146

"""

147

Transform matched groups to the target type

148

149

Args:

150

group_values: Matched string groups

151

152

Returns:

153

Transformed value

154

"""

155

...

156

157

@property

158

def prefer_for_regexp_match(self) -> bool:

159

"""Whether to prefer for regexp matching"""

160

...

161

162

@property

163

def use_for_snippets(self) -> bool:

164

"""Whether to use for snippet generation"""

165

...

166

167

# Public attributes

168

name: Optional[str] # Parameter type name

169

type: type # Python type

170

regexps: List[str] # Regular expression patterns

171

```

172

173

**Usage Examples:**

174

175

```python

176

from cucumber_expressions.parameter_type import ParameterType

177

from cucumber_expressions.parameter_type_registry import ParameterTypeRegistry

178

from cucumber_expressions.expression import CucumberExpression

179

import re

180

from enum import Enum

181

from dataclasses import dataclass

182

from decimal import Decimal

183

from datetime import datetime

184

185

# Simple enum-like parameter type

186

class Color(Enum):

187

RED = "red"

188

GREEN = "green"

189

BLUE = "blue"

190

YELLOW = "yellow"

191

192

color_type = ParameterType(

193

name="color",

194

regexp=r"red|green|blue|yellow",

195

type=Color,

196

transformer=lambda s: Color(s.lower())

197

)

198

199

# Custom object parameter type

200

@dataclass

201

class Point:

202

x: int

203

y: int

204

205

point_type = ParameterType(

206

name="point",

207

regexp=r"\((-?\d+),\s*(-?\d+)\)",

208

type=Point,

209

transformer=lambda x, y: Point(int(x), int(y))

210

)

211

212

# Date parameter type with multiple capture groups

213

date_type = ParameterType(

214

name="date",

215

regexp=r"(\d{4})-(\d{2})-(\d{2})",

216

type=datetime,

217

transformer=lambda year, month, day: datetime(int(year), int(month), int(day))

218

)

219

220

# High-precision decimal type

221

decimal_type = ParameterType(

222

name="precise_decimal",

223

regexp=r"\d+\.\d{4,}",

224

type=Decimal,

225

transformer=lambda s: Decimal(s)

226

)

227

228

# Email with validation

229

def validate_email(email: str) -> str:

230

if '@' not in email:

231

raise ValueError(f"Invalid email: {email}")

232

return email.lower()

233

234

email_type = ParameterType(

235

name="email",

236

regexp=r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}",

237

type=str,

238

transformer=validate_email

239

)

240

241

# Register and use custom types

242

registry = ParameterTypeRegistry()

243

registry.define_parameter_type(color_type)

244

registry.define_parameter_type(point_type)

245

registry.define_parameter_type(date_type)

246

registry.define_parameter_type(decimal_type)

247

registry.define_parameter_type(email_type)

248

249

# Use in expressions

250

color_expr = CucumberExpression("I have a {color} car", registry)

251

result = color_expr.match("I have a red car")

252

print(result[0].value) # Color.RED

253

254

point_expr = CucumberExpression("Move to {point}", registry)

255

result = point_expr.match("Move to (10, -5)")

256

print(result[0].value) # Point(x=10, y=-5)

257

258

date_expr = CucumberExpression("Meeting on {date}", registry)

259

result = date_expr.match("Meeting on 2023-12-25")

260

print(result[0].value) # datetime(2023, 12, 25, 0, 0)

261

262

decimal_expr = CucumberExpression("Price is {precise_decimal}", registry)

263

result = decimal_expr.match("Price is 123.4567")

264

print(result[0].value) # Decimal('123.4567')

265

266

email_expr = CucumberExpression("Send to {email}", registry)

267

result = email_expr.match("Send to User@Example.COM")

268

print(result[0].value) # "user@example.com"

269

```

270

271

### Parameter Type Registry

272

273

Central registry for managing parameter types with Pythonic property access and method naming.

274

275

```python { .api }

276

class ParameterTypeRegistry:

277

"""Registry for managing parameter types"""

278

279

def __init__(self):

280

"""Create a new parameter type registry with built-in types"""

281

...

282

283

@property

284

def parameter_types(self) -> List[ParameterType]:

285

"""List of all registered parameter types"""

286

...

287

288

def lookup_by_type_name(self, name: str) -> Optional[ParameterType]:

289

"""

290

Look up a parameter type by name

291

292

Args:

293

name: Name of the parameter type

294

295

Returns:

296

Parameter type or None if not found

297

"""

298

...

299

300

def lookup_by_regexp(

301

self,

302

parameter_type_regexp: str,

303

expression_regexp,

304

text: str

305

) -> Optional[ParameterType]:

306

"""

307

Look up parameter type by regular expression pattern

308

309

Args:

310

parameter_type_regexp: Regular expression to match

311

expression_regexp: Full expression regexp

312

text: Text being matched

313

314

Returns:

315

Matching parameter type or None

316

"""

317

...

318

319

def define_parameter_type(self, parameter_type: ParameterType) -> None:

320

"""

321

Register a new parameter type

322

323

Args:

324

parameter_type: Parameter type to register

325

"""

326

...

327

```

328

329

**Usage Examples:**

330

331

```python

332

from cucumber_expressions.parameter_type_registry import ParameterTypeRegistry

333

from cucumber_expressions.parameter_type import ParameterType

334

335

registry = ParameterTypeRegistry()

336

337

# Check built-in types

338

int_type = registry.lookup_by_type_name('int')

339

print(int_type.name) # "int"

340

print(int_type.type) # <class 'int'>

341

342

# List all parameter types

343

for param_type in registry.parameter_types:

344

print(f"{param_type.name}: {param_type.regexps}")

345

346

# Define and retrieve custom type

347

custom_type = ParameterType('uuid', r'[0-9a-f-]{36}', str)

348

registry.define_parameter_type(custom_type)

349

350

retrieved = registry.lookup_by_type_name('uuid')

351

print(retrieved is custom_type) # True

352

```

353

354

### Expression Generation

355

356

Generate Cucumber expressions from example text with Pythonic API patterns.

357

358

```python { .api }

359

class CucumberExpressionGenerator:

360

"""Generates Cucumber expressions from example text"""

361

362

def __init__(self, parameter_type_registry: ParameterTypeRegistry):

363

"""

364

Create generator with parameter type registry

365

366

Args:

367

parameter_type_registry: Registry containing available parameter types

368

"""

369

...

370

371

def generate_expressions(self, text: str) -> List[GeneratedExpression]:

372

"""

373

Generate expressions from example text

374

375

Args:

376

text: Example text to generate expressions from

377

378

Returns:

379

List of generated expressions ranked by specificity

380

"""

381

...

382

383

@staticmethod

384

def escape(string: str) -> str:

385

"""

386

Escape special regex characters in string

387

388

Args:

389

string: String to escape

390

391

Returns:

392

Escaped string safe for regex

393

"""

394

...

395

396

class GeneratedExpression:

397

"""Generated expression with metadata"""

398

399

def __init__(self, expression_template: str, parameter_types: List[ParameterType]):

400

"""

401

Create generated expression

402

403

Args:

404

expression_template: Template string with parameter placeholders

405

parameter_types: List of parameter types in order

406

"""

407

self.source = expression_template

408

self.parameter_types = parameter_types

409

self.parameter_names = [pt.name for pt in parameter_types if pt.name]

410

411

@property

412

def source(self) -> str:

413

"""The generated expression source"""

414

...

415

416

@property

417

def parameter_names(self) -> List[str]:

418

"""Parameter names for code generation"""

419

...

420

421

@property

422

def parameter_types(self) -> List[ParameterType]:

423

"""Parameter types in declaration order"""

424

...

425

```

426

427

**Usage Examples:**

428

429

```python

430

from cucumber_expressions.expression_generator import CucumberExpressionGenerator

431

from cucumber_expressions.parameter_type_registry import ParameterTypeRegistry

432

433

registry = ParameterTypeRegistry()

434

generator = CucumberExpressionGenerator(registry)

435

436

# Generate from text with numbers

437

expressions = generator.generate_expressions('I have 42 cucumbers')

438

print(expressions[0].source) # "I have {int} cucumbers"

439

print(expressions[0].parameter_names) # ["int"]

440

441

# Generate from text with floats

442

expressions = generator.generate_expressions('Price is 29.99 dollars')

443

print(expressions[0].source) # "Price is {float} dollars"

444

445

# Generate from text with strings

446

expressions = generator.generate_expressions('I select "Premium Plan" option')

447

print(expressions[0].source) # "I select {string} option"

448

449

# Generate from complex text

450

expressions = generator.generate_expressions('User alice with age 25 has balance 123.45')

451

for i, expr in enumerate(expressions):

452

print(f"Option {i+1}: {expr.source}")

453

print(f"Parameters: {[pt.name for pt in expr.parameter_types]}")

454

455

# Generate method signatures for step definitions

456

expressions = generator.generate_expressions('Order contains 5 items')

457

best = expressions[0]

458

print(f"@given('{best.source}')")

459

param_list = ', '.join(f"{name}: {get_python_type(pt)}"

460

for name, pt in zip(best.parameter_names, best.parameter_types))

461

print(f"def order_contains_items({param_list}):")

462

print(" pass")

463

464

def get_python_type(param_type: ParameterType) -> str:

465

"""Get Python type hint for parameter type"""

466

if param_type.type == int:

467

return "int"

468

elif param_type.type == float:

469

return "float"

470

elif param_type.type == str:

471

return "str"

472

else:

473

return "Any"

474

```

475

476

### Argument Extraction

477

478

Extract and access matched arguments with Pythonic property access.

479

480

```python { .api }

481

class Argument:

482

"""Represents a matched argument from an expression"""

483

484

def __init__(self, group: Group, parameter_type: ParameterType):

485

"""

486

Create argument with group and parameter type

487

488

Args:

489

group: Matched group from regex

490

parameter_type: Parameter type for transformation

491

"""

492

...

493

494

@staticmethod

495

def build(

496

tree_regexp: TreeRegexp,

497

text: str,

498

parameter_types: List[ParameterType]

499

) -> Optional[List[Argument]]:

500

"""

501

Build arguments from matched groups

502

503

Args:

504

tree_regexp: Tree regular expression matcher

505

text: Text that was matched

506

parameter_types: Parameter types for transformation

507

508

Returns:

509

List of arguments or None if no match

510

"""

511

...

512

513

@property

514

def value(self) -> Any:

515

"""The transformed value"""

516

...

517

518

@property

519

def group(self) -> Group:

520

"""The matched group"""

521

...

522

523

# Public attribute

524

parameter_type: ParameterType # The parameter type used for transformation

525

526

class Group:

527

"""Represents a matched group from regex matching"""

528

529

def __init__(self, value: Optional[str], start: int, end: int, children: List['Group']):

530

self.value = value

531

self.start = start

532

self.end = end

533

self.children = children

534

535

@property

536

def value(self) -> Optional[str]:

537

"""Matched text value"""

538

...

539

540

@property

541

def start(self) -> int:

542

"""Start position in source text"""

543

...

544

545

@property

546

def end(self) -> int:

547

"""End position in source text"""

548

...

549

550

@property

551

def children(self) -> List['Group']:

552

"""Child groups"""

553

...

554

```

555

556

**Usage Examples:**

557

558

```python

559

from cucumber_expressions.expression import CucumberExpression

560

from cucumber_expressions.parameter_type_registry import ParameterTypeRegistry

561

562

registry = ParameterTypeRegistry()

563

expr = CucumberExpression('User {word} has {int} items', registry)

564

565

args = expr.match('User alice has 42 items')

566

if args:

567

# Access values directly

568

username = args[0].value # "alice" (str)

569

item_count = args[1].value # 42 (int)

570

571

# Access parameter types

572

print(args[0].parameter_type.name) # "word"

573

print(args[1].parameter_type.name) # "int"

574

575

# Access groups

576

print(args[0].group.value) # "alice"

577

print(args[0].group.start) # position in original text

578

print(args[0].group.end) # end position in original text

579

```

580

581

### Built-in Parameter Types

582

583

Python implementation includes comprehensive built-in parameter types with appropriate Python type conversion.

584

585

```python { .api }

586

# Built-in parameter types in Python implementation:

587

588

{int} # Converts to Python int

589

{float} # Converts to Python float

590

{word} # Returns as Python str (single word)

591

{string} # Returns as Python str (removes quotes)

592

{bigdecimal} # Converts to decimal.Decimal for high precision

593

{double} # Converts to Python float (64-bit)

594

{biginteger} # Converts to Python int (arbitrary precision)

595

{byte} # Converts to Python int (8-bit range)

596

{short} # Converts to Python int (16-bit range)

597

{long} # Converts to Python int (64-bit range)

598

{} # Anonymous - returns as str

599

```

600

601

**Usage Examples:**

602

603

```python

604

from cucumber_expressions.expression import CucumberExpression

605

from cucumber_expressions.parameter_type_registry import ParameterTypeRegistry

606

from decimal import Decimal

607

608

registry = ParameterTypeRegistry()

609

610

# Test all built-in types

611

expr = CucumberExpression(

612

'Values: {int} {float} {word} {string} {bigdecimal}',

613

registry

614

)

615

616

args = expr.match('Values: 42 3.14 hello "world test" 123.456789')

617

if args:

618

print(f"int: {args[0].value} ({type(args[0].value)})") # 42 (<class 'int'>)

619

print(f"float: {args[1].value} ({type(args[1].value)})") # 3.14 (<class 'float'>)

620

print(f"word: {args[2].value} ({type(args[2].value)})") # hello (<class 'str'>)

621

print(f"string: {args[3].value} ({type(args[3].value)})") # world test (<class 'str'>)

622

print(f"bigdecimal: {args[4].value} ({type(args[4].value)})") # 123.456789 (<class 'decimal.Decimal'>)

623

```

624

625

### Error Handling

626

627

Pythonic error handling with appropriate exception types.

628

629

```python { .api }

630

# Exception handling in parameter transformation

631

def safe_transformer(value: str) -> int:

632

try:

633

return int(value)

634

except ValueError as e:

635

raise ValueError(f"Cannot convert '{value}' to integer") from e

636

637

safe_int_type = ParameterType(

638

name="safe_int",

639

regexp=r"\d+",

640

type=int,

641

transformer=safe_transformer

642

)

643

644

# Usage with try/catch

645

try:

646

registry.define_parameter_type(safe_int_type)

647

expr = CucumberExpression('Count: {safe_int}', registry)

648

args = expr.match('Count: invalid') # Will raise during transformation

649

except ValueError as e:

650

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

651

```

652

653

### Integration with Python Testing Frameworks

654

655

Common patterns for integrating with pytest, behave, and other Python BDD frameworks.

656

657

```python

658

# pytest-bdd integration example

659

from pytest_bdd import given, when, then, parsers

660

from cucumber_expressions.expression import CucumberExpression

661

from cucumber_expressions.parameter_type_registry import ParameterTypeRegistry

662

663

# Set up registry once

664

registry = ParameterTypeRegistry()

665

666

# Custom parameter types for domain objects

667

user_type = ParameterType('user', r'user_(\w+)', dict, lambda name: {'username': name})

668

registry.define_parameter_type(user_type)

669

670

# Use in step definitions

671

@given(parsers.cucumber('I have {int} items in my cart'))

672

def have_items_in_cart(int_value):

673

# int_value is already converted to Python int

674

assert isinstance(int_value, int)

675

676

@when(parsers.cucumber('I add {string} to my cart'))

677

def add_item_to_cart(string_value):

678

# string_value has quotes removed

679

assert isinstance(string_value, str)

680

681

# behave integration example

682

from behave import given, when, then

683

from cucumber_expressions.expression import CucumberExpression

684

from cucumber_expressions.parameter_type_registry import ParameterTypeRegistry

685

686

# In environment.py or step files

687

def setup_parameter_types(context):

688

context.registry = ParameterTypeRegistry()

689

# Add custom types...

690

691

@given('User {word} has {int} credits')

692

def user_has_credits(context, username, credit_count):

693

# Parameters automatically converted based on built-in types

694

context.users[username] = {'credits': credit_count}

695

```

696

697

The Python implementation provides idiomatic Python APIs while maintaining full compatibility with the Cucumber Expressions specification, enabling seamless integration with Python BDD testing frameworks and tools.