@@ -38,7 +38,7 @@ def given_beautiful_article(article):
38
38
39
39
import warnings
40
40
from contextlib import suppress
41
- from typing import Any , Callable , Iterable , Iterator , Sequence
41
+ from typing import Any , Callable , Iterable , Iterator , Sequence , cast
42
42
from warnings import warn
43
43
44
44
import pytest
@@ -49,7 +49,7 @@ def given_beautiful_article(article):
49
49
from pytest_bdd .model import Feature , Scenario , Step
50
50
from pytest_bdd .parsers import StepParser , get_parser
51
51
from pytest_bdd .typing .pytest import Config , Parser , TypeAlias
52
- from pytest_bdd .utils import get_caller_module_locals
52
+ from pytest_bdd .utils import deepattrgetter , get_caller_module_locals , setdefaultattr
53
53
from pytest_bdd .warning_types import PytestBDDStepDefinitionWarning
54
54
55
55
@@ -302,16 +302,16 @@ def find_step_definition_matches(
302
302
with suppress (AttributeError ):
303
303
yield from StepHandler .Matcher .find_step_definition_matches (registry .parent , matchers )
304
304
305
- @attrs (auto_attribs = True )
305
+ @attrs (auto_attribs = True , eq = False )
306
306
class Definition :
307
307
func : Callable
308
308
type_ : str | None
309
309
parser : StepParser
310
- converters : dict [str , Any ]
311
- params_fixtures_mapping : dict [str , str ]
310
+ converters : dict [str , Callable ]
311
+ params_fixtures_mapping : set [ str ] | dict [str , str ] | Any
312
312
param_defaults : dict
313
313
target_fixtures : list [str ]
314
- liberal : bool
314
+ liberal : Any | None
315
315
316
316
def get_parameters (self , step : Step ):
317
317
parsed_arguments = self .parser .parse_arguments (step .name ) or {}
@@ -322,43 +322,64 @@ def get_parameters(self, step: Step):
322
322
323
323
@attrs
324
324
class Registry :
325
- registry : list [StepHandler .Definition ] = attrib (default = Factory (list ))
325
+ registry : set [StepHandler .Definition ] = attrib (default = Factory (set ))
326
326
parent : StepHandler .Registry = attrib (default = None , init = False )
327
327
328
328
@classmethod
329
- def register_step (
330
- cls ,
331
- caller_locals : dict ,
332
- func ,
333
- type_ ,
334
- parserlike ,
335
- converters ,
336
- params_fixtures_mapping ,
337
- param_defaults ,
338
- target_fixtures ,
339
- liberal ,
340
- ):
329
+ def setdefault_step_registry_fixture (cls , caller_locals : dict ):
341
330
if "step_registry" not in caller_locals .keys ():
342
331
built_registry = cls ()
343
- caller_locals ["step_registry" ] = built_registry .bind_pytest_bdd_step_registry_fixture ()
344
-
345
- registry : StepHandler .Registry = caller_locals ["step_registry" ].__registry__
346
-
347
- parser = get_parser (parserlike )
348
- registry .registry .append (
349
- StepHandler .Definition ( # type: ignore[call-arg]
350
- func = func ,
351
- type_ = type_ ,
352
- parser = parser ,
353
- converters = converters ,
354
- params_fixtures_mapping = params_fixtures_mapping ,
355
- param_defaults = param_defaults ,
356
- target_fixtures = target_fixtures ,
357
- liberal = liberal ,
358
- )
359
- )
332
+ caller_locals ["step_registry" ] = built_registry .fixture
333
+ return caller_locals ["step_registry" ]
334
+
335
+ @classmethod
336
+ def register_step_definition (cls , step_definition , caller_locals : dict ):
337
+ fixture = cls .setdefault_step_registry_fixture (caller_locals = caller_locals )
338
+ fixture .__registry__ .registry .add (step_definition )
339
+
340
+ @classmethod
341
+ def register_steps (cls , * step_funcs , caller_locals : dict ):
342
+ for step_func in step_funcs :
343
+ for step_definition in step_func .__pytest_bdd_step_definitions__ :
344
+ cls .register_step_definition (step_definition , caller_locals = caller_locals )
345
+
346
+ @classmethod
347
+ def register_steps_from_locals (cls , caller_locals = None , steps = None ):
348
+ if caller_locals is None :
349
+ caller_locals = get_caller_module_locals (depth = 2 )
350
+
351
+ def registrable_steps ():
352
+ for name , obj in caller_locals .items ():
353
+ if hasattr (obj , "__pytest_bdd_step_definitions__" ) and (
354
+ steps is None or any ((name in steps , obj in steps ))
355
+ ):
356
+ yield obj
357
+
358
+ cls .register_steps (* registrable_steps (), caller_locals = caller_locals )
360
359
361
- def bind_pytest_bdd_step_registry_fixture (self ):
360
+ @classmethod
361
+ def register_steps_from_module (cls , module , caller_locals = None , steps = None ):
362
+ if caller_locals is None :
363
+ caller_locals = get_caller_module_locals (depth = 2 )
364
+
365
+ def registrable_steps ():
366
+ # module items
367
+ for name , obj in module .__dict__ .items ():
368
+ if hasattr (obj , "__pytest_bdd_step_definitions__" ) and (
369
+ steps is None or any ((name in steps , obj in steps ))
370
+ ):
371
+ yield obj
372
+ # module registry items
373
+ for obj in deepattrgetter ("__registry__.registry" , default = None )(module .__dict__ .get ("step_registry" ))[
374
+ 0
375
+ ]:
376
+ if steps is None or obj .func in steps :
377
+ yield obj .func
378
+
379
+ cls .register_steps (* set (registrable_steps ()), caller_locals = caller_locals )
380
+
381
+ @property
382
+ def fixture (self ):
362
383
@pytest .fixture
363
384
def step_registry (step_registry ):
364
385
self .parent = step_registry
@@ -376,10 +397,10 @@ def decorator_builder(
376
397
step_parserlike : Any ,
377
398
converters : dict [str , Callable ] | None = None ,
378
399
target_fixture : str | None = None ,
379
- target_fixtures : list [str ] = None ,
400
+ target_fixtures : list [str ] | None = None ,
380
401
params_fixtures_mapping : set [str ] | dict [str , str ] | Any = True ,
381
402
param_defaults : dict | None = None ,
382
- liberal : bool | None = None ,
403
+ liberal : Any | None = None ,
383
404
) -> Callable :
384
405
"""StepHandler decorator for the type and the name.
385
406
@@ -414,17 +435,29 @@ def decorator(step_func: Callable) -> Callable:
414
435
415
436
:param function step_func: StepHandler definition function
416
437
"""
417
- StepHandler . Registry . register_step (
418
- caller_locals = get_caller_module_locals ( depth = 2 ),
438
+
439
+ step_definiton = StepHandler . Definition ( # type: ignore[call-arg]
419
440
func = step_func ,
420
441
type_ = step_type ,
421
- parserlike = step_parserlike ,
422
- converters = converters ,
442
+ parser = get_parser ( step_parserlike ) ,
443
+ converters = cast ( dict , converters ) ,
423
444
params_fixtures_mapping = params_fixtures_mapping ,
424
- param_defaults = param_defaults ,
425
- target_fixtures = target_fixtures ,
445
+ param_defaults = cast ( dict , param_defaults ) ,
446
+ target_fixtures = cast ( list , target_fixtures ) ,
426
447
liberal = liberal ,
427
448
)
449
+
450
+ setdefaultattr (step_func , "__pytest_bdd_step_definitions__" , value_factory = set ).add (step_definiton )
451
+
452
+ StepHandler .Registry .register_step_definition (
453
+ step_definition = step_definiton ,
454
+ caller_locals = get_caller_module_locals (depth = 2 ),
455
+ )
456
+
428
457
return step_func
429
458
430
459
return decorator
460
+
461
+
462
+ step .from_locals = StepHandler .Registry .register_steps_from_locals # type: ignore[attr-defined]
463
+ step .from_module = StepHandler .Registry .register_steps_from_module # type: ignore[attr-defined]
0 commit comments