@@ -135,14 +135,19 @@ class _Bridges(pydantic.RootModel[list[Bridge]]):
135135 pass
136136
137137
138- def _translate (exc : httpware .ClientError , * , url : str ) -> Exception :
138+ Resource : typing .TypeAlias = typing .Literal [
139+ "group" , "project" , "deployments" , "pipelines" , "repository" , "merge_requests"
140+ ]
141+
142+
143+ def _translate (exc : httpware .ClientError , * , url : str , resource : Resource ) -> Exception :
139144 if isinstance (exc , httpware .UnauthorizedError ):
140145 return AuthError ("GitLab rejected the token (401). Check that it is valid and not expired." )
141146 if isinstance (exc , httpware .StatusError ):
142147 status : typing .Final = exc .response .status_code
143- return GitLabError (f"GitLab returned { status } for { unquote (url )} ." , path = url , status = status )
148+ return GitLabError (f"GitLab returned { status } for { unquote (url )} ." , resource = resource , status = status )
144149 reason : typing .Final = type (exc ).__name__
145- return GitLabError (f"GitLab request { unquote (url )} failed: { reason } ." , path = url , reason = reason )
150+ return GitLabError (f"GitLab request { unquote (url )} failed: { reason } ." , resource = resource , reason = reason )
146151
147152
148153def _quote (value : str ) -> str :
@@ -153,18 +158,19 @@ def _quote(value: str) -> str:
153158class GitLabApi :
154159 http : httpware .Client
155160
156- def _get (self , url : str , params : dict [str , typing .Any ], model : type [_ModelT ]) -> _ModelT :
161+ def _get (self , url : str , params : dict [str , typing .Any ], model : type [_ModelT ], * , resource : Resource ) -> _ModelT :
157162 try :
158163 return self .http .get (url , params = params , response_model = model )
159164 except httpware .ClientError as exc :
160- raise _translate (exc , url = url ) from exc
165+ raise _translate (exc , url = url , resource = resource ) from exc
161166
162167 def _pages (
163168 self ,
164169 url : str ,
165170 params : dict [str , typing .Any ],
166171 model : type [pydantic .RootModel [list [_ModelT ]]],
167172 * ,
173+ resource : Resource ,
168174 max_items : int | None = None ,
169175 ) -> tuple [list [_ModelT ], bool ]:
170176 max_pages : typing .Final = _MAX_PAGES if max_items is None else math .ceil (max_items / _PER_PAGE )
@@ -175,7 +181,7 @@ def _pages(
175181 url , params = {** params , "per_page" : _PER_PAGE , "page" : page }, response_model = model
176182 )
177183 except httpware .ClientError as exc :
178- raise _translate (exc , url = url ) from exc
184+ raise _translate (exc , url = url , resource = resource ) from exc
179185 items .extend (batch .root )
180186 if not response .headers .get ("x-next-page" ):
181187 break
@@ -186,13 +192,14 @@ def _pages(
186192 return items , False
187193
188194 def get_project (self , path : str ) -> Project :
189- return self ._get (f"{ _API } /projects/{ _quote (path )} " , {}, Project )
195+ return self ._get (f"{ _API } /projects/{ _quote (path )} " , {}, Project , resource = "project" )
190196
191197 def list_group_projects (self , group : str , * , include_subgroups : bool ) -> list [Project ]:
192198 projects , _ = self ._pages (
193199 f"{ _API } /groups/{ _quote (group )} /projects" ,
194200 {"archived" : "false" , "with_shared" : "false" , "include_subgroups" : str (include_subgroups ).lower ()},
195201 _Projects ,
202+ resource = "group" ,
196203 )
197204 return projects
198205
@@ -201,11 +208,12 @@ def latest_deployment(self, project_id: int, environment: str) -> Deployment | N
201208 f"{ _API } /projects/{ project_id } /deployments" ,
202209 {"environment" : environment , "status" : "success" , "order_by" : "id" , "sort" : "desc" , "per_page" : 1 },
203210 _Deployments ,
211+ resource = "deployments" ,
204212 )
205213 return deployments .root [0 ] if deployments .root else None
206214
207215 def list_tags (self , project_id : int ) -> tuple [list [Tag ], bool ]:
208- return self ._pages (f"{ _API } /projects/{ project_id } /repository/tags" , {}, _Tags )
216+ return self ._pages (f"{ _API } /projects/{ project_id } /repository/tags" , {}, _Tags , resource = "repository" )
209217
210218 def list_first_parent_commits (
211219 self , project_id : int , ref_range : str , * , max_items : int
@@ -214,6 +222,7 @@ def list_first_parent_commits(
214222 f"{ _API } /projects/{ project_id } /repository/commits" ,
215223 {"ref_name" : ref_range , "first_parent" : "true" },
216224 _Commits ,
225+ resource = "repository" ,
217226 max_items = max_items ,
218227 )
219228
@@ -224,12 +233,16 @@ def list_merged_merge_requests(
224233 f"{ _API } /projects/{ project_id } /merge_requests" ,
225234 {"state" : "merged" , "target_branch" : target_branch , "updated_after" : updated_after .isoformat ()},
226235 _MergeRequests ,
236+ resource = "merge_requests" ,
227237 )
228238 return merge_requests
229239
230240 def commit_merge_requests (self , project_id : int , sha : str ) -> list [MergeRequest ]:
231241 merge_requests , _ = self ._pages (
232- f"{ _API } /projects/{ project_id } /repository/commits/{ sha } /merge_requests" , {}, _MergeRequests
242+ f"{ _API } /projects/{ project_id } /repository/commits/{ sha } /merge_requests" ,
243+ {},
244+ _MergeRequests ,
245+ resource = "merge_requests" ,
233246 )
234247 return merge_requests
235248
@@ -238,6 +251,7 @@ def list_push_pipelines(self, project_id: int, *, ref: str, updated_after: datet
238251 f"{ _API } /projects/{ project_id } /pipelines" ,
239252 {"ref" : ref , "source" : "push" , "updated_after" : updated_after .isoformat ()},
240253 _Pipelines ,
254+ resource = "pipelines" ,
241255 )
242256 return pipelines
243257
@@ -246,18 +260,25 @@ def latest_pipeline(self, project_id: int, *, ref: str) -> Pipeline | None:
246260 f"{ _API } /projects/{ project_id } /pipelines" ,
247261 {"ref" : ref , "order_by" : "id" , "sort" : "desc" , "per_page" : 1 },
248262 _Pipelines ,
263+ resource = "pipelines" ,
249264 )
250265 return pipelines .root [0 ] if pipelines .root else None
251266
252267 def failed_jobs (self , project_id : int , pipeline_id : int ) -> list [Job ]:
253268 jobs , _ = self ._pages (
254- f"{ _API } /projects/{ project_id } /pipelines/{ pipeline_id } /jobs" , {"scope[]" : "failed" }, _Jobs
269+ f"{ _API } /projects/{ project_id } /pipelines/{ pipeline_id } /jobs" ,
270+ {"scope[]" : "failed" },
271+ _Jobs ,
272+ resource = "pipelines" ,
255273 )
256274 return jobs
257275
258276 def failed_bridges (self , project_id : int , pipeline_id : int ) -> list [Bridge ]:
259277 # `trigger_jobs` replaces this route only from GitLab 19.2; older instances have `bridges` alone.
260278 bridges , _ = self ._pages (
261- f"{ _API } /projects/{ project_id } /pipelines/{ pipeline_id } /bridges" , {"scope[]" : "failed" }, _Bridges
279+ f"{ _API } /projects/{ project_id } /pipelines/{ pipeline_id } /bridges" ,
280+ {"scope[]" : "failed" },
281+ _Bridges ,
282+ resource = "pipelines" ,
262283 )
263284 return bridges
0 commit comments