Mercurial > libervia-backend
diff sat/tools/common/template.py @ 3028:ab2696e34d29
Python 3 port:
/!\ this is a huge commit
/!\ starting from this commit, SàT is needs Python 3.6+
/!\ SàT maybe be instable or some feature may not work anymore, this will improve with time
This patch port backend, bridge and frontends to Python 3.
Roughly this has been done this way:
- 2to3 tools has been applied (with python 3.7)
- all references to python2 have been replaced with python3 (notably shebangs)
- fixed files not handled by 2to3 (notably the shell script)
- several manual fixes
- fixed issues reported by Python 3 that where not handled in Python 2
- replaced "async" with "async_" when needed (it's a reserved word from Python 3.7)
- replaced zope's "implements" with @implementer decorator
- temporary hack to handle data pickled in database, as str or bytes may be returned,
to be checked later
- fixed hash comparison for password
- removed some code which is not needed anymore with Python 3
- deactivated some code which needs to be checked (notably certificate validation)
- tested with jp, fixed reported issues until some basic commands worked
- ported Primitivus (after porting dependencies like urwid satext)
- more manual fixes
author | Goffi <goffi@goffi.org> |
---|---|
date | Tue, 13 Aug 2019 19:08:41 +0200 |
parents | d8857e913309 |
children | 5f3068915686 |
line wrap: on
line diff
--- a/sat/tools/common/template.py Wed Jul 31 11:31:22 2019 +0200 +++ b/sat/tools/common/template.py Tue Aug 13 19:08:41 2019 +0200 @@ -1,4 +1,4 @@ -#!/usr/bin/env python2 +#!/usr/bin/env python3 # -*- coding: utf-8 -*- # SAT: a jabber client @@ -41,8 +41,8 @@ import sat_templates except ImportError: raise exceptions.MissingModule( - u"sat_templates module is not available, please install it or check your path to " - u"use template engine" + "sat_templates module is not available, please install it or check your path to " + "use template engine" ) else: sat_templates # to avoid pyflakes warning @@ -51,8 +51,8 @@ import jinja2 except: raise exceptions.MissingModule( - u"Missing module jinja2, please install it from http://jinja.pocoo.org or with " - u"pip install jinja2" + "Missing module jinja2, please install it from http://jinja.pocoo.org or with " + "pip install jinja2" ) from jinja2 import Markup as safe @@ -67,9 +67,9 @@ HTML_EXT = ("html", "xhtml") RE_ATTR_ESCAPE = re.compile(r"[^a-z_-]") -SITE_RESERVED_NAMES = (u"sat",) -TPL_RESERVED_CHARS = ur"()/." -RE_TPL_RESERVED_CHARS = re.compile(u"[" + TPL_RESERVED_CHARS + u"]") +SITE_RESERVED_NAMES = ("sat",) +TPL_RESERVED_CHARS = r"()/." +RE_TPL_RESERVED_CHARS = re.compile("[" + TPL_RESERVED_CHARS + "]") TemplateData = namedtuple("TemplateData", ['site', 'theme', 'path']) @@ -85,8 +85,8 @@ as this allow the template to open any file on the system that the launching user can access. """ - if not sites_paths or not u"" in sites_paths: - raise exceptions.InternalError(u"Invalid sites_paths") + if not sites_paths or not "" in sites_paths: + raise exceptions.InternalError("Invalid sites_paths") super(jinja2.BaseLoader, self).__init__() self.sites_paths = sites_paths self.trusted = trusted @@ -108,49 +108,49 @@ site and theme can be both None if absolute path is used Relative path is the path from theme root dir e.g. blog/articles.html """ - if template.startswith(u"("): + if template.startswith("("): # site and/or theme are specified try: - theme_end = template.index(u")") + theme_end = template.index(")") except IndexError: - raise ValueError(u"incorrect site/theme in template") + raise ValueError("incorrect site/theme in template") theme_data = template[1:theme_end] - theme_splitted = theme_data.split(u'/') + theme_splitted = theme_data.split('/') if len(theme_splitted) == 1: - site, theme = u"", theme_splitted[0] + site, theme = "", theme_splitted[0] elif len(theme_splitted) == 2: site, theme = theme_splitted else: - raise ValueError(u"incorrect site/theme in template") + raise ValueError("incorrect site/theme in template") template_path = template[theme_end+1:] - if not template_path or template_path.startswith(u"/"): - raise ValueError(u"incorrect template path") - elif template.startswith(u"/"): + if not template_path or template_path.startswith("/"): + raise ValueError("incorrect template path") + elif template.startswith("/"): # this is an absolute path, so we have no site and no theme site = None theme = None template_path = template else: # a default template - site = u"" + site = "" theme = C.TEMPLATE_THEME_DEFAULT template_path = template if site is not None: site = site.strip() if not site: - site = u"" + site = "" elif site in SITE_RESERVED_NAMES: - raise ValueError(_(u"{site} can't be used as site name, " - u"it's reserved.").format(site=site)) + raise ValueError(_("{site} can't be used as site name, " + "it's reserved.").format(site=site)) if theme is not None: theme = theme.strip() if not theme: theme = C.TEMPLATE_THEME_DEFAULT if RE_TPL_RESERVED_CHARS.search(theme): - raise ValueError(_(u"{theme} contain forbidden char. Following chars " - u"are forbidden: {reserved}").format( + raise ValueError(_("{theme} contain forbidden char. Following chars " + "are forbidden: {reserved}").format( theme=theme, reserved=TPL_RESERVED_CHARS)) return TemplateData(site, theme, template_path) @@ -169,7 +169,7 @@ sites_and_themes.append([site, C.TEMPLATE_THEME_DEFAULT]) if site: # the site is not the default one, so we add default at the end - sites_and_themes.append([u'', C.TEMPLATE_THEME_DEFAULT]) + sites_and_themes.append(['', C.TEMPLATE_THEME_DEFAULT]) return sites_and_themes def _get_template_f(self, site, theme, path_elts): @@ -185,15 +185,15 @@ """ if site is None: raise exceptions.InternalError( - u"_get_template_f must not be used with absolute path") + "_get_template_f must not be used with absolute path") for site, theme in self.getSitesAndThemes(site, theme): try: base_path = self.sites_paths[site] except KeyError: - log.warning(_(u"Unregistered site requested: {site}").format( + log.warning(_("Unregistered site requested: {site}").format( site=site)) filepath = os.path.join(base_path, C.TEMPLATE_TPL_DIR, theme, *path_elts) - f = utils.open_if_exists(filepath) + f = utils.open_if_exists(filepath, 'r') if f is not None: return f, filepath return None, None @@ -211,14 +211,14 @@ if site is None: # we have an abolute template if theme is not None: - raise exceptions.InternalError(u"We can't have a theme with absolute " - u"template.") + raise exceptions.InternalError("We can't have a theme with absolute " + "template.") if not self.trusted: - log.error(_(u"Absolute template used while unsecure is disabled, hack " - u"attempt? Template: {template}").format(template=template)) - raise exceptions.PermissionError(u"absolute template is not allowed") + log.error(_("Absolute template used while unsecure is disabled, hack " + "attempt? Template: {template}").format(template=template)) + raise exceptions.PermissionError("absolute template is not allowed") filepath = template_path - f = utils.open_if_exists(filepath) + f = utils.open_if_exists(filepath, 'r') else: # relative path, we have to deal with site and theme assert theme and template_path @@ -227,19 +227,19 @@ f, filepath = self._get_template_f(site, theme, path_elts) if f is None: - if (site is not None and path_elts[0] == u"error" + if (site is not None and path_elts[0] == "error" and os.path.splitext(template_path)[1][1:] in HTML_EXT): # if an HTML error is requested but doesn't exist, we try again # with base error. f, filepath = self._get_template_f( site, theme, ("error", "base.html")) if f is None: - raise exceptions.InternalError(u"error/base.html should exist") + raise exceptions.InternalError("error/base.html should exist") else: raise TemplateNotFound(template) try: - contents = f.read().decode('utf-8') + contents = f.read() finally: f.close() @@ -285,15 +285,15 @@ @param library_name(unicode): name of the library to import @param loading: """ - if attribute not in (u"defer", u"async", u""): + if attribute not in ("defer", "async", ""): raise exceptions.DataError( - _(u'Invalid attribute, please use one of "defer", "async" or ""') + _('Invalid attribute, please use one of "defer", "async" or ""') ) - if not library_name.endswith(u".js"): - library_name = library_name + u".js" + if not library_name.endswith(".js"): + library_name = library_name + ".js" if (library_name, attribute) not in self.scripts: self.scripts.append((library_name, attribute)) - return u"" + return "" def generate_scripts(self): """Generate the <script> elements @@ -301,28 +301,28 @@ @return (unicode): <scripts> HTML tags """ scripts = [] - tpl = u"<script src={src} {attribute}></script>" + tpl = "<script src={src} {attribute}></script>" for library, attribute in self.scripts: library_path = self.renderer.getStaticPath(self.template_data, library) if library_path is None: - log.warning(_(u"Can't find {libary} javascript library").format( + log.warning(_("Can't find {libary} javascript library").format( library=library)) continue path = self.renderer.getFrontURL(library_path) scripts.append(tpl.format(src=quoteattr(path), attribute=attribute)) - return safe(u"\n".join(scripts)) + return safe("\n".join(scripts)) class Environment(jinja2.Environment): def get_template(self, name, parent=None, globals=None): - if name[0] not in (u'/', u'('): + if name[0] not in ('/', '('): # if name is not an absolute path or a full template name (this happen on # extend or import during rendering), we convert it to a full template name. # This is needed to handle cache correctly when a base template is overriden. # Without that, we could not distinguish something like base/base.html if # it's launched from some_site/some_theme or from [default]/default - name = u"({site}/{theme}){template}".format( + name = "({site}/{theme}){template}".format( site=self._template_data.site, theme=self._template_data.theme, template=name) @@ -348,26 +348,26 @@ self.host = host self.trusted = trusted self.sites_paths = { - u"": os.path.dirname(sat_templates.__file__), + "": os.path.dirname(sat_templates.__file__), } conf = config.parseMainConf() - public_sites = config.getConfig(conf, None, u"sites_path_public_dict", {}) + public_sites = config.getConfig(conf, None, "sites_path_public_dict", {}) sites_data = [public_sites] if private: - private_sites = config.getConfig(conf, None, u"sites_path_private_dict", {}) + private_sites = config.getConfig(conf, None, "sites_path_private_dict", {}) sites_data.append(private_sites) for sites in sites_data: normalised = {} - for name, path in sites.iteritems(): + for name, path in sites.items(): if RE_TPL_RESERVED_CHARS.search(name): - log.warning(_(u"Can't add \"{name}\" site, it contains forbidden " - u"characters. Forbidden characters are {forbidden}.") + log.warning(_("Can't add \"{name}\" site, it contains forbidden " + "characters. Forbidden characters are {forbidden}.") .format(name=name, forbidden=TPL_RESERVED_CHARS)) continue path = os.path.expanduser(os.path.normpath(path)) - if not path or not path.startswith(u"/"): - log.warning(_(u"Can't add \"{name}\" site, it should map to an " - u"absolute path").format(name=name)) + if not path or not path.startswith("/"): + log.warning(_("Can't add \"{name}\" site, it should map to an " + "absolute path").format(name=name)) continue normalised[name] = path self.sites_paths.update(normalised) @@ -385,26 +385,26 @@ self.installTranslations() # we want to have access to SàT constants in templates - self.env.globals[u"C"] = C + self.env.globals["C"] = C # custom filters - self.env.filters[u"next_gidx"] = self._next_gidx - self.env.filters[u"cur_gidx"] = self._cur_gidx - self.env.filters[u"date_fmt"] = self._date_fmt - self.env.filters[u"xmlui_class"] = self._xmlui_class - self.env.filters[u"attr_escape"] = self.attr_escape - self.env.filters[u"item_filter"] = self._item_filter - self.env.filters[u"adv_format"] = self._adv_format - self.env.filters[u"dict_ext"] = self._dict_ext - self.env.filters[u"highlight"] = self.highlight - self.env.filters[u"front_url"] = (self._front_url if front_url_filter is None + self.env.filters["next_gidx"] = self._next_gidx + self.env.filters["cur_gidx"] = self._cur_gidx + self.env.filters["date_fmt"] = self._date_fmt + self.env.filters["xmlui_class"] = self._xmlui_class + self.env.filters["attr_escape"] = self.attr_escape + self.env.filters["item_filter"] = self._item_filter + self.env.filters["adv_format"] = self._adv_format + self.env.filters["dict_ext"] = self._dict_ext + self.env.filters["highlight"] = self.highlight + self.env.filters["front_url"] = (self._front_url if front_url_filter is None else front_url_filter) # custom tests - self.env.tests[u"in_the_past"] = self._in_the_past - self.icons_path = os.path.join(host.media_dir, u"fonts/fontello/svg") + self.env.tests["in_the_past"] = self._in_the_past + self.icons_path = os.path.join(host.media_dir, "fonts/fontello/svg") # policies - self.env.policies[u"ext.i18n.trimmed"] = True + self.env.policies["ext.i18n.trimmed"] = True def getFrontURL(self, template_data, path=None): """Give front URL (i.e. URL seen by end-user) of a path @@ -413,15 +413,15 @@ @param path(unicode, None): relative path of file to get, if set, will remplate template_data.path """ - return self.env.filters[u"front_url"]({u"template_data": template_data}, + return self.env.filters["front_url"]({"template_data": template_data}, path or template_data.path) def installTranslations(self): # TODO: support multi translation # for now, only translations in sat_templates are handled self.translations = {} - for site_key, site_path in self.sites_paths.iteritems(): - site_prefix = u"[{}] ".format(site_key) if site_key else u'' + for site_key, site_path in self.sites_paths.items(): + site_prefix = "[{}] ".format(site_key) if site_key else '' i18n_dir = os.path.join(site_path, "i18n") for lang_dir in os.listdir(i18n_dir): lang_path = os.path.join(i18n_dir, lang_dir) @@ -439,13 +439,13 @@ translations.merge(support.Translations(f, "sat")) except EnvironmentError: log.error( - _(u"Can't find template translation at {path}").format( + _("Can't find template translation at {path}").format( path=po_path)) except UnknownLocaleError as e: - log.error(_(u"{site}Invalid locale name: {msg}").format( + log.error(_("{site}Invalid locale name: {msg}").format( site=site_prefix, msg=e)) else: - log.info(_(u"{site}loaded {lang} templates translations").format( + log.info(_("{site}loaded {lang} templates translations").format( site = site_prefix, lang=lang_dir)) @@ -458,7 +458,7 @@ self.env.install_null_translations(True) # we generate a tuple of locales ordered by display name that templates can access # through the "locales" variable - self.locales = tuple(sorted(self.translations.keys(), + self.locales = tuple(sorted(list(self.translations.keys()), key=lambda l: l.language_name.lower())) @@ -476,21 +476,21 @@ try: locale = Locale.parse(locale_str) except ValueError as e: - log.warning(_(u"invalid locale value: {msg}").format(msg=e)) + log.warning(_("invalid locale value: {msg}").format(msg=e)) locale_str = self._locale_str = C.DEFAULT_LOCALE locale = Locale.parse(locale_str) - locale_str = unicode(locale) + locale_str = str(locale) if locale_str != C.DEFAULT_LOCALE: try: translations = self.translations[locale] except KeyError: - log.warning(_(u"Can't find locale {locale}".format(locale=locale))) + log.warning(_("Can't find locale {locale}".format(locale=locale))) locale_str = C.DEFAULT_LOCALE locale = Locale.parse(self._locale_str) else: self.env.install_gettext_translations(translations, True) - log.debug(_(u"Switched to {lang}").format(lang=locale.english_name)) + log.debug(_("Switched to {lang}").format(lang=locale.english_name)) if locale_str == C.DEFAULT_LOCALE: self.env.install_null_translations(True) @@ -509,7 +509,7 @@ site, theme, __ = self.env.loader.parse_template(template) if site is None: # absolute template - return u"", os.path.dirname(template) + return "", os.path.dirname(template) try: site_root_dir = self.sites_paths[site] except KeyError: @@ -533,9 +533,9 @@ if template_data.site is None: # we have and absolue path if (not template_data.theme is None - or not template_data.path.startswith(u'/')): + or not template_data.path.startswith('/')): raise exceptions.InternalError( - u"invalid template data, was expecting absolute URL") + "invalid template data, was expecting absolute URL") static_dir = os.path.dirname(template_data.path) file_path = os.path.join(static_dir, filename) if os.path.exists(file_path): @@ -562,11 +562,11 @@ @param css_files_noscript(list): list to fill of relative path to found css file with "_noscript" suffix """ - name = name_root + u".css" + name = name_root + ".css" css_path = self.getStaticPath(template_data, name) if css_path is not None: css_files.append(self.getFrontURL(css_path)) - noscript_name = name_root + u"_noscript.css" + noscript_name = name_root + "_noscript.css" noscript_path = self.getStaticPath(template_data, noscript_name) if noscript_path is not None: css_files_noscript.append(self.getFrontURL(noscript_path)) @@ -600,18 +600,18 @@ # TODO: some caching would be nice css_files = [] css_files_noscript = [] - path_elems = template_data.path.split(u'/') + path_elems = template_data.path.split('/') path_elems[-1] = os.path.splitext(path_elems[-1])[0] - css_path = self.getStaticPath(template_data, u'fonts.css') + css_path = self.getStaticPath(template_data, 'fonts.css') if css_path is not None: css_files.append(self.getFrontURL(css_path)) - for name_root in (u'styles', u'styles_extra', u'highlight'): + for name_root in ('styles', 'styles_extra', 'highlight'): self._appendCSSPaths(template_data, css_files, css_files_noscript, name_root) - for idx in xrange(len(path_elems)): - name_root = u"_".join(path_elems[:idx+1]) + for idx in range(len(path_elems)): + name_root = "_".join(path_elems[:idx+1]) self._appendCSSPaths(template_data, css_files, css_files_noscript, name_root) return css_files, css_files_noscript @@ -624,10 +624,10 @@ This default method return absolute full path """ - template_data = ctx[u'template_data'] + template_data = ctx['template_data'] if template_data.site is None: assert template_data.theme is None - assert template_data.path.startswith(u"/") + assert template_data.path.startswith("/") return os.path.join(os.path.dirname(template_data.path, relative_url)) site_root_dir = self.sites_paths[template_data.site] @@ -638,25 +638,25 @@ def _next_gidx(self, ctx, value): """Use next current global index as suffix""" next_ = ctx["gidx"].next(value) - return value if next_ == 0 else u"{}_{}".format(value, next_) + return value if next_ == 0 else "{}_{}".format(value, next_) @contextfilter def _cur_gidx(self, ctx, value): """Use current current global index as suffix""" current = ctx["gidx"].current(value) - return value if not current else u"{}_{}".format(value, current) + return value if not current else "{}_{}".format(value, current) def _date_fmt(self, timestamp, fmt="short", date_only=False, auto_limit=None, auto_old_fmt=None): if is_undefined(fmt): - fmt = u"short" + fmt = "short" try: return date_utils.date_fmt( timestamp, fmt, date_only, auto_limit, auto_old_fmt, locale_str = self._locale_str ) except Exception as e: - log.warning(_(u"Can't parse date: {msg}").format(msg=e)) + log.warning(_("Can't parse date: {msg}").format(msg=e)) return timestamp def attr_escape(self, text): @@ -664,7 +664,7 @@ remove spaces, and put in lower case """ - return RE_ATTR_ESCAPE.sub(u"_", text.strip().lower())[:50] + return RE_ATTR_ESCAPE.sub("_", text.strip().lower())[:50] def _xmlui_class(self, xmlui_item, fields): """return classes computed from XMLUI fields name @@ -683,10 +683,10 @@ classes.append(escaped_name + "_" + self.attr_escape(value)) except KeyError: log.debug( - _(u'ignoring field "{name}": it doesn\'t exists').format(name=name) + _('ignoring field "{name}": it doesn\'t exists').format(name=name) ) continue - return u" ".join(classes) or None + return " ".join(classes) or None @contextfilter def _item_filter(self, ctx, item, filters): @@ -709,8 +709,8 @@ if filter_ is None: return value elif isinstance(filter_, dict): - filters_args = filter_.get(u"filters_args") - for idx, f_name in enumerate(filter_.get(u"filters", [])): + filters_args = filter_.get("filters_args") + for idx, f_name in enumerate(filter_.get("filters", [])): kwargs = filters_args[idx] if filters_args is not None else {} filter_func = self.env.filters[f_name] try: @@ -722,7 +722,7 @@ value = filter_func(ctx.eval_ctx, value, **kwargs) else: value = filter_func(value, **kwargs) - template = filter_.get(u"template") + template = filter_.get("template") if template: # format will return a string, so we need to check first # if the value is safe or not, and re-mark it after formatting @@ -745,7 +745,7 @@ if template is None: return value # jinja use string when no special char is used, so we have to convert to unicode - return unicode(template).format(value=value, **kwargs) + return str(template).format(value=value, **kwargs) def _dict_ext(self, source_dict, extra_dict, key=None): """extend source_dict with extra dict and return the result @@ -813,23 +813,23 @@ ) defs_elt = etree.SubElement(svg_elt, "defs") for name in names: - path = os.path.join(self.icons_path, name + u".svg") + path = os.path.join(self.icons_path, name + ".svg") icon_svg_elt = etree.parse(path).getroot() # we use icon name as id, so we can retrieve them easily icon_svg_elt.set("id", name) if not icon_svg_elt.tag == "{http://www.w3.org/2000/svg}svg": - raise exceptions.DataError(u"invalid SVG element") + raise exceptions.DataError("invalid SVG element") defs_elt.append(icon_svg_elt) return safe(etree.tostring(svg_elt, encoding="unicode")) def _icon_use(self, name, cls=""): - return safe(u'<svg class="svg-icon{cls}" xmlns="http://www.w3.org/2000/svg" ' - u'viewBox="0 0 100 100">\n' - u' <use href="#{name}"/>' - u'</svg>\n'.format(name=name, cls=(" " + cls) if cls else "")) + return safe('<svg class="svg-icon{cls}" xmlns="http://www.w3.org/2000/svg" ' + 'viewBox="0 0 100 100">\n' + ' <use href="#{name}"/>' + '</svg>\n'.format(name=name, cls=(" " + cls) if cls else "")) def render(self, template, site=None, theme=None, locale=C.DEFAULT_LOCALE, - media_path=u"", css_files=None, css_inline=False, **kwargs): + media_path="", css_files=None, css_inline=False, **kwargs): """Render a template @param template(unicode): template to render (e.g. blog/articles.html) @@ -847,21 +847,21 @@ @param **kwargs: variable to transmit to the template """ if not template: - raise ValueError(u"template can't be empty") + raise ValueError("template can't be empty") if site is not None or theme is not None: # user wants to set site and/or theme, so we add it to the template path if site is None: - site = u'' + site = '' if theme is None: theme = C.TEMPLATE_THEME_DEFAULT - if template[0] == u"(": + if template[0] == "(": raise ValueError( - u"you can't specify site or theme in template path and in argument " - u"at the same time" + "you can't specify site or theme in template path and in argument " + "at the same time" ) template_data = TemplateData(site, theme, template) - template = u"({site}/{theme}){template}".format( + template = "({site}/{theme}){template}".format( site=site, theme=theme, template=template) else: template_data = self.env.loader.parse_template(template) @@ -883,15 +883,15 @@ if css_inline: css_contents = [] - for files, suffix in ((css_files, u""), - (css_files_noscript, u"_noscript")): + for files, suffix in ((css_files, ""), + (css_files_noscript, "_noscript")): site_root_dir = self.sites_paths[template_data.site] for css_file in files: css_file_path = os.path.join(site_root_dir, css_file) with open(css_file_path) as f: css_contents.append(f.read()) if css_contents: - kwargs[u"css_content" + suffix] = u"\n".join(css_contents) + kwargs["css_content" + suffix] = "\n".join(css_contents) scripts_handler = ScriptsHandler(self, template_data) self.setLocale(locale)