PyLinks 0.0.0.dev46__tar.gz → 0.0.0.dev48__tar.gz

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.
Files changed (36) hide show
  1. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/PKG-INFO +3 -3
  2. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/pyproject.toml +3 -3
  3. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/PyLinks.egg-info/PKG-INFO +3 -3
  4. pylinks-0.0.0.dev48/src/PyLinks.egg-info/requires.txt +3 -0
  5. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/api/github.py +153 -42
  6. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/api/zenodo.py +107 -22
  7. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/exception/api.py +10 -4
  8. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/http.py +2 -2
  9. pylinks-0.0.0.dev46/src/PyLinks.egg-info/requires.txt +0 -3
  10. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/README.md +0 -0
  11. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/setup.cfg +0 -0
  12. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/PyLinks.egg-info/SOURCES.txt +0 -0
  13. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/PyLinks.egg-info/dependency_links.txt +0 -0
  14. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/PyLinks.egg-info/not-zip-safe +0 -0
  15. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/PyLinks.egg-info/top_level.txt +0 -0
  16. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/__init__.py +0 -0
  17. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/_settings.py +0 -0
  18. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/api/__init__.py +0 -0
  19. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/api/doi.py +0 -0
  20. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/api/orcid.py +0 -0
  21. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/exception/__init__.py +0 -0
  22. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/exception/base.py +0 -0
  23. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/exception/media_type.py +0 -0
  24. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/exception/uri.py +0 -0
  25. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/media_type.py +0 -0
  26. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/site/__init__.py +0 -0
  27. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/site/binder.py +0 -0
  28. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/site/conda.py +0 -0
  29. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/site/github.py +0 -0
  30. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/site/lib_io.py +0 -0
  31. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/site/pypi.py +0 -0
  32. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/site/readthedocs.py +0 -0
  33. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/string.py +0 -0
  34. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/uri/__init__.py +0 -0
  35. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/uri/data.py +0 -0
  36. {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/url.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyLinks
3
- Version: 0.0.0.dev46
3
+ Version: 0.0.0.dev48
4
4
  Requires-Python: >=3.10
5
5
  Requires-Dist: requests<3,>=2.31.0
6
- Requires-Dist: ExceptionMan==0.0.0.dev33
7
- Requires-Dist: MDit==0.0.0.dev33
6
+ Requires-Dist: ExceptionMan==0.0.0.dev35
7
+ Requires-Dist: MDit==0.0.0.dev35
@@ -17,12 +17,12 @@ namespaces = true
17
17
  # ----------------------------------------- Project Metadata -------------------------------------
18
18
  #
19
19
  [project]
20
- version = "0.0.0.dev46"
20
+ version = "0.0.0.dev48"
21
21
  name = "PyLinks"
22
22
  dependencies = [
23
23
  "requests >= 2.31.0, < 3",
24
- "ExceptionMan == 0.0.0.dev33",
25
- "MDit == 0.0.0.dev33",
24
+ "ExceptionMan == 0.0.0.dev35",
25
+ "MDit == 0.0.0.dev35",
26
26
  ]
27
27
  requires-python = ">=3.10"
28
28
 
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyLinks
3
- Version: 0.0.0.dev46
3
+ Version: 0.0.0.dev48
4
4
  Requires-Python: >=3.10
5
5
  Requires-Dist: requests<3,>=2.31.0
6
- Requires-Dist: ExceptionMan==0.0.0.dev33
7
- Requires-Dist: MDit==0.0.0.dev33
6
+ Requires-Dist: ExceptionMan==0.0.0.dev35
7
+ Requires-Dist: MDit==0.0.0.dev35
@@ -0,0 +1,3 @@
1
+ requests<3,>=2.31.0
2
+ ExceptionMan==0.0.0.dev35
3
+ MDit==0.0.0.dev35
@@ -1,5 +1,6 @@
1
1
  # Standard libraries
2
- from typing import Optional, Literal
2
+ from __future__ import annotations as _annotations
3
+ from typing import TYPE_CHECKING as _TYPE_CHECKING
3
4
  from pathlib import Path
4
5
  import re
5
6
  import mimetypes
@@ -7,6 +8,9 @@ import mimetypes
7
8
  # Non-standard libraries
8
9
  import pylinks as _pylinks
9
10
 
11
+ if _TYPE_CHECKING:
12
+ from typing import Optional, Literal, Any
13
+
10
14
 
11
15
  class GitHub:
12
16
  """GitHub API
@@ -38,13 +42,22 @@ class GitHub:
38
42
  def graphql_query(
39
43
  self,
40
44
  query: str,
45
+ variables: dict[str, tuple[Any, str, bool]] | None = None,
41
46
  extra_headers: dict | None = None,
42
47
  ) -> dict:
43
48
  headers = self._headers | extra_headers if extra_headers else self._headers
49
+ if variables:
50
+ args = ", ".join(
51
+ f"${name}:{typ}{"!" if required else ""}" for name, (_, typ, required) in variables.items()
52
+ )
53
+ sig = f"query({args})"
54
+ else:
55
+ sig = "query"
44
56
  response = _pylinks.http.graphql_query(
45
57
  url=self._endpoint["api"] / "graphql",
46
- query=f"{{{query}}}",
58
+ query=f"{sig} {{{query}}}",
47
59
  headers=headers,
60
+ variables={name: value for name, (value, _, _) in variables.items()} if variables else None
48
61
  )
49
62
  return response
50
63
 
@@ -166,6 +179,18 @@ class Repo:
166
179
  endpoint=endpoint
167
180
  )
168
181
 
182
+ def _graphql_query(
183
+ self,
184
+ payload: str,
185
+ variables: dict[str, tuple[Any, str, bool]] | None = None,
186
+ extra_headers: dict | None = None,
187
+ ) -> dict:
188
+ return self._github.graphql_query(
189
+ query=f'repository(name: "{self._name}", owner: "{self._username}") {{{payload}}}',
190
+ variables=variables,
191
+ extra_headers=extra_headers,
192
+ )["repository"]
193
+
169
194
  @property
170
195
  def username(self) -> str:
171
196
  return self._username
@@ -412,13 +437,9 @@ class Repo:
412
437
  - [GitHub Docs](https://docs.github.com/en/graphql/guides/using-the-graphql-api-for-discussions)
413
438
  -
414
439
  """
415
- payload = "{discussionCategories(first: 100) {edges {node {name, slug, id}}}}"
416
- query = f'repository(name: "{self._name}", owner: "{self._username}") {payload}'
417
- data = self._github.graphql_query(query)
418
- discussions = [
419
- entry["node"]
420
- for entry in data["repository"]["discussionCategories"]["edges"]
421
- ]
440
+ payload = "discussionCategories(first: 100) {edges {node {name, slug, id}}}"
441
+ data = self._graphql_query(payload)
442
+ discussions = [entry["node"] for entry in data["discussionCategories"]["edges"]]
422
443
  return discussions
423
444
 
424
445
  def issue(self, number: int) -> dict:
@@ -610,7 +631,14 @@ class Repo:
610
631
  """
611
632
  return self._rest_query(f"pulls/{number}")
612
633
 
613
- def pull_commits(self, number: int) -> list[dict]:
634
+ def pull_commits(
635
+ self,
636
+ number: int,
637
+ count: int = 0,
638
+ cursor_before: str | None = None,
639
+ cursor_after: str | None = None,
640
+ sort: Literal["first", "last"] = "last",
641
+ ) -> list[dict]:
614
642
  """
615
643
  Get a list of commits for a pull request.
616
644
 
@@ -629,15 +657,52 @@ class Repo:
629
657
  ----------
630
658
  - [GitHub API Docs](https://docs.github.com/en/rest/pulls/commits?apiVersion=2022-11-28#list-commits-on-a-pull-request)
631
659
  """
632
- commits = []
633
- page = 1
660
+ def post_process():
661
+ out = []
662
+ for datum in data:
663
+ commits = datum["pullRequest"]["commits"]["nodes"]
664
+ if sort == "last":
665
+ commits = reversed(commits)
666
+ for commit in commits:
667
+ commit["commit"]["authors"] = commit["commit"]["authors"]["nodes"]
668
+ out.append(commit)
669
+ return out
670
+
671
+ git_actor_fields = "{name, email, date user {id, login}}"
672
+ commit_fields = f"{{abbreviatedOid, additions, deletions, authors(first: 100) {{nodes {git_actor_fields}}}, committer {git_actor_fields}, authoredByCommitter, authoredDate, committedDate, message, messageBody, messageHeadline, oid, id, resourcePath, url}}"
673
+ page_info_fields = "pageInfo {startCursor, endCursor, hasNextPage, hasPreviousPage}"
674
+ commits_args = ["after: $after", "before: $before", f"{sort}: {100 if count <= 0 else min(count, 100)}"]
675
+ commits_fields = f"nodes {{id, resourcePath, url, commit {commit_fields} }}"
676
+ commits_sig = f"commits({", ".join(commits_args)})"
677
+ payload = f"pullRequest(number: {number}) {{ {commits_sig} {{ {commits_fields} {page_info_fields} }} }}"
678
+ variables = {
679
+ "after": (cursor_after, "String", False),
680
+ "before": (cursor_before, "String", False),
681
+ }
682
+ data = [self._graphql_query(payload, variables)]
683
+ total_downloaded = 100
634
684
  while True:
635
- response = self._rest_query(f"pulls/{number}/commits?per_page=100&page={page}")
636
- commits.extend(response)
637
- page += 1
638
- if len(response) < 100:
639
- break
640
- return commits
685
+ page_info = data[-1]["pullRequest"]["commits"]["pageInfo"]
686
+ if count <= 0:
687
+ must_continue = page_info["hasNextPage" if sort == "first" else "hasPreviousPage"]
688
+ if not must_continue:
689
+ return post_process()
690
+ elif total_downloaded >= count:
691
+ return post_process()
692
+ else:
693
+ variables[sort] = page_info["endCursor" if sort == "first" else "startCursor"]
694
+ data.append(self._graphql_query(payload, variables))
695
+ total_downloaded += 100
696
+
697
+ # commits = []
698
+ # page = 1
699
+ # while True:
700
+ # response = self._rest_query(f"pulls/{number}/commits?per_page=100&page={page}")
701
+ # commits.extend(response)
702
+ # page += 1
703
+ # if len(response) < 100:
704
+ # break
705
+ # return commits
641
706
 
642
707
  def pull_create(
643
708
  self,
@@ -1030,15 +1095,22 @@ class Repo:
1030
1095
  raise ValueError("At least one of 'new_name', 'color', or 'description' must be specified.")
1031
1096
  return self._rest_query(query=f"labels/{name}", verb="PATCH", json=data)
1032
1097
 
1098
+ def release_get(self, release_id: int) -> dict:
1099
+ return self._rest_query(query=f"releases/{release_id}")
1100
+
1101
+ def release_delete(self, release_id: int) -> None:
1102
+ self._rest_query(query=f"releases/{release_id}", verb="DELETE", response_type=None)
1103
+ return
1104
+
1033
1105
  def release_create(
1034
1106
  self,
1035
1107
  tag_name: str,
1036
- name: str,
1037
- body: str,
1038
- target_commitish: str = "",
1108
+ name: str | None = None,
1109
+ body: str | None = None,
1110
+ target_commitish: str | None = None,
1039
1111
  draft: bool = False,
1040
1112
  prerelease: bool = False,
1041
- discussion_category_name: str = "",
1113
+ discussion_category_name: str | None = None,
1042
1114
  generate_release_notes: bool = False,
1043
1115
  make_latest: Literal['true', 'false', 'legacy'] = 'true'
1044
1116
  ):
@@ -1078,22 +1150,62 @@ class Repo:
1078
1150
  ----------
1079
1151
  [GitHub API Docs](https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#create-a-release)
1080
1152
  """
1081
- data = {
1082
- "tag_name": tag_name,
1083
- "draft": draft,
1084
- "prerelease": prerelease,
1085
- "generate_release_notes": generate_release_notes,
1086
- "make_latest": make_latest,
1087
- }
1088
- if name:
1089
- data["name"] = name
1090
- if body:
1091
- data["body"] = body
1092
- if target_commitish:
1093
- data["target_commitish"] = target_commitish
1094
- if discussion_category_name:
1095
- data["discussion_category_name"] = discussion_category_name
1096
- return self._rest_query(query="releases", verb="POST", json=data)
1153
+ data = {k: v for k, v in locals().items() if k != "self" and v is not None}
1154
+ return self._rest_query(query=f"releases", verb="POST", json=data)
1155
+
1156
+ def release_update(
1157
+ self,
1158
+ release_id: int,
1159
+ tag_name: str | None = None,
1160
+ name: str | None = None,
1161
+ body: str | None = None,
1162
+ target_commitish: str | None = None,
1163
+ draft: bool | None = None,
1164
+ prerelease: bool | None = None,
1165
+ discussion_category_name: str | None = None,
1166
+ make_latest: Literal['true', 'false', 'legacy'] | None = None
1167
+ ):
1168
+ """Update a release.
1169
+
1170
+ Parameters
1171
+ ----------
1172
+ tag_name : str
1173
+ The name of the tag.
1174
+ name : str
1175
+ The name of the release.
1176
+ body : str
1177
+ The body of the release post, i.e. text describing the release.
1178
+ target_commitish : str, optional
1179
+ The commitish value that determines where the Git tag is created from.
1180
+ Can be any branch or commit SHA. Unused if the Git tag already exists.
1181
+ The default is the repository's default branch.
1182
+ draft : bool, optional
1183
+ `True` to create a draft (unpublished) release, `False` to create a published one.
1184
+ prerelease : bool, default: False
1185
+ `True` to identify the release as a prerelease, `False` to identify it as a full release.
1186
+ discussion_category_name : str, optional
1187
+ The name of the discussion category for the release, to be created and linked to the release.
1188
+ The value must be a category that already exists in the repository.
1189
+ make_latest : {'true', 'false', 'legacy'}, optional
1190
+ Whether this release should be set as the latest release for the repository.
1191
+ Drafts and prereleases cannot be set as latest.
1192
+ Defaults to 'true' for newly published releases.
1193
+ 'legacy' specifies that the latest release should be determined based on
1194
+ the release creation date and higher semantic version.
1195
+
1196
+ References
1197
+ ----------
1198
+ [GitHub API Docs](https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#create-a-release)
1199
+ """
1200
+ data = {k: v for k, v in locals().items() if k not in ("self", "release_id") and v is not None}
1201
+ return self._rest_query(query=f"releases/{release_id}", verb="PATCH", json=data)
1202
+
1203
+ def release_asset_list(self, release_id: int) -> list[dict]:
1204
+ return self._rest_query(query=f"releases/{release_id}/assets")
1205
+
1206
+ def release_asset_delete(self, asset_id: int) -> None:
1207
+ self._rest_query(query=f"releases/assets/{asset_id}", verb="DELETE", response_type=None)
1208
+ return
1097
1209
 
1098
1210
  def release_asset_upload(
1099
1211
  self,
@@ -1683,10 +1795,9 @@ class Repo:
1683
1795
  ----------
1684
1796
  - [GitHub API Docs](https://docs.github.com/en/graphql/reference/objects#branchprotectionruleconnection)
1685
1797
  """
1686
- payload = "{branchProtectionRules(first: 100) {nodes {id, pattern}}}"
1687
- query = f'repository(name: "{self._name}", owner: "{self._username}") {payload}'
1688
- data = self._github.graphql_query(query)
1689
- return data["repository"]["branchProtectionRules"]["nodes"]
1798
+ payload = "branchProtectionRules(first: 100) {nodes {id, pattern}}"
1799
+ data = self._graphql_query(payload)
1800
+ return data["branchProtectionRules"]["nodes"]
1690
1801
 
1691
1802
  def branch_protection_rule_create(
1692
1803
  self,
@@ -18,37 +18,90 @@ class Zenodo:
18
18
  """
19
19
  def __init__(self, token: str, sandbox: bool = False):
20
20
  self._sandbox = sandbox
21
- self._url = _pylinks.url.create("https://zenodo.org/api")
22
- self._url_sandbox = _pylinks.url.create("https://sandbox.zenodo.org/api")
23
- self._headers = {"Authorization": token}
21
+ self._url = _pylinks.url.create(
22
+ "https://sandbox.zenodo.org/api" if sandbox else "https://zenodo.org/api"
23
+ )
24
+ self._headers = {"Authorization": f"Bearer {token}"}
24
25
  return
25
26
 
26
27
  def rest_query(
27
28
  self,
28
29
  query: str,
29
30
  verb: Literal["GET", "POST", "PUT", "PATCH", "OPTIONS", "DELETE"] = "GET",
31
+ params: dict | None = None,
30
32
  data = None,
31
33
  json = None,
32
34
  content_type: str | None = "application/json",
33
- sandbox: bool | None = None,
35
+ response_type: Literal["str", "json", "bytes"] | None = "json"
34
36
  ) -> dict | list:
35
- sandbox = sandbox if sandbox is not None else self._sandbox
36
- base_url = self._url_sandbox if sandbox else self._url
37
37
  content_header = {"Content-Type": content_type} if content_type else {}
38
38
  return _pylinks.http.request(
39
- url=base_url / query,
39
+ url=self._url / query,
40
40
  verb=verb,
41
+ params=params,
41
42
  data=data,
42
43
  json=json,
43
44
  headers=self._headers | content_header,
44
- response_type="json", # All responses are JSON (https://developers.zenodo.org/#responses)
45
+ response_type=response_type, # All responses are JSON (https://developers.zenodo.org/#responses)
45
46
  )
46
47
 
47
- def deposition_create(
48
+ def create_and_publish(
48
49
  self,
49
- metadata: dict | None = None,
50
- sandbox: bool | None = None,
51
- ) -> dict:
50
+ metadata: dict,
51
+ files: list[str | _Path | tuple[str | _Path, str]],
52
+ previous_id: str | int | None = None
53
+ ):
54
+ def add_files(bucket_id: str):
55
+ for file in files:
56
+ if not isinstance(file, (str, _Path)):
57
+ filepath = file[0]
58
+ name = file[1]
59
+ else:
60
+ filepath = file
61
+ name = None
62
+ self.file_create(
63
+ bucket_id=bucket_id,
64
+ filepath=filepath,
65
+ name=name,
66
+ )
67
+ return
68
+
69
+ if not previous_id:
70
+ new_depo = self.deposition_create(metadata=metadata)
71
+ add_files(bucket_id=new_depo["links"]["bucket"])
72
+ return self.deposition_publish(new_depo["id"])
73
+ new_ver = self.deposition_new_version(deposition_id=previous_id)
74
+ for previous_file in new_ver["files"]:
75
+ self.file_delete(deposition_id=new_ver["id"], file_id=previous_file["id"])
76
+ add_files(new_ver["links"]["bucket"])
77
+ return self.deposition_publish(new_ver["id"])
78
+
79
+
80
+ def deposition_list(
81
+ self,
82
+ query: str | None = None,
83
+ status: Literal["draft", "published"] | None = None,
84
+ sort: Literal["bestmatch", "mostrecent", "-bestmatch", "-mostrecent"] | None = None,
85
+ page: int | None = None,
86
+ size: int | None = None,
87
+ all_versions: bool | None = None,
88
+ ):
89
+ params = {k: v for k, v in locals().items() if k not in ("self", "query") and v}
90
+ if query:
91
+ params["q"] = query
92
+ return self.rest_query(
93
+ "deposit/depositions",
94
+ verb="GET",
95
+ params=params,
96
+ )
97
+
98
+ def deposition_retrieve(self, deposition_id: str | int):
99
+ return self.rest_query(
100
+ f"deposit/depositions/{deposition_id}",
101
+ verb="GET",
102
+ )
103
+
104
+ def deposition_create(self, metadata: dict | None = None) -> dict:
52
105
  """Create a new deposition.
53
106
 
54
107
  Returns
@@ -91,25 +144,51 @@ class Zenodo:
91
144
  return self.rest_query(
92
145
  query="deposit/depositions",
93
146
  verb="POST",
94
- json=metadata or {},
95
- sandbox=sandbox,
147
+ json={"metadata": metadata} if metadata else {},
96
148
  )
97
149
 
98
- def deposition_publish(self, deposition_id: str) -> dict:
150
+ def deposition_new_version(self, deposition_id: int | str):
151
+ """Create a new version of a deposition as a draft."""
152
+ return self.rest_query(
153
+ query=f"deposit/depositions/{deposition_id}/actions/newversion",
154
+ verb="POST",
155
+ )
156
+
157
+ def deposition_update(self, deposition_id: int | str, metadata: dict):
158
+ """Update and existing deposition."""
159
+ return self.rest_query(
160
+ query=f"deposit/depositions/{deposition_id}",
161
+ verb="PUT",
162
+ json={"metadata": metadata},
163
+ )
164
+
165
+ def deposition_publish(self, deposition_id: int | str) -> dict:
99
166
  """Publish a deposition."""
100
167
  return self.rest_query(
101
168
  query=f"deposit/depositions/{deposition_id}/actions/publish",
102
169
  verb="POST",
103
170
  )
104
171
 
172
+ def file_list(self, deposition_id: str | int):
173
+ return self.rest_query(
174
+ f"deposit/depositions/{deposition_id}/files",
175
+ verb="GET"
176
+ )
177
+
105
178
  def file_create(
106
179
  self,
107
- bucket_url: str | _pylinks.url.URL,
180
+ bucket_id: str,
108
181
  filepath: str | _Path,
109
- upload_path: str | None = None,
182
+ name: str | None = None,
110
183
  ) -> dict:
111
184
  """Upload a file to a Zenodo bucket.
112
185
 
186
+ Parameters
187
+ ----------
188
+ bucket_id
189
+ Bucket ID (e.g., `"d7524553-7f8c-4632-bffb-8bea6a90b88b"`)
190
+ or bucket URL (e.g., `"https://zenodo.org/api/files/d7524553-7f8c-4632-bffb-8bea6a90b88b"`)
191
+
113
192
  Returns
114
193
  -------
115
194
 
@@ -134,14 +213,20 @@ class Zenodo:
134
213
  }
135
214
  :::
136
215
  """
137
- if not isinstance(bucket_url, _pylinks.url.URL):
138
- bucket_url = _pylinks.url.create(bucket_url)
216
+ bucket_id = bucket_id.removeprefix(f"{self._url}/files/")
139
217
  filepath = _Path(filepath)
140
- upload_path = upload_path or filepath.name
218
+ name = name or filepath.name
141
219
  with open(filepath, "rb") as file:
142
220
  return self.rest_query(
143
- query=bucket_url / upload_path,
221
+ query=f"files/{bucket_id}/{name}",
144
222
  verb="PUT",
145
223
  data=file,
146
224
  content_type=None,
147
- )
225
+ )
226
+
227
+ def file_delete(self, deposition_id: str | int, file_id: str | int):
228
+ return self.rest_query(
229
+ f"deposit/depositions/{deposition_id}/files/{file_id}",
230
+ verb="DELETE",
231
+ response_type=None,
232
+ )
@@ -101,7 +101,7 @@ class GraphQLResponseError(WebAPIError):
101
101
  Exception class for GraphQL
102
102
  """
103
103
 
104
- def __init__(self, response: dict):
104
+ def __init__(self, response: dict, query: str):
105
105
  if "errors" in response:
106
106
  intro = "GraphQL response contains errors."
107
107
  elif "data" not in response:
@@ -109,10 +109,16 @@ class GraphQLResponseError(WebAPIError):
109
109
  super().__init__(
110
110
  title="GraphQL Response Error",
111
111
  intro=intro,
112
- details=_mdit.element.code_block(
113
- json.dumps(response, indent=3), language="json", caption="GraphQL Response"
114
- ),
112
+ details=_mdit.block_container(
113
+ _mdit.element.code_block(
114
+ json.dumps(response, indent=3), language="json", caption="GraphQL Response"
115
+ ),
116
+ _mdit.element.code_block(
117
+ query, language="graphql", caption="GraphQL Query"
118
+ ),
119
+ )
115
120
  )
121
+ self.query = query
116
122
  return
117
123
 
118
124
 
@@ -277,9 +277,9 @@ def graphql_query(
277
277
  if isinstance(response, dict):
278
278
  error_title = "GraphQL API Error"
279
279
  if "errors" in response:
280
- raise _exception.GraphQLResponseError(response)
280
+ raise _exception.GraphQLResponseError(response, query)
281
281
  elif "data" not in response:
282
- raise _exception.GraphQLResponseError(response)
282
+ raise _exception.GraphQLResponseError(response, query)
283
283
  else:
284
284
  response = response["data"]
285
285
  return response
@@ -1,3 +0,0 @@
1
- requests<3,>=2.31.0
2
- ExceptionMan==0.0.0.dev33
3
- MDit==0.0.0.dev33
File without changes
File without changes