Skip to content

Commit 3de5e8e

Browse files
authored
Merge pull request #51 from vsoch/master
adding interactive tree for singularity hub
2 parents 03ced77 + 3caa141 commit 3de5e8e

File tree

8 files changed

+165
-22
lines changed

8 files changed

+165
-22
lines changed

singularity/analysis/classify.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
from singularity.package import package as make_package
2323
from singularity.utils import (
2424
get_installdir,
25+
remove_uri,
26+
read_file,
2527
update_dict,
2628
update_dict_sum
2729
)
@@ -175,6 +177,33 @@ def get_tags(container=None,image_package=None,sudopw=None,search_folders=None,d
175177
###################################################################################
176178

177179

180+
def get_files(container,S=None,tmpdir=None):
181+
'''get_files will return a list of files inside a container, sorted by name
182+
:param container: the container to use, either shub:// or docker:// or actual
183+
'''
184+
files = None
185+
if tmpdir == None:
186+
tmpdir = tempfile.mkdtemp()
187+
tmpfile = "%s/files.txt" %tmpdir
188+
container_name = remove_uri(container)
189+
command = ' ls -LR >> %s 2>/dev/null' %(tmpfile)
190+
if S==None:
191+
S = Singularity(sudo=None)
192+
result = S.execute(container,command)
193+
if os.path.exists(tmpfile):
194+
os.system("sed -i '/^$/d' %s" %(tmpfile))
195+
os.system('sort %s -or %s' %(tmpfile,tmpfile))
196+
files = read_file(tmpfile)
197+
if len(files) > 0:
198+
files = [x for x in files if x.startswith('.')]
199+
files = [x.split(container_name)[1:] for x in files]
200+
files = [x for x in files if len(x) > 0]
201+
files = [x[0] for x in files]
202+
shutil.rmtree(tmpdir)
203+
return files
204+
205+
206+
178207
def file_counts(container=None,patterns=None,image_package=None,sudopw=None,diff=None):
179208
'''file counts will return a list of files that match one or more regular expressions.
180209
if no patterns is defined, a default of readme is used. All patterns and files are made

singularity/analysis/compare.py

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ def container_similarity_vector(container1=None,packages_set=None,by=None,custom
7575
return comparisons
7676

7777

78+
7879
def compare_containers(container1=None,container2=None,by=None,
7980
image_package1=None,image_package2=None):
8081
'''compare_containers will generate a data structure with common and unique files to
@@ -105,19 +106,29 @@ def compare_containers(container1=None,container2=None,by=None,
105106
# Do the comparison for each metric
106107
comparisons = dict()
107108
for b in by:
108-
intersect = list(set(container1_guts[b]).intersection(container2_guts[b]))
109-
unique1 = list(set(container1_guts[b]).difference(container2_guts[b]))
110-
unique2 = list(set(container2_guts[b]).difference(container1_guts[b]))
111-
112-
# Return data structure
113-
comparison = {"intersect":intersect,
114-
"unique1": unique1,
115-
"unique2": unique2,
116-
"total1": len(container1_guts[b]),
117-
"total2": len(container2_guts[b])}
118-
comparisons[b] = comparison
109+
comparisons[b] = compare_lists(container1_guts[b],container2_guts[b])
119110

120111
return comparisons
112+
113+
114+
def compare_lists(list1,list2):
115+
'''compare lists is the lowest level that drives compare_containers and
116+
compare_packages. It returns a comparison object (dict) with the unique,
117+
total, and intersecting things between two lists
118+
:param list1: the list for container1
119+
:param list2: the list for container2
120+
'''
121+
intersect = list(set(list1).intersection(list2))
122+
unique1 = list(set(list1).difference(list2))
123+
unique2 = list(set(list2).difference(list1))
124+
125+
# Return data structure
126+
comparison = {"intersect":intersect,
127+
"unique1": unique1,
128+
"unique2": unique2,
129+
"total1": len(list1),
130+
"total2": len(list2)}
131+
return comparison
121132

122133

123134
def calculate_similarity(container1=None,container2=None,image_package1=None,

singularity/build/converter.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -208,15 +208,17 @@ def get_mapping():
208208
# Docker : Singularity
209209
add_command = {"section": "%post","fun": parse_add, "json": True }
210210
copy_command = {"section": "%post", "fun": parse_add, "json": True }
211-
cmd_command = {"section": "%runscript", "fun": parse_cmd, "json": True }
211+
cmd_command = {"section": "%post", "fun": parse_comment, "json": True }
212+
label_command = {"section": "%post", "fun": parse_comment, "json": True }
213+
port_command = {"section": "%post", "fun": parse_comment, "json": True }
212214
env_command = {"section": "%post", "fun": parse_env, "json": False }
213215
comment_command = {"section": "%post", "fun": parse_comment, "json": False }
214216
from_command = {"section": "From", "json": False }
215217
run_command = {"section": "%post", "json": True}
216218
workdir_command = {"section": "%post","fun": parse_workdir, "json": False }
217-
entry_command = {"section": "%post", "fun": parse_entry, "json": True }
219+
entry_command = {"section": "%runscript", "fun": parse_entry, "json": True }
218220

219-
return {"ADD": add_command,
221+
return {"ADD":add_command,
220222
"COPY":copy_command,
221223
"CMD":cmd_command,
222224
"ENTRYPOINT":entry_command,
@@ -225,7 +227,9 @@ def get_mapping():
225227
"RUN":run_command,
226228
"WORKDIR":workdir_command,
227229
"MAINTAINER":comment_command,
228-
"VOLUME":comment_command}
230+
"VOLUME":comment_command,
231+
"EXPOSE":port_command,
232+
"LABEL":label_command}
229233

230234

231235

@@ -352,7 +356,7 @@ def print_sections(sections,mapping=None):
352356
mapping = get_mapping()
353357

354358
finished_spec = None
355-
ordering = ['bootstrap',"From","%runscript","%post"]
359+
ordering = ['bootstrap',"From","%runscript","%post",'%test']
356360

357361
for section in ordering:
358362

singularity/cli.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@
2727

2828
class Singularity:
2929

30-
def __init__(self,sudo=True,sudopw=None,debug=False):
30+
31+
def __init__(self,sudo=False,sudopw=None,debug=False):
3132
'''upon init, store user password to not ask for it again'''
3233

3334
self.sudopw = sudopw
@@ -117,7 +118,7 @@ def execute(self,image_path,command,writable=False,contain=False):
117118
if self.debug == True:
118119
cmd = ["singularity",'--debug',"exec"]
119120
else:
120-
cmd = ["singularity","exec"]
121+
cmd = ["singularity",'--quiet',"exec"]
121122

122123
cmd = self.add_flags(cmd,writable=writable,contain=contain)
123124

@@ -194,6 +195,21 @@ def importcmd(self,image_path,input_source,import_type=None,command=None):
194195
return None
195196

196197

198+
def pull(self,image_path):
199+
'''pull will pull a singularity hub image
200+
:param image_path: full path to image
201+
'''
202+
if not image_path.startswit('shub://'):
203+
bot.logger.error("pull is only valid for the shub://uri, %s is invalid.",image_name)
204+
sys.exit(1)
205+
206+
if self.debug == True:
207+
cmd = ['singularity','--debug','pull',image_path]
208+
else:
209+
cmd = ['singularity','pull',image_path]
210+
return self.run_command(cmd)
211+
212+
197213

198214
def run(self,image_path,command,writable=False,contain=False):
199215
'''run will run a command inside the container, probably not intended for within python
@@ -213,6 +229,7 @@ def run(self,image_path,command,writable=False,contain=False):
213229
# Run the command
214230
return self.run_command(cmd,sudo=sudo)
215231

232+
216233
def start(self,image_path,writable=False,contain=False):
217234
'''start will start a container
218235
'''
@@ -276,7 +293,7 @@ def get_image(image,return_existed=False,sudopw=None,size=None,debug=False):
276293
cli = Singularity(debug=debug) # This command will ask the user for sudo
277294

278295
tmpdir = tempfile.mkdtemp()
279-
image_name = "%s.img" %image.replace("docker://","")
296+
image_name = "%s.img" %image.replace("docker://","").replace("/","-")
280297
bot.logger.info("Found docker image %s, creating and importing...",image_name)
281298
image_path = "%s/%s" %(tmpdir,image_name)
282299
cli.create(image_path,size=size)

singularity/scripts.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ def get_parser():
4040
help="package a singularity container for singularity hub",
4141
default=False, action='store_true')
4242

43+
# Does the user want to package an image?
44+
parser.add_argument('--remove-image', dest="remove_image",
45+
help="remove image file from the package",
46+
default=False, action='store_true')
47+
4348
# Does the user want to estimate the os?
4449
parser.add_argument('--os', dest="os",
4550
help="estimate the operating system of your container.",
@@ -165,7 +170,8 @@ def main():
165170
package(image_path=image,
166171
output_folder=output_folder,
167172
runscript=True,
168-
software=True)
173+
software=True,
174+
remove_image=args.remove_image)
169175
else:
170176
print("Not sure what to do?")
171177
parser.print_help()

singularity/utils.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import requests
1212

1313
import shutil
14+
import json
1415
import simplejson
1516
import singularity.__init__ as hello
1617
from singularity.logman import bot
@@ -225,6 +226,15 @@ def read_file(filename,mode="r"):
225226
return content
226227

227228

229+
def read_json(filename,mode='r'):
230+
'''read_json reads in a json file and returns
231+
the data structure as dict.
232+
'''
233+
with open(filename,mode) as filey:
234+
data = json.load(filey)
235+
return data
236+
237+
228238
############################################################################
229239
## OTHER MISC. #############################################################
230240
############################################################################
@@ -304,6 +314,12 @@ def format_container_name(name,special_characters=None):
304314
return ''.join(e.lower() for e in name if e.isalnum() or e in special_characters)
305315

306316

317+
def remove_uri(container):
318+
'''remove_uri will remove docker:// or shub:// from the uri
319+
'''
320+
return container.replace('docker://','').replace('shub://','')
321+
322+
307323
def download_repo(repo_url,destination,commit=None):
308324
'''download_repo
309325
:param repo_url: the url of the repo to clone from

singularity/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "1.0.8"
1+
__version__ = "1.1.1"
22
AUTHOR = 'Vanessa Sochat'
33
AUTHOR_EMAIL = '[email protected]'
44
NAME = 'singularity'

singularity/views/trees.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
singularity views/trees.py: part of singularity package
55
66
'''
7-
7+
from functools import reduce
88
import json
99

1010
from singularity.logman import bot
@@ -227,3 +227,63 @@ def make_package_tree(matrix=None,labels=None,width=25,height=10,title=None):
227227
leaf_font_size=8., # font size for the x axis labels
228228
labels=labels)
229229
return plt
230+
231+
232+
def make_interactive_tree(matrix=None,labels=None):
233+
'''make interactive tree will return complete html for an interactive tree
234+
:param title: a title for the plot, if not defined, will be left out.
235+
'''
236+
from scipy.cluster.hierarchy import (
237+
dendrogram,
238+
linkage,
239+
to_tree
240+
)
241+
242+
d3 = None
243+
from scipy.cluster.hierarchy import cophenet
244+
from scipy.spatial.distance import pdist
245+
246+
if isinstance(matrix,pandas.DataFrame):
247+
Z = linkage(matrix, 'ward') # clusters
248+
T = to_tree(Z, rd=False)
249+
250+
if labels == None:
251+
labels = matrix.index.tolist()
252+
lookup = dict(zip(range(len(labels)), labels))
253+
254+
# Create a dendrogram object without plotting
255+
dend = dendrogram(Z,no_plot=True,
256+
orientation="right",
257+
leaf_rotation=90., # rotates the x axis labels
258+
leaf_font_size=8., # font size for the x axis labels
259+
labels=labels)
260+
261+
d3 = dict(children=[], name="root")
262+
add_node(T, d3)
263+
label_tree(d3["children"][0],lookup)
264+
else:
265+
bot.logger.warning('Please provide data as pandas Data Frame.')
266+
return d3
267+
268+
269+
def add_node(node, parent):
270+
'''add_node will add a node to it's parent
271+
'''
272+
newNode = dict(node_id=node.id, children=[])
273+
parent["children"].append(newNode)
274+
if node.left: add_node(node.left, newNode)
275+
if node.right: add_node(node.right, newNode)
276+
277+
278+
def label_tree(n,lookup):
279+
'''label tree will again recursively label the tree
280+
:param n: the root node, usually d3['children'][0]
281+
:param lookup: the node/id lookup
282+
'''
283+
if len(n["children"]) == 0:
284+
leaves = [lookup[n["node_id"]]]
285+
else:
286+
leaves = reduce(lambda ls, c: ls + label_tree(c,lookup), n["children"], [])
287+
del n["node_id"]
288+
n["name"] = name = "|||".join(sorted(map(str, leaves)))
289+
return leaves

0 commit comments

Comments
 (0)