1818from basic_memory .cli .commands .cloud .rclone_installer import RcloneInstallError , install_rclone
1919from basic_memory .config import ConfigManager
2020from basic_memory .ignore_utils import get_bmignore_path , create_default_bmignore
21+ from basic_memory .schemas .cloud import (
22+ TenantMountInfo ,
23+ MountCredentials ,
24+ CloudProjectList ,
25+ CloudProjectCreateRequest ,
26+ CloudProjectCreateResponse ,
27+ )
28+ from basic_memory .utils import generate_permalink
2129
2230console = Console ()
2331
@@ -74,7 +82,7 @@ def __init__(
7482}
7583
7684
77- async def get_mount_info () -> dict :
85+ async def get_mount_info () -> TenantMountInfo :
7886 """Get current tenant information from cloud API."""
7987 try :
8088 config_manager = ConfigManager ()
@@ -83,12 +91,12 @@ async def get_mount_info() -> dict:
8391
8492 response = await make_api_request (method = "GET" , url = f"{ host_url } /tenant/mount/info" )
8593
86- return response .json ()
94+ return TenantMountInfo . model_validate ( response .json () )
8795 except Exception as e :
8896 raise BisyncError (f"Failed to get tenant info: { e } " ) from e
8997
9098
91- async def generate_mount_credentials (tenant_id : str ) -> dict :
99+ async def generate_mount_credentials (tenant_id : str ) -> MountCredentials :
92100 """Generate scoped credentials for syncing."""
93101 try :
94102 config_manager = ConfigManager ()
@@ -97,16 +105,16 @@ async def generate_mount_credentials(tenant_id: str) -> dict:
97105
98106 response = await make_api_request (method = "POST" , url = f"{ host_url } /tenant/mount/credentials" )
99107
100- return response .json ()
108+ return MountCredentials . model_validate ( response .json () )
101109 except Exception as e :
102110 raise BisyncError (f"Failed to generate credentials: { e } " ) from e
103111
104112
105- async def fetch_cloud_projects () -> dict :
113+ async def fetch_cloud_projects () -> CloudProjectList :
106114 """Fetch list of projects from cloud API.
107115
108116 Returns:
109- Dict with ' projects' list from cloud
117+ CloudProjectList with projects from cloud
110118 """
111119 try :
112120 config_manager = ConfigManager ()
@@ -115,7 +123,7 @@ async def fetch_cloud_projects() -> dict:
115123
116124 response = await make_api_request (method = "GET" , url = f"{ host_url } /proxy/projects/projects" )
117125
118- return response .json ()
126+ return CloudProjectList . model_validate ( response .json () )
119127 except Exception as e :
120128 raise BisyncError (f"Failed to fetch cloud projects: { e } " ) from e
121129
@@ -140,39 +148,37 @@ def scan_local_directories(sync_dir: Path) -> list[str]:
140148 return directories
141149
142150
143- async def create_cloud_project (project_name : str ) -> dict :
151+ async def create_cloud_project (project_name : str ) -> CloudProjectCreateResponse :
144152 """Create a new project on cloud.
145153
146154 Args:
147155 project_name: Name of project to create
148156
149157 Returns:
150- Project details from API response
158+ CloudProjectCreateResponse with project details from API
151159 """
152160 try :
153161 config_manager = ConfigManager ()
154162 config = config_manager .config
155163 host_url = config .cloud_host .rstrip ("/" )
156164
157165 # Use generate_permalink to ensure consistent naming
158- from basic_memory .utils import generate_permalink
159-
160166 project_path = generate_permalink (project_name )
161167
162- project_data = {
163- " name" : project_name ,
164- " path" : project_path ,
165- " set_default" : False ,
166- }
168+ project_data = CloudProjectCreateRequest (
169+ name = project_name ,
170+ path = project_path ,
171+ set_default = False ,
172+ )
167173
168174 response = await make_api_request (
169175 method = "POST" ,
170176 url = f"{ host_url } /proxy/projects/projects" ,
171177 headers = {"Content-Type" : "application/json" },
172- json_data = project_data ,
178+ json_data = project_data . model_dump () ,
173179 )
174180
175- return response .json ()
181+ return CloudProjectCreateResponse . model_validate ( response .json () )
176182 except Exception as e :
177183 raise BisyncError (f"Failed to create cloud project '{ project_name } ': { e } " ) from e
178184
@@ -364,11 +370,8 @@ def setup_cloud_bisync(sync_dir: Optional[str] = None) -> None:
364370 console .print ("\n [blue]Step 2: Getting tenant information...[/blue]" )
365371 tenant_info = asyncio .run (get_mount_info ())
366372
367- tenant_id = tenant_info .get ("tenant_id" )
368- bucket_name = tenant_info .get ("bucket_name" )
369-
370- if not tenant_id or not bucket_name :
371- raise BisyncError ("Invalid tenant information received from cloud API" )
373+ tenant_id = tenant_info .tenant_id
374+ bucket_name = tenant_info .bucket_name
372375
373376 console .print (f"[green]✓ Found tenant: { tenant_id } [/green]" )
374377 console .print (f"[green]✓ Bucket: { bucket_name } [/green]" )
@@ -377,11 +380,8 @@ def setup_cloud_bisync(sync_dir: Optional[str] = None) -> None:
377380 console .print ("\n [blue]Step 3: Generating sync credentials...[/blue]" )
378381 creds = asyncio .run (generate_mount_credentials (tenant_id ))
379382
380- access_key = creds .get ("access_key" )
381- secret_key = creds .get ("secret_key" )
382-
383- if not access_key or not secret_key :
384- raise BisyncError ("Failed to generate credentials" )
383+ access_key = creds .access_key
384+ secret_key = creds .secret_key
385385
386386 console .print ("[green]✓ Generated secure credentials[/green]" )
387387
@@ -459,11 +459,8 @@ def run_bisync(
459459 # Get tenant info if not provided
460460 if not tenant_id or not bucket_name :
461461 tenant_info = asyncio .run (get_mount_info ())
462- tenant_id = tenant_info .get ("tenant_id" )
463- bucket_name = tenant_info .get ("bucket_name" )
464-
465- if not tenant_id or not bucket_name :
466- raise BisyncError ("Could not determine tenant information" )
462+ tenant_id = tenant_info .tenant_id
463+ bucket_name = tenant_info .bucket_name
467464
468465 # Set default local path if not provided
469466 if not local_path :
@@ -494,17 +491,17 @@ def run_bisync(
494491
495492 # Fetch cloud projects and extract directory names from paths
496493 cloud_data = asyncio .run (fetch_cloud_projects ())
497- cloud_projects = cloud_data .get ( " projects" , [])
494+ cloud_projects = cloud_data .projects
498495
499496 # Extract directory names from cloud project paths
500497 # Compare directory names, not project names
501498 # Cloud path /app/data/basic-memory -> directory name "basic-memory"
502499 cloud_dir_names = set ()
503500 for p in cloud_projects :
504- path = p [ " path" ]
501+ path = p . path
505502 # Strip /app/data/ prefix if present (cloud mode)
506503 if path .startswith ("/app/data/" ):
507- path = path [len ("/app/data/" ):]
504+ path = path [len ("/app/data/" ) :]
508505 # Get the last segment (directory name)
509506 dir_name = Path (path ).name
510507 cloud_dir_names .add (dir_name )
@@ -546,7 +543,13 @@ def run_bisync(
546543
547544 # Build and execute bisync command
548545 bisync_cmd = build_bisync_command (
549- tenant_id , bucket_name , local_path , profile , dry_run = dry_run , resync = resync , verbose = verbose
546+ tenant_id ,
547+ bucket_name ,
548+ local_path ,
549+ profile ,
550+ dry_run = dry_run ,
551+ resync = resync ,
552+ verbose = verbose ,
550553 )
551554
552555 if dry_run :
@@ -595,18 +598,16 @@ async def notify_container_sync(tenant_id: str) -> None:
595598
596599 # Fetch all projects and sync each one
597600 cloud_data = await fetch_cloud_projects ()
598- projects = cloud_data .get ( " projects" , [])
601+ projects = cloud_data .projects
599602
600603 if not projects :
601604 console .print ("[dim]No projects to sync[/dim]" )
602605 return
603606
604- console .print (
605- f"[blue]Notifying cloud to index { len (projects )} project(s)...[/blue]"
606- )
607+ console .print (f"[blue]Notifying cloud to index { len (projects )} project(s)...[/blue]" )
607608
608609 for project in projects :
609- project_name = project .get ( " name" )
610+ project_name = project .name
610611 if project_name :
611612 try :
612613 await run_sync (project = project_name )
@@ -669,11 +670,7 @@ def show_bisync_status() -> None:
669670 try :
670671 # Get tenant info
671672 tenant_info = asyncio .run (get_mount_info ())
672- tenant_id = tenant_info .get ("tenant_id" )
673-
674- if not tenant_id :
675- console .print ("[red]Could not determine tenant ID[/red]" )
676- return
673+ tenant_id = tenant_info .tenant_id
677674
678675 local_path = get_bisync_directory ()
679676 state_path = get_bisync_state_path (tenant_id )
@@ -753,11 +750,8 @@ def run_check(
753750 # Get tenant info if not provided
754751 if not tenant_id or not bucket_name :
755752 tenant_info = asyncio .run (get_mount_info ())
756- tenant_id = tenant_id or tenant_info .get ("tenant_id" )
757- bucket_name = bucket_name or tenant_info .get ("bucket_name" )
758-
759- if not tenant_id or not bucket_name :
760- raise BisyncError ("Could not determine tenant_id or bucket_name" )
753+ tenant_id = tenant_id or tenant_info .tenant_id
754+ bucket_name = bucket_name or tenant_info .bucket_name
761755
762756 # Get local path from config
763757 if not local_path :
0 commit comments