From e32ec8347bf4dede22f20907bda768771c16a981 Mon Sep 17 00:00:00 2001 From: Radko Krkos <krkos@cesnet.cz> Date: Mon, 25 Jun 2018 08:55:01 +0200 Subject: [PATCH] Server: Make code compatible with Py3 (and Py2) * All input files (config, maps, idea schema) are expected in UTF-8, * Add helper get_method_params() hiding 2vs3 internal differences, * Report Python version in getInfo, * Argparse compatibility fixes. --- warden3/warden_server/warden_server.py | 58 ++++++++++++++++---------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/warden3/warden_server/warden_server.py b/warden3/warden_server/warden_server.py index b5ce77a..862f135 100644 --- a/warden3/warden_server/warden_server.py +++ b/warden3/warden_server/warden_server.py @@ -8,6 +8,7 @@ from __future__ import print_function import sys import os +import io from os import path import logging import logging.handlers @@ -25,10 +26,19 @@ import MySQLdb.cursors as mycursors if sys.version_info[0] >= 3: import configparser as ConfigParser from urllib.parse import parse_qs + unicode = str + + def get_method_params(method): + return method.__code__.co_varnames[:method.__code__.co_argcount] + else: import ConfigParser from urlparse import parse_qs + def get_method_params(method): + return method.func_code.co_varnames[:method.func_code.co_argcount] + + # for local version of up to date jsonschema sys.path.append(path.join(path.dirname(__file__), "..", "lib")) @@ -212,7 +222,7 @@ Client = namedtuple("Client", [ class Object(object): def __str__(self): - attrs = self.__init__.func_code.co_varnames[1:self.__init__.func_code.co_argcount] + attrs = get_method_params(self.__init__)[1:] eq_str = ["%s=%r" % (attr, getattr(self, attr, None)) for attr in attrs] return "%s(%s)" % (type(self).__name__, ", ".join(eq_str)) @@ -435,7 +445,7 @@ class JSONSchemaValidator(NoValidator): def __init__(self, req, log, filename=None): NoValidator.__init__(self, req, log) self.path = filename or path.join(path.dirname(__file__), "idea.schema") - with open(self.path) as f: + with io.open(self.path, "r", encoding="utf-8") as f: self.schema = json.load(f) self.validator = Draft4Validator(self.schema) @@ -477,11 +487,11 @@ class MySQL(ObjectBase): self.catmap_filename = catmap_filename self.tagmap_filename = tagmap_filename - with open(catmap_filename, "r") as catmap_fd: + with io.open(catmap_filename, "r", encoding="utf-8") as catmap_fd: self.catmap = json.load(catmap_fd) self.catmap_other = self.catmap["Other"] # Catch error soon, avoid lookup later - with open(tagmap_filename, "r") as tagmap_fd: + with io.open(tagmap_filename, "r", encoding="utf-8") as tagmap_fd: self.tagmap = json.load(tagmap_fd) self.tagmap_other = self.catmap["Other"] # Catch error soon, avoid lookup later @@ -806,10 +816,10 @@ class MySQL(ObjectBase): def load_maps(self): with self as db: db.query("DELETE FROM tags") - for tag, num in self.tagmap.iteritems(): + for tag, num in self.tagmap.items(): db.query("INSERT INTO tags(id, tag) VALUES (%s, %s)", (num, tag)) db.query("DELETE FROM categories") - for cat_subcat, num in self.catmap.iteritems(): + for cat_subcat, num in self.catmap.items(): catsplit = cat_subcat.split(".", 1) category = catsplit[0] subcategory = catsplit[1] if len(catsplit) > 1 else None @@ -931,10 +941,15 @@ class Server(ObjectBase): # Make sure everything is properly encoded - JSON and various function # may spit out unicode instead of str and it gets propagated up (str - # + unicode = unicode). However, the right thing would be to be unicode - # correct among whole source and always decode on input (json module - # does that for us) and on output here. - if isinstance(status, unicode): + # + unicode = unicode). + # For Python2 the right thing would be to be unicode correct among whole + # source and always decode on input (json module does that for us) and + # on output here. + # For Python3 strings are internally unicode so no decoding on input is + # necessary. For output, "status" must be unicode string, "output" must + # be encoded bytes array, what is done here. Important: for Python 3 we + # define: unicode = str + if isinstance(status, unicode) and sys.version_info[0] < 3: status = status.encode("utf-8") if isinstance(output, unicode): output = output.encode("utf-8") @@ -947,11 +962,10 @@ class Server(ObjectBase): def json_wrapper(method): - def meth_deco(self, post, **args): - if "events" in method.func_code.co_varnames[0:method.func_code.co_argcount]: + if "events" in get_method_params(method): try: - events = json.loads(post) if post else None + events = json.loads(post.decode('utf-8')) if post else None except Exception as e: raise self.req.error( message="Deserialization error.", error=400, @@ -973,7 +987,7 @@ def json_wrapper(method): try: meth_deco.arguments = method.arguments except AttributeError: - meth_deco.arguments = method.func_code.co_varnames[:method.func_code.co_argcount] + meth_deco.arguments = get_method_params(method) return meth_deco @@ -997,13 +1011,14 @@ class WardenHandler(ObjectBase): def getDebug(self): return { "environment": self.req.env, - "client": self.req.client.__dict__, + "client": self.req.client._asdict(), "database": self.db.get_debug(), "system": { + "python": sys.version, "uname": os.uname() }, "process": { - "cwd": os.getcwdu(), + "cwd": unicode(os.getcwd()), "pid": os.getpid(), "ppid": os.getppid(), "pgrp": os.getpgrp(), @@ -1177,15 +1192,15 @@ def read_ini(path): def read_cfg(path): - with open(path, "r") as f: + with io.open(path, "r", encoding="utf-8") as f: stripcomments = "\n".join((l for l in f if not l.lstrip().startswith(("#", "//")))) conf = json.loads(stripcomments) # Lowercase keys conf = dict(( sect.lower(), dict( - (subkey.lower(), val) for subkey, val in subsect.iteritems()) - ) for sect, subsect in conf.iteritems()) + (subkey.lower(), val) for subkey, val in subsect.items()) + ) for sect, subsect in conf.items()) return conf @@ -1360,7 +1375,7 @@ def build_server(conf, section_order=section_order, section_def=section_def, par # Process parameters kwargs = {} - for name, definition in params.iteritems(): + for name, definition in params.items(): raw_val = config.get(name, definition["default"]) try: type_callable = conv_dict[definition["type"]] @@ -1573,7 +1588,8 @@ def get_args(): argp.add_argument( "-c", "--config", help="path to configuration file") - subargp = argp.add_subparsers(title="commands") + subargp = argp.add_subparsers(title="commands", dest="command") + subargp.required = True subargp_check = subargp.add_parser( "check", add_help=False, -- GitLab