@@ -745,6 +745,224 @@ def upload_kaggle(
745745 sys .exit (1 )
746746
747747
748+ # DeepFabric Cloud upload command group
749+ @click .group ()
750+ def upload () -> None :
751+ """Upload datasets and graphs to DeepFabric Cloud."""
752+ pass
753+
754+
755+ def _upload_to_cloud (
756+ file : str ,
757+ resource_type : Literal ["dataset" , "graph" ],
758+ handle : str | None ,
759+ name : str | None ,
760+ description : str | None ,
761+ tags : list [str ] | None ,
762+ config_file : str | None ,
763+ ) -> None :
764+ """Shared helper for uploading datasets and graphs to DeepFabric Cloud.
765+
766+ Args:
767+ file: Path to the file to upload
768+ resource_type: Either "dataset" or "graph"
769+ handle: Resource handle (e.g., username/resource-name)
770+ name: Display name for the resource
771+ description: Description for the resource
772+ tags: Tags for the resource (only used for datasets)
773+ config_file: Path to config file with upload settings
774+ """
775+ # Lazy imports to avoid slow startup
776+ import httpx # noqa: PLC0415
777+
778+ from .auth import DEFAULT_API_URL # noqa: PLC0415
779+ from .cloud_upload import ( # noqa: PLC0415
780+ _get_user_friendly_error ,
781+ build_urls ,
782+ derive_frontend_url ,
783+ derive_name_and_slug ,
784+ ensure_authenticated ,
785+ get_current_user ,
786+ upload_dataset ,
787+ upload_topic_graph ,
788+ )
789+
790+ tui = get_tui ()
791+ config_key = resource_type # "dataset" or "graph"
792+ url_resource_type = "datasets" if resource_type == "dataset" else "graphs"
793+
794+ # Load handle from config if not provided via CLI
795+ final_handle = handle
796+ final_description = description or ""
797+ final_tags = list (tags ) if tags else []
798+
799+ if config_file :
800+ config = DeepFabricConfig .from_yaml (config_file )
801+ cloud_config = config .get_deepfabric_cloud_config ()
802+ if not final_handle :
803+ final_handle = cloud_config .get (config_key )
804+ if not description and cloud_config .get ("description" ):
805+ final_description = cloud_config .get ("description" , "" )
806+ if resource_type == "dataset" and not tags and cloud_config .get ("tags" ):
807+ final_tags = cloud_config .get ("tags" , [])
808+
809+ # Ensure authenticated
810+ if not ensure_authenticated (DEFAULT_API_URL , headless = False ):
811+ tui .error ("Authentication required. Run 'deepfabric auth login' first." )
812+ sys .exit (1 )
813+
814+ # Derive name and slug from filename if not provided
815+ default_name , default_slug = derive_name_and_slug (file )
816+ final_name = name or default_name
817+
818+ # Use slug from handle if provided, otherwise use derived slug
819+ if final_handle and "/" in final_handle :
820+ final_slug = final_handle .split ("/" )[- 1 ]
821+ else :
822+ final_slug = final_handle or default_slug
823+
824+ tui .info (f"Uploading { resource_type } '{ final_name } '..." )
825+
826+ try :
827+ # Call the appropriate upload function
828+ if resource_type == "dataset" :
829+ result = upload_dataset (
830+ dataset_path = file ,
831+ name = final_name ,
832+ slug = final_slug ,
833+ description = final_description ,
834+ tags = final_tags ,
835+ api_url = DEFAULT_API_URL ,
836+ )
837+ resource_id = result .get ("dataset_id" ) or result .get ("id" )
838+ else :
839+ result = upload_topic_graph (
840+ graph_path = file ,
841+ name = final_name ,
842+ description = final_description ,
843+ slug = final_slug ,
844+ api_url = DEFAULT_API_URL ,
845+ )
846+ resource_id = result .get ("id" )
847+
848+ # Display success message
849+ tui .success (f"{ resource_type .capitalize ()} '{ final_name } ' uploaded successfully!" )
850+
851+ # Display URL if available
852+ if resource_id :
853+ user_info = get_current_user (DEFAULT_API_URL )
854+ username = user_info .get ("username" ) if user_info else None
855+ frontend_url = derive_frontend_url (DEFAULT_API_URL )
856+ public_url , internal_url = build_urls (
857+ url_resource_type , resource_id , final_slug , username , frontend_url
858+ )
859+ tui .info (f"View at: { public_url or internal_url } " )
860+
861+ except httpx .HTTPStatusError as e :
862+ error_msg = _get_user_friendly_error (e )
863+ if "already exists" in error_msg .lower ():
864+ tui .error (
865+ f"A { resource_type } with slug '{ final_slug } ' already exists. "
866+ "Use a different --handle value."
867+ )
868+ else :
869+ tui .error (f"Error uploading { resource_type } : { error_msg } " )
870+ sys .exit (1 )
871+ except Exception as e :
872+ tui .error (f"Error uploading { resource_type } : { str (e )} " )
873+ sys .exit (1 )
874+
875+
876+ @upload .command ("dataset" )
877+ @click .argument ("file" , type = click .Path (exists = True ))
878+ @click .option ("--handle" , help = "Dataset handle (e.g., username/dataset-name)" )
879+ @click .option ("--name" , help = "Display name for the dataset" )
880+ @click .option ("--description" , help = "Description for the dataset" )
881+ @click .option (
882+ "--tags" , multiple = True , help = "Tags for the dataset (can be specified multiple times)"
883+ )
884+ @click .option (
885+ "--config" ,
886+ "config_file" ,
887+ type = click .Path (exists = True ),
888+ help = "Config file with upload settings" ,
889+ )
890+ def upload_dataset_cmd (
891+ file : str ,
892+ handle : str | None ,
893+ name : str | None ,
894+ description : str | None ,
895+ tags : tuple [str , ...],
896+ config_file : str | None ,
897+ ) -> None :
898+ """Upload a dataset to DeepFabric Cloud.
899+
900+ FILE is the path to the JSONL dataset file.
901+
902+ Examples:
903+
904+ deepfabric upload dataset my-dataset.jsonl --handle myuser/my-dataset
905+
906+ deepfabric upload dataset output.jsonl --config config.yaml
907+ """
908+ trace (
909+ "cli_upload_dataset" ,
910+ {"has_config" : config_file is not None , "has_handle" : handle is not None },
911+ )
912+ _upload_to_cloud (
913+ file = file ,
914+ resource_type = "dataset" ,
915+ handle = handle ,
916+ name = name ,
917+ description = description ,
918+ tags = list (tags ) if tags else None ,
919+ config_file = config_file ,
920+ )
921+
922+
923+ @upload .command ("graph" )
924+ @click .argument ("file" , type = click .Path (exists = True ))
925+ @click .option ("--handle" , help = "Graph handle (e.g., username/graph-name)" )
926+ @click .option ("--name" , help = "Display name for the graph" )
927+ @click .option ("--description" , help = "Description for the graph" )
928+ @click .option (
929+ "--config" ,
930+ "config_file" ,
931+ type = click .Path (exists = True ),
932+ help = "Config file with upload settings" ,
933+ )
934+ def upload_graph_cmd (
935+ file : str ,
936+ handle : str | None ,
937+ name : str | None ,
938+ description : str | None ,
939+ config_file : str | None ,
940+ ) -> None :
941+ """Upload a topic graph to DeepFabric Cloud.
942+
943+ FILE is the path to the JSON graph file.
944+
945+ Examples:
946+
947+ deepfabric upload graph topic_graph.json --handle myuser/my-graph
948+
949+ deepfabric upload graph graph.json --config config.yaml
950+ """
951+ trace (
952+ "cli_upload_graph" ,
953+ {"has_config" : config_file is not None , "has_handle" : handle is not None },
954+ )
955+ _upload_to_cloud (
956+ file = file ,
957+ resource_type = "graph" ,
958+ handle = handle ,
959+ name = name ,
960+ description = description ,
961+ tags = None ,
962+ config_file = config_file ,
963+ )
964+
965+
748966@cli .command ()
749967@click .argument ("graph_file" , type = click .Path (exists = True ))
750968@click .option (
@@ -1152,10 +1370,11 @@ def evaluate(
11521370 handle_error (click .get_current_context (), e )
11531371
11541372
1155- # Register the auth command group
1373+ # Register the auth and upload command groups
11561374# EXPERIMENTAL: Only enable cloud features if explicitly opted in
11571375if get_bool_env ("EXPERIMENTAL_DF" ):
11581376 cli .add_command (auth_group )
1377+ cli .add_command (upload )
11591378
11601379
11611380@cli .command ("import-tools" )
0 commit comments