github-backup 0.51.2__py3-none-any.whl → 0.52.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.51.2"
1
+ __version__ = "0.52.0"
@@ -37,6 +37,15 @@ FNULL = open(os.devnull, "w")
37
37
  FILE_URI_PREFIX = "file://"
38
38
  logger = logging.getLogger(__name__)
39
39
 
40
+
41
+ class RepositoryUnavailableError(Exception):
42
+ """Raised when a repository is unavailable due to legal reasons (e.g., DMCA takedown)."""
43
+
44
+ def __init__(self, message, dmca_url=None):
45
+ super().__init__(message)
46
+ self.dmca_url = dmca_url
47
+
48
+
40
49
  # Setup SSL context with fallback chain
41
50
  https_ctx = ssl.create_default_context()
42
51
  if https_ctx.get_ca_certs():
@@ -592,27 +601,39 @@ def retrieve_data_gen(args, template, query_args=None, single_request=False):
592
601
  auth = get_auth(args, encode=not args.as_app)
593
602
  query_args = get_query_args(query_args)
594
603
  per_page = 100
595
- page = 0
604
+ next_url = None
596
605
 
597
606
  while True:
598
607
  if single_request:
599
- request_page, request_per_page = None, None
608
+ request_per_page = None
600
609
  else:
601
- page = page + 1
602
- request_page, request_per_page = page, per_page
610
+ request_per_page = per_page
603
611
 
604
612
  request = _construct_request(
605
613
  request_per_page,
606
- request_page,
607
614
  query_args,
608
- template,
615
+ next_url or template,
609
616
  auth,
610
617
  as_app=args.as_app,
611
618
  fine=True if args.token_fine is not None else False,
612
619
  ) # noqa
613
- r, errors = _get_response(request, auth, template)
620
+ r, errors = _get_response(request, auth, next_url or template)
614
621
 
615
622
  status_code = int(r.getcode())
623
+
624
+ # Handle DMCA takedown (HTTP 451) - raise exception to skip entire repository
625
+ if status_code == 451:
626
+ dmca_url = None
627
+ try:
628
+ response_data = json.loads(r.read().decode("utf-8"))
629
+ dmca_url = response_data.get("block", {}).get("html_url")
630
+ except Exception:
631
+ pass
632
+ raise RepositoryUnavailableError(
633
+ "Repository unavailable due to legal reasons (HTTP 451)",
634
+ dmca_url=dmca_url
635
+ )
636
+
616
637
  # Check if we got correct data
617
638
  try:
618
639
  response = json.loads(r.read().decode("utf-8"))
@@ -644,15 +665,14 @@ def retrieve_data_gen(args, template, query_args=None, single_request=False):
644
665
  retries += 1
645
666
  time.sleep(5)
646
667
  request = _construct_request(
647
- per_page,
648
- page,
668
+ request_per_page,
649
669
  query_args,
650
- template,
670
+ next_url or template,
651
671
  auth,
652
672
  as_app=args.as_app,
653
673
  fine=True if args.token_fine is not None else False,
654
674
  ) # noqa
655
- r, errors = _get_response(request, auth, template)
675
+ r, errors = _get_response(request, auth, next_url or template)
656
676
 
657
677
  status_code = int(r.getcode())
658
678
  try:
@@ -682,7 +702,16 @@ def retrieve_data_gen(args, template, query_args=None, single_request=False):
682
702
  if type(response) is list:
683
703
  for resp in response:
684
704
  yield resp
685
- if len(response) < per_page:
705
+ # Parse Link header for next page URL (cursor-based pagination)
706
+ link_header = r.headers.get("Link", "")
707
+ next_url = None
708
+ if link_header:
709
+ # Parse Link header: <https://api.github.com/...?per_page=100&after=cursor>; rel="next"
710
+ for link in link_header.split(","):
711
+ if 'rel="next"' in link:
712
+ next_url = link[link.find("<") + 1:link.find(">")]
713
+ break
714
+ if not next_url:
686
715
  break
687
716
  elif type(response) is dict and single_request:
688
717
  yield response
@@ -735,22 +764,27 @@ def _get_response(request, auth, template):
735
764
 
736
765
 
737
766
  def _construct_request(
738
- per_page, page, query_args, template, auth, as_app=None, fine=False
767
+ per_page, query_args, template, auth, as_app=None, fine=False
739
768
  ):
740
- all_query_args = {}
741
- if per_page:
742
- all_query_args["per_page"] = per_page
743
- if page:
744
- all_query_args["page"] = page
745
- if query_args:
746
- all_query_args.update(query_args)
747
-
748
- request_url = template
749
- if all_query_args:
750
- querystring = urlencode(all_query_args)
751
- request_url = template + "?" + querystring
769
+ # If template is already a full URL with query params (from Link header), use it directly
770
+ if "?" in template and template.startswith("http"):
771
+ request_url = template
772
+ # Extract query string for logging
773
+ querystring = template.split("?", 1)[1]
752
774
  else:
753
- querystring = ""
775
+ # Build URL with query parameters
776
+ all_query_args = {}
777
+ if per_page:
778
+ all_query_args["per_page"] = per_page
779
+ if query_args:
780
+ all_query_args.update(query_args)
781
+
782
+ request_url = template
783
+ if all_query_args:
784
+ querystring = urlencode(all_query_args)
785
+ request_url = template + "?" + querystring
786
+ else:
787
+ querystring = ""
754
788
 
755
789
  request = Request(request_url)
756
790
  if auth is not None:
@@ -766,7 +800,7 @@ def _construct_request(
766
800
  "Accept", "application/vnd.github.machine-man-preview+json"
767
801
  )
768
802
 
769
- log_url = template
803
+ log_url = template if "?" not in template else template.split("?")[0]
770
804
  if querystring:
771
805
  log_url += "?" + querystring
772
806
  logger.info("Requesting {}".format(log_url))
@@ -843,8 +877,7 @@ def download_file(url, path, auth, as_app=False, fine=False):
843
877
  return
844
878
 
845
879
  request = _construct_request(
846
- per_page=100,
847
- page=1,
880
+ per_page=None,
848
881
  query_args={},
849
882
  template=url,
850
883
  auth=auth,
@@ -1657,40 +1690,47 @@ def backup_repositories(args, output_directory, repositories):
1657
1690
 
1658
1691
  continue # don't try to back anything else for a gist; it doesn't exist
1659
1692
 
1660
- download_wiki = args.include_wiki or args.include_everything
1661
- if repository["has_wiki"] and download_wiki:
1662
- fetch_repository(
1663
- repository["name"],
1664
- repo_url.replace(".git", ".wiki.git"),
1665
- os.path.join(repo_cwd, "wiki"),
1666
- skip_existing=args.skip_existing,
1667
- bare_clone=args.bare_clone,
1668
- lfs_clone=args.lfs_clone,
1669
- no_prune=args.no_prune,
1670
- )
1671
- if args.include_issues or args.include_everything:
1672
- backup_issues(args, repo_cwd, repository, repos_template)
1693
+ try:
1694
+ download_wiki = args.include_wiki or args.include_everything
1695
+ if repository["has_wiki"] and download_wiki:
1696
+ fetch_repository(
1697
+ repository["name"],
1698
+ repo_url.replace(".git", ".wiki.git"),
1699
+ os.path.join(repo_cwd, "wiki"),
1700
+ skip_existing=args.skip_existing,
1701
+ bare_clone=args.bare_clone,
1702
+ lfs_clone=args.lfs_clone,
1703
+ no_prune=args.no_prune,
1704
+ )
1705
+ if args.include_issues or args.include_everything:
1706
+ backup_issues(args, repo_cwd, repository, repos_template)
1673
1707
 
1674
- if args.include_pulls or args.include_everything:
1675
- backup_pulls(args, repo_cwd, repository, repos_template)
1708
+ if args.include_pulls or args.include_everything:
1709
+ backup_pulls(args, repo_cwd, repository, repos_template)
1676
1710
 
1677
- if args.include_milestones or args.include_everything:
1678
- backup_milestones(args, repo_cwd, repository, repos_template)
1711
+ if args.include_milestones or args.include_everything:
1712
+ backup_milestones(args, repo_cwd, repository, repos_template)
1679
1713
 
1680
- if args.include_labels or args.include_everything:
1681
- backup_labels(args, repo_cwd, repository, repos_template)
1714
+ if args.include_labels or args.include_everything:
1715
+ backup_labels(args, repo_cwd, repository, repos_template)
1682
1716
 
1683
- if args.include_hooks or args.include_everything:
1684
- backup_hooks(args, repo_cwd, repository, repos_template)
1717
+ if args.include_hooks or args.include_everything:
1718
+ backup_hooks(args, repo_cwd, repository, repos_template)
1685
1719
 
1686
- if args.include_releases or args.include_everything:
1687
- backup_releases(
1688
- args,
1689
- repo_cwd,
1690
- repository,
1691
- repos_template,
1692
- include_assets=args.include_assets or args.include_everything,
1693
- )
1720
+ if args.include_releases or args.include_everything:
1721
+ backup_releases(
1722
+ args,
1723
+ repo_cwd,
1724
+ repository,
1725
+ repos_template,
1726
+ include_assets=args.include_assets or args.include_everything,
1727
+ )
1728
+ except RepositoryUnavailableError as e:
1729
+ logger.warning(f"Repository {repository['full_name']} is unavailable (HTTP 451)")
1730
+ if e.dmca_url:
1731
+ logger.warning(f"DMCA notice: {e.dmca_url}")
1732
+ logger.info(f"Skipping remaining resources for {repository['full_name']}")
1733
+ continue
1694
1734
 
1695
1735
  if args.incremental:
1696
1736
  if last_update == "0000-00-00T00:00:00Z":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: github-backup
3
- Version: 0.51.2
3
+ Version: 0.52.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
@@ -0,0 +1,9 @@
1
+ github_backup/__init__.py,sha256=uXy9jtvBCb6cwZyNng8d7tehPtkx2Xcdr7Lm3SEzEF8,23
2
+ github_backup/github_backup.py,sha256=eo8P69vIgeP_ntzY4DdGrzFnEKPIoqBrgBX3Dw_2lIo,77487
3
+ github_backup-0.52.0.data/scripts/github-backup,sha256=I79TNA67phytRmI9nm_LlD9h2zauXFOsOn9tYyAy9pU,1546
4
+ github_backup-0.52.0.dist-info/licenses/LICENSE.txt,sha256=ML-pLyn1qtAKo_DHMcQ8-p7k3f8cp-IR-mf5B49R2rk,1085
5
+ github_backup-0.52.0.dist-info/METADATA,sha256=92rNj52N87cGNzBGRJYCyaotJIEgZ5ZgZ-DUugpuvlI,21544
6
+ github_backup-0.52.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
+ github_backup-0.52.0.dist-info/top_level.txt,sha256=Za6fKRAlxZ0b3ZBH_osPqklLe14LetdNAkvAQANT2AA,14
8
+ github_backup-0.52.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
9
+ github_backup-0.52.0.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- github_backup/__init__.py,sha256=2AuLzuHMBBPVOM-m0Woty11pTO9ExwPKWRWtj1LvS-g,23
2
- github_backup/github_backup.py,sha256=ousskr9hBtfKgAh1x78vnSefQhPGqa9K2fLxWLUKdyY,75428
3
- github_backup-0.51.2.data/scripts/github-backup,sha256=I79TNA67phytRmI9nm_LlD9h2zauXFOsOn9tYyAy9pU,1546
4
- github_backup-0.51.2.dist-info/licenses/LICENSE.txt,sha256=ML-pLyn1qtAKo_DHMcQ8-p7k3f8cp-IR-mf5B49R2rk,1085
5
- github_backup-0.51.2.dist-info/METADATA,sha256=zoLPP4sNYwAsnFUXTZR1K1NB5J-i2xQidc9DZ3hbkfk,21544
6
- github_backup-0.51.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
- github_backup-0.51.2.dist-info/top_level.txt,sha256=Za6fKRAlxZ0b3ZBH_osPqklLe14LetdNAkvAQANT2AA,14
8
- github_backup-0.51.2.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
9
- github_backup-0.51.2.dist-info/RECORD,,