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.
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/PKG-INFO +3 -3
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/pyproject.toml +3 -3
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/PyLinks.egg-info/PKG-INFO +3 -3
- pylinks-0.0.0.dev48/src/PyLinks.egg-info/requires.txt +3 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/api/github.py +153 -42
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/api/zenodo.py +107 -22
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/exception/api.py +10 -4
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/http.py +2 -2
- pylinks-0.0.0.dev46/src/PyLinks.egg-info/requires.txt +0 -3
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/README.md +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/setup.cfg +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/PyLinks.egg-info/SOURCES.txt +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/PyLinks.egg-info/dependency_links.txt +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/PyLinks.egg-info/not-zip-safe +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/PyLinks.egg-info/top_level.txt +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/__init__.py +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/_settings.py +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/api/__init__.py +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/api/doi.py +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/api/orcid.py +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/exception/__init__.py +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/exception/base.py +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/exception/media_type.py +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/exception/uri.py +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/media_type.py +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/site/__init__.py +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/site/binder.py +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/site/conda.py +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/site/github.py +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/site/lib_io.py +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/site/pypi.py +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/site/readthedocs.py +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/string.py +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/uri/__init__.py +0 -0
- {pylinks-0.0.0.dev46 → pylinks-0.0.0.dev48}/src/pylinks/uri/data.py +0 -0
- {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.
|
|
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.
|
|
7
|
-
Requires-Dist: MDit==0.0.0.
|
|
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.
|
|
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.
|
|
25
|
-
"MDit == 0.0.0.
|
|
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.
|
|
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.
|
|
7
|
-
Requires-Dist: MDit==0.0.0.
|
|
6
|
+
Requires-Dist: ExceptionMan==0.0.0.dev35
|
|
7
|
+
Requires-Dist: MDit==0.0.0.dev35
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# Standard libraries
|
|
2
|
-
from
|
|
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 = "
|
|
416
|
-
|
|
417
|
-
|
|
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(
|
|
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
|
-
|
|
633
|
-
|
|
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
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
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
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
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 = "
|
|
1687
|
-
|
|
1688
|
-
data
|
|
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(
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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=
|
|
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=
|
|
45
|
+
response_type=response_type, # All responses are JSON (https://developers.zenodo.org/#responses)
|
|
45
46
|
)
|
|
46
47
|
|
|
47
|
-
def
|
|
48
|
+
def create_and_publish(
|
|
48
49
|
self,
|
|
49
|
-
metadata: dict
|
|
50
|
-
|
|
51
|
-
|
|
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
|
|
95
|
-
sandbox=sandbox,
|
|
147
|
+
json={"metadata": metadata} if metadata else {},
|
|
96
148
|
)
|
|
97
149
|
|
|
98
|
-
def
|
|
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
|
-
|
|
180
|
+
bucket_id: str,
|
|
108
181
|
filepath: str | _Path,
|
|
109
|
-
|
|
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
|
-
|
|
138
|
-
bucket_url = _pylinks.url.create(bucket_url)
|
|
216
|
+
bucket_id = bucket_id.removeprefix(f"{self._url}/files/")
|
|
139
217
|
filepath = _Path(filepath)
|
|
140
|
-
|
|
218
|
+
name = name or filepath.name
|
|
141
219
|
with open(filepath, "rb") as file:
|
|
142
220
|
return self.rest_query(
|
|
143
|
-
query=
|
|
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.
|
|
113
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|