zennewpy 0.1.0__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.
- zennewpy-0.1.0/LICENSE +0 -0
- zennewpy-0.1.0/PKG-INFO +107 -0
- zennewpy-0.1.0/README.md +90 -0
- zennewpy-0.1.0/setup.cfg +4 -0
- zennewpy-0.1.0/setup.py +22 -0
- zennewpy-0.1.0/zennewpy/__init__.py +2 -0
- zennewpy-0.1.0/zennewpy/zennewpy_core.py +756 -0
- zennewpy-0.1.0/zennewpy.egg-info/PKG-INFO +107 -0
- zennewpy-0.1.0/zennewpy.egg-info/SOURCES.txt +10 -0
- zennewpy-0.1.0/zennewpy.egg-info/dependency_links.txt +1 -0
- zennewpy-0.1.0/zennewpy.egg-info/requires.txt +2 -0
- zennewpy-0.1.0/zennewpy.egg-info/top_level.txt +1 -0
zennewpy-0.1.0/LICENSE
ADDED
|
File without changes
|
zennewpy-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: zennewpy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Client to work with Zenodo API
|
|
5
|
+
Home-page: https://github.com/yourusername/zennewpy
|
|
6
|
+
Author: Marco Mancini
|
|
7
|
+
Author-email: marco.mancini@obspm.fr
|
|
8
|
+
License: UNKNOWN
|
|
9
|
+
Platform: UNKNOWN
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
|
|
16
|
+
# zenodopy
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
[](https://codecov.io/gh/lgloege/zenodopy)
|
|
20
|
+
[](https://pypi.org/project/zenodopy)
|
|
21
|
+
[](https://opensource.org/licenses/MIT)
|
|
22
|
+
[](https://github.com/lgloege/zenodopy/issues)
|
|
23
|
+
|
|
24
|
+
### Project under active deveopment, not production ready
|
|
25
|
+
|
|
26
|
+
A Python 3.6+ package to manage [Zenodo](https://zenodo.org/) repositories.
|
|
27
|
+
|
|
28
|
+
### Functions Implemented
|
|
29
|
+
|
|
30
|
+
- `.create_project()`: create a new project
|
|
31
|
+
- `.upload_file()`: upload file to project
|
|
32
|
+
- `.download_file()`: download a file from a project
|
|
33
|
+
- `.delete_file()`: permanently removes a file from a project
|
|
34
|
+
- `.get_urls_from_doi()`: returns the files urls for a given doi
|
|
35
|
+
|
|
36
|
+
Installing
|
|
37
|
+
----------
|
|
38
|
+
|
|
39
|
+
### PyPi
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
pip install zenodopy==0.3.0
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### GitHub
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
pip install -e git+https://github.com/lgloege/zenodopy.git#egg=zenodopy
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Using the Package
|
|
52
|
+
-----------------
|
|
53
|
+
|
|
54
|
+
1. **Create a Zenodo access token** by first logging into your account and clicking on your username in the top right corner. Navigate to "Applications" and then "+new token" under "Personal access tokens". Keep this window open while you proceed to step 2 because **the token is only displayed once**. Note that Sandbox.zenodo is used for testing and zenodo for production. If you want to use both, create for each a token as desribed above.
|
|
55
|
+
2. **Store the token** in `~/.zenodo_token` using the following command.
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
# zenodo token
|
|
59
|
+
{ echo 'ACCESS_TOKEN: your_access_token_here' } > ~/.zenodo_token
|
|
60
|
+
|
|
61
|
+
# sandbox.zenodo token
|
|
62
|
+
{ echo 'ACCESS_TOKEN-sandbox: your_access_token_here' } > ~/.zenodo_token
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
3. **start using the `zenodopy` package**
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
import zenodopy
|
|
69
|
+
|
|
70
|
+
# always start by creating a Client object
|
|
71
|
+
zeno = zenodopy.Client(sandbox=True)
|
|
72
|
+
|
|
73
|
+
# list project id's associated to zenodo account
|
|
74
|
+
zeno.list_projects
|
|
75
|
+
|
|
76
|
+
# create a project
|
|
77
|
+
zeno.create_project(title="test_project", upload_type="other")
|
|
78
|
+
# your zeno object now points to this newly created project
|
|
79
|
+
|
|
80
|
+
# create a file to upload
|
|
81
|
+
with open("~/test_file.txt", "w+") as f:
|
|
82
|
+
f.write("Hello from zenodopy")
|
|
83
|
+
|
|
84
|
+
# upload file to zenodo
|
|
85
|
+
zeno.upload_file("~/test.file.txt")
|
|
86
|
+
|
|
87
|
+
# list files of project
|
|
88
|
+
zeno.list_files
|
|
89
|
+
|
|
90
|
+
# set project to other project id's
|
|
91
|
+
zeno.set_project("<id>")
|
|
92
|
+
|
|
93
|
+
# delete project
|
|
94
|
+
zeno._delete_project(dep_id="<id>")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Notes
|
|
98
|
+
-----
|
|
99
|
+
|
|
100
|
+
This project is under active development. Here is a list of things that needs improvement:
|
|
101
|
+
|
|
102
|
+
- **more tests**: need to test uploading and downloading files
|
|
103
|
+
- **documentation**: need to setup a readthedocs
|
|
104
|
+
- **download based on DOI**: right now you can only download from your own projects. Would be nice to download from
|
|
105
|
+
- **asyncronous functions**: use `asyncio` and `aiohttp` to write async functions. This will speed up downloading multiple files.
|
|
106
|
+
|
|
107
|
+
|
zennewpy-0.1.0/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# zenodopy
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
[](https://codecov.io/gh/lgloege/zenodopy)
|
|
5
|
+
[](https://pypi.org/project/zenodopy)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://github.com/lgloege/zenodopy/issues)
|
|
8
|
+
|
|
9
|
+
### Project under active deveopment, not production ready
|
|
10
|
+
|
|
11
|
+
A Python 3.6+ package to manage [Zenodo](https://zenodo.org/) repositories.
|
|
12
|
+
|
|
13
|
+
### Functions Implemented
|
|
14
|
+
|
|
15
|
+
- `.create_project()`: create a new project
|
|
16
|
+
- `.upload_file()`: upload file to project
|
|
17
|
+
- `.download_file()`: download a file from a project
|
|
18
|
+
- `.delete_file()`: permanently removes a file from a project
|
|
19
|
+
- `.get_urls_from_doi()`: returns the files urls for a given doi
|
|
20
|
+
|
|
21
|
+
Installing
|
|
22
|
+
----------
|
|
23
|
+
|
|
24
|
+
### PyPi
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
pip install zenodopy==0.3.0
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### GitHub
|
|
31
|
+
|
|
32
|
+
```sh
|
|
33
|
+
pip install -e git+https://github.com/lgloege/zenodopy.git#egg=zenodopy
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Using the Package
|
|
37
|
+
-----------------
|
|
38
|
+
|
|
39
|
+
1. **Create a Zenodo access token** by first logging into your account and clicking on your username in the top right corner. Navigate to "Applications" and then "+new token" under "Personal access tokens". Keep this window open while you proceed to step 2 because **the token is only displayed once**. Note that Sandbox.zenodo is used for testing and zenodo for production. If you want to use both, create for each a token as desribed above.
|
|
40
|
+
2. **Store the token** in `~/.zenodo_token` using the following command.
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
# zenodo token
|
|
44
|
+
{ echo 'ACCESS_TOKEN: your_access_token_here' } > ~/.zenodo_token
|
|
45
|
+
|
|
46
|
+
# sandbox.zenodo token
|
|
47
|
+
{ echo 'ACCESS_TOKEN-sandbox: your_access_token_here' } > ~/.zenodo_token
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
3. **start using the `zenodopy` package**
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
import zenodopy
|
|
54
|
+
|
|
55
|
+
# always start by creating a Client object
|
|
56
|
+
zeno = zenodopy.Client(sandbox=True)
|
|
57
|
+
|
|
58
|
+
# list project id's associated to zenodo account
|
|
59
|
+
zeno.list_projects
|
|
60
|
+
|
|
61
|
+
# create a project
|
|
62
|
+
zeno.create_project(title="test_project", upload_type="other")
|
|
63
|
+
# your zeno object now points to this newly created project
|
|
64
|
+
|
|
65
|
+
# create a file to upload
|
|
66
|
+
with open("~/test_file.txt", "w+") as f:
|
|
67
|
+
f.write("Hello from zenodopy")
|
|
68
|
+
|
|
69
|
+
# upload file to zenodo
|
|
70
|
+
zeno.upload_file("~/test.file.txt")
|
|
71
|
+
|
|
72
|
+
# list files of project
|
|
73
|
+
zeno.list_files
|
|
74
|
+
|
|
75
|
+
# set project to other project id's
|
|
76
|
+
zeno.set_project("<id>")
|
|
77
|
+
|
|
78
|
+
# delete project
|
|
79
|
+
zeno._delete_project(dep_id="<id>")
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Notes
|
|
83
|
+
-----
|
|
84
|
+
|
|
85
|
+
This project is under active development. Here is a list of things that needs improvement:
|
|
86
|
+
|
|
87
|
+
- **more tests**: need to test uploading and downloading files
|
|
88
|
+
- **documentation**: need to setup a readthedocs
|
|
89
|
+
- **download based on DOI**: right now you can only download from your own projects. Would be nice to download from
|
|
90
|
+
- **asyncronous functions**: use `asyncio` and `aiohttp` to write async functions. This will speed up downloading multiple files.
|
zennewpy-0.1.0/setup.cfg
ADDED
zennewpy-0.1.0/setup.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name="zennewpy",
|
|
5
|
+
version="0.1.0",
|
|
6
|
+
packages=find_packages(),
|
|
7
|
+
install_requires=[
|
|
8
|
+
'requests',
|
|
9
|
+
'tabulate',
|
|
10
|
+
],
|
|
11
|
+
author="Marco Mancini",
|
|
12
|
+
author_email="marco.mancini@obspm.fr",
|
|
13
|
+
description="Client to work with Zenodo API",
|
|
14
|
+
long_description=open("README.md").read(),
|
|
15
|
+
long_description_content_type="text/markdown",
|
|
16
|
+
url="https://github.com/yourusername/zennewpy",
|
|
17
|
+
classifiers=[
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
],
|
|
22
|
+
)
|
|
@@ -0,0 +1,756 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import re
|
|
5
|
+
import requests
|
|
6
|
+
import warnings
|
|
7
|
+
import tarfile
|
|
8
|
+
import zipfile
|
|
9
|
+
from tabulate import tabulate
|
|
10
|
+
import time
|
|
11
|
+
|
|
12
|
+
def validate_url(url):
|
|
13
|
+
"""validates if URL is formatted correctly
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
bool: True is URL is acceptable False if not acceptable
|
|
17
|
+
"""
|
|
18
|
+
regex = re.compile(
|
|
19
|
+
r'^(?:http|ftp)s?://' # http:// or https://
|
|
20
|
+
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain...
|
|
21
|
+
r'localhost|' # localhost...
|
|
22
|
+
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
|
|
23
|
+
r'(?::\d+)?' # optional port
|
|
24
|
+
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
|
|
25
|
+
|
|
26
|
+
return re.match(regex, url) is not None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def make_tarfile(output_file, source_dir):
|
|
30
|
+
"""tar a directory
|
|
31
|
+
args
|
|
32
|
+
-----
|
|
33
|
+
output_file: path to output file
|
|
34
|
+
source_dir: path to source directory
|
|
35
|
+
|
|
36
|
+
returns
|
|
37
|
+
-----
|
|
38
|
+
tarred directory will be in output_file
|
|
39
|
+
"""
|
|
40
|
+
with tarfile.open(output_file, "w:gz") as tar:
|
|
41
|
+
tar.add(source_dir, arcname=os.path.basename(source_dir))
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def make_zipfile(path, ziph):
|
|
45
|
+
# ziph is zipfile handle
|
|
46
|
+
for root, dirs, files in os.walk(path):
|
|
47
|
+
for file in files:
|
|
48
|
+
ziph.write(os.path.join(root, file),
|
|
49
|
+
os.path.relpath(os.path.join(root, file),
|
|
50
|
+
os.path.join(path, '..')))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class BearerAuth(requests.auth.AuthBase):
|
|
54
|
+
"""Bearer Authentication"""
|
|
55
|
+
|
|
56
|
+
def __init__(self, token):
|
|
57
|
+
self.token = token
|
|
58
|
+
|
|
59
|
+
def __call__(self, r):
|
|
60
|
+
r.headers["authorization"] = "Bearer " + self.token
|
|
61
|
+
return r
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class Client(object):
|
|
65
|
+
"""Zenodo Client object
|
|
66
|
+
|
|
67
|
+
Use this class to instantiate a zenodopy object
|
|
68
|
+
to interact with your Zenodo account
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
import zenodopy
|
|
72
|
+
zeno = zenodopy.Client()
|
|
73
|
+
zeno.help()
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Setup instructions:
|
|
77
|
+
```
|
|
78
|
+
zeno.setup_instructions
|
|
79
|
+
```
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
def __init__(self, title=None, bucket=None, deposition_id=None, sandbox=None, token=None):
|
|
83
|
+
"""initialization method"""
|
|
84
|
+
if sandbox:
|
|
85
|
+
self._endpoint = "https://sandbox.zenodo.org/api"
|
|
86
|
+
self._doi_pattern = r'^10\.5072/zenodo\.\d+$'
|
|
87
|
+
else:
|
|
88
|
+
self._endpoint = "https://zenodo.org/api"
|
|
89
|
+
self._doi_pattern = r'^10\.5281/zenodo\.\d+$'
|
|
90
|
+
|
|
91
|
+
self.title = title
|
|
92
|
+
self.bucket = bucket
|
|
93
|
+
self.deposition_id = deposition_id
|
|
94
|
+
self.sandbox = sandbox
|
|
95
|
+
self._token = self._read_from_config if token is None else token
|
|
96
|
+
self._bearer_auth = BearerAuth(self._token)
|
|
97
|
+
self.concept_id = None
|
|
98
|
+
self.associated = False
|
|
99
|
+
# 'metadata/prereservation_doi/doi'
|
|
100
|
+
|
|
101
|
+
def __repr__(self):
|
|
102
|
+
return f"zenodoapi('{self.title}','{self.bucket}','{self.deposition_id}')"
|
|
103
|
+
|
|
104
|
+
def __str__(self):
|
|
105
|
+
return f"{self.title} --- {self.deposition_id}"
|
|
106
|
+
|
|
107
|
+
# ---------------------------------------------
|
|
108
|
+
# hidden functions
|
|
109
|
+
# ---------------------------------------------
|
|
110
|
+
|
|
111
|
+
@staticmethod
|
|
112
|
+
def _get_upload_types():
|
|
113
|
+
"""Acceptable upload types
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
list: contains acceptable upload_types
|
|
117
|
+
"""
|
|
118
|
+
return [
|
|
119
|
+
"publication",
|
|
120
|
+
"poster",
|
|
121
|
+
"presentation",
|
|
122
|
+
"dataset",
|
|
123
|
+
"image",
|
|
124
|
+
"video",
|
|
125
|
+
"software",
|
|
126
|
+
"lesson",
|
|
127
|
+
"physicalobject",
|
|
128
|
+
"other"
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
@staticmethod
|
|
132
|
+
def _read_config(path=None):
|
|
133
|
+
"""reads the configuration file
|
|
134
|
+
|
|
135
|
+
Configuration file should be ~/.zenodo_token
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
path (str): location of the file with ACCESS_TOKEN
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
dict: dictionary with API ACCESS_TOKEN
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
if path is None:
|
|
145
|
+
print("You need to supply a path")
|
|
146
|
+
|
|
147
|
+
full_path = os.path.expanduser(path)
|
|
148
|
+
if not Path(full_path).exists():
|
|
149
|
+
print(f"{path} does not exist. Please check you entered the correct path")
|
|
150
|
+
|
|
151
|
+
config = {}
|
|
152
|
+
with open(path) as file:
|
|
153
|
+
for line in file.readlines():
|
|
154
|
+
if ":" in line:
|
|
155
|
+
key, value = line.strip().split(":", 1)
|
|
156
|
+
if key in ("ACCESS_TOKEN", "ACCESS_TOKEN-sandbox"):
|
|
157
|
+
config[key] = value.strip()
|
|
158
|
+
return config
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def _read_from_config(self):
|
|
162
|
+
"""reads the web3.storage token from configuration file
|
|
163
|
+
configuration file is ~/.web3_storage_token
|
|
164
|
+
Returns:
|
|
165
|
+
str: ACCESS_TOKEN to connect to web3 storage
|
|
166
|
+
"""
|
|
167
|
+
if self.sandbox:
|
|
168
|
+
dotrc = os.environ.get("ACCESS_TOKEN-sandbox", os.path.expanduser("~/.zenodo_token"))
|
|
169
|
+
else:
|
|
170
|
+
dotrc = os.environ.get("ACCESS_TOKEN", os.path.expanduser("~/.zenodo_token"))
|
|
171
|
+
|
|
172
|
+
if os.path.exists(dotrc):
|
|
173
|
+
config = self._read_config(dotrc)
|
|
174
|
+
key = config.get("ACCESS_TOKEN-sandbox") if self.sandbox else config.get("ACCESS_TOKEN")
|
|
175
|
+
return key
|
|
176
|
+
else:
|
|
177
|
+
print(' ** No token was found, check your ~/.zenodo_token file ** ')
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def get_all_depositions(self):
|
|
181
|
+
"""
|
|
182
|
+
Retrieves all depositions for the client, but only the last version with all metadata.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
list: A list of dictionaries containing the latest version of each deposition with full metadata.
|
|
186
|
+
"""
|
|
187
|
+
url = f"{self._endpoint}/deposit/depositions"
|
|
188
|
+
params = {
|
|
189
|
+
"size": 1000, # Adjust this value based on your needs
|
|
190
|
+
"sort": "mostrecent",
|
|
191
|
+
"all_versions": True
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
all_depositions = []
|
|
195
|
+
concept_ids_processed = set()
|
|
196
|
+
|
|
197
|
+
while True:
|
|
198
|
+
response = requests.get(url, auth=self._bearer_auth,params=params)
|
|
199
|
+
response.raise_for_status()
|
|
200
|
+
depositions = response.json()
|
|
201
|
+
|
|
202
|
+
if not depositions:
|
|
203
|
+
break
|
|
204
|
+
|
|
205
|
+
for deposition in depositions:
|
|
206
|
+
concept_id = deposition['conceptrecid']
|
|
207
|
+
if concept_id not in concept_ids_processed:
|
|
208
|
+
# This is the latest version of this deposition
|
|
209
|
+
all_depositions.append(deposition)
|
|
210
|
+
concept_ids_processed.add(concept_id)
|
|
211
|
+
|
|
212
|
+
# Check if there are more pages
|
|
213
|
+
links = response.links
|
|
214
|
+
if 'next' not in links:
|
|
215
|
+
break
|
|
216
|
+
url = links['next']['url']
|
|
217
|
+
|
|
218
|
+
return all_depositions
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def find_community_identifier(self,community_name):
|
|
223
|
+
"""Find a community id from community name"""
|
|
224
|
+
params = {
|
|
225
|
+
"q": community_name,
|
|
226
|
+
"size": 100 # Adjust as needed
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
response = requests.get(self._endpoint+'/communities', params=params)
|
|
230
|
+
|
|
231
|
+
if response.status_code == 200:
|
|
232
|
+
data = response.json()
|
|
233
|
+
for community in data['hits']['hits']:
|
|
234
|
+
if community['metadata']['title'].lower() == community_name.lower():
|
|
235
|
+
return community['id']
|
|
236
|
+
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def set_deposition(self, id_value):
|
|
241
|
+
"""
|
|
242
|
+
Sets the client to a specific deposition's latest version using a given deposition_id or concept_id.
|
|
243
|
+
|
|
244
|
+
Args:
|
|
245
|
+
id_value (int): The ID of the deposition or the concept ID to set.
|
|
246
|
+
|
|
247
|
+
Raises:
|
|
248
|
+
ValueError: If no valid deposition is found for the given ID.
|
|
249
|
+
"""
|
|
250
|
+
if not id_value:
|
|
251
|
+
raise ValueError("You must provide an ID.")
|
|
252
|
+
|
|
253
|
+
# First, try to retrieve the deposition directly
|
|
254
|
+
try:
|
|
255
|
+
deposition = self.get_deposition_by_id(id_value)
|
|
256
|
+
concept_id = deposition.get('conceptrecid')
|
|
257
|
+
except requests.exceptions.HTTPError:
|
|
258
|
+
# If that fails, assume it's a concept ID
|
|
259
|
+
concept_id = id_value
|
|
260
|
+
|
|
261
|
+
# Retrieve the latest version of the deposition using concept ID
|
|
262
|
+
url = f"{self._endpoint}/deposit/depositions"
|
|
263
|
+
params = {
|
|
264
|
+
"q": f"conceptrecid:{concept_id}",
|
|
265
|
+
"sort": "mostrecent",
|
|
266
|
+
"size": 1,
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
# hack to the problem that the deposition_id is not created instantaneously on the server
|
|
270
|
+
n_try = 8
|
|
271
|
+
response = requests.get(url, auth=self._bearer_auth, params=params)
|
|
272
|
+
depositions = response.json()
|
|
273
|
+
while not depositions and n_try > 0:
|
|
274
|
+
response = requests.get(url, auth=self._bearer_auth, params=params)
|
|
275
|
+
depositions = response.json()
|
|
276
|
+
time.sleep(1)
|
|
277
|
+
n_try -=1
|
|
278
|
+
|
|
279
|
+
response.raise_for_status()
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
if not depositions:
|
|
284
|
+
raise ValueError(f"No depositions found for ID {id_value}.")
|
|
285
|
+
|
|
286
|
+
latest_deposition = depositions[0]
|
|
287
|
+
|
|
288
|
+
# Set class variables based on the latest version of the deposition
|
|
289
|
+
self.title = latest_deposition['metadata'].get('title', None)
|
|
290
|
+
self.bucket = latest_deposition['links'].get('bucket', 'N/A')
|
|
291
|
+
|
|
292
|
+
self.deposition_id = latest_deposition['id']
|
|
293
|
+
self.concept_id = latest_deposition['conceptrecid']
|
|
294
|
+
self.associated = True
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def unset_deposition(self):
|
|
299
|
+
"""
|
|
300
|
+
Unsets the current deposition settings, resetting related attributes.
|
|
301
|
+
"""
|
|
302
|
+
self.title = None
|
|
303
|
+
self.bucket = None
|
|
304
|
+
self.deposition_id = None
|
|
305
|
+
self.concept_id = None
|
|
306
|
+
self.associated = False
|
|
307
|
+
|
|
308
|
+
def get_deposition_by_id(self, deposition_id=None):
|
|
309
|
+
"""
|
|
310
|
+
Retrieves a specific deposition by its ID.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
deposition_id (int): The ID of the deposition to retrieve.
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
dict: A dictionary containing the full metadata of the deposition.
|
|
317
|
+
"""
|
|
318
|
+
if deposition_id is None and self.associated :
|
|
319
|
+
deposition_id = self.deposition_id
|
|
320
|
+
url = f"{self._endpoint}/deposit/depositions/{deposition_id}"
|
|
321
|
+
|
|
322
|
+
response = requests.get(url,auth=self._bearer_auth)
|
|
323
|
+
response.raise_for_status()
|
|
324
|
+
return response.json()
|
|
325
|
+
|
|
326
|
+
@property
|
|
327
|
+
def deposition(self):
|
|
328
|
+
return self.get_deposition_by_id()
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def pretty_print_depositions(self, depositions=None):
|
|
332
|
+
"""
|
|
333
|
+
Pretty prints all depositions with their metadata in a table format.
|
|
334
|
+
|
|
335
|
+
Args:
|
|
336
|
+
depositions (list): A list of deposition dictionaries to print.
|
|
337
|
+
"""
|
|
338
|
+
if depositions is None: depositions = self.get_all_depositions()
|
|
339
|
+
|
|
340
|
+
table_data = []
|
|
341
|
+
|
|
342
|
+
for deposition in depositions:
|
|
343
|
+
title = deposition['metadata'].get('title',None)
|
|
344
|
+
cid = deposition['conceptrecid']
|
|
345
|
+
dep_id = deposition['id']
|
|
346
|
+
published = 'Yes' if deposition['submitted'] else 'No'
|
|
347
|
+
doi = deposition.get('doi', 'N/A')
|
|
348
|
+
|
|
349
|
+
# Check if this is the currently set deposition
|
|
350
|
+
flag = '*' if self.deposition_id == dep_id else ''
|
|
351
|
+
|
|
352
|
+
table_data.append([title, cid, dep_id, published, doi, flag])
|
|
353
|
+
|
|
354
|
+
headers = ["Title", "CID", "ID", "Published", "DOI", "Set"]
|
|
355
|
+
print(tabulate(table_data, headers=headers, tablefmt="pretty"))
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def create_new_deposition(self):
|
|
359
|
+
"""
|
|
360
|
+
Creates a new deposition.
|
|
361
|
+
|
|
362
|
+
Returns:
|
|
363
|
+
int: The ID of the newly created deposition.
|
|
364
|
+
"""
|
|
365
|
+
url = f"{self._endpoint}/deposit/depositions"
|
|
366
|
+
headers = {"Content-Type": "application/json"}
|
|
367
|
+
|
|
368
|
+
response = requests.post(url, auth=self._bearer_auth, json={}, headers=headers)
|
|
369
|
+
response.raise_for_status()
|
|
370
|
+
|
|
371
|
+
deposition_data = response.json()
|
|
372
|
+
return deposition_data['id']
|
|
373
|
+
|
|
374
|
+
def delete_deposition(self, deposition_id=None):
|
|
375
|
+
"""
|
|
376
|
+
Deletes a deposition.
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
deposition_id (int): The ID of the deposition to delete.
|
|
380
|
+
"""
|
|
381
|
+
|
|
382
|
+
if deposition_id is None : deposition_id = self.deposition_id
|
|
383
|
+
|
|
384
|
+
if not type(deposition_id)==list :
|
|
385
|
+
deposition_id = [deposition_id]
|
|
386
|
+
|
|
387
|
+
for ii in deposition_id:
|
|
388
|
+
url = f"{self._endpoint}/deposit/depositions/{ii}"
|
|
389
|
+
response = requests.delete(url, auth=self._bearer_auth)
|
|
390
|
+
#response.raise_for_status()
|
|
391
|
+
if(ii==self.deposition_id) : self.unset_deposition()
|
|
392
|
+
if response.status_code < 400:
|
|
393
|
+
print(f"Deposition {ii} deleted")
|
|
394
|
+
|
|
395
|
+
def create_metadata(self, metadata, **kwargs):
|
|
396
|
+
"""
|
|
397
|
+
Creates or updates metadata for a deposition.
|
|
398
|
+
|
|
399
|
+
Args:
|
|
400
|
+
deposition_id (int): The ID of the deposition.
|
|
401
|
+
metadata (dict): The metadata to set.
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
dict: The updated deposition data.
|
|
405
|
+
"""
|
|
406
|
+
if not self.associated:
|
|
407
|
+
print("create_metadata: deposition is not associated.")
|
|
408
|
+
return
|
|
409
|
+
# Update metadata with additional fields from kwargs
|
|
410
|
+
for key, value in kwargs.items():
|
|
411
|
+
if isinstance(value, dict):
|
|
412
|
+
# If the value is a dictionary, update or add it to metadata
|
|
413
|
+
metadata[key] = metadata.get(key, {})
|
|
414
|
+
metadata[key].update(value)
|
|
415
|
+
else:
|
|
416
|
+
# If it's not a dictionary, simply add or update the field
|
|
417
|
+
metadata[key] = value
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
url = f"{self._endpoint}/deposit/depositions/{self.deposition_id}"
|
|
421
|
+
headers = {"Content-Type": "application/json"}
|
|
422
|
+
data = {"metadata": metadata}
|
|
423
|
+
|
|
424
|
+
response = requests.put(url, auth=self._bearer_auth, data=json.dumps(data), headers=headers)
|
|
425
|
+
if response.status_code >= 500:
|
|
426
|
+
self.delete_deposition()
|
|
427
|
+
response.raise_for_status()
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
return response.json()
|
|
431
|
+
|
|
432
|
+
def upload_file(self,file_path, remote_filename=None, file_id=None):
|
|
433
|
+
"""
|
|
434
|
+
Uploads a file to a Zenodo deposition using PUT (if file_id is provided) or POST (for new files).
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
file_path (str): The local path of the file to upload.
|
|
438
|
+
remote_filename (str, optional): The filename to use on Zenodo. If None, uses the local filename.
|
|
439
|
+
file_id (str, optional): The ID of an existing file to update. If None, a new file will be created.
|
|
440
|
+
|
|
441
|
+
Returns:
|
|
442
|
+
dict: The uploaded file data, including the file_id.
|
|
443
|
+
"""
|
|
444
|
+
deposition_id = self.deposition_id
|
|
445
|
+
|
|
446
|
+
if remote_filename is None:
|
|
447
|
+
remote_filename = os.path.basename(file_path)
|
|
448
|
+
|
|
449
|
+
if file_id:
|
|
450
|
+
print("this functionality does not work at moment on Zenodo!!")
|
|
451
|
+
# Update existing file using PUT
|
|
452
|
+
url = f"{self._endpoint}/deposit/depositions/{deposition_id}/files/{file_id}"
|
|
453
|
+
with open(file_path, "rb") as file:
|
|
454
|
+
response = requests.put(url, auth=self._bearer_auth, data=file)
|
|
455
|
+
else:
|
|
456
|
+
# Upload new file using POST
|
|
457
|
+
url = f"{self._endpoint}/deposit/depositions/{deposition_id}/files"
|
|
458
|
+
with open(file_path, "rb") as file:
|
|
459
|
+
data = {"name": remote_filename}
|
|
460
|
+
files = {"file": file}
|
|
461
|
+
response = requests.post(url, auth=self._bearer_auth, data=data, files=files)
|
|
462
|
+
|
|
463
|
+
response.raise_for_status()
|
|
464
|
+
file_data = response.json()
|
|
465
|
+
|
|
466
|
+
return file_data
|
|
467
|
+
|
|
468
|
+
def get_file_ids(self, deposition_id=None):
|
|
469
|
+
"""
|
|
470
|
+
Retrieves the file IDs for all files in a deposition.
|
|
471
|
+
|
|
472
|
+
Args:
|
|
473
|
+
deposition_id (int): The ID of the deposition.
|
|
474
|
+
|
|
475
|
+
Returns:
|
|
476
|
+
dict: A dictionary mapping filenames to their file IDs.
|
|
477
|
+
"""
|
|
478
|
+
if deposition_id is None and self.associated :
|
|
479
|
+
deposition_id = self.deposition_id
|
|
480
|
+
deposition = self.get_deposition_by_id(deposition_id)
|
|
481
|
+
return {file['filename']: file['id'] for file in deposition.get('files', [])}
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def get_doi(self,deposition_id=None):
|
|
485
|
+
if deposition_id is None and self.associated :
|
|
486
|
+
deposition_id = self.deposition_id
|
|
487
|
+
deposition = self.get_deposition_by_id(deposition_id)
|
|
488
|
+
return deposition.get('doi',None)
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def publish_deposition(self):
|
|
492
|
+
"""
|
|
493
|
+
Publishes a deposition.
|
|
494
|
+
|
|
495
|
+
Args:
|
|
496
|
+
deposition_id (int): The ID of the deposition to publish.
|
|
497
|
+
|
|
498
|
+
Returns:
|
|
499
|
+
dict: The published deposition data.
|
|
500
|
+
"""
|
|
501
|
+
url = f"{self._endpoint}/deposit/depositions/{self.deposition_id}/actions/publish"
|
|
502
|
+
|
|
503
|
+
|
|
504
|
+
try:
|
|
505
|
+
response = requests.post(url, auth=self._bearer_auth)
|
|
506
|
+
response.raise_for_status()
|
|
507
|
+
return True # If we get here, the request was successful
|
|
508
|
+
except requests.exceptions.RequestException as e:
|
|
509
|
+
# Log the error for debugging purposes
|
|
510
|
+
print(f"Error publishing deposition: {str(e)}")
|
|
511
|
+
return False # Return False if any exception occurs
|
|
512
|
+
except requests.exceptions.HTTPError as err:
|
|
513
|
+
print(f"HTTP error occurred: {err}")
|
|
514
|
+
print(f"Response: {response.text}") # For more detailed error information
|
|
515
|
+
except KeyError:
|
|
516
|
+
print("KeyError: Failed to retrieve 'publish' link. Check if the deposition is ready for publication.")
|
|
517
|
+
except Exception as e:
|
|
518
|
+
print(f"An error occurred: {e}")
|
|
519
|
+
|
|
520
|
+
@property
|
|
521
|
+
def is_published(self):
|
|
522
|
+
return self.get_deposition_by_id()['submitted']
|
|
523
|
+
|
|
524
|
+
def create_new_version(self):
|
|
525
|
+
"""
|
|
526
|
+
Creates a new version of an existing deposition.
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
Returns:
|
|
530
|
+
dict: The new deposition data.
|
|
531
|
+
"""
|
|
532
|
+
url = f"{self._endpoint}/deposit/depositions/{self.deposition_id}/actions/newversion"
|
|
533
|
+
|
|
534
|
+
try:
|
|
535
|
+
response = requests.post(url, auth=self._bearer_auth)
|
|
536
|
+
response.raise_for_status()
|
|
537
|
+
new_id = response.json()['id']
|
|
538
|
+
|
|
539
|
+
# Wait briefly to allow Zenodo to index the new version
|
|
540
|
+
# time.sleep(2) # Adjust as necessary
|
|
541
|
+
|
|
542
|
+
self.set_deposition(new_id)
|
|
543
|
+
return new_id
|
|
544
|
+
|
|
545
|
+
except requests.exceptions.RequestException as e:
|
|
546
|
+
if hasattr(e, 'response'):
|
|
547
|
+
print(f"Response content: {e.response.content}")
|
|
548
|
+
raise
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def modify_metadata(self, metadata_updates, **kwargs):
|
|
552
|
+
"""
|
|
553
|
+
Modifies metadata of a deposition (published or not).
|
|
554
|
+
|
|
555
|
+
Args:
|
|
556
|
+
metadata_updates (dict): The metadata fields to update.
|
|
557
|
+
**kwargs: Additional metadata fields to update.
|
|
558
|
+
|
|
559
|
+
Returns:
|
|
560
|
+
dict: The updated deposition data.
|
|
561
|
+
"""
|
|
562
|
+
if not self.associated:
|
|
563
|
+
print (f'Error in "modify_update" Deposition is not associated !')
|
|
564
|
+
return None
|
|
565
|
+
|
|
566
|
+
# Check if the deposition is published
|
|
567
|
+
current_deposition = self.deposition
|
|
568
|
+
|
|
569
|
+
if current_deposition.get('state') == 'done':
|
|
570
|
+
new_id = self.create_new_version()
|
|
571
|
+
print(f"New deposition_id {new_id} was created for concept_id {self.concept_id}")
|
|
572
|
+
current_deposition = self.deposition
|
|
573
|
+
|
|
574
|
+
current_metadata = current_deposition['metadata']
|
|
575
|
+
|
|
576
|
+
# Update the metadata
|
|
577
|
+
current_metadata.update(metadata_updates)
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
# Update metadata with additional fields from kwargs
|
|
581
|
+
for key, value in kwargs.items():
|
|
582
|
+
if isinstance(value, dict):
|
|
583
|
+
current_metadata[key] = current_metadata.get(key, {})
|
|
584
|
+
current_metadata[key].update(value)
|
|
585
|
+
else:
|
|
586
|
+
current_metadata[key] = value
|
|
587
|
+
|
|
588
|
+
# Use the create_metadata method to update
|
|
589
|
+
result = self.create_metadata(current_metadata)
|
|
590
|
+
return result
|
|
591
|
+
|
|
592
|
+
def update_file(self, file_path,remote_filename=None):
|
|
593
|
+
"""
|
|
594
|
+
Updates a file in a deposition (published or not).
|
|
595
|
+
|
|
596
|
+
Args:
|
|
597
|
+
file_path (str): The path to the new file.
|
|
598
|
+
remote_filename (str) : The remote name.
|
|
599
|
+
|
|
600
|
+
Returns:
|
|
601
|
+
dict: The updated file data.
|
|
602
|
+
"""
|
|
603
|
+
|
|
604
|
+
if self.is_published :
|
|
605
|
+
print("Deposition is published. Before to update file please create a new version")
|
|
606
|
+
return
|
|
607
|
+
|
|
608
|
+
file_id = self.get_file_ids()[remote_filename]
|
|
609
|
+
|
|
610
|
+
# Check if the new file exists
|
|
611
|
+
if not os.path.exists(file_path):
|
|
612
|
+
raise FileNotFoundError(f"The file {file_path} does not exist.")
|
|
613
|
+
|
|
614
|
+
# Check if the new file is readable
|
|
615
|
+
if not os.access(file_path, os.R_OK):
|
|
616
|
+
raise PermissionError(f"No permission to read the file {file_path}.")
|
|
617
|
+
|
|
618
|
+
# First, try to delete the existing file
|
|
619
|
+
delete_url = f"{self._endpoint}/deposit/depositions/{self.deposition_id}/files/{file_id}"
|
|
620
|
+
|
|
621
|
+
try:
|
|
622
|
+
delete_response = requests.delete(delete_url, auth=self._bearer_auth)
|
|
623
|
+
delete_response.raise_for_status()
|
|
624
|
+
except requests.exceptions.HTTPError as e:
|
|
625
|
+
if e.response.status_code == 404:
|
|
626
|
+
# File not found, log a warning and continue with upload
|
|
627
|
+
print(f"Warning: File {file_id} not found. Proceeding with upload.")
|
|
628
|
+
else:
|
|
629
|
+
raise Exception(f"Failed to delete existing file: {str(e)}")
|
|
630
|
+
|
|
631
|
+
# Then, upload the new file
|
|
632
|
+
try:
|
|
633
|
+
return self.upload_file(file_path=file_path,remote_filename=remote_filename)
|
|
634
|
+
except Exception as e:
|
|
635
|
+
raise Exception(f"Failed to upload new file: {str(e)}")
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def get_file_ids(self):
|
|
639
|
+
"""
|
|
640
|
+
Retrieves the file IDs for all files in a deposition.
|
|
641
|
+
|
|
642
|
+
Args:
|
|
643
|
+
deposition_id (int): The ID of the deposition.
|
|
644
|
+
|
|
645
|
+
Returns:
|
|
646
|
+
dict: A dictionary mapping filenames to their file IDs.
|
|
647
|
+
"""
|
|
648
|
+
|
|
649
|
+
deposition = self.get_deposition_by_id(self.deposition_id)
|
|
650
|
+
return {file['filename']: file['id'] for file in deposition.get('files', [])}
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def title_exists(self, title):
|
|
654
|
+
"""
|
|
655
|
+
Check if depositions with the given title exist in Zenodo,
|
|
656
|
+
and return their IDs if found.
|
|
657
|
+
|
|
658
|
+
Args:
|
|
659
|
+
title (str): The title to search for.
|
|
660
|
+
|
|
661
|
+
Returns:
|
|
662
|
+
dict: A dictionary containing 'exists' (bool) and 'ids' (list of str)
|
|
663
|
+
"""
|
|
664
|
+
result = {
|
|
665
|
+
'exists': False,
|
|
666
|
+
'ids': []
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
search_url = f"{self._endpoint}/deposit/depositions"
|
|
670
|
+
params = {
|
|
671
|
+
'size': 9999 # Adjust this value based on your needs
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
try:
|
|
675
|
+
response = requests.get(search_url, params=params, auth=self._bearer_auth)
|
|
676
|
+
response.raise_for_status()
|
|
677
|
+
|
|
678
|
+
depositions = response.json()
|
|
679
|
+
for deposition in depositions:
|
|
680
|
+
if deposition.get('metadata', {}).get('title', '').lower() == title.lower():
|
|
681
|
+
result['exists'] = True
|
|
682
|
+
deposition_id = deposition.get('id')
|
|
683
|
+
result['ids'].append(deposition_id)
|
|
684
|
+
status = "published" if deposition.get('submitted', False) else "draft"
|
|
685
|
+
print(f"Found {status} deposition with title: {title}, ID: {deposition_id}")
|
|
686
|
+
|
|
687
|
+
if not result['exists']:
|
|
688
|
+
print(f"No deposition found with title: {title}")
|
|
689
|
+
elif len(result['ids']) > 1:
|
|
690
|
+
print(f"Warning: Multiple depositions found with title: {title}")
|
|
691
|
+
|
|
692
|
+
except requests.exceptions.RequestException as e:
|
|
693
|
+
print(f"Error searching depositions: {e}")
|
|
694
|
+
print(f"Response status code: {e.response.status_code if e.response else 'N/A'}")
|
|
695
|
+
print(f"Response content: {e.response.text if e.response else 'N/A'}")
|
|
696
|
+
|
|
697
|
+
return result
|
|
698
|
+
|
|
699
|
+
def get_metadata(self,dep_id: str=None):
|
|
700
|
+
"""
|
|
701
|
+
Retrieves the current metadata of a deposition on Zenodo.
|
|
702
|
+
|
|
703
|
+
Args:
|
|
704
|
+
dep_id (str): The ID of the deposition.
|
|
705
|
+
|
|
706
|
+
Returns:
|
|
707
|
+
response (dict): The current metadata from the Zenodo API.
|
|
708
|
+
"""
|
|
709
|
+
if dep_id is None :
|
|
710
|
+
dep_id = self.deposition_id
|
|
711
|
+
url = f"{self._endpoint}/deposit/depositions/{dep_id}"
|
|
712
|
+
|
|
713
|
+
# Fetch the existing metadata
|
|
714
|
+
response = requests.get(url, auth=self._bearer_auth)
|
|
715
|
+
|
|
716
|
+
if response.status_code == 200:
|
|
717
|
+
return response.json()["metadata"]
|
|
718
|
+
else:
|
|
719
|
+
print(f"Failed to fetch metadata. Status code: {response.status_code}")
|
|
720
|
+
return None
|
|
721
|
+
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
if __name__ == '__main__':
|
|
725
|
+
zcd = Client(sandbox=True)
|
|
726
|
+
|
|
727
|
+
# Create a new deposition
|
|
728
|
+
deposition_id = zcd.create_new_deposition()
|
|
729
|
+
|
|
730
|
+
# Create metadata
|
|
731
|
+
metadata = {
|
|
732
|
+
'title': 'My New Dataset',
|
|
733
|
+
'description': 'This is a test dataset',
|
|
734
|
+
'upload_type': 'dataset',
|
|
735
|
+
'creators': [{'name': 'Doe, John', 'affiliation': 'zcddo'}]
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
|
|
739
|
+
zcd.set_deposition(deposition_id)
|
|
740
|
+
|
|
741
|
+
zcd.create_metadata(metadata)
|
|
742
|
+
|
|
743
|
+
# Upload a file
|
|
744
|
+
zcd.upload_file('/tmp/eos.zip', 'remote_filename.zip')
|
|
745
|
+
|
|
746
|
+
# Publish the deposition
|
|
747
|
+
#zcd.publish_deposition()
|
|
748
|
+
|
|
749
|
+
# Modify metadata (even after publishing)
|
|
750
|
+
zcd.modify_metadata({'title': 'Updated Dataset Title'})
|
|
751
|
+
|
|
752
|
+
zcd.update_file('/tmp/eos2.zip',remote_filename='remote_filename.zip')
|
|
753
|
+
|
|
754
|
+
|
|
755
|
+
# Delete a deposition (only works for unpublished depositions)
|
|
756
|
+
#zcd.delete_deposition(deposition_id)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: zennewpy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Client to work with Zenodo API
|
|
5
|
+
Home-page: https://github.com/yourusername/zennewpy
|
|
6
|
+
Author: Marco Mancini
|
|
7
|
+
Author-email: marco.mancini@obspm.fr
|
|
8
|
+
License: UNKNOWN
|
|
9
|
+
Platform: UNKNOWN
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
License-File: LICENSE
|
|
15
|
+
|
|
16
|
+
# zenodopy
|
|
17
|
+
|
|
18
|
+

|
|
19
|
+
[](https://codecov.io/gh/lgloege/zenodopy)
|
|
20
|
+
[](https://pypi.org/project/zenodopy)
|
|
21
|
+
[](https://opensource.org/licenses/MIT)
|
|
22
|
+
[](https://github.com/lgloege/zenodopy/issues)
|
|
23
|
+
|
|
24
|
+
### Project under active deveopment, not production ready
|
|
25
|
+
|
|
26
|
+
A Python 3.6+ package to manage [Zenodo](https://zenodo.org/) repositories.
|
|
27
|
+
|
|
28
|
+
### Functions Implemented
|
|
29
|
+
|
|
30
|
+
- `.create_project()`: create a new project
|
|
31
|
+
- `.upload_file()`: upload file to project
|
|
32
|
+
- `.download_file()`: download a file from a project
|
|
33
|
+
- `.delete_file()`: permanently removes a file from a project
|
|
34
|
+
- `.get_urls_from_doi()`: returns the files urls for a given doi
|
|
35
|
+
|
|
36
|
+
Installing
|
|
37
|
+
----------
|
|
38
|
+
|
|
39
|
+
### PyPi
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
pip install zenodopy==0.3.0
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### GitHub
|
|
46
|
+
|
|
47
|
+
```sh
|
|
48
|
+
pip install -e git+https://github.com/lgloege/zenodopy.git#egg=zenodopy
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Using the Package
|
|
52
|
+
-----------------
|
|
53
|
+
|
|
54
|
+
1. **Create a Zenodo access token** by first logging into your account and clicking on your username in the top right corner. Navigate to "Applications" and then "+new token" under "Personal access tokens". Keep this window open while you proceed to step 2 because **the token is only displayed once**. Note that Sandbox.zenodo is used for testing and zenodo for production. If you want to use both, create for each a token as desribed above.
|
|
55
|
+
2. **Store the token** in `~/.zenodo_token` using the following command.
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
# zenodo token
|
|
59
|
+
{ echo 'ACCESS_TOKEN: your_access_token_here' } > ~/.zenodo_token
|
|
60
|
+
|
|
61
|
+
# sandbox.zenodo token
|
|
62
|
+
{ echo 'ACCESS_TOKEN-sandbox: your_access_token_here' } > ~/.zenodo_token
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
3. **start using the `zenodopy` package**
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
import zenodopy
|
|
69
|
+
|
|
70
|
+
# always start by creating a Client object
|
|
71
|
+
zeno = zenodopy.Client(sandbox=True)
|
|
72
|
+
|
|
73
|
+
# list project id's associated to zenodo account
|
|
74
|
+
zeno.list_projects
|
|
75
|
+
|
|
76
|
+
# create a project
|
|
77
|
+
zeno.create_project(title="test_project", upload_type="other")
|
|
78
|
+
# your zeno object now points to this newly created project
|
|
79
|
+
|
|
80
|
+
# create a file to upload
|
|
81
|
+
with open("~/test_file.txt", "w+") as f:
|
|
82
|
+
f.write("Hello from zenodopy")
|
|
83
|
+
|
|
84
|
+
# upload file to zenodo
|
|
85
|
+
zeno.upload_file("~/test.file.txt")
|
|
86
|
+
|
|
87
|
+
# list files of project
|
|
88
|
+
zeno.list_files
|
|
89
|
+
|
|
90
|
+
# set project to other project id's
|
|
91
|
+
zeno.set_project("<id>")
|
|
92
|
+
|
|
93
|
+
# delete project
|
|
94
|
+
zeno._delete_project(dep_id="<id>")
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Notes
|
|
98
|
+
-----
|
|
99
|
+
|
|
100
|
+
This project is under active development. Here is a list of things that needs improvement:
|
|
101
|
+
|
|
102
|
+
- **more tests**: need to test uploading and downloading files
|
|
103
|
+
- **documentation**: need to setup a readthedocs
|
|
104
|
+
- **download based on DOI**: right now you can only download from your own projects. Would be nice to download from
|
|
105
|
+
- **asyncronous functions**: use `asyncio` and `aiohttp` to write async functions. This will speed up downloading multiple files.
|
|
106
|
+
|
|
107
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
zennewpy
|