recce-nightly 1.11.0.20250708__py3-none-any.whl → 1.12.0.20250709__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.

Potentially problematic release.


This version of recce-nightly might be problematic. Click here for more details.

Files changed (34) hide show
  1. recce/VERSION +1 -1
  2. recce/adapter/dbt_adapter/__init__.py +5 -1
  3. recce/artifact.py +2 -2
  4. recce/cli.py +23 -14
  5. recce/core.py +1 -1
  6. recce/data/404.html +1 -1
  7. recce/data/_next/static/chunks/583-0138cef66612d810.js +1 -0
  8. recce/data/_next/static/chunks/71-a829703f4f507654.js +10 -0
  9. recce/data/_next/static/chunks/711-61299a23b04d7eb9.js +30 -0
  10. recce/data/_next/static/chunks/app/layout-500042ac66908cd1.js +1 -0
  11. recce/data/_next/static/chunks/app/page-c7841e2372259324.js +1 -0
  12. recce/data/_next/static/chunks/{webpack-b5a9818d6d67641f.js → webpack-e8321d14a0e3d7fd.js} +1 -1
  13. recce/data/_next/static/css/d4ebbb3f53edc04c.css +14 -0
  14. recce/data/index.html +1 -1
  15. recce/data/index.txt +4 -4
  16. recce/models/types.py +3 -0
  17. recce/state.py +154 -75
  18. recce/util/api_token.py +3 -1
  19. recce/util/recce_cloud.py +38 -2
  20. {recce_nightly-1.11.0.20250708.dist-info → recce_nightly-1.12.0.20250709.dist-info}/METADATA +1 -1
  21. {recce_nightly-1.11.0.20250708.dist-info → recce_nightly-1.12.0.20250709.dist-info}/RECORD +28 -28
  22. tests/adapter/dbt_adapter/test_dbt_cll.py +31 -18
  23. recce/data/_next/static/chunks/488-74d3038680fbbbd1.js +0 -30
  24. recce/data/_next/static/chunks/512-5f44b9448f104530.js +0 -10
  25. recce/data/_next/static/chunks/583-bafc31cf92748715.js +0 -1
  26. recce/data/_next/static/chunks/app/layout-aa349a34c05b8677.js +0 -1
  27. recce/data/_next/static/chunks/app/page-e7d0a50c2a7f36f6.js +0 -1
  28. recce/data/_next/static/css/2d0bc492ccebdbed.css +0 -14
  29. /recce/data/_next/static/{L_GMfqmQNYzCO_95xuR_2 → Sa2q8PU_U99P0fxGYpSh3}/_buildManifest.js +0 -0
  30. /recce/data/_next/static/{L_GMfqmQNYzCO_95xuR_2 → Sa2q8PU_U99P0fxGYpSh3}/_ssgManifest.js +0 -0
  31. {recce_nightly-1.11.0.20250708.dist-info → recce_nightly-1.12.0.20250709.dist-info}/WHEEL +0 -0
  32. {recce_nightly-1.11.0.20250708.dist-info → recce_nightly-1.12.0.20250709.dist-info}/entry_points.txt +0 -0
  33. {recce_nightly-1.11.0.20250708.dist-info → recce_nightly-1.12.0.20250709.dist-info}/licenses/LICENSE +0 -0
  34. {recce_nightly-1.11.0.20250708.dist-info → recce_nightly-1.12.0.20250709.dist-info}/top_level.txt +0 -0
recce/state.py CHANGED
@@ -9,7 +9,7 @@ from base64 import b64encode
9
9
  from dataclasses import dataclass
10
10
  from datetime import datetime
11
11
  from hashlib import md5, sha256
12
- from typing import Dict, List, Optional, Tuple, Union
12
+ from typing import Dict, List, Literal, Optional, Tuple, Union
13
13
  from urllib.parse import urlencode
14
14
 
15
15
  import botocore.exceptions
@@ -17,6 +17,7 @@ from pydantic import BaseModel, Field
17
17
 
18
18
  from recce import get_version
19
19
  from recce.event import get_recce_api_token
20
+ from recce.exceptions import RecceException
20
21
  from recce.git import current_branch
21
22
  from recce.models import CheckDAO
22
23
  from recce.models.types import Check, Run
@@ -136,7 +137,7 @@ class RecceState(BaseModel):
136
137
  if metadata.schema_version == "v0":
137
138
  pass
138
139
  else:
139
- raise Exception(f"Unsupported state file version: {metadata.schema_version}")
140
+ raise RecceException(f"Unsupported state file version: {metadata.schema_version}")
140
141
  return state
141
142
 
142
143
  @staticmethod
@@ -203,28 +204,50 @@ class RecceStateLoader:
203
204
  self.state_lock = threading.Lock()
204
205
  self.state_etag = None
205
206
  self.pr_info = None
207
+ self.catalog: Literal["github", "preview"] = "github"
208
+ self.share_id = None
206
209
 
207
210
  if self.cloud_mode:
208
- if not self.cloud_options.get("token"):
209
- raise Exception(RECCE_CLOUD_TOKEN_MISSING.error_message)
210
- self.pr_info = fetch_pr_metadata(cloud=self.cloud_mode, github_token=self.cloud_options.get("token"))
211
- if self.pr_info.id is None:
212
- raise Exception("Cannot get the pull request information from GitHub.")
211
+ if self.cloud_options.get("github_token"):
212
+ self.catalog = "github"
213
+ self.pr_info = fetch_pr_metadata(
214
+ cloud=self.cloud_mode, github_token=self.cloud_options.get("github_token")
215
+ )
216
+ if self.pr_info.id is None:
217
+ raise RecceException("Cannot get the pull request information from GitHub.")
218
+ elif self.cloud_options.get("api_token"):
219
+ self.catalog = "preview"
220
+ self.share_id = self.cloud_options.get("share_id")
221
+ else:
222
+ raise RecceException(RECCE_CLOUD_TOKEN_MISSING.error_message)
213
223
 
214
224
  # Load the state
215
225
  self.load()
216
226
 
217
227
  def verify(self) -> bool:
218
228
  if self.cloud_mode:
219
- if self.cloud_options.get("token") is None:
220
- self.error_message = RECCE_CLOUD_TOKEN_MISSING.error_message
221
- self.hint_message = RECCE_CLOUD_TOKEN_MISSING.hint_message
222
- return False
223
- if not self.cloud_options.get("host"):
224
- if self.cloud_options.get("password") is None:
225
- self.error_message = RECCE_CLOUD_PASSWORD_MISSING.error_message
226
- self.hint_message = RECCE_CLOUD_PASSWORD_MISSING.hint_message
229
+ if self.catalog == "github":
230
+ if self.cloud_options.get("github_token") is None:
231
+ self.error_message = RECCE_CLOUD_TOKEN_MISSING.error_message
232
+ self.hint_message = RECCE_CLOUD_TOKEN_MISSING.hint_message
227
233
  return False
234
+ if not self.cloud_options.get("host"):
235
+ if self.cloud_options.get("password") is None:
236
+ self.error_message = RECCE_CLOUD_PASSWORD_MISSING.error_message
237
+ self.hint_message = RECCE_CLOUD_PASSWORD_MISSING.hint_message
238
+ return False
239
+ elif self.catalog == "preview":
240
+ if self.cloud_options.get("api_token") is None:
241
+ self.error_message = RECCE_API_TOKEN_MISSING.error_message
242
+ self.hint_message = RECCE_API_TOKEN_MISSING.hint_message
243
+ return False
244
+ if self.cloud_options.get("share_id") is None:
245
+ self.error_message = "No share ID is provided for the preview catalog."
246
+ self.hint_message = (
247
+ 'Please provide a share URL in the command argument with option "--share-url <share-url>"'
248
+ )
249
+ return False
250
+
228
251
  else:
229
252
  if self.review_mode is True and self.state_file is None:
230
253
  self.error_message = "Recce can not launch without a state file."
@@ -233,6 +256,10 @@ class RecceStateLoader:
233
256
  pass
234
257
  return True
235
258
 
259
+ @property
260
+ def token(self):
261
+ return self.cloud_options.get("github_token") or self.cloud_options.get("api_token")
262
+
236
263
  @property
237
264
  def error_and_hint(self) -> (Union[str, None], Union[str, None]):
238
265
  return self.error_message, self.hint_message
@@ -348,8 +375,12 @@ class RecceStateLoader:
348
375
  RecceState: The state object.
349
376
  str: The etag of the state file.
350
377
  """
351
- if (self.pr_info is None) or (self.pr_info.id is None) or (self.pr_info.repository is None):
352
- raise Exception("Cannot get the pull request information from GitHub.")
378
+ if self.catalog == "github":
379
+ if (self.pr_info is None) or (self.pr_info.id is None) or (self.pr_info.repository is None):
380
+ raise RecceException("Cannot get the pull request information from GitHub.")
381
+ elif self.catalog == "preview":
382
+ if self.share_id is None:
383
+ raise RecceException("Cannot load the share state from Recce Cloud. No share ID is provided.")
353
384
 
354
385
  if self.cloud_options.get("host", "").startswith("s3://"):
355
386
  logger.debug("Fetching state from AWS S3 bucket...")
@@ -357,49 +388,60 @@ class RecceStateLoader:
357
388
  else:
358
389
  logger.debug("Fetching state from Recce Cloud...")
359
390
  metadata = self._get_metadata_from_recce_cloud()
360
- if metadata is None:
361
- return None, None
362
- state_etag = metadata.get("etag")
391
+ if metadata:
392
+ state_etag = metadata.get("etag")
393
+ else:
394
+ state_etag = None
363
395
  if self.state_etag and state_etag == self.state_etag:
364
396
  return self.state, self.state_etag
365
397
 
366
398
  return self._load_state_from_recce_cloud(), state_etag
367
399
 
368
400
  def _get_metadata_from_recce_cloud(self) -> Union[dict, None]:
369
- recce_cloud = RecceCloud(token=self.cloud_options.get("token"))
370
- return recce_cloud.get_artifact_metadata(pr_info=self.pr_info)
401
+ recce_cloud = RecceCloud(token=self.token)
402
+ return recce_cloud.get_artifact_metadata(pr_info=self.pr_info) if self.pr_info else None
371
403
 
372
404
  def _load_state_from_recce_cloud(self) -> Union[RecceState, None]:
373
405
  import tempfile
374
406
 
375
407
  import requests
376
408
 
377
- recce_cloud = RecceCloud(token=self.cloud_options.get("token"))
378
- presigned_url = recce_cloud.get_presigned_url(
379
- method=PresignedUrlMethod.DOWNLOAD,
380
- pr_id=self.pr_info.id,
381
- repository=self.pr_info.repository,
382
- artifact_name=RECCE_STATE_COMPRESSED_FILE,
383
- )
409
+ recce_cloud = RecceCloud(token=self.token)
410
+ password = None
384
411
 
385
- password = self.cloud_options.get("password")
386
- if password is None:
387
- raise Exception(RECCE_CLOUD_PASSWORD_MISSING.error_message)
412
+ if self.catalog == "github":
413
+ presigned_url = recce_cloud.get_presigned_url_by_github_repo(
414
+ method=PresignedUrlMethod.DOWNLOAD,
415
+ pr_id=self.pr_info.id,
416
+ repository=self.pr_info.repository,
417
+ artifact_name=RECCE_STATE_COMPRESSED_FILE,
418
+ )
419
+
420
+ password = self.cloud_options.get("password")
421
+ if password is None:
422
+ raise RecceException(RECCE_CLOUD_PASSWORD_MISSING.error_message)
423
+ elif self.catalog == "preview":
424
+ share_id = self.cloud_options.get("share_id")
425
+ presigned_url = recce_cloud.get_presigned_url_by_share_id(
426
+ method=PresignedUrlMethod.DOWNLOAD, share_id=share_id
427
+ )
388
428
 
389
429
  with tempfile.NamedTemporaryFile() as tmp:
390
- headers = s3_sse_c_headers(password)
430
+ headers = s3_sse_c_headers(password) if password else None
391
431
  response = requests.get(presigned_url, headers=headers)
392
432
  if response.status_code == 404:
393
433
  self.error_message = "The state file is not found in Recce Cloud."
394
434
  return None
395
435
  elif response.status_code != 200:
396
436
  self.error_message = response.text
397
- raise Exception(
437
+ raise RecceException(
398
438
  f"{response.status_code} Failed to download the state file from Recce Cloud. The password could be wrong."
399
439
  )
400
440
  with open(tmp.name, "wb") as f:
401
441
  f.write(response.content)
402
- return RecceState.from_file(tmp.name, file_type=SupportedFileTypes.GZIP)
442
+
443
+ file_type = SupportedFileTypes.GZIP if self.catalog == "github" else SupportedFileTypes.FILE
444
+ return RecceState.from_file(tmp.name, file_type=file_type)
403
445
 
404
446
  def _load_state_from_s3_bucket(self) -> Union[RecceState, None]:
405
447
  import tempfile
@@ -408,11 +450,19 @@ class RecceStateLoader:
408
450
 
409
451
  s3_client = boto3.client("s3")
410
452
  s3_bucket_name = self.cloud_options.get("host").replace("s3://", "")
411
- s3_bucket_key = f"github/{self.pr_info.repository}/pulls/{self.pr_info.id}/{RECCE_STATE_COMPRESSED_FILE}"
453
+
454
+ if self.catalog == "github":
455
+ s3_bucket_key = (
456
+ f"{self.catalog}/{self.pr_info.repository}/pulls/{self.pr_info.id}/{RECCE_STATE_COMPRESSED_FILE}"
457
+ )
458
+ elif self.catalog == "preview":
459
+ s3_bucket_key = f"{self.catalog}/{self.share_id}/{RECCE_STATE_FILE}"
460
+ else:
461
+ raise RecceException(f"Unsupported catalog type. {self.catalog} is not supported.")
412
462
 
413
463
  rc, error_message = check_s3_bucket(s3_bucket_name)
414
464
  if rc is False:
415
- raise Exception(error_message)
465
+ raise RecceException(error_message)
416
466
 
417
467
  with tempfile.NamedTemporaryFile() as tmp:
418
468
  try:
@@ -427,8 +477,11 @@ class RecceStateLoader:
427
477
  return RecceState.from_file(tmp.name, file_type=SupportedFileTypes.GZIP)
428
478
 
429
479
  def _export_state_to_cloud(self) -> Tuple[Union[str, None], str]:
430
- if (self.pr_info is None) or (self.pr_info.id is None) or (self.pr_info.repository is None):
431
- raise Exception("Cannot get the pull request information from GitHub.")
480
+ if self.catalog == "github":
481
+ if (self.pr_info is None) or (self.pr_info.id is None) or (self.pr_info.repository is None):
482
+ raise RecceException("Cannot get the pull request information from GitHub.")
483
+ elif self.catalog == "preview":
484
+ pass
432
485
 
433
486
  check_status = CheckDAO().status()
434
487
  metadata = {
@@ -443,9 +496,9 @@ class RecceStateLoader:
443
496
  logger.info("Store recce state to Recce Cloud")
444
497
  message = self._export_state_to_recce_cloud(metadata=metadata)
445
498
  metadata = self._get_metadata_from_recce_cloud()
446
- if metadata is None:
447
- return None
448
- state_etag = metadata.get("etag")
499
+ state_etag = metadata.get("etag") if metadata else None
500
+ if message:
501
+ logger.warning(message)
449
502
  return message, state_etag
450
503
 
451
504
  def _export_state_to_recce_cloud(self, metadata: dict = None) -> Union[str, None]:
@@ -453,24 +506,42 @@ class RecceStateLoader:
453
506
 
454
507
  import requests
455
508
 
456
- presigned_url = RecceCloud(token=self.cloud_options.get("token")).get_presigned_url(
457
- method=PresignedUrlMethod.UPLOAD,
458
- repository=self.pr_info.repository,
459
- artifact_name=RECCE_STATE_COMPRESSED_FILE,
460
- pr_id=self.pr_info.id,
461
- metadata=metadata,
462
- )
509
+ if self.catalog == "github":
510
+ presigned_url = RecceCloud(token=self.token).get_presigned_url_by_github_repo(
511
+ method=PresignedUrlMethod.UPLOAD,
512
+ repository=self.pr_info.repository,
513
+ artifact_name=RECCE_STATE_COMPRESSED_FILE,
514
+ pr_id=self.pr_info.id,
515
+ metadata=metadata,
516
+ )
517
+ elif self.catalog == "preview":
518
+ share_id = self.cloud_options.get("share_id")
519
+ presigned_url = RecceCloud(token=self.token).get_presigned_url_by_share_id(
520
+ method=PresignedUrlMethod.UPLOAD,
521
+ share_id=share_id,
522
+ metadata=metadata,
523
+ )
463
524
  compress_passwd = self.cloud_options.get("password")
464
- headers = s3_sse_c_headers(compress_passwd)
525
+ if compress_passwd:
526
+ headers = s3_sse_c_headers(compress_passwd)
527
+ else:
528
+ headers = {}
529
+
465
530
  if metadata:
466
531
  headers["x-amz-tagging"] = urlencode(metadata)
467
532
  with tempfile.NamedTemporaryFile() as tmp:
468
- self._export_state_to_file(tmp.name, file_type=SupportedFileTypes.GZIP)
469
- response = requests.put(presigned_url, data=open(tmp.name, "rb").read(), headers=headers)
470
- if response.status_code != 200:
533
+ if self.catalog == "github":
534
+ file_type = SupportedFileTypes.GZIP
535
+ elif self.catalog == "preview":
536
+ file_type = SupportedFileTypes.FILE
537
+ self._export_state_to_file(tmp.name, file_type=file_type)
538
+
539
+ with open(tmp.name, "rb") as fd:
540
+ response = requests.put(presigned_url, data=fd.read(), headers=headers)
541
+ if response.status_code not in [200, 204]:
471
542
  self.error_message = response.text
472
543
  return "Failed to upload the state file to Recce Cloud. Reason: " + response.text
473
- return "The state file is uploaded to Recce Cloud."
544
+ return None
474
545
 
475
546
  def _export_state_to_s3_bucket(self, metadata: dict = None) -> Union[str, None]:
476
547
  import tempfile
@@ -479,11 +550,18 @@ class RecceStateLoader:
479
550
 
480
551
  s3_client = boto3.client("s3")
481
552
  s3_bucket_name = self.cloud_options.get("host").replace("s3://", "")
482
- s3_bucket_key = f"github/{self.pr_info.repository}/pulls/{self.pr_info.id}/{RECCE_STATE_COMPRESSED_FILE}"
553
+ if self.catalog == "github":
554
+ s3_bucket_key = (
555
+ f"{self.catalog}/{self.pr_info.repository}/pulls/{self.pr_info.id}/{RECCE_STATE_COMPRESSED_FILE}"
556
+ )
557
+ elif self.catalog == "preview":
558
+ s3_bucket_key = f"{self.catalog}/{self.share_id}/{RECCE_STATE_FILE}"
559
+ else:
560
+ raise RecceException(f"Unsupported catalog type. {self.catalog} is not supported.")
483
561
 
484
562
  rc, error_message = check_s3_bucket(s3_bucket_name)
485
563
  if rc is False:
486
- raise Exception(error_message)
564
+ raise RecceException(error_message)
487
565
 
488
566
  with tempfile.NamedTemporaryFile() as tmp:
489
567
  self._export_state_to_file(tmp.name, file_type=SupportedFileTypes.GZIP)
@@ -495,7 +573,7 @@ class RecceStateLoader:
495
573
  # Casting all the values under metadata to string
496
574
  ExtraArgs={"Metadata": {k: str(v) for k, v in metadata.items()}},
497
575
  )
498
- RecceCloud(token=self.cloud_options.get("token")).update_github_pull_request_check(self.pr_info, metadata)
576
+ RecceCloud(token=self.token).update_github_pull_request_check(self.pr_info, metadata)
499
577
  return f"The state file is uploaded to ' s3://{s3_bucket_name}/{s3_bucket_key}'"
500
578
 
501
579
  def _get_artifact_metadata_from_s3_bucket(self, artifact_name: str) -> Union[dict, None]:
@@ -510,7 +588,7 @@ class RecceStateLoader:
510
588
  return metadata
511
589
  except botocore.exceptions.ClientError as e:
512
590
  self.error_message = e.response.get("Error", {}).get("Message")
513
- raise Exception("Failed to get artifact metadata from Recce Cloud.")
591
+ raise RecceException("Failed to get artifact metadata from Recce Cloud.")
514
592
 
515
593
  def _export_state_to_file(
516
594
  self, file_path: Optional[str] = None, file_type: SupportedFileTypes = SupportedFileTypes.FILE
@@ -538,15 +616,16 @@ class RecceCloudStateManager:
538
616
  self.pr_info = None
539
617
  self.error_message = None
540
618
  self.hint_message = None
619
+ self.github_token = self.cloud_options.get("github_token")
541
620
 
542
- if not self.cloud_options.get("token"):
543
- raise Exception(RECCE_CLOUD_TOKEN_MISSING.error_message)
544
- self.pr_info = fetch_pr_metadata(cloud=True, github_token=self.cloud_options.get("token"))
621
+ if not self.github_token:
622
+ raise RecceException(RECCE_CLOUD_TOKEN_MISSING.error_message)
623
+ self.pr_info = fetch_pr_metadata(cloud=True, github_token=self.github_token)
545
624
  if self.pr_info.id is None:
546
- raise Exception("Cannot get the pull request information from GitHub.")
625
+ raise RecceException("Cannot get the pull request information from GitHub.")
547
626
 
548
627
  def verify(self) -> bool:
549
- if self.cloud_options.get("token") is None:
628
+ if self.github_token is None:
550
629
  self.error_message = RECCE_CLOUD_TOKEN_MISSING.error_message
551
630
  self.hint_message = RECCE_CLOUD_TOKEN_MISSING.hint_message
552
631
  return False
@@ -561,7 +640,7 @@ class RecceCloudStateManager:
561
640
  return self.error_message, self.hint_message
562
641
 
563
642
  def _check_state_in_recce_cloud(self) -> bool:
564
- return RecceCloud(token=self.cloud_options.get("token")).check_artifacts_exists(self.pr_info)
643
+ return RecceCloud(token=self.github_token).check_artifacts_exists(self.pr_info)
565
644
 
566
645
  def _check_state_in_s3_bucket(self) -> bool:
567
646
  import boto3
@@ -588,7 +667,7 @@ class RecceCloudStateManager:
588
667
 
589
668
  import requests
590
669
 
591
- presigned_url = RecceCloud(token=self.cloud_options.get("token")).get_presigned_url(
670
+ presigned_url = RecceCloud(token=self.github_token).get_presigned_url_by_github_repo(
592
671
  method=PresignedUrlMethod.UPLOAD,
593
672
  repository=self.pr_info.repository,
594
673
  artifact_name=RECCE_STATE_COMPRESSED_FILE,
@@ -616,7 +695,7 @@ class RecceCloudStateManager:
616
695
 
617
696
  rc, error_message = check_s3_bucket(s3_bucket_name)
618
697
  if rc is False:
619
- raise Exception(error_message)
698
+ raise RecceException(error_message)
620
699
 
621
700
  with tempfile.NamedTemporaryFile() as tmp:
622
701
  state.to_file(tmp.name, file_type=SupportedFileTypes.GZIP)
@@ -628,12 +707,12 @@ class RecceCloudStateManager:
628
707
  # Casting all the values under metadata to string
629
708
  ExtraArgs={"Metadata": {k: str(v) for k, v in metadata.items()}},
630
709
  )
631
- RecceCloud(token=self.cloud_options.get("token")).update_github_pull_request_check(self.pr_info, metadata)
710
+ RecceCloud(token=self.github_token).update_github_pull_request_check(self.pr_info, metadata)
632
711
  return f"The state file is uploaded to ' s3://{s3_bucket_name}/{s3_bucket_key}'"
633
712
 
634
713
  def upload_state_to_cloud(self, state: RecceState) -> Union[str, None]:
635
714
  if (self.pr_info is None) or (self.pr_info.id is None) or (self.pr_info.repository is None):
636
- raise Exception("Cannot get the pull request information from GitHub.")
715
+ raise RecceException("Cannot get the pull request information from GitHub.")
637
716
 
638
717
  checks = state.checks
639
718
 
@@ -658,7 +737,7 @@ class RecceCloudStateManager:
658
737
 
659
738
  rc, error_message = check_s3_bucket(s3_bucket_name)
660
739
  if rc is False:
661
- raise Exception(error_message)
740
+ raise RecceException(error_message)
662
741
 
663
742
  response = s3_client.get_object(Bucket=s3_bucket_name, Key=s3_bucket_key)
664
743
  byte_stream = io.BytesIO(response["Body"].read())
@@ -676,7 +755,7 @@ class RecceCloudStateManager:
676
755
 
677
756
  import requests
678
757
 
679
- presigned_url = RecceCloud(token=self.cloud_options.get("token")).get_presigned_url(
758
+ presigned_url = RecceCloud(token=self.github_token).get_presigned_url_by_github_repo(
680
759
  method=PresignedUrlMethod.DOWNLOAD,
681
760
  repository=self.pr_info.repository,
682
761
  artifact_name=RECCE_STATE_COMPRESSED_FILE,
@@ -685,13 +764,13 @@ class RecceCloudStateManager:
685
764
 
686
765
  password = self.cloud_options.get("password")
687
766
  if password is None:
688
- raise Exception(RECCE_CLOUD_PASSWORD_MISSING.error_message)
767
+ raise RecceException(RECCE_CLOUD_PASSWORD_MISSING.error_message)
689
768
 
690
769
  headers = s3_sse_c_headers(password)
691
770
  response = requests.get(presigned_url, headers=headers)
692
771
 
693
772
  if response.status_code != 200:
694
- raise Exception(
773
+ raise RecceException(
695
774
  f"{response.status_code} Failed to download the state file from Recce Cloud. The password could be wrong."
696
775
  )
697
776
 
@@ -707,7 +786,7 @@ class RecceCloudStateManager:
707
786
 
708
787
  def download_state_from_cloud(self, filepath: str) -> Union[str, None]:
709
788
  if (self.pr_info is None) or (self.pr_info.id is None) or (self.pr_info.repository is None):
710
- raise Exception("Cannot get the pull request information from GitHub.")
789
+ raise RecceException("Cannot get the pull request information from GitHub.")
711
790
 
712
791
  if self.cloud_options.get("host", "").startswith("s3://"):
713
792
  logger.debug("Download state file from AWS S3 bucket...")
@@ -738,12 +817,12 @@ class RecceCloudStateManager:
738
817
  delete_response = s3_client.delete_objects(Bucket=s3_bucket_name, Delete={"Objects": delete_objects})
739
818
  if "Deleted" not in delete_response:
740
819
  return False, "Failed to delete the state file from the S3 bucket."
741
- RecceCloud(token=self.cloud_options.get("token")).update_github_pull_request_check(self.pr_info)
820
+ RecceCloud(token=self.github_token).update_github_pull_request_check(self.pr_info)
742
821
  return True, None
743
822
 
744
823
  def _purge_state_from_recce_cloud(self) -> (bool, str):
745
824
  try:
746
- RecceCloud(token=self.cloud_options.get("token")).purge_artifacts(self.pr_info)
825
+ RecceCloud(token=self.github_token).purge_artifacts(self.pr_info)
747
826
  except RecceCloudException as e:
748
827
  return False, e.reason
749
828
  return True, None
recce/util/api_token.py CHANGED
@@ -17,7 +17,9 @@ def show_invalid_api_token_message():
17
17
  Show the message when the API token is invalid.
18
18
  """
19
19
  console.print("[[red]Error[/red]] Invalid Recce Cloud API token.")
20
- console.print(f"Please check your API token from {RECCE_CLOUD_BASE_URL}/settings#tokens")
20
+ console.print("Please associate with your Recce Cloud account by the following command 'recce connect-to-cloud'.")
21
+ console.print(
22
+ "For more information, please visit: https://docs.reccehq.com/recce-cloud/share-recce-session-securely/#configure-recce-cloud-association-manually")
21
23
 
22
24
 
23
25
  def prepare_api_token(
recce/util/recce_cloud.py CHANGED
@@ -66,7 +66,7 @@ class RecceCloud:
66
66
  pass
67
67
  return False
68
68
 
69
- def get_presigned_url(
69
+ def get_presigned_url_by_github_repo(
70
70
  self,
71
71
  method: PresignedUrlMethod,
72
72
  repository: str,
@@ -78,7 +78,16 @@ class RecceCloud:
78
78
  response = self._fetch_presigned_url(method, repository, artifact_name, metadata, pr_id, branch)
79
79
  return response.get("presigned_url")
80
80
 
81
- def get_download_presigned_url_with_tags(
81
+ def get_presigned_url_by_share_id(
82
+ self,
83
+ method: PresignedUrlMethod,
84
+ share_id: str,
85
+ metadata: dict = None,
86
+ ) -> str:
87
+ response = self._fetch_presigned_url_by_share_id(method, share_id, metadata=metadata)
88
+ return response.get("presigned_url")
89
+
90
+ def get_download_presigned_url_by_github_repo_with_tags(
82
91
  self, repository: str, artifact_name: str, branch: str = None
83
92
  ) -> (str, dict):
84
93
  response = self._fetch_presigned_url(PresignedUrlMethod.DOWNLOAD, repository, artifact_name, branch=branch)
@@ -110,6 +119,33 @@ class RecceCloud:
110
119
  )
111
120
  return response.json()
112
121
 
122
+ def _fetch_presigned_url_by_share_id(
123
+ self,
124
+ method: PresignedUrlMethod,
125
+ share_id: str,
126
+ metadata: dict = None,
127
+ ):
128
+ api_url = f"{self.base_url}/shares/{share_id}/presigned/{method}"
129
+ data = None
130
+ # Only provide metadata for upload requests
131
+ if method == PresignedUrlMethod.UPLOAD:
132
+ # Covert metadata values to strings to ensure JSON serializability
133
+ data = {"metadata": {key: str(value) for key, value in metadata.items()}} if metadata else None
134
+ response = self._request(
135
+ "POST",
136
+ api_url,
137
+ json=data,
138
+ )
139
+ if response.status_code != 200:
140
+ raise RecceCloudException(
141
+ message="Failed to {method} artifact {preposition} Recce Cloud.".format(
142
+ method=method, preposition="from" if method == PresignedUrlMethod.DOWNLOAD else "to"
143
+ ),
144
+ reason=response.text,
145
+ status_code=response.status_code,
146
+ )
147
+ return response.json()
148
+
113
149
  def get_artifact_metadata(self, pr_info: PullRequestInfo) -> dict:
114
150
  api_url = f"{self.base_url}/{pr_info.repository}/pulls/{pr_info.id}/metadata"
115
151
  response = self._request("GET", api_url)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: recce-nightly
3
- Version: 1.11.0.20250708
3
+ Version: 1.12.0.20250709
4
4
  Summary: Environment diff tool for dbt
5
5
  Home-page: https://github.com/InfuseAI/recce
6
6
  Author: InfuseAI Dev Team