diff --git a/trains/backend_interface/metrics/events.py b/trains/backend_interface/metrics/events.py index 33ccd986..0f08883e 100644 --- a/trains/backend_interface/metrics/events.py +++ b/trains/backend_interface/metrics/events.py @@ -11,6 +11,7 @@ from six.moves.urllib.parse import urlparse, urlunparse from ...backend_api.services import events from ...config import config +from ...storage.util import quote_url @six.add_metaclass(abc.ABCMeta) @@ -294,7 +295,7 @@ class UploadEvent(MetricsEventAdapter): delete_local_file=local_file if self._delete_after_upload else None, ) - def get_target_full_upload_uri(self, storage_uri, storage_key_prefix=None): + def get_target_full_upload_uri(self, storage_uri, storage_key_prefix=None, quote_uri=True): e_storage_uri = self._upload_uri or storage_uri # if we have an entry (with or without a stream), we'll generate the URL and store it in the event filename = self._upload_filename @@ -306,6 +307,10 @@ class UploadEvent(MetricsEventAdapter): # make sure we preserve local path root if e_storage_uri.startswith('/'): url = '/'+url + + if quote_uri: + url = quote_url(url) + return key, url @@ -319,16 +324,7 @@ class ImageEvent(UploadEvent): def get_api_event(self): return events.MetricsImageEvent( - # Hack: replace single '%' with quoted value '%25', - # allowing the link to be properly unquoted during http serving - url=self._url - if ( - not self._url - or self._url.startswith("file://") - or self._url.startswith("/") - or self._url.startswith("\\") - ) - else self._url.replace("%", "%25"), + url=self._url, key=self._key, **self._get_base_dict() ) diff --git a/trains/backend_interface/metrics/interface.py b/trains/backend_interface/metrics/interface.py index 7a53b652..69f2a96a 100644 --- a/trains/backend_interface/metrics/interface.py +++ b/trains/backend_interface/metrics/interface.py @@ -119,7 +119,7 @@ class Metrics(InterfaceBase): entry = ev.get_file_entry() kwargs = {} if entry: - key, url = ev.get_target_full_upload_uri(storage_uri, self.storage_key_prefix) + key, url = ev.get_target_full_upload_uri(storage_uri, self.storage_key_prefix, quote_uri=False) kwargs[entry.key_prop] = key kwargs[entry.url_prop] = url if not entry.stream: diff --git a/trains/backend_interface/metrics/reporter.py b/trains/backend_interface/metrics/reporter.py index 076d1782..4095903e 100644 --- a/trains/backend_interface/metrics/reporter.py +++ b/trains/backend_interface/metrics/reporter.py @@ -552,11 +552,6 @@ class Reporter(InterfaceBase, AbstractContextManager, SetupUploadMixin, AsyncMan return self.report_image_and_upload(title=title, series=series, iter=iter, path=path, image=matrix, upload_uri=upload_uri, max_image_history=max_image_history) - # Hack: replace single '%' with quoted value '%25', - # allowing the link to be properly unquoted during http serving - if url: - url = url.replace('%', '%25') - self._report(ev) plotly_dict = create_image_plot( image_src=url, diff --git a/trains/storage/helper.py b/trains/storage/helper.py index f3bfe7a5..cf0f76c1 100644 --- a/trains/storage/helper.py +++ b/trains/storage/helper.py @@ -30,6 +30,7 @@ from six.moves.queue import Queue, Empty from six.moves.urllib.parse import urlparse from six.moves.urllib.request import url2pathname +from .util import quote_url from ..backend_api.utils import get_http_session_with_retry from ..backend_config.bucket_config import S3BucketConfigurations, GSBucketConfigurations, AzureContainerConfigurations from ..config import config @@ -547,6 +548,10 @@ class StorageHelper(object): if last_ex: raise last_ex + if self.scheme in _HttpDriver.schemes: + # qoute link + dest_path = quote_url(dest_path) + return dest_path def upload(self, src_path, dest_path=None, extra=None, async_enable=False, cb=None, retries=1): diff --git a/trains/storage/util.py b/trains/storage/util.py index a9f4e29e..db416c4f 100644 --- a/trains/storage/util.py +++ b/trains/storage/util.py @@ -1,3 +1,4 @@ +from six.moves.urllib.parse import quote, urlparse, urlunparse import six import fnmatch @@ -16,3 +17,11 @@ def get_config_object_matcher(**patterns): if pat and fnmatch.fnmatch(value, pat): return True return _matcher + + +def quote_url(url): + parsed = urlparse(url) + if parsed.scheme not in ('http', 'https'): + return url + parsed = parsed._replace(path=quote(parsed.path)) + return urlunparse(parsed)