From fd5f8479af2809865974a82ec39869c9ec59de25 Mon Sep 17 00:00:00 2001 From: sharkykh Date: Wed, 10 Jul 2019 17:12:57 +0300 Subject: [PATCH] API v2 fixes (#6931) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Better stack trace for API v2 on Python 2 * Fix `text/plain` response charset Before: `2019-07-10 02:12:30 INFO MAIN :: [f1dc11a] 76703: Unable to find IMDb info in the database: Pokֳ©mon` After: `2019-07-10 02:13:23 INFO MAIN :: [f1dc11a] 76703: Unable to find IMDb info in the database: Pokémon` * Update changelog * Update base.py Remove `future` import --- CHANGELOG.md | 2 ++ medusa/server/api/v2/base.py | 30 +++++++++++++++++++++++++----- medusa/server/api/v2/log.py | 2 +- 3 files changed, 28 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5cd6c7984..c251492a4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,13 @@ #### Improvements - Converted the footer to a Vue component ([#4520](https://github.com/pymedusa/Medusa/pull/4520)) - Converted Edit Show to a Vue SFC ([#4486](https://github.com/pymedusa/Medusa/pull/4486) +- Improved API v2 exception reporting on Python 2 ([#6931](https://github.com/pymedusa/Medusa/pull/6931)) #### Fixes - Fixed hdtorrent provider parse the publishing date with the day first ([#6847](https://github.com/pymedusa/Medusa/pull/6847)) - Fixed release link on Help & Info page ([#6854](https://github.com/pymedusa/Medusa/pull/6854)) - Fixed FreeMobile notifier message encode error ([#6867](https://github.com/pymedusa/Medusa/pull/6867)) +- Fixed charset on API v2 responses with plain text content ([#6931](https://github.com/pymedusa/Medusa/pull/6931)) ----- diff --git a/medusa/server/api/v2/base.py b/medusa/server/api/v2/base.py index 7f59f7c22c..49687ab14c 100644 --- a/medusa/server/api/v2/base.py +++ b/medusa/server/api/v2/base.py @@ -9,7 +9,6 @@ import logging import sys import traceback -from builtins import object from collections import OrderedDict from concurrent.futures import ThreadPoolExecutor from datetime import date, datetime @@ -23,8 +22,9 @@ from medusa import app from medusa.logger.adapters.style import BraceAdapter -from six import ensure_text, iteritems, string_types, text_type, viewitems +from six import PY2, ensure_text, iteritems, string_types, text_type, viewitems +from tornado.concurrent import Future as TornadoFuture from tornado.gen import coroutine from tornado.httputil import url_concat from tornado.ioloop import IOLoop @@ -60,8 +60,28 @@ def async_call(self, *args, **kwargs): return # Authentication check passed, run the method in a thread - prepared = partial(method, *args, **kwargs) - content = yield IOLoop.current().run_in_executor(executor, prepared) + if PY2: + # On Python 2, the original exception stack trace is not passed from the executor. + # This is a workaround based on https://stackoverflow.com/a/27413025/7597273 + tornado_future = TornadoFuture() + + def wrapper(): + try: + result = method(*args, **kwargs) + except: # noqa: E722 [do not use bare 'except'] + tornado_future.set_exc_info(sys.exc_info()) + else: + tornado_future.set_result(result) + + # `executor.submit()` returns a `concurrent.futures.Future`; wait for it to finish, but ignore the result + yield executor.submit(wrapper) + # When this future is yielded, any stored exceptions are raised (with the correct stack trace). + content = yield tornado_future + else: + # On Python 3+, exceptions contain their original stack trace. + prepared = partial(method, *args, **kwargs) + content = yield IOLoop.current().run_in_executor(executor, prepared) + self.finish(content) # This creates a bound method `instance.async_call`, @@ -139,7 +159,7 @@ def write_error(self, status_code, *args, **kwargs): error = exc_info[1].log_message or exc_info[1].reason response = self.api_response(status=status_code, error=error) elif app.DEVELOPER and exc_info: - self.set_header('content-type', 'text/plain') + self.set_header('content-type', 'text/plain; charset=UTF-8') self.set_status(500) for line in traceback.format_exception(*exc_info): self.write(line) diff --git a/medusa/server/api/v2/log.py b/medusa/server/api/v2/log.py index 7fc05d990b..4193e7f59c 100644 --- a/medusa/server/api/v2/log.py +++ b/medusa/server/api/v2/log.py @@ -134,7 +134,7 @@ def data_generator(): return self._paginate(data_generator=data_generator) else: text = '\n'.join(data_generator()) - return self._ok(stream=text, content_type='text/plain') + return self._ok(stream=text, content_type='text/plain; charset=UTF-8') def post(self): """Create a log line.