6565_SUBCOMMAND_KEY_DELETE = "DELETE"
6666_SUBCOMMAND_KEY_LIST = "LIST"
6767_SUBCOMMAND_KEY_EXPORT = "EXPORT"
68+ _SUBCOMMAND_KEY_UPDATE_METADATA = "UPDATEMETADATA"
6869_SUBCOMMAND_KEY_AUTH = "AUTH"
6970_AUTH_SUBCOMMAND_FLAG = "_uploader__subcommand_auth"
7071_AUTH_SUBCOMMAND_KEY_REVOKE = "REVOKE"
7172
7273_DEFAULT_ORIGIN = "https://tensorboard.dev"
7374
7475
76+ # Size limits for input fields not bounded at a wire level. "Chars" in this
77+ # context refers to Unicode code points as stipulated by https://aip.dev/210.
78+ _EXPERIMENT_NAME_MAX_CHARS = 100
79+ _EXPERIMENT_DESCRIPTION_MAX_CHARS = 600
80+
81+
7582def _prompt_for_user_ack (intent ):
7683 """Prompts for user consent, exiting the program if they decline."""
7784 body = intent .get_ack_message_body ()
@@ -139,6 +146,46 @@ def _define_flags(parser):
139146 default = None ,
140147 help = "Directory containing the logs to process" ,
141148 )
149+ upload .add_argument (
150+ "--name" ,
151+ type = str ,
152+ default = None ,
153+ help = "Title of the experiment. Max 100 characters." ,
154+ )
155+ upload .add_argument (
156+ "--description" ,
157+ type = str ,
158+ default = None ,
159+ help = "Experiment description. Markdown format. Max 600 characters." ,
160+ )
161+
162+ update_metadata = subparsers .add_parser (
163+ "update-metadata" ,
164+ help = "change the name, description, or other user "
165+ "metadata associated with an experiment." ,
166+ )
167+ update_metadata .set_defaults (
168+ ** {_SUBCOMMAND_FLAG : _SUBCOMMAND_KEY_UPDATE_METADATA }
169+ )
170+ update_metadata .add_argument (
171+ "--experiment_id" ,
172+ metavar = "EXPERIMENT_ID" ,
173+ type = str ,
174+ default = None ,
175+ help = "ID of the experiment on which to modify the metadata." ,
176+ )
177+ update_metadata .add_argument (
178+ "--name" ,
179+ type = str ,
180+ default = None ,
181+ help = "Title of the experiment. Max 100 characters." ,
182+ )
183+ update_metadata .add_argument (
184+ "--description" ,
185+ type = str ,
186+ default = None ,
187+ help = "Experiment description. Markdown format. Max 600 characters." ,
188+ )
142189
143190 delete = subparsers .add_parser (
144191 "delete" ,
@@ -372,6 +419,72 @@ def execute(self, server_info, channel):
372419 print ("Deleted experiment %s." % experiment_id )
373420
374421
422+ class _UpdateMetadataIntent (_Intent ):
423+ """The user intends to update the metadata for an experiment."""
424+
425+ _MESSAGE_TEMPLATE = textwrap .dedent (
426+ u"""\
427+ This will modify the metadata associated with the experiment on
428+ https://tensorboard.dev with the following experiment ID:
429+
430+ {experiment_id}
431+
432+ You have chosen to modify an experiment. All experiments uploaded
433+ to TensorBoard.dev are publicly visible. Do not upload sensitive
434+ data.
435+ """
436+ )
437+
438+ def __init__ (self , experiment_id , name = None , description = None ):
439+ self .experiment_id = experiment_id
440+ self .name = name
441+ self .description = description
442+
443+ def get_ack_message_body (self ):
444+ return self ._MESSAGE_TEMPLATE .format (experiment_id = self .experiment_id )
445+
446+ def execute (self , server_info , channel ):
447+ api_client = write_service_pb2_grpc .TensorBoardWriterServiceStub (
448+ channel
449+ )
450+ experiment_id = self .experiment_id
451+ _die_if_bad_experiment_name (self .name )
452+ _die_if_bad_experiment_description (self .description )
453+ if not experiment_id :
454+ raise base_plugin .FlagsError (
455+ "Must specify a non-empty experiment ID to modify."
456+ )
457+ try :
458+ uploader_lib .update_experiment_metadata (
459+ api_client ,
460+ experiment_id ,
461+ name = self .name ,
462+ description = self .description ,
463+ )
464+ except uploader_lib .ExperimentNotFoundError :
465+ _die (
466+ "No such experiment %s. Either it never existed or it has "
467+ "already been deleted." % experiment_id
468+ )
469+ except uploader_lib .PermissionDeniedError :
470+ _die (
471+ "Cannot modify experiment %s because it is owned by a "
472+ "different user." % experiment_id
473+ )
474+ except uploader_lib .InvalidArgumentError as cm :
475+ _die (
476+ "Server cannot modify experiment as requested.\n "
477+ "Server responded: %s" % cm .description ()
478+ )
479+ except grpc .RpcError as e :
480+ _die ("Internal error modifying experiment: %s" % e )
481+ logging .info ("Modified experiment %s." , experiment_id )
482+ if self .name is not None :
483+ logging .info ("Set name to %r" , self .name )
484+ if self .description is not None :
485+ logging .info (f"Set description to %r" , repr (self .description ))
486+
487+
375488class _ListIntent (_Intent ):
376489 """The user intends to list all their experiments."""
377490
@@ -409,6 +522,8 @@ def execute(self, server_info, channel):
409522 url = server_info_lib .experiment_url (server_info , experiment_id )
410523 print (url )
411524 data = [
525+ ("Name" , experiment .name or "[No Name]" ),
526+ ("Description" , experiment .description or "[No Description]" ),
412527 ("Id" , experiment .experiment_id ),
413528 ("Created" , util .format_time (experiment .create_time )),
414529 ("Updated" , util .format_time (experiment .update_time )),
@@ -417,7 +532,7 @@ def execute(self, server_info, channel):
417532 ("Tags" , str (experiment .num_tags )),
418533 ]
419534 for (name , value ) in data :
420- print ("\t %s %s" % (name .ljust (10 ), value ))
535+ print ("\t %s %s" % (name .ljust (12 ), value ))
421536 sys .stdout .flush ()
422537 if not count :
423538 sys .stderr .write (
@@ -428,6 +543,24 @@ def execute(self, server_info, channel):
428543 sys .stderr .flush ()
429544
430545
546+ def _die_if_bad_experiment_name (name ):
547+ if name and len (name ) > _EXPERIMENT_NAME_MAX_CHARS :
548+ _die (
549+ "Experiment name is too long. Limit is "
550+ "%s characters.\n "
551+ "%r was provided." % (_EXPERIMENT_NAME_MAX_CHARS , name )
552+ )
553+
554+
555+ def _die_if_bad_experiment_description (description ):
556+ if description and len (description ) > _EXPERIMENT_DESCRIPTION_MAX_CHARS :
557+ _die (
558+ "Experiment description is too long. Limit is %s characters.\n "
559+ "%r was provided."
560+ % (_EXPERIMENT_DESCRIPTION_MAX_CHARS , description )
561+ )
562+
563+
431564class _UploadIntent (_Intent ):
432565 """The user intends to upload an experiment from the given logdir."""
433566
@@ -443,8 +576,10 @@ class _UploadIntent(_Intent):
443576 """
444577 )
445578
446- def __init__ (self , logdir ):
579+ def __init__ (self , logdir , name = None , description = None ):
447580 self .logdir = logdir
581+ self .name = name
582+ self .description = description
448583
449584 def get_ack_message_body (self ):
450585 return self ._MESSAGE_TEMPLATE .format (logdir = self .logdir )
@@ -453,7 +588,14 @@ def execute(self, server_info, channel):
453588 api_client = write_service_pb2_grpc .TensorBoardWriterServiceStub (
454589 channel
455590 )
456- uploader = uploader_lib .TensorBoardUploader (api_client , self .logdir )
591+ _die_if_bad_experiment_name (self .name )
592+ _die_if_bad_experiment_description (self .description )
593+ uploader = uploader_lib .TensorBoardUploader (
594+ api_client ,
595+ self .logdir ,
596+ name = self .name ,
597+ description = self .description ,
598+ )
457599 experiment_id = uploader .create_experiment ()
458600 url = server_info_lib .experiment_url (server_info , experiment_id )
459601 print (
@@ -541,11 +683,31 @@ def _get_intent(flags):
541683 raise base_plugin .FlagsError ("Must specify subcommand (try --help)." )
542684 if cmd == _SUBCOMMAND_KEY_UPLOAD :
543685 if flags .logdir :
544- return _UploadIntent (os .path .expanduser (flags .logdir ))
686+ return _UploadIntent (
687+ os .path .expanduser (flags .logdir ),
688+ name = flags .name ,
689+ description = flags .description ,
690+ )
545691 else :
546692 raise base_plugin .FlagsError (
547693 "Must specify directory to upload via `--logdir`."
548694 )
695+ if cmd == _SUBCOMMAND_KEY_UPDATE_METADATA :
696+ if flags .experiment_id :
697+ if flags .name is not None or flags .description is not None :
698+ return _UpdateMetadataIntent (
699+ flags .experiment_id ,
700+ name = flags .name ,
701+ description = flags .description ,
702+ )
703+ else :
704+ raise base_plugin .FlagsError (
705+ "Must specify either `--name` or `--description`."
706+ )
707+ else :
708+ raise base_plugin .FlagsError (
709+ "Must specify experiment to modify via `--experiment_id`."
710+ )
549711 elif cmd == _SUBCOMMAND_KEY_DELETE :
550712 if flags .experiment_id :
551713 return _DeleteExperimentIntent (flags .experiment_id )
0 commit comments