5
5
# This source code is licensed under the BSD-style license found in the
6
6
# LICENSE file in the root directory of this source tree.
7
7
8
- import argparse
9
8
import copy
10
- import inspect
11
9
import json
12
10
from dataclasses import asdict , dataclass , field
13
11
from enum import Enum
28
26
)
29
27
30
28
import yaml
31
- from torchx .specs .file_linter import get_fn_docstring , TorchXArgumentHelpFormatter
32
- from torchx .util .types import (
33
- decode_from_string ,
34
- decode_optional ,
35
- get_argparse_param_type ,
36
- is_bool ,
37
- is_primitive ,
38
- to_dict ,
39
- )
29
+ from torchx .util .types import to_dict
40
30
41
31
42
32
# ========================================
@@ -832,10 +822,6 @@ def __init__(self, app_handle: "AppHandle") -> None:
832
822
)
833
823
834
824
835
- def make_app_handle (scheduler_backend : str , session_name : str , app_id : str ) -> str :
836
- return f"{ scheduler_backend } ://{ session_name } /{ app_id } "
837
-
838
-
839
825
def parse_app_handle (app_handle : AppHandle ) -> Tuple [str , str , str ]:
840
826
"""
841
827
parses the app handle into ```(scheduler_backend, session_name, and app_id)```
@@ -851,215 +837,3 @@ def parse_app_handle(app_handle: AppHandle) -> Tuple[str, str, str]:
851
837
raise MalformedAppHandleException (app_handle )
852
838
gd = match .groupdict ()
853
839
return gd ["scheduler_backend" ], gd ["session_name" ], gd ["app_id" ]
854
-
855
-
856
- def _create_args_parser (
857
- cmpnt_fn : Callable [..., AppDef ], cmpnt_defaults : Optional [Dict [str , str ]] = None
858
- ) -> argparse .ArgumentParser :
859
- parameters = inspect .signature (cmpnt_fn ).parameters
860
- function_desc , args_desc = get_fn_docstring (cmpnt_fn )
861
- script_parser = argparse .ArgumentParser (
862
- prog = f"torchx run <run args...> { cmpnt_fn .__name__ } " ,
863
- description = function_desc ,
864
- formatter_class = TorchXArgumentHelpFormatter ,
865
- # enables components to have "h" as a parameter
866
- # otherwise argparse by default adds -h/--help as the help argument
867
- # we still add --help but reserve "-"h" to be used as a component argument
868
- add_help = False ,
869
- )
870
- # add help manually since we disabled auto help to allow "h" in component arg
871
- script_parser .add_argument (
872
- "--help" ,
873
- action = "help" ,
874
- default = argparse .SUPPRESS ,
875
- help = "show this help message and exit" ,
876
- )
877
-
878
- class _reminder_action (argparse .Action ):
879
- def __call__ (
880
- self ,
881
- parser : argparse .ArgumentParser ,
882
- namespace : argparse .Namespace ,
883
- values : Any ,
884
- option_string : Optional [str ] = None ,
885
- ) -> None :
886
- setattr (
887
- namespace ,
888
- self .dest ,
889
- (self .default or "" ).split () if len (values ) == 0 else values ,
890
- )
891
-
892
- for param_name , parameter in parameters .items ():
893
- param_desc = args_desc [parameter .name ]
894
- args : Dict [str , Any ] = {
895
- "help" : param_desc ,
896
- "type" : get_argparse_param_type (parameter ),
897
- }
898
- # set defaults specified in the component function declaration
899
- if parameter .default != inspect .Parameter .empty :
900
- if is_bool (type (parameter .default )):
901
- args ["default" ] = str (parameter .default )
902
- else :
903
- args ["default" ] = parameter .default
904
-
905
- # set defaults supplied directly to this method (overwrites the declared defaults)
906
- # the defaults are given as str (as option values passed from CLI) since
907
- # these are typically read from .torchxconfig
908
- if cmpnt_defaults and param_name in cmpnt_defaults :
909
- args ["default" ] = cmpnt_defaults [param_name ]
910
-
911
- if parameter .kind == inspect ._ParameterKind .VAR_POSITIONAL :
912
- args ["nargs" ] = argparse .REMAINDER
913
- args ["action" ] = _reminder_action
914
- script_parser .add_argument (param_name , ** args )
915
- else :
916
- arg_names = [f"--{ param_name } " ]
917
- if len (param_name ) == 1 :
918
- arg_names = [f"-{ param_name } " ] + arg_names
919
- if "default" not in args :
920
- args ["required" ] = True
921
- script_parser .add_argument (* arg_names , ** args )
922
- return script_parser
923
-
924
-
925
- # FIXME this function invokes the component fn to materialize the AppDef
926
- # rename to something more appropriate like - materialize_appdef or eval_component_fn
927
- # seee: https://github.com/pytorch/torchx/issues/368
928
- def from_function (
929
- cmpnt_fn : Callable [..., AppDef ],
930
- cmpnt_args : List [str ],
931
- cmpnt_defaults : Optional [Dict [str , str ]] = None ,
932
- ) -> AppDef :
933
- """
934
- Creates an application by running user defined ``app_fn``.
935
-
936
- ``app_fn`` has the following restrictions:
937
- * Name must be ``app_fn``
938
- * All arguments should be annotated
939
- * Supported argument types:
940
- - primitive: int, str, float
941
- - Dict[primitive, primitive]
942
- - List[primitive]
943
- - Optional[Dict[primitive, primitive]]
944
- - Optional[List[primitive]]
945
- * ``app_fn`` can define a vararg (*arg) at the end
946
- * There should be a docstring for the function that defines
947
- All arguments in a google-style format
948
- * There can be default values for the function arguments.
949
- * The return object must be ``AppDef``
950
-
951
- Args:
952
- cmpnt_fn: Component function
953
- cmpnt_args: Function args
954
- cmpnt_defaults: Additional default values for parameters of ``app_fn``
955
- (overrides the defaults set on the fn declaration)
956
- Returns:
957
- An application spec
958
- """
959
-
960
- script_parser = _create_args_parser (cmpnt_fn , cmpnt_defaults )
961
- parsed_args = script_parser .parse_args (cmpnt_args )
962
-
963
- function_args = []
964
- var_arg = []
965
- kwargs = {}
966
-
967
- parameters = inspect .signature (cmpnt_fn ).parameters
968
- for param_name , parameter in parameters .items ():
969
- arg_value = getattr (parsed_args , param_name )
970
- parameter_type = parameter .annotation
971
- parameter_type = decode_optional (parameter_type )
972
- if is_bool (parameter_type ):
973
- arg_value = arg_value .lower () == "true"
974
- elif not is_primitive (parameter_type ):
975
- arg_value = decode_from_string (arg_value , parameter_type )
976
- if parameter .kind == inspect .Parameter .VAR_POSITIONAL :
977
- var_arg = arg_value
978
- elif parameter .kind == inspect .Parameter .KEYWORD_ONLY :
979
- kwargs [param_name ] = arg_value
980
- elif parameter .kind == inspect .Parameter .VAR_KEYWORD :
981
- raise TypeError ("**kwargs are not supported for component definitions" )
982
- else :
983
- function_args .append (arg_value )
984
- if len (var_arg ) > 0 and var_arg [0 ] == "--" :
985
- var_arg = var_arg [1 :]
986
-
987
- return cmpnt_fn (* function_args , * var_arg , ** kwargs )
988
-
989
-
990
- _MOUNT_OPT_MAP : Mapping [str , str ] = {
991
- "type" : "type" ,
992
- "destination" : "dst" ,
993
- "dst" : "dst" ,
994
- "target" : "dst" ,
995
- "read_only" : "readonly" ,
996
- "readonly" : "readonly" ,
997
- "source" : "src" ,
998
- "src" : "src" ,
999
- "perm" : "perm" ,
1000
- }
1001
-
1002
-
1003
- def parse_mounts (opts : List [str ]) -> List [Union [BindMount , VolumeMount , DeviceMount ]]:
1004
- """
1005
- parse_mounts parses a list of options into typed mounts following a similar
1006
- format to Dockers bind mount.
1007
-
1008
- Multiple mounts can be specified in the same list. ``type`` must be
1009
- specified first in each.
1010
-
1011
- Ex:
1012
- type=bind,src=/host,dst=/container,readonly,[type=bind,src=...,dst=...]
1013
-
1014
- Supported types:
1015
- BindMount: type=bind,src=<host path>,dst=<container path>[,readonly]
1016
- VolumeMount: type=volume,src=<name/id>,dst=<container path>[,readonly]
1017
- DeviceMount: type=device,src=/dev/<dev>[,dst=<container path>][,perm=rwm]
1018
- """
1019
- mount_opts = []
1020
- cur = {}
1021
- for opt in opts :
1022
- key , _ , val = opt .partition ("=" )
1023
- if key not in _MOUNT_OPT_MAP :
1024
- raise KeyError (
1025
- f"unknown mount option { key } , must be one of { list (_MOUNT_OPT_MAP .keys ())} "
1026
- )
1027
- key = _MOUNT_OPT_MAP [key ]
1028
- if key == "type" :
1029
- cur = {}
1030
- mount_opts .append (cur )
1031
- elif len (mount_opts ) == 0 :
1032
- raise KeyError ("type must be specified first" )
1033
- cur [key ] = val
1034
-
1035
- mounts = []
1036
- for opts in mount_opts :
1037
- typ = opts .get ("type" )
1038
- if typ == MountType .BIND :
1039
- mounts .append (
1040
- BindMount (
1041
- src_path = opts ["src" ],
1042
- dst_path = opts ["dst" ],
1043
- read_only = "readonly" in opts ,
1044
- )
1045
- )
1046
- elif typ == MountType .VOLUME :
1047
- mounts .append (
1048
- VolumeMount (
1049
- src = opts ["src" ], dst_path = opts ["dst" ], read_only = "readonly" in opts
1050
- )
1051
- )
1052
- elif typ == MountType .DEVICE :
1053
- src = opts ["src" ]
1054
- dst = opts .get ("dst" , src )
1055
- perm = opts .get ("perm" , "rwm" )
1056
- for c in perm :
1057
- if c not in "rwm" :
1058
- raise ValueError (
1059
- f"{ c } is not a valid permission flags must one of r,w,m"
1060
- )
1061
- mounts .append (DeviceMount (src_path = src , dst_path = dst , permissions = perm ))
1062
- else :
1063
- valid = list (str (item .value ) for item in MountType )
1064
- raise ValueError (f"invalid mount type { repr (typ )} , must be one of { valid } " )
1065
- return mounts
0 commit comments