recce-cloud 1.33.1__py3-none-any.whl → 1.34.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- recce_cloud/VERSION +1 -1
- recce_cloud/api/base.py +9 -9
- recce_cloud/api/client.py +257 -5
- recce_cloud/api/github.py +19 -19
- recce_cloud/api/gitlab.py +19 -19
- recce_cloud/ci_providers/base.py +8 -8
- recce_cloud/ci_providers/detector.py +17 -17
- recce_cloud/ci_providers/github_actions.py +8 -8
- recce_cloud/ci_providers/gitlab_ci.py +8 -8
- recce_cloud/cli.py +244 -86
- recce_cloud/commands/diagnostics.py +3 -3
- recce_cloud/config/project_config.py +10 -10
- recce_cloud/config/resolver.py +47 -19
- recce_cloud/delete.py +6 -6
- recce_cloud/download.py +5 -5
- recce_cloud/review.py +541 -0
- recce_cloud/services/diagnostic_service.py +7 -7
- recce_cloud/upload.py +5 -5
- {recce_cloud-1.33.1.dist-info → recce_cloud-1.34.0.dist-info}/METADATA +6 -6
- recce_cloud-1.34.0.dist-info/RECORD +38 -0
- recce_cloud-1.33.1.dist-info/RECORD +0 -37
- {recce_cloud-1.33.1.dist-info → recce_cloud-1.34.0.dist-info}/WHEEL +0 -0
- {recce_cloud-1.33.1.dist-info → recce_cloud-1.34.0.dist-info}/entry_points.txt +0 -0
recce_cloud/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.
|
|
1
|
+
1.34.0
|
recce_cloud/api/base.py
CHANGED
|
@@ -74,7 +74,7 @@ class BaseRecceCloudClient(ABC):
|
|
|
74
74
|
self,
|
|
75
75
|
branch: str,
|
|
76
76
|
adapter_type: str,
|
|
77
|
-
|
|
77
|
+
pr_number: Optional[int] = None,
|
|
78
78
|
commit_sha: Optional[str] = None,
|
|
79
79
|
session_type: Optional[str] = None,
|
|
80
80
|
) -> Dict:
|
|
@@ -84,9 +84,9 @@ class BaseRecceCloudClient(ABC):
|
|
|
84
84
|
Args:
|
|
85
85
|
branch: Branch name
|
|
86
86
|
adapter_type: DBT adapter type (e.g., 'postgres', 'snowflake', 'bigquery')
|
|
87
|
-
|
|
87
|
+
pr_number: Pull/Merge request number (PR/MR number) for PR sessions
|
|
88
88
|
commit_sha: Commit SHA (GitLab requires this)
|
|
89
|
-
session_type: Session type ("
|
|
89
|
+
session_type: Session type ("pr", "prod", "dev") - determines if pr_number is used
|
|
90
90
|
|
|
91
91
|
Returns:
|
|
92
92
|
Dictionary containing:
|
|
@@ -113,15 +113,15 @@ class BaseRecceCloudClient(ABC):
|
|
|
113
113
|
@abstractmethod
|
|
114
114
|
def get_session_download_urls(
|
|
115
115
|
self,
|
|
116
|
-
|
|
116
|
+
pr_number: Optional[int] = None,
|
|
117
117
|
session_type: Optional[str] = None,
|
|
118
118
|
) -> Dict:
|
|
119
119
|
"""
|
|
120
120
|
Get download URLs for artifacts from a session.
|
|
121
121
|
|
|
122
122
|
Args:
|
|
123
|
-
|
|
124
|
-
session_type: Session type ("
|
|
123
|
+
pr_number: Pull/Merge request number (PR/MR number) for PR sessions
|
|
124
|
+
session_type: Session type ("pr", "prod", "dev")
|
|
125
125
|
|
|
126
126
|
Returns:
|
|
127
127
|
Dictionary containing:
|
|
@@ -134,15 +134,15 @@ class BaseRecceCloudClient(ABC):
|
|
|
134
134
|
@abstractmethod
|
|
135
135
|
def delete_session(
|
|
136
136
|
self,
|
|
137
|
-
|
|
137
|
+
pr_number: Optional[int] = None,
|
|
138
138
|
session_type: Optional[str] = None,
|
|
139
139
|
) -> Dict:
|
|
140
140
|
"""
|
|
141
141
|
Delete a session.
|
|
142
142
|
|
|
143
143
|
Args:
|
|
144
|
-
|
|
145
|
-
session_type: Session type ("
|
|
144
|
+
pr_number: Pull/Merge request number (PR/MR number) for PR sessions
|
|
145
|
+
session_type: Session type ("pr", "prod") - "prod" deletes base session
|
|
146
146
|
|
|
147
147
|
Returns:
|
|
148
148
|
Dictionary containing:
|
recce_cloud/api/client.py
CHANGED
|
@@ -42,6 +42,24 @@ class RecceCloudClient:
|
|
|
42
42
|
}
|
|
43
43
|
return requests.request(method, url, headers=headers, **kwargs)
|
|
44
44
|
|
|
45
|
+
def _safe_get_error_detail(self, response, default: str) -> str:
|
|
46
|
+
"""Safely extract error detail from response JSON.
|
|
47
|
+
|
|
48
|
+
Some error responses may not have a valid JSON body (e.g., HTML error pages
|
|
49
|
+
from proxies), so we need to handle JSONDecodeError gracefully.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
response: The HTTP response object.
|
|
53
|
+
default: Default message to return if JSON parsing fails.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
The error detail string from the response, or the default message.
|
|
57
|
+
"""
|
|
58
|
+
try:
|
|
59
|
+
return response.json().get("detail", default)
|
|
60
|
+
except (json.JSONDecodeError, ValueError):
|
|
61
|
+
return default
|
|
62
|
+
|
|
45
63
|
def _replace_localhost_with_docker_internal(self, url: str) -> str:
|
|
46
64
|
"""Convert localhost URLs to docker internal URLs if running in Docker."""
|
|
47
65
|
if url is None:
|
|
@@ -75,7 +93,7 @@ class RecceCloudClient:
|
|
|
75
93
|
api_url = f"{self.base_url_v2}/sessions/{session_id}"
|
|
76
94
|
response = self._request("GET", api_url)
|
|
77
95
|
if response.status_code == 403:
|
|
78
|
-
return {"status": "error", "message":
|
|
96
|
+
return {"status": "error", "message": self._safe_get_error_detail(response, "Permission denied")}
|
|
79
97
|
if response.status_code != 200:
|
|
80
98
|
raise RecceCloudException(
|
|
81
99
|
reason=response.text,
|
|
@@ -181,7 +199,7 @@ class RecceCloudClient:
|
|
|
181
199
|
data = {"adapter_type": adapter_type}
|
|
182
200
|
response = self._request("PATCH", api_url, json=data)
|
|
183
201
|
if response.status_code == 403:
|
|
184
|
-
return {"status": "error", "message":
|
|
202
|
+
return {"status": "error", "message": self._safe_get_error_detail(response, "Permission denied")}
|
|
185
203
|
if response.status_code != 200:
|
|
186
204
|
raise RecceCloudException(
|
|
187
205
|
reason=response.text,
|
|
@@ -211,7 +229,7 @@ class RecceCloudClient:
|
|
|
211
229
|
return True
|
|
212
230
|
if response.status_code == 403:
|
|
213
231
|
raise RecceCloudException(
|
|
214
|
-
reason=
|
|
232
|
+
reason=self._safe_get_error_detail(response, "Permission denied"),
|
|
215
233
|
status_code=response.status_code,
|
|
216
234
|
)
|
|
217
235
|
if response.status_code == 404:
|
|
@@ -247,7 +265,7 @@ class RecceCloudClient:
|
|
|
247
265
|
return response.json()
|
|
248
266
|
if response.status_code == 403:
|
|
249
267
|
raise RecceCloudException(
|
|
250
|
-
reason=
|
|
268
|
+
reason=self._safe_get_error_detail(response, "Permission denied"),
|
|
251
269
|
status_code=response.status_code,
|
|
252
270
|
)
|
|
253
271
|
if response.status_code == 404:
|
|
@@ -488,7 +506,7 @@ class RecceCloudClient:
|
|
|
488
506
|
)
|
|
489
507
|
if response.status_code == 403:
|
|
490
508
|
raise RecceCloudException(
|
|
491
|
-
reason=
|
|
509
|
+
reason=self._safe_get_error_detail(response, "Permission denied"),
|
|
492
510
|
status_code=response.status_code,
|
|
493
511
|
)
|
|
494
512
|
if response.status_code not in [200, 201]:
|
|
@@ -503,6 +521,240 @@ class RecceCloudClient:
|
|
|
503
521
|
return result["session"]
|
|
504
522
|
return result
|
|
505
523
|
|
|
524
|
+
def check_prerequisites(
|
|
525
|
+
self,
|
|
526
|
+
org_id: str,
|
|
527
|
+
project_id: str,
|
|
528
|
+
session_id: str,
|
|
529
|
+
) -> Dict[str, Any]:
|
|
530
|
+
"""
|
|
531
|
+
Check prerequisites for data review generation.
|
|
532
|
+
|
|
533
|
+
This calls the backend API to verify:
|
|
534
|
+
1. Session exists and belongs to the project
|
|
535
|
+
2. Session has artifacts uploaded (adapter_type is set)
|
|
536
|
+
3. Base session exists for the project
|
|
537
|
+
4. Base session has artifacts uploaded
|
|
538
|
+
|
|
539
|
+
Args:
|
|
540
|
+
org_id: Organization ID or slug.
|
|
541
|
+
project_id: Project ID or slug.
|
|
542
|
+
session_id: Session ID to check.
|
|
543
|
+
|
|
544
|
+
Returns:
|
|
545
|
+
dict with:
|
|
546
|
+
- success: bool
|
|
547
|
+
- session_id: str
|
|
548
|
+
- session_name: str
|
|
549
|
+
- adapter_type: str or None
|
|
550
|
+
- has_base_session: bool
|
|
551
|
+
- base_session_has_artifacts: bool
|
|
552
|
+
- is_ready: bool
|
|
553
|
+
- reason: str or None (explains why not ready)
|
|
554
|
+
|
|
555
|
+
Raises:
|
|
556
|
+
RecceCloudException: If the API call fails.
|
|
557
|
+
"""
|
|
558
|
+
api_url = (
|
|
559
|
+
f"{self.base_url_v2}/organizations/{org_id}/projects/{project_id}/sessions/{session_id}/check-prerequisites"
|
|
560
|
+
)
|
|
561
|
+
response = self._request("GET", api_url)
|
|
562
|
+
|
|
563
|
+
if response.status_code == 404:
|
|
564
|
+
error_detail = "Session or project not found"
|
|
565
|
+
try:
|
|
566
|
+
error_detail = response.json().get("detail", error_detail)
|
|
567
|
+
except Exception:
|
|
568
|
+
pass
|
|
569
|
+
raise RecceCloudException(
|
|
570
|
+
reason=error_detail,
|
|
571
|
+
status_code=response.status_code,
|
|
572
|
+
)
|
|
573
|
+
if response.status_code == 400:
|
|
574
|
+
raise RecceCloudException(
|
|
575
|
+
reason=self._safe_get_error_detail(response, "Bad request"),
|
|
576
|
+
status_code=response.status_code,
|
|
577
|
+
)
|
|
578
|
+
if response.status_code == 403:
|
|
579
|
+
raise RecceCloudException(
|
|
580
|
+
reason=self._safe_get_error_detail(response, "Permission denied"),
|
|
581
|
+
status_code=response.status_code,
|
|
582
|
+
)
|
|
583
|
+
if response.status_code != 200:
|
|
584
|
+
raise RecceCloudException(
|
|
585
|
+
reason=response.text,
|
|
586
|
+
status_code=response.status_code,
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
return response.json()
|
|
590
|
+
|
|
591
|
+
def generate_data_review(
|
|
592
|
+
self,
|
|
593
|
+
org_id: str,
|
|
594
|
+
project_id: str,
|
|
595
|
+
session_id: str,
|
|
596
|
+
regenerate: bool = False,
|
|
597
|
+
) -> Dict[str, Any]:
|
|
598
|
+
"""
|
|
599
|
+
Trigger data review generation for a session.
|
|
600
|
+
|
|
601
|
+
Args:
|
|
602
|
+
org_id: Organization ID or slug.
|
|
603
|
+
project_id: Project ID or slug.
|
|
604
|
+
session_id: Session ID to generate review for.
|
|
605
|
+
regenerate: If True, regenerate even if a review already exists.
|
|
606
|
+
|
|
607
|
+
Returns:
|
|
608
|
+
dict with task_id if a new task was created, or empty if review already exists.
|
|
609
|
+
|
|
610
|
+
Raises:
|
|
611
|
+
RecceCloudException: If the API call fails.
|
|
612
|
+
"""
|
|
613
|
+
api_url = f"{self.base_url_v2}/organizations/{org_id}/projects/{project_id}/sessions/{session_id}/recce_summary"
|
|
614
|
+
data = {"regenerate": regenerate}
|
|
615
|
+
|
|
616
|
+
response = self._request("POST", api_url, json=data)
|
|
617
|
+
|
|
618
|
+
if response.status_code == 404:
|
|
619
|
+
raise RecceCloudException(
|
|
620
|
+
reason="Session not found",
|
|
621
|
+
status_code=response.status_code,
|
|
622
|
+
)
|
|
623
|
+
if response.status_code == 403:
|
|
624
|
+
raise RecceCloudException(
|
|
625
|
+
reason=self._safe_get_error_detail(response, "Permission denied"),
|
|
626
|
+
status_code=response.status_code,
|
|
627
|
+
)
|
|
628
|
+
if response.status_code == 400:
|
|
629
|
+
raise RecceCloudException(
|
|
630
|
+
reason=self._safe_get_error_detail(response, "Bad request"),
|
|
631
|
+
status_code=response.status_code,
|
|
632
|
+
)
|
|
633
|
+
if response.status_code not in [200, 201, 202]:
|
|
634
|
+
raise RecceCloudException(
|
|
635
|
+
reason=response.text,
|
|
636
|
+
status_code=response.status_code,
|
|
637
|
+
)
|
|
638
|
+
|
|
639
|
+
return response.json()
|
|
640
|
+
|
|
641
|
+
def get_data_review(
|
|
642
|
+
self,
|
|
643
|
+
org_id: str,
|
|
644
|
+
project_id: str,
|
|
645
|
+
session_id: str,
|
|
646
|
+
) -> Optional[Dict[str, Any]]:
|
|
647
|
+
"""
|
|
648
|
+
Get the existing data review for a session.
|
|
649
|
+
|
|
650
|
+
Args:
|
|
651
|
+
org_id: Organization ID or slug.
|
|
652
|
+
project_id: Project ID or slug.
|
|
653
|
+
session_id: Session ID to get review for.
|
|
654
|
+
|
|
655
|
+
Returns:
|
|
656
|
+
dict with session_id, session_name, summary (content), trace_id if found.
|
|
657
|
+
None if no review exists.
|
|
658
|
+
|
|
659
|
+
Raises:
|
|
660
|
+
RecceCloudException: If the API call fails.
|
|
661
|
+
"""
|
|
662
|
+
api_url = f"{self.base_url_v2}/organizations/{org_id}/projects/{project_id}/sessions/{session_id}/recce_summary"
|
|
663
|
+
response = self._request("GET", api_url)
|
|
664
|
+
|
|
665
|
+
if response.status_code == 404:
|
|
666
|
+
# No review exists for this session
|
|
667
|
+
return None
|
|
668
|
+
if response.status_code == 403:
|
|
669
|
+
raise RecceCloudException(
|
|
670
|
+
reason=self._safe_get_error_detail(response, "Permission denied"),
|
|
671
|
+
status_code=response.status_code,
|
|
672
|
+
)
|
|
673
|
+
if response.status_code != 200:
|
|
674
|
+
raise RecceCloudException(
|
|
675
|
+
reason=response.text,
|
|
676
|
+
status_code=response.status_code,
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
return response.json()
|
|
680
|
+
|
|
681
|
+
def get_running_task(
|
|
682
|
+
self,
|
|
683
|
+
org_id: str,
|
|
684
|
+
project_id: str,
|
|
685
|
+
session_id: str,
|
|
686
|
+
) -> Optional[Dict[str, Any]]:
|
|
687
|
+
"""
|
|
688
|
+
Get the currently running task for a session.
|
|
689
|
+
|
|
690
|
+
Args:
|
|
691
|
+
org_id: Organization ID or slug.
|
|
692
|
+
project_id: Project ID or slug.
|
|
693
|
+
session_id: Session ID to check.
|
|
694
|
+
|
|
695
|
+
Returns:
|
|
696
|
+
dict with task_id and status if a task is running, None otherwise.
|
|
697
|
+
|
|
698
|
+
Raises:
|
|
699
|
+
RecceCloudException: If the API call fails.
|
|
700
|
+
"""
|
|
701
|
+
api_url = f"{self.base_url_v2}/organizations/{org_id}/projects/{project_id}/sessions/{session_id}/running_task"
|
|
702
|
+
response = self._request("GET", api_url)
|
|
703
|
+
|
|
704
|
+
if response.status_code == 404:
|
|
705
|
+
return None
|
|
706
|
+
if response.status_code == 403:
|
|
707
|
+
raise RecceCloudException(
|
|
708
|
+
reason=self._safe_get_error_detail(response, "Permission denied"),
|
|
709
|
+
status_code=response.status_code,
|
|
710
|
+
)
|
|
711
|
+
if response.status_code != 200:
|
|
712
|
+
raise RecceCloudException(
|
|
713
|
+
reason=response.text,
|
|
714
|
+
status_code=response.status_code,
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
data = response.json()
|
|
718
|
+
# Return None if no task is running
|
|
719
|
+
if data.get("task_id") is None:
|
|
720
|
+
return None
|
|
721
|
+
return data
|
|
722
|
+
|
|
723
|
+
def get_task_status(self, org_id: str, task_id: str) -> Dict[str, Any]:
|
|
724
|
+
"""
|
|
725
|
+
Get the status of a task by ID.
|
|
726
|
+
|
|
727
|
+
Args:
|
|
728
|
+
org_id: Organization ID or slug.
|
|
729
|
+
task_id: Task ID to check.
|
|
730
|
+
|
|
731
|
+
Returns:
|
|
732
|
+
dict with id, command, status, created_at, started_at, finished_at, metadata.
|
|
733
|
+
|
|
734
|
+
Raises:
|
|
735
|
+
RecceCloudException: If the API call fails or task not found.
|
|
736
|
+
"""
|
|
737
|
+
api_url = f"{self.base_url_v2}/organizations/{org_id}/tasks/{task_id}/status"
|
|
738
|
+
response = self._request("GET", api_url)
|
|
739
|
+
|
|
740
|
+
if response.status_code == 404:
|
|
741
|
+
raise RecceCloudException(
|
|
742
|
+
reason="Task not found",
|
|
743
|
+
status_code=response.status_code,
|
|
744
|
+
)
|
|
745
|
+
if response.status_code == 403:
|
|
746
|
+
raise RecceCloudException(
|
|
747
|
+
reason=self._safe_get_error_detail(response, "Permission denied"),
|
|
748
|
+
status_code=response.status_code,
|
|
749
|
+
)
|
|
750
|
+
if response.status_code != 200:
|
|
751
|
+
raise RecceCloudException(
|
|
752
|
+
reason=response.text,
|
|
753
|
+
status_code=response.status_code,
|
|
754
|
+
)
|
|
755
|
+
|
|
756
|
+
return response.json()
|
|
757
|
+
|
|
506
758
|
|
|
507
759
|
class ReportClient:
|
|
508
760
|
"""Client for fetching reports from Recce Cloud API."""
|
recce_cloud/api/github.py
CHANGED
|
@@ -26,7 +26,7 @@ class GitHubRecceCloudClient(BaseRecceCloudClient):
|
|
|
26
26
|
self,
|
|
27
27
|
branch: str,
|
|
28
28
|
adapter_type: str,
|
|
29
|
-
|
|
29
|
+
pr_number: Optional[int] = None,
|
|
30
30
|
commit_sha: Optional[str] = None,
|
|
31
31
|
session_type: Optional[str] = None,
|
|
32
32
|
) -> Dict:
|
|
@@ -36,9 +36,9 @@ class GitHubRecceCloudClient(BaseRecceCloudClient):
|
|
|
36
36
|
Args:
|
|
37
37
|
branch: Branch name
|
|
38
38
|
adapter_type: DBT adapter type
|
|
39
|
-
|
|
39
|
+
pr_number: PR number for pull request sessions (None for prod sessions)
|
|
40
40
|
commit_sha: Not used for GitHub (optional for compatibility)
|
|
41
|
-
session_type: Session type ("
|
|
41
|
+
session_type: Session type ("pr", "prod", "dev") - determines if pr_number is passed
|
|
42
42
|
|
|
43
43
|
Returns:
|
|
44
44
|
Dictionary containing session_id, manifest_upload_url, catalog_upload_url
|
|
@@ -50,10 +50,10 @@ class GitHubRecceCloudClient(BaseRecceCloudClient):
|
|
|
50
50
|
"adapter_type": adapter_type,
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
# Only include pr_number for "
|
|
54
|
-
# For "prod" type, omit pr_number even if
|
|
55
|
-
if session_type == "
|
|
56
|
-
payload["pr_number"] =
|
|
53
|
+
# Only include pr_number for "pr" type sessions
|
|
54
|
+
# For "prod" type, omit pr_number even if pr_number is detected
|
|
55
|
+
if session_type == "pr" and pr_number is not None:
|
|
56
|
+
payload["pr_number"] = pr_number
|
|
57
57
|
|
|
58
58
|
return self._make_request("POST", url, json=payload)
|
|
59
59
|
|
|
@@ -78,15 +78,15 @@ class GitHubRecceCloudClient(BaseRecceCloudClient):
|
|
|
78
78
|
|
|
79
79
|
def get_session_download_urls(
|
|
80
80
|
self,
|
|
81
|
-
|
|
81
|
+
pr_number: Optional[int] = None,
|
|
82
82
|
session_type: Optional[str] = None,
|
|
83
83
|
) -> Dict:
|
|
84
84
|
"""
|
|
85
85
|
Get download URLs for artifacts from a GitHub session.
|
|
86
86
|
|
|
87
87
|
Args:
|
|
88
|
-
|
|
89
|
-
session_type: Session type ("
|
|
88
|
+
pr_number: PR number for pull request sessions
|
|
89
|
+
session_type: Session type ("pr", "prod", "dev")
|
|
90
90
|
|
|
91
91
|
Returns:
|
|
92
92
|
Dictionary containing session_id, manifest_url, catalog_url
|
|
@@ -99,23 +99,23 @@ class GitHubRecceCloudClient(BaseRecceCloudClient):
|
|
|
99
99
|
# For prod session, set base=true
|
|
100
100
|
if session_type == "prod":
|
|
101
101
|
params["base"] = "true"
|
|
102
|
-
# For
|
|
103
|
-
elif session_type == "
|
|
104
|
-
params["pr_number"] =
|
|
102
|
+
# For PR session, include pr_number
|
|
103
|
+
elif session_type == "pr" and pr_number is not None:
|
|
104
|
+
params["pr_number"] = pr_number
|
|
105
105
|
|
|
106
106
|
return self._make_request("GET", url, params=params)
|
|
107
107
|
|
|
108
108
|
def delete_session(
|
|
109
109
|
self,
|
|
110
|
-
|
|
110
|
+
pr_number: Optional[int] = None,
|
|
111
111
|
session_type: Optional[str] = None,
|
|
112
112
|
) -> Dict:
|
|
113
113
|
"""
|
|
114
114
|
Delete a GitHub PR/base session.
|
|
115
115
|
|
|
116
116
|
Args:
|
|
117
|
-
|
|
118
|
-
session_type: Session type ("
|
|
117
|
+
pr_number: PR number for pull request sessions
|
|
118
|
+
session_type: Session type ("pr", "prod") - "prod" deletes base session
|
|
119
119
|
|
|
120
120
|
Returns:
|
|
121
121
|
Dictionary containing session_id of deleted session
|
|
@@ -128,8 +128,8 @@ class GitHubRecceCloudClient(BaseRecceCloudClient):
|
|
|
128
128
|
# For prod session, set base=true
|
|
129
129
|
if session_type == "prod":
|
|
130
130
|
params["base"] = "true"
|
|
131
|
-
# For
|
|
132
|
-
elif session_type == "
|
|
133
|
-
params["pr_number"] =
|
|
131
|
+
# For PR session, include pr_number
|
|
132
|
+
elif session_type == "pr" and pr_number is not None:
|
|
133
|
+
params["pr_number"] = pr_number
|
|
134
134
|
|
|
135
135
|
return self._make_request("DELETE", url, params=params)
|
recce_cloud/api/gitlab.py
CHANGED
|
@@ -28,7 +28,7 @@ class GitLabRecceCloudClient(BaseRecceCloudClient):
|
|
|
28
28
|
self,
|
|
29
29
|
branch: str,
|
|
30
30
|
adapter_type: str,
|
|
31
|
-
|
|
31
|
+
pr_number: Optional[int] = None,
|
|
32
32
|
commit_sha: Optional[str] = None,
|
|
33
33
|
session_type: Optional[str] = None,
|
|
34
34
|
) -> Dict:
|
|
@@ -38,9 +38,9 @@ class GitLabRecceCloudClient(BaseRecceCloudClient):
|
|
|
38
38
|
Args:
|
|
39
39
|
branch: Branch name
|
|
40
40
|
adapter_type: DBT adapter type
|
|
41
|
-
|
|
41
|
+
pr_number: MR IID for merge request sessions (None for prod sessions)
|
|
42
42
|
commit_sha: Commit SHA (required for GitLab)
|
|
43
|
-
session_type: Session type ("
|
|
43
|
+
session_type: Session type ("pr", "prod", "dev") - determines if mr_iid is passed
|
|
44
44
|
|
|
45
45
|
Returns:
|
|
46
46
|
Dictionary containing session_id, manifest_upload_url, catalog_upload_url
|
|
@@ -54,10 +54,10 @@ class GitLabRecceCloudClient(BaseRecceCloudClient):
|
|
|
54
54
|
"repository_url": self.repository_url,
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
# Only include mr_iid for "
|
|
58
|
-
# For "prod" type, omit mr_iid even if
|
|
59
|
-
if session_type == "
|
|
60
|
-
payload["mr_iid"] =
|
|
57
|
+
# Only include mr_iid for "pr" type sessions
|
|
58
|
+
# For "prod" type, omit mr_iid even if pr_number is detected
|
|
59
|
+
if session_type == "pr" and pr_number is not None:
|
|
60
|
+
payload["mr_iid"] = pr_number
|
|
61
61
|
|
|
62
62
|
return self._make_request("POST", url, json=payload)
|
|
63
63
|
|
|
@@ -83,15 +83,15 @@ class GitLabRecceCloudClient(BaseRecceCloudClient):
|
|
|
83
83
|
|
|
84
84
|
def get_session_download_urls(
|
|
85
85
|
self,
|
|
86
|
-
|
|
86
|
+
pr_number: Optional[int] = None,
|
|
87
87
|
session_type: Optional[str] = None,
|
|
88
88
|
) -> Dict:
|
|
89
89
|
"""
|
|
90
90
|
Get download URLs for artifacts from a GitLab session.
|
|
91
91
|
|
|
92
92
|
Args:
|
|
93
|
-
|
|
94
|
-
session_type: Session type ("
|
|
93
|
+
pr_number: MR IID for merge request sessions
|
|
94
|
+
session_type: Session type ("pr", "prod", "dev")
|
|
95
95
|
|
|
96
96
|
Returns:
|
|
97
97
|
Dictionary containing session_id, manifest_url, catalog_url
|
|
@@ -104,23 +104,23 @@ class GitLabRecceCloudClient(BaseRecceCloudClient):
|
|
|
104
104
|
# For prod session, set base=true
|
|
105
105
|
if session_type == "prod":
|
|
106
106
|
params["base"] = "true"
|
|
107
|
-
# For
|
|
108
|
-
elif session_type == "
|
|
109
|
-
params["mr_iid"] =
|
|
107
|
+
# For PR session, include mr_iid
|
|
108
|
+
elif session_type == "pr" and pr_number is not None:
|
|
109
|
+
params["mr_iid"] = pr_number
|
|
110
110
|
|
|
111
111
|
return self._make_request("GET", url, params=params)
|
|
112
112
|
|
|
113
113
|
def delete_session(
|
|
114
114
|
self,
|
|
115
|
-
|
|
115
|
+
pr_number: Optional[int] = None,
|
|
116
116
|
session_type: Optional[str] = None,
|
|
117
117
|
) -> Dict:
|
|
118
118
|
"""
|
|
119
119
|
Delete a GitLab MR/base session.
|
|
120
120
|
|
|
121
121
|
Args:
|
|
122
|
-
|
|
123
|
-
session_type: Session type ("
|
|
122
|
+
pr_number: MR IID for merge request sessions
|
|
123
|
+
session_type: Session type ("pr", "prod") - "prod" deletes base session
|
|
124
124
|
|
|
125
125
|
Returns:
|
|
126
126
|
Dictionary containing session_id of deleted session
|
|
@@ -133,8 +133,8 @@ class GitLabRecceCloudClient(BaseRecceCloudClient):
|
|
|
133
133
|
# For prod session, set base=true
|
|
134
134
|
if session_type == "prod":
|
|
135
135
|
params["base"] = "true"
|
|
136
|
-
# For
|
|
137
|
-
elif session_type == "
|
|
138
|
-
params["mr_iid"] =
|
|
136
|
+
# For PR session, include mr_iid
|
|
137
|
+
elif session_type == "pr" and pr_number is not None:
|
|
138
|
+
params["mr_iid"] = pr_number
|
|
139
139
|
|
|
140
140
|
return self._make_request("DELETE", url, params=params)
|
recce_cloud/ci_providers/base.py
CHANGED
|
@@ -13,9 +13,9 @@ class CIInfo:
|
|
|
13
13
|
"""Information extracted from CI environment."""
|
|
14
14
|
|
|
15
15
|
platform: Optional[str] = None # "github-actions", "gitlab-ci", etc.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
session_type: Optional[str] = None # "
|
|
16
|
+
pr_number: Optional[int] = None # Pull/Merge request number (PR/MR)
|
|
17
|
+
pr_url: Optional[str] = None # Pull/Merge request URL (for session linking)
|
|
18
|
+
session_type: Optional[str] = None # "pr", "prod", "dev"
|
|
19
19
|
commit_sha: Optional[str] = None # Full commit SHA
|
|
20
20
|
base_branch: Optional[str] = None # Target/base branch
|
|
21
21
|
source_branch: Optional[str] = None # Source/head branch
|
|
@@ -64,19 +64,19 @@ class BaseCIProvider(ABC):
|
|
|
64
64
|
return None
|
|
65
65
|
|
|
66
66
|
@staticmethod
|
|
67
|
-
def determine_session_type(
|
|
67
|
+
def determine_session_type(pr_number: Optional[int], source_branch: Optional[str]) -> str:
|
|
68
68
|
"""
|
|
69
69
|
Determine session type based on context.
|
|
70
70
|
|
|
71
71
|
Args:
|
|
72
|
-
|
|
72
|
+
pr_number: Pull/Merge request number (PR/MR)
|
|
73
73
|
source_branch: Source branch name
|
|
74
74
|
|
|
75
75
|
Returns:
|
|
76
|
-
Session type: "
|
|
76
|
+
Session type: "pr", "prod", or "dev"
|
|
77
77
|
"""
|
|
78
|
-
if
|
|
79
|
-
return "
|
|
78
|
+
if pr_number is not None:
|
|
79
|
+
return "pr"
|
|
80
80
|
if source_branch in ["main", "master"]:
|
|
81
81
|
return "prod"
|
|
82
82
|
return "dev"
|