jenkins-bot has submitted this change and it was merged.
Change subject: [FIX] welcome: Fallback for i18n translate
......................................................................
[FIX] welcome: Fallback for i18n translate
In pywikibot compat i18n.translate fell back by default but with core
this was disabled, but this scripts still requires it. This adds the
functionality to give an iterable as fallback.
Bug: T95921
Change-Id: I1ff78112d6cf8f05a587aff690576346a2490c5e
---
M pywikibot/i18n.py
M scripts/welcome.py
2 files changed, 28 insertions(+), 15 deletions(-)
Approvals:
John Vandenberg: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/i18n.py b/pywikibot/i18n.py
index 2fff519..06c4049 100644
--- a/pywikibot/i18n.py
+++ b/pywikibot/i18n.py
@@ -365,6 +365,9 @@
return message
+DEFAULT_FALLBACK = ('_default', )
+
+
def translate(code, xdict, parameters=None, fallback=False):
"""Return the most appropriate translation from a translation dict.
@@ -375,8 +378,9 @@
The language itself is always checked first, then languages that
have been defined to be alternatives, and finally English. If none of
- the options gives result, we just take the first language in the
- list.
+ the options gives result, we just take the one language from xdict which may
+ not be always the same. When fallback is iterable it'll return None if no
+ code applies (instead of returning one).
For PLURAL support have a look at the twntranslate method
@@ -389,9 +393,9 @@
@type xdict: dict, string, unicode
@param parameters: For passing (plural) parameters
@type parameters: dict, string, unicode, int
- @param fallback: Try an alternate language code
- @type fallback: boolean
-
+ @param fallback: Try an alternate language code. If it's iterable it'll
+ also try those entries and choose the first match.
+ @type fallback: boolean or iterable
"""
family = pywikibot.config.family
# If a site is given instead of a code, use its language
@@ -407,20 +411,28 @@
xdict = xdict['wikipedia']
# Get the translated string
- trans = None
if not isinstance(xdict, dict):
trans = xdict
- elif code in xdict:
- trans = xdict[code]
- elif fallback:
- for alt in _altlang(code) + ['_default', 'en']:
- if alt in xdict:
- trans = xdict[alt]
- code = alt
+ elif not xdict:
+ trans = None
+ else:
+ codes = [code]
+ if fallback is True:
+ codes += _altlang(code) + ['_default', 'en']
+ elif fallback is not False:
+ codes += list(fallback)
+ for code in codes:
+ if code in xdict:
+ trans = xdict[code]
break
else:
- trans = list(xdict.values())[0]
+ if fallback is not False and fallback is not True:
+ # future versions shouldn't simply return "any one" code but
+ # no translation as this is not very deterministic. When
+ # fallback is iterable it's a new mode previously not supported
+ return
code = list(xdict.keys())[0]
+ trans = xdict[code]
if trans is None:
return # return None if we have no translation found
if parameters is None:
diff --git a/scripts/welcome.py b/scripts/welcome.py
index 16403be..37b7737 100644
--- a/scripts/welcome.py
+++ b/scripts/welcome.py
@@ -657,7 +657,8 @@
showStatus()
pywikibot.output(
'Log page is not exist, getting information for page creation')
- text = i18n.translate(self.site, logpage_header)
+ text = i18n.translate(self.site, logpage_header,
+ fallback=i18n.DEFAULT_FALLBACK)
text += u'\n!%s' % self.site.namespace(2)
text += u'\n!%s' % str.capitalize(
self.site.mediawiki_message('contribslink'))
--
To view, visit https://gerrit.wikimedia.org/r/203942
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I1ff78112d6cf8f05a587aff690576346a2490c5e
Gerrit-PatchSet: 3
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <ladsgroup(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: Xqt <info(a)gno.de>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged.
Change subject: Appveyor CI Win32 builds
......................................................................
Appveyor CI Win32 builds
Allow tests relying on pywinauto to be skipped if pywinauto
is missing or non-functional, as pywin32 installation has
not been added to the Appveyor build script.
Change-Id: Ifc82582ee2044f1753d0f2c7b9aff4c0f901f259
---
A .appveyor.yml
M setup.py
M tests/README.rst
M tests/ui_tests.py
4 files changed, 91 insertions(+), 8 deletions(-)
Approvals:
John Vandenberg: Looks good to me, but someone else must approve
XZise: Looks good to me, approved
jenkins-bot: Verified
diff --git a/.appveyor.yml b/.appveyor.yml
new file mode 100644
index 0000000..c91b550
--- /dev/null
+++ b/.appveyor.yml
@@ -0,0 +1,50 @@
+environment:
+
+ global:
+ # SDK v7.0 MSVC Express 2008's SetEnv.cmd script will fail if the
+ # /E:ON and /V:ON options are not enabled in the batch script intepreter
+ # See: http://stackoverflow.com/a/13751649/163740
+ WITH_COMPILER: "cmd /E:ON /V:ON /C run_with_compiler.cmd"
+ PYWIKIBOT2_DIR: "%appdata%\\Pywikibot"
+
+ matrix:
+ - PYTHON: "C:\\Python27"
+ PYTHON_VERSION: "2.7.8"
+ PYTHON_ARCH: "32"
+
+ - PYTHON: "C:\\Python33"
+ PYTHON_VERSION: "3.3.5"
+ PYTHON_ARCH: "32"
+
+ - PYTHON: "C:\\Python34"
+ PYTHON_VERSION: "3.4.1"
+ PYTHON_ARCH: "32"
+
+init:
+ - "ECHO %PYTHON% %PYTHON_VERSION% %PYTHON_ARCH%"
+
+install:
+ - git submodule update --init
+ - ps: (new-object net.webclient).DownloadFile('https://raw.githubusercontent.com/pypa/python-packaging-user-guide/master/s…', 'install.ps1')
+ - "powershell install.ps1"
+ - ps: (new-object net.webclient).DownloadFile('https://raw.githubusercontent.com/pypa/python-packaging-user-guide/master/s…', 'run_with_compiler.cmd')
+ - cd externals/httplib2
+ - "%WITH_COMPILER% %PYTHON%\\python setup.py install"
+ - cd ..\\..
+
+build: off
+
+test_script:
+ - chcp 65001
+ - mkdir "%PYWIKIBOT2_DIR%"
+ - echo mylang = 'en' > "%PYWIKIBOT2_DIR%\\user-config.py"
+ - echo family = 'wikipedia' >> "%PYWIKIBOT2_DIR%\\user-config.py"
+ - echo max_retries = 2 >> "%PYWIKIBOT2_DIR%\\user-config.py"
+ - echo maximum_GET_length = 5000 >> "%PYWIKIBOT2_DIR%\\user-config.py"
+ - echo console_encoding = 'utf8' >> "%PYWIKIBOT2_DIR%\\user-config.py"
+ - echo transliteration_target = None >> "%PYWIKIBOT2_DIR%\\user-config.py"
+
+ - "%WITH_COMPILER% %PYTHON%\\python setup.py test"
+
+artifacts:
+ - path: dist\*
diff --git a/setup.py b/setup.py
index b36244a..a33928e 100644
--- a/setup.py
+++ b/setup.py
@@ -91,10 +91,12 @@
# when trying to build the C modules.
dependencies += extra_deps['mwparserfromhell']
+# setup can't detect or install pywin32, which pywinauto depends on.
+# appveyor builds do not install pywin32
if os.name == 'nt':
# FIXME: tests/ui_tests.py suggests pywinauto 0.4.2
# which isnt provided on pypi.
- test_deps += ['pywin32>=218', 'pywinauto>=0.4.0']
+ test_deps += ['pywinauto>=0.4.0']
extra_deps.update(script_deps)
diff --git a/tests/README.rst b/tests/README.rst
index 214c110..3080680 100644
--- a/tests/README.rst
+++ b/tests/README.rst
@@ -71,7 +71,7 @@
python pwb.py tests/site_tests.py -v
-travis-ci
+Travis CI
=========
After changes are published into a github repository, tests may be run on
@@ -114,6 +114,26 @@
It is strongly recommended that an untrusted bot account is created for
travis tests, using a password that is not shared with trusted accounts.
+Appveyor CI
+===========
+
+After changes are published into a github repository, tests may be run on
+a Microsoft Windows box provided by ci.appveyor.com according to the
+configuration in .appveyor.yml . To do this:
+
+1. create a github and appveyor account
+2. fork the main github repository
+3. create a project in ci.appveyor.com
+4. go to https://ci.appveyor.com/project/<username>/pywikibot-core/settings
+ and enter the custom configuration .yml filename: .appveyor.yml
+4. push changes into the forked git repository
+5. watch the build at https://ci.appveyor.com/<username>/pywikibot-core/history
+
+The 'user' tests are not yet enabled on appveyor builds.
+
+Environment variables
+=====================
+
There are a set of 'edit failure' tests, which attempt to write to the wikis
and **should** fail. If there is a bug in pywikibot or MediaWiki, these
tests **may** actually perform a write operation.
@@ -121,16 +141,16 @@
These 'edit failure' tests are disabled by default for the 'wikimedia' builds,
but are enabled by default on builds by any other github account.
-To disable 'edit failure' tests in travis, add PYWIKIBOT2_TEST_WRITE_FAIL=0
+To disable 'edit failure' tests, set PYWIKIBOT2_TEST_WRITE_FAIL=0
There are also several other 'write' tests which also attempt to perform
write operations successfully. These **will** write to the wikis, and they
should always only write to 'test' wikis.
-These 'write' tests are disabled in travis builds, and currently can not be
-run on travis as they require interaction using a terminal.
+These 'write' tests are disabled by default, and currently can not be
+run on travis or appveyor as they require interaction using a terminal.
-To enable 'write' tests in travis, add PYWIKIBOT2_TEST_WRITE=1
+To enable 'write' tests, set PYWIKIBOT2_TEST_WRITE=1
Contributing tests
==================
diff --git a/tests/ui_tests.py b/tests/ui_tests.py
index eea9256..73a86e4 100644
--- a/tests/ui_tests.py
+++ b/tests/ui_tests.py
@@ -34,6 +34,11 @@
import time
import io
+try:
+ import pywinauto
+except ImportError:
+ pywinauto = None
+
import pywikibot
from pywikibot.bot import (
ui, DEBUG, VERBOSE, INFO, STDOUT, INPUT, WARNING, ERROR, CRITICAL
@@ -488,11 +493,12 @@
def setUpClass(cls):
if os.name != 'nt':
raise unittest.SkipTest('requires Windows console')
+ if not pywinauto:
+ raise unittest.SkipTest('requires Windows package pywinauto')
super(WindowsTerminalTestCase, cls).setUpClass()
@classmethod
def setUpProcess(cls, command):
- import pywinauto
import subprocess
si = subprocess.STARTUPINFO()
si.dwFlags = subprocess.STARTF_USESTDHANDLES
@@ -503,7 +509,12 @@
cls._app.connect_(process=cls._process.pid)
# set truetype font (Lucida Console, hopefully)
- cls._app.window_().TypeKeys('% {UP}{ENTER}^L{HOME}L{ENTER}', with_spaces=True)
+ try:
+ cls._app.window_().TypeKeys('% {UP}{ENTER}^L{HOME}L{ENTER}', with_spaces=True)
+ except:
+ # occurs if pywinauto is installed without pywin32.
+ raise unittest.SkipTest('Windows package pywinauto not functional; '
+ 'maybe pywin32 isnt installed')
@classmethod
def tearDownProcess(cls):
--
To view, visit https://gerrit.wikimedia.org/r/203643
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Ifc82582ee2044f1753d0f2c7b9aff4c0f901f259
Gerrit-PatchSet: 16
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <ladsgroup(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged.
Change subject: test pwb: load default encoding from config
......................................................................
test pwb: load default encoding from config
Partial revert of 1e54a7d
Set envvar PYTHONIOENCODING to console_encoding
Add pwb tests for environment and unicode
Use str() on pwb tests env vars
Use with syntax to prevent unclosed file in Python 3
Change-Id: Id8a6c7b9b9ea29c0b92c8eb14f047bd792e20e24
---
M pwb.py
M pywikibot/config2.py
M pywikibot/tools/__init__.py
M tests/__init__.py
M tests/aspects.py
A tests/pwb/print_env.py
M tests/pwb/print_locals.py
A tests/pwb/print_unicode.py
M tests/pwb_tests.py
M tests/utils.py
10 files changed, 151 insertions(+), 60 deletions(-)
Approvals:
John Vandenberg: Looks good to me, but someone else must approve
XZise: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pwb.py b/pwb.py
index 365a22e..b1bb52c 100644
--- a/pwb.py
+++ b/pwb.py
@@ -79,7 +79,8 @@
sys.path[0] = os.path.dirname(filename)
try:
- source = open(filename).read()
+ with open(filename, 'rb') as f:
+ source = f.read()
exec(compile(source, filename, "exec", dont_inherit=True),
main_mod.__dict__)
finally:
@@ -92,6 +93,16 @@
pwb.argvu = old_argvu
# end of snippet from coverage
+
+
+def abspath(path):
+ """Convert path to absolute path, with uppercase drive letter on win32."""
+ path = os.path.abspath(path)
+ if path[0] != '/':
+ # normalise Windows drive letter
+ path = path[0].upper() + path[1:]
+ return path
+
if sys.version_info[0] not in (2, 3):
raise RuntimeError("ERROR: Pywikibot only runs under Python 2 "
@@ -107,9 +118,7 @@
# Establish a normalised path for the directory containing pwb.py.
# Either it is '.' if the user's current working directory is the same,
# or it is the absolute path for the directory of pwb.py
-absolute_path = os.path.dirname(sys.argv[0])
-if not os.path.isabs(absolute_path):
- absolute_path = os.path.abspath(os.path.join(os.curdir, absolute_path))
+absolute_path = abspath(os.path.dirname(sys.argv[0]))
rewrite_path = absolute_path
sys.path = [sys.path[0], rewrite_path,
@@ -157,7 +166,10 @@
# If successful, user-config.py already exists in one of the candidate
# directories. See config2.py for details on search order.
# Use env var to communicate to config2.py pwb.py location (bug 72918).
- os.environ['PYWIKIBOT2_DIR_PWB'] = os.path.split(__file__)[0]
+ _pwb_dir = os.path.split(__file__)[0]
+ if sys.platform == 'win32' and sys.version_info[0] < 3:
+ _pwb_dir = str(_pwb_dir)
+ os.environ[str('PYWIKIBOT2_DIR_PWB')] = _pwb_dir
import pywikibot # noqa
except RuntimeError as err:
# user-config.py to be created
@@ -195,10 +207,10 @@
# This is a rough (and quick!) emulation of 'package name' detection.
# a much more detailed implementation is in coverage's find_module.
# https://bitbucket.org/ned/coveragepy/src/default/coverage/execfile.py
- cwd = os.path.abspath(os.getcwd())
+ cwd = abspath(os.getcwd())
if absolute_path == cwd:
- absolute_filename = os.path.abspath(filename)
- if absolute_filename.startswith(rewrite_path):
+ absolute_filename = abspath(filename)[:len(cwd)]
+ if absolute_filename == cwd:
relative_filename = os.path.relpath(filename)
# remove the filename, and use '.' instead of path separator.
file_package = os.path.dirname(
diff --git a/pywikibot/config2.py b/pywikibot/config2.py
index db23c5e..730d4dd 100644
--- a/pywikibot/config2.py
+++ b/pywikibot/config2.py
@@ -46,8 +46,6 @@
from warnings import warn
-from pywikibot.tools import default_encoding
-
# This frozen set should contain all imported modules/variables, so it must
# occur directly after the imports. At that point globals() only contains the
# names and some magic variables (like __name__)
@@ -346,7 +344,10 @@
# This default code should work fine, so you don't have to think about it.
# TODO: consider getting rid of this config variable.
try:
- console_encoding = sys.stdout.encoding
+ if sys.version_info[0] > 2 or not sys.stdout.encoding:
+ console_encoding = sys.stdout.encoding
+ else:
+ console_encoding = sys.stdout.encoding.decode('ascii')
except:
# When using pywikibot inside a daemonized twisted application,
# we get "StdioOnnaStick instance has no attribute 'encoding'"
@@ -933,7 +934,11 @@
_ConfigurationDeprecationWarning)
# Fix up default console_encoding
-console_encoding = default_encoding(console_encoding)
+if console_encoding is None:
+ if sys.platform == 'win32':
+ console_encoding = 'cp850'
+ else:
+ console_encoding = 'iso-8859-1'
# Fix up transliteration_target
if transliteration_target == 'not set':
diff --git a/pywikibot/tools/__init__.py b/pywikibot/tools/__init__.py
index 67e0087..22d6c4d 100644
--- a/pywikibot/tools/__init__.py
+++ b/pywikibot/tools/__init__.py
@@ -405,26 +405,6 @@
self.stop()
-def stream_encoding(stream):
- """Get encoding of the stream and use a default if not existent/None."""
- try:
- encoding = stream.encoding
- except AttributeError:
- encoding = None
- return default_encoding(encoding)
-
-
-def default_encoding(encoding):
- """Return an encoding even if it's originally None."""
- if encoding is None:
- if sys.platform == 'win32':
- return 'cp850'
- else:
- return 'iso-8859-1'
- else:
- return encoding
-
-
def itergroup(iterable, size):
"""Make an iterator that returns lists of (up to) size items from iterable.
diff --git a/tests/__init__.py b/tests/__init__.py
index 031b90d..3f213d2 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -191,7 +191,8 @@
# overridden here to restrict retries to only 1, so developer builds fail more
# frequently in code paths resulting from mishandled server problems.
if config.max_retries > 2:
- print('max_retries reduced from %d to 1 for tests' % config.max_retries)
+ if 'PYWIKIBOT_TEST_QUIET' not in os.environ:
+ print('tests: max_retries reduced from %d to 1' % config.max_retries)
config.max_retries = 1
cache_misses = 0
diff --git a/tests/aspects.py b/tests/aspects.py
index 78cd606..2458b6b 100644
--- a/tests/aspects.py
+++ b/tests/aspects.py
@@ -1157,14 +1157,17 @@
self.orig_pywikibot_dir = None
if 'PYWIKIBOT2_DIR' in os.environ:
self.orig_pywikibot_dir = os.environ['PYWIKIBOT2_DIR']
- os.environ['PYWIKIBOT2_DIR'] = pywikibot.config.base_dir
+ base_dir = pywikibot.config.base_dir
+ if sys.platform == 'win32' and sys.version_info[0] < 3:
+ base_dir = str(base_dir)
+ os.environ[str('PYWIKIBOT2_DIR')] = base_dir
def tearDown(self):
"""Restore the environment after running the pwb.py script."""
super(PwbTestCase, self).tearDown()
del os.environ['PYWIKIBOT2_DIR']
if self.orig_pywikibot_dir:
- os.environ['PYWIKIBOT2_DIR'] = self.orig_pywikibot_dir
+ os.environ[str('PYWIKIBOT2_DIR')] = self.orig_pywikibot_dir
def _execute(self, args, data_in=None, timeout=0, error=None):
from tests.utils import execute_pwb
diff --git a/tests/pwb/print_env.py b/tests/pwb/print_env.py
new file mode 100644
index 0000000..e61230d
--- /dev/null
+++ b/tests/pwb/print_env.py
@@ -0,0 +1,31 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+"""Script that forms part of pwb_tests."""
+from __future__ import unicode_literals
+
+import os
+import sys
+
+_pwb_dir = os.path.abspath(os.path.join(
+ os.path.split(__file__)[0], '..', '..'))
+_pwb_dir = _pwb_dir[0].upper() + _pwb_dir[1:]
+
+print('os.environ:')
+for k, v in sorted(os.environ.items()):
+ # Don't leak the password into logs
+ if k == 'USER_PASSWORD':
+ continue
+ # This only appears in subprocesses
+ if k in ['PYWIKIBOT2_DIR_PWB']:
+ continue
+ print("%r: %r" % (k, v))
+
+print('sys.path:')
+for path in sys.path:
+ if path == '' or path.startswith('.'):
+ continue
+ # Normalise DOS drive letter
+ path = path[0].upper() + path[1:]
+ if path.startswith(_pwb_dir):
+ continue
+ print(path)
diff --git a/tests/pwb/print_locals.py b/tests/pwb/print_locals.py
index c39b776..261327a 100644
--- a/tests/pwb/print_locals.py
+++ b/tests/pwb/print_locals.py
@@ -1,3 +1,5 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
"""Script that forms part of pwb_tests."""
from __future__ import unicode_literals
diff --git a/tests/pwb/print_unicode.py b/tests/pwb/print_unicode.py
new file mode 100644
index 0000000..a8fa757
--- /dev/null
+++ b/tests/pwb/print_unicode.py
@@ -0,0 +1,9 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+"""Script that forms part of pwb_tests."""
+from __future__ import unicode_literals
+
+import pywikibot
+
+pywikibot.output('Häuser')
+print('Häuser')
diff --git a/tests/pwb_tests.py b/tests/pwb_tests.py
index fe52837..80bcf87 100644
--- a/tests/pwb_tests.py
+++ b/tests/pwb_tests.py
@@ -22,9 +22,7 @@
from tests.utils import execute, execute_pwb
from tests.aspects import unittest, PwbTestCase
-testbasepath = os.path.join(_tests_dir, 'pwb')
-print_locals_test_package = 'tests.pwb.print_locals'
-print_locals_test_script = os.path.join(testbasepath, 'print_locals.py')
+_pwb_tests_dir = os.path.join(_tests_dir, 'pwb')
class TestPwb(PwbTestCase):
@@ -42,17 +40,44 @@
site = False
net = False
- def testScriptEnvironment(self):
+ def _do_check(self, name):
+ package_name = 'tests.pwb.' + name
+ script_path = os.path.join(_pwb_tests_dir, name + '.py')
+
+ direct = execute([sys.executable, '-m', package_name])
+ vpwb = execute_pwb([script_path])
+ self.maxDiff = None
+ self.assertEqual(direct['stdout'], vpwb['stdout'])
+
+ return (direct, vpwb)
+
+ def test_env(self):
"""
- Test environment of pywikibot.
+ Test external environment of pywikibot.
Make sure the environment is not contaminated, and is the same as
the environment we get when directly running a script.
"""
- direct = execute([sys.executable, '-m', 'tests.pwb.print_locals'])
- vpwb = execute_pwb([print_locals_test_script])
- self.maxDiff = None
- self.assertEqual(direct['stdout'], vpwb['stdout'])
+ self._do_check('print_env')
+
+ def test_locals(self):
+ """
+ Test internal environment of pywikibot.
+
+ Make sure the environment is not contaminated, and is the same as
+ the environment we get when directly running a script.
+ """
+ self._do_check('print_locals')
+
+ def test_unicode(self):
+ """Test printing unicode in pywikibot."""
+ (direct, vpwb) = self._do_check('print_unicode')
+
+ self.assertEqual('Häuser', direct['stdout'].strip())
+ self.assertEqual('Häuser', direct['stderr'].strip())
+ self.assertEqual('Häuser', vpwb['stdout'].strip())
+ self.assertEqual('Häuser', vpwb['stderr'].strip())
+
if __name__ == "__main__":
unittest.main(verbosity=10)
diff --git a/tests/utils.py b/tests/utils.py
index d23fd91..9833916 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -16,7 +16,9 @@
from warnings import warn
import pywikibot
-from pywikibot.tools import SelfCallDict, stream_encoding
+
+from pywikibot import config
+from pywikibot.tools import SelfCallDict
from pywikibot.site import Namespace
from pywikibot.data.api import CachedRequest
from pywikibot.data.api import Request as _original_Request
@@ -259,16 +261,24 @@
# Any environment variables added on Windows must be of type
# str() on Python 2.
env = os.environ.copy()
+
+ # Prevent output by test package; e.g. 'max_retries reduced from x to y'
+ env[str('PYWIKIBOT_TEST_QUIET')] = str('1')
+
# sys.path may have been modified by the test runner to load dependencies.
- env['PYTHONPATH'] = ":".join(sys.path)
+ pythonpath = os.pathsep.join(sys.path)
+ if sys.platform == 'win32' and sys.version_info[0] < 3:
+ pythonpath = str(pythonpath)
+ env[str('PYTHONPATH')] = pythonpath
+ env[str('PYTHONIOENCODING')] = str(config.console_encoding)
+
# LC_ALL is used by i18n.input as an alternative for userinterface_lang
if pywikibot.config.userinterface_lang:
- env['LC_ALL'] = str(pywikibot.config.userinterface_lang)
+ env[str('LC_ALL')] = str(pywikibot.config.userinterface_lang)
+
# Set EDITOR to an executable that ignores all arguments and does nothing.
- if sys.platform == 'win32':
- env['EDITOR'] = str('call')
- else:
- env['EDITOR'] = 'true'
+ env[str('EDITOR')] = str('call' if sys.platform == 'win32' else 'true')
+
options = {
'stdout': subprocess.PIPE,
'stderr': subprocess.PIPE
@@ -276,14 +286,27 @@
if data_in is not None:
options['stdin'] = subprocess.PIPE
- p = subprocess.Popen(command, env=env, **options)
-
- stdin_encoding = stream_encoding(p.stdin)
- stdout_encoding = stream_encoding(p.stdout)
- stderr_encoding = stream_encoding(p.stderr)
+ try:
+ p = subprocess.Popen(command, env=env, **options)
+ except TypeError:
+ # Generate a more informative error
+ if sys.platform == 'win32' and sys.version_info[0] < 3:
+ unicode_env = [(k, v) for k, v in os.environ.items()
+ if not isinstance(k, str) or
+ not isinstance(v, str)]
+ if unicode_env:
+ raise TypeError('os.environ must contain only str: %r'
+ % unicode_env)
+ child_unicode_env = [(k, v) for k, v in env.items()
+ if not isinstance(k, str) or
+ not isinstance(v, str)]
+ if child_unicode_env:
+ raise TypeError('os.environ must contain only str: %r'
+ % child_unicode_env)
+ raise
if data_in is not None:
- p.stdin.write(data_in.encode(stdin_encoding))
+ p.stdin.write(data_in.encode(config.console_encoding))
p.stdin.flush() # _communicate() otherwise has a broken pipe
stderr_lines = b''
@@ -296,7 +319,7 @@
if error:
line = p.stderr.readline()
stderr_lines += line
- if error in line.decode(stdout_encoding):
+ if error in line.decode(config.console_encoding):
break
time.sleep(1)
waited += 1
@@ -309,8 +332,8 @@
data_out = p.communicate()
return {'exit_code': p.returncode,
- 'stdout': data_out[0].decode(stdout_encoding),
- 'stderr': (stderr_lines + data_out[1]).decode(stderr_encoding)}
+ 'stdout': data_out[0].decode(config.console_encoding),
+ 'stderr': (stderr_lines + data_out[1]).decode(config.console_encoding)}
def execute_pwb(args, data_in=None, timeout=0, error=None):
--
To view, visit https://gerrit.wikimedia.org/r/204018
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Id8a6c7b9b9ea29c0b92c8eb14f047bd792e20e24
Gerrit-PatchSet: 13
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <ladsgroup(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged.
Change subject: generate_user_files: command line arguments
......................................................................
generate_user_files: command line arguments
Allow generate_user_files to be automated
with command line arguments, either quietly
or verbosely with -v
Added env PYWIKIBOT2_NO_USER_CONFIG=2 support
to config2, so it doesnt emit warnings when
generate_user_files is run without a user-config.
Fixed the extended user-config extraction regex,
and add checks and fallbacks to simple user-config.
Add unicode_literals to generated user-config.
Remove user-config.py if an error occurred while
generating it so the error is reproducible.
Removed use of locals(), added main(), moved imports to top.
Several improvements to bot input methods,
such as defaults and force params, and a list
selection function moved from generate_user_files
into the main library for reuse.
generate_user_files is now used by jenkins so that
the site test:test is explicitly used for nose tests,
avoiding pwb emitting warnings that user-config does
not exist. As a result, it is now easier
do exact comparisons on the script output.
jenkins uses verbose extended user-config, and errors
if the extended user-config cant be generated.
Change-Id: I7356d0fa38032fbbd18b72cfe267c36ee57eb7a7
---
M generate_user_files.py
M pywikibot/bot.py
M pywikibot/config2.py
M pywikibot/userinterfaces/terminal_interface_base.py
M tests/i18n_tests.py
M tests/ui_tests.py
M tox.ini
7 files changed, 308 insertions(+), 127 deletions(-)
Approvals:
XZise: Looks good to me, approved
jenkins-bot: Verified
diff --git a/generate_user_files.py b/generate_user_files.py
index 825f948..57a3f60 100644
--- a/generate_user_files.py
+++ b/generate_user_files.py
@@ -1,23 +1,35 @@
+#!/usr/bin/python
# -*- coding: utf-8 -*-
"""Script to create user files (user-config.py, user-fixes.py)."""
#
-# (C) Pywikibot team, 2010-2014
+# (C) Pywikibot team, 2010-2015
#
# Distributed under the terms of the MIT license.
#
+from __future__ import unicode_literals
__version__ = '$Id$'
-#
-import os
-import sys
-import re
import codecs
-import math
+import os
+import re
+import shutil
+import sys
+
+from textwrap import wrap
+from warnings import warn
# Disable user-config usage as we are creating it here
-os.environ['PYWIKIBOT2_NO_USER_CONFIG'] = '1'
+_orig_no_user_config = os.environ.get('PYWIKIBOT2_NO_USER_CONFIG') # noqa
+os.environ['PYWIKIBOT2_NO_USER_CONFIG'] = '2' # noqa
+
import pywikibot
+from pywikibot import config
+
+if not _orig_no_user_config:
+ del os.environ['PYWIKIBOT2_NO_USER_CONFIG']
+else:
+ os.environ['PYWIKIBOT2_NO_USER_CONFIG'] = _orig_no_user_config
base_dir = pywikibot.config2.base_dir
console_encoding = sys.stdout.encoding
@@ -26,40 +38,6 @@
if console_encoding is None or sys.platform == 'cygwin':
console_encoding = "iso-8859-1"
-
-
-def listchoice(clist, message=None, default=None):
- """Ask the user to select one entry from a list of entries."""
- if not message:
- message = u"Select"
-
- if default:
- message += u" (default: %s)" % default
-
- message += u": "
-
- line_template = u"{{0: >{0}}}: {{1}}".format(int(math.log10(len(clist)) + 1))
- for n, i in enumerate(clist):
- pywikibot.output(line_template.format(n + 1, i))
-
- while True:
- choice = pywikibot.input(message)
-
- if choice == '' and default:
- return default
- try:
- choice = int(choice) - 1
- except ValueError:
- try:
- choice = clist.index(choice)
- except IndexError:
- choice = -1
-
- # User typed choice number
- if 0 <= choice < len(clist):
- return clist[choice]
- else:
- pywikibot.error("Invalid response")
def change_base_dir():
@@ -89,13 +67,13 @@
if new_base == pywikibot.config2.get_base_dir(new_base):
# config would find that file
return new_base
- from textwrap import wrap
+
msg = wrap(u"""WARNING: Your user files will be created in the directory
'%(new_base)s' you have chosen. To access these files, you will either have
to use the argument "-dir:%(new_base)s" every time you run the bot, or set
the environment variable "PYWIKIBOT2_DIR" equal to this directory name in
your operating system. See your operating system documentation for how to
-set environment variables.""" % locals(), width=76)
+set environment variables.""" % {'new_base': new_base}, width=76)
for line in msg:
pywikibot.output(line)
if pywikibot.input_yn('Is this OK?', default=False, automatic_quit=False):
@@ -113,7 +91,7 @@
def get_site_and_lang(default_family='wikipedia', default_lang='en',
- default_username=None):
+ default_username=None, force=False):
"""
Ask the user for the family, language and username.
@@ -130,10 +108,12 @@
known_families = sorted(pywikibot.config2.family_files.keys())
if default_family not in known_families:
default_family = None
- fam = listchoice(known_families,
- u"Select family of sites we are working on, "
- u"just enter the number or name",
- default=default_family)
+ fam = pywikibot.bot.input_list_choice(
+ u"Select family of sites we are working on, "
+ u"just enter the number or name",
+ known_families,
+ force=force,
+ default=default_family)
fam = pywikibot.family.Family.load(fam)
if hasattr(fam, "langs"):
if hasattr(fam, "languages_by_size"):
@@ -163,12 +143,10 @@
else:
default_lang = None
message = "The language code of the site we're working on"
- if default_lang:
- message += " (default: '{0}')".format(default_lang)
- message += ":"
+
mylang = None
while not mylang:
- mylang = pywikibot.input(message) or default_lang
+ mylang = pywikibot.input(message, default=default_lang, force=force)
if known_langs and mylang and mylang not in known_langs:
if not pywikibot.input_yn("The language code {0} is not in the "
"list of known languages. Do you want "
@@ -176,22 +154,15 @@
default=False, automatic_quit=False):
mylang = None
- username = None
message = u"Username on {0}:{1}".format(mylang, fam.name)
- if default_username:
- message += " (default: '{0}')".format(default_username)
- message += ":"
- while not username:
- username = pywikibot.input(message) or default_username
- if not username:
- pywikibot.error('The username may not be empty.')
- if sys.version_info == 2:
- username = username.decode(console_encoding)
+ username = pywikibot.input(message, default=default_username, force=force)
# Escape ''s
- username = username.replace("'", "\\'")
+ if username:
+ username = username.replace("'", "\\'")
return fam.name, mylang, username
EXTENDED_CONFIG = u"""# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
# This is an automatically generated file. You can find more configuration
# parameters in 'config.py' file.
@@ -238,40 +209,58 @@
{config_text}"""
SMALL_CONFIG = (u"# -*- coding: utf-8 -*-\n"
+ u"from __future__ import unicode_literals\n"
u"family = '{main_family}'\n"
u"mylang = '{main_lang}'\n"
u"{usernames}\n")
-def create_user_config():
+def create_user_config(args=None, force=False):
"""Create a user-config.py in base_dir."""
_fnc = os.path.join(base_dir, "user-config.py")
- if not file_exists(_fnc):
- main_family, main_lang, main_username = get_site_and_lang()
+ if file_exists(_fnc):
+ return
+ if args and force and not config.verbose_output:
+ # main_username may be None, which is used in the next block
+ main_family, main_lang, main_username = args
+ usernames = [args]
+ else:
+ main_family, main_lang, main_username = get_site_and_lang(*args,
+ force=force)
usernames = [(main_family, main_lang, main_username)]
+
while pywikibot.input_yn("Do you want to add any other projects?",
+ force=force,
default=False, automatic_quit=False):
usernames += [get_site_and_lang(main_family, main_lang,
main_username)]
+ if not main_username:
+ usernames = "# usernames['{0}']['{1}'] = u'MyUsername'".format(
+ main_family, main_lang)
+ else:
usernames = '\n'.join(
u"usernames['{0}']['{1}'] = u'{2}'".format(*username)
for username in usernames)
- extended = pywikibot.input_yn("Would you like the extended version of "
- "user-config.py, with explanations "
- "included?", automatic_quit=False)
+ config_text = ''
+ config_content = SMALL_CONFIG
- if extended:
+ if ((force and not config.verbose_output) or
+ pywikibot.input_yn('Would you like the extended version of '
+ 'user-config.py, with explanations '
+ 'included?', automatic_quit=False,
+ default=True, force=force)):
+ try:
# config2.py will be in the pywikibot/ directory relative to this
# script (generate_user_files)
install = os.path.dirname(os.path.abspath(__file__))
with codecs.open(os.path.join(install, "pywikibot", "config2.py"),
"r", "utf-8") as config_f:
- config = config_f.read()
+ config_file = config_f.read()
- res = re.findall("^(############## (?:"
+ res = re.findall("^(# ############# (?:"
"LOGFILE|"
"INTERWIKI|"
"SOLVE_DISAMBIGUATION|"
@@ -282,16 +271,41 @@
"SEARCH ENGINE|"
"COPYRIGHT|"
"FURTHER"
- ") SETTINGS .*?)^(?=#####|# =====)",
- config, re.MULTILINE | re.DOTALL)
- config_text = '\n'.join(res)
- config_content = EXTENDED_CONFIG
- else:
- config_content = SMALL_CONFIG
+ ") SETTINGS .*)^(?=#####|# =====)",
+ config_file, re.MULTILINE | re.DOTALL)
+ if not res:
+ warn('Extended config extraction failed', UserWarning)
+
+ config_text = '\n'.join(res)
+ if len(config_text.splitlines()) < 350:
+ warn('Extended config extraction too short: %d'
+ % len(config_text.splitlines()),
+ UserWarning)
+
+ config_content = EXTENDED_CONFIG
+ except Exception as e:
+ # If the warning was explicitly enabled, raise
+ if isinstance(e, UserWarning):
+ raise
+ pywikibot.output('Exception while creating extended user-config; '
+ 'falling back to simple user-config.')
+ pywikibot.exception()
+
+ try:
with codecs.open(_fnc, "w", "utf-8") as f:
- f.write(config_content.format(**locals()))
+ f.write(config_content.format(main_family=main_family,
+ main_lang=main_lang,
+ usernames=usernames,
+ config_text=config_text))
+
pywikibot.output(u"'%s' written." % _fnc)
+ except:
+ try:
+ os.remove(_fnc)
+ except:
+ pass
+ raise
def create_user_fixes():
@@ -318,11 +332,42 @@
""")
pywikibot.output(u"'%s' written." % _fnf)
-if __name__ == "__main__":
- while True:
+
+def main(*args):
+ """
+ Process command line arguments and generate user-config.
+
+ If args is an empty list, sys.argv is used.
+
+ @param args: command line arguments
+ @type args: list of unicode
+ """
+ global base_dir
+ # Force default
+ if config.family == 'wikipedia' and config.mylang == 'language':
+ config.mylang = 'en'
+
+ default_args = (config.family, config.mylang, None)
+
+ local_args = pywikibot.handle_args(args)
+ if local_args:
+ pywikibot.output('Unknown arguments: %s' % ' '.join(local_args))
+ return False
+
+ username = config.usernames[config.family].get(config.mylang)
+ args = (config.family, config.mylang, username)
+
+ if args != default_args:
+ force = True
+ pywikibot.output(u'Automatically generating user-config.py')
+ else:
+ force = False
+
+ while not force or config.verbose_output:
pywikibot.output(u'\nYour default user directory is "%s"' % base_dir)
if pywikibot.input_yn("Do you want to use that directory?",
- default=False, automatic_quit=False):
+ default=True, automatic_quit=False,
+ force=force):
break
else:
new_base = change_base_dir()
@@ -332,12 +377,13 @@
copied_config = False
copied_fixes = False
- while True:
+ while not force or config.verbose_output:
if os.path.exists(os.path.join(base_dir, "user-config.py")):
break
if pywikibot.input_yn(
"Do you want to copy user files from an existing Pywikibot "
"installation?",
+ default=False, force=force,
automatic_quit=False):
oldpath = pywikibot.input("Path to existing user-config.py?")
if not os.path.exists(oldpath):
@@ -349,7 +395,6 @@
if not os.path.isfile(os.path.join(oldpath, "user-config.py")):
pywikibot.error("No user_config.py found in that directory")
continue
- import shutil
shutil.copyfile(os.path.join(oldpath, "user-config.py"),
os.path.join(base_dir, "user-config.py"))
copied_config = True
@@ -362,16 +407,23 @@
else:
break
if not os.path.isfile(os.path.join(base_dir, "user-config.py")):
- if pywikibot.input_yn('Create user-config.py file? Required for '
- 'running bots.',
- default=False, automatic_quit=False):
- create_user_config()
+ if ((force and not config.verbose_output) or
+ pywikibot.input_yn('Create user-config.py file? Required for '
+ 'running bots.',
+ default=True, automatic_quit=False,
+ force=force)):
+ create_user_config(args, force=force)
elif not copied_config:
pywikibot.output("user-config.py already exists in the directory")
if not os.path.isfile(os.path.join(base_dir, "user-fixes.py")):
- if pywikibot.input_yn('Create user-fixes.py file? Optional and for '
- 'advanced users.',
- default=False, automatic_quit=False):
+ if ((force and not config.verbose_output) or
+ pywikibot.input_yn('Create user-fixes.py file? Optional and '
+ 'for advanced users.',
+ force=force,
+ default=False, automatic_quit=False)):
create_user_fixes()
elif not copied_fixes:
pywikibot.output("user-fixes.py already exists in the directory")
+
+if __name__ == '__main__':
+ main()
diff --git a/pywikibot/bot.py b/pywikibot/bot.py
index 580f760..446e8e1 100644
--- a/pywikibot/bot.py
+++ b/pywikibot/bot.py
@@ -241,15 +241,17 @@
warnings_logger = logging.getLogger("py.warnings")
warnings_logger.setLevel(DEBUG)
- if hasattr(logging, 'captureWarnings'):
- logging.captureWarnings(True) # introduced in Python >= 2.7
- else:
- backports.captureWarnings(True)
+ # If there are command line warnings options, do not override them
+ if not sys.warnoptions:
+ if hasattr(logging, 'captureWarnings'):
+ logging.captureWarnings(True) # introduced in Python >= 2.7
+ else:
+ backports.captureWarnings(True)
- if config.debug_log or 'deprecation' in config.log:
- warnings.filterwarnings("always")
- elif config.verbose_output:
- warnings.filterwarnings("module")
+ if config.debug_log or 'deprecation' in config.log:
+ warnings.filterwarnings("always")
+ elif config.verbose_output:
+ warnings.filterwarnings("module")
root_logger.handlers = [] # remove any old handlers
@@ -531,7 +533,7 @@
# User input functions
-def input(question, password=False):
+def input(question, password=False, default='', force=False):
"""Ask the user a question, return the user's answer.
@param question: a string that will be shown to the user. Don't add a
@@ -539,18 +541,24 @@
@type question: unicode
@param password: if True, hides the user's input (for password entry).
@type password: bool
+ @param default: The default answer if none was entered. None to require
+ an answer.
+ @type default: basestring
+ @param force: Automatically use the default
+ @type force: bool
+ @rtype: unicode
@rtype: unicode
"""
# make sure logging system has been initialized
if not _handlers_initialized:
init_handlers()
- data = ui.input(question, password)
+ data = ui.input(question, password=password, default=default, force=force)
return data
def input_choice(question, answers, default=None, return_shortcut=True,
- automatic_quit=True):
+ automatic_quit=True, force=False):
"""
Ask the user the question and return one of the valid answers.
@@ -569,6 +577,8 @@
@param automatic_quit: Adds the option 'Quit' ('q') and throw a
L{QuitKeyboardInterrupt} if selected.
@type automatic_quit: bool
+ @param force: Automatically use the default
+ @type force: bool
@return: The selected answer shortcut or index. Is -1 if the default is
selected, it does not return the shortcut and the default is not a
valid shortcut.
@@ -579,10 +589,10 @@
init_handlers()
return ui.input_choice(question, answers, default, return_shortcut,
- automatic_quit)
+ automatic_quit=automatic_quit, force=force)
-def input_yn(question, default=None, automatic_quit=True):
+def input_yn(question, default=None, automatic_quit=True, force=False):
"""
Ask the user a yes/no question and returns the answer as a bool.
@@ -594,6 +604,8 @@
@param automatic_quit: Adds the option 'Quit' ('q') and throw a
L{QuitKeyboardInterrupt} if selected.
@type automatic_quit: bool
+ @param force: Automatically use the default
+ @type force: bool
@return: Return True if the user selected yes and False if the user
selected no. If the default is not None it'll return True if default
is True or 'y' and False if default is False or 'n'.
@@ -607,7 +619,7 @@
assert default in ['y', 'Y', 'n', 'N', None]
return input_choice(question, [('Yes', 'y'), ('No', 'n')], default,
- automatic_quit=automatic_quit) == 'y'
+ automatic_quit=automatic_quit, force=force) == 'y'
@deprecated('input_choice')
@@ -639,6 +651,30 @@
automatic_quit=False)
+def input_list_choice(question, answers, default=None,
+ automatic_quit=True, force=False):
+ """
+ Ask the user the question and return one of the valid answers.
+
+ @param question: The question asked without trailing spaces.
+ @type question: basestring
+ @param answers: The valid answers each containing a full length answer.
+ @type answers: Iterable of basestring
+ @param default: The result if no answer was entered. It must not be in the
+ valid answers and can be disabled by setting it to None.
+ @type default: basestring
+ @param force: Automatically use the default
+ @type force: bool
+ @return: The selected answer.
+ @rtype: basestring
+ """
+ if not _handlers_initialized:
+ init_handlers()
+
+ return ui.input_list_choice(question, answers, default=default,
+ force=force)
+
+
# Command line parsing and help
def calledModuleName():
"""Return the name of the module calling this function.
diff --git a/pywikibot/config2.py b/pywikibot/config2.py
index 4610e72..db23c5e 100644
--- a/pywikibot/config2.py
+++ b/pywikibot/config2.py
@@ -1,11 +1,26 @@
# -*- coding: utf-8 -*-
"""
-Module to define and load pywikibot configuration.
+Module to define and load pywikibot configuration default and user preferences.
-Provides two family class methods which can be used in
+User preferences are loaded from a python file called user-config.py, which
+may be located in directory specified by the environment variable
+PYWIKIBOT2_DIR, or the same directory as pwb.py, or in a directory within
+the users home. See get_base_dir for more information.
+
+If user-config.py can not be found in any of those locations, this module
+will fail to load unless the environment variable PYWIKIBOT2_NO_USER_CONFIG
+is set to a value other than '0'. i.e. PYWIKIBOT2_NO_USER_CONFIG=1 will
+allow config to load without a user-config.py. However, warnings will be
+shown if user-config.py was not loaded.
+To prevent these warnings, set PYWIKIBOT2_NO_USER_CONFIG=2.
+
+Provides two functions to register family classes which can be used in
the user-config:
- register_family_file
- register_families_folder
+
+Other functions made available to user-config:
+ - user_home_path
Sets module global base_dir and provides utility methods to
build paths relative to base_dir:
@@ -37,6 +52,10 @@
# occur directly after the imports. At that point globals() only contains the
# names and some magic variables (like __name__)
_imports = frozenset(name for name in globals() if not name.startswith('_'))
+
+_no_user_config = os.environ.get('PYWIKIBOT2_NO_USER_CONFIG')
+if _no_user_config == '0':
+ _no_user_config = None
class _ConfigurationDeprecationWarning(UserWarning):
@@ -179,6 +198,11 @@
ignore_file_security_warnings = False
+def user_home_path(path):
+ """Return a file path to a file in the user home."""
+ return os.path.join(os.path.expanduser('~'), path)
+
+
def get_base_dir(test_directory=None):
r"""Return the directory in which user-specific information is stored.
@@ -266,8 +290,9 @@
# check if user-config.py is in base_dir
if not exists(base_dir):
exc_text = "No user-config.py found in directory '%s'.\n" % base_dir
- if os.environ.get('PYWIKIBOT2_NO_USER_CONFIG', '0') == '1':
- print(exc_text)
+ if _no_user_config:
+ if _no_user_config != '2':
+ print(exc_text)
else:
exc_text += " Please check that user-config.py is stored in the correct location.\n"
exc_text += " Directory where user-config.py is searched is determined as follows:\n\n"
@@ -587,12 +612,12 @@
# Example for a pywikibot running on wmflabs:
# db_hostname = 'enwiki.labsdb'
# db_name_format = '{0}_p'
-# db_connect_file = '~/replica.my.cnf'
+# db_connect_file = user_home_path('replica.my.cnf')
db_hostname = 'localhost'
db_username = ''
db_password = ''
db_name_format = '{0}'
-db_connect_file = os.path.expanduser('~/.my.cnf')
+db_connect_file = user_home_path('.my.cnf')
# ############# SEARCH ENGINE SETTINGS ##############
@@ -844,8 +869,9 @@
# Get the user files
_thislevel = 0
-if os.environ.get('PYWIKIBOT2_NO_USER_CONFIG', '0') == '1':
- print("WARNING: Skipping loading of user-config.py.")
+if _no_user_config:
+ if _no_user_config != '2':
+ print("WARNING: Skipping loading of user-config.py.")
_fns = []
else:
_fns = [os.path.join(_base_dir, "user-config.py")]
@@ -934,7 +960,7 @@
# Fix up default site
-if family == 'wikipedia' and mylang == 'language':
+if family == 'wikipedia' and mylang == 'language' and _no_user_config != '2':
print("WARNING: family and mylang are not set.\n"
"Defaulting to family='test' and mylang='test'.")
family = mylang = 'test'
diff --git a/pywikibot/userinterfaces/terminal_interface_base.py b/pywikibot/userinterfaces/terminal_interface_base.py
index 5a4c4b9..968a14b 100755
--- a/pywikibot/userinterfaces/terminal_interface_base.py
+++ b/pywikibot/userinterfaces/terminal_interface_base.py
@@ -11,6 +11,7 @@
import getpass
import logging
+import math
import re
import sys
@@ -198,20 +199,56 @@
else:
return raw_input() # noqa
- def input(self, question, password=False):
+ def input(self, question, password=False, default='', force=False):
"""
Ask the user a question and return the answer.
Works like raw_input(), but returns a unicode string instead of ASCII.
- Unlike raw_input, this function automatically adds a space after the
- question.
+ Unlike raw_input, this function automatically adds a colon and space
+ after the question if they are not already present. Also recognises
+ a trailing question mark.
+
+ @param question: The question, without trailing whitespace.
+ @type question: basestring
+ @param password: if True, hides the user's input (for password entry).
+ @type password: bool
+ @param default: The default answer if none was entered. None to require
+ an answer.
+ @type default: basestring
+ @param force: Automatically use the default
+ @type force: bool
+ @rtype: unicode
"""
+ assert(not password or not default)
+ end_marker = ':'
+ question = question.strip()
+ if question[-1] == ':':
+ question = question[:-1]
+ elif question[-1] == '?':
+ question = question[:-1]
+ end_marker = '?'
+ if default:
+ question = question + ' (default: %s)' % default
+ question = question + end_marker
+ if force:
+ self.output(question + '\n')
+ return default
# sound the terminal bell to notify the user
if config.ring_bell:
sys.stdout.write('\07')
# TODO: make sure this is logged as well
- self.output(question + ' ')
+ while True:
+ self.output(question + ' ')
+ text = self._input_reraise_cntl_c(password)
+ if text:
+ return text
+
+ if default is not None:
+ return default
+
+ def _input_reraise_cntl_c(self, password):
+ """Input and decode, and re-raise Control-C."""
try:
if password:
# Python 3 requires that stderr gets flushed, otherwise is the
@@ -227,7 +264,7 @@
return text
def input_choice(self, question, options, default=None, return_shortcut=True,
- automatic_quit=True):
+ automatic_quit=True, force=False):
"""
Ask the user and returns a value from the options.
@@ -248,6 +285,8 @@
doesn't add the option but throw the exception when the option was
selected.
@type automatic_quit: bool or int
+ @param force: Automatically use the default
+ @type force: bool
@return: If return_shortcut the shortcut of options or the value of
default (if it's not None). Otherwise the index of the answer in
options. If default is not a shortcut, it'll return -1.
@@ -295,7 +334,10 @@
question = u'{0} ({1})'.format(question, ', '.join(formatted_options))
answer = None
while answer is None:
- answer = self.input(question)
+ if force:
+ self.output(question + '\n')
+ else:
+ answer = self.input(question)
if default and not answer: # nothing entered
answer = default_index
else:
@@ -324,6 +366,31 @@
default=default, return_shortcut=True,
automatic_quit=False)
+ def input_list_choice(self, question, answers, default=None, force=False):
+ """Ask the user to select one entry from a list of entries."""
+ message = question
+ clist = answers
+
+ line_template = u"{{0: >{0}}}: {{1}}".format(int(math.log10(len(clist)) + 1))
+ for n, i in enumerate(clist):
+ pywikibot.output(line_template.format(n + 1, i))
+
+ while True:
+ choice = self.input(message, default=default, force=force)
+ try:
+ choice = int(choice) - 1
+ except ValueError:
+ try:
+ choice = clist.index(choice)
+ except IndexError:
+ choice = -1
+
+ # User typed choice number
+ if 0 <= choice < len(clist):
+ return clist[choice]
+ else:
+ pywikibot.error("Invalid response")
+
def editText(self, text, jumpIndex=None, highlight=None):
"""Return the text as edited by the user.
diff --git a/tests/i18n_tests.py b/tests/i18n_tests.py
index c116abb..0634181 100644
--- a/tests/i18n_tests.py
+++ b/tests/i18n_tests.py
@@ -386,7 +386,7 @@
rv = i18n.input('pywikibot-enter-category-name',
fallback_prompt='dummy output')
self.assertEqual(rv, 'dummy input')
- self.assertIn('dummy output ', self.output_text)
+ self.assertIn('dummy output: ', self.output_text)
if __name__ == '__main__':
diff --git a/tests/ui_tests.py b/tests/ui_tests.py
index 79fe078..eea9256 100644
--- a/tests/ui_tests.py
+++ b/tests/ui_tests.py
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
"""Tests for the user interface."""
#
-# (C) Pywikibot team, 2008-2014
+# (C) Pywikibot team, 2008-2015
#
# Distributed under the terms of the MIT license.
#
@@ -318,7 +318,7 @@
"""Terminal input tests."""
- input_choice_output = 'question ([A]nswer 1, a[n]swer 2, an[s]wer 3) '
+ input_choice_output = 'question ([A]nswer 1, a[n]swer 2, an[s]wer 3): '
def testInput(self):
newstdin.write('input to read\n')
@@ -327,7 +327,7 @@
returned = pywikibot.input('question')
self.assertEqual(newstdout.getvalue(), '')
- self.assertEqual(newstderr.getvalue(), 'question ')
+ self.assertEqual(newstderr.getvalue(), 'question: ')
self.assertIsInstance(returned, unicode)
self.assertEqual(returned, u'input to read')
@@ -455,7 +455,7 @@
self.assertEqual(newstdout.getvalue(), '')
self.assertEqual(
newstderr.getvalue(),
- self._encode(u'Википедию? ', 'utf-8'))
+ self._encode(u'Википедию? ', 'utf-8'))
self.assertIsInstance(returned, unicode)
self.assertEqual(returned, u'Заглавная_страница')
diff --git a/tox.ini b/tox.ini
index 0dfda1d..21635da 100644
--- a/tox.ini
+++ b/tox.ini
@@ -116,8 +116,8 @@
flake8-docstrings
[testenv:nose]
-setenv = PYWIKIBOT2_NO_USER_CONFIG=1
commands =
+ python -W error::UserWarning -m generate_user_files -family:test -lang:test -v
nosetests --version
nosetests --with-doctest --with-doctest-ignore-unicode -v -a "!net" tests pywikibot {[params]nose_skip}
deps =
@@ -127,8 +127,8 @@
[testenv:nose34]
basepython = python3
-setenv = PYWIKIBOT2_NO_USER_CONFIG=1
commands =
+ python -W error::UserWarning -m generate_user_files -family:test -lang:test -v
nosetests --version
nosetests --with-doctest --with-doctest-ignore-unicode -v -a "!net" tests pywikibot {[params]nose_skip}
deps =
--
To view, visit https://gerrit.wikimedia.org/r/204334
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I7356d0fa38032fbbd18b72cfe267c36ee57eb7a7
Gerrit-PatchSet: 19
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <ladsgroup(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: jenkins-bot <>
jenkins-bot has submitted this change and it was merged.
Change subject: Simplify claim removal in ItemPage.toJSON
......................................................................
Simplify claim removal in ItemPage.toJSON
Also removes incorrect indent level on line 3346.
Change-Id: I16f67dbbb3686e18cee387d2abc6d6239d05d11c
---
M pywikibot/page.py
1 file changed, 37 insertions(+), 14 deletions(-)
Approvals:
XZise: Looks good to me, but someone else must approve
Mpaa: Looks good to me, approved
jenkins-bot: Verified
diff --git a/pywikibot/page.py b/pywikibot/page.py
index 4e29b24..8b7d1cf 100644
--- a/pywikibot/page.py
+++ b/pywikibot/page.py
@@ -3173,6 +3173,17 @@
data[type_key] = source
def toJSON(self, diffto=None):
+ """
+ Create JSON suitable for Wikibase API.
+
+ When diffto is provided, JSON representing differences
+ to the provided data is created.
+
+ @param diffto: JSON containing claim data
+ @type diffto: dict
+
+ @return: dict
+ """
data = {}
self._diff_to('labels', 'language', 'value', diffto, data)
@@ -3507,6 +3518,17 @@
return self.__class__(target.site, target.title(), target.namespace())
def toJSON(self, diffto=None):
+ """
+ Create JSON suitable for Wikibase API.
+
+ When diffto is provided, JSON representing differences
+ to the provided data is created.
+
+ @param diffto: JSON containing claim data
+ @type diffto: dict
+
+ @return: dict
+ """
data = super(ItemPage, self).toJSON(diffto=diffto)
self._diff_to('sitelinks', 'site', 'title', diffto, data)
@@ -3517,23 +3539,24 @@
claims[prop] = [claim.toJSON() for claim in self.claims[prop]]
if diffto and 'claims' in diffto:
- temp = {}
+ temp = defaultdict(list)
+ claim_ids = set()
+
+ diffto_claims = diffto['claims']
+
for prop in claims:
for claim in claims[prop]:
- if (prop not in diffto['claims'] or
- claim not in diffto['claims'][prop]):
- if prop not in temp:
- temp[prop] = []
+ if (prop not in diffto_claims or
+ claim not in diffto_claims[prop]):
temp[prop].append(claim)
- for prop in diffto['claims']:
- if prop not in claims:
- claims[prop] = []
- for claim1 in diffto['claims'][prop]:
- if 'id' in claim1 and claim1['id'] not in \
- [claim2['id'] for claim2 in claims[prop] if 'id' in claim2]:
- if prop not in temp:
- temp[prop] = []
- temp[prop].append({'id': claim1['id'], 'remove': ''})
+
+ claim_ids.add(claim['id'])
+
+ for prop, prop_claims in diffto_claims.items():
+ for claim in prop_claims:
+ if 'id' in claim and claim['id'] not in claim_ids:
+ temp[prop].append({'id': claim['id'], 'remove': ''})
+
claims = temp
if claims:
--
To view, visit https://gerrit.wikimedia.org/r/203512
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I16f67dbbb3686e18cee387d2abc6d6239d05d11c
Gerrit-PatchSet: 3
Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Owner: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: John Vandenberg <jayvdb(a)gmail.com>
Gerrit-Reviewer: Ladsgroup <ladsgroup(a)gmail.com>
Gerrit-Reviewer: Merlijn van Deen <valhallasw(a)arctus.nl>
Gerrit-Reviewer: Mpaa <mpaa.wiki(a)gmail.com>
Gerrit-Reviewer: Ricordisamoa <ricordisamoa(a)openmailbox.org>
Gerrit-Reviewer: XZise <CommodoreFabianus(a)gmx.de>
Gerrit-Reviewer: jenkins-bot <>