Revision: 6171 Author: russblau Date: 2008-12-19 18:16:50 +0000 (Fri, 19 Dec 2008)
Log Message: ----------- More output formatting, and login bug fixes
Modified Paths: -------------- branches/rewrite/pywikibot/__init__.py branches/rewrite/pywikibot/bot.py branches/rewrite/pywikibot/comms/http.py branches/rewrite/pywikibot/data/api.py branches/rewrite/pywikibot/login.py
Modified: branches/rewrite/pywikibot/__init__.py =================================================================== --- branches/rewrite/pywikibot/__init__.py 2008-12-19 12:30:48 UTC (rev 6170) +++ branches/rewrite/pywikibot/__init__.py 2008-12-19 18:16:50 UTC (rev 6171) @@ -18,7 +18,9 @@ import textlib from bot import *
+logging.basicConfig(fmt="%(message)s")
+ def deprecate_arg(old_arg, new_arg): """Decorator to declare old_arg deprecated and replace it with new_arg""" logger = logging.getLogger() @@ -102,30 +104,6 @@ link_regex = re.compile(r'[[(?P<title>[^]|[#<>{}]*)(|.*?)?]]')
-# User interface functions (kept extremely simple for debugging) - -def output(text, toStdout=False): - if toStdout: - level = STDOUT - else: - level = logging.INFO - logging.getLogger().log(level, - text.encode(config.console_encoding, - "xmlcharrefreplace")) - -def input(prompt, password=False): - if isinstance(prompt, unicode): - encoding = sys.stdout.encoding - # Fallback to _some_ encoding for virtual consoles (cron, screen !) - if not encoding: - encoding = "UTF-8" - prompt = prompt.encode(encoding, "xmlcharrefreplace") - if password: - import getpass - return getpass.getpass(prompt) - return raw_input(prompt) - - def set_debug(layer): """Set the logger for specified layer to DEBUG level.
@@ -144,7 +122,7 @@ This method does not check the 'layer' argument for validity.
""" - logging.getLogger(layer).setLevel(logging.DEBUG) + logging.getLogger(layer).setLevel(DEBUG)
# Throttle and thread handling @@ -175,7 +153,7 @@ # only need one drop() call because all throttles use the same global pid try: _sites[_sites.keys()[0]].throttle.drop() - logger.log(pywikibot.VERBOSE, "Dropped throttle(s).") + logger.log(VERBOSE, "Dropped throttle(s).") except IndexError: pass
Modified: branches/rewrite/pywikibot/bot.py =================================================================== --- branches/rewrite/pywikibot/bot.py 2008-12-19 12:30:48 UTC (rev 6170) +++ branches/rewrite/pywikibot/bot.py 2008-12-19 18:16:50 UTC (rev 6171) @@ -23,11 +23,26 @@
# logging levels
+from logging import DEBUG, INFO, WARNING, ERROR, CRITICAL STDOUT = 16 VERBOSE = 18 INPUT = 25
+def output(text, decoder=None, newline=True, toStdout=False, level=INFO): + if toStdout: + level = STDOUT + logging.getLogger().log(level, text) + + +def input(prompt, password=False): + logging.getLogger().log(INPUT, prompt) + if password: + import getpass + return getpass.getpass("") + return raw_input() + + def calledModuleName(): """Return the name of the module calling this function.
@@ -42,14 +57,19 @@ return os.path.basename(called)
-class LevelFilter(logging.Filter): - """Filter that only passes records at a specific level.""" +class MaxLevelFilter(logging.Filter): + """Filter that only passes records at or below a specific level. + + (setting handler level only passes records at or *above* a specified level, + so this provides the opposite functionality) + + """ def __init__(self, level=None): self.level = level
def filter(self, record): if self.level: - return record.levelno == self.level + return record.levelno <= self.level else: return True
@@ -93,6 +113,7 @@ if not moduleName: moduleName = "terminal-interface" nonGlobalArgs = [] + username = None for arg in args: arg = _decodeArg(arg) if arg == '-help': @@ -101,7 +122,9 @@ elif arg.startswith('-family:'): config.family = arg[8:] elif arg.startswith('-lang:'): - config.code = arg[6:] + config.mylang = arg[6:] + elif arg.startswith("-user:"): + username = arg[6:] elif arg.startswith('-putthrottle:'): config.put_throttle = int(arg[13:]) elif arg.startswith('-pt:'): @@ -133,8 +156,29 @@ # about it. nonGlobalArgs.append(arg)
+ if username: + config.usernames[config.family][config.mylang] = username + # initialize logging system for terminal-based bots
+ # All user output is routed through the logging module. + # Each type of output is handled by an appropriate handler object. + # This structure is used to permit eventual development of other + # user interfaces (GUIs) without modifying the core bot code. + # The following output levels are defined: + # DEBUG - only for file logging; debugging messages + # STDOUT - output that must be sent to sys.stdout (for bots that may + # have their output redirected to a file or other destination) + # VERBOSE - optional progress information for display to user + # INFO - normal (non-optional) progress information for display to user + # INPUT - prompts requiring user response + # WARN - user warning messages + # ERROR - user error messages + # CRITICAL - fatal error messages + # Accordingly, do ''not'' use print statements in bot code; instead, + # send output to the pywikibot.output() function which will route it + # to the logging module. + logging.addLevelName(VERBOSE, "VERBOSE") # for messages to be displayed on terminal at "verbose" setting # use INFO for messages to be displayed even on non-verbose setting @@ -143,15 +187,20 @@ logging.addLevelName(INPUT, "INPUT") # for prompts requiring user response
- logging.basicConfig(format="%(message)s") # initialize root logger root_logger = logging.getLogger() + # default handler for VERBOSE and INFO levels default_handler = root_logger.handlers[0] - root_logger.setLevel(logging.DEBUG) # all records go to logger - # handlers filter separately by level + root_logger.setLevel(DEBUG) # all records go to logger + + # configure default handler for VERBOSE, INFO, and INPUT levels if config.verbose_output: default_handler.setLevel(VERBOSE) else: - default_handler.setLevel(logging.INFO) + default_handler.setLevel(INFO) + default_handler.addFilter(MaxLevelFilter(INPUT)) + default_handler.setFormatter(logging.Formatter(fmt="%(message)s")) + + # if user has enabled file logging, configure file handler if moduleName in config.log or '*' in config.log: if config.logfilename: logfile = config.datafilepath(config.logfilename) @@ -160,7 +209,7 @@ file_handler = logging.handlers.RotatingFileHandler( filename=logfile, maxBytes=2 << 20, backupCount=5) if config.debug_log: - file_handler.setLevel(logging.DEBUG) + file_handler.setLevel(DEBUG) else: file_handler.setLevel(VERBOSE) form = logging.Formatter( @@ -171,11 +220,19 @@ file_handler.setFormatter(form) root_logger.addHandler(file_handler)
+ # handler for level STDOUT output_handler = logging.StreamHandler(strm=sys.stdout) output_handler.setLevel(STDOUT) - output_handler.addFilter(LevelFilter(STDOUT)) + output_handler.addFilter(MaxLevelFilter(STDOUT)) root_logger.addHandler(output_handler)
+ # handler for levels WARNING and higher + warning_handler = logging.StreamHandler() # uses sys.stderr + warning_handler.setLevel(logging.WARNING) + warning_handler.setFormatter( + logging.Formatter(fmt="%(levelname)s: %(message)s")) + root_logger.addHandler(warning_handler) + if config.verbose_output: import re ver = pywikibot.__version__ # probably can be improved on @@ -183,6 +240,7 @@ pywikibot.output(u'Pywikipediabot r%s' % m.group(1)) pywikibot.output(u'Python %s' % sys.version)
+ root_logger.debug("handleArgs() completed.") return nonGlobalArgs
@@ -203,11 +261,13 @@ wikipedia, wiktionary, wikitravel, ... This will override the configuration in user-config.py.
--daemonize:xyz Immediately returns control to the terminal and redirects +-user:xyz Log in as user 'xyz' instead of the default username. + +-daemonize:xyz Immediately return control to the terminal and redirect stdout and stderr to xyz (only use for bots that require no input from stdin).
--help Shows this help text. +-help Show this help text.
-log Enable the logfile, using the default filename '%s-bot.log'
Modified: branches/rewrite/pywikibot/comms/http.py =================================================================== --- branches/rewrite/pywikibot/comms/http.py 2008-12-19 12:30:48 UTC (rev 6170) +++ branches/rewrite/pywikibot/comms/http.py 2008-12-19 18:16:50 UTC (rev 6171) @@ -44,10 +44,7 @@ http_queue = Queue.Queue()
cookie_jar = threadedhttp.LockableCookieJar( - config.datafilepath("%s-%s-%s.lwp" - % (config.family, - config.mylang, - config.usernames[config.family][config.mylang]))) + config.datafilepath("pywikibot.lwp")) try: cookie_jar.load() except (IOError, cookielib.LoadError):
Modified: branches/rewrite/pywikibot/data/api.py =================================================================== --- branches/rewrite/pywikibot/data/api.py 2008-12-19 12:30:48 UTC (rev 6170) +++ branches/rewrite/pywikibot/data/api.py 2008-12-19 18:16:50 UTC (rev 6171) @@ -23,7 +23,7 @@ from pywikibot import login from pywikibot.exceptions import *
-logger = logging.getLogger("data") +logger = logging.getLogger()
lagpattern = re.compile(r"Waiting for [\d.]+: (?P<lag>\d+) seconds? lagged")
@@ -198,6 +198,7 @@ logger.warning("%s, %s", uri, params) self.wait() continue + logger.debug("API response received:\n%s", rawdata) if not isinstance(rawdata, unicode): rawdata = rawdata.decode(self.site.encoding()) if rawdata.startswith(u"unknown_action"): @@ -228,11 +229,11 @@ self.site._userinfo = result['query']['userinfo']
if "warnings" in result: - modules = [k for k in result["warning"] if k != "info"] - logger.warn( - "API warning (%s): %s" - % (", ".join(modules), result['warnings']['info'])) - warnings.warn(result['warnings']['info']) + modules = [k for k in result["warnings"] if k != "info"] + for mod in modules: + logger.warning( + "API warning (%s): %s" + % (mod, result["warnings"][mod]["*"])) if "error" not in result: return result if "*" in result["error"]: @@ -558,19 +559,22 @@ login_result = login_request.submit() if u"login" not in login_result: raise RuntimeError("API login response does not have 'login' key.") - if login_result['login']['result'] != u'Success': - self._waituntil = datetime.datetime.now() + datetime.timedelta(seconds=60) - return None + if login_result['login']['result'] == u'Success': + prefix = login_result['login']['cookieprefix'] + cookies = [] + for key in ('Token', 'UserID', 'UserName'): + cookies.append("%s%s=%s" + % (prefix, key, + login_result['login']['lg'+key.lower()])) + self.username = login_result['login']['lgusername'] + return "\n".join(cookies) + elif login_result['login']['result'] == "Throttled": + self._waituntil = datetime.now() \ + + timedelta(seconds=int( + login_result["login"]["wait"]) + ) + raise APIError(code=login_result["login"]["result"], info="")
- prefix = login_result['login']['cookieprefix'] - cookies = [] - for key in ('Token', 'UserID', 'UserName'): - cookies.append("%s%s=%s" - % (prefix, key, - login_result['login']['lg'+key.lower()])) - self.username = login_result['login']['lgusername'] - return "\n".join(cookies) - def storecookiedata(self, data): pywikibot.cookie_jar.save()
Modified: branches/rewrite/pywikibot/login.py =================================================================== --- branches/rewrite/pywikibot/login.py 2008-12-19 12:30:48 UTC (rev 6170) +++ branches/rewrite/pywikibot/login.py 2008-12-19 18:16:50 UTC (rev 6171) @@ -50,7 +50,7 @@ from pywikibot import config from pywikibot.exceptions import *
-logger = logging.getLogger("wiki") +logger = logging.getLogger()
# On some wikis you are only allowed to run a bot if there is a link to @@ -191,27 +191,29 @@
logger.info(u"Logging in to %(site)s as %(name)s" % {'name': self.username, 'site': self.site}) - cookiedata = self.getCookie() - if cookiedata: - self.storecookiedata(cookiedata) - logger.info(u"Should be logged in now") - # Show a warning according to the local bot policy - if not self.botAllowed(): - logger.error( - u"Username '%(name)s' is not listed on [[%(page)s]]." - % {'name': self.username, - 'page': botList[self.site.family.name][self.site.code]}) - logger.error( -"Please make sure you are allowed to use the robot before actually using it!") - return False - return True - else: - logger.error(u"Login failed. Wrong password or CAPTCHA answer?") + try: + cookiedata = self.getCookie() + except pywikibot.data.api.APIError, e: + logger.error("Login failed (%s).", e.code) if retry: self.password = None return self.login(retry = True) else: return False + self.storecookiedata(cookiedata) + logger.info(u"Should be logged in now") +## # Show a warning according to the local bot policy +## FIXME: disabled due to recursion; need to move this to the Site object after +## login +## if not self.botAllowed(): +## logger.error( +## u"Username '%(name)s' is not listed on [[%(page)s]]." +## % {'name': self.username, +## 'page': botList[self.site.family.name][self.site.code]}) +## logger.error( +##"Please make sure you are allowed to use the robot before actually using it!") +## return False + return True
def showCaptchaWindow(self, url): pass @@ -221,7 +223,7 @@ sysop = False logall = False forceLogin = False - for arg in wikipedia.handleArgs(): #FIXME + for arg in pywikibot.handleArgs(): if arg.startswith("-pass"): if len(arg) == 5: password = pywikibot.input(u'Password for all accounts:', @@ -235,7 +237,7 @@ elif arg == "-force": forceLogin = True else: - wikipedia.showHelp('login') #FIXME + pywikibot.showHelp('login') return if logall: if sysop: @@ -257,7 +259,7 @@ u' is not a valid site, please remove it from your config')
else: - loginMan = LoginManager(password, sysop=sysop) + loginMan = pywikibot.data.api.LoginManager(password, sysop=sysop) loginMan.login()
if __name__ == "__main__":