11from  typing  import  Any 
22import  logging 
33import  subprocess 
4+ from  pydantic  import  BaseModel , Field 
45from  gg_api_core .utils  import  get_client , parse_repo_url 
56
67logger  =  logging .getLogger (__name__ )
78
89
10+ class  SourceCandidate (BaseModel ):
11+     """A candidate source that might match the repository.""" 
12+     id : str  =  Field (description = "Source ID" )
13+     url : str  |  None  =  Field (default = None , description = "Repository URL" )
14+     name : str  |  None  =  Field (default = None , description = "Repository name" )
15+     monitored : bool  |  None  =  Field (default = None , description = "Whether source is monitored" )
16+     deleted_at : str  |  None  =  Field (default = None , description = "Deletion timestamp if deleted" )
917
10- async  def  find_current_source_id () ->  dict [str , Any ]:
18+ 
19+ class  FindCurrentSourceIdResult (BaseModel ):
20+     """Successful result from finding source ID.""" 
21+     repository_name : str  =  Field (description = "Detected repository name" )
22+     source_id : str  |  None  =  Field (default = None , description = "GitGuardian source ID (if exact match)" )
23+     source : dict [str , Any ] |  None  =  Field (default = None , description = "Full source information (if exact match)" )
24+     message : str  |  None  =  Field (default = None , description = "Status or informational message" )
25+     suggestion : str  |  None  =  Field (default = None , description = "Suggestions for next steps" )
26+     candidates : list [SourceCandidate ] |  None  =  Field (default = None , description = "List of candidate sources (if no exact match)" )
27+ 
28+ 
29+ class  FindCurrentSourceIdError (BaseModel ):
30+     """Error result from finding source ID.""" 
31+     error : str  =  Field (description = "Error message" )
32+     repository_name : str  |  None  =  Field (default = None , description = "Repository name if detected" )
33+     details : str  |  None  =  Field (default = None , description = "Additional error details" )
34+     message : str  |  None  =  Field (default = None , description = "User-friendly message" )
35+     suggestion : str  |  None  =  Field (default = None , description = "Suggestions for resolving the error" )
36+ 
37+ 
38+ async  def  find_current_source_id () ->  FindCurrentSourceIdResult  |  FindCurrentSourceIdError :
1139    """ 
1240    Find the GitGuardian source_id for the current repository. 
1341
@@ -19,12 +47,20 @@ async def find_current_source_id() -> dict[str, Any]:
1947    5. If no exact match, returns all search results for the model to choose from 
2048
2149    Returns: 
22-         A dictionary containing: 
23-         - repository_name: The detected repository name 
24-         - source_id: The GitGuardian source ID (if exact match found) 
25-         - source: Full source information from GitGuardian (if exact match found) 
26-         - candidates: List of candidate sources (if no exact match but potential matches found) 
27-         - error: Error message if something went wrong 
50+         FindCurrentSourceIdResult: Pydantic model containing: 
51+             - repository_name: The detected repository name 
52+             - source_id: The GitGuardian source ID (if exact match found) 
53+             - source: Full source information from GitGuardian (if exact match found) 
54+             - message: Status or informational message 
55+             - suggestion: Suggestions for next steps 
56+             - candidates: List of SourceCandidate objects (if no exact match but potential matches found) 
57+ 
58+         FindCurrentSourceIdError: Pydantic model containing: 
59+             - error: Error message 
60+             - repository_name: Repository name if detected 
61+             - details: Additional error details 
62+             - message: User-friendly message 
63+             - suggestion: Suggestions for resolving the error 
2864    """ 
2965    client  =  get_client ()
3066    logger .debug ("Finding source_id for current repository" )
@@ -42,21 +78,21 @@ async def find_current_source_id() -> dict[str, Any]:
4278            remote_url  =  result .stdout .strip ()
4379            logger .debug (f"Found remote URL: { remote_url }  )
4480        except  subprocess .CalledProcessError  as  e :
45-             return  { 
46-                 " error" :  "Not a git repository or no remote 'origin' configured" ,
47-                 " details" :  str (e ),
48-             } 
81+             return  FindCurrentSourceIdError ( 
82+                 error = "Not a git repository or no remote 'origin' configured" ,
83+                 details = str (e ),
84+             ) 
4985        except  subprocess .TimeoutExpired :
50-             return  { " error" :  " Git command timed out"} 
86+             return  FindCurrentSourceIdError ( error = " Git command timed out") 
5187
5288        # Parse repository name from remote URL 
5389        repository_name  =  parse_repo_url (remote_url )
5490
5591        if  not  repository_name :
56-             return  { 
57-                 " error" :  f"Could not parse repository URL: { remote_url }  ,
58-                 " details" :  "The URL format is not recognized. Supported platforms: GitHub, GitLab (Cloud & Self-hosted), Bitbucket (Cloud & Data Center), Azure DevOps" ,
59-             } 
92+             return  FindCurrentSourceIdError ( 
93+                 error = f"Could not parse repository URL: { remote_url }  ,
94+                 details = "The URL format is not recognized. Supported platforms: GitHub, GitLab (Cloud & Self-hosted), Bitbucket (Cloud & Data Center), Azure DevOps" ,
95+             ) 
6096
6197        logger .info (f"Detected repository name: { repository_name }  )
6298
@@ -67,31 +103,31 @@ async def find_current_source_id() -> dict[str, Any]:
67103        if  isinstance (result , dict ):
68104            source_id  =  result .get ("id" )
69105            logger .info (f"Found exact match with source_id: { source_id }  )
70-             return  { 
71-                 " repository_name" :  repository_name ,
72-                 " source_id" :  source_id ,
73-                 " source" :  result ,
74-                 " message" :  f"Successfully found exact match for GitGuardian source: { repository_name }  ,
75-             } 
106+             return  FindCurrentSourceIdResult ( 
107+                 repository_name = repository_name ,
108+                 source_id = source_id ,
109+                 source = result ,
110+                 message = f"Successfully found exact match for GitGuardian source: { repository_name }  ,
111+             ) 
76112
77113        # Handle multiple candidates (list result) 
78114        elif  isinstance (result , list ) and  len (result ) >  0 :
79115            logger .info (f"Found { len (result )} { repository_name }  )
80-             return  { 
81-                 " repository_name" :  repository_name ,
82-                 " message" :  f"No exact match found for '{ repository_name } { len (result )}  ,
83-                 " suggestion" :  "Review the candidates below and determine which source best matches the current repository based on the name and URL." ,
84-                 " candidates" :  [
85-                     { 
86-                         "id" :  source .get ("id" ),
87-                         " url" :  source .get ("url" ),
88-                         " name" :  source .get ("full_name" ) or  source .get ("name" ),
89-                         " monitored" :  source .get ("monitored" ),
90-                         " deleted_at" :  source .get ("deleted_at" ),
91-                     } 
116+             return  FindCurrentSourceIdResult ( 
117+                 repository_name = repository_name ,
118+                 message = f"No exact match found for '{ repository_name } { len (result )}  ,
119+                 suggestion = "Review the candidates below and determine which source best matches the current repository based on the name and URL." ,
120+                 candidates = [
121+                     SourceCandidate ( 
122+                         id = source .get ("id" ),
123+                         url = source .get ("url" ),
124+                         name = source .get ("full_name" ) or  source .get ("name" ),
125+                         monitored = source .get ("monitored" ),
126+                         deleted_at = source .get ("deleted_at" ),
127+                     ) 
92128                    for  source  in  result 
93129                ],
94-             } 
130+             ) 
95131
96132        # No matches found at all 
97133        else :
@@ -105,39 +141,39 @@ async def find_current_source_id() -> dict[str, Any]:
105141                if  isinstance (fallback_result , dict ):
106142                    source_id  =  fallback_result .get ("id" )
107143                    logger .info (f"Found match using repo name only, source_id: { source_id }  )
108-                     return  { 
109-                         " repository_name" :  repository_name ,
110-                         " source_id" :  source_id ,
111-                         " source" :  fallback_result ,
112-                         " message" :  f"Found match using repository name '{ repo_only }  ,
113-                     } 
144+                     return  FindCurrentSourceIdResult ( 
145+                         repository_name = repository_name ,
146+                         source_id = source_id ,
147+                         source = fallback_result ,
148+                         message = f"Found match using repository name '{ repo_only }  ,
149+                     ) 
114150                elif  isinstance (fallback_result , list ) and  len (fallback_result ) >  0 :
115151                    logger .info (f"Found { len (fallback_result )}  )
116-                     return  { 
117-                         " repository_name" :  repository_name ,
118-                         " message" :  f"No exact match for '{ repository_name } { len (fallback_result )} { repo_only }  ,
119-                         " suggestion" :  "Review the candidates below and determine which source best matches the current repository." ,
120-                         " candidates" :  [
121-                             { 
122-                                 "id" :  source .get ("id" ),
123-                                 " url" :  source .get ("url" ),
124-                                 " name" :  source .get ("full_name" ) or  source .get ("name" ),
125-                                 " monitored" :  source .get ("monitored" ),
126-                                 " deleted_at" :  source .get ("deleted_at" ),
127-                             } 
152+                     return  FindCurrentSourceIdResult ( 
153+                         repository_name = repository_name ,
154+                         message = f"No exact match for '{ repository_name } { len (fallback_result )} { repo_only }  ,
155+                         suggestion = "Review the candidates below and determine which source best matches the current repository." ,
156+                         candidates = [
157+                             SourceCandidate ( 
158+                                 id = source .get ("id" ),
159+                                 url = source .get ("url" ),
160+                                 name = source .get ("full_name" ) or  source .get ("name" ),
161+                                 monitored = source .get ("monitored" ),
162+                                 deleted_at = source .get ("deleted_at" ),
163+                             ) 
128164                            for  source  in  fallback_result 
129165                        ],
130-                     } 
166+                     ) 
131167
132168            # Absolutely no matches found 
133169            logger .warning (f"No sources found for repository: { repository_name }  )
134-             return  { 
135-                 " repository_name" :  repository_name ,
136-                 " error" :  f"Repository '{ repository_name }  ,
137-                 " message" :  "The repository may not be connected to GitGuardian, or you may not have access to it." ,
138-                 " suggestion" :  "Check that the repository is properly connected to GitGuardian and that your account has access to it." ,
139-             } 
170+             return  FindCurrentSourceIdError ( 
171+                 repository_name = repository_name ,
172+                 error = f"Repository '{ repository_name }  ,
173+                 message = "The repository may not be connected to GitGuardian, or you may not have access to it." ,
174+                 suggestion = "Check that the repository is properly connected to GitGuardian and that your account has access to it." ,
175+             ) 
140176
141177    except  Exception  as  e :
142178        logger .error (f"Error finding source_id: { str (e )}  )
143-         return  { " error" :  f"Failed to find source_id: { str (e )}  } 
179+         return  FindCurrentSourceIdError ( error = f"Failed to find source_id: { str (e )}  ) 
0 commit comments