' in response.content, response.content
def test_login_without_last_login(self):
- self.reset.append( conf.AUTH.BACKEND.set_for_testing(["desktop.auth.backend.AllowFirstUserDjangoBackend"]) )
- self.reset.append( conf.AUTH.EXPIRES_AFTER.set_for_testing(10) )
+ self.reset.append(conf.AUTH.BACKEND.set_for_testing(["desktop.auth.backend.AllowFirstUserDjangoBackend"]))
+ self.reset.append(conf.AUTH.EXPIRES_AFTER.set_for_testing(10))
client = make_logged_in_client(username=self.test_username, password="test")
client.get('/accounts/logout')
@@ -787,9 +751,7 @@ def teardown_class(cls):
def setup_method(self, method):
self.c = Client()
- self.reset.append(
- conf.AUTH.BACKEND.set_for_testing(['desktop.auth.backend.AllowFirstUserDjangoBackend'])
- )
+ self.reset.append(conf.AUTH.BACKEND.set_for_testing(['desktop.auth.backend.AllowFirstUserDjangoBackend']))
def teardown_method(self, method):
for finish in self.reset:
@@ -799,11 +761,8 @@ def teardown_method(self, method):
if Group.objects.filter(name=self.test_username).exists():
Group.objects.filter(name=self.test_username).delete()
-
def test_login_does_not_reset_groups(self):
- self.reset.append(
- conf.AUTH.BACKEND.set_for_testing(["desktop.auth.backend.AllowFirstUserDjangoBackend"])
- )
+ self.reset.append(conf.AUTH.BACKEND.set_for_testing(["desktop.auth.backend.AllowFirstUserDjangoBackend"]))
client = make_logged_in_client(username=self.test_username, password="test")
client.get('/accounts/logout')
@@ -817,7 +776,6 @@ def test_login_does_not_reset_groups(self):
response = client.post('/hue/accounts/login/', dict(username=self.test_username, password="test"))
assert 302 == response.status_code
-
def test_login_set_auth_backend_in_profile(self):
client = make_logged_in_client(username=self.test_username, password="test")
@@ -829,11 +787,8 @@ def test_login_set_auth_backend_in_profile(self):
assert 'desktop.auth.backend.AllowFirstUserDjangoBackend' == existing_profile.data['auth_backend']
-
def test_login_long_username(self):
- self.reset.append(
- conf.AUTH.BACKEND.set_for_testing(["desktop.auth.backend.AllowFirstUserDjangoBackend"])
- )
+ self.reset.append(conf.AUTH.BACKEND.set_for_testing(["desktop.auth.backend.AllowFirstUserDjangoBackend"]))
c = Client()
@@ -883,7 +838,9 @@ def test_login_does_not_reset_groups(self):
user = User.objects.get(username=self.test_username)
group, created = Group.objects.get_or_create(name=self.test_username)
- response = self.client.post('/hue/accounts/login/', dict(username=self.test_username, password="test", login_as=self.test_login_as_username), follow=True)
+ response = self.client.post(
+ '/hue/accounts/login/', dict(username=self.test_username, password="test", login_as=self.test_login_as_username), follow=True
+ )
assert 200 == response.status_code
assert self.test_login_as_username == response.context[0]['user'].username
diff --git a/desktop/core/src/desktop/conf.py b/desktop/core/src/desktop/conf.py
index f788adfbdeb..1d06ccce4d3 100644
--- a/desktop/core/src/desktop/conf.py
+++ b/desktop/core/src/desktop/conf.py
@@ -26,6 +26,7 @@
from collections import OrderedDict
from django.db import connection
+from django.utils.translation import gettext_lazy as _
from desktop import appmanager
from desktop.lib.conf import (
@@ -46,15 +47,6 @@
from desktop.redaction.engine import parse_redaction_policy_from_file
from metadata.metadata_sites import get_navigator_audit_log_dir, get_navigator_audit_max_file_size
-if sys.version_info[0] > 2:
- from builtins import str as new_str
-
- from django.utils.translation import gettext_lazy as _
-else:
- new_str = unicode
- from django.utils.translation import ugettext_lazy as _
-
-
LOG = logging.getLogger()
@@ -699,6 +691,8 @@ def default_ssl_validate():
#
# Email (SMTP) settings
#
+
+
_default_from_email = None
@@ -2189,7 +2183,7 @@ def task_server_default_result_directory():
def has_channels():
- return sys.version_info[0] > 2 and WEBSOCKETS.ENABLED.get()
+ return WEBSOCKETS.ENABLED.get()
WEBSOCKETS = ConfigSection(
@@ -2540,28 +2534,28 @@ def validate_ldap(user, config):
if bool(bind_dn) != bool(bind_password):
if bind_dn is None:
res.append((LDAP.BIND_DN,
- new_str(_("If you set bind_password, then you must set bind_dn."))))
+ str(_("If you set bind_password, then you must set bind_dn."))))
else:
res.append((LDAP.BIND_PASSWORD,
- new_str(_("If you set bind_dn, then you must set bind_password."))))
+ str(_("If you set bind_dn, then you must set bind_password."))))
else:
if config.NT_DOMAIN.get() is not None or \
config.LDAP_USERNAME_PATTERN.get() is not None:
if config.LDAP_URL.get() is None:
res.append((config.LDAP_URL,
- new_str(_("LDAP is only partially configured. An LDAP URL must be provided."))))
+ str(_("LDAP is only partially configured. An LDAP URL must be provided."))))
if config.LDAP_URL.get() is not None:
if config.NT_DOMAIN.get() is None and \
config.LDAP_USERNAME_PATTERN.get() is None:
res.append((config.LDAP_URL,
- new_str(_("LDAP is only partially configured. An NT Domain or username "
+ str(_("LDAP is only partially configured. An NT Domain or username "
"search pattern must be provided."))))
if config.LDAP_USERNAME_PATTERN.get() is not None and \
'
' not in config.LDAP_USERNAME_PATTERN.get():
res.append((config.LDAP_USERNAME_PATTERN,
- new_str(_("The LDAP username pattern should contain the special"
+ str(_("The LDAP username pattern should contain the special"
" replacement string for authentication."))))
return res
@@ -2585,16 +2579,16 @@ def validate_database(user):
# Promote InnoDB storage engine
if innodb_table_count != total_table_count:
- res.append(('PREFERRED_STORAGE_ENGINE', new_str(_('''We recommend MySQL InnoDB engine over
+ res.append(('PREFERRED_STORAGE_ENGINE', str(_('''We recommend MySQL InnoDB engine over
MyISAM which does not support transactions.'''))))
if innodb_table_count != 0 and innodb_table_count != total_table_count:
- res.append(('MYSQL_STORAGE_ENGINE', new_str(_('''All tables in the database must be of the same
+ res.append(('MYSQL_STORAGE_ENGINE', str(_('''All tables in the database must be of the same
storage engine type (preferably InnoDB).'''))))
except Exception as ex:
LOG.exception("Error in config validation of MYSQL_STORAGE_ENGINE: %s", ex)
elif 'sqlite' in connection.vendor:
- res.append(('SQLITE_NOT_FOR_PRODUCTION_USE', new_str(_('SQLite is only recommended for development environments. '
+ res.append(('SQLITE_NOT_FOR_PRODUCTION_USE', str(_('SQLite is only recommended for development environments. '
'It might cause the "Database is locked" error. Migrating to MySQL, Oracle or PostgreSQL is strongly recommended.'))))
# Check if django_migrations table is up to date
@@ -2615,7 +2609,7 @@ def validate_database(user):
missing_migration_entries.append((app.name, migration_name))
if missing_migration_entries:
- res.append(('django_migrations', new_str(_(
+ res.append(('django_migrations', str(_(
'''django_migrations table seems to be corrupted or incomplete.
%s entries are missing in the table: %s''') % (len(missing_migration_entries), missing_migration_entries)))
)
@@ -2641,48 +2635,48 @@ def config_validator(user):
doc_count = Document.objects.count()
if doc_count > DOCUMENT2_MAX_ENTRIES:
- res.append(('DOCUMENT_CLEANUP_WARNING', new_str(_('Desktop Document has more than %d entries: %d, '
+ res.append(('DOCUMENT_CLEANUP_WARNING', str(_('Desktop Document has more than %d entries: %d, '
'please run "hue desktop_document_cleanup --cm-managed" to remove old entries' % (DOCUMENT2_MAX_ENTRIES, doc_count)))))
doc2_count = Document2.objects.count()
if doc2_count > DOCUMENT2_MAX_ENTRIES:
- res.append(('DOCUMENT2_CLEANUP_WARNING', new_str(_('Desktop Document2 has more than %d entries: %d, '
+ res.append(('DOCUMENT2_CLEANUP_WARNING', str(_('Desktop Document2 has more than %d entries: %d, '
'please run "hue desktop_document_cleanup --cm-managed" to remove old entries' % (DOCUMENT2_MAX_ENTRIES, doc2_count)))))
session_count = Session.objects.count()
if session_count > DOCUMENT2_MAX_ENTRIES:
- res.append(('SESSION_CLEANUP_WARNING', new_str(_('Desktop Session has more than %d entries: %d, '
+ res.append(('SESSION_CLEANUP_WARNING', str(_('Desktop Session has more than %d entries: %d, '
'please run "hue desktop_document_cleanup --cm-managed" to remove old entries' % (DOCUMENT2_MAX_ENTRIES, session_count)))))
qh_count = QueryHistory.objects.count()
if qh_count > DOCUMENT2_MAX_ENTRIES:
- res.append(('QueryHistory_CLEANUP_WARNING', new_str(_('Query History has more than %d entries: %d, '
+ res.append(('QueryHistory_CLEANUP_WARNING', str(_('Query History has more than %d entries: %d, '
'please run "hue desktop_document_cleanup --cm-managed" to remove old entries' % (DOCUMENT2_MAX_ENTRIES, qh_count)))))
sq_count = SavedQuery.objects.count()
if sq_count > DOCUMENT2_MAX_ENTRIES:
- res.append(('SavedQuery_CLEANUP_WARNING', new_str(_('Saved Query has more than %d entries: %d, '
+ res.append(('SavedQuery_CLEANUP_WARNING', str(_('Saved Query has more than %d entries: %d, '
'please run "hue desktop_document_cleanup --cm-managed" to remove old entries' % (DOCUMENT2_MAX_ENTRIES, sq_count)))))
job_count = Job.objects.count()
if job_count > DOCUMENT2_MAX_ENTRIES:
- res.append(('OOZIEJOB_CLEANUP_WARNING', new_str(_('Oozie Job has more than %d entries: %d, '
+ res.append(('OOZIEJOB_CLEANUP_WARNING', str(_('Oozie Job has more than %d entries: %d, '
'please run "hue desktop_document_cleanup --cm-managed" to remove old entries' % (DOCUMENT2_MAX_ENTRIES, job_count)))))
if not get_secret_key():
- res.append((SECRET_KEY, new_str(_("Secret key should be configured as a random string. All sessions will be lost on restart"))))
+ res.append((SECRET_KEY, str(_("Secret key should be configured as a random string. All sessions will be lost on restart"))))
# Validate SSL setup
if SSL_CERTIFICATE.get():
res.extend(validate_path(SSL_CERTIFICATE, is_dir=False))
if not SSL_PRIVATE_KEY.get():
- res.append((SSL_PRIVATE_KEY, new_str(_("SSL private key file should be set to enable HTTPS."))))
+ res.append((SSL_PRIVATE_KEY, str(_("SSL private key file should be set to enable HTTPS."))))
else:
res.extend(validate_path(SSL_PRIVATE_KEY, is_dir=False))
# Validate encoding
if not i18n.validate_encoding(DEFAULT_SITE_ENCODING.get()):
- res.append((DEFAULT_SITE_ENCODING, new_str(_("Encoding not supported."))))
+ res.append((DEFAULT_SITE_ENCODING, str(_("Encoding not supported."))))
# Validate kerberos
if KERBEROS.HUE_KEYTAB.get() is not None:
@@ -2711,7 +2705,7 @@ def config_validator(user):
from oozie.views.editor2 import _is_oozie_mail_enabled
if not _is_oozie_mail_enabled(user):
- res.append(('OOZIE_EMAIL_SERVER', new_str(_('Email notifications is disabled for Workflows and Jobs as SMTP server is localhost.'))))
+ res.append(('OOZIE_EMAIL_SERVER', str(_('Email notifications is disabled for Workflows and Jobs as SMTP server is localhost.'))))
except Exception as e:
LOG.warning('Config check failed because Oozie app not installed %s' % e)
@@ -2722,8 +2716,8 @@ def config_validator(user):
notebook_doc = None
try:
notebook_doc, save_as = _save_notebook(notebook.get_data(), user)
- except Exception as e:
- res.append(('DATABASE_CHARACTER_SET', new_str(
+ except Exception:
+ res.append(('DATABASE_CHARACTER_SET', str(
_('Character set of search field in desktop_document2 table is not UTF-8.
'
'NOTE: Configure the database for character set AL32UTF8 and national character set UTF8.'))
)
@@ -2732,7 +2726,7 @@ def config_validator(user):
notebook_doc.delete()
if 'use_new_editor' in USE_NEW_EDITOR.bind_to:
- res.append(('[desktop] use_new_editor', new_str(_('This configuration flag has been deprecated.'))))
+ res.append(('[desktop] use_new_editor', str(_('This configuration flag has been deprecated.'))))
return res
diff --git a/desktop/core/src/desktop/configuration/api.py b/desktop/core/src/desktop/configuration/api.py
index 1bdcc7c5419..4aadfca2b90 100644
--- a/desktop/core/src/desktop/configuration/api.py
+++ b/desktop/core/src/desktop/configuration/api.py
@@ -15,28 +15,23 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import sys
import json
import logging
-import sys
from django.contrib.auth.models import Group, User
from django.db import transaction
from django.db.models import Q
+from django.utils.translation import gettext as _
from django.views.decorators.http import require_POST
from desktop.lib.django_util import JsonResponse
from desktop.lib.exceptions_renderable import PopupException
from desktop.lib.i18n import force_unicode
from desktop.models import DefaultConfiguration
-
from notebook.connectors.hiveserver2 import HiveConfiguration, ImpalaConfiguration
from notebook.connectors.spark_shell import SparkConfiguration
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext as _
-else:
- from django.utils.translation import ugettext as _
-
try:
from oozie.models2 import WorkflowConfiguration as OozieWorkflowConfiguration
except (ImportError, RuntimeError) as e:
diff --git a/desktop/core/src/desktop/converters.py b/desktop/core/src/desktop/converters.py
index 97fac45a5f7..c61f568e12c 100644
--- a/desktop/core/src/desktop/converters.py
+++ b/desktop/core/src/desktop/converters.py
@@ -15,25 +15,25 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from builtins import object
-import json
-import logging
import sys
+import json
import time
+import logging
+from builtins import object
from django.db import transaction
+from django.utils.translation import gettext as _
from desktop.lib.exceptions_renderable import PopupException
-from desktop.models import Document, DocumentPermission, DocumentTag, Document2, Directory, Document2Permission
+from desktop.models import Directory, Document, Document2, Document2Permission, DocumentPermission, DocumentTag
from notebook.api import _historify
-from notebook.models import import_saved_beeswax_query, import_saved_java_job, import_saved_mapreduce_job, \
- import_saved_pig_script, import_saved_shell_job
-
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext as _
-else:
- from django.utils.translation import ugettext as _
-
+from notebook.models import (
+ import_saved_beeswax_query,
+ import_saved_java_job,
+ import_saved_mapreduce_job,
+ import_saved_pig_script,
+ import_saved_shell_job,
+)
LOG = logging.getLogger()
@@ -51,7 +51,6 @@ def __init__(self, user):
self.imported_doc_count = 0
self.failed_doc_ids = []
-
def convert(self):
self._convert_saved_queries()
@@ -83,11 +82,10 @@ def convert(self):
except Exception as e:
LOG.exception("Failed to set is_trashed field with exception: %s" % e)
-
def _convert_saved_queries(self):
# Convert SavedQuery documents
try:
- from beeswax.models import SavedQuery, HQL, IMPALA, RDBMS
+ from beeswax.models import HQL, IMPALA, RDBMS, SavedQuery
docs = self._get_unconverted_docs(SavedQuery).filter(extra__in=[HQL, IMPALA, RDBMS])
for doc in docs:
@@ -111,11 +109,10 @@ def _convert_saved_queries(self):
except ImportError:
LOG.warning('Cannot convert Saved Query documents: beeswax app is not installed')
-
def _convert_query_histories(self):
# Convert SQL Query history documents
try:
- from beeswax.models import SavedQuery, HQL, IMPALA, RDBMS
+ from beeswax.models import HQL, IMPALA, RDBMS, SavedQuery
docs = self._get_unconverted_docs(SavedQuery, only_history=True).filter(extra__in=[HQL, IMPALA, RDBMS]).order_by(
'-last_modified')
@@ -147,7 +144,6 @@ def _convert_query_histories(self):
except ImportError as e:
LOG.warning('Cannot convert history documents: beeswax app is not installed')
-
def _convert_job_designs(self):
# Convert Job Designer documents
try:
@@ -193,7 +189,6 @@ def _convert_job_designs(self):
except ImportError as e:
LOG.warning('Cannot convert Job Designer documents: oozie app is not installed')
-
def _convert_pig_scripts(self):
# Convert PigScript documents
try:
@@ -222,7 +217,6 @@ def _convert_pig_scripts(self):
except ImportError as e:
LOG.warning('Cannot convert Pig documents: pig app is not installed')
-
def _get_unconverted_docs(self, content_type, only_history=False):
docs = Document.objects.get_docs(self.user, content_type).filter(owner=self.user)
@@ -239,7 +233,6 @@ def _get_unconverted_docs(self, content_type, only_history=False):
return docs.exclude(tags__in=tags)
-
def _get_parent_directory(self, document):
"""
Returns the parent directory object that should be used for a given document. If the document is tagged with a
@@ -257,7 +250,6 @@ def _get_parent_directory(self, document):
)
return parent_dir
-
def _sync_permissions(self, document, document2):
"""
Syncs (creates) Document2Permissions based on the DocumentPermissions found for a given document.
@@ -270,7 +262,6 @@ def _sync_permissions(self, document, document2):
if perm.groups:
doc2_permission.groups.add(*perm.groups.all())
-
def _create_doc2(self, document, doctype, name=None, description=None, data=None):
try:
document2 = None
diff --git a/desktop/core/src/desktop/decorators.py b/desktop/core/src/desktop/decorators.py
index 9fcdd093b92..bf823dccd79 100644
--- a/desktop/core/src/desktop/decorators.py
+++ b/desktop/core/src/desktop/decorators.py
@@ -15,19 +15,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import logging
import sys
+import logging
+
+from django.utils.translation import gettext as _
from desktop.auth.backend import is_admin
-from desktop.lib.exceptions_renderable import PopupException
from desktop.lib.django_util import JsonResponse
+from desktop.lib.exceptions_renderable import PopupException
from desktop.lib.i18n import force_unicode
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext as _
-else:
- from django.utils.translation import ugettext as _
-
try:
from functools import wraps
except ImportError:
diff --git a/desktop/core/src/desktop/kt_renewer.py b/desktop/core/src/desktop/kt_renewer.py
index 96a7303769f..8836a70a5c8 100644
--- a/desktop/core/src/desktop/kt_renewer.py
+++ b/desktop/core/src/desktop/kt_renewer.py
@@ -15,23 +15,25 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import logging
-import subprocess
import sys
import time
-from desktop.supervisor import DjangoCommandSupervisee
+import logging
+import subprocess
+
from desktop.conf import KERBEROS as CONF
+from desktop.supervisor import DjangoCommandSupervisee
LOG = logging.getLogger()
SPEC = DjangoCommandSupervisee("kt_renewer")
NEED_KRB181_WORKAROUND = None
+
def renew_from_kt():
cmdv = [CONF.KINIT_PATH.get(),
- "-k", # host ticket
- "-t", CONF.HUE_KEYTAB.get(), # specify keytab
- "-c", CONF.CCACHE_PATH.get(), # specify credentials cache
+ "-k", # host ticket
+ "-t", CONF.HUE_KEYTAB.get(), # specify keytab
+ "-c", CONF.CCACHE_PATH.get(), # specify credentials cache
CONF.HUE_PRINCIPAL.get()]
retries = 0
max_retries = 3
@@ -48,9 +50,8 @@ def renew_from_kt():
subp_stdout = subp.stdout.readlines()
subp_stderr = subp.stderr.readlines()
- if sys.version_info[0] > 2:
- subp_stdout = [line.decode() for line in subp.stdout.readlines()]
- subp_stderr = [line.decode() for line in subp.stderr.readlines()]
+ subp_stdout = [line.decode() for line in subp.stdout.readlines()]
+ subp_stderr = [line.decode() for line in subp.stderr.readlines()]
LOG.error("Couldn't reinit from keytab! `kinit' exited with %s.\n%s\n%s" % (
subp.returncode, "\n".join(subp_stdout), "\n".join(subp_stderr)))
@@ -70,6 +71,7 @@ def renew_from_kt():
time.sleep(1.5)
perform_krb181_workaround()
+
def perform_krb181_workaround():
cmdv = [CONF.KINIT_PATH.get(),
"-R",
@@ -90,6 +92,7 @@ def perform_krb181_workaround():
"for the '%(princ)s' and `krbtgt' principals." % fmt_dict)
sys.exit(ret)
+
def detect_conf_var():
"""Return true if the ticket cache contains "conf" information as is found
in ticket caches of Kerboers 1.8.1 or later. This is incompatible with the
@@ -99,17 +102,13 @@ def detect_conf_var():
try:
# TODO: the binary check for X-CACHECONF seems fragile, it should be replaced
# with something more robust.
- if sys.version_info[0] > 2:
- f = open(CONF.CCACHE_PATH.get(), "rb")
- data = f.read()
- return b"X-CACHECONF:" in data
- else:
- f = file(CONF.CCACHE_PATH.get(), "rb")
- data = f.read()
- return "X-CACHECONF:" in data
+ f = open(CONF.CCACHE_PATH.get(), "rb")
+ data = f.read()
+ return b"X-CACHECONF:" in data
finally:
f.close()
+
def run():
if CONF.HUE_KEYTAB.get() is None:
LOG.info("Keytab renewer not starting, no keytab configured")
diff --git a/desktop/core/src/desktop/lib/analytics/models.py b/desktop/core/src/desktop/lib/analytics/models.py
index c726b7b8fd0..b7ba2d6aa6b 100644
--- a/desktop/core/src/desktop/lib/analytics/models.py
+++ b/desktop/core/src/desktop/lib/analytics/models.py
@@ -15,17 +15,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import logging
import sys
+import logging
from django.db import connection, models, transaction
from django.db.models import Q
from django.db.models.query import QuerySet
-
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext as _, gettext_lazy as _t
-else:
- from django.utils.translation import ugettext as _, ugettext_lazy as _t
+from django.utils.translation import gettext as _, gettext_lazy as _t
# TODO: ORM queries
diff --git a/desktop/core/src/desktop/lib/analytics/urls.py b/desktop/core/src/desktop/lib/analytics/urls.py
index 610495f7a30..8cedd426e41 100644
--- a/desktop/core/src/desktop/lib/analytics/urls.py
+++ b/desktop/core/src/desktop/lib/analytics/urls.py
@@ -17,13 +17,9 @@
import sys
-from desktop.lib.analytics import views, api
-
-if sys.version_info[0] > 2:
- from django.urls import re_path
-else:
- from django.conf.urls import url as re_path
+from django.urls import re_path
+from desktop.lib.analytics import api, views
urlpatterns = [
re_path(r'^$', views.index, name='desktop.lib.analytics.views.index'),
diff --git a/desktop/core/src/desktop/lib/botserver/api.py b/desktop/core/src/desktop/lib/botserver/api.py
index 5f1c44d37c9..ecdefae0ee3 100644
--- a/desktop/core/src/desktop/lib/botserver/api.py
+++ b/desktop/core/src/desktop/lib/botserver/api.py
@@ -15,26 +15,24 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import logging
-import json
-import yaml
-import sys
import os
+import sys
+import json
+import logging
from urllib.parse import quote_plus
-from desktop.lib.botserver.slack_client import slack_client
-from desktop.lib.exceptions_renderable import PopupException
+import yaml
+from django.utils.translation import gettext as _
+
from desktop.decorators import api_error_handler
+from desktop.lib.botserver.slack_client import slack_client
from desktop.lib.django_util import JsonResponse, login_notrequired
+from desktop.lib.exceptions_renderable import PopupException
from desktop.settings import BASE_DIR
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext as _
-else:
- from django.utils.translation import ugettext as _
-
LOG = logging.getLogger()
+
@api_error_handler
def get_channels(request):
@@ -49,6 +47,7 @@ def get_channels(request):
'channels': bot_channels,
})
+
@api_error_handler
def send_message(request):
channel = request.POST.get('channel')
@@ -61,6 +60,7 @@ def send_message(request):
'ok': slack_response.get('ok'),
})
+
@login_notrequired
@api_error_handler
def generate_slack_install_link(request):
@@ -80,8 +80,9 @@ def generate_slack_install_link(request):
return JsonResponse({'link': install_link})
+
def _send_message(channel_info, message=None, block_element=None, message_ts=None):
try:
return slack_client.chat_postMessage(channel=channel_info, text=message, blocks=block_element, thread_ts=message_ts)
except Exception as e:
- raise PopupException(_("Error posting message in channel"), detail=e)
\ No newline at end of file
+ raise PopupException(_("Error posting message in channel"), detail=e)
diff --git a/desktop/core/src/desktop/lib/botserver/api_tests.py b/desktop/core/src/desktop/lib/botserver/api_tests.py
index 422cc1f740a..546f062ef94 100644
--- a/desktop/core/src/desktop/lib/botserver/api_tests.py
+++ b/desktop/core/src/desktop/lib/botserver/api_tests.py
@@ -15,22 +15,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import json
-import pytest
import sys
+import json
import unittest
+from unittest.mock import Mock, patch
+import pytest
from django.urls import reverse
from desktop import conf
from desktop.lib.django_test_util import make_logged_in_client
-
from useradmin.models import User
-if sys.version_info[0] > 2:
- from unittest.mock import patch, Mock
-else:
- from mock import patch, Mock
@pytest.mark.django_db
class TestApi(object):
@@ -83,7 +79,7 @@ def test_send_message(self):
assert 200 == response.status_code
chat_postMessage.assert_called_with(channel='channel-1', text='@api_user: message with link', blocks=None, thread_ts=None)
assert data.get('ok')
-
+
def test_generate_slack_install_link(self):
response = self.client.get(reverse('api:botserver.api.slack_install_link') + '/?hostname=' + self.hostname)
data = json.loads(response.content)
diff --git a/desktop/core/src/desktop/lib/botserver/urls.py b/desktop/core/src/desktop/lib/botserver/urls.py
index fa68cea58ca..56e2ebbc300 100644
--- a/desktop/core/src/desktop/lib/botserver/urls.py
+++ b/desktop/core/src/desktop/lib/botserver/urls.py
@@ -17,16 +17,13 @@
import sys
-from desktop.lib.botserver import views, api
+from django.urls import re_path
-if sys.version_info[0] > 2:
- from django.urls import re_path
-else:
- from django.conf.urls import url as re_path
+from desktop.lib.botserver import api, views
urlpatterns = [
re_path(r'^events/', views.slack_events, name='desktop.lib.botserver.views.slack_events'),
re_path(r'^api/channels/get/?$', api.get_channels, name='botserver.api.get_channels'),
re_path(r'^api/message/send/?$', api.send_message, name='botserver.api.send_message'),
-]
\ No newline at end of file
+]
diff --git a/desktop/core/src/desktop/lib/botserver/views.py b/desktop/core/src/desktop/lib/botserver/views.py
index c988cbd30de..50911962054 100644
--- a/desktop/core/src/desktop/lib/botserver/views.py
+++ b/desktop/core/src/desktop/lib/botserver/views.py
@@ -15,39 +15,33 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import logging
-import json
-import sys
import os
+import sys
+import json
+import logging
from urllib.parse import urlsplit
+
+from django.http import HttpResponse
+from django.utils.translation import gettext as _
+from django.views.decorators.csrf import csrf_exempt
from tabulate import tabulate
-from desktop.lib.botserver.slack_client import slack_client, SLACK_VERIFICATION_TOKEN
+from desktop.api2 import _gist_create
+from desktop.auth.backend import rewrite_user
from desktop.lib.botserver.api import _send_message
-from desktop.lib.django_util import login_notrequired, JsonResponse
+from desktop.lib.botserver.slack_client import SLACK_VERIFICATION_TOKEN, slack_client
+from desktop.lib.django_util import JsonResponse, login_notrequired
from desktop.lib.exceptions_renderable import PopupException
from desktop.models import Document2, _get_gist_document, get_cluster_config
-from desktop.api2 import _gist_create
-from desktop.auth.backend import rewrite_user
-
-from notebook.api import _fetch_result_data, _check_status, _execute_notebook
-from notebook.models import MockRequest, get_api
-from notebook.connectors.base import _get_snippet_name
-
from metadata.assistant.queries_utils import get_all_queries
-
+from notebook.api import _check_status, _execute_notebook, _fetch_result_data
+from notebook.connectors.base import _get_snippet_name
+from notebook.models import MockRequest, get_api
from useradmin.models import User
-from django.http import HttpResponse
-from django.views.decorators.csrf import csrf_exempt
-
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext as _
-else:
- from django.utils.translation import ugettext as _
-
LOG = logging.getLogger()
+
class SlackBotException(PopupException):
def __init__(self, msg, detail=None, error_code=200):
PopupException.__init__(self, message=msg, detail=detail, error_code=error_code)
@@ -58,7 +52,7 @@ def __init__(self, msg, detail=None, error_code=200):
def slack_events(request):
try:
slack_message = json.loads(request.body)
-
+
if slack_message['token'] != SLACK_VERIFICATION_TOKEN:
return HttpResponse(status=403)
@@ -66,7 +60,7 @@ def slack_events(request):
if slack_message['type'] == 'url_verification':
response_dict = {"challenge": slack_message['challenge']}
return JsonResponse(response_dict, status=200)
-
+
if 'event' in slack_message:
parse_events(request, slack_message['event'])
except ValueError as err:
@@ -124,7 +118,7 @@ def help_message(user_id):
"type": "section",
"text": {
"type": "mrkdwn",
- "text": "*Share query/gist links* in channel which unfurls in a rich preview, showing query details and result in message thread if available."
+ "text": "*Share query/gist links* in channel which unfurls in a rich preview, showing query details and result in message thread if available." # noqa: E501
}
},
{
@@ -187,7 +181,7 @@ def handle_on_message(host_domain, is_http_secure, channel_id, bot_id, elements,
# Ignore bot's own message since that will cause an infinite loop of messages if we respond.
if bot_id is not None:
return HttpResponse(status=200)
-
+
for element_block in elements:
text = element_block['elements'][0].get('text', '') if element_block.get('elements') else ''
@@ -200,7 +194,11 @@ def handle_on_message(host_domain, is_http_secure, channel_id, bot_id, elements,
def handle_select_statement(host_domain, is_http_secure, channel_id, user_id, statement, message_ts):
- msg = 'Hi <@{user}> \n Looks like you are copy/pasting SQL, instead now you can send Editor links which unfurls in a rich preview!'.format(user=user_id)
+ msg = (
+ 'Hi <@{user}> \n Looks like you are copy/pasting SQL, instead now you can send Editor links which unfurls in a rich preview!'.format(
+ user=user_id
+ )
+ )
_send_message(channel_id, message=msg)
# Check Slack user perms to send gist link
@@ -254,7 +252,7 @@ def handle_on_link_shared(host_domain, channel_id, message_ts, links, user_id):
slack_client.chat_unfurl(channel=channel_id, ts=message_ts, unfurls=payload['payload'])
except Exception as e:
raise SlackBotException(_("Cannot unfurl link"), detail=e)
-
+
# Generate and upload result xlsx file only if result available
if payload['file_status']:
send_result_file(request, channel_id, message_ts, doc, 'xls')
@@ -299,7 +297,7 @@ def send_result_file(request, channel_id, message_ts, doc, file_format):
try:
slack_client.files_upload(
channels=channel_id,
- file=next(content_generator),
+ file=next(content_generator),
thread_ts=message_ts,
filetype=file_format,
filename='{name}.{format}'.format(name=file_name, format=file_format),
@@ -336,12 +334,12 @@ def _make_result_table(result):
for row_data in data:
# Replace non-breaking space HTML entity with whitespace
if isinstance(row_data[idx], str):
- row_data[idx] = row_data[idx].replace(' ', ' ')
+ row_data[idx] = row_data[idx].replace(' ', ' ')
pivot_row.append(row_data[idx])
table.append(pivot_row)
- return tabulate(table, headers=['Columns({count})'.format(count=idx+1), '', ''], tablefmt="simple")
+ return tabulate(table, headers=['Columns({count})'.format(count=idx + 1), '', ''], tablefmt="simple")
def _make_unfurl_payload(request, url, id_type, doc, doc_type):
@@ -363,7 +361,7 @@ def _make_unfurl_payload(request, url, id_type, doc, doc_type):
if fetch_result is not None:
unfurl_result = _make_result_table(fetch_result)
file_status = True
- except:
+ except Exception:
pass
result_section = {
@@ -411,4 +409,4 @@ def _make_unfurl_payload(request, url, id_type, doc, doc_type):
if result_section is not None:
payload[url]['blocks'].append(result_section)
- return {'payload': payload, 'file_status': file_status}
\ No newline at end of file
+ return {'payload': payload, 'file_status': file_status}
diff --git a/desktop/core/src/desktop/lib/botserver/views_tests.py b/desktop/core/src/desktop/lib/botserver/views_tests.py
index 47c644bc993..05eb41201b5 100644
--- a/desktop/core/src/desktop/lib/botserver/views_tests.py
+++ b/desktop/core/src/desktop/lib/botserver/views_tests.py
@@ -15,31 +15,25 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import sys
import json
import logging
import unittest
-import pytest
-import sys
+from unittest.mock import Mock, patch
+import pytest
from django.test import TestCase
-from desktop.lib.botserver.views import *
from desktop import conf
-from desktop.models import Document2, _get_gist_document
+from desktop.lib.botserver.views import *
from desktop.lib.django_test_util import make_logged_in_client
-
+from desktop.models import Document2, _get_gist_document
from useradmin.models import User
-
-if sys.version_info[0] > 2:
- from unittest.mock import patch, Mock
-else:
- from mock import patch, Mock
-
LOG = logging.getLogger()
+
class TestBotServer(TestCase):
-
@classmethod
def setup_class(cls):
if not conf.SLACK.IS_ENABLED.get():
@@ -55,7 +49,7 @@ def setup_class(cls):
def setup_method(self):
self.host_domain = 'testserver.gethue.com'
- self.is_http_secure = True # https if true else http
+ self.is_http_secure = True # https if true else http
self.email_domain = '.'.join(self.host_domain.split('.')[-2:])
@@ -66,16 +60,13 @@ def setup_method(self):
def test_handle_on_message(self):
with patch('desktop.lib.botserver.views._send_message') as _send_message:
with patch('desktop.lib.botserver.views.handle_select_statement') as handle_select_statement:
-
bot_id = "bot_id"
- message_element = [{
- 'elements': [{
- 'text': 'hello hue test'
- }]
- }]
+ message_element = [{'elements': [{'text': 'hello hue test'}]}]
# Bot sending message
- response = handle_on_message(self.host_domain, self.is_http_secure, self.channel_id, bot_id, message_element, self.user_id, self.message_ts)
+ response = handle_on_message(
+ self.host_domain, self.is_http_secure, self.channel_id, bot_id, message_element, self.user_id, self.message_ts
+ )
assert response.status_code == 200
assert not _send_message.called
@@ -84,42 +75,39 @@ def test_handle_on_message(self):
"type": "section",
"text": {
"type": "mrkdwn",
- "text": ("Hey <@user_id>, I'm your SQL Assistant! "
- "I'm here to assist users with their SQL queries and **\n"
- "Here are the few things I can help you with:")
- }
- },
- {
- "type": "divider"
+ "text": (
+ "Hey <@user_id>, I'm your SQL Assistant! "
+ "I'm here to assist users with their SQL queries and **\n"
+ "Here are the few things I can help you with:"
+ ),
+ },
},
+ {"type": "divider"},
{
"type": "section",
"text": {
"type": "mrkdwn",
- "text": "*Share query/gist links* in channel which unfurls in a rich preview, showing query details and result in message thread if available."
- }
+ "text": "*Share query/gist links* in channel which unfurls in a rich preview, showing query details and result in message thread if available.", # noqa: E501
+ },
},
{
"type": "section",
"text": {
"type": "mrkdwn",
- "text": "Create a gist for your query, select the channel and *share directly from the Hue Editor* window."
- }
+ "text": "Create a gist for your query, select the channel and *share directly from the Hue Editor* window.",
+ },
},
{
"type": "section",
"text": {
"type": "mrkdwn",
- "text": "*Detect SQL SELECT statements* in the channel and suggests a gist link for improved query discussions."
- }
+ "text": "*Detect SQL SELECT statements* in the channel and suggests a gist link for improved query discussions.",
+ },
},
{
"type": "section",
- "text": {
- "type": "mrkdwn",
- "text": "Type `@Hue queries` to explore a list of important queries from the latest *query bank*."
- }
- }
+ "text": {"type": "mrkdwn", "text": "Type `@Hue queries` to explore a list of important queries from the latest *query bank*."},
+ },
]
# Help message
@@ -129,28 +117,35 @@ def test_handle_on_message(self):
# Detect SQL
message_element = [
{
- 'elements': [{
- 'text': 'Hi Team, need help with query',
- }],
+ 'elements': [
+ {
+ 'text': 'Hi Team, need help with query',
+ }
+ ],
},
{
- 'elements': [{
- 'text': 'SELECT 1',
- }],
+ 'elements': [
+ {
+ 'text': 'SELECT 1',
+ }
+ ],
},
]
handle_on_message(self.host_domain, self.is_http_secure, self.channel_id, None, message_element, self.user_id, self.message_ts)
- handle_select_statement.assert_called_with(self.host_domain, self.is_http_secure, self.channel_id, self.user_id, 'select 1', self.message_ts)
+ handle_select_statement.assert_called_with(
+ self.host_domain, self.is_http_secure, self.channel_id, self.user_id, 'select 1', self.message_ts
+ )
def test_handle_select_statement(self):
with patch('desktop.lib.botserver.views.check_slack_user_permission') as check_slack_user_permission:
with patch('desktop.lib.botserver.views._make_select_statement_gist') as _make_select_statement_gist:
with patch('desktop.lib.botserver.views._send_message') as _send_message:
with patch('desktop.lib.botserver.views.get_user') as get_user:
-
statement = 'select 1'
- detect_msg = 'Hi <@user_id> \n Looks like you are copy/pasting SQL, instead now you can send Editor links which unfurls in a rich preview!'
+ detect_msg = (
+ 'Hi <@user_id> \n Looks like you are copy/pasting SQL, instead now you can send Editor links which unfurls in a rich preview!'
+ )
# For Slack user not Hue user
get_user.side_effect = SlackBotException('Slack user does not have access to the query')
@@ -176,13 +171,14 @@ def test_handle_query_history_link(self):
with patch('desktop.lib.botserver.views._query_result') as query_result:
with patch('desktop.lib.botserver.views._make_result_table') as result_table:
with patch('desktop.lib.botserver.views._send_message') as _send_message:
-
doc_data = {
"dialect": "mysql",
- "snippets": [{
- "statement_raw": "SELECT 5000",
- }],
- 'uuid': 'doc uuid'
+ "snippets": [
+ {
+ "statement_raw": "SELECT 5000",
+ }
+ ],
+ 'uuid': 'doc uuid',
}
doc = Document2.objects.create(data=json.dumps(doc_data), owner=self.user)
links = [{"url": "https://{host_domain}/hue/editor?editor=".format(host_domain=self.host_domain) + str(doc.id)}]
@@ -190,12 +186,7 @@ def test_handle_query_history_link(self):
# Slack user is Hue user but without read access sends link
users_info.return_value = {
"ok": True,
- "user": {
- "is_bot": False,
- "profile": {
- "email": "test_not_me@{domain}".format(domain=self.email_domain)
- }
- }
+ "user": {"is_bot": False, "profile": {"email": "test_not_me@{domain}".format(domain=self.email_domain)}},
}
with pytest.raises(PopupException):
handle_on_link_shared(self.host_domain, "channel", "12.1", links, "<@user_id>")
@@ -214,33 +205,33 @@ def test_handle_query_history_link(self):
query_preview = {
links[0]['url']: {
- "color": "#025BA6",
- "blocks": [
- {
- "type": "section",
- "text": {
- "type": "mrkdwn",
- "text": "\n*<{url}|Open query of mysql dialect created by test in Hue>*".format(url=links[0]['url']),
- }
- },
- {
- "type": "divider",
- },
- {
- "type": "section",
- "text": {
- "type": "mrkdwn",
- "text": "*Statement:*\n```SELECT 5000```",
- }
+ "color": "#025BA6",
+ "blocks": [
+ {
+ "type": "section",
+ "text": {
+ "type": "mrkdwn",
+ "text": "\n*<{url}|Open query of mysql dialect created by test in Hue>*".format(url=links[0]['url']),
+ },
+ },
+ {
+ "type": "divider",
+ },
+ {
+ "type": "section",
+ "text": {
+ "type": "mrkdwn",
+ "text": "*Statement:*\n```SELECT 5000```",
+ },
},
{
'type': 'section',
'text': {
'type': 'mrkdwn',
'text': "*Query result:*\n``` Columns(1)\n------------ ----\n 5000 5000```",
- }
- }
- ]
+ },
+ },
+ ],
}
}
@@ -257,58 +248,48 @@ def test_handle_query_history_link(self):
inv_qhistory_url = "https://{host_domain}/hue/editor/?type=4".format(host_domain=self.host_domain)
with pytest.raises(SlackBotException):
handle_on_link_shared(self.host_domain, "channel", "12.1", [{"url": inv_qhistory_url}], "<@user_id>")
- _send_message.assert_called_with('channel', message='Could not access the query, please check the link again.', message_ts='12.1')
+ _send_message.assert_called_with(
+ 'channel', message='Could not access the query, please check the link again.', message_ts='12.1'
+ )
def test_handle_gist_link(self):
with patch('desktop.lib.botserver.views.slack_client.chat_unfurl') as chat_unfurl:
with patch('desktop.lib.botserver.views.slack_client.users_info') as users_info:
with patch('desktop.lib.botserver.views.send_result_file') as send_result_file:
with patch('desktop.lib.botserver.views._send_message') as _send_message:
-
doc_data = {"statement_raw": "SELECT 98765"}
- gist_doc = Document2.objects.create(
- name='Mysql Query',
- type='gist',
- owner=self.user,
- data=json.dumps(doc_data),
- extra='mysql'
- )
+ gist_doc = Document2.objects.create(name='Mysql Query', type='gist', owner=self.user, data=json.dumps(doc_data), extra='mysql')
links = [{"url": "https://{host_domain}/hue/gist?uuid=".format(host_domain=self.host_domain) + gist_doc.uuid}]
# Slack user who is Hue user sends link
users_info.return_value = {
"ok": True,
- "user": {
- "is_bot": False,
- "profile": {
- "email": "test@{domain}".format(domain=self.email_domain)
- }
- }
+ "user": {"is_bot": False, "profile": {"email": "test@{domain}".format(domain=self.email_domain)}},
}
handle_on_link_shared(self.host_domain, self.channel_id, self.message_ts, links, self.user_id)
gist_preview = {
links[0]['url']: {
- "color": "#025BA6",
- "blocks": [
- {
- "type": "section",
- "text": {
- "type": "mrkdwn",
- "text": "\n*<{url}|Open Mysql Query gist of mysql dialect created by test in Hue>*".format(url=links[0]['url']),
- }
- },
- {
- "type": "divider",
- },
- {
- "type": "section",
- "text": {
- "type": "mrkdwn",
- "text": "*Statement:*\n```SELECT 98765```",
- }
+ "color": "#025BA6",
+ "blocks": [
+ {
+ "type": "section",
+ "text": {
+ "type": "mrkdwn",
+ "text": "\n*<{url}|Open Mysql Query gist of mysql dialect created by test in Hue>*".format(url=links[0]['url']),
+ },
+ },
+ {
+ "type": "divider",
+ },
+ {
+ "type": "section",
+ "text": {
+ "type": "mrkdwn",
+ "text": "*Statement:*\n```SELECT 98765```",
+ },
},
- ]
+ ],
}
}
@@ -320,7 +301,7 @@ def test_handle_gist_link(self):
"ok": True,
"user": {
"is_bot": True,
- }
+ },
}
handle_on_link_shared(self.host_domain, self.channel_id, self.message_ts, links, self.user_id)
@@ -337,21 +318,17 @@ def test_handle_gist_link(self):
inv_gist_url = "https://{host_domain}/hue/gist?uuids/=invalid_link".format(host_domain=self.host_domain)
with pytest.raises(SlackBotException):
handle_on_link_shared(self.host_domain, "channel", "12.1", [{"url": inv_gist_url}], "<@user_id>")
- _send_message.assert_called_with('channel', message='Could not access the query, please check the link again.', message_ts='12.1')
+ _send_message.assert_called_with(
+ 'channel', message='Could not access the query, please check the link again.', message_ts='12.1'
+ )
def test_slack_user_not_hue_user(self):
with patch('desktop.lib.botserver.views.slack_client.users_info') as users_info:
with patch('desktop.lib.botserver.views._send_message') as _send_message:
-
# Same domain but diff email prefix
users_info.return_value = {
"ok": True,
- "user": {
- "is_bot": False,
- "profile": {
- "email": "test_user_not_exist@{domain}".format(domain=self.email_domain)
- }
- }
+ "user": {"is_bot": False, "profile": {"email": "test_user_not_exist@{domain}".format(domain=self.email_domain)}},
}
slack_user = check_slack_user_permission(self.host_domain, self.user_id)
@@ -360,26 +337,17 @@ def test_slack_user_not_hue_user(self):
_send_message.assert_called_with('channel', message='Corresponding Hue user not found or does not have access.', message_ts='12.1')
# Different domain but same email prefix
- users_info.return_value = {
- "ok": True,
- "user": {
- "is_bot": False,
- "profile": {
- "email": "test@example.com"
- }
- }
- }
+ users_info.return_value = {"ok": True, "user": {"is_bot": False, "profile": {"email": "test@example.com"}}}
slack_user = check_slack_user_permission(self.host_domain, self.user_id)
with pytest.raises(SlackBotException):
get_user("channel", slack_user, "12.1")
_send_message.assert_called_with('channel', message='Corresponding Hue user not found or does not have access.', message_ts='12.1')
-
+
def test_handle_on_app_mention(self):
with patch('desktop.lib.botserver.views.check_slack_user_permission') as check_slack_user_permission:
with patch('desktop.lib.botserver.views.get_user') as get_user:
with patch('desktop.lib.botserver.views.handle_query_bank') as handle_query_bank:
-
text = '@hue some message'
handle_on_app_mention(self.host_domain, self.channel_id, self.user_id, text, self.message_ts)
@@ -393,43 +361,17 @@ def test_handle_on_app_mention(self):
def test_handle_query_bank(self):
with patch('desktop.lib.botserver.views.get_all_queries') as get_all_queries:
with patch('desktop.lib.botserver.views._send_message') as _send_message:
-
get_all_queries.return_value = [
- {
- "name": "Test Query 1",
- "data": {
- "query": {
- "statement": "SELECT 1"
- }
- }
- },
- {
- "name": "Test Query 2",
- "data": {
- "query": {
- "statement": "SELECT 2"
- }
- }
- }
+ {"name": "Test Query 1", "data": {"query": {"statement": "SELECT 1"}}},
+ {"name": "Test Query 2", "data": {"query": {"statement": "SELECT 2"}}},
]
test_query_block = [
- {
- 'type': 'section',
- 'text': {'type': 'mrkdwn', 'text': 'Hi <@user_id>, here is the list of all saved queries!'}
- },
- {
- 'type': 'divider'
- },
- {
- 'type': 'section',
- 'text': {'type': 'mrkdwn', 'text': '*Name:* Test Query 1 \n *Statement:*\n ```SELECT 1```'}
- },
- {
- 'type': 'section',
- 'text': {'type': 'mrkdwn', 'text': '*Name:* Test Query 2 \n *Statement:*\n ```SELECT 2```'}
- }
+ {'type': 'section', 'text': {'type': 'mrkdwn', 'text': 'Hi <@user_id>, here is the list of all saved queries!'}},
+ {'type': 'divider'},
+ {'type': 'section', 'text': {'type': 'mrkdwn', 'text': '*Name:* Test Query 1 \n *Statement:*\n ```SELECT 1```'}},
+ {'type': 'section', 'text': {'type': 'mrkdwn', 'text': '*Name:* Test Query 2 \n *Statement:*\n ```SELECT 2```'}},
]
-
+
handle_query_bank(self.channel_id, self.user_id)
- _send_message.assert_called_with(self.channel_id, block_element=test_query_block)
\ No newline at end of file
+ _send_message.assert_called_with(self.channel_id, block_element=test_query_block)
diff --git a/desktop/core/src/desktop/lib/conf.py b/desktop/core/src/desktop/lib/conf.py
index f79406ab5b3..923a2fe2d5d 100644
--- a/desktop/core/src/desktop/lib/conf.py
+++ b/desktop/core/src/desktop/lib/conf.py
@@ -61,38 +61,30 @@
variables.
"""
-# The Config object unfortunately has a kwarg called "type", and everybody is
-# using it. So instead of breaking compatibility, we make a "pytype" alias.
-
from __future__ import print_function
-from six import string_types
-from builtins import object
-pytype = type
+import os
+import re
+import sys
import json
import logging
import numbers
-import os
import textwrap
-import re
import subprocess
-import sys
+from builtins import object
-from django.utils.encoding import smart_str
from configobj import ConfigObj, ConfigObjError
+from django.utils.encoding import smart_str
+from six import string_types
-from desktop.lib.paths import get_desktop_root, get_build_dir
+from desktop.lib.paths import get_build_dir, get_desktop_root
try:
from collections import OrderedDict
except ImportError:
- from ordereddict import OrderedDict # Python 2.6
-
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext_lazy as _t
-else:
- from django.utils.translation import ugettext_lazy as _t
+ from ordereddict import OrderedDict # Python 2.6
+from django.utils.translation import gettext_lazy as _t
# Magical object for use as a "symbol"
_ANONYMOUS = ("_ANONYMOUS")
@@ -103,10 +95,15 @@
# a BoundContainer(BoundConfig) object which has all of the application's configs as members
GLOBAL_CONFIG = None
+# The Config object unfortunately has a kwarg called "type", and everybody is
+# using it. So instead of breaking compatibility, we make a "pytype" alias.
+pytype = type
+
LOG = logging.getLogger()
__all__ = ["UnspecifiedConfigSection", "ConfigSection", "Config", "load_confs", "coerce_bool", "coerce_csv", "coerce_json_dict"]
+
class BoundConfig(object):
def __init__(self, config, bind_to, grab_key=_ANONYMOUS, prefix=''):
"""
@@ -181,7 +178,7 @@ def set_data_presence(data, presence):
self.bind_to[self.grab_key] = data
if not presence:
del self.bind_to[self.grab_key]
- assert self.grab_key is not _ANONYMOUS # TODO(todd) really?
+ assert self.grab_key is not _ANONYMOUS # TODO(todd) really?
old_data = self.bind_to.get(self.grab_key)
old_presence = self.grab_key in self.bind_to
@@ -195,7 +192,9 @@ def print_help(self, *args, **kwargs):
self.config.print_help(*args, **kwargs)
def __repr__(self):
- return repr("%s(config=%s, bind_to=%s, grab_key=%s)" % (str(self.__class__), repr(self.config), repr(self.bind_to), repr(self.grab_key)))
+ return repr(
+ "%s(config=%s, bind_to=%s, grab_key=%s)" % (str(self.__class__), repr(self.config), repr(self.bind_to), repr(self.grab_key))
+ )
class Config(object):
@@ -228,7 +227,7 @@ def __init__(self, key=_ANONYMOUS, default=None, dynamic_default=None,
raise ValueError("%s: '%s' does not match that of the default value %r (%s)"
% (key, type, default, pytype(default)))
- if type == bool:
+ if type is bool:
LOG.warning("%s is of type bool. Resetting it as type 'coerce_bool'."
" Please fix it permanently" % (key,))
type = coerce_bool
@@ -330,7 +329,7 @@ def get_presentable_help_text(self, indent=0):
def get_presentable_key(self):
if self.key is _ANONYMOUS:
- return "" # TODO(todd) add "metavar" like optparse
+ return "" # TODO(todd) add "metavar" like optparse
else:
return self.key
@@ -340,6 +339,7 @@ def default(self):
return self.dynamic_default()
return self.default_value
+
class BoundContainer(BoundConfig):
"""Binds a ConfigSection to actual data."""
@@ -363,6 +363,7 @@ def get_data_dict(self):
def keys(self):
return list(self.get_data_dict().keys())
+
class BoundContainerWithGetAttr(BoundContainer):
"""
A configuration bound to a data container where we expect
@@ -374,6 +375,7 @@ class BoundContainerWithGetAttr(BoundContainer):
def __getattr__(self, attr):
return self.config.get_member(self.get_data_dict(), attr, self.prefix)
+
class BoundContainerWithGetItem(BoundContainer):
"""
A configuration bound to a data container where we expect
@@ -407,7 +409,6 @@ def __init__(self, key=_ANONYMOUS, members=None, **kwargs):
for member in members.values():
assert member.key is not _ANONYMOUS
-
def update_members(self, new_members, overwrite=True):
"""
Add the new_members to this ConfigSection.
@@ -424,7 +425,6 @@ def update_members(self, new_members, overwrite=True):
del new_members[k]
self.members.update(new_members)
-
def bind(self, config, prefix):
return BoundContainerWithGetAttr(self, bind_to=config, grab_key=self.key, prefix=prefix)
@@ -458,6 +458,7 @@ def print_help(self, out=sys.stdout, indent=0, skip_header=False):
for programmer_key, config in sorted(iter(self.members.items()), key=lambda x: x[1].key):
config.print_help(out=out, indent=new_indent)
+
class UnspecifiedConfigSection(Config):
"""
A special Config that maps a section name to a list of anonymous subsections.
@@ -503,7 +504,8 @@ def print_help(self, out=sys.stdout, indent=0):
print(self.get_presentable_help_text(indent=indent), file=out)
print(file=out)
print(indent_str + " Consists of some number of sections like:", file=out)
- self.each.print_help(out=out, indent=indent+2)
+ self.each.print_help(out=out, indent=indent + 2)
+
def _configs_from_dir(conf_dir):
"""
@@ -522,6 +524,7 @@ def _configs_from_dir(conf_dir):
conf['DEFAULT'] = dict(desktop_root=get_desktop_root(), build_dir=get_build_dir())
yield conf
+
def load_confs(conf_source=None):
"""Loads and merges all of the configurations passed in,
returning a ConfigObj for the result.
@@ -538,6 +541,7 @@ def load_confs(conf_source=None):
conf.merge(in_conf)
return conf
+
def _bind_module_members(module, data, section):
"""
Bind all Config instances found inside the given module
@@ -593,6 +597,7 @@ def bind_module_config(mod, conf_data, config_key):
members = _bind_module_members(mod, bind_data, section)
return ConfigSection(section, members=members, help=mod.__doc__)
+
def initialize(modules, config_dir):
"""
Set up the GLOBAL_CONFIG variable by loading all configuration
@@ -618,6 +623,7 @@ def initialize(modules, config_dir):
GLOBAL_CONFIG = new_config.bind(conf_data, prefix='')
return
+
def is_anonymous(key):
return key == _ANONYMOUS
@@ -641,12 +647,14 @@ def coerce_bool(value):
return True
raise Exception("Could not coerce %r to boolean value" % (value,))
+
def coerce_string(value):
- if type(value) == list:
+ if type(value) is list:
return ','.join(value)
else:
return value
+
def coerce_csv(value):
if isinstance(value, str):
return value.split(',')
@@ -654,6 +662,7 @@ def coerce_csv(value):
return value
raise Exception("Could not coerce %r to csv array." % value)
+
def coerce_json_dict(value):
if isinstance(value, string_types):
return json.loads(value)
@@ -661,6 +670,7 @@ def coerce_json_dict(value):
return value
raise Exception("Could not coerce %r to json dictionary." % value)
+
def list_of_compiled_res(skip_empty=False):
def fn(list_of_strings):
if isinstance(list_of_strings, string_types):
@@ -669,6 +679,7 @@ def fn(list_of_strings):
return list(re.compile(x) for x in list_of_strings)
return fn
+
def validate_path(confvar, is_dir=None, fs=os.path, message='Path does not exist on the filesystem.'):
"""
Validate that the value of confvar is an existent path.
@@ -687,7 +698,8 @@ def validate_path(confvar, is_dir=None, fs=os.path, message='Path does not exist
return [(confvar, 'Not a directory.')]
elif not fs.isfile(path):
return [(confvar, 'Not a file.')]
- return [ ]
+ return []
+
def validate_port(confvar):
"""
@@ -702,7 +714,8 @@ def validate_port(confvar):
return error_res
except ValueError:
return error_res
- return [ ]
+ return []
+
def validate_thrift_transport(confvar):
"""
@@ -710,20 +723,26 @@ def validate_thrift_transport(confvar):
Returns [(confvar, error_msg)] or []
"""
transport = confvar.get()
- error_res = [(confvar, 'Thrift transport %s not supported. Please choose a supported transport: %s' % (transport, ', '.join(SUPPORTED_THRIFT_TRANSPORTS)))]
+ error_res = [
+ (
+ confvar,
+ 'Thrift transport %s not supported. Please choose a supported transport: %s' % (transport, ', '.join(SUPPORTED_THRIFT_TRANSPORTS)),
+ )
+ ]
if transport not in SUPPORTED_THRIFT_TRANSPORTS:
return error_res
return []
+
def coerce_password_from_script(script):
p = subprocess.Popen(script, shell=True, stdout=subprocess.PIPE)
stdout, stderr = p.communicate()
- if sys.version_info[0] > 2 and isinstance(stdout, bytes):
+ if isinstance(stdout, bytes):
stdout = stdout.decode('utf-8')
- if sys.version_info[0] > 2 and isinstance(stderr, bytes):
+ if isinstance(stderr, bytes):
stderr = stderr.decode('utf-8')
if p.returncode != 0:
diff --git a/desktop/core/src/desktop/lib/conf_test.py b/desktop/core/src/desktop/lib/conf_test.py
index 81ebceca737..82ae76b025b 100644
--- a/desktop/core/src/desktop/lib/conf_test.py
+++ b/desktop/core/src/desktop/lib/conf_test.py
@@ -16,20 +16,15 @@
# limitations under the License.
import re
-import sys
import logging
from builtins import object
+from io import StringIO as string_io
import pytest
import configobj
from desktop.lib.conf import *
-if sys.version_info[0] > 2:
- from io import StringIO as string_io
-else:
- from cStringIO import StringIO as string_io
-
def my_dynamic_default():
"""
diff --git a/desktop/core/src/desktop/lib/connectors/api.py b/desktop/core/src/desktop/lib/connectors/api.py
index fe6757f8644..3c018f99218 100644
--- a/desktop/core/src/desktop/lib/connectors/api.py
+++ b/desktop/core/src/desktop/lib/connectors/api.py
@@ -15,25 +15,26 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import sys
import json
import logging
-import sys
-from useradmin.models import update_app_permissions
-from notebook.conf import config_validator, _connector_to_interpreter
+from django.utils.translation import gettext as _
from desktop.auth.decorators import admin_required
from desktop.decorators import api_error_handler
+from desktop.lib.connectors.models import (
+ Connector,
+ _augment_connector_properties,
+ _create_connector_examples,
+ _get_installed_connectors,
+ get_connectors_types,
+)
+from desktop.lib.connectors.types import get_connector_by_type, get_connector_categories, get_connectors_types
from desktop.lib.django_util import JsonResponse, render
from desktop.lib.exceptions_renderable import PopupException
-from desktop.lib.connectors.models import _get_installed_connectors, get_connectors_types, Connector, _create_connector_examples, \
- _augment_connector_properties
-from desktop.lib.connectors.types import get_connectors_types, get_connector_categories, get_connector_by_type
-
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext as _
-else:
- from django.utils.translation import ugettext as _
+from notebook.conf import _connector_to_interpreter, config_validator
+from useradmin.models import update_app_permissions
LOG = logging.getLogger()
diff --git a/desktop/core/src/desktop/lib/connectors/api_tests.py b/desktop/core/src/desktop/lib/connectors/api_tests.py
index 15fa096b89e..b21680a51b3 100644
--- a/desktop/core/src/desktop/lib/connectors/api_tests.py
+++ b/desktop/core/src/desktop/lib/connectors/api_tests.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-## -*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
# Licensed to Cloudera, Inc. under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
@@ -16,25 +16,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import json
-import pytest
import sys
+import json
import unittest
+from unittest.mock import Mock, patch
+import pytest
from django.urls import reverse
-from desktop.auth.backend import rewrite_user, is_admin
+from desktop.auth.backend import is_admin, rewrite_user
from desktop.conf import ENABLE_CONNECTORS, ENABLE_ORGANIZATIONS
from desktop.lib.connectors.api import _get_installed_connectors
from desktop.lib.django_test_util import make_logged_in_client
-
-from useradmin.models import User, update_app_permissions, get_default_user_group, Connector
-from useradmin.permissions import HuePermission, GroupPermission
-
-if sys.version_info[0] > 2:
- from unittest.mock import patch, Mock
-else:
- from mock import patch, Mock
+from useradmin.models import Connector, User, get_default_user_group, update_app_permissions
+from useradmin.permissions import GroupPermission, HuePermission
@pytest.mark.django_db
@@ -55,7 +50,6 @@ def teardown_class(cls):
for reset in cls._class_resets:
reset()
-
def test_install_connector_examples(self):
with patch('desktop.lib.connectors.api._create_connector_examples') as _create_connector_examples:
diff --git a/desktop/core/src/desktop/lib/connectors/models.py b/desktop/core/src/desktop/lib/connectors/models.py
index 8ac70f2431b..6b763754c56 100644
--- a/desktop/core/src/desktop/lib/connectors/models.py
+++ b/desktop/core/src/desktop/lib/connectors/models.py
@@ -15,26 +15,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import sys
import json
import logging
-import sys
from django.db import connection, models, transaction
from django.db.models import Q
from django.db.models.query import QuerySet
-
-from useradmin.organization import _fitered_queryset, get_user_request_organization
+from django.utils.translation import gettext as _, gettext_lazy as _t
from desktop.conf import CONNECTORS, ENABLE_ORGANIZATIONS
from desktop.lib.connectors.types import get_connectors_types
from desktop.lib.exceptions_renderable import PopupException
-from desktop.lib.i18n import smart_unicode
-
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext as _, gettext_lazy as _t
-else:
- from django.utils.translation import ugettext as _, ugettext_lazy as _t
-
+from desktop.lib.i18n import smart_str
+from useradmin.organization import _fitered_queryset, get_user_request_organization
LOG = logging.getLogger()
@@ -248,4 +242,4 @@ def __str__(self):
return str(self.message)
def __unicode__(self):
- return smart_unicode(self.message)
+ return smart_str(self.message)
diff --git a/desktop/core/src/desktop/lib/connectors/tests.py b/desktop/core/src/desktop/lib/connectors/tests.py
index 42b67236344..1f487b06205 100644
--- a/desktop/core/src/desktop/lib/connectors/tests.py
+++ b/desktop/core/src/desktop/lib/connectors/tests.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python
-## -*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
# Licensed to Cloudera, Inc. under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
@@ -16,25 +16,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import json
-import pytest
import sys
+import json
import unittest
+from unittest.mock import Mock, patch
+import pytest
from django.test import TestCase
-from desktop.auth.backend import rewrite_user, is_admin
+from desktop.auth.backend import is_admin, rewrite_user
from desktop.conf import ENABLE_CONNECTORS, ENABLE_ORGANIZATIONS
from desktop.lib.connectors.api import _get_installed_connectors
from desktop.lib.django_test_util import make_logged_in_client
-from useradmin.models import User, update_app_permissions, get_default_user_group, Connector
-from useradmin.permissions import HuePermission, GroupPermission
-
-
-if sys.version_info[0] > 2:
- from unittest.mock import patch, Mock
-else:
- from mock import patch, Mock
+from useradmin.models import Connector, User, get_default_user_group, update_app_permissions
+from useradmin.permissions import GroupPermission, HuePermission
@pytest.mark.django_db
@@ -59,19 +54,16 @@ def teardown_class(cls):
for reset in cls._class_resets:
reset()
-
def test_page(self):
response = self.client.get("/desktop/connectors/")
assert 200 == response.status_code
-
def test_get_connector_types(self):
response = self.client.post("/desktop/connectors/api/types/")
assert 200 == response.status_code
-
def test_create_connector_perm(self):
response = self.client.post("/desktop/connectors/api/instance/update/")
assert 401 == response.status_code
@@ -79,7 +71,6 @@ def test_create_connector_perm(self):
response = self.client.post("/desktop/connectors/api/instance/delete/")
assert 401 == response.status_code
-
def test_test_connector(self):
connector = {
'connector': json.dumps({
@@ -144,7 +135,6 @@ def teardown_class(cls):
update_app_permissions()
-
def test_get_installed_editor_connectors(self):
with patch('desktop.lib.connectors.models.Connector.objects.all') as ConnectorObjectsAll:
@@ -158,7 +148,6 @@ def test_get_installed_editor_connectors(self):
assert editor_category, connectors
assert 1 == len(editor_category), editor_category
-
def test_get_connectors_for_user(self):
connector = Connector.objects.create(
name='MySql', dialect='mysql', settings=json.dumps([{"name": "url", "value": "mysql://hue:pwd@hue:3306/hue"}])
diff --git a/desktop/core/src/desktop/lib/connectors/types.py b/desktop/core/src/desktop/lib/connectors/types.py
index 69f1856becd..57bac7accf2 100644
--- a/desktop/core/src/desktop/lib/connectors/types.py
+++ b/desktop/core/src/desktop/lib/connectors/types.py
@@ -15,19 +15,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import sys
import json
import logging
-import sys
+
+from django.utils.translation import gettext as _
from desktop.conf import CONNECTORS_BLACKLIST, CONNECTORS_WHITELIST
from desktop.lib.exceptions_renderable import PopupException
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext as _
-else:
- from django.utils.translation import ugettext as _
-
-
LOG = logging.getLogger()
@@ -886,9 +882,11 @@
def get_connectors_types():
return CONNECTOR_TYPES
+
def get_connector_categories():
return CATEGORIES
+
def get_connector_by_type(dialect, interface):
instance = [
connector
diff --git a/desktop/core/src/desktop/lib/connectors/urls.py b/desktop/core/src/desktop/lib/connectors/urls.py
index 3e77c5f1045..5db2157e3ff 100644
--- a/desktop/core/src/desktop/lib/connectors/urls.py
+++ b/desktop/core/src/desktop/lib/connectors/urls.py
@@ -17,13 +17,9 @@
import sys
-from desktop.lib.connectors import views, api
-
-if sys.version_info[0] > 2:
- from django.urls import re_path
-else:
- from django.conf.urls import url as re_path
+from django.urls import re_path
+from desktop.lib.connectors import api, views
urlpatterns = [
re_path(r'^$', views.index, name='desktop.lib.connectors.views.index'),
diff --git a/desktop/core/src/desktop/lib/django_forms.py b/desktop/core/src/desktop/lib/django_forms.py
index 4e9a2f24f13..9d01aaa98d1 100644
--- a/desktop/core/src/desktop/lib/django_forms.py
+++ b/desktop/core/src/desktop/lib/django_forms.py
@@ -17,34 +17,25 @@
#
# Extra form fields and widgets.
-from future import standard_library
-standard_library.install_aliases()
-from builtins import filter
-from builtins import range
-from builtins import object
-import logging
import json
-import sys
+import logging
+import urllib.error
+import urllib.request
+from builtins import filter, object, range
+from urllib.parse import quote_plus as urllib_quote_plus
-from django.forms import Widget, Field
from django import forms
+from django.forms import Field, Widget
+from django.forms.fields import BooleanField, CharField, ChoiceField, MultiValueField
from django.forms.utils import ErrorList, ValidationError, flatatt
-from django.forms.fields import MultiValueField, CharField, ChoiceField, BooleanField
-from django.forms.widgets import MultiWidget, Select, TextInput, Textarea, HiddenInput, Input
+from django.forms.widgets import HiddenInput, Input, MultiWidget, Select, Textarea, TextInput
from django.utils import formats
+from django.utils.encoding import force_str
from django.utils.safestring import mark_safe
import desktop.lib.i18n
from desktop.lib.i18n import smart_str
-if sys.version_info[0] > 2:
- import urllib.request, urllib.error
- from urllib.parse import quote_plus as urllib_quote_plus
- from django.utils.encoding import force_str
-else:
- from urllib import quote_plus as urllib_quote_plus
- from django.utils.encoding import force_unicode as force_str
-
LOG = logging.getLogger()
try:
@@ -57,6 +48,7 @@ class StrAndUnicode(object):
def __str__(self):
return self.code
+
class SplitDateTimeWidget(forms.MultiWidget):
"""
A Widget that splits datetime input into two boxes.
@@ -91,6 +83,7 @@ def decompress(self, value):
return [value.date(), value.time().replace(microsecond=0)]
return [None, None]
+
class MultipleInputWidget(Widget):
"""
Together with MultipleInputField, represents repeating a form element many times,
@@ -126,6 +119,7 @@ def value_from_datadict(self, data, files, name):
non_empty = lambda x: len(x) != 0
return list(filter(non_empty, data.getlist(name)))
+
class MultipleInputField(Field):
widget = MultipleInputWidget
@@ -135,8 +129,10 @@ def __init__(self, *args, **kwargs):
def clean(self, value):
return value
+
OTHER_VAL, OTHER_PRES = "__other__", "Other..."
+
class ChoiceOrOtherWidget(MultiWidget):
"""
Together with ChoiceOrOtherField represents a drop-down and an "other"
@@ -160,11 +156,12 @@ def decompress(self, value):
else:
return [OTHER_VAL, value]
+
class ChoiceOrOtherField(MultiValueField):
def __init__(self, choices, initial=None, *args, **kwargs):
assert not kwargs.get('required', False), "required=True is not supported"
- allchoices = [x for x in choices] # Force choices into a list.
+ allchoices = [x for x in choices] # Force choices into a list.
allchoices.append((OTHER_VAL, OTHER_PRES))
self.widget = ChoiceOrOtherWidget(choices=allchoices)
choice_initial, other_initial = None, None
@@ -195,6 +192,7 @@ def compress(self, data_list):
raise ValidationError("Either select from the drop-down or select %s" % OTHER_PRES)
return data_list[0]
+
class KeyValueWidget(Textarea):
def render(self, name, value, attrs=None):
# If we have a dictionary, render back into a string.
@@ -202,6 +200,7 @@ def render(self, name, value, attrs=None):
value = " ".join("=".join([k, v]) for k, v in value.items())
return super(KeyValueWidget, self).render(name, value, attrs)
+
class KeyValueField(CharField):
"""
Represents an input area for key/value pairs in the following format:
@@ -221,6 +220,7 @@ def clean(self, value):
except Exception:
raise ValidationError("Not in key=value format.")
+
class UnicodeEncodingField(ChoiceOrOtherField):
"""
The cleaned value of the field is the actual encoding, not a tuple
@@ -335,6 +335,7 @@ def is_valid(self):
r = False
return r
+
class SubmitButton(Input):
"""
A widget that presents itself as a submit button.
@@ -508,6 +509,7 @@ def is_valid(self):
return valid and not bool(self.non_form_errors())
+
def simple_formset_factory(form, add_label="+", formset=BaseSimpleFormSet, initial=None):
"""Return a FormSet for the given form class."""
attrs = {
@@ -517,6 +519,7 @@ def simple_formset_factory(form, add_label="+", formset=BaseSimpleFormSet, initi
}
return type(form.__name__ + 'SimpleFormSet', (formset,), attrs)
+
class DependencyAwareForm(forms.Form):
"""
Inherit from this class and add
diff --git a/desktop/core/src/desktop/lib/django_mako.py b/desktop/core/src/desktop/lib/django_mako.py
index 70f4304fd42..3d5f1749e3a 100644
--- a/desktop/core/src/desktop/lib/django_mako.py
+++ b/desktop/core/src/desktop/lib/django_mako.py
@@ -24,8 +24,8 @@
from django.conf import settings
from django.contrib.staticfiles.storage import staticfiles_storage
from django.http import HttpResponse
-
-from mako.lookup import TemplateLookup, TemplateCollection
+from django.template.context_processors import csrf
+from mako.lookup import TemplateCollection, TemplateLookup
from desktop.lib import apputil, i18n
@@ -34,13 +34,14 @@
ENCODING_ERRORS = 'replace'
# Things to automatically import into all template namespaces
-IMPORTS=[
+IMPORTS = [
"from django.utils.html import escape",
"from desktop.lib.django_mako import url",
"from desktop.lib.django_mako import csrf_token",
"from desktop.lib.django_mako import static",
]
+
class DesktopLookup(TemplateCollection):
"""
Template loader for Mako which uses the app-specific
@@ -63,7 +64,7 @@ def _get_loader(self, app):
# and thus the temp dir would be owned as root, not the
# unpriveleged user!
if self.module_dir is None:
- self.module_dir = tempfile.mkdtemp() # TODO(todd) configurable?
+ self.module_dir = tempfile.mkdtemp() # TODO(todd) configurable?
app_module = __import__(app)
app_dir = os.path.dirname(app_module.__file__)
app_template_dir = os.path.join(app_dir, 'templates')
@@ -88,8 +89,10 @@ def get_template(self, uri):
real_loader = self._get_loader(app)
return real_loader.get_template(uri)
+
lookup = DesktopLookup()
+
def render_to_string_test(template_name, django_context):
"""
In tests, send a template rendered signal. This puts
@@ -101,6 +104,7 @@ def render_to_string_test(template_name, django_context):
signals.template_rendered.send(sender=None, template=template_name, context=django_context)
return render_to_string_normal(template_name, django_context)
+
def render_to_string_normal(template_name, django_context):
data_dict = dict()
if isinstance(django_context, django.template.context.Context):
@@ -114,11 +118,14 @@ def render_to_string_normal(template_name, django_context):
template = lookup.get_template(template_name)
data_dict = dict([(str(k), data_dict.get(k)) for k in list(data_dict.keys())])
result = template.render(**data_dict)
- return i18n.smart_unicode(result)
+ return i18n.smart_str(result)
# This variable is overridden in test code.
+
+
render_to_string = render_to_string_normal
+
def render_to_response(template_name, data_dictionary, **kwargs):
"""
Returns a HttpResponse whose content is filled with the result of calling
@@ -132,7 +139,6 @@ def url(view_name, *args, **view_args):
from django.urls import reverse
return reverse(view_name, args=args, kwargs=view_args)
-from django.template.context_processors import csrf
def csrf_token(request):
"""
@@ -141,6 +147,7 @@ def csrf_token(request):
csrf_token = str(csrf(request)["csrf_token"])
return str.format("", csrf_token)
+
def static(path):
"""
Returns the URL to a file using the staticfiles's storage engine
diff --git a/desktop/core/src/desktop/lib/django_test_util.py b/desktop/core/src/desktop/lib/django_test_util.py
index 67af9d996df..e357fe84536 100644
--- a/desktop/core/src/desktop/lib/django_test_util.py
+++ b/desktop/core/src/desktop/lib/django_test_util.py
@@ -15,26 +15,29 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import json
-import logging
import re
-import django.test.client
+import json
from unittest.mock import Mock
+import django.test.client
+
from desktop.conf import ENABLE_ORGANIZATIONS
-from useradmin.models import User, Group, Organization
+from useradmin.models import Group, User
class Client(django.test.client.Client):
"""
Extends client to have a get_json method.
"""
+
def get_json(self, *args, **kwargs):
response = self.get(*args, **kwargs)
return json.JSONDecoder().decode(response.content)
-def make_logged_in_client(username="test", password="test", is_superuser=True, recreate=False, groupname=None, is_admin=False, request=None):
+def make_logged_in_client(
+ username="test", password="test", is_superuser=True, recreate=False, groupname=None, is_admin=False, request=None
+):
"""
Create a client with a user already logged in.
@@ -78,7 +81,8 @@ def make_logged_in_client(username="test", password="test", is_superuser=True, r
return c
-_MULTI_WHITESPACE = re.compile("\s+", flags=re.MULTILINE)
+_MULTI_WHITESPACE = re.compile(r"\s+", flags=re.MULTILINE)
+
def compact_whitespace(s):
"""
@@ -87,6 +91,7 @@ def compact_whitespace(s):
"""
return _MULTI_WHITESPACE.sub(" ", s).strip()
+
def assert_equal_mod_whitespace(first, second, msg=None):
"""
Asserts that two strings are equal, ignoring whitespace.
@@ -108,7 +113,7 @@ def configure_django_for_test():
def create_tables(model):
- """ Create all tables for the given model.
+ """Create all tables for the given model.
This is a subset of django.core.management.commands.migrate
"""
diff --git a/desktop/core/src/desktop/lib/django_util.py b/desktop/core/src/desktop/lib/django_util.py
index 21bd933f118..3c3596719cd 100644
--- a/desktop/core/src/desktop/lib/django_util.py
+++ b/desktop/core/src/desktop/lib/django_util.py
@@ -17,28 +17,27 @@
#
# Utilities for django operations.
-from builtins import object
-import logging
import re
import json
import socket
+import logging
import datetime
-import sys
from django.conf import settings
from django.core import serializers
from django.core.exceptions import FieldDoesNotExist
-from django.template import context as django_template_context
-from django.template.context_processors import csrf
from django.core.serializers.json import DjangoJSONEncoder
from django.db import models
-from django.http import QueryDict, HttpResponse, HttpResponseRedirect
+from django.http import HttpResponse, HttpResponseRedirect, QueryDict
from django.shortcuts import render as django_render
+from django.template import context as django_template_context
from django.template.context import RequestContext
+from django.template.context_processors import csrf
from django.template.loader import render_to_string as django_render_to_string
from django.urls import reverse
-from django.utils.http import urlencode # this version is unicode-friendly
+from django.utils.http import urlencode # this version is unicode-friendly
from django.utils.timezone import get_current_timezone
+from django.utils.translation import gettext as _, ngettext as _t
import desktop.conf
import desktop.lib.thrift_util
@@ -46,12 +45,6 @@
from desktop.lib.json_utils import JSONEncoderForHTML
from desktop.monkey_patches import monkey_patch_request_context_init
-if sys.version_info[0] > 2:
- from django.utils.translation import ngettext as _t, gettext as _
-else:
- from django.utils.translation import ungettext as _t, ugettext as _
-
-
LOG = logging.getLogger()
# Values for template_lib parameter
@@ -59,14 +52,15 @@
MAKO = 'mako'
# This is what Debian allows. See chkname.c in shadow.
-USERNAME_RE_RULE = "[^-:\s][^:\s]*"
+USERNAME_RE_RULE = r"[^-:\s][^:\s]*"
GROUPNAME_RE_RULE = ".{,80}"
django_template_context.RequestContext.__init__ = monkey_patch_request_context_init
# For backward compatibility for upgrades to Hue 2.2
-class PopupException(object): pass
+class PopupException(object):
+ pass
class Encoder(json.JSONEncoder):
@@ -88,18 +82,24 @@ def default(self, o):
return json.JSONEncoder.default(self, o)
+
def get_username_re_rule():
return USERNAME_RE_RULE
+
def get_groupname_re_rule():
return GROUPNAME_RE_RULE
+
def login_notrequired(func):
"""A decorator for view functions to allow access without login"""
func.login_notrequired = True
return func
+
_uri_prefix = None
+
+
def get_desktop_uri_prefix():
"""
Return the uri prefix where desktop is generally accessible.
@@ -138,13 +138,14 @@ def make_absolute(request, view_name, kwargs=None):
"""
return request.build_absolute_uri(reverse(view_name, kwargs=kwargs))
+
def _get_template_lib(template, kwargs):
template_lib = kwargs.get('template_lib')
if 'template_lib' in kwargs:
del kwargs['template_lib']
# Default based on file extension
- if template_lib == None:
+ if template_lib is None:
if template.endswith('.mako'):
return MAKO
else:
@@ -162,6 +163,7 @@ def _render_to_response(template, request, *args, **kwargs):
else:
raise Exception("Bad template lib: %s" % template_lib)
+
def render_to_string(template, *args, **kwargs):
"""Wrapper around django.template.loader.render_to_string which supports
different template libraries."""
@@ -173,6 +175,7 @@ def render_to_string(template, *args, **kwargs):
else:
raise Exception("Bad template lib: %s" % template_lib)
+
def format_preserving_redirect(request, target, get_dict=None):
"""
If request represents an ajax or embeddable "format", tries
@@ -189,7 +192,7 @@ def format_preserving_redirect(request, target, get_dict=None):
my_get_dict['is_embeddable'] = True
if is_jframe_request(request):
- logging.info("JFrame redirection" + target)
+ logging.info("JFrame redirection" + target)
my_get_dict['format'] = 'embed'
elif request.ajax:
my_get_dict['format'] = 'json'
@@ -203,6 +206,7 @@ def format_preserving_redirect(request, target, get_dict=None):
return HttpResponseRedirect(target + param)
+
def is_jframe_request(request):
"""
The JFrame container uses ?format=embed to request
@@ -214,6 +218,7 @@ def is_jframe_request(request):
return request.META.get('HTTP_X_HUE_JFRAME') or \
request.GET.get("format") == "embed"
+
def render(template, request, data, json=None, template_lib=None, force_template=False, status=200, **kwargs):
"""
Render() is the main shortcut/workhorse for rendering view responses.
@@ -290,6 +295,7 @@ def encode_json(data, indent=None):
"""
return json.dumps(data, indent=indent, cls=Encoder)
+
def encode_json_for_js(data, indent=None):
"""
Converts data into a JSON string.
@@ -299,11 +305,14 @@ def encode_json_for_js(data, indent=None):
"""
return json.dumps(data, indent=indent, cls=JSONEncoderForHTML)
+
VALID_JSON_IDENTIFIER = re.compile("^[a-zA-Z_$][a-zA-Z0-9_$]*$")
+
class IllegalJsonpCallbackNameException(Exception):
pass
+
def render_json(data, jsonp_callback=None, js_safe=False, status=200):
"""
Renders data as json. If jsonp is specified, wraps the result in a function.
@@ -325,6 +334,7 @@ def render_json(data, jsonp_callback=None, js_safe=False, status=200):
return HttpResponse(json, content_type='text/javascript', status=status)
+
def update_if_dirty(model_instance, **kwargs):
"""
Updates an instance of a model with kwargs.
@@ -339,6 +349,7 @@ def update_if_dirty(model_instance, **kwargs):
if dirty:
model_instance.save()
+
def extract_field_data(field):
"""
given a form field, return its value
@@ -351,13 +362,15 @@ def extract_field_data(field):
else:
return field.data
+
def get_app_nice_name(app_name):
try:
return desktop.appmanager.get_desktop_module(app_name).settings.NICE_NAME
- except:
+ except Exception:
LOG.exception('failed to get nice name for app %s' % app_name)
return app_name
+
class TruncatingModel(models.Model):
"""
Abstract class which truncates Text and Char fields to their configured
@@ -369,13 +382,14 @@ class Meta(object):
def __setattr__(self, name, value):
try:
field = self._meta.get_field(name)
- if type(field) in [models.CharField, models.TextField] and type(value) == str:
+ if type(field) in [models.CharField, models.TextField] and type(value) is str:
value = value[:field.max_length]
except FieldDoesNotExist:
- pass # This happens with foreign keys.
+ pass # This happens with foreign keys.
super.__setattr__(self, name, value)
+
def reverse_with_get(view, args=None, kwargs=None, get=None):
"""
Version of reverse that also manages get parameters.
@@ -393,11 +407,13 @@ def reverse_with_get(view, args=None, kwargs=None, get=None):
url = url + "?" + params
return url
+
def humanize_duration(seconds, abbreviate=False, separator=','):
d = datetime.datetime.fromtimestamp(0)
now = datetime.datetime.fromtimestamp(seconds)
return timesince(d, now, abbreviate, separator)
+
def timesince(d=None, now=None, abbreviate=False, separator=','):
"""
Takes two datetime objects and returns the time between d and now
diff --git a/desktop/core/src/desktop/lib/django_util_test.py b/desktop/core/src/desktop/lib/django_util_test.py
index da22d035caf..2aea8f421b7 100644
--- a/desktop/core/src/desktop/lib/django_util_test.py
+++ b/desktop/core/src/desktop/lib/django_util_test.py
@@ -16,22 +16,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from builtins import object
import datetime
-import pytest
-import sys
+import pytest
+from django.db import models
from django.http import HttpResponse, HttpResponseRedirect
+from desktop.lib import django_util, exceptions
from desktop.lib.django_test_util import configure_django_for_test, create_tables
-from desktop.lib.django_util import reverse_with_get, timesince, humanize_duration
-configure_django_for_test()
+from desktop.lib.django_util import humanize_duration, reverse_with_get, timesince
-from desktop.lib import django_util, exceptions
-from django.db import models
+configure_django_for_test()
-if sys.version_info[0] > 2:
- unichr = chr
class TestModel(models.Model):
class Meta(object):
@@ -41,6 +37,7 @@ class Meta(object):
my_str = models.TextField(max_length=100)
last_modified = models.DateTimeField(auto_now=True)
+
@pytest.mark.django_db
class TestDjangoUtil(object):
def test_update_if_dirty(self):
@@ -80,7 +77,7 @@ def test_encode_json_model(self):
django_util.encode_json(TestModel(my_int=3, my_str="foo")))
assert ('[{"model": "TEST_APP.testmodel", "pk": null, "fields": {"my_int": 3, "my_str": "foo", "last_modified": null}}]' ==
django_util.encode_json([TestModel(my_int=3, my_str="foo")]))
-
+
def test_timesince(self):
assert timesince(datetime.datetime.fromtimestamp(0), datetime.datetime.fromtimestamp(1000)) == "16 minutes, 40 seconds"
assert timesince(datetime.datetime.fromtimestamp(0), datetime.datetime.fromtimestamp(60)) == "1 minute"
@@ -107,7 +104,6 @@ def test_humanize_duration(self):
assert humanize_duration(seconds=1, abbreviate=True) == "1s"
assert humanize_duration(seconds=2, abbreviate=True) == "2s"
-
def test_encode_json_to_jsonable(self):
class Foo(object):
def to_jsonable(self):
@@ -120,7 +116,7 @@ def to_jsonable(self):
class Bar(object):
to_jsonable = "not a callable"
with pytest.raises(TypeError):
- django_util.encode_json([ Bar() ])
+ django_util.encode_json([Bar()])
def test_encode_json_thrift(self):
# TODO(philip): I've avoided writing this because
@@ -139,7 +135,6 @@ def test_render_json_jsonp_bad_name(self):
for x in ["a", "$", "_", "a9", "a9$"]:
django_util.render_json("whatever-value", x)
-
def test_exceptions(self):
msg = "b0rked file"
the_file = "foobar"
@@ -168,6 +163,7 @@ def test_popup_injection():
with pytest.raises(AssertionError):
django_util.render_injected("foo", "bar")
+
def test_reverse_with_get():
# Basic view
assert "/" == reverse_with_get("desktop_views.index")
@@ -175,14 +171,15 @@ def test_reverse_with_get():
assert "/desktop/api2/user_preferences/foo" == reverse_with_get("desktop.api2.user_preferences", kwargs=dict(key="foo"))
# Arguments for the view as well as GET parameters
assert ("/desktop/api2/user_preferences/foo?a=1&b=2" ==
- reverse_with_get("desktop.api2.user_preferences", kwargs=dict(key="foo"), get=dict(a=1,b=2)))
+ reverse_with_get("desktop.api2.user_preferences", kwargs=dict(key="foo"), get=dict(a=1, b=2)))
# You can use a list of args instead of kwargs, too
assert ("/desktop/api2/user_preferences/foo?a=1&b=2" ==
- reverse_with_get("desktop.api2.user_preferences", args=["foo"], get=dict(a=1,b=2)))
+ reverse_with_get("desktop.api2.user_preferences", args=["foo"], get=dict(a=1, b=2)))
# Just GET parameters
assert "/?a=1" == reverse_with_get("desktop_views.index", get=dict(a="1"))
# No GET parameters
assert "/" == reverse_with_get("desktop_views.index", get=dict())
+
def test_unicode_ok():
- assert "/?a=x%C3%A9" == reverse_with_get("desktop_views.index", get=dict(a="x" + unichr(233)))
+ assert "/?a=x%C3%A9" == reverse_with_get("desktop_views.index", get=dict(a="x" + chr(233)))
diff --git a/desktop/core/src/desktop/lib/exceptions_renderable.py b/desktop/core/src/desktop/lib/exceptions_renderable.py
index db31b988cd2..35cd896a4ac 100644
--- a/desktop/core/src/desktop/lib/exceptions_renderable.py
+++ b/desktop/core/src/desktop/lib/exceptions_renderable.py
@@ -19,18 +19,14 @@
This file exists to remove circular reference caused by importing django_util.
"""
-import logging
import sys
+import logging
import traceback
-if sys.version_info[0] > 2:
- from django.utils.encoding import force_str
-else:
- from django.utils.encoding import force_unicode as force_str
+from django.utils.encoding import force_str
import desktop.lib.django_util
-
LOG = logging.getLogger()
@@ -38,6 +34,7 @@ def raise_popup_exception(message, title="Error", detail=None, error_code=500):
tb = sys.exc_info()
raise PopupException(message, title=title, detail=detail, error_code=error_code, tb=traceback.extract_tb(tb[2]))
+
class PopupException(Exception):
"""
Middleware will render this exception; and the template renders it as a pop-up.
@@ -54,7 +51,7 @@ def __init__(self, message, title="Error", detail=None, error_code=500, tb=None)
if tb:
self.traceback = tb
- else: # At this point the previous trace is already lost
+ else: # At this point the previous trace is already lost
# Traceback is only relevant if an exception was thrown, caught, and we reraise with this exception.
tb = sys.exc_info()
self.traceback = traceback.extract_tb(tb[2])
@@ -77,9 +74,9 @@ def response(self, request):
data['traceback'] = traceback.format_list(data['traceback'])
response = desktop.lib.django_util.render("popup_error.mako", request, data)
- if self.error_code == 500 and data['is_embeddable']: # Hue 4
+ if self.error_code == 500 and data['is_embeddable']: # Hue 4
response.status_code = 200
else:
response.status_code = self.error_code
- return response
\ No newline at end of file
+ return response
diff --git a/desktop/core/src/desktop/lib/export_csvxls.py b/desktop/core/src/desktop/lib/export_csvxls.py
index 4ce2abd7e62..ec9d0abbd4f 100644
--- a/desktop/core/src/desktop/lib/export_csvxls.py
+++ b/desktop/core/src/desktop/lib/export_csvxls.py
@@ -18,33 +18,24 @@
"""
Common library to export either CSV or XLS.
"""
-from future import standard_library
-standard_library.install_aliases()
-from builtins import next, object
import gc
+import re
import logging
import numbers
-import openpyxl
-import re
+from io import BytesIO as string_io
+from urllib.parse import quote
+
import six
-import sys
import tablib
-
-from django.http import StreamingHttpResponse, HttpResponse
+import openpyxl
+from django.http import HttpResponse, StreamingHttpResponse
from django.utils.encoding import smart_str
-from desktop.lib import i18n
-
-if sys.version_info[0] > 2:
- from io import BytesIO as string_io
- from urllib.parse import quote
-else:
- from StringIO import StringIO as string_io
- from django.utils.http import urlquote as quote
+from desktop.lib import i18n
LOG = logging.getLogger()
-DOWNLOAD_CHUNK_SIZE = 1 * 1024 * 1024 # 1MB
+DOWNLOAD_CHUNK_SIZE = 1 * 1024 * 1024 # 1MB
ILLEGAL_CHARS = r'[\000-\010]|[\013-\014]|[\016-\037]'
FORMAT_TO_CONTENT_TYPE = {
'csv': 'application/csv',
@@ -56,6 +47,7 @@
def nullify(cell):
return cell if cell is not None else "NULL"
+
def file_reader(fh):
"""Generator that reads a file, chunk-by-chunk."""
while True:
@@ -65,6 +57,7 @@ def file_reader(fh):
break
yield chunk
+
def encode_row(row, encoding=None, make_excel_links=False):
encoded_row = []
encoding = encoding or i18n.get_site_encoding()
@@ -138,7 +131,7 @@ def create_generator(content_generator, format, encoding=None):
raise Exception("Unknown format: %s" % format)
-def make_response(generator, format, name, encoding=None, user_agent=None): #TODO: Add support for 3rd party (e.g. nginx file serving)
+def make_response(generator, format, name, encoding=None, user_agent=None): # TODO: Add support for 3rd party (e.g. nginx file serving)
"""
@param data An iterator of rows, where every row is a list of strings
@param format Either "csv" or "xls"
diff --git a/desktop/core/src/desktop/lib/export_csvxls_tests.py b/desktop/core/src/desktop/lib/export_csvxls_tests.py
index 949c53f6262..a44680d763e 100644
--- a/desktop/core/src/desktop/lib/export_csvxls_tests.py
+++ b/desktop/core/src/desktop/lib/export_csvxls_tests.py
@@ -16,18 +16,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from future import standard_library
-standard_library.install_aliases()
-import sys
+from io import BytesIO as string_io
from openpyxl import load_workbook
from desktop.lib.export_csvxls import create_generator, make_response
-if sys.version_info[0] > 2:
- from io import BytesIO as string_io
-else:
- from cStringIO import StringIO as string_io
def content_generator(header, data):
yield header, data
@@ -35,7 +29,7 @@ def content_generator(header, data):
def test_export_csv():
headers = ["x", "y"]
- data = [ ["1", "2"], ["3", "4"], ["5,6", "7"], [None, None], ["http://gethue.com", "http://gethue.com"] ]
+ data = [["1", "2"], ["3", "4"], ["5,6", "7"], [None, None], ["http://gethue.com", "http://gethue.com"]]
# Check CSV
generator = create_generator(content_generator(headers, data), "csv")
@@ -47,7 +41,7 @@ def test_export_csv():
# Check non-ASCII for any browser except FF or no browser info
generator = create_generator(content_generator(headers, data), "csv")
- response = make_response(generator, "csv", u'gんtbhんjk?¥n')
+ response = make_response(generator, "csv", 'gんtbhんjk?¥n')
assert "application/csv" == response["content-type"]
content = b''.join(response.streaming_content)
assert b'x,y\r\n1,2\r\n3,4\r\n"5,6",7\r\nNULL,NULL\r\nhttp://gethue.com,http://gethue.com\r\n' == content
@@ -56,16 +50,15 @@ def test_export_csv():
# Check non-ASCII for FF browser
generator = create_generator(content_generator(headers, data), "csv")
response = make_response(
- generator, "csv", u'gんtbhんjk?¥n',
- user_agent='Mozilla / 5.0(Macintosh; Intel Mac OS X 10.12;rv:59.0) Gecko / 20100101 Firefox / 59.0)'
+ generator,
+ "csv",
+ 'gんtbhんjk?¥n',
+ user_agent='Mozilla / 5.0(Macintosh; Intel Mac OS X 10.12;rv:59.0) Gecko / 20100101 Firefox / 59.0)',
)
assert "application/csv" == response["content-type"]
content = b''.join(response.streaming_content)
assert b'x,y\r\n1,2\r\n3,4\r\n"5,6",7\r\nNULL,NULL\r\nhttp://gethue.com,http://gethue.com\r\n' == content
- assert (
- 'attachment; filename*="g%E3%82%93tbh%E3%82%93jk%EF%BC%9F%EF%BF%A5n.csv"' ==
- response["content-disposition"])
-
+ assert 'attachment; filename*="g%E3%82%93tbh%E3%82%93jk%EF%BC%9F%EF%BF%A5n.csv"' == response["content-disposition"]
def test_export_xls():
@@ -78,7 +71,9 @@ def test_export_xls():
response = make_response(generator, "xls", "foo")
assert "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" == response["content-type"]
- expected_data = [[cell is not None and cell.replace("http://gethue.com", '=HYPERLINK("http://gethue.com")') or "NULL" for cell in row] for row in sheet]
+ expected_data = [
+ [cell is not None and cell.replace("http://gethue.com", '=HYPERLINK("http://gethue.com")') or "NULL" for cell in row] for row in sheet
+ ]
sheet_data = _read_xls_sheet_data(response)
assert expected_data == sheet_data
diff --git a/desktop/core/src/desktop/lib/fs/__init__.py b/desktop/core/src/desktop/lib/fs/__init__.py
index 15226f447b4..05c21f2e351 100644
--- a/desktop/core/src/desktop/lib/fs/__init__.py
+++ b/desktop/core/src/desktop/lib/fs/__init__.py
@@ -14,26 +14,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from __future__ import absolute_import
-
-from future import standard_library
-standard_library.install_aliases()
-from builtins import filter
import posixpath
-import sys
+from urllib.parse import urlparse as lib_urlparse
from desktop.lib.fs.proxyfs import ProxyFS # Imported later from this module
-if sys.version_info[0] > 2:
- from urllib.parse import urlparse as lib_urlparse
-else:
- from urlparse import urlparse as lib_urlparse
-
def splitpath(path):
split = lib_urlparse(path)
path_parsed_as_query = ''
-
+
# Make sure the splitpath can handle a path that contains "?" since
# that is the case for the file browser paths.
if '?' in path:
diff --git a/desktop/core/src/desktop/lib/fs/ozone/ofs.py b/desktop/core/src/desktop/lib/fs/ozone/ofs.py
index f4f49885c4d..abfd19d4baa 100644
--- a/desktop/core/src/desktop/lib/fs/ozone/ofs.py
+++ b/desktop/core/src/desktop/lib/fs/ozone/ofs.py
@@ -18,31 +18,21 @@
"""
Interfaces for Hadoop filesystem access via HttpFs/WebHDFS
"""
+import stat
import errno
import logging
import posixpath
-import stat
-import sys
-import threading
+from urllib.parse import urlparse as lib_urlparse
from django.utils.encoding import smart_str
+from django.utils.translation import gettext as _
-from desktop.lib.rest import http_client, resource
-from desktop.lib.fs.ozone import OFS_ROOT, normpath, is_root, parent_path, _serviceid_join, join as ofs_join
-from desktop.lib.fs.ozone.ofsstat import OzoneFSStat
from desktop.conf import PERMISSION_ACTION_OFS
-
+from desktop.lib.fs.ozone import OFS_ROOT, _serviceid_join, is_root, join as ofs_join, normpath, parent_path
+from desktop.lib.fs.ozone.ofsstat import OzoneFSStat
from hadoop.fs.exceptions import WebHdfsException
-from hadoop.hdfs_site import get_umask_mode
from hadoop.fs.webhdfs import WebHdfs
-
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext as _
- from urllib.parse import urlparse as lib_urlparse
-else:
- from django.utils.translation import ugettext as _
- from urlparse import urlparse as lib_urlparse
-
+from hadoop.hdfs_site import get_umask_mode
LOG = logging.getLogger()
@@ -149,20 +139,20 @@ def _stats(self, path):
if ex.server_exc == 'FileNotFoundException' or ex.code == 404:
return None
raise ex
-
+
return OzoneFSStat(json['FileStatus'], path, self._netloc)
-
+
def _handle_serviceid_path_status(self):
json = {
'FileStatuses': {
'FileStatus': [{
- 'pathSuffix': self._netloc, 'type': 'DIRECTORY', 'length': 0, 'owner': '', 'group': '',
+ 'pathSuffix': self._netloc, 'type': 'DIRECTORY', 'length': 0, 'owner': '', 'group': '',
'permission': '777', 'accessTime': 0, 'modificationTime': 0, 'blockSize': 0, 'replication': 0
}]
}
}
return json
-
+
def stats(self, path):
"""
stats(path) -> OzoneFSStat
@@ -198,7 +188,7 @@ def rename(self, old, new):
if not result['boolean']:
raise IOError(_("Rename failed: %s -> %s") % (smart_str(old, errors='replace'), smart_str(new, errors='replace')))
-
+
def rename_star(self, old_dir, new_dir):
"""Equivalent to `mv old_dir/* new"""
if not self.isdir(old_dir):
@@ -208,7 +198,7 @@ def rename_star(self, old_dir, new_dir):
self.mkdir(new_dir)
elif not self.isdir(new_dir):
raise IOError(errno.ENOTDIR, _("'%s' is not a directory") % new_dir)
-
+
ls = self.listdir(old_dir)
for dirent in ls:
self.rename(_serviceid_join(ofs_join(old_dir, dirent), self._netloc), _serviceid_join(ofs_join(new_dir, dirent), self._netloc))
@@ -269,7 +259,7 @@ def copy(self, src, dest, recursive=False, dir_mode=None, owner=None):
if not self.exists(src):
raise IOError(errno.ENOENT, _("File not found: %s") % src)
- skip_file_list = '' # Store the files to skip copying which are greater than the upload_chunck_size()
+ skip_file_list = '' # Store the files to skip copying which are greater than the upload_chunck_size()
if self.isdir(src):
# 'src' is directory.
@@ -290,7 +280,7 @@ def copy(self, src, dest, recursive=False, dir_mode=None, owner=None):
self.do_as_user(owner, self.mkdir, dest, mode=dir_mode)
# Copy files in 'src' directory to 'dest'.
-
+
skip_file_list = self.copy_remote_dir(src, dest, dir_mode, owner, skip_file_list)
else:
# 'src' is a file.
diff --git a/desktop/core/src/desktop/lib/fs/ozone/upload.py b/desktop/core/src/desktop/lib/fs/ozone/upload.py
index c9a83f610ba..f6126ba13bf 100644
--- a/desktop/core/src/desktop/lib/fs/ozone/upload.py
+++ b/desktop/core/src/desktop/lib/fs/ozone/upload.py
@@ -21,19 +21,14 @@
from django.core.files.uploadedfile import SimpleUploadedFile
from django.core.files.uploadhandler import FileUploadHandler, StopFutureHandlers, StopUpload, UploadFileException
-
-from desktop.lib.fsmanager import get_client
-from hadoop.conf import UPLOAD_CHUNK_SIZE
-from hadoop.fs.exceptions import WebHdfsException
-
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext as _
-else:
- from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from desktop.conf import TASK_SERVER_V2
from desktop.lib.exceptions_renderable import PopupException
+from desktop.lib.fsmanager import get_client
from filebrowser.utils import calculate_total_size, generate_chunks
+from hadoop.conf import UPLOAD_CHUNK_SIZE
+from hadoop.fs.exceptions import WebHdfsException
LOG = logging.getLogger()
diff --git a/desktop/core/src/desktop/lib/fs/ozone/upload_test.py b/desktop/core/src/desktop/lib/fs/ozone/upload_test.py
index a86a4b81fb2..9026dabd217 100644
--- a/desktop/core/src/desktop/lib/fs/ozone/upload_test.py
+++ b/desktop/core/src/desktop/lib/fs/ozone/upload_test.py
@@ -17,14 +17,10 @@
# limitations under the License.
import sys
+from unittest.mock import Mock, patch
from desktop.lib.fs.ozone.upload import OFSFileUploadHandler
-if sys.version_info[0] > 2:
- from unittest.mock import patch, Mock
-else:
- from mock import patch, Mock
-
class TestOFSFileUploadHandler(object):
diff --git a/desktop/core/src/desktop/lib/fs/proxyfs_test.py b/desktop/core/src/desktop/lib/fs/proxyfs_test.py
index 3aa9f404b7f..3ea3d670289 100644
--- a/desktop/core/src/desktop/lib/fs/proxyfs_test.py
+++ b/desktop/core/src/desktop/lib/fs/proxyfs_test.py
@@ -14,24 +14,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from __future__ import absolute_import
-
-import pytest
-import sys
-
from builtins import object
+from unittest.mock import MagicMock, patch
-from useradmin.models import User
+import pytest
-from desktop.auth.backend import rewrite_user
-from desktop.lib.fs import ProxyFS
from desktop.lib.django_test_util import make_logged_in_client
+from desktop.lib.fs import ProxyFS
from desktop.lib.test_utils import add_permission, remove_from_group
-
-if sys.version_info[0] > 2:
- from unittest.mock import patch, MagicMock
-else:
- from mock import patch, MagicMock
+from useradmin.models import User
@pytest.mark.django_db
@@ -74,6 +65,7 @@ def test_fs_selection():
with pytest.raises(IOError):
proxy_fs.stats('ftp://host')
+
def wrapper(mock):
def tmp(*args, **kwargs):
return mock
@@ -144,7 +136,6 @@ def filebrowser_action(self):
return self._filebrowser_action
-
@pytest.mark.django_db
class TestFsPermissions(object):
@@ -233,4 +224,3 @@ def test_fs_permissions_admin_user(self):
f('/tmp')
f('gs://bucket/key')
f('ofs://volume/bucket/key')
-
diff --git a/desktop/core/src/desktop/lib/i18n.py b/desktop/core/src/desktop/lib/i18n.py
index 40b809fe519..b758c529be4 100644
--- a/desktop/core/src/desktop/lib/i18n.py
+++ b/desktop/core/src/desktop/lib/i18n.py
@@ -19,19 +19,14 @@
Library methods to deal with non-ascii data
"""
-import codecs
-import logging
import os
import re
-import sys
+import codecs
+import logging
-import desktop.conf
import django.utils.encoding
-if sys.version_info[0] > 2:
- from django.utils.encoding import smart_str as django_smart_unicode
-else:
- from django.utils.encoding import smart_text as django_smart_unicode
+import desktop.conf
SITE_ENCODING = None
REPLACEMENT_CHAR = u'\ufffd'
@@ -52,6 +47,7 @@ def get_site_encoding():
SITE_ENCODING = encoding
return SITE_ENCODING
+
def validate_encoding(encoding):
"""Return True/False on whether the system understands this encoding"""
try:
@@ -60,37 +56,26 @@ def validate_encoding(encoding):
except LookupError:
return False
-def smart_unicode(s, strings_only=False, errors='strict', encoding=None):
- """
- Wrapper around Django's version, while supplying our configured encoding.
- Decode char array to unicode.
- For py3 -> this is becoming a 'string' now (no more 'unicode' in py3).
- """
-
- return django_smart_unicode(
- s, encoding if encoding is not None else get_site_encoding(), strings_only, errors)
def force_unicode(s, strings_only=False, errors='strict'):
"""
Wrapper around Django's version, while supplying our configured encoding.
Decode char array to unicode.
"""
- if sys.version_info[0] > 2:
- return django.utils.encoding.force_str(s, get_site_encoding(), strings_only, errors)
- else:
- return django.utils.encoding.force_unicode(s, get_site_encoding(), strings_only, errors)
+ return django.utils.encoding.force_str(s, get_site_encoding(), strings_only, errors)
+
-def smart_str(s, strings_only=False, errors='strict'):
+def smart_str(s, strings_only=False, errors='strict', encoding=None):
"""
Wrapper around Django's version, while supplying our configured encoding.
Encode unicode into char array.
"""
- return django.utils.encoding.smart_str(
- s, get_site_encoding(), strings_only, errors)
+ return django.utils.encoding.smart_str(s, encoding if encoding is not None else get_site_encoding(), strings_only, errors)
_CACHED_ENV = None
+
def make_utf8_env():
"""
Communication with child processes is in utf8. Make a utf8 environment.
@@ -99,7 +84,7 @@ def make_utf8_env():
if not _CACHED_ENV:
# LANG are in the form of [.[@]]
# We want to replace the "encoding" part with UTF-8
- lang_re = re.compile('\.([^@]*)')
+ lang_re = re.compile(r'\.([^@]*)')
env = os.environ.copy()
lang = env.get('LANG', DEFAULT_LANG)
diff --git a/desktop/core/src/desktop/lib/metrics/file_reporter.py b/desktop/core/src/desktop/lib/metrics/file_reporter.py
index 550bc19f1c8..20251bc4627 100644
--- a/desktop/core/src/desktop/lib/metrics/file_reporter.py
+++ b/desktop/core/src/desktop/lib/metrics/file_reporter.py
@@ -14,17 +14,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import json
-import logging
import os
import sys
+import json
+import logging
import tempfile
from pyformance.reporters.reporter import Reporter
from desktop.lib.metrics import global_registry
-
LOG = logging.getLogger()
@@ -47,7 +46,7 @@ def report_now(self, registry=None, timestamp=None):
# rename the file to the real location.
f = tempfile.NamedTemporaryFile(
- mode='w' if sys.version_info[0] > 2 else 'w+b',
+ mode='w',
dir=dirname,
delete=False)
@@ -67,6 +66,7 @@ def report_now(self, registry=None, timestamp=None):
os.remove(f.name)
raise
+
_reporter = None
diff --git a/desktop/core/src/desktop/lib/metrics/urls.py b/desktop/core/src/desktop/lib/metrics/urls.py
index f6bf18f61ba..c5600fe6516 100644
--- a/desktop/core/src/desktop/lib/metrics/urls.py
+++ b/desktop/core/src/desktop/lib/metrics/urls.py
@@ -17,12 +17,9 @@
import sys
-from desktop.lib.metrics import views
+from django.urls import re_path
-if sys.version_info[0] > 2:
- from django.urls import re_path
-else:
- from django.conf.urls import url as re_path
+from desktop.lib.metrics import views
urlpatterns = [
re_path(r'^$', views.index, name='desktop.lib.metrics.views.index'),
diff --git a/desktop/core/src/desktop/lib/python_util.py b/desktop/core/src/desktop/lib/python_util.py
index abaea1a06a8..23c35c23d0a 100644
--- a/desktop/core/src/desktop/lib/python_util.py
+++ b/desktop/core/src/desktop/lib/python_util.py
@@ -15,25 +15,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from builtins import object
-from six import string_types
-
-import datetime
-import logging
+import sys
import select
import socket
-import sys
+import logging
+import datetime
+from builtins import object
+from codecs import BOM_UTF8, BOM_UTF16_BE, BOM_UTF16_LE, BOM_UTF32_BE, BOM_UTF32_LE
+
+from django.utils.translation import gettext as _
+from six import string_types
from desktop import conf
from desktop.lib.i18n import smart_str
-from codecs import BOM_UTF8, BOM_UTF16_BE, BOM_UTF16_LE, BOM_UTF32_BE, BOM_UTF32_LE
-
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext as _
-else:
- from django.utils.translation import ugettext as _
-
BOMS = (
(BOM_UTF8, "UTF-8"),
(BOM_UTF32_BE, "UTF-32-BE"),
@@ -238,6 +233,7 @@ def isISO8859_1(data):
else:
return True
+
def isCP1252(data):
try:
data.decode('cp1252')
@@ -246,6 +242,7 @@ def isCP1252(data):
else:
return True
+
def isUTF8Strict(data):
try:
decoded = data.decode('UTF-8')
@@ -290,5 +287,6 @@ def check_encoding(data):
else:
return 'cp1252'
+
def current_ms_from_utc():
return (datetime.datetime.utcnow() - datetime.datetime.utcfromtimestamp(0)).total_seconds() * 1000
diff --git a/desktop/core/src/desktop/lib/raz/clients_test.py b/desktop/core/src/desktop/lib/raz/clients_test.py
index edba1b870f0..519f1d237d6 100644
--- a/desktop/core/src/desktop/lib/raz/clients_test.py
+++ b/desktop/core/src/desktop/lib/raz/clients_test.py
@@ -14,18 +14,16 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import pytest
import sys
import unittest
+from unittest.mock import Mock, patch
+import pytest
from django.test import TestCase
+
from desktop.conf import RAZ
-from desktop.lib.raz.clients import S3RazClient, AdlsRazClient
+from desktop.lib.raz.clients import AdlsRazClient, S3RazClient
-if sys.version_info[0] > 2:
- from unittest.mock import patch, Mock
-else:
- from mock import patch, Mock
class S3RazClientLiveTest(TestCase):
@@ -42,7 +40,6 @@ def test_check_access_s3_list_buckets(self):
assert 'Signature=' in url
assert 'Expires=' in url
-
def test_check_acccess_s3_list_file(self):
# e.g. 'https://gethue-test.s3.amazonaws.com/data/query-hive-weblogs.csv?AWSAccessKeyId=AKIA23E77ZX2HVY76YGL&'
# 'Signature=3lhK%2BwtQ9Q2u5VDIqb4MEpoY3X4%3D&Expires=1617207304'
@@ -61,14 +58,14 @@ def test_check_acccess_s3_list_file(self):
assert 'Signature=' in url
assert 'Expires=' in url
-
def test_check_acccess_s3_list_file_no_access(self): pass
+
class AdlsRazClientTest(TestCase):
def setup_method(self, method):
self.username = 'csso_hueuser'
-
+
def test_check_rename_operation(self):
with patch('desktop.lib.raz.raz_client.requests.post') as requests_post:
with patch('desktop.lib.raz.raz_client.uuid.uuid4') as uuid:
@@ -87,7 +84,7 @@ def test_check_rename_operation(self):
check_access.assert_called_with(
headers={
- 'x-ms-version': '2019-12-12',
+ 'x-ms-version': '2019-12-12',
'x-ms-rename-source': '/data/user/csso_hueuser/rename_source_dir?some_random_sas_token'
},
method='PUT',
diff --git a/desktop/core/src/desktop/lib/raz/raz_client.py b/desktop/core/src/desktop/lib/raz/raz_client.py
index ed8671c92de..4c6abc96b6e 100644
--- a/desktop/core/src/desktop/lib/raz/raz_client.py
+++ b/desktop/core/src/desktop/lib/raz/raz_client.py
@@ -16,28 +16,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import base64
-import json
-import logging
import sys
+import json
import uuid
+import base64
+import logging
+from urllib.parse import unquote as lib_urlunquote, urlparse as lib_urlparse
import requests
import requests_kerberos
-from desktop.conf import AUTH_USERNAME
+import desktop.lib.raz.signer_protos_pb2 as raz_signer
from desktop.lib.exceptions_renderable import PopupException
from desktop.lib.sdxaas.knox_jwt import fetch_jwt
-import desktop.lib.raz.signer_protos_pb2 as raz_signer
-
-if sys.version_info[0] > 2:
- from urllib.parse import urlparse as lib_urlparse, unquote as lib_urlunquote
-else:
- from urlparse import urlparse as lib_urlparse
- from urllib import unquote as lib_urlunquote
-
-
LOG = logging.getLogger()
@@ -266,8 +258,8 @@ def _make_s3_request(self, request_data, request_headers, method, params, header
(method, headers, allparams, endpoint, resource_path, data)
)
- # Raz signed request proto call expects data as bytes instead of str for Py3.
- if sys.version_info[0] > 2 and data is not None and not isinstance(data, bytes):
+ # Raz signed request proto call expects data as bytes instead of str.
+ if data is not None and not isinstance(data, bytes):
data = data.encode()
raz_req = raz_signer.SignRequestProto(
@@ -282,10 +274,7 @@ def _make_s3_request(self, request_data, request_headers, method, params, header
time_offset=0
)
raz_req_serialized = raz_req.SerializeToString()
- if sys.version_info[0] > 2:
- signed_request = base64.b64encode(raz_req_serialized).decode('utf-8')
- else:
- signed_request = base64.b64encode(raz_req_serialized)
+ signed_request = base64.b64encode(raz_req_serialized).decode('utf-8')
request_headers["Accept-Encoding"] = "gzip,deflate"
request_data["context"] = {
diff --git a/desktop/core/src/desktop/lib/rest/http_client.py b/desktop/core/src/desktop/lib/rest/http_client.py
index e06d82ef85b..924cf9d953f 100644
--- a/desktop/core/src/desktop/lib/rest/http_client.py
+++ b/desktop/core/src/desktop/lib/rest/http_client.py
@@ -14,33 +14,23 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from future import standard_library
-standard_library.install_aliases()
-from builtins import object
import logging
import posixpath
-import requests
import threading
-import sys
+import urllib.error
+import urllib.request
+from urllib.parse import quote as urllib_quote, urlparse as lib_urlparse
+import requests
from django.utils.encoding import iri_to_uri, smart_str
from django.utils.http import urlencode
-
from requests import exceptions
from requests.auth import AuthBase, HTTPBasicAuth, HTTPDigestAuth
-from requests_kerberos import HTTPKerberosAuth, REQUIRED, OPTIONAL, DISABLED
+from requests_kerberos import DISABLED, OPTIONAL, REQUIRED, HTTPKerberosAuth
from urllib3.contrib import pyopenssl
from desktop import conf
-if sys.version_info[0] > 2:
- import urllib.request, urllib.error
- from urllib.parse import quote as urllib_quote, urlparse as lib_urlparse
-else:
- from urllib import quote as urllib_quote
- from urlparse import urlparse as lib_urlparse
-
-
pyopenssl.DEFAULT_SSL_CIPHER_LIST = conf.SSL_CIPHER_LIST.get()
__docformat__ = "epytext"
diff --git a/desktop/core/src/desktop/lib/rest/resource.py b/desktop/core/src/desktop/lib/rest/resource.py
index cdd907899b9..92b150dcc7c 100644
--- a/desktop/core/src/desktop/lib/rest/resource.py
+++ b/desktop/core/src/desktop/lib/rest/resource.py
@@ -14,20 +14,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from builtins import object
-import logging
import math
-import posixpath
-import urllib
import time
+import urllib
+import logging
+import posixpath
+from builtins import object
from django.utils.encoding import iri_to_uri
from django.utils.http import urlencode
from desktop import conf
-from desktop.lib.i18n import smart_unicode
-from desktop.lib.apputil import WARN_LEVEL_CALL_DURATION_MS, INFO_LEVEL_CALL_DURATION_MS
-
+from desktop.lib.apputil import INFO_LEVEL_CALL_DURATION_MS, WARN_LEVEL_CALL_DURATION_MS
+from desktop.lib.i18n import smart_str
LOG = logging.getLogger()
@@ -118,13 +117,13 @@ def _invoke(self, method, relpath=None, params=None, data=None, headers=None, fi
log_length = conf.REST_RESPONSE_SIZE.get() != -1 and conf.REST_RESPONSE_SIZE.get() if log_response else 0
duration = time.time() - start_time
try:
- req_data = smart_unicode(data, errors='replace')
- resp_content = smart_unicode(resp.content, errors='replace') if resp and resp.content else ''
+ req_data = smart_str(data, errors='replace')
+ resp_content = smart_str(resp.content, errors='replace') if resp and resp.content else ''
message = u'%s %s %s%s%s %s%s returned in %dms %s %s %s%s' % (
method,
type(self._client._session.auth) if self._client._session and self._client._session.auth else None,
self._client._base_url,
- smart_unicode(path, errors='replace'),
+ smart_str(path, errors='replace'),
iri_to_uri('?' + urlencode(params)) if params else '',
req_data[:log_length] if data else '',
log_length and len(req_data) > log_length and '...' or '' if data else '',
@@ -134,7 +133,7 @@ def _invoke(self, method, relpath=None, params=None, data=None, headers=None, fi
resp_content[:log_length] if resp else '',
log_length and len(resp_content) > log_length and '...' or '' if resp else ''
)
- except:
+ except Exception:
short_call_name = '%s %s' % (method, self._client._base_url)
LOG.exception('Error logging return call %s' % short_call_name)
message = '%s returned in %dms' % (short_call_name, duration)
@@ -144,7 +143,6 @@ def _invoke(self, method, relpath=None, params=None, data=None, headers=None, fi
return resp
-
def get(self, relpath=None, params=None, headers=None, clear_cookies=False):
"""
Invoke the GET method on a resource.
@@ -156,7 +154,6 @@ def get(self, relpath=None, params=None, headers=None, clear_cookies=False):
"""
return self.invoke("GET", relpath, params, headers=headers, allow_redirects=True, clear_cookies=clear_cookies)
-
def delete(self, relpath=None, params=None, headers=None, clear_cookies=False):
"""
Invoke the DELETE method on a resource.
@@ -169,7 +166,6 @@ def delete(self, relpath=None, params=None, headers=None, clear_cookies=False):
"""
return self.invoke("DELETE", relpath, params, headers=headers, clear_cookies=clear_cookies)
-
def post(self, relpath=None, params=None, data=None, contenttype=None, headers=None, files=None, allow_redirects=False,
clear_cookies=False, log_response=True
):
@@ -190,7 +186,6 @@ def post(self, relpath=None, params=None, data=None, contenttype=None, headers=N
allow_redirects=allow_redirects, clear_cookies=clear_cookies, log_response=log_response
)
-
def put(self, relpath=None, params=None, data=None, contenttype=None, allow_redirects=False, clear_cookies=False, headers=None):
"""
Invoke the PUT method on a resource.
@@ -241,7 +236,7 @@ def log_if_slow_call(duration, message, logger):
elif duration >= math.floor(INFO_LEVEL_CALL_DURATION_MS / 1000):
logger.info('SLOW: %.2f - %s' % (duration, message))
else:
- #Leave this as logging.debug and not logger.
- #Otherwise we never get these logging messages even with debug enabled.
- #Review this in the future to find out why.
+ # Leave this as logging.debug and not logger.
+ # Otherwise we never get these logging messages even with debug enabled.
+ # Review this in the future to find out why.
logging.debug(message)
diff --git a/desktop/core/src/desktop/lib/rest/resource_test.py b/desktop/core/src/desktop/lib/rest/resource_test.py
index 058b1542f22..4227fc35973 100644
--- a/desktop/core/src/desktop/lib/rest/resource_test.py
+++ b/desktop/core/src/desktop/lib/rest/resource_test.py
@@ -17,44 +17,31 @@
# limitations under the License.
import json
-import sys
+from unittest.mock import Mock, patch
-from desktop.lib.i18n import smart_unicode, smart_str
+from desktop.lib.i18n import smart_str
from desktop.lib.rest.resource import Resource
-if sys.version_info[0] > 2:
- from unittest.mock import patch, Mock
-else:
- from mock import patch, Mock
-
-
def test_concat_unicode_with_ascii_python2():
try:
u'The currency is: %s' % '€'
- if sys.version_info[0] == 2:
- raise Exception('Should have failed.')
except UnicodeDecodeError:
pass
- assert u'The currency is: €' == u'The currency is: %s' % smart_unicode('€')
-
+ assert u'The currency is: €' == u'The currency is: %s' % smart_str('€')
try:
u'%s' % '/user/domain/Джейкоб'
- if sys.version_info[0] == 2:
- raise Exception('Should have failed.')
except UnicodeDecodeError:
pass
try:
u'%s' % smart_str('/user/domain/Джейкоб')
- if sys.version_info[0] == 2:
- raise Exception('Should have failed.')
except UnicodeDecodeError:
pass
- u'%s' % smart_unicode('/user/domain/Джейкоб')
+ u'%s' % smart_str('/user/domain/Джейкоб')
def test_avoid_concat_unicode_with_ascii():
diff --git a/desktop/core/src/desktop/lib/scheduler/api.py b/desktop/core/src/desktop/lib/scheduler/api.py
index f2e14e37af5..1e83fcccb8d 100644
--- a/desktop/core/src/desktop/lib/scheduler/api.py
+++ b/desktop/core/src/desktop/lib/scheduler/api.py
@@ -104,4 +104,4 @@ def submit_schedule(request, doc_id):
force_template=True
).content
- return JsonResponse(popup.decode("utf-8") if sys.version_info[0] > 2 else popup, safe=False)
+ return JsonResponse(popup.decode("utf-8"), safe=False)
diff --git a/desktop/core/src/desktop/lib/scheduler/urls.py b/desktop/core/src/desktop/lib/scheduler/urls.py
index 5e806e20d73..6f02e86d117 100644
--- a/desktop/core/src/desktop/lib/scheduler/urls.py
+++ b/desktop/core/src/desktop/lib/scheduler/urls.py
@@ -17,13 +17,9 @@
import sys
-from desktop.lib.scheduler import api
-
-if sys.version_info[0] > 2:
- from django.urls import re_path
-else:
- from django.conf.urls import url as re_path
+from django.urls import re_path
+from desktop.lib.scheduler import api
urlpatterns = [
re_path(r'^api/schedule/new/?$', api.get_schedule, name='scheduler.api.new_schedule'),
diff --git a/desktop/core/src/desktop/lib/security_util.py b/desktop/core/src/desktop/lib/security_util.py
index a387f7f487d..3bc218caad0 100644
--- a/desktop/core/src/desktop/lib/security_util.py
+++ b/desktop/core/src/desktop/lib/security_util.py
@@ -16,13 +16,10 @@
# limitations under the License.
import re
-import socket
import sys
+import socket
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext as _
-else:
- from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
# Pattern to replace with hostname
HOSTNAME_PATTERN = '_HOST'
@@ -46,7 +43,8 @@ def get_components(principal):
"""
if not principal:
return None
- return re.split('[\/@]', str(principal))
+ return re.split(r'[\/@]', str(principal))
+
def replace_hostname_pattern(components, host):
fqdn = host
@@ -54,9 +52,11 @@ def replace_hostname_pattern(components, host):
fqdn = get_localhost_name()
return '%s/%s@%s' % (components[0], fqdn.lower(), components[2])
+
def get_localhost_name():
return socket.getfqdn()
+
def get_fqdn(hostname_or_ip):
# Get hostname
try:
diff --git a/desktop/core/src/desktop/lib/tasks/compress_files/compress_utils.py b/desktop/core/src/desktop/lib/tasks/compress_files/compress_utils.py
index e2da2fc4f35..a9ab9982739 100644
--- a/desktop/core/src/desktop/lib/tasks/compress_files/compress_utils.py
+++ b/desktop/core/src/desktop/lib/tasks/compress_files/compress_utils.py
@@ -15,31 +15,23 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from future import standard_library
-standard_library.install_aliases()
import json
-import sys
+import urllib.error
+import urllib.request
+from urllib.parse import quote as urllib_quote
-from django.urls import reverse
+from django.utils.translation import gettext as _
from desktop.conf import DEFAULT_USER
-from desktop.lib.paths import get_desktop_root, SAFE_CHARACTERS_URI_COMPONENTS, SAFE_CHARACTERS_URI
-
+from desktop.lib.paths import SAFE_CHARACTERS_URI, SAFE_CHARACTERS_URI_COMPONENTS, get_desktop_root
from notebook.connectors.base import Notebook
-if sys.version_info[0] > 2:
- import urllib.request, urllib.error
- from urllib.parse import quote as urllib_quote
- from django.utils.translation import gettext as _
-else:
- from urllib import quote as urllib_quote
- from django.utils.translation import ugettext as _
def compress_files_in_hdfs(request, file_names, upload_path, archive_name):
_upload_compress_files_script_to_hdfs(request.fs)
- files = [{"value": urllib_quote(upload_path.encode('utf-8'), SAFE_CHARACTERS_URI) + '/' + \
+ files = [{"value": urllib_quote(upload_path.encode('utf-8'), SAFE_CHARACTERS_URI) + '/' +
urllib_quote(file_name.encode('utf-8'), SAFE_CHARACTERS_URI)} for file_name in file_names]
files.append({'value': '/user/' + DEFAULT_USER.get() + '/common/compress_files_in_hdfs.sh'})
start_time = json.loads(request.POST.get('start_time', '-1'))
@@ -49,7 +41,7 @@ def compress_files_in_hdfs(request, file_names, upload_path, archive_name):
isManaged=True,
onSuccessUrl='/filebrowser/view=' + urllib_quote(upload_path.encode('utf-8'), safe=SAFE_CHARACTERS_URI_COMPONENTS)
)
-
+
shell_notebook.add_shell_snippet(
shell_command='compress_files_in_hdfs.sh',
arguments=[{'value': '-u=' + upload_path}, {'value': '-f=' + ','.join(file_names)}, {'value': '-n=' + archive_name}],
@@ -61,13 +53,14 @@ def compress_files_in_hdfs(request, file_names, upload_path, archive_name):
return shell_notebook.execute(request, batch=True)
+
def _upload_compress_files_script_to_hdfs(fs):
if not fs.exists('/user/' + DEFAULT_USER.get() + '/common/'):
fs.do_as_user(DEFAULT_USER.get(), fs.mkdir, '/user/' + DEFAULT_USER.get() + '/common/')
fs.do_as_user(DEFAULT_USER.get(), fs.chmod, '/user/' + DEFAULT_USER.get() + '/common/', 0o755)
if not fs.do_as_user(DEFAULT_USER.get(), fs.exists, '/user/' + DEFAULT_USER.get() + '/common/compress_files_in_hdfs.sh'):
- fs.do_as_user(DEFAULT_USER.get(), fs.copyFromLocal, get_desktop_root() + \
- '/core/src/desktop/lib/tasks/compress_files/compress_in_hdfs.sh',\
+ fs.do_as_user(DEFAULT_USER.get(), fs.copyFromLocal, get_desktop_root() +
+ '/core/src/desktop/lib/tasks/compress_files/compress_in_hdfs.sh',
'/user/' + DEFAULT_USER.get() + '/common/compress_files_in_hdfs.sh')
fs.do_as_user(DEFAULT_USER.get(), fs.chmod, '/user/' + DEFAULT_USER.get() + '/common/', 0o755)
diff --git a/desktop/core/src/desktop/lib/tasks/extract_archive/extract_utils.py b/desktop/core/src/desktop/lib/tasks/extract_archive/extract_utils.py
index 44718d2ae58..3b72ada800f 100644
--- a/desktop/core/src/desktop/lib/tasks/extract_archive/extract_utils.py
+++ b/desktop/core/src/desktop/lib/tasks/extract_archive/extract_utils.py
@@ -15,23 +15,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from future import standard_library
-standard_library.install_aliases()
-import json
import sys
-import urllib.request, urllib.parse, urllib.error
+import json
+import urllib.error
+import urllib.parse
+import urllib.request
from django.urls import reverse
+from django.utils.translation import gettext as _
from desktop.conf import DEFAULT_USER
-from desktop.lib.paths import get_desktop_root, SAFE_CHARACTERS_URI_COMPONENTS
-
+from desktop.lib.paths import SAFE_CHARACTERS_URI_COMPONENTS, get_desktop_root
from notebook.connectors.base import Notebook
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext as _
-else:
- from django.utils.translation import ugettext as _
def extract_archive_in_hdfs(request, upload_path, file_name):
_upload_extract_archive_script_to_hdfs(request.fs)
@@ -40,18 +36,21 @@ def extract_archive_in_hdfs(request, upload_path, file_name):
start_time = json.loads(request.POST.get('start_time', '-1'))
shell_notebook = Notebook(
- name=_('HDFS Extraction of %(upload_path)s/%(file_name)s') % {'upload_path': upload_path, 'file_name': file_name},
- isManaged=True,
- onSuccessUrl='/filebrowser/view=' + urllib.parse.quote(output_path.encode('utf-8'), safe=SAFE_CHARACTERS_URI_COMPONENTS)
+ name=_('HDFS Extraction of %(upload_path)s/%(file_name)s') % {'upload_path': upload_path, 'file_name': file_name},
+ isManaged=True,
+ onSuccessUrl='/filebrowser/view=' + urllib.parse.quote(output_path.encode('utf-8'), safe=SAFE_CHARACTERS_URI_COMPONENTS),
)
shell_notebook.add_shell_snippet(
- shell_command='extract_archive_in_hdfs.sh',
- arguments=[{'value': '-u=' + upload_path}, {'value': '-f=' + file_name}, {'value': '-o=' + output_path}],
- archives=[],
- files=[{'value': '/user/' + DEFAULT_USER.get() + '/common/extract_archive_in_hdfs.sh'}, {"value": upload_path + '/' + urllib.parse.quote(file_name)}],
- env_var=[{'value': 'HADOOP_USER_NAME=${wf:user()}'}],
- last_executed=start_time
+ shell_command='extract_archive_in_hdfs.sh',
+ arguments=[{'value': '-u=' + upload_path}, {'value': '-f=' + file_name}, {'value': '-o=' + output_path}],
+ archives=[],
+ files=[
+ {'value': '/user/' + DEFAULT_USER.get() + '/common/extract_archive_in_hdfs.sh'},
+ {"value": upload_path + '/' + urllib.parse.quote(file_name)},
+ ],
+ env_var=[{'value': 'HADOOP_USER_NAME=${wf:user()}'}],
+ last_executed=start_time,
)
return shell_notebook.execute(request, batch=True)
@@ -63,6 +62,10 @@ def _upload_extract_archive_script_to_hdfs(fs):
fs.do_as_user(DEFAULT_USER.get(), fs.chmod, '/user/' + DEFAULT_USER.get() + '/common/', 0o755)
if not fs.do_as_user(DEFAULT_USER.get(), fs.exists, '/user/' + DEFAULT_USER.get() + '/common/extract_archive_in_hdfs.sh'):
- fs.do_as_user(DEFAULT_USER.get(), fs.copyFromLocal, get_desktop_root() + '/core/src/desktop/lib/tasks/extract_archive/extract_in_hdfs.sh',
- '/user/' + DEFAULT_USER.get() + '/common/extract_archive_in_hdfs.sh')
+ fs.do_as_user(
+ DEFAULT_USER.get(),
+ fs.copyFromLocal,
+ get_desktop_root() + '/core/src/desktop/lib/tasks/extract_archive/extract_in_hdfs.sh',
+ '/user/' + DEFAULT_USER.get() + '/common/extract_archive_in_hdfs.sh',
+ )
fs.do_as_user(DEFAULT_USER.get(), fs.chmod, '/user/' + DEFAULT_USER.get() + '/common/', 0o755)
diff --git a/desktop/core/src/desktop/lib/thread_util.py b/desktop/core/src/desktop/lib/thread_util.py
index 9d4d8af3996..6f8c47e4e9a 100644
--- a/desktop/core/src/desktop/lib/thread_util.py
+++ b/desktop/core/src/desktop/lib/thread_util.py
@@ -15,19 +15,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from __future__ import print_function
-from future import standard_library
-standard_library.install_aliases()
-import logging
-import socket
import sys
+import socket
+import logging
import threading
import traceback
-
-if sys.version_info[0] > 2:
- from io import StringIO as string_io
-else:
- from cStringIO import StringIO as string_io
+from io import StringIO as string_io
LOG = logging.getLogger()
diff --git a/desktop/core/src/desktop/lib/thread_util_test.py b/desktop/core/src/desktop/lib/thread_util_test.py
index 77929cfa694..911dcfb02c6 100644
--- a/desktop/core/src/desktop/lib/thread_util_test.py
+++ b/desktop/core/src/desktop/lib/thread_util_test.py
@@ -15,18 +15,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from future import standard_library
-standard_library.install_aliases()
-import sys
-import threading
import time
+import threading
+from io import StringIO as string_io
from desktop.lib.thread_util import dump_traceback
-if sys.version_info[0] > 2:
- from io import StringIO as string_io
-else:
- from cStringIO import StringIO as string_io
def test_dump_traceback():
started = threading.Event()
diff --git a/desktop/core/src/desktop/lib/thrift_/http_client.py b/desktop/core/src/desktop/lib/thrift_/http_client.py
index a065a50947b..6c085eda79e 100644
--- a/desktop/core/src/desktop/lib/thrift_/http_client.py
+++ b/desktop/core/src/desktop/lib/thrift_/http_client.py
@@ -16,21 +16,14 @@
# limitations under the License.
#
-from future import standard_library
-standard_library.install_aliases()
import logging
-import sys
+from io import BytesIO as buffer_writer
from thrift.transport.TTransport import *
from desktop.lib.rest.http_client import HttpClient
from desktop.lib.rest.resource import Resource
-if sys.version_info[0] > 2:
- from io import BytesIO as buffer_writer
-else:
- from cStringIO import StringIO as buffer_writer
-
LOG = logging.getLogger()
diff --git a/desktop/core/src/desktop/lib/thrift_sasl.py b/desktop/core/src/desktop/lib/thrift_sasl.py
index 5ff63ae50ab..bd4b5441cab 100644
--- a/desktop/core/src/desktop/lib/thrift_sasl.py
+++ b/desktop/core/src/desktop/lib/thrift_sasl.py
@@ -17,23 +17,19 @@
# under the License.
#
""" SASL transports for Thrift. """
-from __future__ import absolute_import
-from future import standard_library
-standard_library.install_aliases()
-from thrift.transport import TTransport
-from thrift.transport.TTransport import *
-from thrift.protocol import TBinaryProtocol
-import struct
import sys
+import struct
# TODO: Check whether the following distinction is necessary. Does not appear to
# break anything when `io.BytesIO` is used everywhere, but there may be some edge
# cases where things break down.
-if sys.version_info[0] > 2:
- from io import BytesIO as string_io
-else:
- from cStringIO import StringIO as string_io
+from io import BytesIO as string_io
+
+from thrift.protocol import TBinaryProtocol
+from thrift.transport import TTransport
+from thrift.transport.TTransport import *
+
class TSaslClientTransport(TTransportBase, CReadableTransport):
START = 1
@@ -119,12 +115,12 @@ def flush(self):
# If the length doesn't change, then we must be using a QOP
# of auth and we should no longer call sasl.encode(), otherwise
# we encode every time.
- if self.encode == None:
+ if self.encode is None:
success, encoded = self.sasl.encode(buffer)
if not success:
raise TTransportException(type=TTransportException.UNKNOWN,
message=self.sasl.getError())
- if (len(encoded)==len(buffer)):
+ if (len(encoded) == len(buffer)):
self.encode = False
self._flushPlain(buffer)
else:
diff --git a/desktop/core/src/desktop/lib/thrift_util.py b/desktop/core/src/desktop/lib/thrift_util.py
index 54708e90bcc..e6ff6b66ae2 100644
--- a/desktop/core/src/desktop/lib/thrift_util.py
+++ b/desktop/core/src/desktop/lib/thrift_util.py
@@ -30,7 +30,8 @@
from builtins import map, object, range
from django.conf import settings
-from past.builtins import basestring
+from django.utils.translation import gettext as _
+from past.builtins import basestring, long
from thrift.protocol.TBinaryProtocol import TBinaryProtocol
from thrift.protocol.TMultiplexedProtocol import TMultiplexedProtocol
from thrift.Thrift import TApplicationException, TType
@@ -45,13 +46,6 @@
from desktop.lib.thrift_.TSSLSocketWithWildcardSAN import TSSLSocketWithWildcardSAN
from desktop.lib.thrift_sasl import TSaslClientTransport
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext as _
- from past.builtins import long
-else:
- from django.utils.translation import ugettext as _
-
-
LOG = logging.getLogger()
@@ -591,12 +585,8 @@ def set_timeout(self, timeout_seconds):
def _unpack_guid_secret_in_handle(str_args):
if 'operationHandle' in str_args or 'sessionHandle' in str_args:
- if sys.version_info[0] > 2:
- guid = re.search('guid=(b".*"), secret', str_args) or re.search('guid=(b\'.*\'), secret', str_args)
- secret = re.search(r'secret=(b".+?")\)', str_args) or re.search('secret=(b\'.+?\')\\)', str_args)
- else:
- secret = re.search('secret=(".*"), guid', str_args) or re.search('secret=(\'.*\'), guid', str_args)
- guid = re.search(r'guid=(".*")\)\)', str_args) or re.search('guid=(\'.*\')\\)\\)', str_args)
+ guid = re.search('guid=(b".*"), secret', str_args) or re.search('guid=(b\'.*\'), secret', str_args)
+ secret = re.search(r'secret=(b".+?")\)', str_args) or re.search('secret=(b\'.+?\')\\)', str_args)
if secret and guid:
try:
@@ -616,7 +606,7 @@ def unpack_guid(guid):
def unpack_guid_base64(guid):
- decoded_guid = base64.b64decode(guid) if sys.version_info[0] > 2 else base64.decodestring(guid)
+ decoded_guid = base64.b64decode(guid)
return "%016x:%016x" % struct.unpack(b"QQ", decoded_guid)
diff --git a/desktop/core/src/desktop/lib/thrift_util_test.py b/desktop/core/src/desktop/lib/thrift_util_test.py
index 6031be0aa88..8ffb37a86dd 100644
--- a/desktop/core/src/desktop/lib/thrift_util_test.py
+++ b/desktop/core/src/desktop/lib/thrift_util_test.py
@@ -14,41 +14,34 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from __future__ import absolute_import
-from builtins import range
-from builtins import object
-import logging
import os
-import pytest
-import socket
import sys
-import threading
import time
+import socket
+import logging
import unittest
+import threading
+from unittest.mock import Mock, patch
-if sys.version_info[0] > 2:
- from unittest.mock import patch, Mock
-else:
- from mock import patch, Mock
+import pytest
gen_py_path = os.path.abspath(os.path.join(os.path.dirname(__file__), "gen-py"))
-if not gen_py_path in sys.path:
+if gen_py_path not in sys.path:
sys.path.insert(1, gen_py_path)
-from djangothrift_test_gen import TestService
-from djangothrift_test_gen.ttypes import TestStruct, TestNesting, TestEnum, TestManyTypes
from django.test import TestCase
+from djangothrift_test_gen import TestService
+from djangothrift_test_gen.ttypes import TestEnum, TestManyTypes, TestNesting, TestStruct
from thrift.protocol.TBinaryProtocol import TBinaryProtocolFactory
from thrift.server import TServer
from thrift.transport import TSocket
from thrift.transport.TTransport import TBufferedTransportFactory, TTransportException
-from desktop.lib import python_util, thrift_util
-from desktop.lib.thrift_util import jsonable2thrift, thrift2json, _unpack_guid_secret_in_handle
+from desktop.auth.backend import create_user, ensure_has_a_group, find_or_create_user, rewrite_user
from desktop.conf import USE_THRIFT_HTTP_JWT
+from desktop.lib import python_util, thrift_util
from desktop.lib.django_test_util import make_logged_in_client
-from desktop.auth.backend import rewrite_user, find_or_create_user, ensure_has_a_group, create_user
-
+from desktop.lib.thrift_util import _unpack_guid_secret_in_handle, jsonable2thrift, thrift2json
from useradmin.models import User
LOG = logging.getLogger()
@@ -90,7 +83,7 @@ def start_server_process(self):
TBufferedTransportFactory(),
TBinaryProtocolFactory())
server.serve()
- except:
+ except Exception:
LOG.exception('failed to start thrift server')
sys.exit(1)
@@ -105,7 +98,7 @@ def _ensure_online(self):
ping_s.connect(('localhost', self.port))
ping_s.close()
return
- except:
+ except Exception:
LOG.exception('failed to connect to child server')
_, status = os.waitpid(self.pid, os.WNOHANG)
if status != 0:
@@ -173,6 +166,7 @@ def run(self):
racer.join()
assert 0 == len(racer.errors)
+
class ThriftUtilTest(TestCase):
def test_simpler_string(self):
struct = TestStruct()
@@ -224,38 +218,21 @@ def test_fixup_enums(self):
assert struct1.myenumAsString == 'ENUM_ONE'
def test_unpack_guid_secret_in_handle(self):
- if sys.version_info[0] > 2:
- hive_handle = ("(TGetTablesReq(sessionHandle=TSessionHandle(sessionId=THandleIdentifier(guid=%s, secret=%s)), catalogName=None,"
- " schemaName='default', tableName='customers', tableTypes=None),"
- ")") % (str(b'N\xc5\xed\x14k\xbeI\xda\xb9\x14\xe7\xf2\x9a\xb7\xf0\xa5'), str(b']s(\xb5\xf6ZO\x03\x99\x955\xacl\xb4\x98\xae'))
-
- assert (_unpack_guid_secret_in_handle(hive_handle) == ("(TGetTablesReq(sessionHandle=TSessionHandle(sessionId="
- "THandleIdentifier(guid=da49be6b14edc54e:a5f0b79af2e714b9, secret=034f5af6b528735d:ae98b46cac359599)), catalogName=None, "
- "schemaName=\'default\', tableName=\'customers\', tableTypes=None),)"))
-
- impala_handle = ("(TExecuteStatementReq(sessionHandle=TSessionHandle(sessionId=THandleIdentifier(guid=%s, secret=%s)), "
- "statement=b\'USE `default`\', confOverlay={\'QUERY_TIMEOUT_S\': \'300\'}, runAsync=False)"
- ",)") % (str(b'\xc4\xccnI\xf1\xbdJ\xc3\xb2\n\xd5[9\xe1Mr'), str(b'\xb0\x9d\xfd\x82\x94%L\xae\x9ch$f=\xfa{\xd0'))
-
- assert (_unpack_guid_secret_in_handle(impala_handle) == ("(TExecuteStatementReq(sessionHandle=TSessionHandle("
- "sessionId=THandleIdentifier(guid=c34abdf1496eccc4:724de1395bd50ab2, secret=ae4c259482fd9db0:d07bfa3d6624689c)), "
- "statement=b\'USE `default`\', confOverlay={\'QUERY_TIMEOUT_S\': \'300\'}, runAsync=False),)"))
- else:
- hive_handle = ("(TExecuteStatementReq(confOverlay={}, sessionHandle=TSessionHandle(sessionId=THandleIdentifier("
- "secret=\'\x1aOYj\xf3\x86M\x95\xbb\xc8\xe9/;\xb0{9\', guid=\'\x86\xa6$\xb2\xb8\xdaF\xbd\xbd\xf5\xc5\xf4\xcb\x96\x03<\')), "
- 'runAsync=True, statement="SELECT \'Hello World!\'"),)')
-
- assert (_unpack_guid_secret_in_handle(hive_handle) == ("(TExecuteStatementReq(confOverlay={}, sessionHandle=TSessionHandle("
- "sessionId=THandleIdentifier(secret=954d86f36a594f1a:397bb03b2fe9c8bb, guid=bd46dab8b224a686:3c0396cbf4c5f5bd)), runAsync=True, "
- 'statement="SELECT \'Hello World!\'"),)'))
-
- impala_handle = ("(TGetTablesReq(schemaName=u\'default\', sessionHandle=TSessionHandle(sessionId=THandleIdentifier(secret="
- "\'\x7f\x98\x97s\xe1\xa8G\xf4\x8a\x8a\\r\x0e6\xc2\xee\xf0\', guid=\'\xfa\xb0/\x04 \xfeDX\x99\xfcq\xff2\x07\x02\xfe\')), "
- "tableName=u\'customers\', tableTypes=None, catalogName=None),)")
-
- assert (_unpack_guid_secret_in_handle(impala_handle) == ("(TGetTablesReq(schemaName=u\'default\', sessionHandle="
- "TSessionHandle(sessionId=THandleIdentifier(secret=f447a8e17397987f:f0eec2360e0d8a8a, guid=5844fe20042fb0fa:fe020732ff71fc99)),"
- " tableName=u\'customers\', tableTypes=None, catalogName=None),)"))
+ hive_handle = ("(TGetTablesReq(sessionHandle=TSessionHandle(sessionId=THandleIdentifier(guid=%s, secret=%s)), catalogName=None,"
+ " schemaName='default', tableName='customers', tableTypes=None),"
+ ")") % (str(b'N\xc5\xed\x14k\xbeI\xda\xb9\x14\xe7\xf2\x9a\xb7\xf0\xa5'), str(b']s(\xb5\xf6ZO\x03\x99\x955\xacl\xb4\x98\xae'))
+
+ assert (_unpack_guid_secret_in_handle(hive_handle) == ("(TGetTablesReq(sessionHandle=TSessionHandle(sessionId="
+ "THandleIdentifier(guid=da49be6b14edc54e:a5f0b79af2e714b9, secret=034f5af6b528735d:ae98b46cac359599)), catalogName=None, "
+ "schemaName=\'default\', tableName=\'customers\', tableTypes=None),)"))
+
+ impala_handle = ("(TExecuteStatementReq(sessionHandle=TSessionHandle(sessionId=THandleIdentifier(guid=%s, secret=%s)), "
+ "statement=b\'USE `default`\', confOverlay={\'QUERY_TIMEOUT_S\': \'300\'}, runAsync=False)"
+ ",)") % (str(b'\xc4\xccnI\xf1\xbdJ\xc3\xb2\n\xd5[9\xe1Mr'), str(b'\xb0\x9d\xfd\x82\x94%L\xae\x9ch$f=\xfa{\xd0'))
+
+ assert (_unpack_guid_secret_in_handle(impala_handle) == ("(TExecuteStatementReq(sessionHandle=TSessionHandle("
+ "sessionId=THandleIdentifier(guid=c34abdf1496eccc4:724de1395bd50ab2, secret=ae4c259482fd9db0:d07bfa3d6624689c)), "
+ "statement=b\'USE `default`\', confOverlay={\'QUERY_TIMEOUT_S\': \'300\'}, runAsync=False),)"))
# Following should be added to test, but fails because eval doesn't handle null bytes
# impala_handle = ("(TGetTablesReq(schemaName=u\'default\', sessionHandle=TSessionHandle(sessionId=THandleIdentifier(secret="
@@ -266,6 +243,7 @@ def test_unpack_guid_secret_in_handle(self):
# "sessionHandle=TSessionHandle(sessionId=THandleIdentifier(secret=f447a8e17397987f:f0eec2360e0d8a8a, "
# "guid=9144f53015fa33d2:0091efd700000000)), tableName=u\'customers\', tableTypes=None, catalogName=None),)"))
+
class TestJsonable2Thrift(TestCase):
"""
Tests a handful of permutations of jsonable2thrift.
@@ -353,7 +331,6 @@ def test_wrapper_no_retry(self):
client.my_call()
# Could check output for "Not retrying thrift call my_call due to socket timeout"
-
def test_wrapper_with_retry(self):
wrapped_client, transport = Mock(), Mock()
wrapped_client.my_call = Mock(
@@ -375,7 +352,6 @@ def setup_method(self):
self.client = make_logged_in_client(username="test_user", groupname="default", recreate=True, is_superuser=False)
self.user = rewrite_user(User.objects.get(username="test_user"))
-
def test_jwt_thrift(self):
with patch('desktop.lib.thrift_util.TBinaryProtocol'):
with patch('desktop.lib.thrift_util.TBufferedTransport'):
@@ -402,7 +378,6 @@ def test_jwt_thrift(self):
finally:
reset()
-
def test_jwt_thrift_exceptions(self):
with patch('desktop.lib.thrift_util.TBinaryProtocol'):
with patch('desktop.lib.thrift_util.TBufferedTransport'):
diff --git a/desktop/core/src/desktop/lib/vcs/apis/base_api.py b/desktop/core/src/desktop/lib/vcs/apis/base_api.py
index 96dc1b0f640..cb5aa96e854 100644
--- a/desktop/core/src/desktop/lib/vcs/apis/base_api.py
+++ b/desktop/core/src/desktop/lib/vcs/apis/base_api.py
@@ -15,18 +15,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from builtins import object
-import logging
import sys
+import logging
+from builtins import object
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext as _
-else:
- from django.utils.translation import ugettext as _
+from django.utils.translation import gettext as _
from desktop.lib.exceptions_renderable import PopupException
-
LOG = logging.getLogger()
GITHUB_OFFICIAL = 'github'
@@ -52,4 +48,4 @@ def __init__(self):
def authorize(self, request): return {}
- def contents(self, request): return {}
\ No newline at end of file
+ def contents(self, request): return {}
diff --git a/desktop/core/src/desktop/lib/vcs/apis/github_api.py b/desktop/core/src/desktop/lib/vcs/apis/github_api.py
index 09cece909f3..91f5bbcd008 100644
--- a/desktop/core/src/desktop/lib/vcs/apis/github_api.py
+++ b/desktop/core/src/desktop/lib/vcs/apis/github_api.py
@@ -15,23 +15,18 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import json
import sys
+import json
from django.http import HttpResponseBadRequest, HttpResponseRedirect
+from django.utils.translation import gettext as _
from django.views.decorators.http import require_GET
from desktop.lib.django_util import JsonResponse
-
-from desktop.lib.vcs.github_client import GithubClient
from desktop.lib.vcs.apis.base_api import Api
+from desktop.lib.vcs.github_client import GithubClient
from desktop.lib.view_util import is_ajax
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext as _
-else:
- from django.utils.translation import ugettext as _
-
class GithubApi(Api):
@@ -74,7 +69,7 @@ def authorize(self, request):
request.session['github_callback_fetch'] = request.GET.get('fetchURL')
response = {
'status': -1,
- 'auth_url':auth_url
+ 'auth_url': auth_url
}
if is_ajax(request):
return JsonResponse(response)
@@ -88,4 +83,4 @@ def callback(self, request):
request.session['github_access_token'] = GithubClient.get_access_token(session_code)
return HttpResponseRedirect(redirect_base + "0&github_fetch=" + request.session['github_callback_fetch'])
else:
- return HttpResponseRedirect(redirect_base + "-1&github_fetch=" + request.session['github_callback_fetch'])
\ No newline at end of file
+ return HttpResponseRedirect(redirect_base + "-1&github_fetch=" + request.session['github_callback_fetch'])
diff --git a/desktop/core/src/desktop/lib/vcs/apis/github_readonly_api.py b/desktop/core/src/desktop/lib/vcs/apis/github_readonly_api.py
index f0f6df7b191..a5e85a34f2c 100644
--- a/desktop/core/src/desktop/lib/vcs/apis/github_readonly_api.py
+++ b/desktop/core/src/desktop/lib/vcs/apis/github_readonly_api.py
@@ -15,33 +15,23 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-
-from future import standard_library
-standard_library.install_aliases()
-import binascii
-import logging
import re
-import sys
+import logging
+import binascii
+import urllib.error
+import urllib.request
+from urllib.parse import unquote as urllib_unquote, urlsplit as lib_urlsplit, urlunsplit as lib_urlunsplit
from django.http import HttpResponseBadRequest
+from django.utils.translation import gettext as _
+from desktop.conf import VCS
from desktop.lib.django_util import JsonResponse
-from desktop.lib.rest.http_client import HttpClient, RestException
from desktop.lib.rest import resource
-
-from desktop.conf import VCS
-from desktop.lib.vcs.apis.base_api import Api, GIT_READ_ONLY
+from desktop.lib.rest.http_client import HttpClient, RestException
+from desktop.lib.vcs.apis.base_api import GIT_READ_ONLY, Api
from desktop.lib.vcs.github_client import GithubClientException
-if sys.version_info[0] > 2:
- import urllib.request, urllib.error
- from urllib.parse import unquote as urllib_unquote, urlsplit as lib_urlsplit, urlunsplit as lib_urlunsplit
- from django.utils.translation import gettext as _
-else:
- from urllib import unquote as urllib_unquote
- from urlparse import urlsplit as lib_urlsplit, urlunsplit as lib_urlunsplit
- from django.utils.translation import ugettext as _
-
LOG = logging.getLogger()
@@ -51,8 +41,8 @@ class GithubReadOnlyApi(Api):
"""
OWNER_RE = "(?P[A-Za-z0-9](?:-?[A-Za-z0-9]){0,38})"
- REPO_RE = "(?P[\w\.@\:\-~]+)"
- BRANCH_RE = "(?P[\w\.@\:\-~]+)"
+ REPO_RE = r"(?P[\w\.@\:\-~]+)"
+ BRANCH_RE = r"(?P[\w\.@\:\-~]+)"
DEFAULT_SCOPES = ['repo', 'user']
@@ -134,4 +124,4 @@ def _massage_content(blob):
'path': file.get('path', '')
}
response.append(file)
- return response
\ No newline at end of file
+ return response
diff --git a/desktop/core/src/desktop/lib/vcs/github_client.py b/desktop/core/src/desktop/lib/vcs/github_client.py
index 33fd2ddae82..754c8115faa 100644
--- a/desktop/core/src/desktop/lib/vcs/github_client.py
+++ b/desktop/core/src/desktop/lib/vcs/github_client.py
@@ -16,27 +16,19 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from future import standard_library
-standard_library.install_aliases()
-from builtins import object
-import binascii
+import re
import json
import logging
-import re
-import sys
-
-from desktop.lib.rest.http_client import HttpClient, RestException
-from desktop.lib.rest import resource
+import binascii
+import urllib.error
+import urllib.request
+from urllib.parse import unquote as urllib_unquote
from desktop.conf import VCS
+from desktop.lib.rest import resource
+from desktop.lib.rest.http_client import HttpClient, RestException
from desktop.lib.vcs.apis.base_api import GITHUB_OFFICIAL
-if sys.version_info[0] > 2:
- import urllib.request, urllib.error
- from urllib.parse import unquote as urllib_unquote
-else:
- from urllib import unquote as urllib_unquote
-
LOG = logging.getLogger()
@@ -50,13 +42,12 @@ class GithubClient(object):
"""
OWNER_RE = "(?P[A-Za-z0-9](?:-?[A-Za-z0-9]){0,38})"
- REPO_RE = "(?P[\w\.@\:\-~]+)"
- BRANCH_RE = "(?P[\w\.@\:\-~]+)"
+ REPO_RE = r"(?P[\w\.@\:\-~]+)"
+ BRANCH_RE = r"(?P[\w\.@\:\-~]+)"
FILEPATH_RE = "(?P.+)"
DEFAULT_SCOPES = ['repo', 'user']
-
def __init__(self, **options):
self._github_base_url = options.get('remote_url', VCS[GITHUB_OFFICIAL].REMOTE_URL.get()).strip('/')
self._api_url = options.get('api_url', VCS[GITHUB_OFFICIAL].API_URL.get()).strip('/')
@@ -71,7 +62,6 @@ def __init__(self, **options):
# TODO: Redact access_token from logs
self.__params = ()
-
@classmethod
def get_authorization_url(cls, **options):
"""
@@ -83,7 +73,6 @@ def get_authorization_url(cls, **options):
scopes = ','.join(scopes_list)
return '%s/login/oauth/authorize?scope=%s&client_id=%s' % (remote_url, scopes, client_id)
-
@classmethod
def get_access_token(cls, session_code, **options):
remote_url = options.get('remote_url', VCS[GITHUB_OFFICIAL].REMOTE_URL.get()).strip('/')
@@ -99,7 +88,7 @@ def get_access_token(cls, session_code, **options):
'code': session_code
}
headers = {
- 'content-type':'application/json',
+ 'content-type': 'application/json',
'Accept': 'application/json'
}
response = root.post('login/oauth/access_token', headers=headers, data=json.dumps(data))
@@ -110,7 +99,6 @@ def get_access_token(cls, session_code, **options):
except KeyError:
raise GithubClientException('Failed to find access_token in GitHub oAuth response')
-
@classmethod
def is_authenticated(cls, access_token, **options):
api_url = options.get('api_url', VCS[GITHUB_OFFICIAL].API_URL.get()).strip('/')
@@ -126,28 +114,24 @@ def is_authenticated(cls, access_token, **options):
except RestException:
return False
-
@classmethod
def _get_json(cls, response):
- if type(response) != dict:
+ if type(response) is not dict:
try:
response = json.loads(response)
except ValueError:
raise GithubClientException('GitHub API did not return JSON response')
return response
-
@property
def github_url_regex(self):
return re.compile('%s/%s/%s/blob/%s/%s' % (self._github_base_url, self.OWNER_RE, self.REPO_RE, self.BRANCH_RE, self.FILEPATH_RE))
-
def _clean_path(self, filepath):
cleaned_path = filepath.strip('/')
cleaned_path = urllib_unquote(cleaned_path)
return cleaned_path
-
def parse_github_url(self, url):
"""
Given a base URL to a Github repository, return a tuple of the owner, repo, branch, and filepath
@@ -160,7 +144,6 @@ def parse_github_url(self, url):
else:
raise ValueError('GitHub URL is not formatted correctly: %s' % url)
-
def get_file_contents(self, owner, repo, filepath, branch='master'):
filepath = self._clean_path(filepath)
try:
@@ -173,7 +156,6 @@ def get_file_contents(self, owner, repo, filepath, branch='master'):
except KeyError as e:
raise GithubClientException('Failed to find expected content object in blob object: %s' % e)
-
def get_sha(self, owner, repo, filepath, branch='master'):
"""
Return the sha for a given filepath by recursively calling Trees API for each level of the path
@@ -193,7 +175,6 @@ def get_sha(self, owner, repo, filepath, branch='master'):
return sha
-
def get_tree(self, owner, repo, sha='master', recursive=True):
"""
GET /repos/:owner/:repo/git/trees/:sha
@@ -210,7 +191,6 @@ def get_tree(self, owner, repo, sha='master', recursive=True):
except RestException as e:
raise GithubClientException('Could not find GitHub object, check owner, repo and filepath or permissions: %s' % e)
-
def get_blob(self, owner, repo, sha):
"""
GET /repos/:owner/:repo/git/blobs/:sha
diff --git a/desktop/core/src/desktop/lib/view_util.py b/desktop/core/src/desktop/lib/view_util.py
index 48abb7bdee1..3b20fd31798 100644
--- a/desktop/core/src/desktop/lib/view_util.py
+++ b/desktop/core/src/desktop/lib/view_util.py
@@ -15,18 +15,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utilities for views (text and number formatting, etc)"""
-from __future__ import division
-import datetime
-import logging
import math
-import sys
+import logging
+import datetime
from django.urls import reverse
from hadoop.fs.hadoopfs import Hdfs
-
LOG = logging.getLogger()
@@ -44,7 +41,8 @@ def big_filesizeformat(bytes):
index = int(math.floor(math.log(bytes, 1024)))
index = min(len(units) - 1, index)
- return("%.1f %s" % ((bytes / math.pow(1024, index)), units[index]))
+ return ("%.1f %s" % ((bytes / math.pow(1024, index)), units[index]))
+
def format_time_diff(start=None, end=None):
"""
@@ -69,6 +67,7 @@ def format_time_diff(start=None, end=None):
output.append("%ds" % seconds)
return ":".join(output)
+
def format_duration_in_millis(duration=0):
"""
formats the difference between two times in millis as Xd:Xh:Xm:Xs
@@ -90,14 +89,12 @@ def format_duration_in_millis(duration=0):
output.append("%ds" % seconds)
return ":".join(output)
-def is_ajax(request):
- if sys.version_info[0] > 2:
- _is_ajax = request.headers.get('x-requested-with') == 'XMLHttpRequest' or request.path.startswith('/api/')
- else:
- _is_ajax = request.is_ajax()
+def is_ajax(request):
+ _is_ajax = request.headers.get('x-requested-with') == 'XMLHttpRequest' or request.path.startswith('/api/')
return _is_ajax
+
def location_to_url(location, strict=True, is_embeddable=False):
"""
If possible, returns a file browser URL to the location.
diff --git a/desktop/core/src/desktop/lib/wsgiserver.py b/desktop/core/src/desktop/lib/wsgiserver.py
index b5f8f45129b..378156e1168 100644
--- a/desktop/core/src/desktop/lib/wsgiserver.py
+++ b/desktop/core/src/desktop/lib/wsgiserver.py
@@ -136,14 +136,10 @@ def my_crazy_app(environ, start_response):
import errno
import logging
-if sys.version_info[0] > 2:
- from io import StringIO as string_io
- import urllib.request, urllib.error
- from urllib.parse import unquote as urllib_unquote, urlparse as lib_urlparse
-else:
- from cStringIO import StringIO as string_io
- from urllib import unquote as urllib_unquote
- from urlparse import urlparse as lib_urlparse
+from io import StringIO as string_io
+import urllib.request, urllib.error
+from urllib.parse import unquote as urllib_unquote, urlparse as lib_urlparse
+
LOG = logging.getLogger()
diff --git a/desktop/core/src/desktop/log/__init__.py b/desktop/core/src/desktop/log/__init__.py
index 1d51eae30ec..31a15905bb8 100644
--- a/desktop/core/src/desktop/log/__init__.py
+++ b/desktop/core/src/desktop/log/__init__.py
@@ -15,17 +15,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from __future__ import print_function
-from __future__ import absolute_import
-from future import standard_library
-standard_library.install_aliases()
-import logging
-import logging.config
import os
-import os.path
import re
import sys
-
+import logging
+import os.path
+import logging.config
+from io import StringIO as string_io
from logging import FileHandler
from logging.handlers import RotatingFileHandler, SocketHandler
@@ -33,13 +29,6 @@
from desktop.log import formatter
from desktop.log.formatter import MessageOnlyFormatter
-if sys.version_info[0] > 2:
- from io import StringIO as string_io
- open_file = open
-else:
- from cStringIO import StringIO as string_io
- open_file = file
-
DEFAULT_LOG_DIR = 'logs'
LOG_FORMAT = '[%(asctime)s] %(module)-12s %(levelname)-8s %(message)s'
DATE_FORMAT = '%d/%b/%Y %H:%M:%S %z'
@@ -68,7 +57,7 @@ def _repl(match):
return None
try:
- raw = open_file(log_conf).read()
+ raw = open(log_conf).read()
sio = string_io(CONF_RE.sub(_repl, raw))
return sio
except IOError as ex:
@@ -92,7 +81,7 @@ def get_audit_logger():
from desktop.conf import AUDIT_EVENT_LOG_DIR, AUDIT_LOG_MAX_FILE_SIZE
audit_logger = logging.getLogger('audit')
- if not [hclass for hclass in audit_logger.handlers if isinstance(hclass, AuditHandler)]: # Don't add handler twice
+ if not [hclass for hclass in audit_logger.handlers if isinstance(hclass, AuditHandler)]: # Don't add handler twice
size, unit = int(AUDIT_LOG_MAX_FILE_SIZE.get()[:-2]), AUDIT_LOG_MAX_FILE_SIZE.get()[-2:]
maxBytes = size * 1024 ** (1 if unit == 'KB' else 2 if unit == 'MB' else 3)
@@ -203,14 +192,15 @@ def basic_logging(proc_name, log_dir=None):
if hasattr(DATABASE_LOGGING, 'get') and not DATABASE_LOGGING.get():
def disable_database_logging():
logger = logging.getLogger()
- logger.manager.loggerDict['django.db.backends'].level = 20 # INFO level
+ logger.manager.loggerDict['django.db.backends'].level = 20 # INFO level
disable_database_logging()
+
def fancy_logging():
"""Configure logging into a buffer for /logs endpoint."""
from .log_buffer import FixedBufferHandler
- BUFFER_SIZE = 1500 * 200 # This is the size in characters, not bytes. Targets about 1500 rows.
+ BUFFER_SIZE = 1500 * 200 # This is the size in characters, not bytes. Targets about 1500 rows.
buffer_handler = FixedBufferHandler(BUFFER_SIZE)
_formatter = formatter.Formatter(LOG_FORMAT, DATE_FORMAT)
@@ -228,7 +218,7 @@ def get_all_debug():
def set_all_debug():
- from desktop.settings import ENV_HUE_PROCESS_NAME # Circular dependency
+ from desktop.settings import ENV_HUE_PROCESS_NAME # Circular dependency
global FORCE_DEBUG
FORCE_DEBUG = True
@@ -237,7 +227,7 @@ def set_all_debug():
def reset_all_debug():
- from desktop.settings import ENV_HUE_PROCESS_NAME # Circular dependency
+ from desktop.settings import ENV_HUE_PROCESS_NAME # Circular dependency
global FORCE_DEBUG
FORCE_DEBUG = False
diff --git a/desktop/core/src/desktop/manage_entry.py b/desktop/core/src/desktop/manage_entry.py
index 2314233c382..10bb61efebc 100644
--- a/desktop/core/src/desktop/manage_entry.py
+++ b/desktop/core/src/desktop/manage_entry.py
@@ -15,19 +15,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from __future__ import print_function
-from future import standard_library
-standard_library.install_aliases()
-import logging
import os
-import os.path
-import fnmatch
import sys
+import fnmatch
+import logging
+import os.path
import traceback
import subprocess
LOG = logging.getLogger()
+
def _deprecation_check(arg0):
"""HUE-71. Deprecate build/env/bin/desktop"""
if os.path.basename(arg0) == 'desktop':
@@ -36,10 +34,11 @@ def _deprecation_check(arg0):
print(msg, file=sys.stderr)
LOG.warning(msg)
+
def reload_with_cm_env(cm_managed):
try:
from django.db.backends.oracle.base import Oracle_datetime
- except:
+ except Exception:
if 'LD_LIBRARY_PATH' in os.environ:
print("We need to reload the process to include LD_LIBRARY_PATH for Oracle backend")
try:
@@ -52,12 +51,12 @@ def reload_with_cm_env(cm_managed):
print('Failed re-exec: %s' % exc)
sys.exit(1)
+
def entry():
_deprecation_check(sys.argv[0])
from django.core.exceptions import ImproperlyConfigured
- from django.core.management import execute_from_command_line, find_commands
- from django.core.management import CommandParser
+ from django.core.management import CommandParser, execute_from_command_line, find_commands
from django.core.management.base import BaseCommand
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'desktop.settings')
@@ -86,16 +85,13 @@ def entry():
if len(sys.argv) > 1:
subcommand = sys.argv[1]
- if sys.version_info[0] < 3:
- args = [None]
- else:
- args = []
+ args = []
parser = CommandParser(*args, usage="%(prog)s subcommand [options] [args]", add_help=False)
parser.parse_known_args(sys.argv[2:])
if len(sys.argv) > 1:
prof_id = subcommand = sys.argv[1]
- #Check if this is a CM managed cluster
+ # Check if this is a CM managed cluster
if os.path.isfile(cm_config_file) and not cm_managed and not skip_reload:
print("ALERT: This appears to be a CM Managed environment")
print("ALERT: HUE_CONF_DIR must be set when running hue commands in CM Managed environment")
@@ -105,12 +101,9 @@ def entry():
# CM managed configure env vars
if cm_managed:
- if sys.version_info[0] > 2:
- from configparser import NoOptionError, RawConfigParser
- else:
- from ConfigParser import NoOptionError, RawConfigParser
+ from configparser import NoOptionError, RawConfigParser
- config = RawConfigParser(strict=False) if sys.version_info[0] > 2 else RawConfigParser()
+ config = RawConfigParser(strict=False)
config.read(cm_config_file)
try:
cm_agent_run_dir = config.get('General', 'agent_wide_credential_cache_location')
@@ -118,7 +111,7 @@ def entry():
cm_agent_run_dir = '/var/run/cloudera-scm-agent'
pass
- #Parse CM supervisor include file for Hue and set env vars
+ # Parse CM supervisor include file for Hue and set env vars
cm_supervisor_dir = cm_agent_run_dir + '/supervisor/include'
cm_process_dir = cm_agent_run_dir + '/process'
hue_env_conf = None
@@ -130,14 +123,14 @@ def entry():
hue_env_conf = file
hue_env_conf = cm_supervisor_dir + "/" + hue_env_conf
- if hue_env_conf == None:
+ if hue_env_conf is None:
process_dirs = fnmatch.filter(os.listdir(cm_process_dir), '*%s*' % cm_hue_string)
process_dirs.sort()
hue_process_dir = cm_process_dir + "/" + process_dirs[-1]
hue_env_conf = fnmatch.filter(os.listdir(hue_process_dir), 'supervisor.conf')[0]
hue_env_conf = hue_process_dir + "/" + hue_env_conf
- if not hue_env_conf == None:
+ if hue_env_conf is not None:
if os.path.isfile(hue_env_conf):
hue_env_conf_file = open(hue_env_conf, "r")
for line in hue_env_conf_file:
@@ -161,7 +154,7 @@ def entry():
print("")
print("If the above does not work, make sure Hue has been started on this server.")
- if not envline == None:
+ if envline is not None:
# spliting envline by "environment=" won't work since new key cdp_environment was added
environment = envline.replace("environment=", "")
for envvar in environment.split(","):
@@ -171,7 +164,7 @@ def entry():
envval = envval.replace("'", "").rstrip()
os.environ[envkey] = envval
- #Set JAVA_HOME
+ # Set JAVA_HOME
if "JAVA_HOME" not in list(os.environ.keys()):
if os.path.isfile('/usr/lib64/cmf/service/common/cloudera-config.sh'):
locate_java = subprocess.Popen(
@@ -190,7 +183,7 @@ def entry():
for line in iter(locate_java.stdout.readline, b''):
if b'JAVA_HOME' in line:
JAVA_HOME = line.rstrip().split(b'=')[1]
- if sys.version_info[0] > 2 and type(JAVA_HOME) == bytes:
+ if type(JAVA_HOME) is bytes:
JAVA_HOME = JAVA_HOME.decode("utf-8")
if JAVA_HOME != "UNKNOWN":
@@ -201,7 +194,7 @@ def entry():
print(" export JAVA_HOME=")
sys.exit(1)
- #Make sure we set Oracle Client if configured
+ # Make sure we set Oracle Client if configured
if "LD_LIBRARY_PATH" not in list(os.environ.keys()):
if "SCM_DEFINES_SCRIPTS" in list(os.environ.keys()):
for scm_script in os.environ["SCM_DEFINES_SCRIPTS"].split(":"):
@@ -233,7 +226,7 @@ def entry():
execute_from_command_line(sys.argv)
except ImproperlyConfigured as e:
if len(sys.argv) > 1 and sys.argv[1] == 'is_db_alive' and 'oracle' in str(e).lower():
- print(e, file=sys.stderr) # Oracle connector is improperly configured
+ print(e, file=sys.stderr) # Oracle connector is improperly configured
sys.exit(10)
else:
raise e
@@ -242,6 +235,7 @@ def entry():
print("%s" % e)
print("HUE_CONF_DIR seems to be set to CM location and '--cm-managed' flag not used")
+
def _profile(prof_id, func):
"""
Wrap a call with a profiler
diff --git a/desktop/core/src/desktop/management/commands/change_owner_of_docs.py b/desktop/core/src/desktop/management/commands/change_owner_of_docs.py
index 4ae95d25311..be2f94ec555 100644
--- a/desktop/core/src/desktop/management/commands/change_owner_of_docs.py
+++ b/desktop/core/src/desktop/management/commands/change_owner_of_docs.py
@@ -16,16 +16,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import logging
import sys
-from desktop.models import Document2
+import logging
+
from django.contrib.auth.models import User
from django.core.management.base import BaseCommand
+from django.utils.translation import gettext as _, gettext_lazy as _t
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext_lazy as _t, gettext as _
-else:
- from django.utils.translation import ugettext_lazy as _t, ugettext as _
+from desktop.models import Document2
LOG = logging.getLogger()
@@ -44,7 +42,7 @@ class Command(BaseCommand):
action="store"),
)
- except AttributeError, e:
+ except AttributeError as e:
baseoption_test = 'BaseCommand' in str(e) and 'option_list' in str(e)
if baseoption_test:
def add_arguments(self, parser):
diff --git a/desktop/core/src/desktop/management/commands/config_dump.py b/desktop/core/src/desktop/management/commands/config_dump.py
index 7c22059a756..897f72af607 100644
--- a/desktop/core/src/desktop/management/commands/config_dump.py
+++ b/desktop/core/src/desktop/management/commands/config_dump.py
@@ -22,18 +22,17 @@
textual representation.
"""
from __future__ import print_function
-from django.core.management.base import BaseCommand
-import desktop.appmanager
-import textwrap
+
import sys
+import textwrap
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext as _
-else:
- from django.utils.translation import ugettext as _
+from django.core.management.base import BaseCommand
+from django.utils.translation import gettext as _
+import desktop.appmanager
from desktop.lib.conf import BoundContainer, is_anonymous
+
class Command(BaseCommand):
def __init__(self, *args, **kwargs):
super(Command, self).__init__(*args, **kwargs)
@@ -46,12 +45,11 @@ def handle(self, *args, **options):
self.recurse(desktop.lib.conf.GLOBAL_CONFIG)
def p(self, s):
- print(" "*self.indent + s)
+ print(" " * self.indent + s)
def fill(self, s):
print(textwrap.fill(s.strip(),
- initial_indent=" "*self.indent, subsequent_indent=" "*self.indent))
-
+ initial_indent=" " * self.indent, subsequent_indent=" " * self.indent))
def recurse(self, config_obj):
if isinstance(config_obj, BoundContainer):
@@ -63,7 +61,7 @@ def recurse(self, config_obj):
self.p("%s:" % key)
self.indent += 2
print(textwrap.fill(config_obj.config.help or _("No help available."),
- initial_indent=" "*self.indent, subsequent_indent=" "*self.indent))
+ initial_indent=" " * self.indent, subsequent_indent=" " * self.indent))
print()
for v in list(config_obj.get().values()):
self.recurse(v)
diff --git a/desktop/core/src/desktop/management/commands/config_upgrade.py b/desktop/core/src/desktop/management/commands/config_upgrade.py
index 41c2a2c6fa1..426a12d4c3f 100644
--- a/desktop/core/src/desktop/management/commands/config_upgrade.py
+++ b/desktop/core/src/desktop/management/commands/config_upgrade.py
@@ -21,23 +21,25 @@
s///
This will rewrite the configurations if any changes are required.
"""
-import logging
+import os
import sys
+import glob
+import string
+import logging
+
+from django.core.management.base import BaseCommand, CommandError
+from django.utils.translation import gettext as _
-import os, glob, string
-import desktop.conf
import desktop.log
+import desktop.conf
from desktop.lib.paths import get_desktop_root
-from django.core.management.base import BaseCommand, CommandError
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext as _
-else:
- from django.utils.translation import ugettext as _
LOG = logging.getLogger()
+
class Command(BaseCommand):
help = _('Upgrades the Hue configuration with a mapping file.')
+
def add_arguments(self, parser):
parser.add_argument('--mapping_file', help=_('Location of the mapping file.'))
@@ -47,8 +49,8 @@ def handle(self, *args, **options):
for r in required:
if not options.get(r):
raise CommandError(_("--%(param)s is required.") % {'param': r})
-
- # Pull out all the mappings
+
+ # Pull out all the mappings
mapping_file = options["mapping_file"]
mapping_handle = open(mapping_file, 'r')
mappings = []
@@ -76,14 +78,12 @@ def handle(self, *args, **options):
new_value = mapping[1]
if old_value in line[0]:
- LOG.info("Replacing %s with %s in line %s" %
- (old_value, new_value, '='.join(line),))
+ LOG.info("Replacing %s with %s in line %s" %
+ (old_value, new_value, '='.join(line),))
line[0] = line[0].replace(old_value, new_value)
-
-
+
# Rewrite file with replacements made
conf_handle.close()
conf_handle = open(conf_file, 'w')
- data_to_write = ''.join([ '='.join(split) for split in data ])
+ data_to_write = ''.join(['='.join(split) for split in data])
conf_handle.write(data_to_write)
-
diff --git a/desktop/core/src/desktop/management/commands/create_desktop_app.py b/desktop/core/src/desktop/management/commands/create_desktop_app.py
index e86264b50d1..8105d738e2e 100644
--- a/desktop/core/src/desktop/management/commands/create_desktop_app.py
+++ b/desktop/core/src/desktop/management/commands/create_desktop_app.py
@@ -19,21 +19,15 @@
import os
import re
import shutil
-import sys
-from django.core.management.base import CommandError, BaseCommand
-from mako.template import Template
-
import logging
-if sys.version_info[0] > 2:
- open_file = open
- from django.utils.translation import gettext as _
-else:
- from django.utils.translation import ugettext as _
- open_file = file
+from django.core.management.base import BaseCommand, CommandError
+from django.utils.translation import gettext as _
+from mako.template import Template
LOG = logging.getLogger()
+
class Command(BaseCommand):
help = _("Creates a Hue application directory structure.")
args = "[appname]"
@@ -51,7 +45,7 @@ def handle(self, *args, **options):
else:
app_dir = os.getcwd()
- app_template = os.path.abspath(os.path.join(os.path.dirname(__file__),'..','..','app_template'))
+ app_template = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..', 'app_template'))
assert os.path.isdir(app_template), _("App template dir missing: %(template)s.") % {'template': app_template}
app_dir = os.path.join(app_dir, app_name)
@@ -67,6 +61,7 @@ def handle(self, *args, **options):
copy_template(app_template, app_dir, app_name)
+
def copy_template(app_template, copy_to, app_name):
"""copies the specified template directory to the copy_to location"""
@@ -75,22 +70,22 @@ def copy_template(app_template, copy_to, app_name):
# walks the template structure and copies it
for directory, subdirs, files in os.walk(app_template):
- relative_dir = directory[len(app_template)+1:].replace('app_name_camel', app_name_camel).replace('app_name',app_name)
+ relative_dir = directory[len(app_template) + 1:].replace('app_name_camel', app_name_camel).replace('app_name', app_name)
if not os.path.exists(os.path.join(copy_to, relative_dir)):
os.mkdir(os.path.join(copy_to, relative_dir))
for f in files:
if f.endswith('.pyc') or f.startswith("."):
continue
-
+
path_old = os.path.join(directory, f)
path_new = os.path.join(copy_to, relative_dir, f.replace('app_name_camel', app_name_camel).replace('app_name', app_name))
LOG.info("Writing %s" % path_new)
fp_new = open(path_new, 'w')
if path_old.endswith(".png"):
- shutil.copyfileobj(open_file(path_old), fp_new)
+ shutil.copyfileobj(open(path_old), fp_new)
else:
- fp_new.write( Template(filename=path_old).render(app_name=app_name, app_name_camel=app_name_camel, app_name_spaces=app_name_spaces) )
+ fp_new.write(Template(filename=path_old).render(app_name=app_name, app_name_camel=app_name_camel, app_name_spaces=app_name_spaces))
fp_new.close()
-
+
shutil.copymode(path_old, path_new)
diff --git a/desktop/core/src/desktop/management/commands/create_proxy_app.py b/desktop/core/src/desktop/management/commands/create_proxy_app.py
index ca7605de602..d019d6aaf8e 100644
--- a/desktop/core/src/desktop/management/commands/create_proxy_app.py
+++ b/desktop/core/src/desktop/management/commands/create_proxy_app.py
@@ -19,21 +19,15 @@
import os
import re
import shutil
-import sys
-from django.core.management.base import CommandError, BaseCommand
-from mako.template import Template
-
import logging
-if sys.version_info[0] > 2:
- open_file = open
- from django.utils.translation import gettext as _
-else:
- from django.utils.translation import ugettext as _
- open_file = file
+from django.core.management.base import BaseCommand, CommandError
+from django.utils.translation import gettext as _
+from mako.template import Template
LOG = logging.getLogger()
+
class Command(BaseCommand):
help = _("Creates a Hue proxy application directory structure.")
args = "app_name app_web_url [app_dir]"
@@ -69,6 +63,7 @@ def handle(self, *args, **options):
copy_template(app_template, app_dir, app_name, app_url)
+
def copy_template(app_template, copy_to, app_name, app_url):
"""copies the specified template directory to the copy_to location"""
@@ -77,22 +72,26 @@ def copy_template(app_template, copy_to, app_name, app_url):
# walks the template structure and copies it
for directory, subdirs, files in os.walk(app_template):
- relative_dir = directory[len(app_template)+1:].replace('app_name_camel', app_name_camel).replace('app_name',app_name)
+ relative_dir = directory[len(app_template) + 1:].replace('app_name_camel', app_name_camel).replace('app_name', app_name)
if not os.path.exists(os.path.join(copy_to, relative_dir)):
os.mkdir(os.path.join(copy_to, relative_dir))
for f in files:
if f.endswith('.pyc') or f.startswith("."):
continue
-
+
path_old = os.path.join(directory, f)
path_new = os.path.join(copy_to, relative_dir, f.replace('app_name_camel', app_name_camel).replace('app_name', app_name))
LOG.info("Writing %s" % path_new)
fp_new = open(path_new, 'w')
if path_old.endswith(".png"):
- shutil.copyfileobj(open_file(path_old), fp_new)
+ shutil.copyfileobj(open(path_old), fp_new)
else:
- fp_new.write( Template(filename=path_old).render(app_name=app_name, app_name_camel=app_name_camel, app_name_spaces=app_name_spaces, app_url=app_url) )
+ fp_new.write(
+ Template(filename=path_old).render(
+ app_name=app_name, app_name_camel=app_name_camel, app_name_spaces=app_name_spaces, app_url=app_url
+ )
+ )
fp_new.close()
-
+
shutil.copymode(path_old, path_new)
diff --git a/desktop/core/src/desktop/management/commands/create_test_fs.py b/desktop/core/src/desktop/management/commands/create_test_fs.py
index 68780c8de28..b5dfff120c8 100644
--- a/desktop/core/src/desktop/management/commands/create_test_fs.py
+++ b/desktop/core/src/desktop/management/commands/create_test_fs.py
@@ -15,18 +15,16 @@
# limitations under the License.
from __future__ import print_function
+
import os
import sys
from django.core.management.base import BaseCommand
+from django.utils.translation import gettext as _
from desktop.lib.paths import get_build_dir
from hadoop.fs import fs_for_testing
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext as _
-else:
- from django.utils.translation import ugettext as _
class Command(BaseCommand):
"""Creates file system for testing."""
diff --git a/desktop/core/src/desktop/management/commands/create_user_directories.py b/desktop/core/src/desktop/management/commands/create_user_directories.py
index 65162772826..daa7e1f1975 100644
--- a/desktop/core/src/desktop/management/commands/create_user_directories.py
+++ b/desktop/core/src/desktop/management/commands/create_user_directories.py
@@ -16,20 +16,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import logging
import sys
+import logging
-from django.core.management.base import CommandError, BaseCommand
-
-from useradmin.models import User
+from django.core.management.base import BaseCommand, CommandError
+from django.utils.translation import gettext_lazy as _
from desktop.models import Document2
-
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext_lazy as _
-else:
- from django.utils.translation import ugettext_lazy as _
-
+from useradmin.models import User
LOG = logging.getLogger()
@@ -44,6 +38,7 @@ class Command(BaseCommand):
If --username is specified, it will only perform the operation for the specific user.
"""
help = _("Creates home and Trash directories for users as needed, or specific user if username is provided.")
+
def add_arguments(self, parser):
parser.add_argument('--username', help=_("Username of user to create directories for."), action='store', default=None)
diff --git a/desktop/core/src/desktop/management/commands/desktop_document_cleanup.py b/desktop/core/src/desktop/management/commands/desktop_document_cleanup.py
index 61a5289807f..551d0eb8d6b 100644
--- a/desktop/core/src/desktop/management/commands/desktop_document_cleanup.py
+++ b/desktop/core/src/desktop/management/commands/desktop_document_cleanup.py
@@ -15,29 +15,26 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import desktop.conf
-import desktop.conf
-import logging.handlers
import os
import sys
import time
-from beeswax.models import SavedQuery
-from beeswax.models import Session
+import logging.handlers
from datetime import date, timedelta
-from desktop.models import Document2
-from desktop.settings import INSTALLED_APPS
+from importlib import import_module
+
from django.conf import settings
from django.core.management.base import BaseCommand
from django.db.utils import DatabaseError
-from importlib import import_module
+
+import desktop.conf
+from beeswax.models import SavedQuery, Session
+from desktop.models import Document2
+from desktop.settings import INSTALLED_APPS
if 'oozie' in INSTALLED_APPS:
from oozie.models import Workflow
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext_lazy as _t, gettext as _
-else:
- from django.utils.translation import ugettext_lazy as _t, ugettext as _
+from django.utils.translation import gettext as _, gettext_lazy as _t
LOG = logging.getLogger()
@@ -75,7 +72,7 @@ def objectCleanup(self, objClass, filterType, filterValue, dateField):
deleteRecords = self.deleteRecordsBase
totalObjects = objClass.objects.filter(
- **{'%s' % filterType: filterValue, '%s__lte' % dateField: self.timeDeltaObj,}) \
+ **{'%s' % filterType: filterValue, '%s__lte' % dateField: self.timeDeltaObj, }) \
.values_list("id", flat=True)
LOG.info("Looping through %s objects. %s objects to be deleted." % (objClass.__name__, totalObjects.count()))
while totalObjects.count():
@@ -87,7 +84,7 @@ def objectCleanup(self, objClass, filterType, filterValue, dateField):
checkCount = 0
LOG.info("%s objects left: %s" % (objClass.__name__, totalObjects.count()))
deleteObjects = objClass.objects.filter(
- **{'%s' % filterType: filterValue, '%s__lte' % dateField: self.timeDeltaObj,}) \
+ **{'%s' % filterType: filterValue, '%s__lte' % dateField: self.timeDeltaObj, }) \
.values_list("id", flat=True)[:deleteRecords]
try:
objClass.objects.filter(pk__in=list(deleteObjects)).delete()
@@ -103,7 +100,7 @@ def objectCleanup(self, objClass, filterType, filterValue, dateField):
deleteRecords = max(deleteRecords - 10, 1)
LOG.info("Decreasing max delete records to: %s" % deleteRecords)
totalObjects = objClass.objects.filter(
- **{'%s' % filterType: filterValue, '%s__lte' % dateField: self.timeDeltaObj,}) \
+ **{'%s' % filterType: filterValue, '%s__lte' % dateField: self.timeDeltaObj, }) \
.values_list("id", flat=True)
def handle(self, *args, **options):
diff --git a/desktop/core/src/desktop/management/commands/get_backend_curl.py b/desktop/core/src/desktop/management/commands/get_backend_curl.py
index c770fe43968..f1cffbaac2d 100644
--- a/desktop/core/src/desktop/management/commands/get_backend_curl.py
+++ b/desktop/core/src/desktop/management/commands/get_backend_curl.py
@@ -16,23 +16,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import logging
import os
-import subprocess
import sys
import time
-from desktop.conf import TIME_ZONE
+import logging
+import subprocess
+
from django.core.management.base import BaseCommand
-from hadoop import cluster
-from hadoop import conf as hdfs_conf
+from django.utils.translation import gettext as _, gettext_lazy as _t
+
+from desktop.conf import TIME_ZONE
from desktop.hue_curl import Curl
+from hadoop import cluster, conf as hdfs_conf
from liboozie.conf import OOZIE_URL, SECURITY_ENABLED as OOZIE_SECURITY_ENABLED
-from search.conf import SOLR_URL, SECURITY_ENABLED as SOLR_SECURITY_ENABLED
-
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext_lazy as _t, gettext as _
-else:
- from django.utils.translation import ugettext_lazy as _t, ugettext as _
+from search.conf import SECURITY_ENABLED as SOLR_SECURITY_ENABLED, SOLR_URL
LOG = logging.getLogger()
@@ -81,7 +78,7 @@ def get_service_info(service):
def add_service_test(available_services, options='all', service_name=None, testname=None, suburl=None, method='GET',
teststring=None, test_options=None):
if options['service'] == "all" or options['service'] == service_name.lower():
- if not service_name in available_services:
+ if service_name not in available_services:
service_info = get_service_info(service_name)
url = service_info['url']
security_enabled = service_info['security_enabled']
@@ -89,9 +86,9 @@ def add_service_test(available_services, options='all', service_name=None, testn
available_services[service_name]['url'] = url
available_services[service_name]['security_enabled'] = security_enabled
# Tests
- if not 'tests' in available_services[service_name]:
+ if 'tests' not in available_services[service_name]:
available_services[service_name]['tests'] = {}
- if not testname in available_services[service_name]['tests']:
+ if testname not in available_services[service_name]['tests']:
for test_option in test_options.keys():
suburl = suburl.replace(test_option, str(test_options[test_option]))
available_services[service_name]['tests'][testname] = {}
@@ -306,16 +303,16 @@ def handle(self, *args, **options):
LOG.info("TEST: %s %s: Failed in %dms: Response: %s" % (service, service_test, returned_in, response))
log_file = log_dir + '/backend_test_curl.log'
- print ("")
- print ("Tests completed, view logs here: %s") % log_file
- print ("Report:")
+ print("")
+ print("Tests completed, view logs here: %s") % log_file
+ print("Report:")
cmd = 'grep -A1000 "%s" %s | grep "TEST:" | sed "s/.*INFO.*TEST:/ TEST:/g"' % (str(test_options['NOW']), log_file)
grep_process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
grep_response = grep_process.communicate()[0]
- print ("%s") % grep_response
- print ("")
- print ("OS Repro Commands are:")
+ print("%s") % grep_response
+ print("")
+ print("OS Repro Commands are:")
cmd = 'grep -A1000 "%s" %s | grep "OSRUN:" | sed "s/.*INFO.*OSRUN:/ /g"' % (str(test_options['NOW']), log_file)
grep_process = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE)
grep_response = grep_process.communicate()[0]
- print ("%s") % grep_response
+ print("%s") % grep_response
diff --git a/desktop/core/src/desktop/management/commands/ldaptest.py b/desktop/core/src/desktop/management/commands/ldaptest.py
index 31adbfcabdc..ec417d1bbdc 100644
--- a/desktop/core/src/desktop/management/commands/ldaptest.py
+++ b/desktop/core/src/desktop/management/commands/ldaptest.py
@@ -27,21 +27,18 @@
This script uses HUE libraries and works in HUE setup only.
"""
-import ldap
-import ldap.filter
-import logging
import os
-import socket
import sys
+import socket
+import logging
-from desktop.conf import LDAP
+import ldap
+import ldap.filter
from django.core.management.base import BaseCommand
-from useradmin import ldap_access
+from django.utils.translation import gettext as _
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext as _
-else:
- from django.utils.translation import ugettext as _
+from desktop.conf import LDAP
+from useradmin import ldap_access
LOG = logging.getLogger()
LOG.setLevel(logging.DEBUG)
@@ -286,7 +283,7 @@ def find_ldapusers(self, ldap_config, ldap_obj):
LOG.info(_(base_dn_msg))
LOG.warning('hints: check base_dn')
err_code = 1
- except:
+ except Exception:
typ, value, traceback = sys.exc_info()
LOG.warning("%s %s" % (typ, value))
LOG.info(_(base_dn_msg))
@@ -333,7 +330,7 @@ def find_ldapgroups(self, ldap_config, ldap_obj):
LOG.info(_(base_dn_msg))
LOG.warning("hints: check base_dn")
err_code = 1
- except:
+ except Exception:
typ, value, traceback = sys.exc_info()
LOG.warning("%s %s" % (typ, value))
LOG.info(_(base_dn_msg))
@@ -370,7 +367,7 @@ def find_users_of_group(self, ldap_config, ldap_obj):
LOG.info(_(base_dn_msg))
LOG.warning('hints: check base_dn')
err_code = 1
- except:
+ except Exception:
typ, value, traceback = sys.exc_info()
LOG.warning("%s %s" % (typ, value))
LOG.info(_(base_dn_msg))
@@ -412,7 +409,7 @@ def find_groups_of_group(self, ldap_config, ldap_obj):
LOG.info(_(base_dn_msg))
LOG.warning('hints: check base_dn')
err_code = 1
- except:
+ except Exception:
typ, value, traceback = sys.exc_info()
LOG.warning("%s %s" % (typ, value))
LOG.info(_(base_dn_msg))
@@ -488,7 +485,7 @@ def check_single_ldap_setting(self, ldap_config, is_multi_ldap=False):
LOG.warning('ldap_url="%s"' % ldap_config.LDAP_URL.get())
LOG.warning('bind_dn="%s"' % ldap_config.BIND_DN.get())
err_code = 1
- except:
+ except Exception:
typ, value, traceback = sys.exc_info()
LOG.warning("%s %s" % (typ, value))
LOG.info(_(ldap_url_msg))
@@ -507,7 +504,7 @@ def check_single_ldap_setting(self, ldap_config, is_multi_ldap=False):
try:
LOG.info('LDAP whoami_s() %s' % (connection.ldap_handle.whoami_s()))
- except:
+ except Exception:
LOG.warn('Not able to execute whoami_s() command')
if ldap_config.TEST_LDAP_USER.get() is not None:
@@ -519,7 +516,7 @@ def check_single_ldap_setting(self, ldap_config, is_multi_ldap=False):
group_dn = None
try:
group_dn = ldap.explode_dn(ldap_config.TEST_LDAP_GROUP.get())
- except:
+ except Exception:
group_dn = None
if group_dn is not None:
diff --git a/desktop/core/src/desktop/management/commands/runcelery.py b/desktop/core/src/desktop/management/commands/runcelery.py
index c7634c0aa72..c3687f01344 100644
--- a/desktop/core/src/desktop/management/commands/runcelery.py
+++ b/desktop/core/src/desktop/management/commands/runcelery.py
@@ -22,17 +22,13 @@
from celery.bin.celery import CeleryCommand, main as celery_main
from django.core.management.base import BaseCommand
from django.utils import autoreload
+from django.utils.translation import gettext as _
from desktop import conf
from desktop.conf import TASK_SERVER_V2
from desktop.lib.daemon_utils import drop_privileges_if_necessary
from desktop.log import DEFAULT_LOG_DIR
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext as _
-else:
- from django.utils.translation import ugettext as _
-
SERVER_HELP = r"""
Run celery worker.
"""
diff --git a/desktop/core/src/desktop/management/commands/runcpserver.py b/desktop/core/src/desktop/management/commands/runcpserver.py
index 0ef989fd1b6..24b31bebec8 100644
--- a/desktop/core/src/desktop/management/commands/runcpserver.py
+++ b/desktop/core/src/desktop/management/commands/runcpserver.py
@@ -15,17 +15,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import logging
-from django.core.management.base import BaseCommand
-from desktop import conf
-from desktop import supervisor
import os
import sys
+import logging
+
+from django.core.management.base import BaseCommand
+from django.utils.translation import gettext as _
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext as _
-else:
- from django.utils.translation import ugettext as _
+from desktop import conf, supervisor
SERVER_HELP = r"""
Run Hue using either the CherryPy server or the Spawning server, based on
@@ -34,6 +31,7 @@
LOG = logging.getLogger()
+
class Command(BaseCommand):
help = _("Web server for Hue.")
@@ -43,6 +41,7 @@ def handle(self, *args, **options):
def usage(self, subcommand):
return SERVER_HELP
+
def runserver():
script_name = "rungunicornserver"
if conf.USE_CHERRYPY_SERVER.get():
@@ -52,5 +51,6 @@ def runserver():
LOG.error("Failed to exec '%s' with argument '%s'" % (cmdv[0], cmdv[1],))
sys.exit(-1)
+
if __name__ == '__main__':
runserver()
diff --git a/desktop/core/src/desktop/management/commands/runruff.py b/desktop/core/src/desktop/management/commands/runruff.py
index 4fb04c6706b..fa02d179889 100644
--- a/desktop/core/src/desktop/management/commands/runruff.py
+++ b/desktop/core/src/desktop/management/commands/runruff.py
@@ -15,18 +15,17 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import sys
import logging
import os.path
-import subprocess
-import sys
import argparse
+import subprocess
from django.core.management.base import BaseCommand
from django.utils.translation import gettext as _
from desktop.lib import paths
-
LOG = logging.getLogger()
@@ -132,6 +131,5 @@ def handle(self, *args, **options):
ret = subprocess.run(ruff_cmd, check=True)
if ret.returncode != 0:
sys.exit(1)
- except subprocess.CalledProcessError as e:
- LOG.debug(f"Ruff command: {e}")
+ except subprocess.CalledProcessError:
sys.exit(1)
diff --git a/desktop/core/src/desktop/middleware.py b/desktop/core/src/desktop/middleware.py
index 426677ea971..827abf03279 100644
--- a/desktop/core/src/desktop/middleware.py
+++ b/desktop/core/src/desktop/middleware.py
@@ -17,66 +17,66 @@
from __future__ import absolute_import
-from builtins import object
-import inspect
+import re
import json
+import time
+import socket
+import inspect
import logging
-import mimetypes
import os.path
-import re
-import socket
-import sys
import tempfile
-import time
+import mimetypes
import traceback
+from builtins import object
+from urllib.parse import quote, urlparse
import kerberos
import django.db
-import django.views.static
import django_prometheus
-
+import django.views.static
from django.conf import settings
from django.contrib import messages
-from django.contrib.auth import REDIRECT_FIELD_NAME, BACKEND_SESSION_KEY, authenticate, load_backend, login
+from django.contrib.auth import BACKEND_SESSION_KEY, REDIRECT_FIELD_NAME, authenticate, load_backend, login
from django.contrib.auth.middleware import RemoteUserMiddleware
from django.core import exceptions
-from django.http import HttpResponseNotAllowed, HttpResponseForbidden
+from django.http import HttpResponse, HttpResponseForbidden, HttpResponseNotAllowed, HttpResponseRedirect
from django.urls import resolve
-from django.http import HttpResponseRedirect, HttpResponse
from django.utils.deprecation import MiddlewareMixin
-
-from hadoop import cluster
-from dashboard.conf import IS_ENABLED as DASHBOARD_ENABLED
-from useradmin.models import User
+from django.utils.http import url_has_allowed_host_and_scheme
+from django.utils.translation import gettext as _
import desktop.views
+from dashboard.conf import IS_ENABLED as DASHBOARD_ENABLED
from desktop import appmanager, metrics
-from desktop.auth.backend import is_admin, find_or_create_user, ensure_has_a_group, rewrite_user
-from desktop.conf import AUTH, HTTP_ALLOWED_METHODS, ENABLE_PROMETHEUS, KNOX, DJANGO_DEBUG_MODE, AUDIT_EVENT_LOG_DIR, \
- METRICS, SERVER_USER, REDIRECT_WHITELIST, SECURE_CONTENT_SECURITY_POLICY, has_connectors, is_gunicorn_report_enabled, \
- CUSTOM_CACHE_CONTROL, HUE_LOAD_BALANCER
+from desktop.auth.backend import ensure_has_a_group, find_or_create_user, is_admin, knox_login_headers, rewrite_user
+from desktop.conf import (
+ AUDIT_EVENT_LOG_DIR,
+ AUTH,
+ CUSTOM_CACHE_CONTROL,
+ DJANGO_DEBUG_MODE,
+ ENABLE_PROMETHEUS,
+ HTTP_ALLOWED_METHODS,
+ HUE_LOAD_BALANCER,
+ KNOX,
+ METRICS,
+ REDIRECT_WHITELIST,
+ SECURE_CONTENT_SECURITY_POLICY,
+ SERVER_USER,
+ has_connectors,
+ is_gunicorn_report_enabled,
+)
from desktop.context_processors import get_app_name
-from desktop.lib import apputil, i18n, fsmanager
+from desktop.lib import apputil, fsmanager, i18n
from desktop.lib.django_util import JsonResponse, render, render_json
from desktop.lib.exceptions import StructuredException
from desktop.lib.exceptions_renderable import PopupException
from desktop.lib.metrics import global_registry
from desktop.lib.view_util import is_ajax
from desktop.log import get_audit_logger
-from desktop.log.access import access_log, log_page_hit, access_warn
-from desktop.auth.backend import knox_login_headers
-
+from desktop.log.access import access_log, access_warn, log_page_hit
+from hadoop import cluster
from libsaml.conf import CDP_LOGOUT_URL
-from urllib.parse import urlparse
-
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext as _
- from django.utils.http import url_has_allowed_host_and_scheme
- from urllib.parse import quote
-else:
- from django.utils.translation import ugettext as _
- from django.utils.http import is_safe_url as url_has_allowed_host_and_scheme, urlquote as quote
-
+from useradmin.models import User
LOG = logging.getLogger()
@@ -94,6 +94,7 @@
HUE_LB_HOSTS = [urlparse(hue_lb).netloc for hue_lb in HUE_LOAD_BALANCER.get()] if HUE_LOAD_BALANCER.get() else []
+
class AjaxMiddleware(MiddlewareMixin):
"""
Middleware that augments request to set request.ajax
@@ -112,7 +113,7 @@ class ExceptionMiddleware(MiddlewareMixin):
def process_exception(self, request, exception):
tb = traceback.format_exc()
logging.info("Processing exception: %s: %s" % (
- i18n.smart_unicode(exception), i18n.smart_unicode(tb))
+ i18n.smart_str(exception), i18n.smart_str(tb))
)
if isinstance(exception, PopupException):
@@ -217,7 +218,8 @@ def process_view(self, request, view_func, view_args, view_kwargs):
ret = None
for middleware in self._get_middlewares(request._desktop_app, 'view'):
ret = middleware(request, view_func, view_args, view_kwargs)
- if ret: return ret # Short circuit
+ if ret:
+ return ret # Short circuit
return ret
def process_response(self, request, response):
@@ -240,7 +242,8 @@ def process_exception(self, request, exception):
ret = None
for middleware in self._get_middlewares(request._desktop_app, 'exception'):
ret = middleware(request, exception)
- if ret: return ret # short circuit
+ if ret:
+ return ret # short circuit
return ret
def _load_app_middleware(cls, app):
@@ -256,7 +259,7 @@ def _load_app_middleware(cls, app):
dot = middleware_path.rindex('.')
except ValueError:
raise exceptions.ImproperlyConfigured(_('%(module)s isn\'t a middleware module.') % {'module': middleware_path})
- mw_module, mw_classname = middleware_path[:dot], middleware_path[dot+1:]
+ mw_module, mw_classname = middleware_path[:dot], middleware_path[dot + 1:]
try:
mod = __import__(mw_module, {}, {}, [''])
except ImportError as e:
@@ -279,7 +282,7 @@ def _load_app_middleware(cls, app):
# We need to make sure we don't have a process_request function because we don't know what
# application will handle the request at the point process_request is called
if hasattr(mw_instance, 'process_request'):
- raise exceptions.ImproperlyConfigured(_('AppSpecificMiddleware module "%(module)s" has a process_request function' + \
+ raise exceptions.ImproperlyConfigured(_('AppSpecificMiddleware module "%(module)s" has a process_request function' +
' which is impossible.') % {'module': middleware_path})
if hasattr(mw_instance, 'process_view'):
result['view'].append(mw_instance.process_view)
@@ -316,7 +319,7 @@ def process_view(self, request, view_func, view_args, view_kwargs):
return None
if AUTH.AUTO_LOGIN_ENABLED.get() and request.path.startswith('/api/v1/token/auth'):
- pass # allow /api/token/auth can create user or make it active
+ pass # allow /api/token/auth can create user or make it active
elif request.path.startswith('/api/'):
return None
@@ -410,7 +413,7 @@ def process_view(self, request, view_func, view_args, view_kwargs):
REDIRECT_FIELD_NAME,
quote('/hue' + request.get_full_path().replace('is_embeddable=true', '').replace('&&', '&'))
)
- }) # Remove embeddable so redirect from & to login works. Login page is not embeddable
+ }) # Remove embeddable so redirect from & to login works. Login page is not embeddable
else:
return HttpResponseRedirect("%s?%s=%s" % (settings.LOGIN_URL, REDIRECT_FIELD_NAME, quote(request.get_full_path())))
@@ -557,7 +560,7 @@ def process_response(self, request, response):
try:
fn = resolve(request.path)[0]
fn_name = '%s.%s' % (fn.__module__, fn.__name__)
- except:
+ except Exception:
LOG.exception('failed to resolve url')
fn_name = ''
@@ -598,7 +601,7 @@ class ProxyMiddleware(MiddlewareMixin):
def __init__(self, get_response):
self.get_response = get_response
- if not 'desktop.auth.backend.AllowAllBackend' in AUTH.BACKEND.get():
+ if 'desktop.auth.backend.AllowAllBackend' not in AUTH.BACKEND.get():
LOG.info('Unloading ProxyMiddleware')
raise exceptions.MiddlewareNotUsed
@@ -635,7 +638,7 @@ def process_request(self, request):
'operationText': msg
}
return
- except:
+ except Exception:
LOG.exception('Unexpected error when authenticating')
return
@@ -764,7 +767,7 @@ def process_request(self, request):
}
access_warn(request, msg)
return
- except:
+ except Exception:
LOG.exception('Unexpected error when authenticating against KDC')
return
else:
@@ -831,7 +834,7 @@ class HueRemoteUserMiddleware(RemoteUserMiddleware):
in use.
"""
def __init__(self, get_response):
- if not 'desktop.auth.backend.RemoteUserDjangoBackend' in AUTH.BACKEND.get():
+ if 'desktop.auth.backend.RemoteUserDjangoBackend' not in AUTH.BACKEND.get():
LOG.info('Unloading HueRemoteUserMiddleware')
raise exceptions.MiddlewareNotUsed
super().__init__(get_response)
@@ -874,6 +877,7 @@ def process_response(self, request, response):
else:
return response
+
class MetricsMiddleware(MiddlewareMixin):
"""
Middleware to track the number of active requests.
@@ -908,7 +912,7 @@ def __init__(self, get_response):
raise exceptions.MiddlewareNotUsed
def process_response(self, request, response):
- if self.secure_content_security_policy and not 'Content-Security-Policy' in response:
+ if self.secure_content_security_policy and 'Content-Security-Policy' not in response:
response["Content-Security-Policy"] = self.secure_content_security_policy
return response
@@ -933,6 +937,7 @@ def process_response(self, request, response):
return response
+
class MultipleProxyMiddleware:
FORWARDED_FOR_FIELDS = [
'HTTP_X_FORWARDED_FOR',
@@ -959,7 +964,7 @@ def __call__(self, request):
location += 1
else:
request.META['HTTP_X_FORWARDED_FOR'] = item.strip()
- break;
+ break
for field in self.FORWARDED_FOR_FIELDS:
if field in request.META:
@@ -985,4 +990,4 @@ def process_response(self, request, response):
response['Cache-Control'] = 'no-cache, no-store, must-revalidate'
response['Pragma'] = 'no-cache'
response['Expires'] = '0'
- return response
\ No newline at end of file
+ return response
diff --git a/desktop/core/src/desktop/middleware_test.py b/desktop/core/src/desktop/middleware_test.py
index 3ec6189269c..0d8da6a9724 100644
--- a/desktop/core/src/desktop/middleware_test.py
+++ b/desktop/core/src/desktop/middleware_test.py
@@ -15,29 +15,25 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-import json
import os
-import pytest
import sys
+import json
import tempfile
+from unittest.mock import Mock, patch
+import pytest
from django.conf import settings
-from django.test.client import Client
-from django.test import RequestFactory, TestCase
-from django.http import HttpResponse
from django.core import exceptions
+from django.http import HttpResponse
+from django.test import RequestFactory, TestCase
+from django.test.client import Client
import desktop.conf
-
-from desktop.middleware import CacheControlMiddleware, MultipleProxyMiddleware
from desktop.conf import AUDIT_EVENT_LOG_DIR, CUSTOM_CACHE_CONTROL
from desktop.lib.django_test_util import make_logged_in_client
from desktop.lib.test_utils import add_permission
+from desktop.middleware import CacheControlMiddleware, MultipleProxyMiddleware
-if sys.version_info[0] > 2:
- from unittest.mock import patch, Mock
-else:
- from mock import patch, Mock
@pytest.mark.django_db
def test_view_perms():
@@ -60,7 +56,7 @@ def test_view_perms():
response = c.get("/useradmin/users/edit/test")
assert 401 == response.status_code
- response = c.get("/useradmin/users/edit/user") # Can access his profile page
+ response = c.get("/useradmin/users/edit/user") # Can access his profile page
assert 200 == response.status_code, response.content
@@ -92,7 +88,7 @@ def test_audit_logging_middleware_enable():
with tempfile.NamedTemporaryFile("w+t") as log_tmp:
log_path = log_tmp.name
reset = AUDIT_EVENT_LOG_DIR.set_for_testing(log_path)
- settings.MIDDLEWARE.append('desktop.middleware.AuditLoggingMiddleware') # Re-add middleware
+ settings.MIDDLEWARE.append('desktop.middleware.AuditLoggingMiddleware') # Re-add middleware
try:
# Check if we audit correctly
@@ -110,6 +106,7 @@ def test_audit_logging_middleware_enable():
settings.MIDDLEWARE.pop()
reset()
+
@pytest.mark.django_db
def test_audit_logging_middleware_disable():
c = make_logged_in_client(username='test_audit_logging', is_superuser=False)
@@ -118,7 +115,7 @@ def test_audit_logging_middleware_disable():
try:
# No middleware yet
response = c.get("/oozie/")
- assert not 'audited' in response, response
+ assert 'audited' not in response, response
finally:
reset()
@@ -139,7 +136,7 @@ def test_ensure_safe_redirect_middleware():
assert 302 == response.status_code
# Disallow most redirects
- done.append(desktop.conf.REDIRECT_WHITELIST.set_for_testing('^\d+$'))
+ done.append(desktop.conf.REDIRECT_WHITELIST.set_for_testing(r'^\d+$'))
response = c.post("/hue/accounts/login/", {
'username': 'test',
'password': 'test',
@@ -158,7 +155,7 @@ def test_ensure_safe_redirect_middleware():
# Allow all redirects and disallow most at the same time.
# should have a logic OR functionality.
- done.append(desktop.conf.REDIRECT_WHITELIST.set_for_testing('\d+,.*'))
+ done.append(desktop.conf.REDIRECT_WHITELIST.set_for_testing(r'\d+,.*'))
response = c.post("", {
'username': 'test',
'password': 'test',
@@ -170,6 +167,7 @@ def test_ensure_safe_redirect_middleware():
for finish in done:
finish()
+
@pytest.mark.django_db
def test_spnego_middleware():
done = []
@@ -209,6 +207,7 @@ def test_spnego_middleware():
finish()
settings.AUTHENTICATION_BACKENDS = orig_backends
+
def test_cache_control_middleware():
c = Client()
request = c.get("/")
@@ -238,9 +237,11 @@ def dummy_get_response(request):
finally:
reset()
+
def get_response(request):
return request
+
@pytest.mark.django_db
class TestMultipleProxyMiddleware(TestCase):
@@ -266,4 +267,3 @@ def test_multiple_proxy_middleware_without_x_forwarded_for(self):
request.META['REMOTE_ADDR'] = '192.0.2.0'
self.middleware(request)
assert request.META['HTTP_X_FORWARDED_FOR'] == '192.0.2.0'
-
diff --git a/desktop/core/src/desktop/models.py b/desktop/core/src/desktop/models.py
index 3c7285c5c23..90200207943 100644
--- a/desktop/core/src/desktop/models.py
+++ b/desktop/core/src/desktop/models.py
@@ -15,10 +15,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-from __future__ import absolute_import
-
import os
-import sys
import json
import uuid
import logging
@@ -26,6 +23,7 @@
from builtins import next, object
from collections import OrderedDict
from itertools import chain
+from urllib.parse import quote as urllib_quote
from django.contrib.auth.validators import UnicodeUsernameValidator
from django.contrib.contenttypes.fields import GenericForeignKey, GenericRelation
@@ -35,6 +33,7 @@
from django.db.models import Q
from django.db.models.query import QuerySet
from django.urls import NoReverseMatch, reverse
+from django.utils.translation import gettext as _, gettext_lazy as _t
from dashboard.conf import HAS_REPORT_ENABLED, IS_ENABLED as DASHBOARD_ENABLED, get_engines
from desktop import appmanager
@@ -73,16 +72,6 @@
from useradmin.models import Group, User, get_organization
from useradmin.organization import _fitered_queryset
-if sys.version_info[0] > 2:
- from urllib.parse import quote as urllib_quote
-
- from django.utils.translation import gettext as _, gettext_lazy as _t
-else:
- from urllib import quote as urllib_quote
-
- from django.utils.translation import ugettext as _, ugettext_lazy as _t
-
-
LOG = logging.getLogger()
SAMPLE_USER_ID = 1100713
diff --git a/desktop/core/src/desktop/monkey_patches.py b/desktop/core/src/desktop/monkey_patches.py
index d8c8cecfc29..16f450359e1 100644
--- a/desktop/core/src/desktop/monkey_patches.py
+++ b/desktop/core/src/desktop/monkey_patches.py
@@ -15,12 +15,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+import re
import hashlib
-import imp
-import importlib
import logging
-import re
-import sys
+import importlib
from django.conf import settings
from django.core.validators import RegexValidator
@@ -39,8 +37,8 @@ def monkey_patch_username_validator():
regular expression inside the username validator.
"""
- from useradmin.models import User
from desktop.lib.django_util import get_username_re_rule
+ from useradmin.models import User
username = User._meta.get_field("username")
@@ -93,17 +91,16 @@ def monkey_patch_md5(modules_to_patch):
Modules must use `import hashlib` and not `from hashlib import md5`.
"""
orig_hashlib_md5 = hashlib.md5
+
def _non_security_md5(*args, **kwargs):
kwargs['usedforsecurity'] = False
return orig_hashlib_md5(*args, **kwargs)
LOG.debug("Start monkey patch md5 ...")
- if sys.version_info[0] > 2:
- hashlib_spec = importlib.util.find_spec('hashlib')
- patched_hashlib = importlib.util.module_from_spec(hashlib_spec)
- hashlib_spec.loader.exec_module(patched_hashlib)
- else:
- patched_hashlib = imp.load_module('hashlib', *imp.find_module('hashlib'))
+
+ hashlib_spec = importlib.util.find_spec('hashlib')
+ patched_hashlib = importlib.util.module_from_spec(hashlib_spec)
+ hashlib_spec.loader.exec_module(patched_hashlib)
patched_hashlib.md5 = _non_security_md5
diff --git a/desktop/core/src/desktop/require_login_test.py b/desktop/core/src/desktop/require_login_test.py
index f5e4eaf1835..6f606654f13 100644
--- a/desktop/core/src/desktop/require_login_test.py
+++ b/desktop/core/src/desktop/require_login_test.py
@@ -19,15 +19,11 @@
import sys
-import pytest
+from unittest.mock import Mock
-from django.test.client import Client
import django
-
-if sys.version_info[0] > 2:
- from unittest.mock import Mock
-else:
- from mock import Mock
+import pytest
+from django.test.client import Client
@pytest.mark.django_db
diff --git a/desktop/core/src/desktop/settings.py b/desktop/core/src/desktop/settings.py
index ca0f93188a4..f2e40390146 100644
--- a/desktop/core/src/desktop/settings.py
+++ b/desktop/core/src/desktop/settings.py
@@ -30,6 +30,7 @@
from builtins import map, zip
import pkg_resources
+from django.utils.translation import gettext_lazy as _
import desktop.redaction
from aws.conf import is_enabled as is_s3_enabled
@@ -40,11 +41,6 @@
from desktop.lib.paths import get_desktop_root, get_run_root
from desktop.lib.python_util import force_dict_to_strings
-if sys.version_info[0] > 2:
- from django.utils.translation import gettext_lazy as _
-else:
- from django.utils.translation import ugettext_lazy as _
-
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(__file__)), '..', '..', '..'))
@@ -527,8 +523,7 @@
AUTHENTICATION_BACKENDS = tuple(desktop.conf.AUTH.BACKEND.get())
# AxesBackend should be the first backend in the AUTHENTICATION_BACKENDS list.
-if sys.version_info[0] > 2:
- AUTHENTICATION_BACKENDS = ('axes.backends.AxesBackend',) + AUTHENTICATION_BACKENDS
+AUTHENTICATION_BACKENDS = ('axes.backends.AxesBackend',) + AUTHENTICATION_BACKENDS
EMAIL_HOST = desktop.conf.SMTP.HOST.get()
EMAIL_PORT = desktop.conf.SMTP.PORT.get()
@@ -708,10 +703,7 @@ def is_oidc_configured():
# Instrumentation
if desktop.conf.INSTRUMENTATION.get():
- if sys.version_info[0] > 2:
- gc.set_debug(gc.DEBUG_UNCOLLECTABLE)
- else:
- gc.set_debug(gc.DEBUG_UNCOLLECTABLE | gc.DEBUG_OBJECTS)
+ gc.set_debug(gc.DEBUG_UNCOLLECTABLE)
if not desktop.conf.DATABASE_LOGGING.get():
@@ -828,10 +820,7 @@ def tracer():
'django.utils.cache',
)
-if sys.version_info[0] > 2:
- MIDDLEWARE.append('axes.middleware.AxesMiddleware') # AxesMiddleware should be the last middleware in the MIDDLEWARE list.
-else:
- MIDDLEWARE.remove('desktop.middleware.MultipleProxyMiddleware')
+MIDDLEWARE.append('axes.middleware.AxesMiddleware') # AxesMiddleware should be the last middleware in the MIDDLEWARE list.
try:
import hashlib
diff --git a/desktop/core/src/desktop/supervisor.py b/desktop/core/src/desktop/supervisor.py
index 5f07500fb22..b77b26662ab 100644
--- a/desktop/core/src/desktop/supervisor.py
+++ b/desktop/core/src/desktop/supervisor.py
@@ -31,45 +31,27 @@
"""
from __future__ import print_function
-from builtins import range
-from builtins import object
-import daemon
-import grp
-import logging
-import optparse
+
import os
-import pkg_resources
+import grp
import pwd
-import signal
-import subprocess
import sys
-import threading
import time
+import signal
+import logging
+import optparse
+import threading
+import subprocess
+from builtins import object, range
-import desktop.lib.daemon_utils
-import desktop.lib.paths
-import desktop.log
-
-
-if sys.version_info[0] > 2:
- from daemon.pidfile import TimeoutPIDLockFile
- from daemon.daemon import DaemonContext
- open_file = open
-else:
- from daemon.pidlockfile import PIDLockFile
- open_file = file
-
- class TimeoutPIDLockFile(PIDLockFile):
- """A PIDLockFile subclass that passes through a timeout on acquisition."""
-
- def __init__(self, lockfile, timeout, **kwargs):
- PIDLockFile.__init__(self, lockfile, **kwargs)
- self.timeout = timeout
-
- def __enter__(self):
- super(TimeoutPIDLockFile, self).acquire(timeout=self.timeout)
- return self
+import daemon
+import pkg_resources
+from daemon.daemon import DaemonContext
+from daemon.pidfile import TimeoutPIDLockFile
+import desktop.log
+import desktop.lib.paths
+import desktop.lib.daemon_utils
PROC_NAME = 'supervisor'
LOG = logging.getLogger()
@@ -105,6 +87,7 @@ def __enter__(self):
# killing them forceably (seconds)
WAIT_FOR_DEATH = 5
+
class SuperviseeSpec(object):
"""
A specification of something that should be supervised.
@@ -119,6 +102,7 @@ def __init__(self, drop_root=True):
"""
self.drop_root = drop_root
+
class DjangoCommandSupervisee(SuperviseeSpec):
"""A supervisee which is simply a desktop django management command."""
def __init__(self, django_command, **kwargs):
@@ -129,6 +113,7 @@ def __init__(self, django_command, **kwargs):
def cmdv(self):
return [HUE_BIN, self.django_command]
+
class Supervisor(threading.Thread):
"""A thread responsible for keeping the supervised subprocess running"""
# States of the subprocess
@@ -149,7 +134,7 @@ def run(self):
while True:
self.state = Supervisor.RUNNING
LOG.info("Starting process %s" % proc_str)
- pipe = subprocess.Popen(self.cmdv, close_fds=True, stdin=open_file("/dev/null"), **self.popen_kwargs)
+ pipe = subprocess.Popen(self.cmdv, close_fds=True, stdin=open("/dev/null"), **self.popen_kwargs)
LOG.info("Started proceses (pid %s) %s" % (pipe.pid, proc_str))
CHILD_PIDS.append(pipe.pid)
exitcode = pipe.wait()
@@ -217,9 +202,11 @@ def shutdown(sups):
sys.exit(1)
+
def sig_handler(signum, frame):
raise SystemExit("Signal %d received. Exiting" % signum)
+
def parse_args():
parser = optparse.OptionParser()
parser.add_option("-d", "--daemon", dest="daemonize", action="store_true", default=False)
@@ -235,9 +222,11 @@ def parse_args():
(options, args) = parser.parse_args()
return options
+
def get_pid_cmdline(pid):
return subprocess.Popen(["ps", "-p", str(pid), "-o", "cmd", "h"], stdout=subprocess.PIPE, close_fds=True).communicate()[0]
+
def get_supervisees():
"""Pull the supervisor specifications out of the entry point."""
eps = list(pkg_resources.iter_entry_points(ENTRY_POINT_GROUP))
@@ -333,10 +322,7 @@ def main():
pidfile_context.break_lock()
if options.daemonize:
- if sys.version_info[0] > 2:
- outfile = open_file(os.path.join(log_dir, 'supervisor.out'), 'ba+', 0)
- else:
- outfile = open_file(os.path.join(log_dir, 'supervisor.out'), 'a+', 0)
+ outfile = open(os.path.join(log_dir, 'supervisor.out'), 'ba+', 0)
context = daemon.DaemonContext(
working_directory=root,
pidfile=pidfile_context,
@@ -368,10 +354,7 @@ def main():
preexec_fn = None
if options.daemonize:
- if sys.version_info[0] > 2:
- log_stdout = open_file(os.path.join(log_dir, name + '.out'), 'ba+', 0)
- else:
- log_stdout = open_file(os.path.join(log_dir, name + '.out'), 'a+', 0)
+ log_stdout = open(os.path.join(log_dir, name + '.out'), 'ba+', 0)
log_stderr = log_stdout
else:
# Passing None to subprocess.Popen later makes the subprocess inherit the
diff --git a/desktop/core/src/desktop/templates/500.mako b/desktop/core/src/desktop/templates/500.mako
index cfe507d5164..54fc58c7916 100644
--- a/desktop/core/src/desktop/templates/500.mako
+++ b/desktop/core/src/desktop/templates/500.mako
@@ -16,7 +16,7 @@
<%!
import sys
-from desktop.lib.i18n import smart_unicode
+from desktop.lib.i18n import smart_str
from desktop.views import commonheader, commonfooter
if sys.version_info[0] > 2:
from django.utils.translation import gettext as _
@@ -66,9 +66,9 @@ ${ commonheader(_('500 - Server error'), "", user, request) | n,unicode }
% for (file_name, line_number, function_name, text) in traceback:
- ${smart_unicode(file_name) or ""} |
- ${smart_unicode(line_number) or ""} |
- ${smart_unicode(function_name) or ""} |
+ ${smart_str(file_name) or ""} |
+ ${smart_str(line_number) or ""} |
+ ${smart_str(function_name) or ""} |
% endfor
diff --git a/desktop/core/src/desktop/templates/common_footer.mako b/desktop/core/src/desktop/templates/common_footer.mako
index d80aa2c546b..79c58bf6a85 100644
--- a/desktop/core/src/desktop/templates/common_footer.mako
+++ b/desktop/core/src/desktop/templates/common_footer.mako
@@ -17,7 +17,7 @@
import sys
from django.http import HttpRequest
-from desktop.lib.i18n import smart_unicode
+from desktop.lib.i18n import smart_str
from desktop.views import login_modal
if sys.version_info[0] > 2:
@@ -29,7 +29,7 @@ else:
<%namespace name="commonHeaderFooterComponents" file="/common_header_footer_components.mako" />
% if request is not None:
-${ smart_unicode(login_modal(request).content) | n,unicode }
+${ smart_str(login_modal(request).content) | n,unicode }
% endif
diff --git a/desktop/core/src/desktop/templates/common_footer_m.mako b/desktop/core/src/desktop/templates/common_footer_m.mako
index 723eb7b11fd..19cf13b9b76 100644
--- a/desktop/core/src/desktop/templates/common_footer_m.mako
+++ b/desktop/core/src/desktop/templates/common_footer_m.mako
@@ -17,7 +17,6 @@
import sys
from django.http import HttpRequest
from django.template.defaultfilters import escape, escapejs
-from desktop.lib.i18n import smart_unicode
if sys.version_info[0] > 2:
from django.utils.translation import gettext as _
diff --git a/desktop/core/src/desktop/templates/common_header.mako b/desktop/core/src/desktop/templates/common_header.mako
index 78dce8825c0..edae07ad5a4 100644
--- a/desktop/core/src/desktop/templates/common_header.mako
+++ b/desktop/core/src/desktop/templates/common_header.mako
@@ -23,7 +23,7 @@ from desktop import conf
from desktop.auth.backend import is_admin
from desktop.conf import USE_NEW_EDITOR
from desktop.models import hue_version
-from desktop.lib.i18n import smart_unicode
+from desktop.lib.i18n import smart_str
from desktop.webpack_utils import get_hue_bundles
if sys.version_info[0] > 2:
@@ -55,7 +55,7 @@ if USE_NEW_EDITOR.get():
<%def name="get_title(title)">
% if title:
- - ${smart_unicode(title)}
+ - ${smart_str(title)}
% endif
%def>
diff --git a/desktop/core/src/desktop/templates/common_header_footer_components.mako b/desktop/core/src/desktop/templates/common_header_footer_components.mako
index 5be3e6c6f80..14d3ceef759 100644
--- a/desktop/core/src/desktop/templates/common_header_footer_components.mako
+++ b/desktop/core/src/desktop/templates/common_header_footer_components.mako
@@ -19,7 +19,6 @@ import sys
from django.template.defaultfilters import escape, escapejs
from desktop import conf
-from desktop.lib.i18n import smart_unicode
from desktop.views import _ko
from beeswax.conf import LIST_PARTITIONS_LIMIT
diff --git a/desktop/core/src/desktop/templates/common_header_m.mako b/desktop/core/src/desktop/templates/common_header_m.mako
index 257e8549763..9f0bb8a0b81 100644
--- a/desktop/core/src/desktop/templates/common_header_m.mako
+++ b/desktop/core/src/desktop/templates/common_header_m.mako
@@ -17,7 +17,7 @@
import sys
from desktop import conf
-from desktop.lib.i18n import smart_unicode
+from desktop.lib.i18n import smart_str
from desktop.webpack_utils import get_hue_bundles
from metadata.conf import has_optimizer, OPTIMIZER
@@ -47,7 +47,7 @@ if USE_NEW_EDITOR.get():
%def>
<%def name="get_title(title)">
% if title:
- ${smart_unicode(title).upper()}
+ ${smart_str(title).upper()}
% endif
%def>
diff --git a/desktop/core/src/desktop/templates/common_notebook_ko_components.mako b/desktop/core/src/desktop/templates/common_notebook_ko_components.mako
index 26df0258c97..d8c835c03b3 100644
--- a/desktop/core/src/desktop/templates/common_notebook_ko_components.mako
+++ b/desktop/core/src/desktop/templates/common_notebook_ko_components.mako
@@ -19,7 +19,6 @@ import logging
import sys
from desktop import conf
-from desktop.lib.i18n import smart_unicode
from desktop.views import _ko
from beeswax.conf import DOWNLOAD_ROW_LIMIT, DOWNLOAD_BYTES_LIMIT
diff --git a/desktop/core/src/desktop/templates/config_ko_components.mako b/desktop/core/src/desktop/templates/config_ko_components.mako
index 3180a35580e..5661c05e351 100644
--- a/desktop/core/src/desktop/templates/config_ko_components.mako
+++ b/desktop/core/src/desktop/templates/config_ko_components.mako
@@ -17,7 +17,6 @@
<%!
import sys
from desktop import conf
-from desktop.lib.i18n import smart_unicode
from desktop.views import _ko
if sys.version_info[0] > 2:
diff --git a/desktop/core/src/desktop/templates/document_browser.mako b/desktop/core/src/desktop/templates/document_browser.mako
index 08fae1c765d..64d3dd83ba0 100644
--- a/desktop/core/src/desktop/templates/document_browser.mako
+++ b/desktop/core/src/desktop/templates/document_browser.mako
@@ -18,7 +18,6 @@
import sys
from desktop import conf
-from desktop.lib.i18n import smart_unicode
from desktop.views import _ko
if sys.version_info[0] > 2:
diff --git a/desktop/core/src/desktop/templates/error.mako b/desktop/core/src/desktop/templates/error.mako
index 8981f00a972..53a1a5b3070 100644
--- a/desktop/core/src/desktop/templates/error.mako
+++ b/desktop/core/src/desktop/templates/error.mako
@@ -16,7 +16,7 @@
<%!
import sys
from desktop.views import commonheader, commonfooter
-from desktop.lib.i18n import smart_unicode
+from desktop.lib.i18n import smart_str
from desktop import conf
from desktop.auth.backend import is_admin
@@ -37,11 +37,11 @@ ${ commonheader(_('Error'), app_name, user, request, "40px") | n,unicode }
${ _('Error!') }
-
${ smart_unicode(error) }
+
${ smart_str(error) }
%if traceback and is_admin(user):
%endif
diff --git a/desktop/core/src/desktop/templates/hue.mako b/desktop/core/src/desktop/templates/hue.mako
index 50dee07a29c..c012831c5f5 100644
--- a/desktop/core/src/desktop/templates/hue.mako
+++ b/desktop/core/src/desktop/templates/hue.mako
@@ -20,7 +20,7 @@
from desktop import conf
from desktop.auth.backend import is_admin
from desktop.conf import ENABLE_HUE_5, has_multi_clusters
- from desktop.lib.i18n import smart_unicode
+ from desktop.lib.i18n import smart_str
from desktop.models import hue_version
from desktop.views import _ko, commonshare, login_modal
from desktop.webpack_utils import get_hue_bundles
@@ -328,7 +328,7 @@ ${ hueAceAutocompleter.hueAceAutocompleter() }
${ commonHeaderFooterComponents.header_pollers(user, is_s3_enabled, apps) }
% if request is not None:
-${ smart_unicode(login_modal(request).content) | n,unicode }
+${ smart_str(login_modal(request).content) | n,unicode }
% endif
diff --git a/desktop/core/src/desktop/templates/ko_editor.mako b/desktop/core/src/desktop/templates/ko_editor.mako
index ddd96dd39d8..6296fec1639 100644
--- a/desktop/core/src/desktop/templates/ko_editor.mako
+++ b/desktop/core/src/desktop/templates/ko_editor.mako
@@ -17,7 +17,6 @@
<%!
import sys
from desktop import conf
-from desktop.lib.i18n import smart_unicode
from desktop.views import _ko
if sys.version_info[0] > 2:
diff --git a/desktop/core/src/desktop/templates/ko_metastore.mako b/desktop/core/src/desktop/templates/ko_metastore.mako
index 8712a29f176..8c66d65d514 100644
--- a/desktop/core/src/desktop/templates/ko_metastore.mako
+++ b/desktop/core/src/desktop/templates/ko_metastore.mako
@@ -17,7 +17,6 @@
<%!
import sys
from desktop import conf
-from desktop.lib.i18n import smart_unicode
if sys.version_info[0] > 2:
from django.utils.translation import gettext as _
else:
diff --git a/desktop/core/src/desktop/templates/logs.mako b/desktop/core/src/desktop/templates/logs.mako
index a059e8ebc2f..f9394d680c5 100644
--- a/desktop/core/src/desktop/templates/logs.mako
+++ b/desktop/core/src/desktop/templates/logs.mako
@@ -18,7 +18,7 @@ import re
import sys
from desktop.lib.conf import BoundConfig
-from desktop.lib.i18n import smart_unicode
+from desktop.lib.i18n import smart_str
from desktop.views import commonheader, commonfooter
if sys.version_info[0] > 2:
@@ -101,7 +101,7 @@ ${ layout.menubar(section='log_view') }
% for l in log:
-
${ smart_unicode(l, errors='ignore') }
+
${ smart_str(l, errors='ignore') }
% endfor
diff --git a/desktop/core/src/desktop/templates/popup_error.mako b/desktop/core/src/desktop/templates/popup_error.mako
index eebe7d2c0cb..2b38c8338fd 100644
--- a/desktop/core/src/desktop/templates/popup_error.mako
+++ b/desktop/core/src/desktop/templates/popup_error.mako
@@ -18,7 +18,7 @@
import sys
from desktop.views import commonheader, commonfooter
-from desktop.lib.i18n import smart_unicode
+from desktop.lib.i18n import smart_str
from desktop.auth.backend import is_admin
if sys.version_info[0] > 2:
@@ -38,9 +38,9 @@ ${ commonheader(title, "", user, request, "40px") | n,unicode }
${ _('Error!') }
-
${ smart_unicode(message) }
+
${ smart_str(message) }
% if detail:
-
${ smart_unicode(detail) }
+
${ smart_str(detail) }
% endif
@@ -65,9 +65,9 @@ ${ commonheader(title, "", user, request, "40px") | n,unicode }
% for (file_name, line_number, function_name, text) in traceback:
- ${smart_unicode(file_name) or ""} |
- ${smart_unicode(line_number) or ""} |
- ${smart_unicode(function_name) or ""} |
+ ${smart_str(file_name) or ""} |
+ ${smart_str(line_number) or ""} |
+ ${smart_str(function_name) or ""} |
% endfor
diff --git a/desktop/core/src/desktop/test_data/hue_5.10.db b/desktop/core/src/desktop/test_data/hue_5.10.db
index b89f25024ea63c28dedcb423f06b6c71c31b092d..cf0179c357ab3edfb4414b6d805d6ef4b39e51c4 100644
GIT binary patch
delta 18517
zcmc(Hd0-pWwfEeSX5Z|1QM}7`Vn=oyOO|C@@e(_Uv&V5l!rm&1rNkzV
!`Odu~jU?L*-+SL5pB?LH
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
delta 3381
zcmZ`*2~-qU7OlOi|Joqf2q>-K(x}kjO2!3~;10pK#3Uw&&?*K5r3JT$)$KBIh>ETL
z6HKBq6Jyl4F~`b@QHk5pL?cgoJATjLwHqxxVS#Zt{)ccBSu6+`j1R#BILM
z&g~R)PT&%C8t3>}sH5kqiTyMc&j&_F#5n;$%dWSaB8xOyj2x}UPNEZNCLKv1r-NvJnoN7pF4RdQX&5Ecpc1)58pto?2Xc{oL(Y*;$SHE1
z)R1?{-^ot0l~j>e$$GMytRPFsA~KgeL&lK|GMqe029UnQP2x!$=|Cb#C_zLc0=|WB
z;qgWJ;!Vy6_5@0jD0e^%S;n(miJOfX__u;#+8t#B^
zz}F#ztKmz~2NysuEPzvC7Muvjz>)AVH~{v639u`4!geqeLa2Zi&;YK1E8qh70(=Ti
zfn(qxu)uEc7N`OpH-NR^Wv~pCfw^E7m;rLYBrqOifMFmNq=015y*kA0Gy+ZSdR2Fi
zKQH5`@v1C}QR7-|z>$!S<)Oi`oYrasvl80lu))D`*q~Nhy&<6}sT$VS$Lvjo&T8nc
zkEu#^R%P1vWI$w?>)%q}FITi^M}f5(MkQ
zeDSKJ_0lxQu#SOj5*tXwC{T)4S%F33tP4n@^+6I};B`{Z>@|41wSThIo3d+Xu!i`=
zH)bzxza9^adur4i`%nTr4+ll*5D+jz~#X%pqRg8t7Dx(7kTxw%}x?eq&Xtz4dag
zPgRxpP|+0VIPxa0!xC(P13|fMz44vVQ(vSUQu3wurEt;I_NdOjXI0JL~(IzDXU$)1IeW``=VY7`+|%a~tW(JIQPKbGnf3r|qeS{6tRj>fA(L
zqyup<0L6<`W<*(F5
zO!N=5S=}jz{Lzo^^h5^x3}nhTOtJ+c77EG}
z_!xE~leedd`cL{`ZJl~m{FQbjYxv|btIKX}1ZOMs*>%ULMA3Ayk5B0iR=Y>T%o(Ch
zwkCh4kC7CAlv%>2wbMqjS8u>)7`_Q3SjfNOHyqTScG?(`oo%bVZCwr3@@2)O=0bMp
zIvl_%+iC+?O9RBDuym$pc2-_)Czly&E@1u!IFK#80S$Jd0cKcB9NKvi^NmZ=9o@T&
zOAEZ&jOR8^;kYRK!B1Yc`H1dVkSO}vvFm;k%?4f5li2AcWO-Gs^M2XhE#9DAlgbm3
zsyCN)vx_?f>7q26W|5;L7W>d~cnAE=)@VGf+tf;>i@cHt+_K%M7~-%VtI-c6R@J87
zZFaTwiT`bU^)btK8_Rav_U#6uJy9Ji*u@_OO%#9RO|T9A8o!Dfz){<;#xDJ)9@_Ne3
zD$LLQW3TW5vlBIj;Y<1>ESIQ7mg^&sBC@z6;Iv})@XfR8kAUYzg}-Pgf9p2IjP&KP
zGk#jhUO5cPt#QY|r;=hm>YK&7p8$uIB?-PF>-q`MUloI$GFTAzlE^doCO?)oi4~mC
z*Jwqi%9FvbHJM5RQCbA;xaL$>D=qvq9>`Bi2~GxEL7}b0*r{j8`=yIws8E3RaEYUG
z90Mio0$-+F;neU8*613LE7y!t(SDQ)TnX?w{wddxgSdY{f)3-
zEo!E5c6pINtI2YF5xYnYC4&dKo<7v~pF%ks{iQ|$HRX7x_#o71On|D^w&p3Z19Y;XU45F6YKBk(;h
zr!(}&vYn0a5ph~Dz9T!`2r+Xs!OmhQHn52o!IehXflX|JZP>OZ*j4Np^hU9tn_wxJ
z>v!r7mrGjeVl$iJCn5J&bxtWP%y(uyF^;Wnfn(VATQHI>y9GNTml@%Y(H&!xL^Fkb
zd<#az-iPP*5iLh^&}1|U4d9Y60s$@suW%9gJ{NzLa49T;Ps72m
zGYp3k7j|EO!(bbz1j~UJWP!3_AQ5x`z}8^9Wc$=sW7}kJy)LQjD^$m5UI#10`R!6A))i~9zv?!OAkGZVbrmR!S
zl&MO((pQOAB>A#@Qr<4FkxS%f*X6$CGnsFf`w{WlOtdNshpdunN&@Cb^ustGD4>tZj
W`h;~ld4Vr~QL`rKWShYo`+opt)d`0H
diff --git a/desktop/core/src/desktop/test_data/hue_5.11.db b/desktop/core/src/desktop/test_data/hue_5.11.db
index 9c5c99c5f144e93448ac907b7459ed882b0a8e01..f4ee9c6b5dd2070361af21dd0b0aceb681a009b4 100644
GIT binary patch
delta 20219
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
delta 4953
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