Skip to content

Commit c95a5dc

Browse files
committed
Add async client handler decorator
1 parent ec7b326 commit c95a5dc

3 files changed

Lines changed: 455 additions & 54 deletions

File tree

cli/polyaxon/_client/decorators/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from polyaxon._client.decorators.client_call_handler import (
2+
async_client_handler,
23
client_handler,
34
get_global_or_inline_config,
45
)

cli/polyaxon/_client/decorators/client_call_handler.py

Lines changed: 150 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1+
import asyncio
12
import functools
23

34
from typing import Callable, Optional
45

6+
import aiohttp
7+
58
from urllib3.exceptions import HTTPError
69

710
from polyaxon import settings
@@ -11,6 +14,105 @@
1114
from polyaxon.logger import logger
1215

1316

17+
def _check_global_or_inline_config(args, config_key) -> Optional[bool]:
18+
self_arg = args[0] if args else None
19+
config_value = getattr(self_arg, f"_{config_key}", None)
20+
return get_global_or_inline_config(
21+
config_key=config_key,
22+
config_value=config_value,
23+
client=getattr(self_arg, "_client", None),
24+
)
25+
26+
27+
def _should_skip_client_call(
28+
args,
29+
check_no_op: bool,
30+
check_offline: bool,
31+
) -> bool:
32+
if check_no_op and _check_global_or_inline_config(args, "no_op"):
33+
logger.debug("Using NO_OP mode")
34+
return True
35+
if check_offline and _check_global_or_inline_config(args, "is_offline"):
36+
logger.debug("Using IS_OFFLINE mode")
37+
return True
38+
return False
39+
40+
41+
def _warn_missing_loggers(
42+
self_arg,
43+
f: Callable,
44+
can_log_events: bool,
45+
can_log_outputs: bool,
46+
):
47+
if can_log_events and (
48+
not hasattr(self_arg, "_event_logger")
49+
or self_arg._event_logger is None # pylint:disable=protected-access
50+
):
51+
logger.warning(
52+
"You should set an event logger before calling: {}".format(f.__name__)
53+
)
54+
55+
if can_log_outputs and (
56+
not hasattr(self_arg, "_outputs_path")
57+
or self_arg._outputs_path is None # pylint:disable=protected-access
58+
):
59+
logger.warning(
60+
"You should set an an outputs path before calling: {}".format(f.__name__)
61+
)
62+
63+
64+
def _prepare_client_call(
65+
args,
66+
f: Callable,
67+
check_no_op: bool,
68+
check_offline: bool,
69+
can_log_events: bool,
70+
can_log_outputs: bool,
71+
):
72+
if _should_skip_client_call(args, check_no_op, check_offline):
73+
return True, False
74+
75+
manual_exceptions_handling = False
76+
if args:
77+
self_arg = args[0]
78+
manual_exceptions_handling = getattr(
79+
self_arg, "_manual_exceptions_handling", False
80+
)
81+
_warn_missing_loggers(
82+
self_arg=self_arg,
83+
f=f,
84+
can_log_events=can_log_events,
85+
can_log_outputs=can_log_outputs,
86+
)
87+
88+
return False, manual_exceptions_handling
89+
90+
91+
def _get_client_error_message(f: Callable) -> str:
92+
return (
93+
"\nAPI Client failed at the function `%(name)s` in file "
94+
"`%(filename)s` line number `%(line)s`."
95+
"\nClient config:\n%(config)s\n"
96+
% {
97+
"name": f.__name__,
98+
"filename": f.__code__.co_filename,
99+
"line": f.__code__.co_firstlineno + 1,
100+
"config": settings.CLIENT_CONFIG.to_dict(),
101+
}
102+
)
103+
104+
105+
def _handle_client_exception(
106+
f: Callable,
107+
e: Exception,
108+
manual_exceptions_handling: bool,
109+
):
110+
handle_client_error(
111+
e=None if manual_exceptions_handling else e,
112+
message=_get_client_error_message(f),
113+
)
114+
115+
14116
def client_handler(
15117
check_no_op: bool = True,
16118
check_offline: bool = False,
@@ -41,68 +143,27 @@ def my_func(arg1, arg2):
41143
return ...
42144
"""
43145

44-
def _check_global_or_inline_config(args, config_key) -> Optional[bool]:
45-
self_arg = args[0] if args else None
46-
config_value = getattr(self_arg, f"_{config_key}", None)
47-
return get_global_or_inline_config(
48-
config_key=config_key,
49-
config_value=config_value,
50-
client=getattr(self_arg, "_client", None),
51-
)
52-
53146
def client_handler_wrapper(f: Callable) -> Callable:
54147
@functools.wraps(f)
55148
def wrapper(*args, **kwargs):
56-
if check_no_op and _check_global_or_inline_config(args, "no_op"):
57-
logger.debug("Using NO_OP mode")
149+
skip_call, manual_exceptions_handling = _prepare_client_call(
150+
args=args,
151+
f=f,
152+
check_no_op=check_no_op,
153+
check_offline=check_offline,
154+
can_log_events=can_log_events,
155+
can_log_outputs=can_log_outputs,
156+
)
157+
if skip_call:
58158
return None
59-
if check_offline and _check_global_or_inline_config(args, "is_offline"):
60-
logger.debug("Using IS_OFFLINE mode")
61-
return None
62-
manual_exceptions_handling = False
63-
if args:
64-
self_arg = args[0]
65-
66-
manual_exceptions_handling = getattr(
67-
self_arg, "_manual_exceptions_handling", False
68-
)
69-
70-
if can_log_events and (
71-
not hasattr(self_arg, "_event_logger")
72-
or self_arg._event_logger is None # pylint:disable=protected-access
73-
):
74-
logger.warning(
75-
"You should set an event logger before calling: {}".format(
76-
f.__name__
77-
)
78-
)
79-
80-
if can_log_outputs and (
81-
not hasattr(self_arg, "_outputs_path")
82-
or self_arg._outputs_path is None # pylint:disable=protected-access
83-
):
84-
logger.warning(
85-
"You should set an an outputs path before calling: {}".format(
86-
f.__name__
87-
)
88-
)
89159

90160
try:
91161
return f(*args, **kwargs)
92162
except (ApiException, HTTPError) as e:
93-
message = (
94-
"\nAPI Client failed at the function `%(name)s` in file "
95-
"`%(filename)s` line number `%(line)s`."
96-
"\nClient config:\n%(config)s\n"
97-
% {
98-
"name": f.__name__,
99-
"filename": f.__code__.co_filename,
100-
"line": f.__code__.co_firstlineno + 1,
101-
"config": settings.CLIENT_CONFIG.to_dict(),
102-
}
103-
)
104-
handle_client_error(
105-
e=None if manual_exceptions_handling else e, message=message
163+
_handle_client_exception(
164+
f=f,
165+
e=e,
166+
manual_exceptions_handling=manual_exceptions_handling,
106167
)
107168
raise e
108169

@@ -111,6 +172,41 @@ def wrapper(*args, **kwargs):
111172
return client_handler_wrapper
112173

113174

175+
def async_client_handler(
176+
check_no_op: bool = True,
177+
check_offline: bool = False,
178+
can_log_events: bool = False,
179+
can_log_outputs: bool = False,
180+
) -> Callable:
181+
def async_client_handler_wrapper(f: Callable) -> Callable:
182+
@functools.wraps(f)
183+
async def wrapper(*args, **kwargs):
184+
skip_call, manual_exceptions_handling = _prepare_client_call(
185+
args=args,
186+
f=f,
187+
check_no_op=check_no_op,
188+
check_offline=check_offline,
189+
can_log_events=can_log_events,
190+
can_log_outputs=can_log_outputs,
191+
)
192+
if skip_call:
193+
return None
194+
195+
try:
196+
return await f(*args, **kwargs)
197+
except (ApiException, aiohttp.ClientError, asyncio.TimeoutError) as e:
198+
_handle_client_exception(
199+
f=f,
200+
e=e,
201+
manual_exceptions_handling=manual_exceptions_handling,
202+
)
203+
raise e
204+
205+
return wrapper
206+
207+
return async_client_handler_wrapper
208+
209+
114210
def get_global_or_inline_config(
115211
config_key: str,
116212
config_value: Optional[bool] = None,

0 commit comments

Comments
 (0)