@@ -34,70 +34,90 @@ def _make_feature_decorator(
34
34
default_message : str ,
35
35
block_usage : bool = False ,
36
36
bypass_env_var : Optional [str ] = None ,
37
- ) -> Callable [[str ], Callable [[T ], T ]]:
38
- def decorator_factory (message : str = default_message ) -> Callable [[T ], T ]:
39
- def decorator (obj : T ) -> T :
40
- obj_name = getattr (obj , "__name__" , type (obj ).__name__ )
41
- msg = f"[{ label .upper ()} ] { obj_name } : { message } "
42
-
43
- if isinstance (obj , type ): # decorating a class
44
- orig_init = obj .__init__
45
-
46
- @functools .wraps (orig_init )
47
- def new_init (self , * args , ** kwargs ):
48
- # Load .env file if dotenv is available
49
- load_dotenv ()
50
-
51
- # Check if usage should be bypassed via environment variable at call time
52
- should_bypass = (
53
- bypass_env_var is not None
54
- and os .environ .get (bypass_env_var , "" ).lower () == "true"
55
- )
56
-
57
- if should_bypass :
58
- # Bypass completely - no warning, no error
59
- pass
60
- elif block_usage :
61
- raise RuntimeError (msg )
62
- else :
63
- warnings .warn (msg , category = UserWarning , stacklevel = 2 )
64
- return orig_init (self , * args , ** kwargs )
65
-
66
- obj .__init__ = new_init # type: ignore[attr-defined]
67
- return cast (T , obj )
68
-
69
- elif callable (obj ): # decorating a function or method
70
-
71
- @functools .wraps (obj )
72
- def wrapper (* args , ** kwargs ):
73
- # Load .env file if dotenv is available
74
- load_dotenv ()
75
-
76
- # Check if usage should be bypassed via environment variable at call time
77
- should_bypass = (
78
- bypass_env_var is not None
79
- and os .environ .get (bypass_env_var , "" ).lower () == "true"
80
- )
81
-
82
- if should_bypass :
83
- # Bypass completely - no warning, no error
84
- pass
85
- elif block_usage :
86
- raise RuntimeError (msg )
87
- else :
88
- warnings .warn (msg , category = UserWarning , stacklevel = 2 )
89
- return obj (* args , ** kwargs )
90
-
91
- return cast (T , wrapper )
92
-
93
- else :
94
- raise TypeError (
95
- f"@{ label } can only be applied to classes or callable objects"
37
+ ) -> Callable :
38
+ def decorator_factory (message_or_obj = None ):
39
+ # Case 1: Used as @decorator without parentheses
40
+ # message_or_obj is the decorated class/function
41
+ if message_or_obj is not None and (
42
+ isinstance (message_or_obj , type ) or callable (message_or_obj )
43
+ ):
44
+ return _create_decorator (
45
+ default_message , label , block_usage , bypass_env_var
46
+ )(message_or_obj )
47
+
48
+ # Case 2: Used as @decorator() with or without message
49
+ # message_or_obj is either None or a string message
50
+ message = (
51
+ message_or_obj if isinstance (message_or_obj , str ) else default_message
52
+ )
53
+ return _create_decorator (message , label , block_usage , bypass_env_var )
54
+
55
+ return decorator_factory
56
+
57
+
58
+ def _create_decorator (
59
+ message : str , label : str , block_usage : bool , bypass_env_var : Optional [str ]
60
+ ) -> Callable [[T ], T ]:
61
+ def decorator (obj : T ) -> T :
62
+ obj_name = getattr (obj , "__name__" , type (obj ).__name__ )
63
+ msg = f"[{ label .upper ()} ] { obj_name } : { message } "
64
+
65
+ if isinstance (obj , type ): # decorating a class
66
+ orig_init = obj .__init__
67
+
68
+ @functools .wraps (orig_init )
69
+ def new_init (self , * args , ** kwargs ):
70
+ # Load .env file if dotenv is available
71
+ load_dotenv ()
72
+
73
+ # Check if usage should be bypassed via environment variable at call time
74
+ should_bypass = (
75
+ bypass_env_var is not None
76
+ and os .environ .get (bypass_env_var , "" ).lower () == "true"
96
77
)
97
78
98
- return decorator
79
+ if should_bypass :
80
+ # Bypass completely - no warning, no error
81
+ pass
82
+ elif block_usage :
83
+ raise RuntimeError (msg )
84
+ else :
85
+ warnings .warn (msg , category = UserWarning , stacklevel = 2 )
86
+ return orig_init (self , * args , ** kwargs )
87
+
88
+ obj .__init__ = new_init # type: ignore[attr-defined]
89
+ return cast (T , obj )
90
+
91
+ elif callable (obj ): # decorating a function or method
92
+
93
+ @functools .wraps (obj )
94
+ def wrapper (* args , ** kwargs ):
95
+ # Load .env file if dotenv is available
96
+ load_dotenv ()
97
+
98
+ # Check if usage should be bypassed via environment variable at call time
99
+ should_bypass = (
100
+ bypass_env_var is not None
101
+ and os .environ .get (bypass_env_var , "" ).lower () == "true"
102
+ )
99
103
100
- return decorator_factory
104
+ if should_bypass :
105
+ # Bypass completely - no warning, no error
106
+ pass
107
+ elif block_usage :
108
+ raise RuntimeError (msg )
109
+ else :
110
+ warnings .warn (msg , category = UserWarning , stacklevel = 2 )
111
+ return obj (* args , ** kwargs )
112
+
113
+ return cast (T , wrapper )
114
+
115
+ else :
116
+ raise TypeError (
117
+ f"@{ label } can only be applied to classes or callable objects"
118
+ )
119
+
120
+ return decorator
101
121
102
122
103
123
working_in_progress = _make_feature_decorator (
@@ -137,8 +157,19 @@ def my_wip_function():
137
157
Sample usage:
138
158
139
159
```
140
- @experimental("This API may have breaking change in the future.")
160
+ # Use with default message
161
+ @experimental
141
162
class ExperimentalClass:
142
163
pass
164
+
165
+ # Use with custom message
166
+ @experimental("This API may have breaking change in the future.")
167
+ class CustomExperimentalClass:
168
+ pass
169
+
170
+ # Use with empty parentheses (same as default message)
171
+ @experimental()
172
+ def experimental_function():
173
+ pass
143
174
```
144
175
"""
0 commit comments