-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathget_env.py
executable file
·392 lines (322 loc) · 10.1 KB
/
get_env.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
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
#!/usr/bin/env python
__author__ = "P Lewis"
__copyright__ = "Copyright 2020 P Lewis"
__license__ = "GPLv3"
__email__ = "p.lewis@ucl.ac.uk"
import yaml
import json
import os
import sys
import getopt
from pathlib import Path
class getEnv():
'''
Utility to get environment variables
from-to various format
takes setup dictionary variable if it exists.
'''
def __init__(self,filename=None,initial=None,override=None,\
upper=False,export=True,stripstr='"'):
'''
Read environments from yaml, json
or bash environment
Start with initial
Override with dictionary override
Write to yaml, json
or bash environment
Options:
filename=None : filename to read data from
initial=None : initial dictionary
override=None ~: override dictionary
Any of these can be None (so {}), but the logig is that
override over-rides filename over-rides initial
For bash env:
upper=False : if set True, key names converted to lower case on read
(to be consistent with conversion to upper on write)
export=True : if set True, write bash op as "export KEY=VALUE"
'''
self.upper = upper
self.initial = initial or {}
self.override = override or {}
self.filename = filename
self.export = export
self.info = self.initial.copy()
# get info from file
self.read_info = (self.filename and self.get_load(self.filename,stripstr=stripstr)) or {}
self.info.update(self.read_info)
self.info.update(self.override)
def write(self,filename,append=True,tidy=False):
'''
Write the info in self.info to filename.
The type is determined by the suffix.
Options:
append=True : add this to the existing information
over-riding what may be there
tidy=False : flag to try to tidy up file in case of
multiple entries (as in multiple lines of the same
thing in a bash file). NOT IMPLEMENTED
Also:
self.export=True : add 'export' to env if bash
'''
filename = Path(filename).expanduser()
suffix = filename.suffix.lower()
if (suffix == '.yml') or (suffix == '.yaml'):
info = (append and self.get_load(filename)) or {}
info.update(self.info)
with open(filename,'w') as f:
yaml.dump(info,f)
elif (suffix == '.jsn') or (suffix == '.json'):
info = (append and self.get_load(filename)) or {}
info.update(self.info)
with open(filename,'w') as f:
json.dump(info,f)
else:
# convert dict to env list
env = self.convert_to_env(self.info)
with open(filename,(append and 'a') or 'w') as f:
for l in env:
f.writelines(l)
def convert_to_env(self,info,nl=True):
'''
convert dictionary info (1-deep) from info into
list of bash env strings
options:
nl : add newline to each (default True)
e.g.
self.export=False
self.convert_to_env({'hello': 'world'})
[['hello=world\n']]
e.g.
self.export=True
self.convert_to_env({'hello': 'world'})
[['export hello=world\n']]
e.g.
self.upper = True
self.convert_to_env({'hello': 'world'})
[['export HELLO=world\n']]
'''
nl = (nl and '\n') or ''
pre = (self.export and 'export ') or ''
return [f'{pre}{self.upper and k.upper() or k}={v}{nl}' for k,v in info.items()]
def get_load(self,filename,stripstr='"'):
'''
load as implied by filename
else, assume its a text file containing
env variables of the form
KEY=VALUE
or
export KEY=VALUE
It is non-strict on spaces (unlike bash)
self.upper=False : if set True, key names converted to lower case on read
(to be consistent with conversion to upper on write)
'''
filename = Path(filename).expanduser()
suffix = filename.suffix.lower()
retval = {}
# return retval isd not exist
if not filename.exists():
return retval
with open(filename,'r') as f:
if (suffix == '.yml') or (suffix == '.yaml'):
retval = yaml.safe_load(f)
elif (suffix == '.jsn') or (suffix == '.json'):
retval = json.load(f)
else:
# some sort of bash-type env, with '=' sep and # comments
retval1 = [i.strip().split('#')[0] for i in f.readlines()]
retval = {}
for i in retval1:
# remove any export
f = i.find('export ')
if f >= 0:
i = i[len('export ')+f:]
# split on first =
f = i.find('=')
if f > 0:
key = i[:f].strip()
value_str = i[f+1: ].strip()
value = json.loads(value_str.replace("'",'"'))
retval[key] = value
else:
# not sure we want to be here`??
# so lump it in anyway
retval['no_key'] = i
# lower-case the keys
if self.upper:
for k in retval.keys():
value = retval[k]
del retval[k]
retval[k.lower()] = value
# now, look for sub-dictionaries
for k,v in retval.items():
if type(v) is str:
retval[k] = self.get_dict(v)
return retval
# for test
meta_yaml = '''
# ipypi and conda parameters
#
package:
name: uclgeog
version: 1.0.0
source:
git_rev: 1.0.0
git_url: https://github.com/UCL-EO/uclgeog
build:
noarch: python
number: 0
script: python -m pip install --no-deps --ignore-installed .
requirements:
build:
- python>=3.7
- setuptools
run:
- python
test:
imports:
- gdal
about:
home: https://github.com/UCL-EO/uclgeog
description: UCL Geography course notes
author: Prof. P. Lewis
email: p.lewis@ucl.ac.uk
keywords: scientific computing
license: MIT
docker:
user: proflewis
tag: latest
nb_user: uclgeog
'''
def main(argv):
ifile=None
initial=None
override=None
upper=False
export=False
ofile=None
verbose=False
convert_to_env=False
append=True
help = False
helpstr = f"{sys.argv[0]} [-e|--export] [-v|--verbose] [-h|--help]" + "\n" + \
"[-I|--initial=initial.sh] [-O|--override=override.sh]" + "\n" + \
"[-i|--ifile=input.sh] [-o|--ofile=ofile.sh]" + "\n" + \
"[-t | --test] [-a|--append or -w|--write] [-c|--convert_to_env]"
try:
opts, args = getopt.getopt(argv,"twcavi:I:o:O:eu",["ifile=","initial=","ofile=","override="])
except getopt.GetoptError:
print(helpstr)
sys.exit(2)
for opt, arg in opts:
if opt in ("-i","--ifile"):
ifile=arg
elif opt in ("-I","--initial"):
initial=arg
elif opt in ("-O","--override"):
override=arg
elif opt in ("-o","--ofile"):
ofile=arg
elif opt in ("-t", "--test"):
test();
sys.exit()
elif opt in ("-v", "--verbose"):
verbose = True
elif opt in ('-u','--upper'):
upper = True
elif opt in ('-a','--append'):
append = True
elif opt in ('-w','--write'):
append = False
elif opt in ('-e','--export'):
export = True
elif opt in ('-h','--help'):
print(helpstr)
sys.exit()
elif opt in ('-c','--convert_to_env'):
convert_to_env = True
# read files
if verbose and initial:
print(f"reading initial from {initial}")
initial = (initial and getEnv(filename=initial,upper=upper,export=export).info) or {}
if verbose and override:
print(f"reading override from {override}")
override = (override and getEnv(filename=override,upper=upper,export=export).info) or {}
if verbose and ifile:
print(f"reading data from {ifile}")
env = getEnv(filename=ifile,upper=upper,export=export,override=override,initial=initial)
if verbose and ofile:
print(f"writing to ofile\nappend={append}")
ofile and env.write(ofile,append=append)
# maybe pretty this
if verbose:
if convert_to_env:
print('converting to env')
print(env.convert_to_env(env.info));
else:
print('printing dictionary')
print(env.info);
def test(tdir='.test'):
'''
a test
'''
import deepdiff
tdir = Path(tdir).expanduser()
tdir.mkdir(parents=True, exist_ok=True)
ifile_yml = 'input_test1.yml'
tfile = tdir.joinpath(ifile_yml)
# write meta_yaml
with open(tfile,'w') as f:
f.writelines(meta_yaml)
# now read it
result1 = getEnv(filename=tfile)
ofile_yml = 'test1.yml'
tfile = tdir.joinpath(ofile_yml)
# write result1 to yml
result1.write(tfile,append=False)
ofile_json = 'test1.json'
tfile = tdir.joinpath(ofile_json)
# write result1 to json
result1.write(tfile,append=False)
ofile_sh = 'test1.sh'
tfile = tdir.joinpath(ofile_sh)
# write result1 to sh
result1.write(tfile,append=False)
# read
result1_yml = getEnv(filename=tdir.joinpath(ofile_yml)).info
result1_sh = getEnv(filename=tdir.joinpath(ofile_sh)).info
result1_jsn = getEnv(filename=tdir.joinpath(ofile_json)).info
try:
# see if yaml and json are the same
assert deepdiff.DeepDiff(result1_yml,result1_jsn) == {}
except AssertionError as error:
print('AssertionError: see if yaml and json are the same',error)
print(deepdiff.DeepDiff(result1_yml,result1_jsn))
print('='*40)
print(tdir.joinpath(ofile_yml))
print('='*40)
print(result1_yml)
print('='*40)
print(tdir.joinpath(ofile_jsn))
print('='*40)
print(result1_jsn)
except Exception as exception:
print('Exception: see if yaml and json are the same',exception)
try:
# see if yaml and sh are the same
assert deepdiff.DeepDiff(result1_yml,result1_sh) == {}
except AssertionError as error:
print('AssertionError: see if yaml and sh are the same',error)
print(deepdiff.DeepDiff(result1_yml,result1_sh))
print('='*40)
print(tdir.joinpath(ofile_yml))
print('='*40)
print(result1_yml)
print('='*40)
print(tdir.joinpath(ofile_sh))
print('='*40)
print(result1_sh)
except Exception as exception:
print('Exception: see if yaml and sh are the same',exception)
return True
if __name__ == "__main__":
main(sys.argv[1:])