3838from rich .progress import Progress , SpinnerColumn , TextColumn
3939
4040from dynamo .sdk import DYNAMO_IMAGE
41+ from dynamo .sdk .core .protocol .deployment import Service
4142from dynamo .sdk .core .protocol .interface import (
43+ DynamoConfig ,
4244 DynamoTransport ,
4345 LinkedServices ,
4446 ServiceInterface ,
4547)
4648from dynamo .sdk .core .runner import TargetEnum
49+ from dynamo .sdk .lib .utils import upload_graph
4750
4851logger = logging .getLogger (__name__ )
4952console = Console ()
@@ -104,7 +107,7 @@ class ServiceConfig(BaseModel):
104107 resources : t .Dict [str , t .Any ] = Field (default_factory = dict )
105108 workers : t .Optional [int ] = None
106109 image : str = "dynamo:latest"
107- dynamo : t . Dict [ str , t . Any ] = Field (default_factory = dict )
110+ dynamo : DynamoConfig = Field (default_factory = DynamoConfig )
108111 http_exposed : bool = False
109112 api_endpoints : t .List [str ] = Field (default_factory = list )
110113
@@ -141,7 +144,7 @@ def from_service(cls, service: ServiceInterface[T]) -> ServiceInfo:
141144 resources = service .config .resources .model_dump (),
142145 workers = service .config .workers ,
143146 image = image ,
144- dynamo = service .config .dynamo .model_dump (),
147+ dynamo = DynamoConfig ( ** service .config .dynamo .model_dump () ),
145148 http_exposed = len (api_endpoints ) > 0 ,
146149 api_endpoints = api_endpoints ,
147150 )
@@ -155,7 +158,7 @@ def from_service(cls, service: ServiceInterface[T]) -> ServiceInfo:
155158
156159
157160class BuildConfig (BaseModel ):
158- """Configuration for building a Dynamo pipeline ."""
161+ """Configuration for building a Dynamo graph ."""
159162
160163 service : str
161164 name : t .Optional [str ] = None
@@ -277,7 +280,7 @@ def dynamo_service(
277280 cls ,
278281 build_config : BuildConfig ,
279282 build_ctx : t .Optional [str ] = None ,
280- ) -> t . Any :
283+ ) -> ServiceInterface :
281284 """Get a dynamo service from config."""
282285 build_ctx = (
283286 os .getcwd ()
@@ -367,6 +370,26 @@ def generate_manifests(self) -> None:
367370 with open (os .path .join (self .path , "dynamo.yaml" ), "w" ) as f :
368371 yaml .dump (manifest_dict , f , default_flow_style = False )
369372
373+ def get_entry_service (self ) -> Service :
374+ """Get the entry service."""
375+ for service in self .info .services :
376+ if service .name == self .info .entry_service :
377+ entry_service = service
378+ break
379+ else :
380+ raise ValueError (
381+ f"Entry service { self .info .entry_service } not found in services"
382+ )
383+
384+ return Service (
385+ service_name = self .info .service ,
386+ name = self .info .entry_service ,
387+ namespace = entry_service .config .dynamo .namespace ,
388+ version = self .info .tag .version ,
389+ path = self .path ,
390+ envs = self .info .envs ,
391+ )
392+
370393 @staticmethod
371394 def load_service (service_path : str , working_dir : str ) -> t .Any :
372395 """Load a service from a path."""
@@ -481,6 +504,9 @@ def build(
481504 service : str = typer .Argument (
482505 ..., help = "Service specification in the format module:ServiceClass"
483506 ),
507+ endpoint : t .Optional [str ] = typer .Option (
508+ None , "--endpoint" , "-e" , help = "Dynamo Cloud endpoint" , envvar = "DYNAMO_CLOUD"
509+ ),
484510 output_dir : t .Optional [str ] = typer .Option (
485511 None , "--output-dir" , "-o" , help = "Output directory for the build"
486512 ),
@@ -490,13 +516,25 @@ def build(
490516 containerize : bool = typer .Option (
491517 False ,
492518 "--containerize" ,
493- help = "Containerize the dynamo pipeline after building." ,
519+ help = "Containerize the dynamo graph after building." ,
520+ ),
521+ push : bool = typer .Option (
522+ False ,
523+ "--push" ,
524+ help = "Push the built dynamo graph to the Dynamo cloud remote API store." ,
494525 ),
495526) -> None :
496- """Packages Dynamo service for deployment. Optionally builds a docker container."""
527+ """Packages Dynamo service for deployment. Optionally builds and/or pushes a docker container."""
497528 from dynamo .sdk .cli .utils import configure_target_environment
498529
499530 configure_target_environment (TargetEnum .DYNAMO )
531+ if push :
532+ containerize = True
533+ if endpoint is None :
534+ console .print (
535+ "[bold red]Error: --push requires --endpoint, -e, or DYNAMO_CLOUD environment variable to be set.[/]"
536+ )
537+ raise typer .Exit (1 )
500538
501539 # Determine output directory
502540 if output_dir is None :
@@ -558,9 +596,12 @@ def build(
558596 next_steps = []
559597 if not containerize :
560598 next_steps .append (
561- "\n \n * Containerize your Dynamo pipeline with "
599+ "\n \n * Containerize your Dynamo graph with "
562600 "`dynamo build --containerize <service_name>`:\n "
563601 f" $ dynamo build --containerize { service } "
602+ "\n \n * Push your Dynamo graph to the Dynamo cloud with "
603+ "`dynamo build --push <service_name>`:\n "
604+ f" $ dynamo build --push { service } "
564605 )
565606
566607 if next_steps :
@@ -597,13 +638,29 @@ def build(
597638 check = True ,
598639 )
599640 console .print (f"[green]Successfully built Docker image { image_name } ." )
641+
642+ if push :
643+ # Upload the graph to the Dynamo cloud remote API store
644+ with Progress (
645+ SpinnerColumn (),
646+ TextColumn (
647+ f"[bold green]Pushing graph { image_name } to Dynamo cloud..."
648+ ),
649+ transient = True ,
650+ ) as progress :
651+ progress .add_task ("push" , total = None )
652+ entry_service = package .get_entry_service ()
653+ upload_graph (endpoint , image_name , entry_service )
654+ console .print (
655+ f"[green]Successfully pushed graph { image_name } to Dynamo cloud."
656+ )
600657 except Exception as e :
601- console .print (f"[red]Error building package : { str (e )} " )
658+ console .print (f"[red]Error with build : { str (e )} " )
602659 raise
603660
604661
605662def generate_random_tag () -> str :
606- """Generate a random tag for the Dynamo pipeline ."""
663+ """Generate a random tag for the Dynamo graph ."""
607664 return f"{ uuid .uuid4 ().hex [:8 ]} "
608665
609666
0 commit comments