From a212641ad75d9d1223a22d2683991fa33443f6e6 Mon Sep 17 00:00:00 2001 From: Kirill Zaitsev Date: Mon, 30 Nov 2015 17:59:04 +0300 Subject: [PATCH] Introduced python3 support Replaces types with builtin functions Replaces force_unicode with force_text Adds six usage of .iteritems and .string_types where relevant Updates setup.cfg entries Targets blueprint: murano-python-3-support Change-Id: I369f79c4258367974eb676cecb6eb1c941803503 --- muranodashboard/api/__init__.py | 4 +-- muranodashboard/catalog/views.py | 3 ++- muranodashboard/common/utils.py | 5 ++-- muranodashboard/dynamic_ui/fields.py | 10 +++++--- muranodashboard/dynamic_ui/forms.py | 25 ++++++++++--------- muranodashboard/dynamic_ui/helpers.py | 18 +++++++------ muranodashboard/dynamic_ui/services.py | 10 ++++---- muranodashboard/dynamic_ui/yaql_functions.py | 9 +++---- muranodashboard/environments/api.py | 3 ++- muranodashboard/environments/topology.py | 9 ++++--- muranodashboard/packages/views.py | 5 ++-- .../notes/python3-cae8a08d96696550.yaml | 3 +++ setup.cfg | 11 ++++++-- tox.ini | 7 +++--- 14 files changed, 70 insertions(+), 52 deletions(-) create mode 100644 releasenotes/notes/python3-cae8a08d96696550.yaml diff --git a/muranodashboard/api/__init__.py b/muranodashboard/api/__init__.py index 737ffebf..f35031eb 100644 --- a/muranodashboard/api/__init__.py +++ b/muranodashboard/api/__init__.py @@ -16,7 +16,7 @@ import contextlib from django.conf import settings from django.contrib.messages import api as msg_api -from django.utils.encoding import force_unicode +from django.utils.encoding import force_text from django.utils.translation import ugettext_lazy as _ from horizon import exceptions import muranoclient.client as client @@ -33,7 +33,7 @@ LOG = logging.getLogger(__name__) def _handle_message(request, message): def horizon_message_already_queued(_message): - _message = force_unicode(_message) + _message = force_text(_message) if request.is_ajax(): for tag, msg, extra in request.horizon['async_messages']: if _message == msg: diff --git a/muranodashboard/catalog/views.py b/muranodashboard/catalog/views.py index ab31a1b3..aa55015d 100644 --- a/muranodashboard/catalog/views.py +++ b/muranodashboard/catalog/views.py @@ -41,6 +41,7 @@ from horizon.forms import views from horizon import messages from horizon import tabs from oslo_log import log as logging +import six from muranoclient.common import exceptions as exc from muranodashboard import api @@ -60,7 +61,7 @@ LATEST_APPS_QUEUE_LIMIT = 3 class DictToObj(object): def __init__(self, **kwargs): - for key, value in kwargs.iteritems(): + for key, value in six.iteritems(kwargs): setattr(self, key, value) diff --git a/muranodashboard/common/utils.py b/muranodashboard/common/utils.py index bb071047..dbf23f67 100644 --- a/muranodashboard/common/utils.py +++ b/muranodashboard/common/utils.py @@ -20,6 +20,7 @@ import bs4 import string from muranodashboard.dynamic_ui import yaql_expression +import six import yaql @@ -46,7 +47,7 @@ class Bunch(object): object-like attribute access. """ def __init__(self, **kwargs): - for key, value in kwargs.iteritems(): + for key, value in six.iteritems(kwargs): setattr(self, key, value) def __getitem__(self, item): @@ -62,7 +63,7 @@ class Bunch(object): return hasattr(self, item) def __iter__(self): - return iter(self.__dict__.itervalues()) + return iter(six.itervalues(self.__dict__)) class BlankFormatter(string.Formatter): diff --git a/muranodashboard/dynamic_ui/fields.py b/muranodashboard/dynamic_ui/fields.py index aec94b56..cfa61335 100644 --- a/muranodashboard/dynamic_ui/fields.py +++ b/muranodashboard/dynamic_ui/fields.py @@ -23,6 +23,7 @@ from django.core import validators as django_validator from django import forms from django.http import Http404 from django.template import defaultfilters +from django.utils.encoding import force_text from django.utils import html from django.utils.translation import ugettext_lazy as _ from horizon import exceptions @@ -31,6 +32,7 @@ from horizon import messages from openstack_dashboard.api import glance from openstack_dashboard.api import nova from oslo_log import log as logging +import six import yaql from muranoclient.common import exceptions as muranoclient_exc @@ -52,7 +54,7 @@ def with_request(func): """ def update(self, initial, request=None, **kwargs): initial_request = initial.get('request') - for key, value in initial.iteritems(): + for key, value in six.iteritems(initial): if key != 'request' and key not in kwargs: kwargs[key] = value @@ -157,11 +159,11 @@ class CustomPropertiesField(forms.Field): *args, **kwargs): self.description = description self.description_title = (description_title or - unicode(kwargs.get('label', ''))) + force_text(kwargs.get('label', ''))) for arg in FIELD_ARGS_TO_ESCAPE: if kwargs.get(arg): - kwargs[arg] = html.escape(unicode(kwargs[arg])) + kwargs[arg] = html.escape(force_text(kwargs[arg])) validators = [] for validator in kwargs.get('validators', []): @@ -384,7 +386,7 @@ class ImageChoiceField(ChoiceField): continue image_map[image.id] = title - for id_, title in sorted(image_map.iteritems(), key=lambda e: e[1]): + for id_, title in sorted(six.iteritems(image_map), key=lambda e: e[1]): image_choices.append((id_, title)) if image_choices: image_choices.insert(0, ("", _("Select Image"))) diff --git a/muranodashboard/dynamic_ui/forms.py b/muranodashboard/dynamic_ui/forms.py index 0ce4ad3d..ab0e207d 100644 --- a/muranodashboard/dynamic_ui/forms.py +++ b/muranodashboard/dynamic_ui/forms.py @@ -14,11 +14,11 @@ from collections import defaultdict import copy -import types from django import forms from django.utils.translation import ugettext_lazy as _ from oslo_log import log as logging +import six from yaql import legacy import muranodashboard.dynamic_ui.fields as fields @@ -63,7 +63,7 @@ TYPES_KWARGS = { def _collect_fields(field_specs, form_name, service): def process_widget(cls, kwargs): - if isinstance(cls, types.TupleType): + if isinstance(cls, tuple): cls, _w = cls kwargs['widget'] = _w @@ -85,24 +85,25 @@ def _collect_fields(field_specs, form_name, service): def parse_spec(spec, keys=None): if keys is None: keys = [] - if not isinstance(keys, types.ListType): + if not isinstance(keys, list): keys = [keys] key = keys and keys[-1] or None if isinstance(spec, yaql_expression.YaqlExpression): return key, fields.RawProperty(key, spec) - elif isinstance(spec, types.DictType): + elif isinstance(spec, dict): items = [] - for k, v in spec.iteritems(): + for k, v in six.iteritems(spec): k = helpers.decamelize(k) new_key, v = parse_spec(v, keys + [k]) if new_key: k = new_key items.append((k, v)) return key, dict(items) - elif isinstance(spec, types.ListType): + elif isinstance(spec, list): return key, [parse_spec(_spec, keys)[1] for _spec in spec] - elif isinstance(spec, basestring) and helpers.is_localizable(keys): + elif isinstance(spec, + six.string_types) and helpers.is_localizable(keys): return key, spec else: if key == 'hidden': @@ -159,7 +160,7 @@ class UpdatableFieldsForm(forms.Form): # collections.OrderedDict for Django >= 1.7 updated_fields = self.fields.__class__() - for name, field in self.fields.iteritems(): + for name, field in six.iteritems(self.fields): updated_fields[name] = field if (isinstance(field, fields.PasswordField) and not field.has_clone and field.original): @@ -168,7 +169,7 @@ class UpdatableFieldsForm(forms.Form): self.fields = updated_fields - for name, field in self.fields.iteritems(): + for name, field in six.iteritems(self.fields): if hasattr(field, 'update'): field.update(self.initial, form=self, request=request) if not field.required: @@ -188,12 +189,12 @@ class ServiceConfigurationForm(UpdatableFieldsForm): self.update_fields() def finalize_fields(self): - for field_name, field in self.fields.iteritems(): + for field_name, field in six.iteritems(self.fields): field.form = self validators = [] for v in field.validators: - expr = isinstance(v, types.DictType) and v.get('expr') + expr = isinstance(v, dict) and v.get('expr') if expr and isinstance(expr, fields.RawProperty): v = fields.make_yaql_validator(v) validators.append(v) @@ -215,7 +216,7 @@ class ServiceConfigurationForm(UpdatableFieldsForm): if error_messages: raise forms.ValidationError(error_messages) - for name, field in self.fields.iteritems(): + for name, field in six.iteritems(self.fields): if (isinstance(field, fields.PasswordField) and getattr(field, 'enabled', True)): field.compare(name, cleaned_data) diff --git a/muranodashboard/dynamic_ui/helpers.py b/muranodashboard/dynamic_ui/helpers.py index 2e471075..0b7b4244 100644 --- a/muranodashboard/dynamic_ui/helpers.py +++ b/muranodashboard/dynamic_ui/helpers.py @@ -17,6 +17,8 @@ import string import types import uuid +import six + from django.core import validators _LOCALIZABLE_KEYS = set(['label', 'help_text', 'error_messages']) @@ -47,7 +49,7 @@ def decamelize(name): def explode(_string): """Explodes a string into a list of one-character strings.""" - if not _string or not isinstance(_string, basestring): + if not _string or not isinstance(_string, six.string_types): return _string else: return list(_string) @@ -76,11 +78,11 @@ def recursive_apply(predicate, transformer, value, *args): def rec(val): if predicate(val, *args): return rec(transformer(val, *args)) - elif isinstance(val, types.DictType): - return dict((rec(k), rec(v)) for (k, v) in val.iteritems()) - elif isinstance(val, types.ListType): + elif isinstance(val, dict): + return dict((rec(k), rec(v)) for (k, v) in six.iteritems(val)) + elif isinstance(val, list): return [rec(v) for v in val] - elif isinstance(val, types.TupleType): + elif isinstance(val, tuple): return tuple([rec(v) for v in val]) elif isinstance(val, types.GeneratorType): return rec(val) @@ -106,9 +108,9 @@ def insert_hidden_ids(application): return rec(k), rec(v) def rec(val): - if isinstance(val, types.DictType): - return dict(wrap(k, v) for k, v in val.iteritems()) - elif isinstance(val, types.ListType): + if isinstance(val, dict): + return dict(wrap(k, v) for k, v in six.iteritems(val)) + elif isinstance(val, list): return [rec(v) for v in val] else: return val diff --git a/muranodashboard/dynamic_ui/services.py b/muranodashboard/dynamic_ui/services.py index 54d07d2f..b4b4175c 100644 --- a/muranodashboard/dynamic_ui/services.py +++ b/muranodashboard/dynamic_ui/services.py @@ -73,7 +73,7 @@ class Service(object): yaql_functions.register(self.context) self.forms = [] - for key, value in kwargs.iteritems(): + for key, value in six.iteritems(kwargs): setattr(self, key, value) if forms: @@ -107,13 +107,13 @@ class Service(object): @staticmethod def extract_form_data(data): - for form_name, form_data in data.iteritems(): + for form_name, form_data in six.iteritems(data): return form_name, form_data['fields'], form_data.get('validators', []) def extract_attributes(self): self.context['$'] = self.cleaned_data - for name, template in self.templates.iteritems(): + for name, template in six.iteritems(self.templates): self.context[name] = template if semantic_version.Version.coerce(self.spec_version) \ >= semantic_version.Version.coerce('2.2'): @@ -154,7 +154,7 @@ def import_app(request, app_id): app_version = ui_desc.pop('Version', version.LATEST_FORMAT_VERSION) version.check_version(app_version) service = dict( - (helpers.decamelize(k), v) for (k, v) in ui_desc.iteritems()) + (helpers.decamelize(k), v) for (k, v) in six.iteritems(ui_desc)) global _apps # In-memory caching of dynamic UI forms if app_id in _apps: @@ -240,7 +240,7 @@ def get_app_field_descriptions(request, app_id, index): form_cls = app.forms[index] descriptions = [] - for name, field in form_cls.base_fields.iteritems(): + for name, field in six.iteritems(form_cls.base_fields): title = field.description_title description = field.description if description: diff --git a/muranodashboard/dynamic_ui/yaql_functions.py b/muranodashboard/dynamic_ui/yaql_functions.py index ed7376ea..fca4a611 100644 --- a/muranodashboard/dynamic_ui/yaql_functions.py +++ b/muranodashboard/dynamic_ui/yaql_functions.py @@ -15,7 +15,6 @@ import random import string import time -import types from yaql.language import specs from yaql.language import yaqltypes @@ -48,10 +47,10 @@ def _generate_hostname(pattern, number): """ global _random_string_counter - if pattern and isinstance(pattern, types.UnicodeType): - return pattern.replace(u'#', unicode(number)) - elif pattern: - return pattern.replace('#', str(number)) + if pattern: + # NOTE(kzaitsev) works both for unicode and simple strings in py2 + # and works as expected in py3 + pattern.replace('#', str(number)) counter = _random_string_counter or 1 # generate first 5 random chars diff --git a/muranodashboard/environments/api.py b/muranodashboard/environments/api.py index e265a3e9..2039d4ec 100644 --- a/muranodashboard/environments/api.py +++ b/muranodashboard/environments/api.py @@ -15,6 +15,7 @@ from django.utils.translation import ugettext_lazy as _ from oslo_log import log as logging +import six from muranoclient.common import exceptions as exc from muranodashboard import api @@ -325,7 +326,7 @@ def extract_actions_list(service): return dict(_action.items() + [('id', action_id)]) return [make_action_datum(_id, action) for (_id, action) in - actions_data.iteritems() if action.get('enabled')] + six.iteritems(actions_data) if action.get('enabled')] def run_action(request, environment_id, action_id): diff --git a/muranodashboard/environments/topology.py b/muranodashboard/environments/topology.py index 436ae86c..1fc08f61 100644 --- a/muranodashboard/environments/topology.py +++ b/muranodashboard/environments/topology.py @@ -13,12 +13,13 @@ # under the License. import json -import types from django.contrib.staticfiles.templatetags.staticfiles import static from django.core.urlresolvers import reverse from django.template import loader +import six + from muranodashboard.api import packages as pkg_cli from muranodashboard.environments import consts @@ -152,7 +153,7 @@ def _split_seq_by_predicate(seq, predicate): def _is_atomic(elt): key, value = elt - return not isinstance(value, (types.DictType, types.ListType)) + return not isinstance(value, (dict, list)) def render_d3_data(request, environment): @@ -195,7 +196,7 @@ def render_d3_data(request, environment): node_type = node_data.get('?', {}).get('type') node_id = node_data.get('?', {}).get('id') atomics, containers = _split_seq_by_predicate( - node_data.iteritems(), _is_atomic) + six.iteritems(node_data), _is_atomic) if node_type and node_data is not parent_node: node = _create_empty_node() node_refs[node_id] = node @@ -230,7 +231,7 @@ def render_d3_data(request, environment): node = node_refs[node_id] atomics, containers = _split_seq_by_predicate( - node_data.iteritems(), _is_atomic) + six.iteritems(node_data), _is_atomic) # the actual second pass of node linking if parent_node is not None: diff --git a/muranodashboard/packages/views.py b/muranodashboard/packages/views.py index 29a42919..0f158b2f 100644 --- a/muranodashboard/packages/views.py +++ b/muranodashboard/packages/views.py @@ -37,6 +37,7 @@ from muranoclient.common import utils as muranoclient_utils from openstack_dashboard.api import glance from openstack_dashboard.api import keystone from oslo_log import log as logging +import six from muranodashboard import api from muranodashboard.api import packages as pkg_api @@ -235,7 +236,7 @@ class ImportBundleWizard(views.ModalFormMixin, continue reqs = package.requirements(base_url=base_url) - for dep_name, dep_package in reqs.iteritems(): + for dep_name, dep_package in six.iteritems(reqs): try: imgs = muranoclient_utils.ensure_images( glance_client=glance_client, @@ -470,7 +471,7 @@ class ImportPackageWizard(views.ModalFormMixin, original_package = reqs.pop(name) step_data['dependencies'] = [] step_data['images'] = [] - for dep_name, dep_package in reqs.iteritems(): + for dep_name, dep_package in six.iteritems(reqs): _ensure_images(dep_name, dep_package) try: files = {dep_name: dep_package.file()} diff --git a/releasenotes/notes/python3-cae8a08d96696550.yaml b/releasenotes/notes/python3-cae8a08d96696550.yaml new file mode 100644 index 00000000..2f834e0f --- /dev/null +++ b/releasenotes/notes/python3-cae8a08d96696550.yaml @@ -0,0 +1,3 @@ +--- +prelude: > + Murano-dashboard now supports python3 diff --git a/setup.cfg b/setup.cfg index 0e865f9d..9986a488 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,8 +20,8 @@ description-file = README.rst license = Apache License, Version 2.0 author = Mirantis, Inc. -author-email = murano-all@lists.openstack.org -home-page = https://launchpad.net/murano +author-email = openstack-dev@lists.openstack.org +home-page = http://docs.openstack.org/developer/murano/ classifier = Development Status :: 5 - Production/Stable Framework :: Django @@ -29,9 +29,16 @@ classifier = Environment :: OpenStack Intended Audience :: Developers Intended Audience :: Information Technology + Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: OS Independent + Operating System :: POSIX :: Linux Programming Language :: Python + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.4 [files] packages = diff --git a/tox.ini b/tox.ini index 0a302aba..93b5e500 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27,py33,py34,pep8 +envlist = py34,py27,pep8 minversion = 1.6 skipsdist = True @@ -60,7 +60,7 @@ import_exceptions = collections.defaultdict, django.core.urlresolvers.reverse_lazy, django.template.loader.render_to_string, django.test.utils.override_settings, - django.utils.encoding.force_unicode, + django.utils.encoding.force_text, django.utils.encoding.smart_text, django.utils.html.escape, django.utils.http.urlencode, @@ -68,5 +68,4 @@ import_exceptions = collections.defaultdict, django.utils.translation.pgettext_lazy, django.utils.translation.ugettext_lazy, django.utils.translation.ungettext_lazy, - operator.attrgetter, - StringIO.StringIO + operator.attrgetter