github-backup 0.59.0__py3-none-any.whl → 0.60.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.
github_backup/__init__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.59.0"
1
+ __version__ = "0.60.0"
@@ -74,9 +74,6 @@ else:
74
74
  " 3. Debian/Ubuntu: apt-get install ca-certificates\n\n"
75
75
  )
76
76
 
77
- # Retry configuration
78
- MAX_RETRIES = 5
79
-
80
77
 
81
78
  def logging_subprocess(
82
79
  popenargs, stdout_log_level=logging.DEBUG, stderr_log_level=logging.ERROR, **kwargs
@@ -144,6 +141,17 @@ def mask_password(url, secret="*****"):
144
141
  return url.replace(parsed.password, secret)
145
142
 
146
143
 
144
+ def non_negative_int(value):
145
+ """Argparse type validator for non-negative integers."""
146
+ try:
147
+ ivalue = int(value)
148
+ except ValueError:
149
+ raise argparse.ArgumentTypeError(f"'{value}' is not a valid integer")
150
+ if ivalue < 0:
151
+ raise argparse.ArgumentTypeError(f"{value} must be 0 or greater")
152
+ return ivalue
153
+
154
+
147
155
  def parse_args(args=None):
148
156
  parser = argparse.ArgumentParser(description="Backup a github account")
149
157
  parser.add_argument("user", metavar="USER", type=str, help="github username")
@@ -468,6 +476,13 @@ def parse_args(args=None):
468
476
  parser.add_argument(
469
477
  "--exclude", dest="exclude", help="names of repositories to exclude", nargs="*"
470
478
  )
479
+ parser.add_argument(
480
+ "--retries",
481
+ dest="max_retries",
482
+ type=non_negative_int,
483
+ default=5,
484
+ help="maximum number of retries for API calls (default: 5)",
485
+ )
471
486
  return parser.parse_args(args)
472
487
 
473
488
 
@@ -622,7 +637,7 @@ def retrieve_data(args, template, query_args=None, paginated=True):
622
637
  def _extract_next_page_url(link_header):
623
638
  for link in link_header.split(","):
624
639
  if 'rel="next"' in link:
625
- return link[link.find("<") + 1:link.find(">")]
640
+ return link[link.find("<") + 1 : link.find(">")]
626
641
  return None
627
642
 
628
643
  def fetch_all() -> Generator[dict, None, None]:
@@ -631,7 +646,7 @@ def retrieve_data(args, template, query_args=None, paginated=True):
631
646
  while True:
632
647
  # FIRST: Fetch response
633
648
 
634
- for attempt in range(MAX_RETRIES):
649
+ for attempt in range(args.max_retries + 1):
635
650
  request = _construct_request(
636
651
  per_page=per_page if paginated else None,
637
652
  query_args=query_args,
@@ -640,7 +655,7 @@ def retrieve_data(args, template, query_args=None, paginated=True):
640
655
  as_app=args.as_app,
641
656
  fine=args.token_fine is not None,
642
657
  )
643
- http_response = make_request_with_retry(request, auth)
658
+ http_response = make_request_with_retry(request, auth, args.max_retries)
644
659
 
645
660
  match http_response.getcode():
646
661
  case 200:
@@ -654,10 +669,10 @@ def retrieve_data(args, template, query_args=None, paginated=True):
654
669
  TimeoutError,
655
670
  ) as e:
656
671
  logger.warning(f"{type(e).__name__} reading response")
657
- if attempt < MAX_RETRIES - 1:
672
+ if attempt < args.max_retries:
658
673
  delay = calculate_retry_delay(attempt, {})
659
674
  logger.warning(
660
- f"Retrying in {delay:.1f}s (attempt {attempt + 1}/{MAX_RETRIES})"
675
+ f"Retrying read in {delay:.1f}s (attempt {attempt + 1}/{args.max_retries + 1})"
661
676
  )
662
677
  time.sleep(delay)
663
678
  continue # Next retry attempt
@@ -683,10 +698,10 @@ def retrieve_data(args, template, query_args=None, paginated=True):
683
698
  )
684
699
  else:
685
700
  logger.error(
686
- f"Failed to read response after {MAX_RETRIES} attempts for {next_url or template}"
701
+ f"Failed to read response after {args.max_retries + 1} attempts for {next_url or template}"
687
702
  )
688
703
  raise Exception(
689
- f"Failed to read response after {MAX_RETRIES} attempts for {next_url or template}"
704
+ f"Failed to read response after {args.max_retries + 1} attempts for {next_url or template}"
690
705
  )
691
706
 
692
707
  # SECOND: Process and paginate
@@ -718,7 +733,7 @@ def retrieve_data(args, template, query_args=None, paginated=True):
718
733
  return list(fetch_all())
719
734
 
720
735
 
721
- def make_request_with_retry(request, auth):
736
+ def make_request_with_retry(request, auth, max_retries=5):
722
737
  """Make HTTP request with automatic retry for transient errors."""
723
738
 
724
739
  def is_retryable_status(status_code, headers):
@@ -730,40 +745,49 @@ def make_request_with_retry(request, auth):
730
745
  return int(headers.get("x-ratelimit-remaining", 1)) < 1
731
746
  return False
732
747
 
733
- for attempt in range(MAX_RETRIES):
748
+ for attempt in range(max_retries + 1):
734
749
  try:
735
750
  return urlopen(request, context=https_ctx)
736
751
 
737
752
  except HTTPError as exc:
738
753
  # HTTPError can be used as a response-like object
739
754
  if not is_retryable_status(exc.code, exc.headers):
755
+ logger.error(
756
+ f"API Error: {exc.code} {exc.reason} for {request.full_url}"
757
+ )
740
758
  raise # Non-retryable error
741
759
 
742
- if attempt >= MAX_RETRIES - 1:
743
- logger.error(f"HTTP {exc.code} failed after {MAX_RETRIES} attempts")
760
+ if attempt >= max_retries:
761
+ logger.error(
762
+ f"HTTP {exc.code} failed after {max_retries + 1} attempts for {request.full_url}"
763
+ )
744
764
  raise
745
765
 
746
766
  delay = calculate_retry_delay(attempt, exc.headers)
747
767
  logger.warning(
748
- f"HTTP {exc.code}, retrying in {delay:.1f}s "
749
- f"(attempt {attempt + 1}/{MAX_RETRIES})"
768
+ f"HTTP {exc.code} ({exc.reason}), retrying in {delay:.1f}s "
769
+ f"(attempt {attempt + 1}/{max_retries + 1}) for {request.full_url}"
750
770
  )
751
771
  if auth is None and exc.code in (403, 429):
752
772
  logger.info("Hint: Authenticate to raise your GitHub rate limit")
753
773
  time.sleep(delay)
754
774
 
755
775
  except (URLError, socket.error) as e:
756
- if attempt >= MAX_RETRIES - 1:
757
- logger.error(f"Connection error failed after {MAX_RETRIES} attempts: {e}")
776
+ if attempt >= max_retries:
777
+ logger.error(
778
+ f"Connection error failed after {max_retries + 1} attempts: {e} for {request.full_url}"
779
+ )
758
780
  raise
759
781
  delay = calculate_retry_delay(attempt, {})
760
782
  logger.warning(
761
783
  f"Connection error: {e}, retrying in {delay:.1f}s "
762
- f"(attempt {attempt + 1}/{MAX_RETRIES})"
784
+ f"(attempt {attempt + 1}/{max_retries + 1}) for {request.full_url}"
763
785
  )
764
786
  time.sleep(delay)
765
787
 
766
- raise Exception(f"Request failed after {MAX_RETRIES} attempts") # pragma: no cover
788
+ raise Exception(
789
+ f"Request failed after {max_retries + 1} attempts"
790
+ ) # pragma: no cover
767
791
 
768
792
 
769
793
  def _construct_request(per_page, query_args, template, auth, as_app=None, fine=False):
@@ -1579,9 +1603,7 @@ def filter_repositories(args, unfiltered_repositories):
1579
1603
  repositories = [r for r in repositories if not r.get("archived")]
1580
1604
  if args.starred_skip_size_over is not None:
1581
1605
  if args.starred_skip_size_over <= 0:
1582
- logger.warning(
1583
- "--starred-skip-size-over must be greater than 0, ignoring"
1584
- )
1606
+ logger.warning("--starred-skip-size-over must be greater than 0, ignoring")
1585
1607
  else:
1586
1608
  size_limit_kb = args.starred_skip_size_over * 1024
1587
1609
  filtered = []
@@ -1590,7 +1612,9 @@ def filter_repositories(args, unfiltered_repositories):
1590
1612
  size_mb = r.get("size", 0) / 1024
1591
1613
  logger.info(
1592
1614
  "Skipping starred repo {0} ({1:.0f} MB) due to --starred-skip-size-over {2}".format(
1593
- r.get("full_name", r.get("name")), size_mb, args.starred_skip_size_over
1615
+ r.get("full_name", r.get("name")),
1616
+ size_mb,
1617
+ args.starred_skip_size_over,
1594
1618
  )
1595
1619
  )
1596
1620
  else:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: github-backup
3
- Version: 0.59.0
3
+ Version: 0.60.0
4
4
  Summary: backup a github user or organization
5
5
  Home-page: http://github.com/josegonzalez/python-github-backup
6
6
  Author: Jose Diaz-Gonzalez
@@ -182,7 +182,8 @@ CLI Help output::
182
182
  --throttle-limit to be set)
183
183
  --exclude [EXCLUDE ...]
184
184
  names of repositories to exclude
185
-
185
+ --retries MAX_RETRIES
186
+ maximum number of retries for API calls (default: 5)
186
187
 
187
188
  Usage Details
188
189
  =============
@@ -0,0 +1,11 @@
1
+ github_backup/__init__.py,sha256=XCDU0PzRSGNchH5cFRqniZdytnPgXmacSgzeC0BxXiI,23
2
+ github_backup/__main__.py,sha256=3yorCiC63maaxbjdtaeAs-G-yAAxKSO3mprhGfa1Fho,271
3
+ github_backup/cli.py,sha256=ifnOne5AaEI7laofgIq3geURJtvbA-bzxSQgLcmZgd0,2345
4
+ github_backup/github_backup.py,sha256=DJ1mFWELLAZBbM8UXmsHOxin3OBGFIFZk_SXQdhv_7w,82562
5
+ github_backup-0.60.0.dist-info/licenses/LICENSE.txt,sha256=ML-pLyn1qtAKo_DHMcQ8-p7k3f8cp-IR-mf5B49R2rk,1085
6
+ github_backup-0.60.0.dist-info/METADATA,sha256=Lwu1h4UYm6z8OUq9iieqgS_7ITlsB-oQujHB0h302PM,26529
7
+ github_backup-0.60.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
+ github_backup-0.60.0.dist-info/entry_points.txt,sha256=cLPKK7MqC77QVb6HnT2mnyL_f-ciavJG_Y9vVHlCF68,57
9
+ github_backup-0.60.0.dist-info/top_level.txt,sha256=Za6fKRAlxZ0b3ZBH_osPqklLe14LetdNAkvAQANT2AA,14
10
+ github_backup-0.60.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
11
+ github_backup-0.60.0.dist-info/RECORD,,
@@ -1,11 +0,0 @@
1
- github_backup/__init__.py,sha256=HMyZ2jNndR4_RS4EcSAHrVoKBifJVKSLuqmMuKnl_JI,23
2
- github_backup/__main__.py,sha256=3yorCiC63maaxbjdtaeAs-G-yAAxKSO3mprhGfa1Fho,271
3
- github_backup/cli.py,sha256=ifnOne5AaEI7laofgIq3geURJtvbA-bzxSQgLcmZgd0,2345
4
- github_backup/github_backup.py,sha256=VkUaqxAMT3ooQ4xGp54xpUtAJL4h4zZ8b_-EtrFi2T4,81615
5
- github_backup-0.59.0.dist-info/licenses/LICENSE.txt,sha256=ML-pLyn1qtAKo_DHMcQ8-p7k3f8cp-IR-mf5B49R2rk,1085
6
- github_backup-0.59.0.dist-info/METADATA,sha256=_IyD8iBRAXp-Kkz1Ru0pkwjzVNDECjB6jKN6IR3n-Sk,26421
7
- github_backup-0.59.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
- github_backup-0.59.0.dist-info/entry_points.txt,sha256=cLPKK7MqC77QVb6HnT2mnyL_f-ciavJG_Y9vVHlCF68,57
9
- github_backup-0.59.0.dist-info/top_level.txt,sha256=Za6fKRAlxZ0b3ZBH_osPqklLe14LetdNAkvAQANT2AA,14
10
- github_backup-0.59.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
11
- github_backup-0.59.0.dist-info/RECORD,,