-
Notifications
You must be signed in to change notification settings - Fork 39
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
13 changed files
with
308 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
"""Interface for accessing Sacred files.""" | ||
|
||
|
||
class FilesDAO: | ||
"""Interface for accessing files.""" | ||
|
||
def get(self, file_id): | ||
""" | ||
Return the file associated with the id. | ||
:raise NotFoundError when not found | ||
""" | ||
raise NotImplementedError("RunDAO is abstract.") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
"""Module responsible for accessing the MongoDB database.""" | ||
from .genericdao import GenericDAO | ||
from .metricsdao import MongoMetricsDAO | ||
from .filesdao import MongoFilesDAO | ||
from .mongodb import PyMongoDataAccess | ||
|
||
__all__ = ("MongoMetricsDAO", "GenericDAO", "PyMongoDataAccess") | ||
__all__ = ("MongoMetricsDAO", "GenericDAO", "PyMongoDataAccess", "MongoFilesDAO") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
"""Module responsible for accessing the Files data in MongoDB.""" | ||
from typing import Union | ||
|
||
import bson | ||
import gridfs | ||
|
||
from sacredboard.app.data.pymongo import GenericDAO | ||
from sacredboard.app.data.filesdao import FilesDAO | ||
|
||
|
||
class MongoFilesDAO(FilesDAO): | ||
"""Implementation of FilesDAO for MongoDB.""" | ||
|
||
def __init__(self, generic_dao: GenericDAO): | ||
""" | ||
Create new Files accessor for MongoDB. | ||
:param generic_dao: A configured generic MongoDB data access object | ||
pointing to an appropriate database. | ||
""" | ||
self.generic_dao = generic_dao | ||
|
||
self._fs = gridfs.GridFS(self.generic_dao._database) | ||
|
||
def get(self, file_id: Union[str, bson.ObjectId]) -> gridfs.GridOut: | ||
""" | ||
Return the file identified by a file_id string. | ||
The return value is a file-like object which also has the following attributes: | ||
filename: str | ||
upload_date: datetime | ||
""" | ||
if isinstance(file_id, str): | ||
file_id = bson.ObjectId(file_id) | ||
return self._fs.get(file_id) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
"""Accessing the files.""" | ||
from enum import Enum | ||
import io | ||
import os | ||
import mimetypes | ||
import zipfile | ||
|
||
from flask import Blueprint, current_app, render_template, send_file, Response | ||
|
||
from sacredboard.app.data import NotFoundError | ||
|
||
|
||
files = Blueprint("files", __name__) | ||
|
||
|
||
class _FileType(Enum): | ||
ARTIFACT = 1 | ||
SOURCE = 2 | ||
|
||
|
||
_filetype_suffices = { | ||
_FileType.ARTIFACT: "artifact", | ||
_FileType.SOURCE: "source", | ||
} | ||
|
||
|
||
def _get_binary_info(binary: bytes): | ||
hex_data = "" | ||
for i in range(0, 10): | ||
if i > 0: | ||
hex_data += " " | ||
hex_data += hex(binary[i]) | ||
hex_data += " ..." | ||
return "Binary data\nLength: {}\nFirst 10 bytes: {}".format(len(binary), hex_data) | ||
|
||
|
||
def get_file(file_id: str, download): | ||
""" | ||
Get a specific file from GridFS. | ||
Returns a binary stream response or HTTP 404 if not found. | ||
""" | ||
data = current_app.config["data"] # type: DataStorage | ||
dao = data.get_files_dao() | ||
file = dao.get(file_id) | ||
|
||
if download: | ||
mime = mimetypes.guess_type(file.filename)[0] | ||
if mime is None: | ||
# unknown type | ||
mime = "binary/octet-stream" | ||
|
||
basename = os.path.basename(file.filename) | ||
return send_file(file, mimetype=mime, attachment_filename=basename, as_attachment=True) | ||
else: | ||
rawdata = file.read() | ||
try: | ||
text = rawdata.decode('utf-8') | ||
except UnicodeDecodeError: | ||
# not decodable as utf-8 | ||
text = _get_binary_info(rawdata) | ||
html = render_template("api/file_view.html", content=text) | ||
return Response(html) | ||
|
||
|
||
def get_files_zip(run_id: int, filetype: _FileType): | ||
"""Send all artifacts or sources of a run as ZIP.""" | ||
data = current_app.config["data"] | ||
dao_runs = data.get_run_dao() | ||
dao_files = data.get_files_dao() | ||
run = dao_runs.get(run_id) | ||
|
||
if filetype == _FileType.ARTIFACT: | ||
target_files = run['artifacts'] | ||
elif filetype == _FileType.SOURCE: | ||
target_files = run['experiment']['sources'] | ||
else: | ||
raise Exception("Unknown file type: %s" % filetype) | ||
|
||
memory_file = io.BytesIO() | ||
with zipfile.ZipFile(memory_file, 'w') as zf: | ||
for f in target_files: | ||
# source and artifact files use a different data structure | ||
file_id = f['file_id'] if 'file_id' in f else f[1] | ||
file = dao_files.get(file_id) | ||
data = zipfile.ZipInfo(file.filename, date_time=file.upload_date.timetuple()) | ||
data.compress_type = zipfile.ZIP_DEFLATED | ||
zf.writestr(data, file.read()) | ||
memory_file.seek(0) | ||
|
||
fn_suffix = _filetype_suffices[filetype] | ||
return send_file(memory_file, attachment_filename='run{}_{}.zip'.format(run_id, fn_suffix), as_attachment=True) | ||
|
||
|
||
@files.route("/api/file/<string:file_id>") | ||
def api_file(file_id): | ||
"""Download a file.""" | ||
return get_file(file_id, True) | ||
|
||
|
||
@files.route("/api/fileview/<string:file_id>") | ||
def api_fileview(file_id): | ||
"""View a file.""" | ||
return get_file(file_id, False) | ||
|
||
|
||
@files.route("/api/artifacts/<int:run_id>") | ||
def api_artifacts(run_id): | ||
"""Download all artifacts of a run as ZIP.""" | ||
return get_files_zip(run_id, _FileType.ARTIFACT) | ||
|
||
|
||
@files.route("/api/sources/<int:run_id>") | ||
def api_sources(run_id): | ||
"""Download all sources of a run as ZIP.""" | ||
return get_files_zip(run_id, _FileType.SOURCE) | ||
|
||
|
||
@files.errorhandler(NotFoundError) | ||
def handle_not_found_error(e): | ||
"""Handle exception when a metric is not found.""" | ||
return "Couldn't find resource:\n%s" % e, 404 | ||
|
||
|
||
def initialize(app, app_config): | ||
"""Register the module in Flask.""" | ||
app.register_blueprint(files) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
"use strict"; | ||
define(["knockout", "text!runs/filesBrowser/template.html"], | ||
function (ko, htmlTemplate) { | ||
ko.components.register("files-browser", { | ||
viewModel: function (params) { | ||
var self = this; | ||
this.run_id = params.run_id; | ||
this.files = params.files; | ||
this.url_all = params.all_url; | ||
this.files = []; | ||
|
||
// sources files just are arrays. convert to objects here | ||
params.files.forEach((element, index, array) => { | ||
if (Array.isArray(element)) { | ||
this.files.push({name: element[0], file_id: element[1]}); | ||
} | ||
else { | ||
// just pass through | ||
this.files.push(element); | ||
} | ||
}); | ||
|
||
this.downloadFile = function (file) { | ||
window.location.href = `api/file/${file.file_id.$oid}`; | ||
}; | ||
|
||
this.viewFile = function (file) { | ||
window.open(`api/fileview/${file.file_id.$oid}`, "_blank"); | ||
}; | ||
|
||
this.downloadFileAll = function (vm) { | ||
window.location.href = this.url_all + vm.run_id; | ||
}; | ||
}, | ||
template: htmlTemplate | ||
}); | ||
}); |
25 changes: 25 additions & 0 deletions
25
sacredboard/static/scripts/runs/filesBrowser/template.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
<!-- ko if: files.length > 0 --> | ||
<table class="table table-condensed table-hover" | ||
style="border-left: groove"> | ||
<tbody as data-bind="foreach: files"> | ||
<tr> | ||
<td data-bind="text: name"></td> | ||
<td> | ||
<button class="btn btn-info" data-bind="click: $parent.viewFile"> | ||
VIEW | ||
</button> | ||
<button class="btn btn-primary" data-bind="click: $parent.downloadFile"> | ||
DOWNLOAD | ||
</button> | ||
</td> | ||
</tr> | ||
</tbody> | ||
</table> | ||
<button class="btn btn-primary" data-bind="click: downloadFileAll"> | ||
DOWNLOAD ALL | ||
</button> | ||
<!-- /ko --> | ||
<!-- ko ifnot: files.length > 0 --> | ||
No files | ||
<!-- /ko --> | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
<style type="text/css"> | ||
div { | ||
white-space: pre-wrap; | ||
} | ||
</style> | ||
<div>{{ content }}</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters