22# + All contributors to <https://github.com/smarie/python-pytest-cases>
33#
44# License: 3-clause BSD, <https://github.com/smarie/python-pytest-cases/blob/master/LICENSE>
5+ from copy import copy
56from decopatch import function_decorator , DECORATED
67
7- from .case_info import CaseInfo , _tags_match_query
8-
98try : # python 3.5+
109 from typing import Type , Callable , Union , Optional , Any , Tuple , Dict , Iterable , List , Set
1110except ImportError :
1211 pass
1312
13+ from .common_mini_six import string_types
1414from .common_pytest import safe_isclass
1515from .common_pytest_marks import get_pytest_marks_on_function , markdecorators_as_tuple , markdecorators_to_markinfos
1616
2828"""Prefix used by default to identify case functions within a module"""
2929
3030
31+ CASE_FIELD = '_pytestcase'
32+
33+
34+ class _CaseInfo (object ):
35+ """
36+ Contains all information available about a case.
37+ It is attached to a case function as an attribute.
38+
39+ Currently we do not wish to export an object-oriented API for this but rather a set of functions.
40+ This is why this class remains private. Public functions to access the various elements in this class
41+ are provided below (`get_case_id`, `get_case_tags` and `get_case_marks`). This is a safeguard to allow us
42+ to change this class design later while easily guaranteeing retrocompatibility.
43+ """
44+ __slots__ = ('id' , 'marks' , 'tags' )
45+
46+ def __init__ (self ,
47+ id = None , # type: str
48+ marks = (), # type: Tuple[MarkDecorator, ...]
49+ tags = () # type: Tuple[Any]
50+ ):
51+ self .id = id
52+ self .marks = marks
53+ self .tags = ()
54+ self .add_tags (tags )
55+
56+ def __repr__ (self ):
57+ return "_CaseInfo(id=%r,marks=%r,tags=%r)" % (self .id , self .marks , self .tags )
58+
59+ @classmethod
60+ def get_from (cls ,
61+ case_func , # type: Callable
62+ create_if_missing = False # type: bool
63+ ):
64+ """ Return the _CaseInfo associated with case_fun or None
65+
66+ :param case_func:
67+ :param create_if_missing: if no case information is present on the function, by default None is returned. If
68+ this flag is set to True, a new _CaseInfo will be created and attached on the function, and returned.
69+ """
70+ ci = getattr (case_func , CASE_FIELD , None )
71+ if ci is None and create_if_missing :
72+ ci = cls ()
73+ ci .attach_to (case_func )
74+ return ci
75+
76+ def attach_to (self ,
77+ case_func # type: Callable
78+ ):
79+ """attach this case_info to the given case function"""
80+ setattr (case_func , CASE_FIELD , self )
81+
82+ def add_tags (self ,
83+ tags # type: Union[Any, Union[List, Set, Tuple]]
84+ ):
85+ """add the given tag or tags"""
86+ if tags :
87+ if isinstance (tags , string_types ) or not isinstance (tags , (set , list , tuple )):
88+ # a single tag, create a tuple around it
89+ tags = (tags ,)
90+
91+ self .tags += tuple (tags )
92+
93+ def matches_tag_query (self ,
94+ has_tag = None , # type: Union[str, Iterable[str]]
95+ ):
96+ """
97+ Returns True if the case function with this case_info is selected by the query
98+
99+ :param has_tag:
100+ :return:
101+ """
102+ return _tags_match_query (self .tags , has_tag )
103+
104+ @classmethod
105+ def copy_info (cls ,
106+ from_case_func ,
107+ to_case_func ):
108+ case_info = cls .get_from (from_case_func )
109+ if case_info is not None :
110+ # there is something to copy: do it
111+ cp = copy (case_info )
112+ cp .attach_to (to_case_func )
113+
114+
115+ def _tags_match_query (tags , # type: Iterable[str]
116+ has_tag # type: Optional[Union[str, Iterable[str]]]
117+ ):
118+ """Internal routine to determine is all tags in `has_tag` are persent in `tags`
119+ Note that `has_tag` can be a single tag, or none
120+ """
121+ if has_tag is None :
122+ return True
123+
124+ if not isinstance (has_tag , (tuple , list , set )):
125+ has_tag = (has_tag ,)
126+
127+ return all (t in tags for t in has_tag )
128+
129+
31130def copy_case_info (from_fun , # type: Callable
32131 to_fun # type: Callable
33132 ):
34133 """Copy all information from case function `from_fun` to `to_fun`."""
35- CaseInfo .copy_info (from_fun , to_fun )
134+ _CaseInfo .copy_info (from_fun , to_fun )
36135
37136
38137def set_case_id (id , # type: str
39138 case_func # type: Callable
40139 ):
41140 """Set an explicit id on case function `case_func`."""
42- ci = CaseInfo .get_from (case_func , create_if_missing = True )
141+ ci = _CaseInfo .get_from (case_func , create_if_missing = True )
43142 ci .id = id
44143
45144
@@ -57,7 +156,7 @@ def get_case_id(case_func, # type: Callable
57156 case id.
58157 :return:
59158 """
60- _ci = CaseInfo .get_from (case_func )
159+ _ci = _CaseInfo .get_from (case_func )
61160 _id = _ci .id if _ci is not None else None
62161
63162 if _id is None :
@@ -95,7 +194,7 @@ def get_case_marks(case_func, # type: Callable
95194 returned. Otherwise (default) the marks are returned as is.
96195 :return:
97196 """
98- _ci = CaseInfo .get_from (case_func )
197+ _ci = _CaseInfo .get_from (case_func )
99198 if _ci is None :
100199 _ci_marks = None
101200 else :
@@ -119,7 +218,7 @@ def get_case_marks(case_func, # type: Callable
119218def get_case_tags (case_func # type: Callable
120219 ):
121220 """Return the tags on this case function or an empty tuple"""
122- ci = CaseInfo .get_from (case_func )
221+ ci = _CaseInfo .get_from (case_func )
123222 return ci .tags if ci is not None else ()
124223
125224
@@ -198,7 +297,7 @@ def case_hi():
198297 :return:
199298 """
200299 marks = markdecorators_as_tuple (marks )
201- case_info = CaseInfo (id , marks , tags )
300+ case_info = _CaseInfo (id , marks , tags )
202301 case_info .attach_to (case_func )
203302 return case_func
204303
0 commit comments