55# This source code is licensed under the BSD-style license found in the
66# LICENSE file in the root directory of this source tree.
77
8- import argparse
98import copy
10- import inspect
119import json
1210from dataclasses import asdict , dataclass , field
1311from enum import Enum
2826)
2927
3028import 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
4030
4131
4232# ========================================
@@ -832,10 +822,6 @@ def __init__(self, app_handle: "AppHandle") -> None:
832822 )
833823
834824
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-
839825def parse_app_handle (app_handle : AppHandle ) -> Tuple [str , str , str ]:
840826 """
841827 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]:
851837 raise MalformedAppHandleException (app_handle )
852838 gd = match .groupdict ()
853839 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