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/review.py ADDED
@@ -0,0 +1,541 @@
1
+ """
2
+ Data review helper functions for recce-cloud CLI.
3
+
4
+ Handles the business logic for generating and retrieving data reviews.
5
+ """
6
+
7
+ import json
8
+ import os
9
+ import sys
10
+ import time
11
+ from dataclasses import dataclass
12
+ from enum import Enum
13
+ from typing import Any, Dict, Optional
14
+
15
+ from recce_cloud.api.client import RecceCloudClient
16
+ from recce_cloud.api.exceptions import RecceCloudException
17
+
18
+ # Cloud UI configuration - use same defaults as auth/login.py
19
+ RECCE_CLOUD_API_HOST = os.environ.get("RECCE_CLOUD_API_HOST", "https://cloud.datarecce.io")
20
+ RECCE_CLOUD_BASE_URL = os.environ.get("RECCE_CLOUD_BASE_URL", RECCE_CLOUD_API_HOST)
21
+
22
+
23
+ class ReviewStatus(Enum):
24
+ """Status of the data review generation.
25
+
26
+ These align with Recce Cloud UI/backend task statuses where applicable.
27
+ """
28
+
29
+ # Terminal states (align with backend RecceTaskStatus)
30
+ SUCCEEDED = "SUCCEEDED"
31
+ FAILED = "FAILED"
32
+
33
+ # CLI-specific states
34
+ ALREADY_EXISTS = "ALREADY_EXISTS"
35
+ TIMEOUT = "TIMEOUT"
36
+
37
+ # In-progress states (align with backend RecceTaskStatus)
38
+ QUEUED = "QUEUED"
39
+ SCHEDULED = "SCHEDULED"
40
+ RUNNING = "RUNNING"
41
+
42
+
43
+ @dataclass
44
+ class ReviewResult:
45
+ """Result of a data review operation."""
46
+
47
+ status: ReviewStatus
48
+ session_id: Optional[str] = None
49
+ session_name: Optional[str] = None
50
+ review_url: Optional[str] = None
51
+ task_id: Optional[str] = None
52
+ error_message: Optional[str] = None
53
+ summary: Optional[str] = None
54
+
55
+
56
+ # Default polling configuration
57
+ DEFAULT_POLL_INTERVAL_SECONDS = 5
58
+ DEFAULT_TIMEOUT_SECONDS = 300 # 5 minutes
59
+
60
+
61
+ def generate_review_url(
62
+ org_id: str,
63
+ project_id: str,
64
+ session_id: str,
65
+ client: Optional[RecceCloudClient] = None,
66
+ ) -> str:
67
+ """
68
+ Generate the URL to view the data review.
69
+
70
+ Args:
71
+ org_id: Organization ID.
72
+ project_id: Project ID.
73
+ session_id: Session ID.
74
+ client: Optional RecceCloudClient to fetch current slugs from API.
75
+ If provided, the URL will use current slugs (handles renames).
76
+ If not provided, falls back to using IDs directly.
77
+
78
+ Returns:
79
+ URL string to view the data review.
80
+ """
81
+ # Default to using IDs
82
+ org_slug = org_id
83
+ project_slug = project_id
84
+
85
+ # If client provided, fetch current slugs from API
86
+ if client:
87
+ try:
88
+ org = client.get_organization(org_id)
89
+ if org:
90
+ org_slug = org.get("name") or org.get("slug") or org_id
91
+
92
+ project = client.get_project(org_id, project_id)
93
+ if project:
94
+ project_slug = project.get("name") or project.get("slug") or project_id
95
+ except Exception:
96
+ # If API call fails, fall back to using IDs
97
+ pass
98
+
99
+ # Use the Recce Cloud web UI URL (respects RECCE_CLOUD_BASE_URL env var)
100
+ return f"{RECCE_CLOUD_BASE_URL}/{org_slug}/{project_slug}/{session_id}/review"
101
+
102
+
103
+ def check_prerequisites(
104
+ client: RecceCloudClient,
105
+ org_id: str,
106
+ project_id: str,
107
+ session_name: Optional[str] = None,
108
+ session_id: Optional[str] = None,
109
+ ) -> Dict[str, Any]:
110
+ """
111
+ Check prerequisites for data review generation.
112
+
113
+ This function calls the backend API which verifies:
114
+ 1. Session must exist
115
+ 2. Session must have artifacts uploaded
116
+ 3. Base session must exist
117
+ 4. Base session must have artifacts uploaded
118
+
119
+ Args:
120
+ client: RecceCloudClient instance.
121
+ org_id: Organization ID or slug.
122
+ project_id: Project ID or slug.
123
+ session_name: Session name to check (used if session_id not provided).
124
+ session_id: Session ID to check directly (takes precedence over session_name).
125
+
126
+ Returns:
127
+ dict with:
128
+ - success: bool indicating if all prerequisites are met
129
+ - session: session dict if found (with id, name, adapter_type)
130
+ - error: error message if prerequisites not met
131
+ """
132
+ result = {
133
+ "success": False,
134
+ "session": None,
135
+ "error": None,
136
+ }
137
+
138
+ # If session_id is not provided, look up by session_name
139
+ if not session_id:
140
+ if not session_name:
141
+ result["error"] = "Either session_id or session_name must be provided"
142
+ return result
143
+
144
+ try:
145
+ session = client.get_session_by_name(org_id, project_id, session_name)
146
+ if not session:
147
+ result["error"] = f"Session '{session_name}' not found in project"
148
+ return result
149
+ session_id = session.get("id")
150
+ except RecceCloudException as e:
151
+ result["error"] = f"Failed to find session: {e.reason}"
152
+ return result
153
+
154
+ # Call the backend API to check all prerequisites
155
+ try:
156
+ prereq_response = client.check_prerequisites(org_id, project_id, session_id)
157
+
158
+ # Build session dict from the response
159
+ result["session"] = {
160
+ "id": prereq_response.get("session_id"),
161
+ "name": prereq_response.get("session_name"),
162
+ "adapter_type": prereq_response.get("adapter_type"),
163
+ }
164
+
165
+ if prereq_response.get("is_ready"):
166
+ result["success"] = True
167
+ else:
168
+ result["error"] = prereq_response.get("reason", "Prerequisites not met")
169
+
170
+ return result
171
+
172
+ except RecceCloudException as e:
173
+ result["error"] = f"Failed to check prerequisites: {e.reason}"
174
+ return result
175
+
176
+
177
+ def poll_task_status(
178
+ client: RecceCloudClient,
179
+ org_id: str,
180
+ task_id: str,
181
+ poll_interval: int = DEFAULT_POLL_INTERVAL_SECONDS,
182
+ timeout: int = DEFAULT_TIMEOUT_SECONDS,
183
+ progress_callback=None,
184
+ ) -> Dict[str, Any]:
185
+ """
186
+ Poll task status until completion or timeout.
187
+
188
+ Args:
189
+ client: RecceCloudClient instance.
190
+ org_id: Organization ID or slug.
191
+ task_id: Task ID to poll.
192
+ poll_interval: Seconds between polls.
193
+ timeout: Maximum seconds to wait.
194
+ progress_callback: Optional callback(status_dict) called on each poll.
195
+
196
+ Returns:
197
+ dict with:
198
+ - success: bool indicating if task completed successfully
199
+ - status: final task status string
200
+ - error: error message if failed
201
+ - task: full task status dict
202
+ """
203
+ start_time = time.time()
204
+ last_status = None
205
+
206
+ while True:
207
+ elapsed = time.time() - start_time
208
+ if elapsed > timeout:
209
+ return {
210
+ "success": False,
211
+ "status": "timeout",
212
+ "error": f"Task timed out after {timeout} seconds",
213
+ "task": None,
214
+ }
215
+
216
+ try:
217
+ task_status = client.get_task_status(org_id, task_id)
218
+ except RecceCloudException as e:
219
+ return {
220
+ "success": False,
221
+ "status": "error",
222
+ "error": f"Failed to get task status: {e.reason}",
223
+ "task": None,
224
+ }
225
+
226
+ status = task_status.get("status", "unknown")
227
+
228
+ # Call progress callback if status changed
229
+ if progress_callback and status != last_status:
230
+ progress_callback(task_status)
231
+ last_status = status
232
+
233
+ # Check terminal states (handle both lowercase and uppercase from backend)
234
+ if status.lower() in ["completed", "succeeded"]:
235
+ return {
236
+ "success": True,
237
+ "status": status,
238
+ "error": None,
239
+ "task": task_status,
240
+ }
241
+ elif status.lower() in ["failed", "error", "cancelled"]:
242
+ error_msg = task_status.get("metadata", {}).get("error", f"Task {status}")
243
+ return {
244
+ "success": False,
245
+ "status": status,
246
+ "error": error_msg,
247
+ "task": task_status,
248
+ }
249
+
250
+ # Still running, wait and poll again
251
+ time.sleep(poll_interval)
252
+
253
+
254
+ def generate_data_review(
255
+ console,
256
+ client: RecceCloudClient,
257
+ org_id: str,
258
+ project_id: str,
259
+ session: Dict[str, Any],
260
+ regenerate: bool = False,
261
+ timeout: int = DEFAULT_TIMEOUT_SECONDS,
262
+ json_output: bool = False,
263
+ ) -> ReviewResult:
264
+ """
265
+ Generate data review for a session.
266
+
267
+ This function handles:
268
+ 1. Checking if a review already exists
269
+ 2. Checking if a task is already running
270
+ 3. Triggering new review generation
271
+ 4. Polling for completion
272
+
273
+ Args:
274
+ console: Rich console for output.
275
+ client: RecceCloudClient instance.
276
+ org_id: Organization ID or slug.
277
+ project_id: Project ID or slug.
278
+ session: Session dict from API.
279
+ regenerate: If True, regenerate even if review exists.
280
+ timeout: Maximum seconds to wait for generation.
281
+ json_output: If True, suppress progress output.
282
+
283
+ Returns:
284
+ ReviewResult with status and details.
285
+ """
286
+ session_id = session["id"]
287
+ session_name = session.get("name", session_id)
288
+
289
+ # 1. Check if review already exists (skip if regenerating)
290
+ # Note: get_data_review() returns None for 404 (no review), so we don't need try-except for that case.
291
+ # Let other errors (403, 500) propagate - they indicate real problems that would also fail generation.
292
+ if not regenerate:
293
+ existing_review = client.get_data_review(org_id, project_id, session_id)
294
+ if existing_review and existing_review.get("summary"):
295
+ return ReviewResult(
296
+ status=ReviewStatus.ALREADY_EXISTS,
297
+ session_id=session_id,
298
+ session_name=session_name,
299
+ review_url=generate_review_url(org_id, project_id, session_id, client),
300
+ summary=existing_review.get("summary"),
301
+ )
302
+
303
+ # 2. Check if a task is already running
304
+ # Note: get_running_task() returns None for 404 (no running task), so we don't need try-except.
305
+ # Let other errors (403, 500) propagate - they indicate real problems.
306
+ running_task = client.get_running_task(org_id, project_id, session_id)
307
+ if running_task:
308
+ task_id = running_task["task_id"]
309
+ if not json_output:
310
+ console.print(f"[yellow]Task already running:[/yellow] {task_id}")
311
+ console.print("Waiting for completion...")
312
+ else:
313
+ task_id = None
314
+
315
+ # 3. Trigger new generation if no task running
316
+ if not task_id:
317
+ if not json_output:
318
+ action = "Regenerating" if regenerate else "Generating"
319
+ console.print(f"[cyan]{action} data review...[/cyan]")
320
+
321
+ try:
322
+ response = client.generate_data_review(org_id, project_id, session_id, regenerate=regenerate)
323
+ task_id = response.get("task_id")
324
+
325
+ if not task_id:
326
+ # Review was already generated and no new task needed
327
+ existing_review = client.get_data_review(org_id, project_id, session_id)
328
+ if existing_review and existing_review.get("summary"):
329
+ return ReviewResult(
330
+ status=ReviewStatus.ALREADY_EXISTS,
331
+ session_id=session_id,
332
+ session_name=session_name,
333
+ review_url=generate_review_url(org_id, project_id, session_id, client),
334
+ summary=existing_review.get("summary"),
335
+ )
336
+ else:
337
+ return ReviewResult(
338
+ status=ReviewStatus.FAILED,
339
+ session_id=session_id,
340
+ session_name=session_name,
341
+ error_message="No task created and no existing review found",
342
+ )
343
+
344
+ except RecceCloudException as e:
345
+ return ReviewResult(
346
+ status=ReviewStatus.FAILED,
347
+ session_id=session_id,
348
+ session_name=session_name,
349
+ error_message=f"Failed to trigger review generation: {e.reason}",
350
+ )
351
+
352
+ # 4. Poll for completion
353
+ def progress_callback(task_status):
354
+ if not json_output:
355
+ # Display status as-is from backend (aligned with Recce Cloud UI)
356
+ status = task_status.get("status", "unknown")
357
+ console.print(f" Status: [cyan]{status}[/cyan]")
358
+
359
+ if not json_output:
360
+ console.print(f"[cyan]Task ID:[/cyan] {task_id}")
361
+ console.print("Waiting for review generation to complete...")
362
+
363
+ poll_result = poll_task_status(
364
+ client,
365
+ org_id,
366
+ task_id,
367
+ timeout=timeout,
368
+ progress_callback=progress_callback if not json_output else None,
369
+ )
370
+
371
+ if poll_result["success"]:
372
+ # Fetch the generated review
373
+ try:
374
+ review = client.get_data_review(org_id, project_id, session_id)
375
+ return ReviewResult(
376
+ status=ReviewStatus.SUCCEEDED,
377
+ session_id=session_id,
378
+ session_name=session_name,
379
+ review_url=generate_review_url(org_id, project_id, session_id, client),
380
+ task_id=task_id,
381
+ summary=review.get("summary") if review else None,
382
+ )
383
+ except RecceCloudException as e:
384
+ return ReviewResult(
385
+ status=ReviewStatus.FAILED,
386
+ session_id=session_id,
387
+ session_name=session_name,
388
+ task_id=task_id,
389
+ error_message=f"Review generated but failed to fetch: {e.reason}",
390
+ )
391
+ elif poll_result["status"] == "timeout":
392
+ return ReviewResult(
393
+ status=ReviewStatus.TIMEOUT,
394
+ session_id=session_id,
395
+ session_name=session_name,
396
+ task_id=task_id,
397
+ error_message=poll_result["error"],
398
+ )
399
+ else:
400
+ return ReviewResult(
401
+ status=ReviewStatus.FAILED,
402
+ session_id=session_id,
403
+ session_name=session_name,
404
+ task_id=task_id,
405
+ error_message=poll_result["error"],
406
+ )
407
+
408
+
409
+ def run_review_command(
410
+ console,
411
+ token: str,
412
+ org_id: str,
413
+ project_id: str,
414
+ session_name: Optional[str] = None,
415
+ session_id: Optional[str] = None,
416
+ regenerate: bool = False,
417
+ timeout: int = DEFAULT_TIMEOUT_SECONDS,
418
+ json_output: bool = False,
419
+ ):
420
+ """
421
+ Main entry point for the review command.
422
+
423
+ This orchestrates the full review workflow:
424
+ 1. Initialize client
425
+ 2. Check prerequisites
426
+ 3. Generate review
427
+ 4. Display results
428
+
429
+ Args:
430
+ console: Rich console for output.
431
+ token: API token.
432
+ org_id: Organization ID or slug.
433
+ project_id: Project ID or slug.
434
+ session_name: Session name to generate review for (used if session_id not provided).
435
+ session_id: Session ID to generate review for (takes precedence over session_name).
436
+ regenerate: If True, regenerate even if review exists.
437
+ timeout: Maximum seconds to wait for generation.
438
+ json_output: If True, output JSON instead of human-readable text.
439
+ """
440
+ # 1. Initialize client
441
+ try:
442
+ client = RecceCloudClient(token)
443
+ except Exception as e:
444
+ if json_output:
445
+ print(json.dumps({"success": False, "error": f"Failed to initialize client: {e}"}))
446
+ else:
447
+ console.print("[red]Error:[/red] Failed to initialize API client")
448
+ console.print(f"Reason: {e}")
449
+ sys.exit(2)
450
+
451
+ # 2. Check prerequisites
452
+ if not json_output:
453
+ console.rule("Checking Prerequisites", style="blue")
454
+
455
+ prereq = check_prerequisites(client, org_id, project_id, session_name=session_name, session_id=session_id)
456
+
457
+ if not prereq["success"]:
458
+ if json_output:
459
+ output = {
460
+ "success": False,
461
+ "error": prereq["error"],
462
+ }
463
+ if session_name:
464
+ output["session_name"] = session_name
465
+ if session_id:
466
+ output["session_id"] = session_id
467
+ print(json.dumps(output))
468
+ else:
469
+ console.print(f"[red]Error:[/red] {prereq['error']}")
470
+ sys.exit(3)
471
+
472
+ session = prereq["session"]
473
+ resolved_session_id = session["id"]
474
+ resolved_session_name = session.get("name") or session_id or "(unnamed)"
475
+
476
+ if not json_output:
477
+ console.print(f"[green]✓[/green] Session found: {resolved_session_name}")
478
+ console.print(f" Session ID: {resolved_session_id}")
479
+ console.print(f" Adapter: {session.get('adapter_type', 'unknown')}")
480
+ console.print("[green]✓[/green] Base session available")
481
+
482
+ # 3. Generate review
483
+ if not json_output:
484
+ console.rule("Generating Data Review", style="blue")
485
+
486
+ result = generate_data_review(
487
+ console=console,
488
+ client=client,
489
+ org_id=org_id,
490
+ project_id=project_id,
491
+ session=session,
492
+ regenerate=regenerate,
493
+ timeout=timeout,
494
+ json_output=json_output,
495
+ )
496
+
497
+ # 4. Display results
498
+ if json_output:
499
+ output = {
500
+ "success": result.status in [ReviewStatus.SUCCEEDED, ReviewStatus.ALREADY_EXISTS],
501
+ "status": result.status.value,
502
+ "session_id": result.session_id,
503
+ "session_name": result.session_name,
504
+ "review_url": result.review_url,
505
+ }
506
+ if result.task_id:
507
+ output["task_id"] = result.task_id
508
+ if result.error_message:
509
+ output["error"] = result.error_message
510
+ print(json.dumps(output))
511
+ else:
512
+ if result.status == ReviewStatus.SUCCEEDED:
513
+ console.rule("Review Generated Successfully", style="green")
514
+ console.print(f"[green]✓[/green] Data review generated for session '{result.session_name}'")
515
+ console.print()
516
+ console.print(f"[cyan]View review at:[/cyan] {result.review_url}")
517
+ elif result.status == ReviewStatus.ALREADY_EXISTS:
518
+ console.rule("Review Already Exists", style="green")
519
+ console.print(f"[green]✓[/green] Data review already exists for session '{result.session_name}'")
520
+ console.print()
521
+ console.print(f"[cyan]View review at:[/cyan] {result.review_url}")
522
+ console.print()
523
+ console.print("[dim]Tip: Use --regenerate to create a new review[/dim]")
524
+ elif result.status == ReviewStatus.TIMEOUT:
525
+ console.rule("Review Generation Timeout", style="yellow")
526
+ console.print("[yellow]Warning:[/yellow] Review generation is still in progress")
527
+ console.print(f"Task ID: {result.task_id}")
528
+ console.print("The review will be available shortly. Check the web UI for status.")
529
+ console.print()
530
+ console.print(f"[cyan]Check status at:[/cyan] {result.review_url}")
531
+ else:
532
+ console.rule("Review Generation Failed", style="red")
533
+ console.print(f"[red]Error:[/red] {result.error_message}")
534
+
535
+ # Exit with appropriate code
536
+ if result.status in [ReviewStatus.SUCCEEDED, ReviewStatus.ALREADY_EXISTS]:
537
+ sys.exit(0)
538
+ elif result.status == ReviewStatus.TIMEOUT:
539
+ sys.exit(0) # Timeout is not a hard failure - task is still running
540
+ else:
541
+ sys.exit(4)
@@ -172,18 +172,18 @@ class DiagnosticService:
172
172
  binding = get_project_binding()
173
173
 
174
174
  if binding:
175
- self._org = binding.get("org")
176
- self._project = binding.get("project")
175
+ self._org = binding.get("org_id")
176
+ self._project = binding.get("project_id")
177
177
  return CheckResult(
178
178
  status=CheckStatus.PASS,
179
179
  details={
180
- "org": self._org,
181
- "project": self._project,
180
+ "org_id": self._org,
181
+ "project_id": self._project,
182
182
  "source": "config_file",
183
183
  },
184
184
  )
185
185
 
186
- # Check environment variables as fallback
186
+ # Check environment variables as fallback (accept both slugs and IDs)
187
187
  env_org = os.environ.get("RECCE_ORG")
188
188
  env_project = os.environ.get("RECCE_PROJECT")
189
189
 
@@ -193,8 +193,8 @@ class DiagnosticService:
193
193
  return CheckResult(
194
194
  status=CheckStatus.PASS,
195
195
  details={
196
- "org": self._org,
197
- "project": self._project,
196
+ "org_id": self._org,
197
+ "project_id": self._project,
198
198
  "source": "env_vars",
199
199
  },
200
200
  )
recce_cloud/upload.py CHANGED
@@ -159,7 +159,7 @@ def upload_with_platform_apis(
159
159
  session_response = client.touch_recce_session(
160
160
  branch=ci_info.source_branch or ci_info.base_branch or "main",
161
161
  adapter_type=adapter_type,
162
- cr_number=ci_info.cr_number,
162
+ pr_number=ci_info.pr_number,
163
163
  commit_sha=ci_info.commit_sha,
164
164
  session_type=ci_info.session_type,
165
165
  )
@@ -226,8 +226,8 @@ def upload_with_platform_apis(
226
226
  console.print(f'Uploaded dbt artifacts to Recce Cloud for session ID "{session_id}"')
227
227
  console.print(f'Artifacts from: "{os.path.abspath(target_path)}"')
228
228
 
229
- if ci_info.cr_url:
230
- console.print(f"Change request: {ci_info.cr_url}")
229
+ if ci_info.pr_url:
230
+ console.print(f"Pull request: {ci_info.pr_url}")
231
231
 
232
232
  sys.exit(0)
233
233
 
@@ -256,8 +256,8 @@ def upload_with_session_name(
256
256
  console.rule("Session Name Resolution", style="blue")
257
257
  try:
258
258
  config = resolve_config()
259
- org = config.org
260
- project = config.project
259
+ org = config.org_id
260
+ project = config.project_id
261
261
  console.print(f"[cyan]Organization:[/cyan] {org}")
262
262
  console.print(f"[cyan]Project:[/cyan] {project}")
263
263
  console.print(f"[cyan]Config Source:[/cyan] {config.source}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: recce-cloud
3
- Version: 1.33.1
3
+ Version: 1.34.0
4
4
  Summary: Lightweight CLI for Recce Cloud operations
5
5
  Project-URL: Bug Tracker, https://github.com/InfuseAI/recce/issues
6
6
  Author-email: InfuseAI Dev Team <dev@infuseai.io>
@@ -159,7 +159,7 @@ recce-cloud upload
159
159
  recce-cloud upload --target-path custom-target
160
160
 
161
161
  # With manual overrides
162
- recce-cloud upload --cr 123 --type cr
162
+ recce-cloud upload --pr 123 --type pr
163
163
  ```
164
164
 
165
165
  **Requirements:**
@@ -355,8 +355,8 @@ Upload dbt artifacts to Recce Cloud session.
355
355
  | --------------- | ------- | -------- | --------------------------------------------- |
356
356
  | `--target-path` | path | `target` | Path to dbt target directory |
357
357
  | `--session-id` | string | - | Session ID for generic workflow (optional) |
358
- | `--cr` | integer | - | Override PR/MR number |
359
- | `--type` | choice | - | Override session type: `cr`, `prod`, `dev` |
358
+ | `--pr` | integer | - | Override PR/MR number |
359
+ | `--type` | choice | - | Override session type: `pr`, `prod`, `dev` |
360
360
  | `--dry-run` | flag | false | Show what would be uploaded without uploading |
361
361
 
362
362
  **Environment Variables:**
@@ -492,13 +492,13 @@ You can override auto-detected values:
492
492
 
493
493
  ```bash
494
494
  # Override PR/MR number
495
- recce-cloud upload --cr 456
495
+ recce-cloud upload --pr 456
496
496
 
497
497
  # Override session type
498
498
  recce-cloud upload --type prod
499
499
 
500
500
  # Multiple overrides
501
- recce-cloud upload --cr 789 --type cr
501
+ recce-cloud upload --pr 789 --type pr
502
502
 
503
503
  # Dry run - preview what would be uploaded
504
504
  recce-cloud upload --dry-run
@@ -0,0 +1,38 @@
1
+ recce_cloud/VERSION,sha256=xBVSUVA8aWplLFRKB0ZS35rrhaZQwe68qZ7iCifdheg,7
2
+ recce_cloud/__init__.py,sha256=2Zlm9V1IBJ9DxovzHIUJB0QDl6ergO5ktY09baQ-Kxc,907
3
+ recce_cloud/artifact.py,sha256=C6q1i8d0JdTIVLcJY1ZEmJJ0uBz-0g3zVZD8ss0K1iI,1458
4
+ recce_cloud/cli.py,sha256=eFS4WQkL-1K7gdSe4Cc9g6y-8QbcTc-5CgaKJMFGfe4,56652
5
+ recce_cloud/delete.py,sha256=sZSJkCLcTkiI2qVRUZwV5vqqV3BRzzuRUKdTLlVZfr0,3751
6
+ recce_cloud/download.py,sha256=bNBkh2tKLdFoQIuZNXG_wcP46VPCO_WlVszSmHAseGE,8482
7
+ recce_cloud/report.py,sha256=MRlT-CGV-SBzzYepp1zzoFuHqomC2vhpn7s5ACoNyyQ,10214
8
+ recce_cloud/review.py,sha256=0r4AT0Y7y-VjSs2Xxm1A79WChbreY9FYQKu-8oBcCXs,19059
9
+ recce_cloud/upload.py,sha256=SYYb4bJ4tfgXYUGSqvMMwLCgnmE2to02L2c1-z_5btQ,17543
10
+ recce_cloud/api/__init__.py,sha256=HrGOh7wp_U3G3gOXyGjJo1WoDXrMvhwjw8VGO2aKNTw,562
11
+ recce_cloud/api/base.py,sha256=aSjxqKuyrT5YHxP_NzwGqoBwwK0pXDEPu9DdHUdNm2I,4857
12
+ recce_cloud/api/client.py,sha256=qd3NqcTrm7WvTtmAqhE1pigGkNsMeBD7kX_CF6s1bXs,33096
13
+ recce_cloud/api/exceptions.py,sha256=BSfuh7dGZsx1qDkr2ZhRIAoPo_eXkAIhyho5PrUX2yo,634
14
+ recce_cloud/api/factory.py,sha256=0hdo2jTdTlvd8cU7YTZFJJ2q3ybSssA2JKLr0bO1QmQ,2081
15
+ recce_cloud/api/github.py,sha256=tMvUSq47aCdS8xgbgDmfUUD_JcGkdTslnWk5GemyMxE,4362
16
+ recce_cloud/api/gitlab.py,sha256=z9CobQUYwXkE8ke0yPRLsfRSfFPrd2y6UWJh9UiA5d4,4593
17
+ recce_cloud/auth/__init__.py,sha256=UP4yCMSmLNm9UhJBt80ybduZdPrAE3S2FYaZUocdh7E,453
18
+ recce_cloud/auth/callback_server.py,sha256=F2ph89mfdUMJYeTd9Nt5Jd-j7FECCRhn9cQcotIFlN0,3861
19
+ recce_cloud/auth/login.py,sha256=9t5AU1e8F7zNxfpjAQampxUkGhAV_9GwjKOG25QEo_U,7704
20
+ recce_cloud/auth/profile.py,sha256=hLQzPlIDb5MDUkxWZyheXVjaC4vCCL4PyqGsEm5FVvQ,3145
21
+ recce_cloud/auth/templates/error.html,sha256=MlirKaUGIc89QPPjqI3uQ-4sMbEy6k4JwS3YsDhzFFg,95074
22
+ recce_cloud/auth/templates/success.html,sha256=7tZttX6oFJWNW9R3y5xlOzNcECINj-lJvN_NcgW8puI,95092
23
+ recce_cloud/ci_providers/__init__.py,sha256=cB2bydn4QOMurJVfWhIN9uTD9gIyiBcYOloN-0TvMW4,328
24
+ recce_cloud/ci_providers/base.py,sha256=0R5M6GNV-9u2gPkeB9fmV0WgTC0U8-WUkB9Io33eGQs,2589
25
+ recce_cloud/ci_providers/detector.py,sha256=kyJx1Tkt6HXz8ZGwm4lcIeLMo0uXo8R1T2s0Fu4JNsY,5060
26
+ recce_cloud/ci_providers/github_actions.py,sha256=8Be3FcnHBUHwXpfdtuKFJNVlZYEe5oxiF-nUBGBhN34,4302
27
+ recce_cloud/ci_providers/gitlab_ci.py,sha256=vMFdqbdk2yxGnDEtsjeEbjIdqFZeai47W_NLLZA0NSQ,4175
28
+ recce_cloud/commands/__init__.py,sha256=H5LHyMEOc2mrFxu56lQlvrG_AJ_rxj0gnHjT2efGb2w,36
29
+ recce_cloud/commands/diagnostics.py,sha256=NqH8l0Xx90dwNVMvGKtFvszHvtQOcyFrWTDDf-Q3oXI,6073
30
+ recce_cloud/config/__init__.py,sha256=-26T9sXcsfHi1mAFCz6t19hy2WaK7ZYQlcGSPKhpcHU,423
31
+ recce_cloud/config/project_config.py,sha256=sfToZjP1TZNnSr31x0pwpLc_2DBZywrcqNpYqG1Wj3s,5269
32
+ recce_cloud/config/resolver.py,sha256=EYsWX0dwKHVKYf6kftekrbWd17vrALMUMl-T26up4VM,4712
33
+ recce_cloud/services/__init__.py,sha256=GMxN0Wtf166qvSSMGmU5jwqfly5b3K0vsIe8PkX7AKo,36
34
+ recce_cloud/services/diagnostic_service.py,sha256=X3YBHFTkB31WRIH9MCKpZxbl4drw9evD6bBiD8cDIkw,13049
35
+ recce_cloud-1.34.0.dist-info/METADATA,sha256=4TjurNAzv-MoZrNEVnzMS1JPm4juK9EqzaL_ZavJdjo,22589
36
+ recce_cloud-1.34.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
37
+ recce_cloud-1.34.0.dist-info/entry_points.txt,sha256=MVzgsEDWaQ4fWf33zlyPF83wg5Rgg5Axq2hlLEf0Yxg,58
38
+ recce_cloud-1.34.0.dist-info/RECORD,,