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 )} candidate sources for repository: { repository_name } " )
80- return {
81- " repository_name" : repository_name ,
82- " message" : f"No exact match found for '{ repository_name } ', but found { len (result )} potential matches." ,
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 } ', but found { len (result )} potential matches." ,
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 } ' (without organization prefix)" ,
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 } ' (without organization prefix)" ,
149+ )
114150 elif isinstance (fallback_result , list ) and len (fallback_result ) > 0 :
115151 logger .info (f"Found { len (fallback_result )} candidates using repo name only" )
116- return {
117- " repository_name" : repository_name ,
118- " message" : f"No exact match for '{ repository_name } ', but found { len (fallback_result )} potential matches using repo name '{ 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 } ', but found { len (fallback_result )} potential matches using repo name '{ 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 } ' not found in GitGuardian" ,
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 } ' not found in GitGuardian" ,
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