-
Notifications
You must be signed in to change notification settings - Fork 1
/
config.py
279 lines (234 loc) 路 8.4 KB
/
config.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
__author__ = "Vini Salazar"
__license__ = "MIT"
__maintainer__ = "Vini Salazar"
__url__ = "https://github.com/vinisalazar/bioprov"
__version__ = "0.1.18a"
"""
Contains the Config class and other package-level settings.
Define your configurations in the 'config' variable at the end of the module.
"""
import os
import bioprov
from bioprov.data import data_dir, genomes_dir
from prov.model import Namespace
from provstore.api import Api
from bioprov.utils import serializer, dict_to_sha1, serializer_filter
from tinydb import TinyDB
from pathlib import Path
class Config:
"""
Class to define package level variables and settings.
"""
def __init__(self, db_path=None, threads=0):
"""
:param db_path: Path to database file. Default is bioprov_directory/db.json
:param threads: Number of threads. Default is half of processors.
"""
# This duplication is to order the keys in the __dict__ attribute.
self.user = None
self.env = EnvProv()
self.user = self.env.user
if not threads:
threads = int(os.cpu_count() / 2)
self.db = None
self.db_path = None
self.threads = threads
self.bioprov_dir = Path(bioprov.__file__).parent
self.data = data_dir
self.genomes = genomes_dir
if db_path is None:
db_path = self.bioprov_dir.joinpath("db.json")
self.db_path = db_path
self.db = BioProvDB(self.db_path)
self._provstore_file = None
self._provstore_user = None
self._provstore_token = None
self._provstore_api = None
self._provstore_endpoint = "https://openprovenance.org/store/api/v0/"
def __repr__(self):
return f"BioProv Config class set in {__file__}"
def db_all(self):
"""
:return: List all items in BioProv database.
"""
return self.db.all()
def clear_db(self, confirm=False):
"""
Deletes the local BioProv database.
:param confirm:
:return:
"""
self.db.clear_db(confirm) # no cover
@property
def provstore_api(self):
if self._provstore_api is None:
self._provstore_api = Api(
username=self.provstore_user, api_key=self.provstore_token
)
Api.base_url = self._provstore_endpoint
return self._provstore_api
@provstore_api.setter
def provstore_api(self, value):
self._provstore_api = value
@property
def provstore_file(self):
if self._provstore_file is None:
self._provstore_file = self.bioprov_dir.joinpath("provstore_api.txt")
return self._provstore_file
@provstore_file.setter
def provstore_file(self, value):
self._provstore_file = value
@property
def provstore_user(self): # no cover
if self._provstore_user is None:
self.read_provstore_file()
return self._provstore_user
@provstore_user.setter
def provstore_user(self, value):
self._provstore_user = value
@property
def provstore_token(self): # no cover
if self._provstore_token is None:
self.read_provstore_file()
return self._provstore_token
@provstore_token.setter
def provstore_token(self, value):
self._provstore_token = value
def create_provstore_file(self, user=None, token=None):
with open(self.provstore_file, "w") as f:
if user is None:
user = input("Please paste your ProvStore user: ") # no cover
if token is None:
token = input("Please paste your ProvStore API token: ") # no cover
f.write(user + "\n")
f.write(token + "\n")
print(f"Wrote ProvStore credentials file to {self.provstore_file}.")
print("Make sure that the contents of that file are private.")
def read_provstore_file(self):
"""
Attempts to read self.provstore_file.
Will prompt to create one if unable to retrieve credentials.
:return: Updates self.provstore_user and self.provstore_token.
"""
could_not_read = [
f"Could not read credentials from ProvStore file at {self.provstore_file}",
"It may be empty or not exist.",
]
def prompt(): # no cover
_prompt = input(
"\n".join(could_not_read + ["Would you like to create one? Y/n\n"])
)
if _prompt.lower() in ("y", "yes", ""):
return True
else:
print("Did not create ProvStore credentials file.")
return False
try:
with open(self.provstore_file) as f:
user, token, *_ = f.read().splitlines()
assert all((user, token))
self.provstore_user = user
self.provstore_token = token
return
# If not found, prompt to create
except FileNotFoundError: # no cover
if prompt():
self.create_provstore_file()
self.read_provstore_file()
else:
return
# Any other errors, return None and raise Exception
except (ValueError, AssertionError, UnboundLocalError): # no cover
print(
"\n".join(
could_not_read
+ [
"Please create one with bioprov.config.create_provstore_file() method."
]
)
)
self.provstore_user = None
self.provstore_token = None
return
def serializer(self):
keys_to_remove = [i for i in self.__dict__.keys() if i.startswith("_")] + [
"env",
]
serial_out = serializer_filter(self, keys_to_remove)
serial_out["provstore_file"] = self.provstore_file
return serial_out
class BioProvDB(TinyDB):
"""
Inherits from tinydb.TinyDB
Class to hold database configuration and methods.
"""
def __init__(self, path):
super().__init__(path)
self.db_path = path
def __repr__(self):
return f"BioProvDB located in {self.db_path}"
def clear_db(self, confirm=False): # no cover
"""
Deletes the local BioProv database.
:param confirm:
:return:
"""
proceed = True
if not confirm:
def _get_confirm():
print(
f"The BioProv database at {self.db_path} containing {len(self)} projects will be erased."
)
print(
"This action cannot be reversed. Are you sure you want to proceed? y/N"
)
get_confirm = input()
if get_confirm == "":
get_confirm = "n"
if get_confirm.lower() in ("y", "yes"):
return True
elif get_confirm.lower() in ("n", "no"):
return False
else:
print("Invalid option. Please pick 'y' or 'n'.")
_get_confirm()
proceed = _get_confirm()
if proceed:
self.truncate()
print("Erased BioProv database.")
else:
print("Canceled operation.")
class EnvProv:
"""
Class containing provenance information about the current environment.
"""
def __init__(self):
"""
Class constructor. All attributes are empty and are initialized with self.update()
"""
self.env_hash = None
self.env_dict = None
self.user = None
self.env_namespace = None
self.update()
def __repr__(self):
return f"Environment_{self.env_hash}"
def update(self):
"""
Checks current environment and updates attributes using the os.environ module.
:return: Sets attributes to self.
"""
env_dict = dict(os.environ.items())
env_hash = dict_to_sha1(env_dict)
if env_hash != self.env_hash:
self.env_dict = env_dict
self.env_hash = env_hash
# this is only to prevent build errors
try:
self.user = self.env_dict["USER"]
except KeyError: # no cover
self.env_dict["USER"] = "unknown" # no cover
self.env_namespace = Namespace("envs", str(self))
def serializer(self):
return serializer(self)
config = Config()