summaryrefslogtreecommitdiffstats
path: root/Source/QtDialog/QCMakeWidgets.h
diff options
context:
space:
mode:
authorBrad King <brad.king@kitware.com>2016-04-29 14:53:13 (GMT)
committerBrad King <brad.king@kitware.com>2016-04-29 17:58:54 (GMT)
commite1c7747253ac71a5215dd32a910b62a1fd8c561a (patch)
treedd4f8bf9663bf7a64c01d9391c3559f7b419dcb7 /Source/QtDialog/QCMakeWidgets.h
parent180538c70634dd6dc7fc68b4afbc1cd288c5b5c6 (diff)
downloadCMake-e1c7747253ac71a5215dd32a910b62a1fd8c561a.zip
CMake-e1c7747253ac71a5215dd32a910b62a1fd8c561a.tar.gz
CMake-e1c7747253ac71a5215dd32a910b62a1fd8c561a.tar.bz2
Format include directive blocks and ordering with clang-format
Sort include directives within each block (separated by a blank line) in lexicographic order (except to prioritize `sys/types.h` first). First run `clang-format` with the config file: --- SortIncludes: false ... Commit the result temporarily. Then run `clang-format` again with: --- SortIncludes: true IncludeCategories: - Regex: 'sys/types.h' Priority: -1 ... Commit the result temporarily. Start a new branch and cherry-pick the second commit. Manually resolve conflicts to preserve indentation of re-ordered includes. This cleans up the include ordering without changing any other style. Use the following command to run `clang-format`: $ git ls-files -z -- \ '*.c' '*.cc' '*.cpp' '*.cxx' '*.h' '*.hh' '*.hpp' '*.hxx' | egrep -z -v '(Lexer|Parser|ParserHelper)\.' | egrep -z -v '^Source/cm_sha2' | egrep -z -v '^Source/(kwsys|CursesDialog/form)/' | egrep -z -v '^Utilities/(KW|cm).*/' | egrep -z -v '^Tests/Module/GenerateExportHeader' | egrep -z -v '^Tests/RunCMake/CommandLine/cmake_depends/test_UTF-16LE.h' | xargs -0 clang-format -i This selects source files that do not come from a third-party. Inspired-by: Daniel Pfeifer <daniel@pfeifer-mail.de>
Diffstat (limited to 'Source/QtDialog/QCMakeWidgets.h')
-rw-r--r--Source/QtDialog/QCMakeWidgets.h2
1 files changed, 1 insertions, 1 deletions
diff --git a/Source/QtDialog/QCMakeWidgets.h b/Source/QtDialog/QCMakeWidgets.h
index 2207282..759b635 100644
--- a/Source/QtDialog/QCMakeWidgets.h
+++ b/Source/QtDialog/QCMakeWidgets.h
@@ -13,9 +13,9 @@
#ifndef QCMakeWidgets_h
#define QCMakeWidgets_h
-#include <QLineEdit>
#include <QComboBox>
#include <QCompleter>
+#include <QLineEdit>
class QToolButton;
ight'>181
-rwxr-xr-xLib/cProfile.py9
-rwxr-xr-xLib/cgi.py263
-rw-r--r--Lib/cmd.py20
-rw-r--r--Lib/code.py5
-rw-r--r--Lib/codecs.py23
-rw-r--r--Lib/collections.py287
-rw-r--r--Lib/compileall.py278
-rw-r--r--Lib/concurrent/__init__.py1
-rw-r--r--Lib/concurrent/futures/__init__.py18
-rw-r--r--Lib/concurrent/futures/_base.py567
-rw-r--r--Lib/concurrent/futures/process.py363
-rw-r--r--Lib/concurrent/futures/thread.py136
-rw-r--r--Lib/configparser.py1288
-rw-r--r--Lib/contextlib.py62
-rw-r--r--Lib/copy.py4
-rw-r--r--Lib/csv.py16
-rw-r--r--Lib/ctypes/__init__.py57
-rw-r--r--Lib/ctypes/test/test_arrays.py4
-rw-r--r--Lib/ctypes/test/test_bitfields.py4
-rw-r--r--Lib/ctypes/test/test_buffers.py14
-rw-r--r--Lib/ctypes/test/test_bytes.py19
-rw-r--r--Lib/ctypes/test/test_callbacks.py34
-rw-r--r--Lib/ctypes/test/test_cast.py8
-rw-r--r--Lib/ctypes/test/test_cfuncs.py2
-rw-r--r--Lib/ctypes/test/test_errno.py75
-rw-r--r--Lib/ctypes/test/test_internals.py18
-rw-r--r--Lib/ctypes/test/test_keeprefs.py8
-rw-r--r--Lib/ctypes/test/test_libc.py2
-rw-r--r--Lib/ctypes/test/test_loading.py2
-rw-r--r--Lib/ctypes/test/test_objects.py4
-rw-r--r--Lib/ctypes/test/test_parameters.py10
-rw-r--r--Lib/ctypes/test/test_prototypes.py2
-rw-r--r--Lib/ctypes/test/test_python_api.py4
-rw-r--r--Lib/ctypes/test/test_random_things.py4
-rw-r--r--Lib/ctypes/test/test_repr.py4
-rw-r--r--Lib/ctypes/test/test_returnfuncptrs.py8
-rw-r--r--Lib/ctypes/test/test_sizes.py9
-rw-r--r--Lib/ctypes/test/test_stringptr.py6
-rw-r--r--Lib/ctypes/test/test_strings.py17
-rw-r--r--Lib/ctypes/test/test_structures.py24
-rw-r--r--Lib/ctypes/test/test_unicode.py103
-rw-r--r--Lib/ctypes/util.py31
-rw-r--r--Lib/ctypes/wintypes.py169
-rw-r--r--Lib/datetime.py2108
-rw-r--r--Lib/dbm/__init__.py23
-rw-r--r--Lib/dbm/dumb.py4
-rw-r--r--Lib/decimal.py589
-rw-r--r--Lib/difflib.py131
-rw-r--r--Lib/dis.py108
-rw-r--r--Lib/distutils/__init__.py2
-rw-r--r--Lib/distutils/command/bdist_msi.py1
-rw-r--r--Lib/distutils/command/build_ext.py6
-rw-r--r--Lib/distutils/command/install.py27
-rw-r--r--Lib/distutils/file_util.py9
-rw-r--r--Lib/distutils/sysconfig.py103
-rw-r--r--Lib/distutils/tests/test_bdist_msi.py25
-rw-r--r--Lib/distutils/tests/test_build.py55
-rw-r--r--Lib/distutils/tests/test_build_ext.py19
-rw-r--r--[-rwxr-xr-x]Lib/distutils/tests/test_clean.py0
-rw-r--r--Lib/distutils/tests/test_dep_util.py81
-rw-r--r--Lib/distutils/tests/test_dist.py1
-rw-r--r--Lib/distutils/tests/test_sysconfig.py11
-rw-r--r--Lib/distutils/text_file.py5
-rw-r--r--Lib/distutils/util.py9
-rw-r--r--Lib/doctest.py39
-rw-r--r--Lib/email/__init__.py19
-rw-r--r--Lib/email/_parseaddr.py20
-rw-r--r--Lib/email/base64mime.py8
-rw-r--r--Lib/email/charset.py33
-rw-r--r--Lib/email/encoders.py9
-rw-r--r--Lib/email/feedparser.py7
-rw-r--r--Lib/email/generator.py215
-rw-r--r--Lib/email/header.py29
-rw-r--r--Lib/email/message.py104
-rw-r--r--Lib/email/parser.py47
-rw-r--r--Lib/email/quoprimime.py6
-rw-r--r--Lib/email/test/data/msg_10.txt7
-rw-r--r--Lib/email/test/data/msg_26.txt3
-rw-r--r--Lib/email/test/test_email.py468
-rw-r--r--Lib/email/test/test_email_codecs.py46
-rw-r--r--Lib/email/test/test_email_torture.py6
-rw-r--r--Lib/email/utils.py11
-rw-r--r--Lib/encodings/__init__.py2
-rw-r--r--Lib/encodings/aliases.py8
-rw-r--r--Lib/encodings/base64_codec.py55
-rw-r--r--Lib/encodings/bz2_codec.py77
-rw-r--r--Lib/encodings/cp720.py309
-rw-r--r--Lib/encodings/cp858.py698
-rw-r--r--Lib/encodings/hex_codec.py55
-rw-r--r--Lib/encodings/punycode.py1
-rw-r--r--Lib/encodings/quopri_codec.py56
-rwxr-xr-xLib/encodings/rot_13.py113
-rw-r--r--Lib/encodings/uu_codec.py99
-rw-r--r--Lib/encodings/zlib_codec.py77
-rw-r--r--Lib/fileinput.py6
-rw-r--r--Lib/fnmatch.py47
-rw-r--r--Lib/fractions.py102
-rw-r--r--Lib/ftplib.py314
-rw-r--r--Lib/functools.py158
-rw-r--r--Lib/genericpath.py2
-rw-r--r--Lib/getopt.py1
-rw-r--r--Lib/gettext.py50
-rw-r--r--Lib/gzip.py245
-rw-r--r--Lib/hashlib.py81
-rw-r--r--Lib/heapq.py4
-rw-r--r--Lib/hmac.py15
-rw-r--r--Lib/html/__init__.py21
-rw-r--r--Lib/html/parser.py99
-rw-r--r--Lib/http/client.py217
-rw-r--r--Lib/http/cookiejar.py20
-rw-r--r--Lib/http/cookies.py244
-rw-r--r--Lib/http/server.py92
-rw-r--r--Lib/idlelib/Bindings.py8
-rw-r--r--Lib/idlelib/EditorWindow.py14
-rw-r--r--Lib/idlelib/NEWS.txt4
-rw-r--r--Lib/idlelib/PyShell.py33
-rw-r--r--Lib/idlelib/idlever.py2
-rw-r--r--Lib/idlelib/macosxSupport.py96
-rw-r--r--Lib/idlelib/run.py7
-rw-r--r--Lib/imaplib.py80
-rw-r--r--Lib/importlib/__init__.py2
-rw-r--r--Lib/importlib/_bootstrap.py594
-rw-r--r--Lib/importlib/abc.py246
-rw-r--r--Lib/importlib/test/__init__.py31
-rw-r--r--Lib/importlib/test/__main__.py29
-rw-r--r--Lib/importlib/test/benchmark.py184
-rw-r--r--Lib/importlib/test/builtin/test_loader.py9
-rw-r--r--Lib/importlib/test/extension/test_case_sensitivity.py3
-rw-r--r--Lib/importlib/test/extension/test_finder.py3
-rw-r--r--Lib/importlib/test/extension/test_loader.py5
-rw-r--r--Lib/importlib/test/extension/test_path_hook.py2
-rw-r--r--Lib/importlib/test/frozen/test_loader.py7
-rw-r--r--Lib/importlib/test/import_/test___package__.py29
-rw-r--r--Lib/importlib/test/import_/test_api.py22
-rw-r--r--Lib/importlib/test/import_/test_fromlist.py11
-rw-r--r--Lib/importlib/test/import_/test_packages.py10
-rw-r--r--Lib/importlib/test/import_/test_path.py35
-rw-r--r--Lib/importlib/test/import_/test_relative_imports.py13
-rw-r--r--Lib/importlib/test/import_/util.py15
-rw-r--r--Lib/importlib/test/regrtest.py35
-rw-r--r--Lib/importlib/test/source/test_abc_loader.py556
-rw-r--r--Lib/importlib/test/source/test_case_sensitivity.py12
-rw-r--r--Lib/importlib/test/source/test_file_loader.py300
-rw-r--r--Lib/importlib/test/source/test_finder.py39
-rw-r--r--Lib/importlib/test/source/test_path_hook.py7
-rw-r--r--Lib/importlib/test/source/test_source_encoding.py13
-rw-r--r--Lib/importlib/test/source/util.py19
-rw-r--r--Lib/importlib/test/test_abc.py13
-rw-r--r--Lib/importlib/test/test_api.py5
-rw-r--r--Lib/importlib/test/test_util.py4
-rw-r--r--Lib/importlib/test/util.py27
-rw-r--r--Lib/importlib/util.py1
-rw-r--r--Lib/inspect.py221
-rw-r--r--Lib/json/decoder.py10
-rw-r--r--Lib/json/encoder.py11
-rw-r--r--Lib/json/scanner.py10
-rwxr-xr-xLib/keyword.py24
-rwxr-xr-xLib/lib2to3/pgen2/token.py2
-rwxr-xr-xLib/lib2to3/tests/pytree_idempotency.py2
-rw-r--r--Lib/linecache.py4
-rw-r--r--Lib/locale.py105
-rw-r--r--Lib/logging/__init__.py613
-rw-r--r--Lib/logging/config.py704
-rw-r--r--Lib/logging/handlers.py287
-rw-r--r--Lib/macpath.py5
-rw-r--r--Lib/mailbox.py422
-rw-r--r--Lib/mimetypes.py54
-rw-r--r--Lib/modulefinder.py22
-rw-r--r--Lib/msilib/__init__.py1
-rw-r--r--Lib/multiprocessing/__init__.py4
-rw-r--r--Lib/multiprocessing/connection.py29
-rw-r--r--Lib/multiprocessing/forking.py10
-rw-r--r--Lib/multiprocessing/managers.py2
-rw-r--r--Lib/multiprocessing/pool.py144
-rw-r--r--Lib/multiprocessing/synchronize.py3
-rw-r--r--Lib/nntplib.py1149
-rw-r--r--Lib/ntpath.py68
-rw-r--r--Lib/numbers.py2
-rw-r--r--Lib/opcode.py11
-rw-r--r--Lib/os.py236
-rw-r--r--Lib/os2emxpath.py3
-rw-r--r--Lib/pdb.doc202
-rwxr-xr-xLib/pdb.py1319
-rw-r--r--Lib/pickle.py98
-rw-r--r--Lib/pickletools.py79
-rw-r--r--Lib/pkgutil.py3
-rw-r--r--Lib/plat-atheos/IN.py944
-rw-r--r--Lib/plat-atheos/TYPES.py142
-rw-r--r--Lib/plat-atheos/regen3
-rwxr-xr-x[-rw-r--r--]Lib/plat-freebsd4/regen0
-rwxr-xr-x[-rw-r--r--]Lib/plat-freebsd5/regen0
-rwxr-xr-x[-rw-r--r--]Lib/plat-freebsd6/regen0
-rwxr-xr-x[-rw-r--r--]Lib/plat-freebsd7/regen0
-rwxr-xr-x[-rw-r--r--]Lib/plat-freebsd8/regen0
-rwxr-xr-x[-rw-r--r--]Lib/plat-os2emx/regen0
-rw-r--r--[-rwxr-xr-x]Lib/plat-sunos5/IN.py0
-rwxr-xr-xLib/platform.py77
-rw-r--r--Lib/plistlib.py2
-rw-r--r--Lib/poplib.py18
-rw-r--r--Lib/posixpath.py9
-rw-r--r--Lib/pprint.py9
-rwxr-xr-xLib/profile.py20
-rw-r--r--Lib/pstats.py111
-rw-r--r--Lib/py_compile.py150
-rwxr-xr-xLib/pydoc.py656
-rw-r--r--Lib/pydoc_data/_pydoc.css6
-rw-r--r--Lib/pydoc_data/topics.py78
-rw-r--r--Lib/queue.py10
-rwxr-xr-xLib/quopri.py20
-rw-r--r--Lib/random.py137
-rw-r--r--Lib/re.py40
-rw-r--r--Lib/reprlib.py32
-rw-r--r--Lib/runpy.py193
-rw-r--r--Lib/shelve.py3
-rw-r--r--Lib/shutil.py491
-rw-r--r--Lib/site.py176
-rwxr-xr-xLib/smtpd.py244
-rwxr-xr-xLib/smtplib.py250
-rw-r--r--Lib/socket.py100
-rw-r--r--Lib/socketserver.py48
-rw-r--r--Lib/sqlite3/__init__.py3
-rw-r--r--Lib/sqlite3/dbapi2.py3
-rw-r--r--Lib/sqlite3/test/dbapi.py75
-rw-r--r--Lib/sqlite3/test/regression.py113
-rw-r--r--Lib/sqlite3/test/transactions.py20
-rw-r--r--Lib/sqlite3/test/types.py8
-rw-r--r--Lib/sre_compile.py2
-rw-r--r--Lib/ssl.py192
-rw-r--r--Lib/string.py51
-rw-r--r--Lib/struct.py2
-rw-r--r--Lib/subprocess.py479
-rw-r--r--Lib/sunau.py11
-rwxr-xr-xLib/symbol.py128
-rw-r--r--Lib/symtable.py9
-rw-r--r--Lib/sysconfig.py779
-rwxr-xr-xLib/tabnanny.py9
-rw-r--r--Lib/tarfile.py680
-rw-r--r--Lib/tempfile.py103
-rw-r--r--Lib/test/__main__.py13
-rw-r--r--Lib/test/badsyntax_nocaret.py2
-rw-r--r--Lib/test/capath/4e1295a3.014
-rw-r--r--Lib/test/capath/5ed36f99.041
-rw-r--r--Lib/test/capath/6e88d7b8.014
-rw-r--r--Lib/test/capath/99d0fa06.041
-rw-r--r--Lib/test/cfgparser.2537
-rw-r--r--Lib/test/cfgparser.369
-rw-r--r--Lib/test/cmath_testcases.txt31
-rw-r--r--Lib/test/crashers/recursive_call.py2
-rw-r--r--Lib/test/crashers/underlying_dict.py20
-rw-r--r--Lib/test/curses_tests.py2
-rw-r--r--Lib/test/data/README2
-rw-r--r--Lib/test/datetimetester.py3679
-rw-r--r--Lib/test/decimaltestdata/and.decTest676
-rw-r--r--Lib/test/decimaltestdata/class.decTest262
-rw-r--r--Lib/test/decimaltestdata/comparetotal.decTest1596
-rw-r--r--Lib/test/decimaltestdata/comparetotmag.decTest1580
-rw-r--r--Lib/test/decimaltestdata/copy.decTest172
-rw-r--r--Lib/test/decimaltestdata/copyabs.decTest172
-rw-r--r--Lib/test/decimaltestdata/copynegate.decTest172
-rw-r--r--Lib/test/decimaltestdata/copysign.decTest354
-rw-r--r--Lib/test/decimaltestdata/ddAbs.decTest252
-rw-r--r--Lib/test/decimaltestdata/ddAdd.decTest2656
-rw-r--r--Lib/test/decimaltestdata/ddAnd.decTest694
-rw-r--r--Lib/test/decimaltestdata/ddBase.decTest2208
-rw-r--r--Lib/test/decimaltestdata/ddCanonical.decTest714
-rw-r--r--Lib/test/decimaltestdata/ddClass.decTest152
-rw-r--r--Lib/test/decimaltestdata/ddCompare.decTest1488
-rw-r--r--Lib/test/decimaltestdata/ddCompareSig.decTest1294
-rw-r--r--Lib/test/decimaltestdata/ddCompareTotal.decTest1412
-rw-r--r--Lib/test/decimaltestdata/ddCompareTotalMag.decTest1412
-rw-r--r--Lib/test/decimaltestdata/ddCopy.decTest176
-rw-r--r--Lib/test/decimaltestdata/ddCopyAbs.decTest176
-rw-r--r--Lib/test/decimaltestdata/ddCopyNegate.decTest176
-rw-r--r--Lib/test/decimaltestdata/ddCopySign.decTest350
-rw-r--r--Lib/test/decimaltestdata/ddDivide.decTest1726
-rw-r--r--Lib/test/decimaltestdata/ddDivideInt.decTest898
-rw-r--r--Lib/test/decimaltestdata/ddEncode.decTest990
-rw-r--r--Lib/test/decimaltestdata/ddFMA.decTest3396
-rw-r--r--Lib/test/decimaltestdata/ddInvert.decTest404
-rw-r--r--Lib/test/decimaltestdata/ddLogB.decTest318
-rw-r--r--Lib/test/decimaltestdata/ddMax.decTest644
-rw-r--r--Lib/test/decimaltestdata/ddMaxMag.decTest608
-rw-r--r--Lib/test/decimaltestdata/ddMin.decTest618
-rw-r--r--Lib/test/decimaltestdata/ddMinMag.decTest586
-rw-r--r--Lib/test/decimaltestdata/ddMinus.decTest176
-rw-r--r--Lib/test/decimaltestdata/ddMultiply.decTest1106
-rw-r--r--Lib/test/decimaltestdata/ddNextMinus.decTest252
-rw-r--r--Lib/test/decimaltestdata/ddNextPlus.decTest248
-rw-r--r--Lib/test/decimaltestdata/ddNextToward.decTest748
-rw-r--r--Lib/test/decimaltestdata/ddOr.decTest584
-rw-r--r--Lib/test/decimaltestdata/ddPlus.decTest176
-rw-r--r--Lib/test/decimaltestdata/ddQuantize.decTest1666
-rw-r--r--Lib/test/decimaltestdata/ddReduce.decTest364
-rw-r--r--Lib/test/decimaltestdata/ddRemainder.decTest1200
-rw-r--r--Lib/test/decimaltestdata/ddRemainderNear.decTest1258
-rw-r--r--Lib/test/decimaltestdata/ddRotate.decTest524
-rw-r--r--Lib/test/decimaltestdata/ddSameQuantum.decTest778
-rw-r--r--Lib/test/decimaltestdata/ddScaleB.decTest486
-rw-r--r--Lib/test/decimaltestdata/ddShift.decTest524
-rw-r--r--Lib/test/decimaltestdata/ddSubtract.decTest1258
-rw-r--r--Lib/test/decimaltestdata/ddToIntegral.decTest514
-rw-r--r--Lib/test/decimaltestdata/ddXor.decTest674
-rw-r--r--Lib/test/decimaltestdata/dqAbs.decTest252
-rw-r--r--Lib/test/decimaltestdata/dqAdd.decTest2430
-rw-r--r--Lib/test/decimaltestdata/dqAnd.decTest840
-rw-r--r--Lib/test/decimaltestdata/dqBase.decTest2162
-rw-r--r--Lib/test/decimaltestdata/dqCanonical.decTest744
-rw-r--r--Lib/test/decimaltestdata/dqClass.decTest154
-rw-r--r--Lib/test/decimaltestdata/dqCompare.decTest1506
-rw-r--r--Lib/test/decimaltestdata/dqCompareSig.decTest1294
-rw-r--r--Lib/test/decimaltestdata/dqCompareTotal.decTest1412
-rw-r--r--Lib/test/decimaltestdata/dqCompareTotalMag.decTest1412
-rw-r--r--Lib/test/decimaltestdata/dqCopy.decTest176
-rw-r--r--Lib/test/decimaltestdata/dqCopyAbs.decTest176
-rw-r--r--Lib/test/decimaltestdata/dqCopyNegate.decTest176
-rw-r--r--Lib/test/decimaltestdata/dqCopySign.decTest350
-rw-r--r--Lib/test/decimaltestdata/dqDivide.decTest1616
-rw-r--r--Lib/test/decimaltestdata/dqDivideInt.decTest906
-rw-r--r--Lib/test/decimaltestdata/dqEncode.decTest954
-rw-r--r--Lib/test/decimaltestdata/dqFMA.decTest3572
-rw-r--r--Lib/test/decimaltestdata/dqInvert.decTest490
-rw-r--r--Lib/test/decimaltestdata/dqLogB.decTest320
-rw-r--r--Lib/test/decimaltestdata/dqMax.decTest644
-rw-r--r--Lib/test/decimaltestdata/dqMaxMag.decTest608
-rw-r--r--Lib/test/decimaltestdata/dqMin.decTest618
-rw-r--r--Lib/test/decimaltestdata/dqMinMag.decTest586
-rw-r--r--Lib/test/decimaltestdata/dqMinus.decTest176
-rw-r--r--Lib/test/decimaltestdata/dqMultiply.decTest1178
-rw-r--r--Lib/test/decimaltestdata/dqNextMinus.decTest252
-rw-r--r--Lib/test/decimaltestdata/dqNextPlus.decTest248
-rw-r--r--Lib/test/decimaltestdata/dqNextToward.decTest750
-rw-r--r--Lib/test/decimaltestdata/dqOr.decTest802
-rw-r--r--Lib/test/decimaltestdata/dqPlus.decTest176
-rw-r--r--Lib/test/decimaltestdata/dqQuantize.decTest1672
-rw-r--r--Lib/test/decimaltestdata/dqReduce.decTest366
-rw-r--r--Lib/test/decimaltestdata/dqRemainder.decTest1194
-rw-r--r--Lib/test/decimaltestdata/dqRemainderNear.decTest1262
-rw-r--r--Lib/test/decimaltestdata/dqRotate.decTest596
-rw-r--r--Lib/test/decimaltestdata/dqSameQuantum.decTest778
-rw-r--r--Lib/test/decimaltestdata/dqScaleB.decTest520
-rw-r--r--Lib/test/decimaltestdata/dqShift.decTest596
-rw-r--r--Lib/test/decimaltestdata/dqSubtract.decTest1270
-rw-r--r--Lib/test/decimaltestdata/dqToIntegral.decTest514
-rw-r--r--Lib/test/decimaltestdata/dqXor.decTest820
-rw-r--r--Lib/test/decimaltestdata/dsBase.decTest2124
-rw-r--r--Lib/test/decimaltestdata/dsEncode.decTest744
-rw-r--r--Lib/test/decimaltestdata/exp.decTest1348
-rw-r--r--Lib/test/decimaltestdata/fma.decTest6852
-rw-r--r--Lib/test/decimaltestdata/invert.decTest352
-rw-r--r--Lib/test/decimaltestdata/ln.decTest1222
-rw-r--r--Lib/test/decimaltestdata/log10.decTest1102
-rw-r--r--Lib/test/decimaltestdata/logb.decTest376
-rw-r--r--Lib/test/decimaltestdata/maxmag.decTest808
-rw-r--r--Lib/test/decimaltestdata/minmag.decTest780
-rw-r--r--Lib/test/decimaltestdata/nextminus.decTest296
-rw-r--r--Lib/test/decimaltestdata/nextplus.decTest300
-rw-r--r--Lib/test/decimaltestdata/nexttoward.decTest852
-rw-r--r--Lib/test/decimaltestdata/or.decTest668
-rw-r--r--Lib/test/decimaltestdata/powersqrt.decTest5940
-rw-r--r--Lib/test/decimaltestdata/rotate.decTest494
-rw-r--r--Lib/test/decimaltestdata/scaleb.decTest418
-rw-r--r--Lib/test/decimaltestdata/shift.decTest500
-rw-r--r--Lib/test/decimaltestdata/tointegralx.decTest510
-rw-r--r--Lib/test/decimaltestdata/xor.decTest670
-rw-r--r--Lib/test/doctest_aliases.py2
-rw-r--r--Lib/test/encoded_modules/__init__.py23
-rw-r--r--Lib/test/encoded_modules/module_iso_8859_1.py5
-rw-r--r--Lib/test/encoded_modules/module_koi8_r.py3
-rw-r--r--Lib/test/exception_hierarchy.txt1
-rw-r--r--Lib/test/fork_wait.py6
-rw-r--r--Lib/test/formatfloat_testcases.txt69
-rw-r--r--Lib/test/gdb_sample.py12
-rw-r--r--Lib/test/ieee754.txt6
-rw-r--r--Lib/test/json_tests/__init__.py (renamed from Lib/json/tests/__init__.py)2
-rw-r--r--Lib/test/json_tests/test_decode.py (renamed from Lib/json/tests/test_decode.py)28
-rw-r--r--Lib/test/json_tests/test_default.py (renamed from Lib/json/tests/test_default.py)0
-rw-r--r--Lib/test/json_tests/test_dump.py (renamed from Lib/json/tests/test_dump.py)0
-rw-r--r--Lib/test/json_tests/test_encode_basestring_ascii.py (renamed from Lib/json/tests/test_encode_basestring_ascii.py)0
-rw-r--r--Lib/test/json_tests/test_fail.py (renamed from Lib/json/tests/test_fail.py)0
-rw-r--r--Lib/test/json_tests/test_float.py (renamed from Lib/json/tests/test_float.py)0
-rw-r--r--Lib/test/json_tests/test_indent.py (renamed from Lib/json/tests/test_indent.py)40
-rw-r--r--Lib/test/json_tests/test_pass1.py (renamed from Lib/json/tests/test_pass1.py)0
-rw-r--r--Lib/test/json_tests/test_pass2.py (renamed from Lib/json/tests/test_pass2.py)0
-rw-r--r--Lib/test/json_tests/test_pass3.py (renamed from Lib/json/tests/test_pass3.py)0
-rw-r--r--Lib/test/json_tests/test_recursion.py (renamed from Lib/json/tests/test_recursion.py)0
-rw-r--r--Lib/test/json_tests/test_scanstring.py (renamed from Lib/json/tests/test_scanstring.py)0
-rw-r--r--Lib/test/json_tests/test_separators.py (renamed from Lib/json/tests/test_separators.py)0
-rw-r--r--Lib/test/json_tests/test_speedups.py (renamed from Lib/json/tests/test_speedups.py)1
-rw-r--r--Lib/test/json_tests/test_unicode.py (renamed from Lib/json/tests/test_unicode.py)0
-rw-r--r--Lib/test/keycert.pem59
-rw-r--r--Lib/test/keycert2.pem31
-rw-r--r--Lib/test/lock_tests.py320
-rw-r--r--Lib/test/make_ssl_certs.py64
-rw-r--r--Lib/test/mapping_tests.py46
-rw-r--r--Lib/test/math_testcases.txt519
-rw-r--r--Lib/test/mock_socket.py154
-rw-r--r--Lib/test/pickletester.py69
-rw-r--r--Lib/test/pstats.pckbin0 -> 66607 bytes-rwxr-xr-xLib/test/pystone.py4
-rwxr-xr-xLib/test/re_tests.py2
-rwxr-xr-xLib/test/regrtest.py900
-rw-r--r--Lib/test/script_helper.py24
-rw-r--r--Lib/test/ssl_cert.pem25
-rw-r--r--Lib/test/ssl_key.pem25
-rw-r--r--Lib/test/ssl_servers.py189
-rw-r--r--Lib/test/string_tests.py37
-rw-r--r--Lib/test/subprocessdata/fd_status.py24
-rw-r--r--Lib/test/subprocessdata/input_reader.py7
-rw-r--r--Lib/test/subprocessdata/qcat.py7
-rw-r--r--Lib/test/subprocessdata/qgrep.py10
-rw-r--r--Lib/test/support.py591
-rw-r--r--Lib/test/test_SimpleHTTPServer.py41
-rw-r--r--Lib/test/test___all__.py8
-rw-r--r--Lib/test/test___future__.py2
-rw-r--r--Lib/test/test__locale.py2
-rw-r--r--Lib/test/test_abc.py68
-rw-r--r--Lib/test/test_abstract_numbers.py1
-rw-r--r--Lib/test/test_argparse.py4431
-rwxr-xr-xLib/test/test_array.py267
-rw-r--r--Lib/test/test_ascii_formatd.py64
-rw-r--r--Lib/test/test_ast.py30
-rw-r--r--Lib/test/test_asynchat.py174
-rw-r--r--Lib/test/test_asyncore.py602
-rw-r--r--Lib/test/test_atexit.py11
-rw-r--r--Lib/test/test_augassign.py4
-rw-r--r--Lib/test/test_base64.py18
-rw-r--r--Lib/test/test_bigmem.py44
-rw-r--r--[-rwxr-xr-x]Lib/test/test_binascii.py160
-rwxr-xr-xLib/test/test_binhex.py2
-rw-r--r--Lib/test/test_binop.py89
-rw-r--r--Lib/test/test_bool.py28
-rw-r--r--Lib/test/test_builtin.py396
-rw-r--r--Lib/test/test_bytes.py215
-rw-r--r--Lib/test/test_bz2.py229
-rw-r--r--Lib/test/test_calendar.py12
-rw-r--r--Lib/test/test_capi.py36
-rw-r--r--Lib/test/test_cfgparser.py1306
-rw-r--r--Lib/test/test_cgi.py136
-rw-r--r--[-rwxr-xr-x]Lib/test/test_cmath.py194
-rw-r--r--Lib/test/test_cmd.py25
-rw-r--r--Lib/test/test_cmd_line.py277
-rw-r--r--Lib/test/test_cmd_line_script.py256
-rw-r--r--Lib/test/test_code.py45
-rw-r--r--Lib/test/test_codecencodings_cn.py2
-rw-r--r--Lib/test/test_codecencodings_hk.py2
-rw-r--r--Lib/test/test_codecencodings_jp.py2
-rw-r--r--Lib/test/test_codecencodings_kr.py2
-rw-r--r--Lib/test/test_codecencodings_tw.py2
-rw-r--r--Lib/test/test_codecmaps_cn.py2
-rw-r--r--Lib/test/test_codecmaps_hk.py2
-rw-r--r--Lib/test/test_codecmaps_jp.py2
-rw-r--r--Lib/test/test_codecmaps_kr.py2
-rw-r--r--Lib/test/test_codecmaps_tw.py2
-rw-r--r--Lib/test/test_codecs.py86
-rw-r--r--Lib/test/test_codeop.py4
-rw-r--r--Lib/test/test_coding.py31
-rw-r--r--Lib/test/test_collections.py278
-rw-r--r--Lib/test/test_compile.py51
-rw-r--r--Lib/test/test_compileall.py285
-rw-r--r--Lib/test/test_complex.py170
-rw-r--r--Lib/test/test_concurrent_futures.py588
-rw-r--r--Lib/test/test_contains.py32
-rw-r--r--Lib/test/test_contextlib.py349
-rw-r--r--Lib/test/test_copy.py13
-rw-r--r--Lib/test/test_copyreg.py4
-rw-r--r--Lib/test/test_cprofile.py19
-rw-r--r--[-rwxr-xr-x]Lib/test/test_crypt.py0
-rw-r--r--Lib/test/test_csv.py20
-rw-r--r--Lib/test/test_datetime.py3415
-rw-r--r--Lib/test/test_dbm.py12
-rw-r--r--Lib/test/test_dbm_dumb.py12
-rwxr-xr-xLib/test/test_dbm_gnu.py8
-rwxr-xr-xLib/test/test_dbm_ndbm.py2
-rw-r--r--Lib/test/test_decimal.py690
-rw-r--r--Lib/test/test_defaultdict.py16
-rw-r--r--Lib/test/test_deque.py52
-rw-r--r--Lib/test/test_descr.py152
-rw-r--r--Lib/test/test_dict.py163
-rw-r--r--Lib/test/test_dictviews.py133
-rw-r--r--Lib/test/test_difflib.py84
-rw-r--r--Lib/test/test_dis.py221
-rw-r--r--Lib/test/test_distutils.py1
-rw-r--r--Lib/test/test_doctest.py182
-rw-r--r--Lib/test/test_doctest2.py5
-rw-r--r--Lib/test/test_docxmlrpc.py86
-rw-r--r--Lib/test/test_dummy_thread.py10
-rw-r--r--Lib/test/test_dynamic.py143
-rw-r--r--Lib/test/test_email.py2
-rw-r--r--Lib/test/test_enumerate.py14
-rw-r--r--Lib/test/test_eof.py2
-rw-r--r--Lib/test/test_epoll.py2
-rwxr-xr-xLib/test/test_errno.py9
-rw-r--r--Lib/test/test_exceptions.py66
-rw-r--r--Lib/test/test_extcall.py35
-rw-r--r--[-rwxr-xr-x]Lib/test/test_fcntl.py13
-rw-r--r--Lib/test/test_file.py13
-rw-r--r--Lib/test/test_filecmp.py2
-rw-r--r--Lib/test/test_fileinput.py24
-rw-r--r--Lib/test/test_fileio.py10
-rw-r--r--Lib/test/test_float.py443
-rw-r--r--Lib/test/test_fnmatch.py35
-rw-r--r--Lib/test/test_fork1.py49
-rw-r--r--Lib/test/test_format.py35
-rw-r--r--Lib/test/test_fractions.py55
-rw-r--r--Lib/test/test_frozen.py25
-rw-r--r--Lib/test/test_ftplib.py433
-rw-r--r--Lib/test/test_funcattrs.py6
-rw-r--r--Lib/test/test_functools.py328
-rw-r--r--Lib/test/test_future.py2
-rw-r--r--Lib/test/test_future5.py2
-rw-r--r--Lib/test/test_gc.py49
-rw-r--r--Lib/test/test_gdb.py681
-rw-r--r--Lib/test/test_generators.py52
-rw-r--r--Lib/test/test_genericpath.py320
-rw-r--r--Lib/test/test_getargs2.py213
-rw-r--r--Lib/test/test_getopt.py1
-rw-r--r--Lib/test/test_glob.py16
-rw-r--r--Lib/test/test_global.py18
-rw-r--r--Lib/test/test_grammar.py22
-rw-r--r--[-rwxr-xr-x]Lib/test/test_grp.py8
-rw-r--r--Lib/test/test_gzip.py312
-rw-r--r--Lib/test/test_hash.py4
-rw-r--r--Lib/test/test_hashlib.py110
-rw-r--r--Lib/test/test_heapq.py2
-rw-r--r--Lib/test/test_hmac.py10
-rw-r--r--Lib/test/test_html.py24
-rw-r--r--[-rwxr-xr-x]Lib/test/test_htmlparser.py49
-rw-r--r--Lib/test/test_http_cookiejar.py117
-rw-r--r--Lib/test/test_http_cookies.py110
-rw-r--r--Lib/test/test_httplib.py221
-rw-r--r--Lib/test/test_httpservers.py347
-rw-r--r--Lib/test/test_imaplib.py30
-rw-r--r--Lib/test/test_imp.py235
-rw-r--r--Lib/test/test_import.py244
-rw-r--r--Lib/test/test_importhooks.py10
-rw-r--r--Lib/test/test_importlib.py7
-rw-r--r--Lib/test/test_index.py92
-rw-r--r--Lib/test/test_inspect.py553
-rw-r--r--Lib/test/test_int.py29
-rw-r--r--Lib/test/test_io.py340
-rw-r--r--Lib/test/test_ioctl.py10
-rw-r--r--Lib/test/test_isinstance.py14
-rw-r--r--Lib/test/test_iter.py25
-rw-r--r--Lib/test/test_itertools.py38
-rw-r--r--Lib/test/test_json.py4
-rw-r--r--Lib/test/test_keywordonlyarg.py10
-rw-r--r--Lib/test/test_kqueue.py11
-rw-r--r--Lib/test/test_linecache.py14
-rw-r--r--Lib/test/test_logging.py1315
-rw-r--r--Lib/test/test_long.py765
-rw-r--r--Lib/test/test_macpath.py46
-rw-r--r--Lib/test/test_mailbox.py263
-rw-r--r--Lib/test/test_marshal.py12
-rw-r--r--Lib/test/test_math.py326
-rw-r--r--Lib/test/test_memoryio.py167
-rw-r--r--Lib/test/test_memoryview.py52
-rw-r--r--Lib/test/test_mimetypes.py27
-rw-r--r--Lib/test/test_minidom.py41
-rw-r--r--Lib/test/test_mmap.py253
-rw-r--r--Lib/test/test_multibytecodec.py5
-rw-r--r--Lib/test/test_multibytecodec_support.py5
-rw-r--r--Lib/test/test_multiprocessing.py128
-rw-r--r--Lib/test/test_nis.py1
-rw-r--r--Lib/test/test_nntplib.py1243
-rw-r--r--Lib/test/test_normalization.py19
-rw-r--r--Lib/test/test_ntpath.py26
-rw-r--r--Lib/test/test_numeric_tower.py206
-rw-r--r--Lib/test/test_opcodes.py2
-rw-r--r--Lib/test/test_operator.py9
-rw-r--r--Lib/test/test_optparse.py13
-rw-r--r--Lib/test/test_os.py539
-rw-r--r--Lib/test/test_ossaudiodev.py10
-rw-r--r--Lib/test/test_parser.py22
-rw-r--r--Lib/test/test_pdb.py383
-rw-r--r--Lib/test/test_peepholer.py126
-rw-r--r--Lib/test/test_pep263.py2
-rw-r--r--Lib/test/test_pep277.py178
-rw-r--r--Lib/test/test_pep292.py60
-rw-r--r--Lib/test/test_pep3120.py3
-rw-r--r--Lib/test/test_pep3131.py1
-rw-r--r--Lib/test/test_pep352.py6
-rw-r--r--Lib/test/test_pickle.py23
-rw-r--r--Lib/test/test_pickletools.py4
-rw-r--r--Lib/test/test_pipes.py15
-rw-r--r--Lib/test/test_pkg.py37
-rw-r--r--Lib/test/test_pkgimport.py34
-rw-r--r--Lib/test/test_platform.py61
-rw-r--r--Lib/test/test_plistlib.py2
-rw-r--r--Lib/test/test_popen.py5
-rw-r--r--Lib/test/test_poplib.py28
-rw-r--r--Lib/test/test_posix.py117
-rw-r--r--Lib/test/test_posixpath.py631
-rw-r--r--Lib/test/test_pprint.py38
-rw-r--r--Lib/test/test_print.py1
-rw-r--r--[-rwxr-xr-x]Lib/test/test_profile.py3
-rw-r--r--Lib/test/test_property.py41
-rw-r--r--Lib/test/test_pstats.py19
-rw-r--r--Lib/test/test_pwd.py31
-rw-r--r--Lib/test/test_pyclbr.py9
-rw-r--r--Lib/test/test_pydoc.py179
-rw-r--r--Lib/test/test_pyexpat.py218
-rw-r--r--Lib/test/test_queue.py38
-rw-r--r--Lib/test/test_quopri.py2
-rw-r--r--Lib/test/test_raise.py24
-rw-r--r--Lib/test/test_random.py38
-rw-r--r--Lib/test/test_range.py380
-rw-r--r--Lib/test/test_re.py26
-rw-r--r--Lib/test/test_readline.py47
-rw-r--r--Lib/test/test_reprlib.py34
-rw-r--r--Lib/test/test_resource.py4
-rw-r--r--Lib/test/test_richcmp.py15
-rw-r--r--Lib/test/test_runpy.py233
-rw-r--r--Lib/test/test_sax.py130
-rw-r--r--Lib/test/test_sched.py80
-rw-r--r--Lib/test/test_scope.py360
-rw-r--r--Lib/test/test_select.py2
-rw-r--r--Lib/test/test_set.py104
-rw-r--r--Lib/test/test_shelve.py13
-rw-r--r--Lib/test/test_shutil.py619
-rw-r--r--Lib/test/test_site.py183
-rw-r--r--Lib/test/test_smtpd.py289
-rw-r--r--Lib/test/test_smtplib.py214
-rw-r--r--Lib/test/test_smtpnet.py2
-rw-r--r--Lib/test/test_socket.py613
-rw-r--r--Lib/test/test_socketserver.py15
-rw-r--r--Lib/test/test_sqlite.py18
-rw-r--r--Lib/test/test_ssl.py912
-rw-r--r--[-rwxr-xr-x]Lib/test/test_strftime.py1
-rw-r--r--Lib/test/test_string.py24
-rw-r--r--Lib/test/test_strptime.py21
-rw-r--r--Lib/test/test_strtod.py20
-rw-r--r--Lib/test/test_struct.py491
-rw-r--r--Lib/test/test_structmembers.py23
-rw-r--r--Lib/test/test_structseq.py15
-rw-r--r--Lib/test/test_subprocess.py1441
-rw-r--r--Lib/test/test_sundry.py15
-rw-r--r--Lib/test/test_symtable.py2
-rw-r--r--Lib/test/test_syntax.py53
-rw-r--r--Lib/test/test_sys.py264
-rw-r--r--Lib/test/test_sysconfig.py298
-rw-r--r--Lib/test/test_syslog.py2
-rw-r--r--Lib/test/test_tarfile.py1142
-rw-r--r--Lib/test/test_tcl.py12
-rw-r--r--Lib/test/test_telnetlib.py13
-rw-r--r--Lib/test/test_tempfile.py189
-rw-r--r--Lib/test/test_thread.py74
-rw-r--r--Lib/test/test_threaded_import.py4
-rw-r--r--Lib/test/test_threadedtempfile.py5
-rw-r--r--Lib/test/test_threading.py127
-rw-r--r--Lib/test/test_threading_local.py22
-rw-r--r--Lib/test/test_threadsignals.py145
-rw-r--r--Lib/test/test_time.py244
-rw-r--r--Lib/test/test_timeit.py305
-rw-r--r--Lib/test/test_timeout.py142
-rw-r--r--Lib/test/test_tokenize.py41
-rw-r--r--Lib/test/test_trace.py43
-rw-r--r--Lib/test/test_traceback.py37
-rw-r--r--Lib/test/test_ttk_guionly.py1
-rw-r--r--Lib/test/test_ttk_textonly.py1
-rw-r--r--Lib/test/test_tuple.py4
-rw-r--r--Lib/test/test_types.py184
-rw-r--r--Lib/test/test_ucn.py6
-rw-r--r--Lib/test/test_unicode.py351
-rw-r--r--Lib/test/test_unicode_file.py61
-rw-r--r--Lib/test/test_unicodedata.py36
-rw-r--r--Lib/test/test_unittest.py3361
-rw-r--r--Lib/test/test_unpack.py6
-rw-r--r--Lib/test/test_urllib.py52
-rw-r--r--Lib/test/test_urllib2.py90
-rw-r--r--Lib/test/test_urllib2_localnet.py64
-rw-r--r--Lib/test/test_urllib2net.py45
-rw-r--r--Lib/test/test_urllibnet.py22
-rw-r--r--Lib/test/test_urlparse.py293
-rw-r--r--Lib/test/test_userdict.py12
-rwxr-xr-xLib/test/test_userstring.py2
-rw-r--r--Lib/test/test_warnings.py127
-rw-r--r--Lib/test/test_wave.py14
-rw-r--r--Lib/test/test_weakref.py53
-rw-r--r--Lib/test/test_weakset.py52
-rw-r--r--Lib/test/test_winreg.py227
-rw-r--r--Lib/test/test_winsound.py1
-rw-r--r--Lib/test/test_with.py46
-rw-r--r--[-rwxr-xr-x]Lib/test/test_wsgiref.py67
-rw-r--r--Lib/test/test_xml_etree.py1734
-rw-r--r--Lib/test/test_xml_etree_c.py247
-rw-r--r--Lib/test/test_xmlrpc.py373
-rw-r--r--Lib/test/test_xmlrpc_net.py4
-rw-r--r--Lib/test/test_zipfile.py1720
-rw-r--r--Lib/test/test_zipimport.py72
-rw-r--r--Lib/test/test_zipimport_support.py173
-rw-r--r--Lib/test/test_zlib.py30
-rw-r--r--Lib/test/testtar.tarbin272384 -> 427008 bytes-rw-r--r--Lib/test/threaded_import_hangers.py2
-rw-r--r--Lib/test/win_console_handler.py49
-rw-r--r--Lib/test/xmltestdata/simple-ns.xml7
-rw-r--r--Lib/test/xmltestdata/simple.xml6
-rw-r--r--Lib/test/xmltestdata/test.xml (renamed from Lib/test/test.xml)0
-rw-r--r--Lib/test/xmltestdata/test.xml.out (renamed from Lib/test/test.xml.out)0
-rw-r--r--Lib/test/zip_cp437_header.zipbin0 -> 270 bytes-rw-r--r--Lib/threading.py349
-rw-r--r--Lib/timeit.py12
-rw-r--r--Lib/tkinter/__init__.py139
-rw-r--r--Lib/tkinter/__main__.py7
-rw-r--r--Lib/tkinter/font.py2
-rw-r--r--Lib/tkinter/scrolledtext.py7
-rw-r--r--Lib/tkinter/test/test_tkinter/test_font.py33
-rw-r--r--Lib/tkinter/test/test_tkinter/test_loadtk.py3
-rw-r--r--Lib/tkinter/test/test_ttk/test_widgets.py4
-rw-r--r--Lib/tkinter/tix.py175
-rw-r--r--Lib/tkinter/ttk.py12
-rwxr-xr-xLib/token.py18
-rw-r--r--Lib/tokenize.py159
-rw-r--r--Lib/trace.py187
-rw-r--r--Lib/turtle.py52
-rw-r--r--Lib/turtledemo/__init__.py0
-rwxr-xr-xLib/turtledemo/__main__.py266
-rw-r--r--Lib/turtledemo/about_turtle.txt76
-rw-r--r--Lib/turtledemo/about_turtledemo.txt13
-rw-r--r--Lib/turtledemo/bytedesign.py162
-rw-r--r--Lib/turtledemo/chaos.py59
-rw-r--r--Lib/turtledemo/clock.py132
-rw-r--r--Lib/turtledemo/colormixer.py60
-rw-r--r--Lib/turtledemo/demohelp.txt70
-rw-r--r--Lib/turtledemo/forest.py109
-rw-r--r--Lib/turtledemo/fractalcurves.py138
-rw-r--r--Lib/turtledemo/lindenmayer.py119
-rw-r--r--Lib/turtledemo/minimal_hanoi.py76
-rw-r--r--Lib/turtledemo/nim.py226
-rw-r--r--Lib/turtledemo/paint.py50
-rw-r--r--Lib/turtledemo/peace.py65
-rw-r--r--Lib/turtledemo/penrose.py181
-rw-r--r--Lib/turtledemo/planet_and_moon.py113
-rw-r--r--Lib/turtledemo/round_dance.py86
-rw-r--r--Lib/turtledemo/tree.py63
-rw-r--r--Lib/turtledemo/turtle.cfg10
-rw-r--r--Lib/turtledemo/two_canvases.py52
-rw-r--r--Lib/turtledemo/wikipedia.py65
-rw-r--r--Lib/turtledemo/yinyang.py49
-rw-r--r--Lib/unittest.py1623
-rw-r--r--Lib/unittest/__init__.py69
-rw-r--r--Lib/unittest/__main__.py12
-rw-r--r--Lib/unittest/case.py1256
-rw-r--r--Lib/unittest/loader.py321
-rw-r--r--Lib/unittest/main.py274
-rw-r--r--Lib/unittest/result.py186
-rw-r--r--Lib/unittest/runner.py213
-rw-r--r--Lib/unittest/signals.py57
-rw-r--r--Lib/unittest/suite.py285
-rw-r--r--Lib/unittest/test/__init__.py21
-rw-r--r--Lib/unittest/test/_test_warnings.py74
-rw-r--r--Lib/unittest/test/dummy.py1
-rw-r--r--Lib/unittest/test/support.py118
-rw-r--r--Lib/unittest/test/test_assertions.py286
-rw-r--r--Lib/unittest/test/test_break.py252
-rw-r--r--Lib/unittest/test/test_case.py1236
-rw-r--r--Lib/unittest/test/test_discovery.py395
-rw-r--r--Lib/unittest/test/test_functiontestcase.py144
-rw-r--r--Lib/unittest/test/test_loader.py1292
-rw-r--r--Lib/unittest/test/test_program.py358
-rw-r--r--Lib/unittest/test/test_result.py501
-rw-r--r--Lib/unittest/test/test_runner.py318
-rw-r--r--Lib/unittest/test/test_setups.py507
-rw-r--r--Lib/unittest/test/test_skipping.py134
-rw-r--r--Lib/unittest/test/test_suite.py368
-rw-r--r--Lib/unittest/util.py140
-rw-r--r--Lib/urllib/parse.py290
-rw-r--r--Lib/urllib/request.py71
-rwxr-xr-xLib/uu.py2
-rw-r--r--Lib/warnings.py19
-rw-r--r--Lib/wave.py8
-rw-r--r--Lib/weakref.py6
-rw-r--r--Lib/webbrowser.py50
-rw-r--r--Lib/wsgiref/handlers.py203
-rw-r--r--Lib/wsgiref/headers.py62
-rw-r--r--Lib/wsgiref/simple_server.py59
-rw-r--r--Lib/wsgiref/util.py40
-rw-r--r--Lib/wsgiref/validate.py42
-rw-r--r--Lib/xml/dom/minidom.py12
-rw-r--r--Lib/xml/etree/ElementInclude.py8
-rw-r--r--Lib/xml/etree/ElementPath.py351
-rw-r--r--Lib/xml/etree/ElementTree.py1238
-rw-r--r--Lib/xml/etree/__init__.py6
-rw-r--r--Lib/xml/parsers/expat.py6
-rw-r--r--Lib/xml/sax/expatreader.py3
-rw-r--r--Lib/xml/sax/saxutils.py42
-rw-r--r--Lib/xmlrpc/client.py290
-rw-r--r--Lib/xmlrpc/server.py114
-rw-r--r--Lib/zipfile.py514
799 files changed, 129214 insertions, 78809 deletions
diff --git a/Lib/_dummy_thread.py b/Lib/_dummy_thread.py
index 7aa6579..ed50520 100644
--- a/Lib/_dummy_thread.py
+++ b/Lib/_dummy_thread.py
@@ -16,7 +16,13 @@ Suggested usage is::
__all__ = ['error', 'start_new_thread', 'exit', 'get_ident', 'allocate_lock',
'interrupt_main', 'LockType']
-import traceback as _traceback
+# A dummy value
+TIMEOUT_MAX = 2**31
+
+# NOTE: this module can be imported early in the extension building process,
+# and so top level imports of other modules should be avoided. Instead, all
+# imports are done when needed on a function-by-function basis. Since threads
+# are disabled, the import lock should not be an issue anyway (??).
class error(Exception):
"""Dummy implementation of _thread.error."""
@@ -48,7 +54,8 @@ def start_new_thread(function, args, kwargs={}):
except SystemExit:
pass
except:
- _traceback.print_exc()
+ import traceback
+ traceback.print_exc()
_main = True
global _interrupt
if _interrupt:
@@ -92,7 +99,7 @@ class LockType(object):
def __init__(self):
self.locked_status = False
- def acquire(self, waitflag=None):
+ def acquire(self, waitflag=None, timeout=-1):
"""Dummy implementation of acquire().
For blocking calls, self.locked_status is automatically set to
@@ -111,6 +118,9 @@ class LockType(object):
self.locked_status = True
return True
else:
+ if timeout > 0:
+ import time
+ time.sleep(timeout)
return False
__enter__ = acquire
diff --git a/Lib/_markupbase.py b/Lib/_markupbase.py
index adf6ba2..98b9037 100644
--- a/Lib/_markupbase.py
+++ b/Lib/_markupbase.py
@@ -122,7 +122,7 @@ class ParserBase:
# this could be handled in a separate doctype parser
if decltype == "doctype":
j = self._parse_doctype_subset(j + 1, i)
- elif decltype in ("attlist", "linktype", "link", "element"):
+ elif decltype in {"attlist", "linktype", "link", "element"}:
# must tolerate []'d groups in a content model in an element declaration
# also in data attribute specifications of attlist declaration
# also link type declaration subsets in linktype declarations
@@ -145,10 +145,10 @@ class ParserBase:
sectName, j = self._scan_name( i+3, i )
if j < 0:
return j
- if sectName in ("temp", "cdata", "ignore", "include", "rcdata"):
+ if sectName in {"temp", "cdata", "ignore", "include", "rcdata"}:
# look for standard ]]> ending
match= _markedsectionclose.search(rawdata, i+3)
- elif sectName in ("if", "else", "endif"):
+ elif sectName in {"if", "else", "endif"}:
# look for MS Office ]> ending
match= _msmarkedsectionclose.search(rawdata, i+3)
else:
@@ -203,7 +203,7 @@ class ParserBase:
name, j = self._scan_name(j + 2, declstartpos)
if j == -1:
return -1
- if name not in ("attlist", "element", "entity", "notation"):
+ if name not in {"attlist", "element", "entity", "notation"}:
self.updatepos(declstartpos, j + 2)
self.error(
"unknown declaration %r in internal subset" % name)
diff --git a/Lib/_pyio.py b/Lib/_pyio.py
index fa00eb4..35dea41 100644
--- a/Lib/_pyio.py
+++ b/Lib/_pyio.py
@@ -13,8 +13,8 @@ except ImportError:
from _dummy_thread import allocate_lock as Lock
import io
-from io import __all__
-from io import SEEK_SET, SEEK_CUR, SEEK_END
+from io import (__all__, SEEK_SET, SEEK_CUR, SEEK_END)
+from errno import EINTR
# open() uses st_blksize whenever we can
DEFAULT_BUFFER_SIZE = 8 * 1024 # bytes
@@ -35,9 +35,8 @@ class BlockingIOError(IOError):
self.characters_written = characters_written
-def open(file, mode = "r", buffering = None,
- encoding = None, errors = None,
- newline = None, closefd = True) -> "IOBase":
+def open(file, mode="r", buffering=-1, encoding=None, errors=None,
+ newline=None, closefd=True):
r"""Open file and return a stream. Raise IOError upon failure.
@@ -97,7 +96,7 @@ def open(file, mode = "r", buffering = None,
use line buffering. Other text files use the policy described above
for binary files.
- encoding is the name of the encoding used to decode or encode the
+ encoding is the str name of the encoding used to decode or encode the
file. This should only be used in text mode. The default encoding is
platform dependent, but any encoding supported by Python can be
passed. See the codecs module for the list of supported encodings.
@@ -110,9 +109,9 @@ def open(file, mode = "r", buffering = None,
See the documentation for codecs.register for a list of the permitted
encoding error strings.
- newline controls how universal newlines works (it only applies to text
- mode). It can be None, '', '\n', '\r', and '\r\n'. It works as
- follows:
+ newline is a string controlling how universal newlines works (it only
+ applies to text mode). It can be None, '', '\n', '\r', and '\r\n'. It works
+ as follows:
* On input, if newline is None, universal newlines mode is
enabled. Lines in the input can end in '\n', '\r', or '\r\n', and
@@ -128,9 +127,9 @@ def open(file, mode = "r", buffering = None,
other legal values, any '\n' characters written are translated to
the given string.
- If closefd is False, the underlying file descriptor will be kept open
- when the file is closed. This does not work when a file name is given
- and must be True in that case.
+ closedfd is a bool. If closefd is False, the underlying file descriptor will
+ be kept open when the file is closed. This does not work when a file name is
+ given and must be True in that case.
open() returns a file object whose type depends on the mode, and
through which the standard file operations such as reading and writing
@@ -150,7 +149,7 @@ def open(file, mode = "r", buffering = None,
raise TypeError("invalid file: %r" % file)
if not isinstance(mode, str):
raise TypeError("invalid mode: %r" % mode)
- if buffering is not None and not isinstance(buffering, int):
+ if not isinstance(buffering, int):
raise TypeError("invalid buffering: %r" % buffering)
if encoding is not None and not isinstance(encoding, str):
raise TypeError("invalid encoding: %r" % encoding)
@@ -187,8 +186,6 @@ def open(file, mode = "r", buffering = None,
(appending and "a" or "") +
(updating and "+" or ""),
closefd)
- if buffering is None:
- buffering = -1
line_buffering = False
if buffering == 1 or buffering < 0 and raw.isatty():
buffering = -1
@@ -228,7 +225,7 @@ class DocDescriptor:
"""
def __get__(self, obj, typ):
return (
- "open(file, mode='r', buffering=None, encoding=None, "
+ "open(file, mode='r', buffering=-1, encoding=None, "
"errors=None, newline=None, closefd=True)\n\n" +
open.__doc__)
@@ -246,8 +243,13 @@ class OpenWrapper:
return open(*args, **kwargs)
-class UnsupportedOperation(ValueError, IOError):
- pass
+# In normal operation, both `UnsupportedOperation`s should be bound to the
+# same object.
+try:
+ UnsupportedOperation = io.UnsupportedOperation
+except AttributeError:
+ class UnsupportedOperation(ValueError, IOError):
+ pass
class IOBase(metaclass=abc.ABCMeta):
@@ -262,7 +264,8 @@ class IOBase(metaclass=abc.ABCMeta):
Even though IOBase does not declare read, readinto, or write because
their signatures will vary, implementations and clients should
consider those methods part of the interface. Also, implementations
- may raise a IOError when operations they do not support are called.
+ may raise UnsupportedOperation when operations they do not support are
+ called.
The basic type used for binary data read from or written to a file is
bytes. bytearrays are accepted too, and in some cases (such as
@@ -285,32 +288,32 @@ class IOBase(metaclass=abc.ABCMeta):
### Internal ###
def _unsupported(self, name):
- """Internal: raise an exception for unsupported operations."""
+ """Internal: raise an IOError exception for unsupported operations."""
raise UnsupportedOperation("%s.%s() not supported" %
(self.__class__.__name__, name))
### Positioning ###
- def seek(self, pos, whence = 0):
+ def seek(self, pos, whence=0):
"""Change stream position.
Change the stream position to byte offset offset. offset is
interpreted relative to the position indicated by whence. Values
- for whence are:
+ for whence are ints:
* 0 -- start of stream (the default); offset should be zero or positive
* 1 -- current stream position; offset may be negative
* 2 -- end of stream; offset is usually negative
- Return the new absolute position.
+ Return an int indicating the new absolute position.
"""
self._unsupported("seek")
def tell(self):
- """Return current stream position."""
+ """Return an int indicating the current stream position."""
return self.seek(0, 1)
- def truncate(self, pos = None):
+ def truncate(self, pos=None):
"""Truncate file to size bytes.
Size defaults to the current IO position as reported by tell(). Return
@@ -354,49 +357,47 @@ class IOBase(metaclass=abc.ABCMeta):
### Inquiries ###
def seekable(self):
- """Return whether object supports random access.
+ """Return a bool indicating whether object supports random access.
- If False, seek(), tell() and truncate() will raise IOError.
+ If False, seek(), tell() and truncate() will raise UnsupportedOperation.
This method may need to do a test seek().
"""
return False
def _checkSeekable(self, msg=None):
- """Internal: raise an IOError if file is not seekable
+ """Internal: raise UnsupportedOperation if file is not seekable
"""
if not self.seekable():
- raise IOError("File or stream is not seekable."
- if msg is None else msg)
-
+ raise UnsupportedOperation("File or stream is not seekable."
+ if msg is None else msg)
def readable(self):
+ """Return a bool indicating whether object was opened for reading.
- """Return whether object was opened for reading.
-
- If False, read() will raise IOError.
+ If False, read() will raise UnsupportedOperation.
"""
return False
def _checkReadable(self, msg=None):
- """Internal: raise an IOError if file is not readable
+ """Internal: raise UnsupportedOperation if file is not readable
"""
if not self.readable():
- raise IOError("File or stream is not readable."
- if msg is None else msg)
+ raise UnsupportedOperation("File or stream is not readable."
+ if msg is None else msg)
def writable(self):
- """Return whether object was opened for writing.
+ """Return a bool indicating whether object was opened for writing.
- If False, write() and truncate() will raise IOError.
+ If False, write() and truncate() will raise UnsupportedOperation.
"""
return False
def _checkWritable(self, msg=None):
- """Internal: raise an IOError if file is not writable
+ """Internal: raise UnsupportedOperation if file is not writable
"""
if not self.writable():
- raise IOError("File or stream is not writable."
- if msg is None else msg)
+ raise UnsupportedOperation("File or stream is not writable."
+ if msg is None else msg)
@property
def closed(self):
@@ -416,7 +417,7 @@ class IOBase(metaclass=abc.ABCMeta):
### Context manager ###
def __enter__(self): # That's a forward reference
- """Context management protocol. Returns self."""
+ """Context management protocol. Returns self (an instance of IOBase)."""
self._checkClosed()
return self
@@ -429,14 +430,14 @@ class IOBase(metaclass=abc.ABCMeta):
# XXX Should these be present even if unimplemented?
def fileno(self):
- """Returns underlying file descriptor if one exists.
+ """Returns underlying file descriptor (an int) if one exists.
An IOError is raised if the IO object does not use a file descriptor.
"""
self._unsupported("fileno")
def isatty(self):
- """Return whether this is an 'interactive' stream.
+ """Return a bool indicating whether this is an 'interactive' stream.
Return False if it can't be determined.
"""
@@ -445,10 +446,11 @@ class IOBase(metaclass=abc.ABCMeta):
### Readline[s] and writelines ###
- def readline(self, limit = -1):
- r"""Read and return a line from the stream.
+ def readline(self, limit=-1):
+ r"""Read and return a line of bytes from the stream.
If limit is specified, at most limit bytes will be read.
+ Limit should be an int.
The line terminator is always b'\n' for binary files; for text
files, the newlines argument to open can be used to select the line
@@ -531,8 +533,8 @@ class RawIOBase(IOBase):
# primitive operation, but that would lead to nasty recursion in case
# a subclass doesn't implement either.)
- def read(self, n = -1):
- """Read and return up to n bytes.
+ def read(self, n=-1):
+ """Read and return up to n bytes, where n is an int.
Returns an empty bytes object on EOF, or None if the object is
set not to block and has no data to read.
@@ -559,10 +561,10 @@ class RawIOBase(IOBase):
return bytes(res)
def readinto(self, b):
- """Read up to len(b) bytes into b.
+ """Read up to len(b) bytes into bytearray b.
- Returns number of bytes read (0 for EOF), or None if the object
- is set not to block and has no data to read.
+ Returns an int representing the number of bytes read (0 for EOF), or
+ None if the object is set not to block and has no data to read.
"""
self._unsupported("readinto")
@@ -595,8 +597,8 @@ class BufferedIOBase(IOBase):
implementation, but wrap one.
"""
- def read(self, n = None):
- """Read and return up to n bytes.
+ def read(self, n=None):
+ """Read and return up to n bytes, where n is an int.
If the argument is omitted, None, or negative, reads and
returns all data until EOF.
@@ -615,17 +617,19 @@ class BufferedIOBase(IOBase):
"""
self._unsupported("read")
- def read1(self, n = None):
- """Read up to n bytes with at most one read() system call."""
+ def read1(self, n=None):
+ """Read up to n bytes with at most one read() system call,
+ where n is an int.
+ """
self._unsupported("read1")
def readinto(self, b):
- """Read up to len(b) bytes into b.
+ """Read up to len(b) bytes into bytearray b.
Like read(), this may issue multiple reads to the underlying raw
stream, unless the latter is 'interactive'.
- Returns the number of bytes read (0 for EOF).
+ Returns an int representing the number of bytes read (0 for EOF).
Raises BlockingIOError if the underlying raw stream has no
data at the moment.
@@ -643,7 +647,7 @@ class BufferedIOBase(IOBase):
return n
def write(self, b):
- """Write the given buffer to the IO stream.
+ """Write the given bytes buffer to the IO stream.
Return the number of bytes written, which is never less than
len(b).
@@ -750,6 +754,10 @@ class _BufferedIOMixin(BufferedIOBase):
def mode(self):
return self.raw.mode
+ def __getstate__(self):
+ raise TypeError("can not serialize a '{0}' object"
+ .format(self.__class__.__name__))
+
def __repr__(self):
clsname = self.__class__.__name__
try:
@@ -779,6 +787,11 @@ class BytesIO(BufferedIOBase):
self._buffer = buf
self._pos = 0
+ def __getstate__(self):
+ if self.closed:
+ raise ValueError("__getstate__ on closed file")
+ return self.__dict__.copy()
+
def getvalue(self):
"""Return the bytes value (contents) of the buffer
"""
@@ -786,6 +799,11 @@ class BytesIO(BufferedIOBase):
raise ValueError("getvalue on closed file")
return bytes(self._buffer)
+ def getbuffer(self):
+ """Return a readable and writable view of the buffer.
+ """
+ return memoryview(self._buffer)
+
def read(self, n=None):
if self.closed:
raise ValueError("read from closed file")
@@ -827,7 +845,7 @@ class BytesIO(BufferedIOBase):
if self.closed:
raise ValueError("seek on closed file")
try:
- pos = pos.__index__()
+ pos.__index__
except AttributeError as err:
raise TypeError("an integer is required") from err
if whence == 0:
@@ -852,8 +870,13 @@ class BytesIO(BufferedIOBase):
raise ValueError("truncate on closed file")
if pos is None:
pos = self._pos
- elif pos < 0:
- raise ValueError("negative truncate position %r" % (pos,))
+ else:
+ try:
+ pos.__index__
+ except AttributeError as err:
+ raise TypeError("an integer is required") from err
+ if pos < 0:
+ raise ValueError("negative truncate position %r" % (pos,))
del self._buffer[pos:]
return pos
@@ -921,7 +944,12 @@ class BufferedReader(_BufferedIOMixin):
current_size = 0
while True:
# Read until EOF or until read() would block.
- chunk = self.raw.read()
+ try:
+ chunk = self.raw.read()
+ except IOError as e:
+ if e.errno != EINTR:
+ raise
+ continue
if chunk in empty_values:
nodata_val = chunk
break
@@ -940,7 +968,12 @@ class BufferedReader(_BufferedIOMixin):
chunks = [buf[pos:]]
wanted = max(self.buffer_size, n)
while avail < n:
- chunk = self.raw.read(wanted)
+ try:
+ chunk = self.raw.read(wanted)
+ except IOError as e:
+ if e.errno != EINTR:
+ raise
+ continue
if chunk in empty_values:
nodata_val = chunk
break
@@ -969,7 +1002,14 @@ class BufferedReader(_BufferedIOMixin):
have = len(self._read_buf) - self._read_pos
if have < want or have <= 0:
to_read = self.buffer_size - have
- current = self.raw.read(to_read)
+ while True:
+ try:
+ current = self.raw.read(to_read)
+ except IOError as e:
+ if e.errno != EINTR:
+ raise
+ continue
+ break
if current:
self._read_buf = self._read_buf[self._read_pos:] + current
self._read_pos = 0
@@ -1076,7 +1116,12 @@ class BufferedWriter(_BufferedIOMixin):
written = 0
try:
while self._write_buf:
- n = self.raw.write(self._write_buf)
+ try:
+ n = self.raw.write(self._write_buf)
+ except IOError as e:
+ if e.errno != EINTR:
+ raise
+ continue
if n > len(self._write_buf) or n < 0:
raise IOError("write() returned incorrect number of bytes")
del self._write_buf[:n]
@@ -1252,20 +1297,22 @@ class TextIOBase(IOBase):
are immutable. There is no public constructor.
"""
- def read(self, n = -1):
- """Read at most n characters from stream.
+ def read(self, n=-1):
+ """Read at most n characters from stream, where n is an int.
Read from underlying buffer until we have n characters or we hit EOF.
If n is negative or omitted, read until EOF.
+
+ Returns a string.
"""
self._unsupported("read")
def write(self, s):
- """Write string s to stream."""
+ """Write string s to stream and returning an int."""
self._unsupported("write")
- def truncate(self, pos = None):
- """Truncate size to pos."""
+ def truncate(self, pos=None):
+ """Truncate size to pos, where pos is an int."""
self._unsupported("truncate")
def readline(self):
@@ -1484,13 +1531,20 @@ class TextIOWrapper(TextIOBase):
# - "chars_..." for integer variables that count decoded characters
def __repr__(self):
+ result = "<_pyio.TextIOWrapper"
try:
name = self.name
except AttributeError:
- return "<_pyio.TextIOWrapper encoding={0!r}>".format(self.encoding)
+ pass
else:
- return "<_pyio.TextIOWrapper name={0!r} encoding={1!r}>".format(
- name, self.encoding)
+ result += " name={0!r}".format(name)
+ try:
+ mode = self.mode
+ except AttributeError:
+ pass
+ else:
+ result += " mode={0!r}".format(mode)
+ return result + " encoding={0!r}>".format(self.encoding)
@property
def encoding(self):
@@ -1540,7 +1594,8 @@ class TextIOWrapper(TextIOBase):
def isatty(self):
return self.buffer.isatty()
- def write(self, s: str):
+ def write(self, s):
+ 'Write data, where s is a str'
if self.closed:
raise ValueError("write to closed file")
if not isinstance(s, str):
@@ -1651,7 +1706,7 @@ class TextIOWrapper(TextIOBase):
def tell(self):
if not self._seekable:
- raise IOError("underlying stream is not seekable")
+ raise UnsupportedOperation("underlying stream is not seekable")
if not self._telling:
raise IOError("telling position disabled by next() call")
self.flush()
@@ -1730,17 +1785,17 @@ class TextIOWrapper(TextIOBase):
if self.closed:
raise ValueError("tell on closed file")
if not self._seekable:
- raise IOError("underlying stream is not seekable")
+ raise UnsupportedOperation("underlying stream is not seekable")
if whence == 1: # seek relative to current position
if cookie != 0:
- raise IOError("can't do nonzero cur-relative seeks")
+ raise UnsupportedOperation("can't do nonzero cur-relative seeks")
# Seeking to the current position should attempt to
# sync the underlying buffer with the current position.
whence = 0
cookie = self.tell()
if whence == 2: # seek relative to end of file
if cookie != 0:
- raise IOError("can't do nonzero end-relative seeks")
+ raise UnsupportedOperation("can't do nonzero end-relative seeks")
self.flush()
position = self.buffer.seek(0, 2)
self._set_decoded_chars('')
@@ -1803,6 +1858,10 @@ class TextIOWrapper(TextIOBase):
if n is None:
n = -1
decoder = self._decoder or self._get_decoder()
+ try:
+ n.__index__
+ except AttributeError as err:
+ raise TypeError("an integer is required") from err
if n < 0:
# Read everything.
result = (self._get_decoded_chars() +
diff --git a/Lib/_strptime.py b/Lib/_strptime.py
index ee30b42..0ea4c43 100644
--- a/Lib/_strptime.py
+++ b/Lib/_strptime.py
@@ -16,7 +16,9 @@ import calendar
from re import compile as re_compile
from re import IGNORECASE, ASCII
from re import escape as re_escape
-from datetime import date as datetime_date
+from datetime import (date as datetime_date,
+ timedelta as datetime_timedelta,
+ timezone as datetime_timezone)
try:
from _thread import allocate_lock as _thread_allocate_lock
except:
@@ -204,6 +206,7 @@ class TimeRE(dict):
#XXX: Does 'Y' need to worry about having less or more than
# 4 digits?
'Y': r"(?P<Y>\d\d\d\d)",
+ 'z': r"(?P<z>[+-]\d\d[0-5]\d)",
'A': self.__seqToRE(self.locale_time.f_weekday, 'A'),
'a': self.__seqToRE(self.locale_time.a_weekday, 'a'),
'B': self.__seqToRE(self.locale_time.f_month[1:], 'B'),
@@ -293,7 +296,9 @@ def _calc_julian_from_U_or_W(year, week_of_year, day_of_week, week_starts_Mon):
def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
- """Return a time struct based on the input string and the format string."""
+ """Return a 2-tuple consisting of a time struct and an int containing
+ the number of microseconds based on the input string and the
+ format string."""
for index, arg in enumerate([data_string, format]):
if not isinstance(arg, str):
@@ -333,10 +338,12 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
if len(data_string) != found.end():
raise ValueError("unconverted data remains: %s" %
data_string[found.end():])
+
year = 1900
month = day = 1
hour = minute = second = fraction = 0
tz = -1
+ tzoffset = None
# Default to -1 to signify that values not known; not critical to have,
# though
week_of_year = -1
@@ -417,6 +424,11 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
else:
# W starts week on Monday.
week_of_year_start = 0
+ elif group_key == 'z':
+ z = found_dict['z']
+ tzoffset = int(z[1:3]) * 60 + int(z[3:5])
+ if z.startswith("-"):
+ tzoffset = -tzoffset
elif group_key == 'Z':
# Since -1 is default value only need to worry about setting tz if
# it can be something other than -1.
@@ -453,9 +465,35 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"):
day = datetime_result.day
if weekday == -1:
weekday = datetime_date(year, month, day).weekday()
- return (time.struct_time((year, month, day,
- hour, minute, second,
- weekday, julian, tz)), fraction)
+ # Add timezone info
+ tzname = found_dict.get("Z")
+ if tzoffset is not None:
+ gmtoff = tzoffset * 60
+ else:
+ gmtoff = None
+
+ return (year, month, day,
+ hour, minute, second,
+ weekday, julian, tz, gmtoff, tzname), fraction
def _strptime_time(data_string, format="%a %b %d %H:%M:%S %Y"):
- return _strptime(data_string, format)[0]
+ """Return a time struct based on the input string and the
+ format string."""
+ tt = _strptime(data_string, format)[0]
+ return time.struct_time(tt[:9])
+
+def _strptime_datetime(cls, data_string, format="%a %b %d %H:%M:%S %Y"):
+ """Return a class cls instance based on the input string and the
+ format string."""
+ tt, fraction = _strptime(data_string, format)
+ gmtoff, tzname = tt[-2:]
+ args = tt[:6] + (fraction,)
+ if gmtoff is not None:
+ tzdelta = datetime_timedelta(seconds=gmtoff)
+ if tzname:
+ tz = datetime_timezone(tzdelta, tzname)
+ else:
+ tz = datetime_timezone(tzdelta)
+ args += (tz,)
+
+ return cls(*args)
diff --git a/Lib/_threading_local.py b/Lib/_threading_local.py
index afdd6ea..4ec4828 100644
--- a/Lib/_threading_local.py
+++ b/Lib/_threading_local.py
@@ -132,6 +132,9 @@ affects what we see:
>>> del mydata
"""
+from weakref import ref
+from contextlib import contextmanager
+
__all__ = ["local"]
# We need to use objects from the threading module, but the threading
@@ -139,112 +142,105 @@ __all__ = ["local"]
# isn't compiled in to the `thread` module. This creates potential problems
# with circular imports. For that reason, we don't import `threading`
# until the bottom of this file (a hack sufficient to worm around the
-# potential problems). Note that almost all platforms do have support for
-# locals in the `thread` module, and there is no circular import problem
+# potential problems). Note that all platforms on CPython do have support
+# for locals in the `thread` module, and there is no circular import problem
# then, so problems introduced by fiddling the order of imports here won't
-# manifest on most boxes.
+# manifest.
+
+class _localimpl:
+ """A class managing thread-local dicts"""
+ __slots__ = 'key', 'dicts', 'localargs', 'locallock', '__weakref__'
+
+ def __init__(self):
+ # The key used in the Thread objects' attribute dicts.
+ # We keep it a string for speed but make it unlikely to clash with
+ # a "real" attribute.
+ self.key = '_threading_local._localimpl.' + str(id(self))
+ # { id(Thread) -> (ref(Thread), thread-local dict) }
+ self.dicts = {}
+
+ def get_dict(self):
+ """Return the dict for the current thread. Raises KeyError if none
+ defined."""
+ thread = current_thread()
+ return self.dicts[id(thread)][1]
+
+ def create_dict(self):
+ """Create a new dict for the current thread, and return it."""
+ localdict = {}
+ key = self.key
+ thread = current_thread()
+ idt = id(thread)
+ def local_deleted(_, key=key):
+ # When the localimpl is deleted, remove the thread attribute.
+ thread = wrthread()
+ if thread is not None:
+ del thread.__dict__[key]
+ def thread_deleted(_, idt=idt):
+ # When the thread is deleted, remove the local dict.
+ # Note that this is suboptimal if the thread object gets
+ # caught in a reference loop. We would like to be called
+ # as soon as the OS-level thread ends instead.
+ local = wrlocal()
+ if local is not None:
+ dct = local.dicts.pop(idt)
+ wrlocal = ref(self, local_deleted)
+ wrthread = ref(thread, thread_deleted)
+ thread.__dict__[key] = wrlocal
+ self.dicts[idt] = wrthread, localdict
+ return localdict
+
+
+@contextmanager
+def _patch(self):
+ impl = object.__getattribute__(self, '_local__impl')
+ try:
+ dct = impl.get_dict()
+ except KeyError:
+ dct = impl.create_dict()
+ args, kw = impl.localargs
+ self.__init__(*args, **kw)
+ with impl.locallock:
+ object.__setattr__(self, '__dict__', dct)
+ yield
-class _localbase(object):
- __slots__ = '_local__key', '_local__args', '_local__lock'
- def __new__(cls, *args, **kw):
- self = object.__new__(cls)
- key = '_local__key', 'thread.local.' + str(id(self))
- object.__setattr__(self, '_local__key', key)
- object.__setattr__(self, '_local__args', (args, kw))
- object.__setattr__(self, '_local__lock', RLock())
+class local:
+ __slots__ = '_local__impl', '__dict__'
- if args or kw and (cls.__init__ is object.__init__):
+ def __new__(cls, *args, **kw):
+ if (args or kw) and (cls.__init__ is object.__init__):
raise TypeError("Initialization arguments are not supported")
-
+ self = object.__new__(cls)
+ impl = _localimpl()
+ impl.localargs = (args, kw)
+ impl.locallock = RLock()
+ object.__setattr__(self, '_local__impl', impl)
# We need to create the thread dict in anticipation of
# __init__ being called, to make sure we don't call it
# again ourselves.
- dict = object.__getattribute__(self, '__dict__')
- current_thread().__dict__[key] = dict
-
+ impl.create_dict()
return self
-def _patch(self):
- key = object.__getattribute__(self, '_local__key')
- d = current_thread().__dict__.get(key)
- if d is None:
- d = {}
- current_thread().__dict__[key] = d
- object.__setattr__(self, '__dict__', d)
-
- # we have a new instance dict, so call out __init__ if we have
- # one
- cls = type(self)
- if cls.__init__ is not object.__init__:
- args, kw = object.__getattribute__(self, '_local__args')
- cls.__init__(self, *args, **kw)
- else:
- object.__setattr__(self, '__dict__', d)
-
-class local(_localbase):
-
def __getattribute__(self, name):
- lock = object.__getattribute__(self, '_local__lock')
- lock.acquire()
- try:
- _patch(self)
+ with _patch(self):
return object.__getattribute__(self, name)
- finally:
- lock.release()
def __setattr__(self, name, value):
if name == '__dict__':
raise AttributeError(
"%r object attribute '__dict__' is read-only"
% self.__class__.__name__)
- lock = object.__getattribute__(self, '_local__lock')
- lock.acquire()
- try:
- _patch(self)
+ with _patch(self):
return object.__setattr__(self, name, value)
- finally:
- lock.release()
def __delattr__(self, name):
if name == '__dict__':
raise AttributeError(
"%r object attribute '__dict__' is read-only"
% self.__class__.__name__)
- lock = object.__getattribute__(self, '_local__lock')
- lock.acquire()
- try:
- _patch(self)
+ with _patch(self):
return object.__delattr__(self, name)
- finally:
- lock.release()
-
- def __del__(self):
- import threading
-
- key = object.__getattribute__(self, '_local__key')
-
- try:
- # We use the non-locking API since we might already hold the lock
- # (__del__ can be called at any point by the cyclic GC).
- threads = threading._enumerate()
- except:
- # If enumerating the current threads fails, as it seems to do
- # during shutdown, we'll skip cleanup under the assumption
- # that there is nothing to clean up.
- return
-
- for thread in threads:
- try:
- __dict__ = thread.__dict__
- except AttributeError:
- # Thread is dying, rest in peace.
- continue
-
- if key in __dict__:
- try:
- del __dict__[key]
- except KeyError:
- pass # didn't have anything in this thread
+
from threading import current_thread, RLock
diff --git a/Lib/abc.py b/Lib/abc.py
index 0f98036..a6c2dc4 100644
--- a/Lib/abc.py
+++ b/Lib/abc.py
@@ -25,6 +25,46 @@ def abstractmethod(funcobj):
return funcobj
+class abstractclassmethod(classmethod):
+ """A decorator indicating abstract classmethods.
+
+ Similar to abstractmethod.
+
+ Usage:
+
+ class C(metaclass=ABCMeta):
+ @abstractclassmethod
+ def my_abstract_classmethod(cls, ...):
+ ...
+ """
+
+ __isabstractmethod__ = True
+
+ def __init__(self, callable):
+ callable.__isabstractmethod__ = True
+ super().__init__(callable)
+
+
+class abstractstaticmethod(staticmethod):
+ """A decorator indicating abstract staticmethods.
+
+ Similar to abstractmethod.
+
+ Usage:
+
+ class C(metaclass=ABCMeta):
+ @abstractstaticmethod
+ def my_abstract_staticmethod(...):
+ ...
+ """
+
+ __isabstractmethod__ = True
+
+ def __init__(self, callable):
+ callable.__isabstractmethod__ = True
+ super().__init__(callable)
+
+
class abstractproperty(property):
"""A decorator indicating abstract properties.
diff --git a/Lib/argparse.py b/Lib/argparse.py
new file mode 100644
index 0000000..de3cd11
--- /dev/null
+++ b/Lib/argparse.py
@@ -0,0 +1,2358 @@
+# Author: Steven J. Bethard <steven.bethard@gmail.com>.
+
+"""Command-line parsing library
+
+This module is an optparse-inspired command-line parsing library that:
+
+ - handles both optional and positional arguments
+ - produces highly informative usage messages
+ - supports parsers that dispatch to sub-parsers
+
+The following is a simple usage example that sums integers from the
+command-line and writes the result to a file::
+
+ parser = argparse.ArgumentParser(
+ description='sum the integers at the command line')
+ parser.add_argument(
+ 'integers', metavar='int', nargs='+', type=int,
+ help='an integer to be summed')
+ parser.add_argument(
+ '--log', default=sys.stdout, type=argparse.FileType('w'),
+ help='the file where the sum should be written')
+ args = parser.parse_args()
+ args.log.write('%s' % sum(args.integers))
+ args.log.close()
+
+The module contains the following public classes:
+
+ - ArgumentParser -- The main entry point for command-line parsing. As the
+ example above shows, the add_argument() method is used to populate
+ the parser with actions for optional and positional arguments. Then
+ the parse_args() method is invoked to convert the args at the
+ command-line into an object with attributes.
+
+ - ArgumentError -- The exception raised by ArgumentParser objects when
+ there are errors with the parser's actions. Errors raised while
+ parsing the command-line are caught by ArgumentParser and emitted
+ as command-line messages.
+
+ - FileType -- A factory for defining types of files to be created. As the
+ example above shows, instances of FileType are typically passed as
+ the type= argument of add_argument() calls.
+
+ - Action -- The base class for parser actions. Typically actions are
+ selected by passing strings like 'store_true' or 'append_const' to
+ the action= argument of add_argument(). However, for greater
+ customization of ArgumentParser actions, subclasses of Action may
+ be defined and passed as the action= argument.
+
+ - HelpFormatter, RawDescriptionHelpFormatter, RawTextHelpFormatter,
+ ArgumentDefaultsHelpFormatter -- Formatter classes which
+ may be passed as the formatter_class= argument to the
+ ArgumentParser constructor. HelpFormatter is the default,
+ RawDescriptionHelpFormatter and RawTextHelpFormatter tell the parser
+ not to change the formatting for help text, and
+ ArgumentDefaultsHelpFormatter adds information about argument defaults
+ to the help.
+
+All other classes in this module are considered implementation details.
+(Also note that HelpFormatter and RawDescriptionHelpFormatter are only
+considered public as object names -- the API of the formatter objects is
+still considered an implementation detail.)
+"""
+
+__version__ = '1.1'
+__all__ = [
+ 'ArgumentParser',
+ 'ArgumentError',
+ 'ArgumentTypeError',
+ 'FileType',
+ 'HelpFormatter',
+ 'ArgumentDefaultsHelpFormatter',
+ 'RawDescriptionHelpFormatter',
+ 'RawTextHelpFormatter',
+ 'Namespace',
+ 'Action',
+ 'ONE_OR_MORE',
+ 'OPTIONAL',
+ 'PARSER',
+ 'REMAINDER',
+ 'SUPPRESS',
+ 'ZERO_OR_MORE',
+]
+
+
+import copy as _copy
+import os as _os
+import re as _re
+import sys as _sys
+import textwrap as _textwrap
+
+from gettext import gettext as _, ngettext
+
+
+def _callable(obj):
+ return hasattr(obj, '__call__') or hasattr(obj, '__bases__')
+
+
+SUPPRESS = '==SUPPRESS=='
+
+OPTIONAL = '?'
+ZERO_OR_MORE = '*'
+ONE_OR_MORE = '+'
+PARSER = 'A...'
+REMAINDER = '...'
+_UNRECOGNIZED_ARGS_ATTR = '_unrecognized_args'
+
+# =============================
+# Utility functions and classes
+# =============================
+
+class _AttributeHolder(object):
+ """Abstract base class that provides __repr__.
+
+ The __repr__ method returns a string in the format::
+ ClassName(attr=name, attr=name, ...)
+ The attributes are determined either by a class-level attribute,
+ '_kwarg_names', or by inspecting the instance __dict__.
+ """
+
+ def __repr__(self):
+ type_name = type(self).__name__
+ arg_strings = []
+ for arg in self._get_args():
+ arg_strings.append(repr(arg))
+ for name, value in self._get_kwargs():
+ arg_strings.append('%s=%r' % (name, value))
+ return '%s(%s)' % (type_name, ', '.join(arg_strings))
+
+ def _get_kwargs(self):
+ return sorted(self.__dict__.items())
+
+ def _get_args(self):
+ return []
+
+
+def _ensure_value(namespace, name, value):
+ if getattr(namespace, name, None) is None:
+ setattr(namespace, name, value)
+ return getattr(namespace, name)
+
+
+# ===============
+# Formatting Help
+# ===============
+
+class HelpFormatter(object):
+ """Formatter for generating usage messages and argument help strings.
+
+ Only the name of this class is considered a public API. All the methods
+ provided by the class are considered an implementation detail.
+ """
+
+ def __init__(self,
+ prog,
+ indent_increment=2,
+ max_help_position=24,
+ width=None):
+
+ # default setting for width
+ if width is None:
+ try:
+ width = int(_os.environ['COLUMNS'])
+ except (KeyError, ValueError):
+ width = 80
+ width -= 2
+
+ self._prog = prog
+ self._indent_increment = indent_increment
+ self._max_help_position = max_help_position
+ self._width = width
+
+ self._current_indent = 0
+ self._level = 0
+ self._action_max_length = 0
+
+ self._root_section = self._Section(self, None)
+ self._current_section = self._root_section
+
+ self._whitespace_matcher = _re.compile(r'\s+')
+ self._long_break_matcher = _re.compile(r'\n\n\n+')
+
+ # ===============================
+ # Section and indentation methods
+ # ===============================
+ def _indent(self):
+ self._current_indent += self._indent_increment
+ self._level += 1
+
+ def _dedent(self):
+ self._current_indent -= self._indent_increment
+ assert self._current_indent >= 0, 'Indent decreased below 0.'
+ self._level -= 1
+
+ class _Section(object):
+
+ def __init__(self, formatter, parent, heading=None):
+ self.formatter = formatter
+ self.parent = parent
+ self.heading = heading
+ self.items = []
+
+ def format_help(self):
+ # format the indented section
+ if self.parent is not None:
+ self.formatter._indent()
+ join = self.formatter._join_parts
+ for func, args in self.items:
+ func(*args)
+ item_help = join([func(*args) for func, args in self.items])
+ if self.parent is not None:
+ self.formatter._dedent()
+
+ # return nothing if the section was empty
+ if not item_help:
+ return ''
+
+ # add the heading if the section was non-empty
+ if self.heading is not SUPPRESS and self.heading is not None:
+ current_indent = self.formatter._current_indent
+ heading = '%*s%s:\n' % (current_indent, '', self.heading)
+ else:
+ heading = ''
+
+ # join the section-initial newline, the heading and the help
+ return join(['\n', heading, item_help, '\n'])
+
+ def _add_item(self, func, args):
+ self._current_section.items.append((func, args))
+
+ # ========================
+ # Message building methods
+ # ========================
+ def start_section(self, heading):
+ self._indent()
+ section = self._Section(self, self._current_section, heading)
+ self._add_item(section.format_help, [])
+ self._current_section = section
+
+ def end_section(self):
+ self._current_section = self._current_section.parent
+ self._dedent()
+
+ def add_text(self, text):
+ if text is not SUPPRESS and text is not None:
+ self._add_item(self._format_text, [text])
+
+ def add_usage(self, usage, actions, groups, prefix=None):
+ if usage is not SUPPRESS:
+ args = usage, actions, groups, prefix
+ self._add_item(self._format_usage, args)
+
+ def add_argument(self, action):
+ if action.help is not SUPPRESS:
+
+ # find all invocations
+ get_invocation = self._format_action_invocation
+ invocations = [get_invocation(action)]
+ for subaction in self._iter_indented_subactions(action):
+ invocations.append(get_invocation(subaction))
+
+ # update the maximum item length
+ invocation_length = max([len(s) for s in invocations])
+ action_length = invocation_length + self._current_indent
+ self._action_max_length = max(self._action_max_length,
+ action_length)
+
+ # add the item to the list
+ self._add_item(self._format_action, [action])
+
+ def add_arguments(self, actions):
+ for action in actions:
+ self.add_argument(action)
+
+ # =======================
+ # Help-formatting methods
+ # =======================
+ def format_help(self):
+ help = self._root_section.format_help()
+ if help:
+ help = self._long_break_matcher.sub('\n\n', help)
+ help = help.strip('\n') + '\n'
+ return help
+
+ def _join_parts(self, part_strings):
+ return ''.join([part
+ for part in part_strings
+ if part and part is not SUPPRESS])
+
+ def _format_usage(self, usage, actions, groups, prefix):
+ if prefix is None:
+ prefix = _('usage: ')
+
+ # if usage is specified, use that
+ if usage is not None:
+ usage = usage % dict(prog=self._prog)
+
+ # if no optionals or positionals are available, usage is just prog
+ elif usage is None and not actions:
+ usage = '%(prog)s' % dict(prog=self._prog)
+
+ # if optionals and positionals are available, calculate usage
+ elif usage is None:
+ prog = '%(prog)s' % dict(prog=self._prog)
+
+ # split optionals from positionals
+ optionals = []
+ positionals = []
+ for action in actions:
+ if action.option_strings:
+ optionals.append(action)
+ else:
+ positionals.append(action)
+
+ # build full usage string
+ format = self._format_actions_usage
+ action_usage = format(optionals + positionals, groups)
+ usage = ' '.join([s for s in [prog, action_usage] if s])
+
+ # wrap the usage parts if it's too long
+ text_width = self._width - self._current_indent
+ if len(prefix) + len(usage) > text_width:
+
+ # break usage into wrappable parts
+ part_regexp = r'\(.*?\)+|\[.*?\]+|\S+'
+ opt_usage = format(optionals, groups)
+ pos_usage = format(positionals, groups)
+ opt_parts = _re.findall(part_regexp, opt_usage)
+ pos_parts = _re.findall(part_regexp, pos_usage)
+ assert ' '.join(opt_parts) == opt_usage
+ assert ' '.join(pos_parts) == pos_usage
+
+ # helper for wrapping lines
+ def get_lines(parts, indent, prefix=None):
+ lines = []
+ line = []
+ if prefix is not None:
+ line_len = len(prefix) - 1
+ else:
+ line_len = len(indent) - 1
+ for part in parts:
+ if line_len + 1 + len(part) > text_width:
+ lines.append(indent + ' '.join(line))
+ line = []
+ line_len = len(indent) - 1
+ line.append(part)
+ line_len += len(part) + 1
+ if line:
+ lines.append(indent + ' '.join(line))
+ if prefix is not None:
+ lines[0] = lines[0][len(indent):]
+ return lines
+
+ # if prog is short, follow it with optionals or positionals
+ if len(prefix) + len(prog) <= 0.75 * text_width:
+ indent = ' ' * (len(prefix) + len(prog) + 1)
+ if opt_parts:
+ lines = get_lines([prog] + opt_parts, indent, prefix)
+ lines.extend(get_lines(pos_parts, indent))
+ elif pos_parts:
+ lines = get_lines([prog] + pos_parts, indent, prefix)
+ else:
+ lines = [prog]
+
+ # if prog is long, put it on its own line
+ else:
+ indent = ' ' * len(prefix)
+ parts = opt_parts + pos_parts
+ lines = get_lines(parts, indent)
+ if len(lines) > 1:
+ lines = []
+ lines.extend(get_lines(opt_parts, indent))
+ lines.extend(get_lines(pos_parts, indent))
+ lines = [prog] + lines
+
+ # join lines into usage
+ usage = '\n'.join(lines)
+
+ # prefix with 'usage:'
+ return '%s%s\n\n' % (prefix, usage)
+
+ def _format_actions_usage(self, actions, groups):
+ # find group indices and identify actions in groups
+ group_actions = set()
+ inserts = {}
+ for group in groups:
+ try:
+ start = actions.index(group._group_actions[0])
+ except ValueError:
+ continue
+ else:
+ end = start + len(group._group_actions)
+ if actions[start:end] == group._group_actions:
+ for action in group._group_actions:
+ group_actions.add(action)
+ if not group.required:
+ if start in inserts:
+ inserts[start] += ' ['
+ else:
+ inserts[start] = '['
+ inserts[end] = ']'
+ else:
+ if start in inserts:
+ inserts[start] += ' ('
+ else:
+ inserts[start] = '('
+ inserts[end] = ')'
+ for i in range(start + 1, end):
+ inserts[i] = '|'
+
+ # collect all actions format strings
+ parts = []
+ for i, action in enumerate(actions):
+
+ # suppressed arguments are marked with None
+ # remove | separators for suppressed arguments
+ if action.help is SUPPRESS:
+ parts.append(None)
+ if inserts.get(i) == '|':
+ inserts.pop(i)
+ elif inserts.get(i + 1) == '|':
+ inserts.pop(i + 1)
+
+ # produce all arg strings
+ elif not action.option_strings:
+ part = self._format_args(action, action.dest)
+
+ # if it's in a group, strip the outer []
+ if action in group_actions:
+ if part[0] == '[' and part[-1] == ']':
+ part = part[1:-1]
+
+ # add the action string to the list
+ parts.append(part)
+
+ # produce the first way to invoke the option in brackets
+ else:
+ option_string = action.option_strings[0]
+
+ # if the Optional doesn't take a value, format is:
+ # -s or --long
+ if action.nargs == 0:
+ part = '%s' % option_string
+
+ # if the Optional takes a value, format is:
+ # -s ARGS or --long ARGS
+ else:
+ default = action.dest.upper()
+ args_string = self._format_args(action, default)
+ part = '%s %s' % (option_string, args_string)
+
+ # make it look optional if it's not required or in a group
+ if not action.required and action not in group_actions:
+ part = '[%s]' % part
+
+ # add the action string to the list
+ parts.append(part)
+
+ # insert things at the necessary indices
+ for i in sorted(inserts, reverse=True):
+ parts[i:i] = [inserts[i]]
+
+ # join all the action items with spaces
+ text = ' '.join([item for item in parts if item is not None])
+
+ # clean up separators for mutually exclusive groups
+ open = r'[\[(]'
+ close = r'[\])]'
+ text = _re.sub(r'(%s) ' % open, r'\1', text)
+ text = _re.sub(r' (%s)' % close, r'\1', text)
+ text = _re.sub(r'%s *%s' % (open, close), r'', text)
+ text = _re.sub(r'\(([^|]*)\)', r'\1', text)
+ text = text.strip()
+
+ # return the text
+ return text
+
+ def _format_text(self, text):
+ if '%(prog)' in text:
+ text = text % dict(prog=self._prog)
+ text_width = self._width - self._current_indent
+ indent = ' ' * self._current_indent
+ return self._fill_text(text, text_width, indent) + '\n\n'
+
+ def _format_action(self, action):
+ # determine the required width and the entry label
+ help_position = min(self._action_max_length + 2,
+ self._max_help_position)
+ help_width = self._width - help_position
+ action_width = help_position - self._current_indent - 2
+ action_header = self._format_action_invocation(action)
+
+ # ho nelp; start on same line and add a final newline
+ if not action.help:
+ tup = self._current_indent, '', action_header
+ action_header = '%*s%s\n' % tup
+
+ # short action name; start on the same line and pad two spaces
+ elif len(action_header) <= action_width:
+ tup = self._current_indent, '', action_width, action_header
+ action_header = '%*s%-*s ' % tup
+ indent_first = 0
+
+ # long action name; start on the next line
+ else:
+ tup = self._current_indent, '', action_header
+ action_header = '%*s%s\n' % tup
+ indent_first = help_position
+
+ # collect the pieces of the action help
+ parts = [action_header]
+
+ # if there was help for the action, add lines of help text
+ if action.help:
+ help_text = self._expand_help(action)
+ help_lines = self._split_lines(help_text, help_width)
+ parts.append('%*s%s\n' % (indent_first, '', help_lines[0]))
+ for line in help_lines[1:]:
+ parts.append('%*s%s\n' % (help_position, '', line))
+
+ # or add a newline if the description doesn't end with one
+ elif not action_header.endswith('\n'):
+ parts.append('\n')
+
+ # if there are any sub-actions, add their help as well
+ for subaction in self._iter_indented_subactions(action):
+ parts.append(self._format_action(subaction))
+
+ # return a single string
+ return self._join_parts(parts)
+
+ def _format_action_invocation(self, action):
+ if not action.option_strings:
+ metavar, = self._metavar_formatter(action, action.dest)(1)
+ return metavar
+
+ else:
+ parts = []
+
+ # if the Optional doesn't take a value, format is:
+ # -s, --long
+ if action.nargs == 0:
+ parts.extend(action.option_strings)
+
+ # if the Optional takes a value, format is:
+ # -s ARGS, --long ARGS
+ else:
+ default = action.dest.upper()
+ args_string = self._format_args(action, default)
+ for option_string in action.option_strings:
+ parts.append('%s %s' % (option_string, args_string))
+
+ return ', '.join(parts)
+
+ def _metavar_formatter(self, action, default_metavar):
+ if action.metavar is not None:
+ result = action.metavar
+ elif action.choices is not None:
+ choice_strs = [str(choice) for choice in action.choices]
+ result = '{%s}' % ','.join(choice_strs)
+ else:
+ result = default_metavar
+
+ def format(tuple_size):
+ if isinstance(result, tuple):
+ return result
+ else:
+ return (result, ) * tuple_size
+ return format
+
+ def _format_args(self, action, default_metavar):
+ get_metavar = self._metavar_formatter(action, default_metavar)
+ if action.nargs is None:
+ result = '%s' % get_metavar(1)
+ elif action.nargs == OPTIONAL:
+ result = '[%s]' % get_metavar(1)
+ elif action.nargs == ZERO_OR_MORE:
+ result = '[%s [%s ...]]' % get_metavar(2)
+ elif action.nargs == ONE_OR_MORE:
+ result = '%s [%s ...]' % get_metavar(2)
+ elif action.nargs == REMAINDER:
+ result = '...'
+ elif action.nargs == PARSER:
+ result = '%s ...' % get_metavar(1)
+ else:
+ formats = ['%s' for _ in range(action.nargs)]
+ result = ' '.join(formats) % get_metavar(action.nargs)
+ return result
+
+ def _expand_help(self, action):
+ params = dict(vars(action), prog=self._prog)
+ for name in list(params):
+ if params[name] is SUPPRESS:
+ del params[name]
+ for name in list(params):
+ if hasattr(params[name], '__name__'):
+ params[name] = params[name].__name__
+ if params.get('choices') is not None:
+ choices_str = ', '.join([str(c) for c in params['choices']])
+ params['choices'] = choices_str
+ return self._get_help_string(action) % params
+
+ def _iter_indented_subactions(self, action):
+ try:
+ get_subactions = action._get_subactions
+ except AttributeError:
+ pass
+ else:
+ self._indent()
+ for subaction in get_subactions():
+ yield subaction
+ self._dedent()
+
+ def _split_lines(self, text, width):
+ text = self._whitespace_matcher.sub(' ', text).strip()
+ return _textwrap.wrap(text, width)
+
+ def _fill_text(self, text, width, indent):
+ text = self._whitespace_matcher.sub(' ', text).strip()
+ return _textwrap.fill(text, width, initial_indent=indent,
+ subsequent_indent=indent)
+
+ def _get_help_string(self, action):
+ return action.help
+
+
+class RawDescriptionHelpFormatter(HelpFormatter):
+ """Help message formatter which retains any formatting in descriptions.
+
+ Only the name of this class is considered a public API. All the methods
+ provided by the class are considered an implementation detail.
+ """
+
+ def _fill_text(self, text, width, indent):
+ return ''.join([indent + line for line in text.splitlines(True)])
+
+
+class RawTextHelpFormatter(RawDescriptionHelpFormatter):
+ """Help message formatter which retains formatting of all help text.
+
+ Only the name of this class is considered a public API. All the methods
+ provided by the class are considered an implementation detail.
+ """
+
+ def _split_lines(self, text, width):
+ return text.splitlines()
+
+
+class ArgumentDefaultsHelpFormatter(HelpFormatter):
+ """Help message formatter which adds default values to argument help.
+
+ Only the name of this class is considered a public API. All the methods
+ provided by the class are considered an implementation detail.
+ """
+
+ def _get_help_string(self, action):
+ help = action.help
+ if '%(default)' not in action.help:
+ if action.default is not SUPPRESS:
+ defaulting_nargs = [OPTIONAL, ZERO_OR_MORE]
+ if action.option_strings or action.nargs in defaulting_nargs:
+ help += ' (default: %(default)s)'
+ return help
+
+
+# =====================
+# Options and Arguments
+# =====================
+
+def _get_action_name(argument):
+ if argument is None:
+ return None
+ elif argument.option_strings:
+ return '/'.join(argument.option_strings)
+ elif argument.metavar not in (None, SUPPRESS):
+ return argument.metavar
+ elif argument.dest not in (None, SUPPRESS):
+ return argument.dest
+ else:
+ return None
+
+
+class ArgumentError(Exception):
+ """An error from creating or using an argument (optional or positional).
+
+ The string value of this exception is the message, augmented with
+ information about the argument that caused it.
+ """
+
+ def __init__(self, argument, message):
+ self.argument_name = _get_action_name(argument)
+ self.message = message
+
+ def __str__(self):
+ if self.argument_name is None:
+ format = '%(message)s'
+ else:
+ format = 'argument %(argument_name)s: %(message)s'
+ return format % dict(message=self.message,
+ argument_name=self.argument_name)
+
+
+class ArgumentTypeError(Exception):
+ """An error from trying to convert a command line string to a type."""
+ pass
+
+
+# ==============
+# Action classes
+# ==============
+
+class Action(_AttributeHolder):
+ """Information about how to convert command line strings to Python objects.
+
+ Action objects are used by an ArgumentParser to represent the information
+ needed to parse a single argument from one or more strings from the
+ command line. The keyword arguments to the Action constructor are also
+ all attributes of Action instances.
+
+ Keyword Arguments:
+
+ - option_strings -- A list of command-line option strings which
+ should be associated with this action.
+
+ - dest -- The name of the attribute to hold the created object(s)
+
+ - nargs -- The number of command-line arguments that should be
+ consumed. By default, one argument will be consumed and a single
+ value will be produced. Other values include:
+ - N (an integer) consumes N arguments (and produces a list)
+ - '?' consumes zero or one arguments
+ - '*' consumes zero or more arguments (and produces a list)
+ - '+' consumes one or more arguments (and produces a list)
+ Note that the difference between the default and nargs=1 is that
+ with the default, a single value will be produced, while with
+ nargs=1, a list containing a single value will be produced.
+
+ - const -- The value to be produced if the option is specified and the
+ option uses an action that takes no values.
+
+ - default -- The value to be produced if the option is not specified.
+
+ - type -- The type which the command-line arguments should be converted
+ to, should be one of 'string', 'int', 'float', 'complex' or a
+ callable object that accepts a single string argument. If None,
+ 'string' is assumed.
+
+ - choices -- A container of values that should be allowed. If not None,
+ after a command-line argument has been converted to the appropriate
+ type, an exception will be raised if it is not a member of this
+ collection.
+
+ - required -- True if the action must always be specified at the
+ command line. This is only meaningful for optional command-line
+ arguments.
+
+ - help -- The help string describing the argument.
+
+ - metavar -- The name to be used for the option's argument with the
+ help string. If None, the 'dest' value will be used as the name.
+ """
+
+ def __init__(self,
+ option_strings,
+ dest,
+ nargs=None,
+ const=None,
+ default=None,
+ type=None,
+ choices=None,
+ required=False,
+ help=None,
+ metavar=None):
+ self.option_strings = option_strings
+ self.dest = dest
+ self.nargs = nargs
+ self.const = const
+ self.default = default
+ self.type = type
+ self.choices = choices
+ self.required = required
+ self.help = help
+ self.metavar = metavar
+
+ def _get_kwargs(self):
+ names = [
+ 'option_strings',
+ 'dest',
+ 'nargs',
+ 'const',
+ 'default',
+ 'type',
+ 'choices',
+ 'help',
+ 'metavar',
+ ]
+ return [(name, getattr(self, name)) for name in names]
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ raise NotImplementedError(_('.__call__() not defined'))
+
+
+class _StoreAction(Action):
+
+ def __init__(self,
+ option_strings,
+ dest,
+ nargs=None,
+ const=None,
+ default=None,
+ type=None,
+ choices=None,
+ required=False,
+ help=None,
+ metavar=None):
+ if nargs == 0:
+ raise ValueError('nargs for store actions must be > 0; if you '
+ 'have nothing to store, actions such as store '
+ 'true or store const may be more appropriate')
+ if const is not None and nargs != OPTIONAL:
+ raise ValueError('nargs must be %r to supply const' % OPTIONAL)
+ super(_StoreAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ nargs=nargs,
+ const=const,
+ default=default,
+ type=type,
+ choices=choices,
+ required=required,
+ help=help,
+ metavar=metavar)
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ setattr(namespace, self.dest, values)
+
+
+class _StoreConstAction(Action):
+
+ def __init__(self,
+ option_strings,
+ dest,
+ const,
+ default=None,
+ required=False,
+ help=None,
+ metavar=None):
+ super(_StoreConstAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ nargs=0,
+ const=const,
+ default=default,
+ required=required,
+ help=help)
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ setattr(namespace, self.dest, self.const)
+
+
+class _StoreTrueAction(_StoreConstAction):
+
+ def __init__(self,
+ option_strings,
+ dest,
+ default=False,
+ required=False,
+ help=None):
+ super(_StoreTrueAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ const=True,
+ default=default,
+ required=required,
+ help=help)
+
+
+class _StoreFalseAction(_StoreConstAction):
+
+ def __init__(self,
+ option_strings,
+ dest,
+ default=True,
+ required=False,
+ help=None):
+ super(_StoreFalseAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ const=False,
+ default=default,
+ required=required,
+ help=help)
+
+
+class _AppendAction(Action):
+
+ def __init__(self,
+ option_strings,
+ dest,
+ nargs=None,
+ const=None,
+ default=None,
+ type=None,
+ choices=None,
+ required=False,
+ help=None,
+ metavar=None):
+ if nargs == 0:
+ raise ValueError('nargs for append actions must be > 0; if arg '
+ 'strings are not supplying the value to append, '
+ 'the append const action may be more appropriate')
+ if const is not None and nargs != OPTIONAL:
+ raise ValueError('nargs must be %r to supply const' % OPTIONAL)
+ super(_AppendAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ nargs=nargs,
+ const=const,
+ default=default,
+ type=type,
+ choices=choices,
+ required=required,
+ help=help,
+ metavar=metavar)
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ items = _copy.copy(_ensure_value(namespace, self.dest, []))
+ items.append(values)
+ setattr(namespace, self.dest, items)
+
+
+class _AppendConstAction(Action):
+
+ def __init__(self,
+ option_strings,
+ dest,
+ const,
+ default=None,
+ required=False,
+ help=None,
+ metavar=None):
+ super(_AppendConstAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ nargs=0,
+ const=const,
+ default=default,
+ required=required,
+ help=help,
+ metavar=metavar)
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ items = _copy.copy(_ensure_value(namespace, self.dest, []))
+ items.append(self.const)
+ setattr(namespace, self.dest, items)
+
+
+class _CountAction(Action):
+
+ def __init__(self,
+ option_strings,
+ dest,
+ default=None,
+ required=False,
+ help=None):
+ super(_CountAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ nargs=0,
+ default=default,
+ required=required,
+ help=help)
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ new_count = _ensure_value(namespace, self.dest, 0) + 1
+ setattr(namespace, self.dest, new_count)
+
+
+class _HelpAction(Action):
+
+ def __init__(self,
+ option_strings,
+ dest=SUPPRESS,
+ default=SUPPRESS,
+ help=None):
+ super(_HelpAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ default=default,
+ nargs=0,
+ help=help)
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ parser.print_help()
+ parser.exit()
+
+
+class _VersionAction(Action):
+
+ def __init__(self,
+ option_strings,
+ version=None,
+ dest=SUPPRESS,
+ default=SUPPRESS,
+ help="show program's version number and exit"):
+ super(_VersionAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ default=default,
+ nargs=0,
+ help=help)
+ self.version = version
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ version = self.version
+ if version is None:
+ version = parser.version
+ formatter = parser._get_formatter()
+ formatter.add_text(version)
+ parser.exit(message=formatter.format_help())
+
+
+class _SubParsersAction(Action):
+
+ class _ChoicesPseudoAction(Action):
+
+ def __init__(self, name, aliases, help):
+ metavar = dest = name
+ if aliases:
+ metavar += ' (%s)' % ', '.join(aliases)
+ sup = super(_SubParsersAction._ChoicesPseudoAction, self)
+ sup.__init__(option_strings=[], dest=dest, help=help,
+ metavar=metavar)
+
+ def __init__(self,
+ option_strings,
+ prog,
+ parser_class,
+ dest=SUPPRESS,
+ help=None,
+ metavar=None):
+
+ self._prog_prefix = prog
+ self._parser_class = parser_class
+ self._name_parser_map = {}
+ self._choices_actions = []
+
+ super(_SubParsersAction, self).__init__(
+ option_strings=option_strings,
+ dest=dest,
+ nargs=PARSER,
+ choices=self._name_parser_map,
+ help=help,
+ metavar=metavar)
+
+ def add_parser(self, name, **kwargs):
+ # set prog from the existing prefix
+ if kwargs.get('prog') is None:
+ kwargs['prog'] = '%s %s' % (self._prog_prefix, name)
+
+ aliases = kwargs.pop('aliases', ())
+
+ # create a pseudo-action to hold the choice help
+ if 'help' in kwargs:
+ help = kwargs.pop('help')
+ choice_action = self._ChoicesPseudoAction(name, aliases, help)
+ self._choices_actions.append(choice_action)
+
+ # create the parser and add it to the map
+ parser = self._parser_class(**kwargs)
+ self._name_parser_map[name] = parser
+
+ # make parser available under aliases also
+ for alias in aliases:
+ self._name_parser_map[alias] = parser
+
+ return parser
+
+ def _get_subactions(self):
+ return self._choices_actions
+
+ def __call__(self, parser, namespace, values, option_string=None):
+ parser_name = values[0]
+ arg_strings = values[1:]
+
+ # set the parser name if requested
+ if self.dest is not SUPPRESS:
+ setattr(namespace, self.dest, parser_name)
+
+ # select the parser
+ try:
+ parser = self._name_parser_map[parser_name]
+ except KeyError:
+ args = {'parser_name': parser_name,
+ 'choices': ', '.join(self._name_parser_map)}
+ msg = _('unknown parser %(parser_name)r (choices: %(choices)s)') % args
+ raise ArgumentError(self, msg)
+
+ # parse all the remaining options into the namespace
+ # store any unrecognized options on the object, so that the top
+ # level parser can decide what to do with them
+ namespace, arg_strings = parser.parse_known_args(arg_strings, namespace)
+ if arg_strings:
+ vars(namespace).setdefault(_UNRECOGNIZED_ARGS_ATTR, [])
+ getattr(namespace, _UNRECOGNIZED_ARGS_ATTR).extend(arg_strings)
+
+
+# ==============
+# Type classes
+# ==============
+
+class FileType(object):
+ """Factory for creating file object types
+
+ Instances of FileType are typically passed as type= arguments to the
+ ArgumentParser add_argument() method.
+
+ Keyword Arguments:
+ - mode -- A string indicating how the file is to be opened. Accepts the
+ same values as the builtin open() function.
+ - bufsize -- The file's desired buffer size. Accepts the same values as
+ the builtin open() function.
+ """
+
+ def __init__(self, mode='r', bufsize=-1):
+ self._mode = mode
+ self._bufsize = bufsize
+
+ def __call__(self, string):
+ # the special argument "-" means sys.std{in,out}
+ if string == '-':
+ if 'r' in self._mode:
+ return _sys.stdin
+ elif 'w' in self._mode:
+ return _sys.stdout
+ else:
+ msg = _('argument "-" with mode %r') % self._mode
+ raise ValueError(msg)
+
+ # all other arguments are used as file names
+ try:
+ return open(string, self._mode, self._bufsize)
+ except IOError as e:
+ message = _("can't open '%s': %s")
+ raise ArgumentTypeError(message % (string, e))
+
+ def __repr__(self):
+ args = self._mode, self._bufsize
+ args_str = ', '.join(repr(arg) for arg in args if arg != -1)
+ return '%s(%s)' % (type(self).__name__, args_str)
+
+# ===========================
+# Optional and Positional Parsing
+# ===========================
+
+class Namespace(_AttributeHolder):
+ """Simple object for storing attributes.
+
+ Implements equality by attribute names and values, and provides a simple
+ string representation.
+ """
+
+ def __init__(self, **kwargs):
+ for name in kwargs:
+ setattr(self, name, kwargs[name])
+
+ def __eq__(self, other):
+ return vars(self) == vars(other)
+
+ def __ne__(self, other):
+ return not (self == other)
+
+ def __contains__(self, key):
+ return key in self.__dict__
+
+
+class _ActionsContainer(object):
+
+ def __init__(self,
+ description,
+ prefix_chars,
+ argument_default,
+ conflict_handler):
+ super(_ActionsContainer, self).__init__()
+
+ self.description = description
+ self.argument_default = argument_default
+ self.prefix_chars = prefix_chars
+ self.conflict_handler = conflict_handler
+
+ # set up registries
+ self._registries = {}
+
+ # register actions
+ self.register('action', None, _StoreAction)
+ self.register('action', 'store', _StoreAction)
+ self.register('action', 'store_const', _StoreConstAction)
+ self.register('action', 'store_true', _StoreTrueAction)
+ self.register('action', 'store_false', _StoreFalseAction)
+ self.register('action', 'append', _AppendAction)
+ self.register('action', 'append_const', _AppendConstAction)
+ self.register('action', 'count', _CountAction)
+ self.register('action', 'help', _HelpAction)
+ self.register('action', 'version', _VersionAction)
+ self.register('action', 'parsers', _SubParsersAction)
+
+ # raise an exception if the conflict handler is invalid
+ self._get_handler()
+
+ # action storage
+ self._actions = []
+ self._option_string_actions = {}
+
+ # groups
+ self._action_groups = []
+ self._mutually_exclusive_groups = []
+
+ # defaults storage
+ self._defaults = {}
+
+ # determines whether an "option" looks like a negative number
+ self._negative_number_matcher = _re.compile(r'^-\d+$|^-\d*\.\d+$')
+
+ # whether or not there are any optionals that look like negative
+ # numbers -- uses a list so it can be shared and edited
+ self._has_negative_number_optionals = []
+
+ # ====================
+ # Registration methods
+ # ====================
+ def register(self, registry_name, value, object):
+ registry = self._registries.setdefault(registry_name, {})
+ registry[value] = object
+
+ def _registry_get(self, registry_name, value, default=None):
+ return self._registries[registry_name].get(value, default)
+
+ # ==================================
+ # Namespace default accessor methods
+ # ==================================
+ def set_defaults(self, **kwargs):
+ self._defaults.update(kwargs)
+
+ # if these defaults match any existing arguments, replace
+ # the previous default on the object with the new one
+ for action in self._actions:
+ if action.dest in kwargs:
+ action.default = kwargs[action.dest]
+
+ def get_default(self, dest):
+ for action in self._actions:
+ if action.dest == dest and action.default is not None:
+ return action.default
+ return self._defaults.get(dest, None)
+
+
+ # =======================
+ # Adding argument actions
+ # =======================
+ def add_argument(self, *args, **kwargs):
+ """
+ add_argument(dest, ..., name=value, ...)
+ add_argument(option_string, option_string, ..., name=value, ...)
+ """
+
+ # if no positional args are supplied or only one is supplied and
+ # it doesn't look like an option string, parse a positional
+ # argument
+ chars = self.prefix_chars
+ if not args or len(args) == 1 and args[0][0] not in chars:
+ if args and 'dest' in kwargs:
+ raise ValueError('dest supplied twice for positional argument')
+ kwargs = self._get_positional_kwargs(*args, **kwargs)
+
+ # otherwise, we're adding an optional argument
+ else:
+ kwargs = self._get_optional_kwargs(*args, **kwargs)
+
+ # if no default was supplied, use the parser-level default
+ if 'default' not in kwargs:
+ dest = kwargs['dest']
+ if dest in self._defaults:
+ kwargs['default'] = self._defaults[dest]
+ elif self.argument_default is not None:
+ kwargs['default'] = self.argument_default
+
+ # create the action object, and add it to the parser
+ action_class = self._pop_action_class(kwargs)
+ if not _callable(action_class):
+ raise ValueError('unknown action "%s"' % action_class)
+ action = action_class(**kwargs)
+
+ # raise an error if the action type is not callable
+ type_func = self._registry_get('type', action.type, action.type)
+ if not _callable(type_func):
+ raise ValueError('%r is not callable' % type_func)
+
+ return self._add_action(action)
+
+ def add_argument_group(self, *args, **kwargs):
+ group = _ArgumentGroup(self, *args, **kwargs)
+ self._action_groups.append(group)
+ return group
+
+ def add_mutually_exclusive_group(self, **kwargs):
+ group = _MutuallyExclusiveGroup(self, **kwargs)
+ self._mutually_exclusive_groups.append(group)
+ return group
+
+ def _add_action(self, action):
+ # resolve any conflicts
+ self._check_conflict(action)
+
+ # add to actions list
+ self._actions.append(action)
+ action.container = self
+
+ # index the action by any option strings it has
+ for option_string in action.option_strings:
+ self._option_string_actions[option_string] = action
+
+ # set the flag if any option strings look like negative numbers
+ for option_string in action.option_strings:
+ if self._negative_number_matcher.match(option_string):
+ if not self._has_negative_number_optionals:
+ self._has_negative_number_optionals.append(True)
+
+ # return the created action
+ return action
+
+ def _remove_action(self, action):
+ self._actions.remove(action)
+
+ def _add_container_actions(self, container):
+ # collect groups by titles
+ title_group_map = {}
+ for group in self._action_groups:
+ if group.title in title_group_map:
+ msg = _('cannot merge actions - two groups are named %r')
+ raise ValueError(msg % (group.title))
+ title_group_map[group.title] = group
+
+ # map each action to its group
+ group_map = {}
+ for group in container._action_groups:
+
+ # if a group with the title exists, use that, otherwise
+ # create a new group matching the container's group
+ if group.title not in title_group_map:
+ title_group_map[group.title] = self.add_argument_group(
+ title=group.title,
+ description=group.description,
+ conflict_handler=group.conflict_handler)
+
+ # map the actions to their new group
+ for action in group._group_actions:
+ group_map[action] = title_group_map[group.title]
+
+ # add container's mutually exclusive groups
+ # NOTE: if add_mutually_exclusive_group ever gains title= and
+ # description= then this code will need to be expanded as above
+ for group in container._mutually_exclusive_groups:
+ mutex_group = self.add_mutually_exclusive_group(
+ required=group.required)
+
+ # map the actions to their new mutex group
+ for action in group._group_actions:
+ group_map[action] = mutex_group
+
+ # add all actions to this container or their group
+ for action in container._actions:
+ group_map.get(action, self)._add_action(action)
+
+ def _get_positional_kwargs(self, dest, **kwargs):
+ # make sure required is not specified
+ if 'required' in kwargs:
+ msg = _("'required' is an invalid argument for positionals")
+ raise TypeError(msg)
+
+ # mark positional arguments as required if at least one is
+ # always required
+ if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]:
+ kwargs['required'] = True
+ if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs:
+ kwargs['required'] = True
+
+ # return the keyword arguments with no option strings
+ return dict(kwargs, dest=dest, option_strings=[])
+
+ def _get_optional_kwargs(self, *args, **kwargs):
+ # determine short and long option strings
+ option_strings = []
+ long_option_strings = []
+ for option_string in args:
+ # error on strings that don't start with an appropriate prefix
+ if not option_string[0] in self.prefix_chars:
+ args = {'option': option_string,
+ 'prefix_chars': self.prefix_chars}
+ msg = _('invalid option string %(option)r: '
+ 'must start with a character %(prefix_chars)r')
+ raise ValueError(msg % args)
+
+ # strings starting with two prefix characters are long options
+ option_strings.append(option_string)
+ if option_string[0] in self.prefix_chars:
+ if len(option_string) > 1:
+ if option_string[1] in self.prefix_chars:
+ long_option_strings.append(option_string)
+
+ # infer destination, '--foo-bar' -> 'foo_bar' and '-x' -> 'x'
+ dest = kwargs.pop('dest', None)
+ if dest is None:
+ if long_option_strings:
+ dest_option_string = long_option_strings[0]
+ else:
+ dest_option_string = option_strings[0]
+ dest = dest_option_string.lstrip(self.prefix_chars)
+ if not dest:
+ msg = _('dest= is required for options like %r')
+ raise ValueError(msg % option_string)
+ dest = dest.replace('-', '_')
+
+ # return the updated keyword arguments
+ return dict(kwargs, dest=dest, option_strings=option_strings)
+
+ def _pop_action_class(self, kwargs, default=None):
+ action = kwargs.pop('action', default)
+ return self._registry_get('action', action, action)
+
+ def _get_handler(self):
+ # determine function from conflict handler string
+ handler_func_name = '_handle_conflict_%s' % self.conflict_handler
+ try:
+ return getattr(self, handler_func_name)
+ except AttributeError:
+ msg = _('invalid conflict_resolution value: %r')
+ raise ValueError(msg % self.conflict_handler)
+
+ def _check_conflict(self, action):
+
+ # find all options that conflict with this option
+ confl_optionals = []
+ for option_string in action.option_strings:
+ if option_string in self._option_string_actions:
+ confl_optional = self._option_string_actions[option_string]
+ confl_optionals.append((option_string, confl_optional))
+
+ # resolve any conflicts
+ if confl_optionals:
+ conflict_handler = self._get_handler()
+ conflict_handler(action, confl_optionals)
+
+ def _handle_conflict_error(self, action, conflicting_actions):
+ message = ngettext('conflicting option string: %s',
+ 'conflicting option strings: %s',
+ len(conflicting_actions))
+ conflict_string = ', '.join([option_string
+ for option_string, action
+ in conflicting_actions])
+ raise ArgumentError(action, message % conflict_string)
+
+ def _handle_conflict_resolve(self, action, conflicting_actions):
+
+ # remove all conflicting options
+ for option_string, action in conflicting_actions:
+
+ # remove the conflicting option
+ action.option_strings.remove(option_string)
+ self._option_string_actions.pop(option_string, None)
+
+ # if the option now has no option string, remove it from the
+ # container holding it
+ if not action.option_strings:
+ action.container._remove_action(action)
+
+
+class _ArgumentGroup(_ActionsContainer):
+
+ def __init__(self, container, title=None, description=None, **kwargs):
+ # add any missing keyword arguments by checking the container
+ update = kwargs.setdefault
+ update('conflict_handler', container.conflict_handler)
+ update('prefix_chars', container.prefix_chars)
+ update('argument_default', container.argument_default)
+ super_init = super(_ArgumentGroup, self).__init__
+ super_init(description=description, **kwargs)
+
+ # group attributes
+ self.title = title
+ self._group_actions = []
+
+ # share most attributes with the container
+ self._registries = container._registries
+ self._actions = container._actions
+ self._option_string_actions = container._option_string_actions
+ self._defaults = container._defaults
+ self._has_negative_number_optionals = \
+ container._has_negative_number_optionals
+ self._mutually_exclusive_groups = container._mutually_exclusive_groups
+
+ def _add_action(self, action):
+ action = super(_ArgumentGroup, self)._add_action(action)
+ self._group_actions.append(action)
+ return action
+
+ def _remove_action(self, action):
+ super(_ArgumentGroup, self)._remove_action(action)
+ self._group_actions.remove(action)
+
+
+class _MutuallyExclusiveGroup(_ArgumentGroup):
+
+ def __init__(self, container, required=False):
+ super(_MutuallyExclusiveGroup, self).__init__(container)
+ self.required = required
+ self._container = container
+
+ def _add_action(self, action):
+ if action.required:
+ msg = _('mutually exclusive arguments must be optional')
+ raise ValueError(msg)
+ action = self._container._add_action(action)
+ self._group_actions.append(action)
+ return action
+
+ def _remove_action(self, action):
+ self._container._remove_action(action)
+ self._group_actions.remove(action)
+
+
+class ArgumentParser(_AttributeHolder, _ActionsContainer):
+ """Object for parsing command line strings into Python objects.
+
+ Keyword Arguments:
+ - prog -- The name of the program (default: sys.argv[0])
+ - usage -- A usage message (default: auto-generated from arguments)
+ - description -- A description of what the program does
+ - epilog -- Text following the argument descriptions
+ - parents -- Parsers whose arguments should be copied into this one
+ - formatter_class -- HelpFormatter class for printing help messages
+ - prefix_chars -- Characters that prefix optional arguments
+ - fromfile_prefix_chars -- Characters that prefix files containing
+ additional arguments
+ - argument_default -- The default value for all arguments
+ - conflict_handler -- String indicating how to handle conflicts
+ - add_help -- Add a -h/-help option
+ """
+
+ def __init__(self,
+ prog=None,
+ usage=None,
+ description=None,
+ epilog=None,
+ version=None,
+ parents=[],
+ formatter_class=HelpFormatter,
+ prefix_chars='-',
+ fromfile_prefix_chars=None,
+ argument_default=None,
+ conflict_handler='error',
+ add_help=True):
+
+ if version is not None:
+ import warnings
+ warnings.warn(
+ """The "version" argument to ArgumentParser is deprecated. """
+ """Please use """
+ """"add_argument(..., action='version', version="N", ...)" """
+ """instead""", DeprecationWarning)
+
+ superinit = super(ArgumentParser, self).__init__
+ superinit(description=description,
+ prefix_chars=prefix_chars,
+ argument_default=argument_default,
+ conflict_handler=conflict_handler)
+
+ # default setting for prog
+ if prog is None:
+ prog = _os.path.basename(_sys.argv[0])
+
+ self.prog = prog
+ self.usage = usage
+ self.epilog = epilog
+ self.version = version
+ self.formatter_class = formatter_class
+ self.fromfile_prefix_chars = fromfile_prefix_chars
+ self.add_help = add_help
+
+ add_group = self.add_argument_group
+ self._positionals = add_group(_('positional arguments'))
+ self._optionals = add_group(_('optional arguments'))
+ self._subparsers = None
+
+ # register types
+ def identity(string):
+ return string
+ self.register('type', None, identity)
+
+ # add help and version arguments if necessary
+ # (using explicit default to override global argument_default)
+ default_prefix = '-' if '-' in prefix_chars else prefix_chars[0]
+ if self.add_help:
+ self.add_argument(
+ default_prefix+'h', default_prefix*2+'help',
+ action='help', default=SUPPRESS,
+ help=_('show this help message and exit'))
+ if self.version:
+ self.add_argument(
+ default_prefix+'v', default_prefix*2+'version',
+ action='version', default=SUPPRESS,
+ version=self.version,
+ help=_("show program's version number and exit"))
+
+ # add parent arguments and defaults
+ for parent in parents:
+ self._add_container_actions(parent)
+ try:
+ defaults = parent._defaults
+ except AttributeError:
+ pass
+ else:
+ self._defaults.update(defaults)
+
+ # =======================
+ # Pretty __repr__ methods
+ # =======================
+ def _get_kwargs(self):
+ names = [
+ 'prog',
+ 'usage',
+ 'description',
+ 'version',
+ 'formatter_class',
+ 'conflict_handler',
+ 'add_help',
+ ]
+ return [(name, getattr(self, name)) for name in names]
+
+ # ==================================
+ # Optional/Positional adding methods
+ # ==================================
+ def add_subparsers(self, **kwargs):
+ if self._subparsers is not None:
+ self.error(_('cannot have multiple subparser arguments'))
+
+ # add the parser class to the arguments if it's not present
+ kwargs.setdefault('parser_class', type(self))
+
+ if 'title' in kwargs or 'description' in kwargs:
+ title = _(kwargs.pop('title', 'subcommands'))
+ description = _(kwargs.pop('description', None))
+ self._subparsers = self.add_argument_group(title, description)
+ else:
+ self._subparsers = self._positionals
+
+ # prog defaults to the usage message of this parser, skipping
+ # optional arguments and with no "usage:" prefix
+ if kwargs.get('prog') is None:
+ formatter = self._get_formatter()
+ positionals = self._get_positional_actions()
+ groups = self._mutually_exclusive_groups
+ formatter.add_usage(self.usage, positionals, groups, '')
+ kwargs['prog'] = formatter.format_help().strip()
+
+ # create the parsers action and add it to the positionals list
+ parsers_class = self._pop_action_class(kwargs, 'parsers')
+ action = parsers_class(option_strings=[], **kwargs)
+ self._subparsers._add_action(action)
+
+ # return the created parsers action
+ return action
+
+ def _add_action(self, action):
+ if action.option_strings:
+ self._optionals._add_action(action)
+ else:
+ self._positionals._add_action(action)
+ return action
+
+ def _get_optional_actions(self):
+ return [action
+ for action in self._actions
+ if action.option_strings]
+
+ def _get_positional_actions(self):
+ return [action
+ for action in self._actions
+ if not action.option_strings]
+
+ # =====================================
+ # Command line argument parsing methods
+ # =====================================
+ def parse_args(self, args=None, namespace=None):
+ args, argv = self.parse_known_args(args, namespace)
+ if argv:
+ msg = _('unrecognized arguments: %s')
+ self.error(msg % ' '.join(argv))
+ return args
+
+ def parse_known_args(self, args=None, namespace=None):
+ # args default to the system args
+ if args is None:
+ args = _sys.argv[1:]
+
+ # default Namespace built from parser defaults
+ if namespace is None:
+ namespace = Namespace()
+
+ # add any action defaults that aren't present
+ for action in self._actions:
+ if action.dest is not SUPPRESS:
+ if not hasattr(namespace, action.dest):
+ if action.default is not SUPPRESS:
+ default = action.default
+ if isinstance(action.default, str):
+ default = self._get_value(action, default)
+ setattr(namespace, action.dest, default)
+
+ # add any parser defaults that aren't present
+ for dest in self._defaults:
+ if not hasattr(namespace, dest):
+ setattr(namespace, dest, self._defaults[dest])
+
+ # parse the arguments and exit if there are any errors
+ try:
+ namespace, args = self._parse_known_args(args, namespace)
+ if hasattr(namespace, _UNRECOGNIZED_ARGS_ATTR):
+ args.extend(getattr(namespace, _UNRECOGNIZED_ARGS_ATTR))
+ delattr(namespace, _UNRECOGNIZED_ARGS_ATTR)
+ return namespace, args
+ except ArgumentError:
+ err = _sys.exc_info()[1]
+ self.error(str(err))
+
+ def _parse_known_args(self, arg_strings, namespace):
+ # replace arg strings that are file references
+ if self.fromfile_prefix_chars is not None:
+ arg_strings = self._read_args_from_files(arg_strings)
+
+ # map all mutually exclusive arguments to the other arguments
+ # they can't occur with
+ action_conflicts = {}
+ for mutex_group in self._mutually_exclusive_groups:
+ group_actions = mutex_group._group_actions
+ for i, mutex_action in enumerate(mutex_group._group_actions):
+ conflicts = action_conflicts.setdefault(mutex_action, [])
+ conflicts.extend(group_actions[:i])
+ conflicts.extend(group_actions[i + 1:])
+
+ # find all option indices, and determine the arg_string_pattern
+ # which has an 'O' if there is an option at an index,
+ # an 'A' if there is an argument, or a '-' if there is a '--'
+ option_string_indices = {}
+ arg_string_pattern_parts = []
+ arg_strings_iter = iter(arg_strings)
+ for i, arg_string in enumerate(arg_strings_iter):
+
+ # all args after -- are non-options
+ if arg_string == '--':
+ arg_string_pattern_parts.append('-')
+ for arg_string in arg_strings_iter:
+ arg_string_pattern_parts.append('A')
+
+ # otherwise, add the arg to the arg strings
+ # and note the index if it was an option
+ else:
+ option_tuple = self._parse_optional(arg_string)
+ if option_tuple is None:
+ pattern = 'A'
+ else:
+ option_string_indices[i] = option_tuple
+ pattern = 'O'
+ arg_string_pattern_parts.append(pattern)
+
+ # join the pieces together to form the pattern
+ arg_strings_pattern = ''.join(arg_string_pattern_parts)
+
+ # converts arg strings to the appropriate and then takes the action
+ seen_actions = set()
+ seen_non_default_actions = set()
+
+ def take_action(action, argument_strings, option_string=None):
+ seen_actions.add(action)
+ argument_values = self._get_values(action, argument_strings)
+
+ # error if this argument is not allowed with other previously
+ # seen arguments, assuming that actions that use the default
+ # value don't really count as "present"
+ if argument_values is not action.default:
+ seen_non_default_actions.add(action)
+ for conflict_action in action_conflicts.get(action, []):
+ if conflict_action in seen_non_default_actions:
+ msg = _('not allowed with argument %s')
+ action_name = _get_action_name(conflict_action)
+ raise ArgumentError(action, msg % action_name)
+
+ # take the action if we didn't receive a SUPPRESS value
+ # (e.g. from a default)
+ if argument_values is not SUPPRESS:
+ action(self, namespace, argument_values, option_string)
+
+ # function to convert arg_strings into an optional action
+ def consume_optional(start_index):
+
+ # get the optional identified at this index
+ option_tuple = option_string_indices[start_index]
+ action, option_string, explicit_arg = option_tuple
+
+ # identify additional optionals in the same arg string
+ # (e.g. -xyz is the same as -x -y -z if no args are required)
+ match_argument = self._match_argument
+ action_tuples = []
+ while True:
+
+ # if we found no optional action, skip it
+ if action is None:
+ extras.append(arg_strings[start_index])
+ return start_index + 1
+
+ # if there is an explicit argument, try to match the
+ # optional's string arguments to only this
+ if explicit_arg is not None:
+ arg_count = match_argument(action, 'A')
+
+ # if the action is a single-dash option and takes no
+ # arguments, try to parse more single-dash options out
+ # of the tail of the option string
+ chars = self.prefix_chars
+ if arg_count == 0 and option_string[1] not in chars:
+ action_tuples.append((action, [], option_string))
+ char = option_string[0]
+ option_string = char + explicit_arg[0]
+ new_explicit_arg = explicit_arg[1:] or None
+ optionals_map = self._option_string_actions
+ if option_string in optionals_map:
+ action = optionals_map[option_string]
+ explicit_arg = new_explicit_arg
+ else:
+ msg = _('ignored explicit argument %r')
+ raise ArgumentError(action, msg % explicit_arg)
+
+ # if the action expect exactly one argument, we've
+ # successfully matched the option; exit the loop
+ elif arg_count == 1:
+ stop = start_index + 1
+ args = [explicit_arg]
+ action_tuples.append((action, args, option_string))
+ break
+
+ # error if a double-dash option did not use the
+ # explicit argument
+ else:
+ msg = _('ignored explicit argument %r')
+ raise ArgumentError(action, msg % explicit_arg)
+
+ # if there is no explicit argument, try to match the
+ # optional's string arguments with the following strings
+ # if successful, exit the loop
+ else:
+ start = start_index + 1
+ selected_patterns = arg_strings_pattern[start:]
+ arg_count = match_argument(action, selected_patterns)
+ stop = start + arg_count
+ args = arg_strings[start:stop]
+ action_tuples.append((action, args, option_string))
+ break
+
+ # add the Optional to the list and return the index at which
+ # the Optional's string args stopped
+ assert action_tuples
+ for action, args, option_string in action_tuples:
+ take_action(action, args, option_string)
+ return stop
+
+ # the list of Positionals left to be parsed; this is modified
+ # by consume_positionals()
+ positionals = self._get_positional_actions()
+
+ # function to convert arg_strings into positional actions
+ def consume_positionals(start_index):
+ # match as many Positionals as possible
+ match_partial = self._match_arguments_partial
+ selected_pattern = arg_strings_pattern[start_index:]
+ arg_counts = match_partial(positionals, selected_pattern)
+
+ # slice off the appropriate arg strings for each Positional
+ # and add the Positional and its args to the list
+ for action, arg_count in zip(positionals, arg_counts):
+ args = arg_strings[start_index: start_index + arg_count]
+ start_index += arg_count
+ take_action(action, args)
+
+ # slice off the Positionals that we just parsed and return the
+ # index at which the Positionals' string args stopped
+ positionals[:] = positionals[len(arg_counts):]
+ return start_index
+
+ # consume Positionals and Optionals alternately, until we have
+ # passed the last option string
+ extras = []
+ start_index = 0
+ if option_string_indices:
+ max_option_string_index = max(option_string_indices)
+ else:
+ max_option_string_index = -1
+ while start_index <= max_option_string_index:
+
+ # consume any Positionals preceding the next option
+ next_option_string_index = min([
+ index
+ for index in option_string_indices
+ if index >= start_index])
+ if start_index != next_option_string_index:
+ positionals_end_index = consume_positionals(start_index)
+
+ # only try to parse the next optional if we didn't consume
+ # the option string during the positionals parsing
+ if positionals_end_index > start_index:
+ start_index = positionals_end_index
+ continue
+ else:
+ start_index = positionals_end_index
+
+ # if we consumed all the positionals we could and we're not
+ # at the index of an option string, there were extra arguments
+ if start_index not in option_string_indices:
+ strings = arg_strings[start_index:next_option_string_index]
+ extras.extend(strings)
+ start_index = next_option_string_index
+
+ # consume the next optional and any arguments for it
+ start_index = consume_optional(start_index)
+
+ # consume any positionals following the last Optional
+ stop_index = consume_positionals(start_index)
+
+ # if we didn't consume all the argument strings, there were extras
+ extras.extend(arg_strings[stop_index:])
+
+ # if we didn't use all the Positional objects, there were too few
+ # arg strings supplied.
+ if positionals:
+ self.error(_('too few arguments'))
+
+ # make sure all required actions were present
+ for action in self._actions:
+ if action.required:
+ if action not in seen_actions:
+ name = _get_action_name(action)
+ self.error(_('argument %s is required') % name)
+
+ # make sure all required groups had one option present
+ for group in self._mutually_exclusive_groups:
+ if group.required:
+ for action in group._group_actions:
+ if action in seen_non_default_actions:
+ break
+
+ # if no actions were used, report the error
+ else:
+ names = [_get_action_name(action)
+ for action in group._group_actions
+ if action.help is not SUPPRESS]
+ msg = _('one of the arguments %s is required')
+ self.error(msg % ' '.join(names))
+
+ # return the updated namespace and the extra arguments
+ return namespace, extras
+
+ def _read_args_from_files(self, arg_strings):
+ # expand arguments referencing files
+ new_arg_strings = []
+ for arg_string in arg_strings:
+
+ # for regular arguments, just add them back into the list
+ if arg_string[0] not in self.fromfile_prefix_chars:
+ new_arg_strings.append(arg_string)
+
+ # replace arguments referencing files with the file content
+ else:
+ try:
+ args_file = open(arg_string[1:])
+ try:
+ arg_strings = []
+ for arg_line in args_file.read().splitlines():
+ for arg in self.convert_arg_line_to_args(arg_line):
+ arg_strings.append(arg)
+ arg_strings = self._read_args_from_files(arg_strings)
+ new_arg_strings.extend(arg_strings)
+ finally:
+ args_file.close()
+ except IOError:
+ err = _sys.exc_info()[1]
+ self.error(str(err))
+
+ # return the modified argument list
+ return new_arg_strings
+
+ def convert_arg_line_to_args(self, arg_line):
+ return [arg_line]
+
+ def _match_argument(self, action, arg_strings_pattern):
+ # match the pattern for this action to the arg strings
+ nargs_pattern = self._get_nargs_pattern(action)
+ match = _re.match(nargs_pattern, arg_strings_pattern)
+
+ # raise an exception if we weren't able to find a match
+ if match is None:
+ nargs_errors = {
+ None: _('expected one argument'),
+ OPTIONAL: _('expected at most one argument'),
+ ONE_OR_MORE: _('expected at least one argument'),
+ }
+ default = ngettext('expected %s argument',
+ 'expected %s arguments',
+ action.nargs) % action.nargs
+ msg = nargs_errors.get(action.nargs, default)
+ raise ArgumentError(action, msg)
+
+ # return the number of arguments matched
+ return len(match.group(1))
+
+ def _match_arguments_partial(self, actions, arg_strings_pattern):
+ # progressively shorten the actions list by slicing off the
+ # final actions until we find a match
+ result = []
+ for i in range(len(actions), 0, -1):
+ actions_slice = actions[:i]
+ pattern = ''.join([self._get_nargs_pattern(action)
+ for action in actions_slice])
+ match = _re.match(pattern, arg_strings_pattern)
+ if match is not None:
+ result.extend([len(string) for string in match.groups()])
+ break
+
+ # return the list of arg string counts
+ return result
+
+ def _parse_optional(self, arg_string):
+ # if it's an empty string, it was meant to be a positional
+ if not arg_string:
+ return None
+
+ # if it doesn't start with a prefix, it was meant to be positional
+ if not arg_string[0] in self.prefix_chars:
+ return None
+
+ # if the option string is present in the parser, return the action
+ if arg_string in self._option_string_actions:
+ action = self._option_string_actions[arg_string]
+ return action, arg_string, None
+
+ # if it's just a single character, it was meant to be positional
+ if len(arg_string) == 1:
+ return None
+
+ # if the option string before the "=" is present, return the action
+ if '=' in arg_string:
+ option_string, explicit_arg = arg_string.split('=', 1)
+ if option_string in self._option_string_actions:
+ action = self._option_string_actions[option_string]
+ return action, option_string, explicit_arg
+
+ # search through all possible prefixes of the option string
+ # and all actions in the parser for possible interpretations
+ option_tuples = self._get_option_tuples(arg_string)
+
+ # if multiple actions match, the option string was ambiguous
+ if len(option_tuples) > 1:
+ options = ', '.join([option_string
+ for action, option_string, explicit_arg in option_tuples])
+ args = {'option': arg_string, 'matches': options}
+ msg = _('ambiguous option: %(option)s could match %(matches)s')
+ self.error(msg % args)
+
+ # if exactly one action matched, this segmentation is good,
+ # so return the parsed action
+ elif len(option_tuples) == 1:
+ option_tuple, = option_tuples
+ return option_tuple
+
+ # if it was not found as an option, but it looks like a negative
+ # number, it was meant to be positional
+ # unless there are negative-number-like options
+ if self._negative_number_matcher.match(arg_string):
+ if not self._has_negative_number_optionals:
+ return None
+
+ # if it contains a space, it was meant to be a positional
+ if ' ' in arg_string:
+ return None
+
+ # it was meant to be an optional but there is no such option
+ # in this parser (though it might be a valid option in a subparser)
+ return None, arg_string, None
+
+ def _get_option_tuples(self, option_string):
+ result = []
+
+ # option strings starting with two prefix characters are only
+ # split at the '='
+ chars = self.prefix_chars
+ if option_string[0] in chars and option_string[1] in chars:
+ if '=' in option_string:
+ option_prefix, explicit_arg = option_string.split('=', 1)
+ else:
+ option_prefix = option_string
+ explicit_arg = None
+ for option_string in self._option_string_actions:
+ if option_string.startswith(option_prefix):
+ action = self._option_string_actions[option_string]
+ tup = action, option_string, explicit_arg
+ result.append(tup)
+
+ # single character options can be concatenated with their arguments
+ # but multiple character options always have to have their argument
+ # separate
+ elif option_string[0] in chars and option_string[1] not in chars:
+ option_prefix = option_string
+ explicit_arg = None
+ short_option_prefix = option_string[:2]
+ short_explicit_arg = option_string[2:]
+
+ for option_string in self._option_string_actions:
+ if option_string == short_option_prefix:
+ action = self._option_string_actions[option_string]
+ tup = action, option_string, short_explicit_arg
+ result.append(tup)
+ elif option_string.startswith(option_prefix):
+ action = self._option_string_actions[option_string]
+ tup = action, option_string, explicit_arg
+ result.append(tup)
+
+ # shouldn't ever get here
+ else:
+ self.error(_('unexpected option string: %s') % option_string)
+
+ # return the collected option tuples
+ return result
+
+ def _get_nargs_pattern(self, action):
+ # in all examples below, we have to allow for '--' args
+ # which are represented as '-' in the pattern
+ nargs = action.nargs
+
+ # the default (None) is assumed to be a single argument
+ if nargs is None:
+ nargs_pattern = '(-*A-*)'
+
+ # allow zero or one arguments
+ elif nargs == OPTIONAL:
+ nargs_pattern = '(-*A?-*)'
+
+ # allow zero or more arguments
+ elif nargs == ZERO_OR_MORE:
+ nargs_pattern = '(-*[A-]*)'
+
+ # allow one or more arguments
+ elif nargs == ONE_OR_MORE:
+ nargs_pattern = '(-*A[A-]*)'
+
+ # allow any number of options or arguments
+ elif nargs == REMAINDER:
+ nargs_pattern = '([-AO]*)'
+
+ # allow one argument followed by any number of options or arguments
+ elif nargs == PARSER:
+ nargs_pattern = '(-*A[-AO]*)'
+
+ # all others should be integers
+ else:
+ nargs_pattern = '(-*%s-*)' % '-*'.join('A' * nargs)
+
+ # if this is an optional action, -- is not allowed
+ if action.option_strings:
+ nargs_pattern = nargs_pattern.replace('-*', '')
+ nargs_pattern = nargs_pattern.replace('-', '')
+
+ # return the pattern
+ return nargs_pattern
+
+ # ========================
+ # Value conversion methods
+ # ========================
+ def _get_values(self, action, arg_strings):
+ # for everything but PARSER args, strip out '--'
+ if action.nargs not in [PARSER, REMAINDER]:
+ arg_strings = [s for s in arg_strings if s != '--']
+
+ # optional argument produces a default when not present
+ if not arg_strings and action.nargs == OPTIONAL:
+ if action.option_strings:
+ value = action.const
+ else:
+ value = action.default
+ if isinstance(value, str):
+ value = self._get_value(action, value)
+ self._check_value(action, value)
+
+ # when nargs='*' on a positional, if there were no command-line
+ # args, use the default if it is anything other than None
+ elif (not arg_strings and action.nargs == ZERO_OR_MORE and
+ not action.option_strings):
+ if action.default is not None:
+ value = action.default
+ else:
+ value = arg_strings
+ self._check_value(action, value)
+
+ # single argument or optional argument produces a single value
+ elif len(arg_strings) == 1 and action.nargs in [None, OPTIONAL]:
+ arg_string, = arg_strings
+ value = self._get_value(action, arg_string)
+ self._check_value(action, value)
+
+ # REMAINDER arguments convert all values, checking none
+ elif action.nargs == REMAINDER:
+ value = [self._get_value(action, v) for v in arg_strings]
+
+ # PARSER arguments convert all values, but check only the first
+ elif action.nargs == PARSER:
+ value = [self._get_value(action, v) for v in arg_strings]
+ self._check_value(action, value[0])
+
+ # all other types of nargs produce a list
+ else:
+ value = [self._get_value(action, v) for v in arg_strings]
+ for v in value:
+ self._check_value(action, v)
+
+ # return the converted value
+ return value
+
+ def _get_value(self, action, arg_string):
+ type_func = self._registry_get('type', action.type, action.type)
+ if not _callable(type_func):
+ msg = _('%r is not callable')
+ raise ArgumentError(action, msg % type_func)
+
+ # convert the value to the appropriate type
+ try:
+ result = type_func(arg_string)
+
+ # ArgumentTypeErrors indicate errors
+ except ArgumentTypeError:
+ name = getattr(action.type, '__name__', repr(action.type))
+ msg = str(_sys.exc_info()[1])
+ raise ArgumentError(action, msg)
+
+ # TypeErrors or ValueErrors also indicate errors
+ except (TypeError, ValueError):
+ name = getattr(action.type, '__name__', repr(action.type))
+ args = {'type': name, 'value': arg_string}
+ msg = _('invalid %(type)s value: %(value)r')
+ raise ArgumentError(action, msg % args)
+
+ # return the converted value
+ return result
+
+ def _check_value(self, action, value):
+ # converted value must be one of the choices (if specified)
+ if action.choices is not None and value not in action.choices:
+ args = {'value': value,
+ 'choices': ', '.join(map(repr, action.choices))}
+ msg = _('invalid choice: %(value)r (choose from %(choices)s)')
+ raise ArgumentError(action, msg % args)
+
+ # =======================
+ # Help-formatting methods
+ # =======================
+ def format_usage(self):
+ formatter = self._get_formatter()
+ formatter.add_usage(self.usage, self._actions,
+ self._mutually_exclusive_groups)
+ return formatter.format_help()
+
+ def format_help(self):
+ formatter = self._get_formatter()
+
+ # usage
+ formatter.add_usage(self.usage, self._actions,
+ self._mutually_exclusive_groups)
+
+ # description
+ formatter.add_text(self.description)
+
+ # positionals, optionals and user-defined groups
+ for action_group in self._action_groups:
+ formatter.start_section(action_group.title)
+ formatter.add_text(action_group.description)
+ formatter.add_arguments(action_group._group_actions)
+ formatter.end_section()
+
+ # epilog
+ formatter.add_text(self.epilog)
+
+ # determine help from format above
+ return formatter.format_help()
+
+ def format_version(self):
+ import warnings
+ warnings.warn(
+ 'The format_version method is deprecated -- the "version" '
+ 'argument to ArgumentParser is no longer supported.',
+ DeprecationWarning)
+ formatter = self._get_formatter()
+ formatter.add_text(self.version)
+ return formatter.format_help()
+
+ def _get_formatter(self):
+ return self.formatter_class(prog=self.prog)
+
+ # =====================
+ # Help-printing methods
+ # =====================
+ def print_usage(self, file=None):
+ if file is None:
+ file = _sys.stdout
+ self._print_message(self.format_usage(), file)
+
+ def print_help(self, file=None):
+ if file is None:
+ file = _sys.stdout
+ self._print_message(self.format_help(), file)
+
+ def print_version(self, file=None):
+ import warnings
+ warnings.warn(
+ 'The print_version method is deprecated -- the "version" '
+ 'argument to ArgumentParser is no longer supported.',
+ DeprecationWarning)
+ self._print_message(self.format_version(), file)
+
+ def _print_message(self, message, file=None):
+ if message:
+ if file is None:
+ file = _sys.stderr
+ file.write(message)
+
+ # ===============
+ # Exiting methods
+ # ===============
+ def exit(self, status=0, message=None):
+ if message:
+ self._print_message(message, _sys.stderr)
+ _sys.exit(status)
+
+ def error(self, message):
+ """error(message: string)
+
+ Prints a usage message incorporating the message to stderr and
+ exits.
+
+ If you override this in a subclass, it should not return -- it
+ should either exit or raise an exception.
+ """
+ self.print_usage(_sys.stderr)
+ args = {'prog': self.prog, 'message': message}
+ self.exit(2, _('%(prog)s: error: %(message)s\n') % args)
diff --git a/Lib/ast.py b/Lib/ast.py
index 112c970..72237ed 100644
--- a/Lib/ast.py
+++ b/Lib/ast.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""
ast
~~~
@@ -50,7 +49,7 @@ def literal_eval(node_or_string):
if isinstance(node_or_string, Expression):
node_or_string = node_or_string.body
def _convert(node):
- if isinstance(node, Str):
+ if isinstance(node, (Str, Bytes)):
return node.s
elif isinstance(node, Num):
return node.n
@@ -58,25 +57,33 @@ def literal_eval(node_or_string):
return tuple(map(_convert, node.elts))
elif isinstance(node, List):
return list(map(_convert, node.elts))
+ elif isinstance(node, Set):
+ return set(map(_convert, node.elts))
elif isinstance(node, Dict):
return dict((_convert(k), _convert(v)) for k, v
in zip(node.keys, node.values))
elif isinstance(node, Name):
if node.id in _safe_names:
return _safe_names[node.id]
+ elif isinstance(node, UnaryOp) and \
+ isinstance(node.op, (UAdd, USub)) and \
+ isinstance(node.operand, (Num, UnaryOp, BinOp)):
+ operand = _convert(node.operand)
+ if isinstance(node.op, UAdd):
+ return + operand
+ else:
+ return - operand
elif isinstance(node, BinOp) and \
isinstance(node.op, (Add, Sub)) and \
- isinstance(node.right, Num) and \
- isinstance(node.right.n, complex) and \
- isinstance(node.left, Num) and \
- isinstance(node.left.n, (int, float)):
- left = node.left.n
- right = node.right.n
+ isinstance(node.right, (Num, UnaryOp, BinOp)) and \
+ isinstance(node.left, (Num, UnaryOp, BinOp)):
+ left = _convert(node.left)
+ right = _convert(node.right)
if isinstance(node.op, Add):
return left + right
else:
return left - right
- raise ValueError('malformed string')
+ raise ValueError('malformed node or string: ' + repr(node))
return _convert(node_or_string)
diff --git a/Lib/asyncore.py b/Lib/asyncore.py
index 00464a9..5d7bdda 100644
--- a/Lib/asyncore.py
+++ b/Lib/asyncore.py
@@ -50,10 +50,12 @@ import select
import socket
import sys
import time
+import warnings
+
import os
from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, EINVAL, \
- ENOTCONN, ESHUTDOWN, EINTR, EISCONN, EBADF, ECONNABORTED, EPIPE, \
- EAGAIN, errorcode
+ ENOTCONN, ESHUTDOWN, EINTR, EISCONN, EBADF, ECONNABORTED, EPIPE, EAGAIN, \
+ errorcode
_DISCONNECTED = frozenset((ECONNRESET, ENOTCONN, ESHUTDOWN, ECONNABORTED, EPIPE,
EBADF))
@@ -270,6 +272,8 @@ class dispatcher:
status.append(repr(self.addr))
return '<%s at %#x>' % (' '.join(status), id(self))
+ __str__ = __repr__
+
def add_channel(self, map=None):
#self.log_info('adding channel %s' % self)
if map is None:
@@ -367,7 +371,7 @@ class dispatcher:
except socket.error as why:
if why.args[0] == EWOULDBLOCK:
return 0
- elif why.args[0] in (ECONNRESET, ENOTCONN, ESHUTDOWN, ECONNABORTED):
+ elif why.args[0] in _DISCONNECTED:
self.handle_close()
return 0
else:
@@ -385,7 +389,7 @@ class dispatcher:
return data
except socket.error as why:
# winsock sometimes throws ENOTCONN
- if why.args[0] in [ECONNRESET, ENOTCONN, ESHUTDOWN, ECONNABORTED]:
+ if why.args[0] in _DISCONNECTED:
self.handle_close()
return b''
else:
@@ -405,10 +409,15 @@ class dispatcher:
# references to the underlying socket object.
def __getattr__(self, attr):
try:
- return getattr(self.socket, attr)
+ retattr = getattr(self.socket, attr)
except AttributeError:
raise AttributeError("%s instance has no attribute '%s'"
%(self.__class__.__name__, attr))
+ else:
+ msg = "%(me)s.%(attr)s is deprecated; use %(me)s.socket.%(attr)s " \
+ "instead" % {'me' : self.__class__.__name__, 'attr' : attr}
+ warnings.warn(msg, DeprecationWarning, stacklevel=2)
+ return retattr
# log and log_info may be overridden to provide more sophisticated
# logging and warning methods. In general, log is for 'hit' logging
@@ -502,7 +511,13 @@ class dispatcher:
self.log_info('unhandled connect event', 'warning')
def handle_accept(self):
- self.log_info('unhandled accept event', 'warning')
+ pair = self.accept()
+ if pair is not None:
+ self.handle_accepted(*pair)
+
+ def handle_accepted(self, sock, addr):
+ sock.close()
+ self.log_info('unhandled accepted event', 'warning')
def handle_close(self):
self.log_info('unhandled close event', 'warning')
diff --git a/Lib/base64.py b/Lib/base64.py
index f93b3a4..895d813 100755
--- a/Lib/base64.py
+++ b/Lib/base64.py
@@ -1,4 +1,4 @@
-#! /usr/bin/env python
+#! /usr/bin/env python3
"""RFC 3548: Base16, Base32, Base64 Data Encodings"""
@@ -65,16 +65,19 @@ def b64encode(s, altchars=None):
return encoded
-def b64decode(s, altchars=None):
+def b64decode(s, altchars=None, validate=False):
"""Decode a Base64 encoded byte string.
s is the byte string to decode. Optional altchars must be a
string of length 2 which specifies the alternative alphabet used
instead of the '+' and '/' characters.
- The decoded byte string is returned. binascii.Error is raised if
- s were incorrectly padded or if there are non-alphabet characters
- present in the string.
+ The decoded string is returned. A binascii.Error is raised if s is
+ incorrectly padded.
+
+ If validate is False (the default), non-base64-alphabet characters are
+ discarded prior to the padding check. If validate is True,
+ non-base64-alphabet characters in the input result in a binascii.Error.
"""
if not isinstance(s, bytes_types):
raise TypeError("expected bytes, not %s" % s.__class__.__name__)
@@ -84,6 +87,8 @@ def b64decode(s, altchars=None):
% altchars.__class__.__name__)
assert len(altchars) == 2, repr(altchars)
s = _translate(s, {chr(altchars[0]): b'+', chr(altchars[1]): b'/'})
+ if validate and not re.match(b'^[A-Za-z0-9+/]*={0,2}$', s):
+ raise binascii.Error('Non-base64 digit found')
return binascii.a2b_base64(s)
@@ -241,7 +246,7 @@ def b32decode(s, casefold=False, map01=None):
acc += _b32rev[c] << shift
shift -= 5
if shift < 0:
- parts.append(binascii.unhexlify('%010x' % acc))
+ parts.append(binascii.unhexlify(bytes('%010x' % acc, "ascii")))
acc = 0
shift = 35
# Process the last, partial quanta
diff --git a/Lib/bdb.py b/Lib/bdb.py
index 3ed25fe..f711004 100644
--- a/Lib/bdb.py
+++ b/Lib/bdb.py
@@ -3,16 +3,14 @@
import fnmatch
import sys
import os
-import types
-__all__ = ["BdbQuit","Bdb","Breakpoint"]
+__all__ = ["BdbQuit", "Bdb", "Breakpoint"]
class BdbQuit(Exception):
- """Exception to give up completely"""
+ """Exception to give up completely."""
class Bdb:
-
"""Generic Python debugger base class.
This class takes care of details of the trace facility;
@@ -120,14 +118,14 @@ class Bdb:
def break_here(self, frame):
filename = self.canonic(frame.f_code.co_filename)
- if not filename in self.breaks:
+ if filename not in self.breaks:
return False
lineno = frame.f_lineno
- if not lineno in self.breaks[filename]:
+ if lineno not in self.breaks[filename]:
# The line itself has no breakpoint, but maybe the line is the
# first line of a function with breakpoint set by function name.
lineno = frame.f_code.co_firstlineno
- if not lineno in self.breaks[filename]:
+ if lineno not in self.breaks[filename]:
return False
# flag says ok to delete temp. bp
@@ -170,7 +168,7 @@ class Bdb:
def _set_stopinfo(self, stopframe, returnframe, stoplineno=0):
self.stopframe = stopframe
self.returnframe = returnframe
- self.quitting = 0
+ self.quitting = False
# stoplineno >= 0 means: stop at line >= the stoplineno
# stoplineno -1 means: don't stop at all
self.stoplineno = stoplineno
@@ -178,10 +176,13 @@ class Bdb:
# Derived classes and clients can call the following methods
# to affect the stepping state.
- def set_until(self, frame): #the name "until" is borrowed from gdb
+ def set_until(self, frame, lineno=None):
"""Stop when the line with the line no greater than the current one is
reached or when returning from current frame"""
- self._set_stopinfo(frame, frame, frame.f_lineno+1)
+ # the name "until" is borrowed from gdb
+ if lineno is None:
+ lineno = frame.f_lineno + 1
+ self._set_stopinfo(frame, frame, lineno)
def set_step(self):
"""Stop after one line of code."""
@@ -224,7 +225,7 @@ class Bdb:
def set_quit(self):
self.stopframe = self.botframe
self.returnframe = None
- self.quitting = 1
+ self.quitting = True
sys.settrace(None)
# Derived classes and clients can call the following methods
@@ -234,18 +235,15 @@ class Bdb:
# Call self.get_*break*() to see the breakpoints or better
# for bp in Breakpoint.bpbynumber: if bp: bp.bpprint().
- def set_break(self, filename, lineno, temporary=0, cond = None,
+ def set_break(self, filename, lineno, temporary=False, cond=None,
funcname=None):
filename = self.canonic(filename)
import linecache # Import as late as possible
line = linecache.getline(filename, lineno)
if not line:
- return 'Line %s:%d does not exist' % (filename,
- lineno)
- if not filename in self.breaks:
- self.breaks[filename] = []
- list = self.breaks[filename]
- if not lineno in list:
+ return 'Line %s:%d does not exist' % (filename, lineno)
+ list = self.breaks.setdefault(filename, [])
+ if lineno not in list:
list.append(lineno)
bp = Breakpoint(filename, lineno, temporary, cond, funcname)
@@ -257,11 +255,10 @@ class Bdb:
def clear_break(self, filename, lineno):
filename = self.canonic(filename)
- if not filename in self.breaks:
+ if filename not in self.breaks:
return 'There are no breakpoints in %s' % filename
if lineno not in self.breaks[filename]:
- return 'There is no breakpoint at %s:%d' % (filename,
- lineno)
+ return 'There is no breakpoint at %s:%d' % (filename, lineno)
# If there's only one bp in the list for that file,line
# pair, then remove the breaks entry
for bp in Breakpoint.bplist[filename, lineno][:]:
@@ -270,21 +267,15 @@ class Bdb:
def clear_bpbynumber(self, arg):
try:
- number = int(arg)
- except:
- return 'Non-numeric breakpoint number (%s)' % arg
- try:
- bp = Breakpoint.bpbynumber[number]
- except IndexError:
- return 'Breakpoint number (%d) out of range' % number
- if not bp:
- return 'Breakpoint (%d) already deleted' % number
+ bp = self.get_bpbynumber(arg)
+ except ValueError as err:
+ return str(err)
bp.deleteMe()
self._prune_breaks(bp.file, bp.line)
def clear_all_file_breaks(self, filename):
filename = self.canonic(filename)
- if not filename in self.breaks:
+ if filename not in self.breaks:
return 'There are no breakpoints in %s' % filename
for line in self.breaks[filename]:
blist = Breakpoint.bplist[filename, line]
@@ -300,6 +291,21 @@ class Bdb:
bp.deleteMe()
self.breaks = {}
+ def get_bpbynumber(self, arg):
+ if not arg:
+ raise ValueError('Breakpoint number expected')
+ try:
+ number = int(arg)
+ except ValueError:
+ raise ValueError('Non-numeric breakpoint number %s' % arg)
+ try:
+ bp = Breakpoint.bpbynumber[number]
+ except IndexError:
+ raise ValueError('Breakpoint number %d out of range' % number)
+ if bp is None:
+ raise ValueError('Breakpoint %d already deleted' % number)
+ return bp
+
def get_break(self, filename, lineno):
filename = self.canonic(filename)
return filename in self.breaks and \
@@ -342,35 +348,35 @@ class Bdb:
i = max(0, len(stack) - 1)
return stack, i
- #
-
def format_stack_entry(self, frame_lineno, lprefix=': '):
import linecache, reprlib
frame, lineno = frame_lineno
filename = self.canonic(frame.f_code.co_filename)
s = '%s(%r)' % (filename, lineno)
if frame.f_code.co_name:
- s = s + frame.f_code.co_name
+ s += frame.f_code.co_name
else:
- s = s + "<lambda>"
+ s += "<lambda>"
if '__args__' in frame.f_locals:
args = frame.f_locals['__args__']
else:
args = None
if args:
- s = s + reprlib.repr(args)
+ s += reprlib.repr(args)
else:
- s = s + '()'
+ s += '()'
if '__return__' in frame.f_locals:
rv = frame.f_locals['__return__']
- s = s + '->'
- s = s + reprlib.repr(rv)
+ s += '->'
+ s += reprlib.repr(rv)
line = linecache.getline(filename, lineno, frame.f_globals)
- if line: s = s + lprefix + line.strip()
+ if line:
+ s += lprefix + line.strip()
return s
- # The following two methods can be called by clients to use
- # a debugger to debug a statement, given as a string.
+ # The following methods can be called by clients to use
+ # a debugger to debug a statement or an expression.
+ # Both can be given as a string, or a code object.
def run(self, cmd, globals=None, locals=None):
if globals is None:
@@ -379,15 +385,15 @@ class Bdb:
if locals is None:
locals = globals
self.reset()
+ if isinstance(cmd, str):
+ cmd = compile(cmd, "<string>", "exec")
sys.settrace(self.trace_dispatch)
- if not isinstance(cmd, types.CodeType):
- cmd = cmd+'\n'
try:
exec(cmd, globals, locals)
except BdbQuit:
pass
finally:
- self.quitting = 1
+ self.quitting = True
sys.settrace(None)
def runeval(self, expr, globals=None, locals=None):
@@ -398,14 +404,12 @@ class Bdb:
locals = globals
self.reset()
sys.settrace(self.trace_dispatch)
- if not isinstance(expr, types.CodeType):
- expr = expr+'\n'
try:
return eval(expr, globals, locals)
except BdbQuit:
pass
finally:
- self.quitting = 1
+ self.quitting = True
sys.settrace(None)
def runctx(self, cmd, globals, locals):
@@ -423,7 +427,7 @@ class Bdb:
except BdbQuit:
pass
finally:
- self.quitting = 1
+ self.quitting = True
sys.settrace(None)
return res
@@ -433,8 +437,7 @@ def set_trace():
class Breakpoint:
-
- """Breakpoint class
+ """Breakpoint class.
Implements temporary breakpoints, ignore counts, disabling and
(re)-enabling, and conditionals.
@@ -456,7 +459,7 @@ class Breakpoint:
# index 0 is unused, except for marking an
# effective break .... see effective()
- def __init__(self, file, line, temporary=0, cond=None, funcname=None):
+ def __init__(self, file, line, temporary=False, cond=None, funcname=None):
self.funcname = funcname
# Needed if funcname is not None.
self.func_first_executable_line = None
@@ -464,11 +467,11 @@ class Breakpoint:
self.line = line
self.temporary = temporary
self.cond = cond
- self.enabled = 1
+ self.enabled = True
self.ignore = 0
self.hits = 0
self.number = Breakpoint.next
- Breakpoint.next = Breakpoint.next + 1
+ Breakpoint.next += 1
# Build the two lists
self.bpbynumber.append(self)
if (file, line) in self.bplist:
@@ -476,7 +479,6 @@ class Breakpoint:
else:
self.bplist[file, line] = [self]
-
def deleteMe(self):
index = (self.file, self.line)
self.bpbynumber[self.number] = None # No longer in list
@@ -486,14 +488,17 @@ class Breakpoint:
del self.bplist[index]
def enable(self):
- self.enabled = 1
+ self.enabled = True
def disable(self):
- self.enabled = 0
+ self.enabled = False
def bpprint(self, out=None):
if out is None:
out = sys.stdout
+ print(self.bpformat(), file=out)
+
+ def bpformat(self):
if self.temporary:
disp = 'del '
else:
@@ -502,17 +507,22 @@ class Breakpoint:
disp = disp + 'yes '
else:
disp = disp + 'no '
- print('%-4dbreakpoint %s at %s:%d' % (self.number, disp,
- self.file, self.line), file=out)
+ ret = '%-4dbreakpoint %s at %s:%d' % (self.number, disp,
+ self.file, self.line)
if self.cond:
- print('\tstop only if %s' % (self.cond,), file=out)
+ ret += '\n\tstop only if %s' % (self.cond,)
if self.ignore:
- print('\tignore next %d hits' % (self.ignore), file=out)
- if (self.hits):
- if (self.hits > 1): ss = 's'
- else: ss = ''
- print(('\tbreakpoint already hit %d time%s' %
- (self.hits, ss)), file=out)
+ ret += '\n\tignore next %d hits' % (self.ignore,)
+ if self.hits:
+ if self.hits > 1:
+ ss = 's'
+ else:
+ ss = ''
+ ret += '\n\tbreakpoint already hit %d time%s' % (self.hits, ss)
+ return ret
+
+ def __str__(self):
+ return 'breakpoint %s at %s:%s' % (self.number, self.file, self.line)
# -----------end of Breakpoint class----------
@@ -552,49 +562,44 @@ def effective(file, line, frame):
that indicates if it is ok to delete a temporary bp.
"""
- possibles = Breakpoint.bplist[file,line]
- for i in range(0, len(possibles)):
- b = possibles[i]
- if b.enabled == 0:
+ possibles = Breakpoint.bplist[file, line]
+ for b in possibles:
+ if not b.enabled:
continue
if not checkfuncname(b, frame):
continue
# Count every hit when bp is enabled
- b.hits = b.hits + 1
+ b.hits += 1
if not b.cond:
- # If unconditional, and ignoring,
- # go on to next, else break
+ # If unconditional, and ignoring go on to next, else break
if b.ignore > 0:
- b.ignore = b.ignore -1
+ b.ignore -= 1
continue
else:
- # breakpoint and marker that's ok
- # to delete if temporary
- return (b,1)
+ # breakpoint and marker that it's ok to delete if temporary
+ return (b, True)
else:
# Conditional bp.
# Ignore count applies only to those bpt hits where the
# condition evaluates to true.
try:
- val = eval(b.cond, frame.f_globals,
- frame.f_locals)
+ val = eval(b.cond, frame.f_globals, frame.f_locals)
if val:
if b.ignore > 0:
- b.ignore = b.ignore -1
+ b.ignore -= 1
# continue
else:
- return (b,1)
+ return (b, True)
# else:
# continue
except:
- # if eval fails, most conservative
- # thing is to stop on breakpoint
- # regardless of ignore count.
- # Don't delete temporary,
- # as another hint to user.
- return (b,0)
+ # if eval fails, most conservative thing is to stop on
+ # breakpoint regardless of ignore count. Don't delete
+ # temporary, as another hint to user.
+ return (b, False)
return (None, None)
+
# -------------------- testing --------------------
class Tdb(Bdb):
@@ -627,5 +632,3 @@ def bar(a):
def test():
t = Tdb()
t.run('import bdb; bdb.foo(10)')
-
-# end
diff --git a/Lib/cProfile.py b/Lib/cProfile.py
index 3e924ba..c24d45b 100755
--- a/Lib/cProfile.py
+++ b/Lib/cProfile.py
@@ -1,10 +1,10 @@
-#! /usr/bin/env python
+#! /usr/bin/env python3
"""Python interface for the 'lsprof' profiler.
Compatible with the 'profile' module.
"""
-__all__ = ["run", "runctx", "help", "Profile"]
+__all__ = ["run", "runctx", "Profile"]
import _lsprof
@@ -56,11 +56,6 @@ def runctx(statement, globals, locals, filename=None, sort=-1):
result = prof.print_stats(sort)
return result
-# Backwards compatibility.
-def help():
- print("Documentation for the profile/cProfile modules can be found ")
- print("in the Python Library Reference, section 'The Python Profiler'.")
-
# ____________________________________________________________
class Profile(_lsprof.Profiler):
diff --git a/Lib/cgi.py b/Lib/cgi.py
index 7da2b23..e198ed8 100755
--- a/Lib/cgi.py
+++ b/Lib/cgi.py
@@ -31,13 +31,15 @@ __version__ = "2.6"
# Imports
# =======
-from operator import attrgetter
-from io import StringIO
+from io import StringIO, BytesIO, TextIOWrapper
import sys
import os
import urllib.parse
-import email.parser
+from email.parser import FeedParser
from warnings import warn
+import html
+import locale
+import tempfile
__all__ = ["MiniFieldStorage", "FieldStorage",
"parse", "parse_qs", "parse_qsl", "parse_multipart",
@@ -109,7 +111,7 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
Arguments, all optional:
- fp : file pointer; default: sys.stdin
+ fp : file pointer; default: sys.stdin.buffer
environ : environment dictionary; default: os.environ
@@ -126,6 +128,18 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
"""
if fp is None:
fp = sys.stdin
+
+ # field keys and values (except for files) are returned as strings
+ # an encoding is required to decode the bytes read from self.fp
+ if hasattr(fp,'encoding'):
+ encoding = fp.encoding
+ else:
+ encoding = 'latin-1'
+
+ # fp.read() must return bytes
+ if isinstance(fp, TextIOWrapper):
+ fp = fp.buffer
+
if not 'REQUEST_METHOD' in environ:
environ['REQUEST_METHOD'] = 'GET' # For testing stand-alone
if environ['REQUEST_METHOD'] == 'POST':
@@ -136,7 +150,7 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
clength = int(environ['CONTENT_LENGTH'])
if maxlen and clength > maxlen:
raise ValueError('Maximum content length exceeded')
- qs = fp.read(clength)
+ qs = fp.read(clength).decode(encoding)
else:
qs = '' # Unknown content-type
if 'QUERY_STRING' in environ:
@@ -154,7 +168,8 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0):
else:
qs = ""
environ['QUERY_STRING'] = qs # XXX Shouldn't, really
- return urllib.parse.parse_qs(qs, keep_blank_values, strict_parsing)
+ return urllib.parse.parse_qs(qs, keep_blank_values, strict_parsing,
+ encoding=encoding)
# parse query string function called from urlparse,
@@ -236,8 +251,8 @@ def parse_multipart(fp, pdict):
if not line:
terminator = lastpart # End outer loop
break
- if line[:2] == "--":
- terminator = line.strip()
+ if line.startswith("--"):
+ terminator = line.rstrip()
if terminator in (nextpart, lastpart):
break
lines.append(line)
@@ -352,9 +367,10 @@ class FieldStorage:
value: the value as a *string*; for file uploads, this
transparently reads the file every time you request the value
+ and returns *bytes*
- file: the file(-like) object from which you can read the data;
- None if the data is stored a simple string
+ file: the file(-like) object from which you can read the data *as
+ bytes* ; None if the data is stored a simple string
type: the content-type, or None if not specified
@@ -375,15 +391,18 @@ class FieldStorage:
directory and unlinking them as soon as they have been opened.
"""
-
- def __init__(self, fp=None, headers=None, outerboundary="",
- environ=os.environ, keep_blank_values=0, strict_parsing=0):
+ def __init__(self, fp=None, headers=None, outerboundary=b'',
+ environ=os.environ, keep_blank_values=0, strict_parsing=0,
+ limit=None, encoding='utf-8', errors='replace'):
"""Constructor. Read multipart/* until last part.
Arguments, all optional:
- fp : file pointer; default: sys.stdin
+ fp : file pointer; default: sys.stdin.buffer
(not used when the request method is GET)
+ Can be :
+ 1. a TextIOWrapper object
+ 2. an object whose read() and readline() methods return bytes
headers : header dictionary-like object; default:
taken from environ as per CGI spec
@@ -404,6 +423,16 @@ class FieldStorage:
If false (the default), errors are silently ignored.
If true, errors raise a ValueError exception.
+ limit : used internally to read parts of multipart/form-data forms,
+ to exit from the reading loop when reached. It is the difference
+ between the form content-length and the number of bytes already
+ read
+
+ encoding, errors : the encoding and error handler used to decode the
+ binary stream to strings. Must be the same as the charset defined
+ for the page sending the form (content-type : meta http-equiv or
+ header)
+
"""
method = 'GET'
self.keep_blank_values = keep_blank_values
@@ -418,7 +447,8 @@ class FieldStorage:
qs = sys.argv[1]
else:
qs = ""
- fp = StringIO(qs)
+ qs = qs.encode(locale.getpreferredencoding(), 'surrogateescape')
+ fp = BytesIO(qs)
if headers is None:
headers = {'content-type':
"application/x-www-form-urlencoded"}
@@ -433,10 +463,26 @@ class FieldStorage:
self.qs_on_post = environ['QUERY_STRING']
if 'CONTENT_LENGTH' in environ:
headers['content-length'] = environ['CONTENT_LENGTH']
- self.fp = fp or sys.stdin
+ if fp is None:
+ self.fp = sys.stdin.buffer
+ # self.fp.read() must return bytes
+ elif isinstance(fp, TextIOWrapper):
+ self.fp = fp.buffer
+ else:
+ self.fp = fp
+
+ self.encoding = encoding
+ self.errors = errors
+
self.headers = headers
+ if not isinstance(outerboundary, bytes):
+ raise TypeError('outerboundary must be bytes, not %s'
+ % type(outerboundary).__name__)
self.outerboundary = outerboundary
+ self.bytes_read = 0
+ self.limit = limit
+
# Process content-disposition header
cdisp, pdict = "", {}
if 'content-disposition' in self.headers:
@@ -449,6 +495,7 @@ class FieldStorage:
self.filename = None
if 'filename' in pdict:
self.filename = pdict['filename']
+ self._binary_file = self.filename is not None
# Process content-type header
#
@@ -470,9 +517,11 @@ class FieldStorage:
ctype, pdict = 'application/x-www-form-urlencoded', {}
self.type = ctype
self.type_options = pdict
- self.innerboundary = ""
if 'boundary' in pdict:
- self.innerboundary = pdict['boundary']
+ self.innerboundary = pdict['boundary'].encode(self.encoding)
+ else:
+ self.innerboundary = b""
+
clen = -1
if 'content-length' in self.headers:
try:
@@ -482,6 +531,8 @@ class FieldStorage:
if maxlen and clen > maxlen:
raise ValueError('Maximum content length exceeded')
self.length = clen
+ if self.limit is None and clen:
+ self.limit = clen
self.list = self.file = None
self.done = 0
@@ -531,7 +582,7 @@ class FieldStorage:
"""Dictionary style get() method, including 'value' lookup."""
if key in self:
value = self[key]
- if type(value) is type([]):
+ if isinstance(value, list):
return [x.value for x in value]
else:
return value.value
@@ -542,7 +593,7 @@ class FieldStorage:
""" Return the first value received."""
if key in self:
value = self[key]
- if type(value) is type([]):
+ if isinstance(value, list):
return value[0].value
else:
return value.value
@@ -553,7 +604,7 @@ class FieldStorage:
""" Return list of received values."""
if key in self:
value = self[key]
- if type(value) is type([]):
+ if isinstance(value, list):
return [x.value for x in value]
else:
return [value.value]
@@ -582,12 +633,18 @@ class FieldStorage:
def read_urlencoded(self):
"""Internal: read data in query string format."""
qs = self.fp.read(self.length)
+ if not isinstance(qs, bytes):
+ raise ValueError("%s should return bytes, got %s" \
+ % (self.fp, type(qs).__name__))
+ qs = qs.decode(self.encoding, self.errors)
if self.qs_on_post:
qs += '&' + self.qs_on_post
- self.list = list = []
- for key, value in urllib.parse.parse_qsl(qs, self.keep_blank_values,
- self.strict_parsing):
- list.append(MiniFieldStorage(key, value))
+ self.list = []
+ query = urllib.parse.parse_qsl(
+ qs, self.keep_blank_values, self.strict_parsing,
+ encoding=self.encoding, errors=self.errors)
+ for key, value in query:
+ self.list.append(MiniFieldStorage(key, value))
self.skip_lines()
FieldStorageClass = None
@@ -599,24 +656,42 @@ class FieldStorage:
raise ValueError('Invalid boundary in multipart form: %r' % (ib,))
self.list = []
if self.qs_on_post:
- for key, value in urllib.parse.parse_qsl(self.qs_on_post,
- self.keep_blank_values, self.strict_parsing):
+ query = urllib.parse.parse_qsl(
+ self.qs_on_post, self.keep_blank_values, self.strict_parsing,
+ encoding=self.encoding, errors=self.errors)
+ for key, value in query:
self.list.append(MiniFieldStorage(key, value))
FieldStorageClass = None
klass = self.FieldStorageClass or self.__class__
- parser = email.parser.FeedParser()
- # Create bogus content-type header for proper multipart parsing
- parser.feed('Content-Type: %s; boundary=%s\r\n\r\n' % (self.type, ib))
- parser.feed(self.fp.read())
- full_msg = parser.close()
- # Get subparts
- msgs = full_msg.get_payload()
- for msg in msgs:
- fp = StringIO(msg.get_payload())
- part = klass(fp, msg, ib, environ, keep_blank_values,
- strict_parsing)
+ first_line = self.fp.readline() # bytes
+ if not isinstance(first_line, bytes):
+ raise ValueError("%s should return bytes, got %s" \
+ % (self.fp, type(first_line).__name__))
+ self.bytes_read += len(first_line)
+ # first line holds boundary ; ignore it, or check that
+ # b"--" + ib == first_line.strip() ?
+ while True:
+ parser = FeedParser()
+ hdr_text = b""
+ while True:
+ data = self.fp.readline()
+ hdr_text += data
+ if not data.strip():
+ break
+ if not hdr_text:
+ break
+ # parser takes strings, not bytes
+ self.bytes_read += len(hdr_text)
+ parser.feed(hdr_text.decode(self.encoding, self.errors))
+ headers = parser.close()
+ part = klass(self.fp, headers, ib, environ, keep_blank_values,
+ strict_parsing,self.limit-self.bytes_read,
+ self.encoding, self.errors)
+ self.bytes_read += part.bytes_read
self.list.append(part)
+ if self.bytes_read >= self.length:
+ break
self.skip_lines()
def read_single(self):
@@ -636,7 +711,11 @@ class FieldStorage:
todo = self.length
if todo >= 0:
while todo > 0:
- data = self.fp.read(min(todo, self.bufsize))
+ data = self.fp.read(min(todo, self.bufsize)) # bytes
+ if not isinstance(data, bytes):
+ raise ValueError("%s should return bytes, got %s"
+ % (self.fp, type(data).__name__))
+ self.bytes_read += len(data)
if not data:
self.done = -1
break
@@ -645,59 +724,77 @@ class FieldStorage:
def read_lines(self):
"""Internal: read lines until EOF or outerboundary."""
- self.file = self.__file = StringIO()
+ if self._binary_file:
+ self.file = self.__file = BytesIO() # store data as bytes for files
+ else:
+ self.file = self.__file = StringIO() # as strings for other fields
if self.outerboundary:
self.read_lines_to_outerboundary()
else:
self.read_lines_to_eof()
def __write(self, line):
+ """line is always bytes, not string"""
if self.__file is not None:
if self.__file.tell() + len(line) > 1000:
self.file = self.make_file()
data = self.__file.getvalue()
self.file.write(data)
self.__file = None
- self.file.write(line)
+ if self._binary_file:
+ # keep bytes
+ self.file.write(line)
+ else:
+ # decode to string
+ self.file.write(line.decode(self.encoding, self.errors))
def read_lines_to_eof(self):
"""Internal: read lines until EOF."""
while 1:
- line = self.fp.readline(1<<16)
+ line = self.fp.readline(1<<16) # bytes
+ self.bytes_read += len(line)
if not line:
self.done = -1
break
self.__write(line)
def read_lines_to_outerboundary(self):
- """Internal: read lines until outerboundary."""
- next = "--" + self.outerboundary
- last = next + "--"
- delim = ""
+ """Internal: read lines until outerboundary.
+ Data is read as bytes: boundaries and line ends must be converted
+ to bytes for comparisons.
+ """
+ next_boundary = b"--" + self.outerboundary
+ last_boundary = next_boundary + b"--"
+ delim = b""
last_line_lfend = True
+ _read = 0
while 1:
- line = self.fp.readline(1<<16)
+ if _read >= self.limit:
+ break
+ line = self.fp.readline(1<<16) # bytes
+ self.bytes_read += len(line)
+ _read += len(line)
if not line:
self.done = -1
break
- if line[:2] == "--" and last_line_lfend:
- strippedline = line.strip()
- if strippedline == next:
+ if line.startswith(b"--") and last_line_lfend:
+ strippedline = line.rstrip()
+ if strippedline == next_boundary:
break
- if strippedline == last:
+ if strippedline == last_boundary:
self.done = 1
break
odelim = delim
- if line[-2:] == "\r\n":
- delim = "\r\n"
+ if line.endswith(b"\r\n"):
+ delim = b"\r\n"
line = line[:-2]
last_line_lfend = True
- elif line[-1] == "\n":
- delim = "\n"
+ elif line.endswith(b"\n"):
+ delim = b"\n"
line = line[:-1]
last_line_lfend = True
else:
- delim = ""
+ delim = b""
last_line_lfend = False
self.__write(odelim + line)
@@ -705,22 +802,23 @@ class FieldStorage:
"""Internal: skip lines until outer boundary if defined."""
if not self.outerboundary or self.done:
return
- next = "--" + self.outerboundary
- last = next + "--"
+ next_boundary = b"--" + self.outerboundary
+ last_boundary = next_boundary + b"--"
last_line_lfend = True
- while 1:
+ while True:
line = self.fp.readline(1<<16)
+ self.bytes_read += len(line)
if not line:
self.done = -1
break
- if line[:2] == "--" and last_line_lfend:
+ if line.endswith(b"--") and last_line_lfend:
strippedline = line.strip()
- if strippedline == next:
+ if strippedline == next_boundary:
break
- if strippedline == last:
+ if strippedline == last_boundary:
self.done = 1
break
- last_line_lfend = line.endswith('\n')
+ last_line_lfend = line.endswith(b'\n')
def make_file(self):
"""Overridable: return a readable & writable file.
@@ -730,7 +828,8 @@ class FieldStorage:
- seek(0)
- data is read from it
- The file is always opened in text mode.
+ The file is opened in binary mode for files, in text mode
+ for other fields
This version opens a temporary file for reading and writing,
and immediately deletes (unlinks) it. The trick (on Unix!) is
@@ -745,8 +844,11 @@ class FieldStorage:
which unlinks the temporary files you have created.
"""
- import tempfile
- return tempfile.TemporaryFile("w+", encoding="utf-8", newline="\n")
+ if self._binary_file:
+ return tempfile.TemporaryFile("wb+")
+ else:
+ return tempfile.TemporaryFile("w+",
+ encoding=self.encoding, newline = '\n')
# Test/debug code
@@ -800,8 +902,8 @@ def print_exception(type=None, value=None, tb=None, limit=None):
list = traceback.format_tb(tb, limit) + \
traceback.format_exception_only(type, value)
print("<PRE>%s<B>%s</B></PRE>" % (
- escape("".join(list[:-1])),
- escape(list[-1]),
+ html.escape("".join(list[:-1])),
+ html.escape(list[-1]),
))
del tb
@@ -812,7 +914,7 @@ def print_environ(environ=os.environ):
print("<H3>Shell Environment:</H3>")
print("<DL>")
for key in keys:
- print("<DT>", escape(key), "<DD>", escape(environ[key]))
+ print("<DT>", html.escape(key), "<DD>", html.escape(environ[key]))
print("</DL>")
print()
@@ -825,10 +927,10 @@ def print_form(form):
print("<P>No form fields.")
print("<DL>")
for key in keys:
- print("<DT>" + escape(key) + ":", end=' ')
+ print("<DT>" + html.escape(key) + ":", end=' ')
value = form[key]
- print("<i>" + escape(repr(type(value))) + "</i>")
- print("<DD>" + escape(repr(value)))
+ print("<i>" + html.escape(repr(type(value))) + "</i>")
+ print("<DD>" + html.escape(repr(value)))
print("</DL>")
print()
@@ -839,9 +941,9 @@ def print_directory():
try:
pwd = os.getcwd()
except os.error as msg:
- print("os.error:", escape(str(msg)))
+ print("os.error:", html.escape(str(msg)))
else:
- print(escape(pwd))
+ print(html.escape(pwd))
print()
def print_arguments():
@@ -899,9 +1001,9 @@ environment as well. Here are some common variable names:
# =========
def escape(s, quote=None):
- '''Replace special characters "&", "<" and ">" to HTML-safe sequences.
- If the optional flag quote is true, the quotation mark character (")
- is also translated.'''
+ """Deprecated API."""
+ warn("cgi.escape is deprecated, use html.escape instead",
+ PendingDeprecationWarning, stacklevel=2)
s = s.replace("&", "&amp;") # Must be done first!
s = s.replace("<", "&lt;")
s = s.replace(">", "&gt;")
@@ -909,8 +1011,13 @@ def escape(s, quote=None):
s = s.replace('"', "&quot;")
return s
-def valid_boundary(s, _vb_pattern="^[ -~]{0,200}[!-~]$"):
+
+def valid_boundary(s, _vb_pattern=None):
import re
+ if isinstance(s, bytes):
+ _vb_pattern = b"^[ -~]{0,200}[!-~]$"
+ else:
+ _vb_pattern = "^[ -~]{0,200}[!-~]$"
return re.match(_vb_pattern, s)
# Invoke mainline
diff --git a/Lib/cmd.py b/Lib/cmd.py
index 10aa5ac..8fa7d01 100644
--- a/Lib/cmd.py
+++ b/Lib/cmd.py
@@ -84,7 +84,6 @@ class Cmd:
sys.stdin and sys.stdout are used.
"""
- import sys
if stdin is not None:
self.stdin = stdin
else:
@@ -278,21 +277,18 @@ class Cmd:
return None
def get_names(self):
- # Inheritance says we have to look in class and
- # base classes; order is not important.
- names = []
- classes = [self.__class__]
- while classes:
- aclass = classes.pop(0)
- if aclass.__bases__:
- classes = classes + list(aclass.__bases__)
- names = names + dir(aclass)
- return names
+ # This method used to pull in base class attributes
+ # at a time dir() didn't do it yet.
+ return dir(self.__class__)
def complete_help(self, *args):
- return self.completenames(*args)
+ commands = set(self.completenames(*args))
+ topics = set(a[5:] for a in self.get_names()
+ if a.startswith('help_' + args[0]))
+ return list(commands | topics)
def do_help(self, arg):
+ 'List available commands with "help" or detailed help with "help cmd".'
if arg:
# XXX check arg syntax
try:
diff --git a/Lib/code.py b/Lib/code.py
index 8962927..605aede 100644
--- a/Lib/code.py
+++ b/Lib/code.py
@@ -287,6 +287,5 @@ def interact(banner=None, readfunc=None, local=None):
console.interact(banner)
-if __name__ == '__main__':
- import pdb
- pdb.run("interact()\n")
+if __name__ == "__main__":
+ interact()
diff --git a/Lib/codecs.py b/Lib/codecs.py
index f6c2448..b150d64 100644
--- a/Lib/codecs.py
+++ b/Lib/codecs.py
@@ -396,6 +396,8 @@ class StreamWriter(Codec):
class StreamReader(Codec):
+ charbuffertype = str
+
def __init__(self, stream, errors='strict'):
""" Creates a StreamReader instance.
@@ -417,9 +419,8 @@ class StreamReader(Codec):
self.stream = stream
self.errors = errors
self.bytebuffer = b""
- # For str->str decoding this will stay a str
- # For str->unicode decoding the first read will promote it to unicode
- self.charbuffer = ""
+ self._empty_charbuffer = self.charbuffertype()
+ self.charbuffer = self._empty_charbuffer
self.linebuffer = None
def decode(self, input, errors='strict'):
@@ -455,7 +456,7 @@ class StreamReader(Codec):
"""
# If we have lines cached, first merge them back into characters
if self.linebuffer:
- self.charbuffer = "".join(self.linebuffer)
+ self.charbuffer = self._empty_charbuffer.join(self.linebuffer)
self.linebuffer = None
# read until we get the required number of characters (if available)
@@ -498,7 +499,7 @@ class StreamReader(Codec):
if chars < 0:
# Return everything we've got
result = self.charbuffer
- self.charbuffer = ""
+ self.charbuffer = self._empty_charbuffer
else:
# Return the first chars characters
result = self.charbuffer[:chars]
@@ -529,7 +530,7 @@ class StreamReader(Codec):
return line
readsize = size or 72
- line = ""
+ line = self._empty_charbuffer
# If size is given, we call read() only once
while True:
data = self.read(readsize, firstline=True)
@@ -537,7 +538,8 @@ class StreamReader(Codec):
# If we're at a "\r" read one extra character (which might
# be a "\n") to get a proper line ending. If the stream is
# temporarily exhausted we return the wrong line ending.
- if data.endswith("\r"):
+ if (isinstance(data, str) and data.endswith("\r")) or \
+ (isinstance(data, bytes) and data.endswith(b"\r")):
data += self.read(size=1, chars=1)
line += data
@@ -563,7 +565,8 @@ class StreamReader(Codec):
line0withoutend = lines[0].splitlines(False)[0]
if line0withend != line0withoutend: # We really have a line end
# Put the rest back together and keep it until the next call
- self.charbuffer = "".join(lines[1:]) + self.charbuffer
+ self.charbuffer = self._empty_charbuffer.join(lines[1:]) + \
+ self.charbuffer
if keepends:
line = line0withend
else:
@@ -574,7 +577,7 @@ class StreamReader(Codec):
if line and not keepends:
line = line.splitlines(False)[0]
break
- if readsize<8000:
+ if readsize < 8000:
readsize *= 2
return line
@@ -603,7 +606,7 @@ class StreamReader(Codec):
"""
self.bytebuffer = b""
- self.charbuffer = ""
+ self.charbuffer = self._empty_charbuffer
self.linebuffer = None
def seek(self, offset, whence=0):
diff --git a/Lib/collections.py b/Lib/collections.py
index 27bb5e1..98c4325 100644
--- a/Lib/collections.py
+++ b/Lib/collections.py
@@ -13,6 +13,7 @@ import sys as _sys
import heapq as _heapq
from weakref import proxy as _proxy
from itertools import repeat as _repeat, chain as _chain, starmap as _starmap
+from reprlib import recursive_repr as _recursive_repr
################################################################################
### OrderedDict
@@ -31,6 +32,7 @@ class OrderedDict(dict):
# The internal self.__map dictionary maps keys to links in a doubly linked list.
# The circular doubly linked list starts and ends with a sentinel element.
# The sentinel element never gets deleted (this simplifies the algorithm).
+ # The sentinel is stored in self.__hardroot with a weakref proxy in self.__root.
# The prev/next links are weakref proxies (to prevent circular references).
# Individual links are kept alive by the hard reference in self.__map.
# Those hard references disappear when a key is deleted from an OrderedDict.
@@ -43,42 +45,39 @@ class OrderedDict(dict):
'''
if len(args) > 1:
raise TypeError('expected at most 1 arguments, got %d' % len(args))
- self.__in_repr = False # detects recursive repr
try:
self.__root
except AttributeError:
- self.__root = root = _Link() # sentinel node for the doubly linked list
+ self.__hardroot = _Link()
+ self.__root = root = _proxy(self.__hardroot)
root.prev = root.next = root
self.__map = {}
self.__update(*args, **kwds)
- def clear(self):
- 'od.clear() -> None. Remove all items from od.'
- root = self.__root
- root.prev = root.next = root
- self.__map.clear()
- dict.clear(self)
-
- def __setitem__(self, key, value):
+ def __setitem__(self, key, value,
+ dict_setitem=dict.__setitem__, proxy=_proxy, Link=_Link):
'od.__setitem__(i, y) <==> od[i]=y'
# Setting a new item creates a new link which goes at the end of the linked
# list, and the inherited dictionary is updated with the new key/value pair.
if key not in self:
- self.__map[key] = link = _Link()
+ self.__map[key] = link = Link()
root = self.__root
last = root.prev
link.prev, link.next, link.key = last, root, key
- last.next = root.prev = _proxy(link)
- dict.__setitem__(self, key, value)
+ last.next = link
+ root.prev = proxy(link)
+ dict_setitem(self, key, value)
- def __delitem__(self, key):
+ def __delitem__(self, key, dict_delitem=dict.__delitem__):
'od.__delitem__(y) <==> del od[y]'
# Deleting an existing item uses self.__map to find the link which is
# then removed by updating the links in the predecessor and successor nodes.
- dict.__delitem__(self, key)
+ dict_delitem(self, key)
link = self.__map.pop(key)
- link.prev.next = link.next
- link.next.prev = link.prev
+ link_prev = link.prev
+ link_next = link.next
+ link_prev.next = link_next
+ link_next.prev = link_prev
def __iter__(self):
'od.__iter__() <==> iter(od)'
@@ -98,21 +97,85 @@ class OrderedDict(dict):
yield curr.key
curr = curr.prev
+ def clear(self):
+ 'od.clear() -> None. Remove all items from od.'
+ root = self.__root
+ root.prev = root.next = root
+ self.__map.clear()
+ dict.clear(self)
+
+ def popitem(self, last=True):
+ '''od.popitem() -> (k, v), return and remove a (key, value) pair.
+ Pairs are returned in LIFO order if last is true or FIFO order if false.
+
+ '''
+ if not self:
+ raise KeyError('dictionary is empty')
+ root = self.__root
+ if last:
+ link = root.prev
+ link_prev = link.prev
+ link_prev.next = root
+ root.prev = link_prev
+ else:
+ link = root.next
+ link_next = link.next
+ root.next = link_next
+ link_next.prev = root
+ key = link.key
+ del self.__map[key]
+ value = dict.pop(self, key)
+ return key, value
+
+ def move_to_end(self, key, last=True):
+ '''Move an existing element to the end (or beginning if last==False).
+
+ Raises KeyError if the element does not exist.
+ When last=True, acts like a fast version of self[key]=self.pop(key).
+
+ '''
+ link = self.__map[key]
+ link_prev = link.prev
+ link_next = link.next
+ link_prev.next = link_next
+ link_next.prev = link_prev
+ root = self.__root
+ if last:
+ last = root.prev
+ link.prev = last
+ link.next = root
+ last.next = root.prev = link
+ else:
+ first = root.next
+ link.prev = root
+ link.next = first
+ root.next = first.prev = link
+
def __reduce__(self):
'Return state information for pickling'
items = [[k, self[k]] for k in self]
- tmp = self.__map, self.__root, self.__in_repr
- del self.__map, self.__root, self.__in_repr
+ tmp = self.__map, self.__root, self.__hardroot
+ del self.__map, self.__root, self.__hardroot
inst_dict = vars(self).copy()
- self.__map, self.__root, self.__in_repr = tmp
+ self.__map, self.__root, self.__hardroot = tmp
if inst_dict:
return (self.__class__, (items,), inst_dict)
return self.__class__, (items,)
+ def __sizeof__(self):
+ sizeof = _sys.getsizeof
+ n = len(self) + 1 # number of links including root
+ size = sizeof(self.__dict__) # instance dictionary
+ size += sizeof(self.__map) * 2 # internal dict and inherited dict
+ size += sizeof(self.__hardroot) * n # link objects
+ size += sizeof(self.__root) * n # proxy objects
+ return size
+
update = __update = MutableMapping.update
keys = MutableMapping.keys
values = MutableMapping.values
items = MutableMapping.items
+ __ne__ = MutableMapping.__ne__
__marker = object()
@@ -126,35 +189,18 @@ class OrderedDict(dict):
return default
def setdefault(self, key, default=None):
- 'od.setdefault(k[,d]) -> od.get(k,d), also set od[k]=d if k not in od'
+ 'OD.setdefault(k[,d]) -> OD.get(k,d), also set OD[k]=d if k not in OD'
if key in self:
return self[key]
self[key] = default
return default
- def popitem(self, last=True):
- '''od.popitem() -> (k, v), return and remove a (key, value) pair.
- Pairs are returned in LIFO order if last is true or FIFO order if false.
-
- '''
- if not self:
- raise KeyError('dictionary is empty')
- key = next(reversed(self) if last else iter(self))
- value = self.pop(key)
- return key, value
-
+ @_recursive_repr()
def __repr__(self):
'od.__repr__() <==> repr(od)'
- if self.__in_repr:
- return '...'
if not self:
return '%s()' % (self.__class__.__name__,)
- self.__in_repr = True
- try:
- result = '%s(%r)' % (self.__class__.__name__, list(self.items()))
- finally:
- self.__in_repr = False
- return result
+ return '%s(%r)' % (self.__class__.__name__, list(self.items()))
def copy(self):
'od.copy() -> a shallow copy of od'
@@ -181,14 +227,6 @@ class OrderedDict(dict):
all(p==q for p, q in zip(self.items(), other.items()))
return dict.__eq__(self, other)
- def __ne__(self, other):
- '''od.__ne__(y) <==> od!=y. Comparison to another OD is order-sensitive
- while comparison to a regular mapping is order-insensitive.
-
- '''
- return not self == other
-
-
################################################################################
### namedtuple
@@ -257,6 +295,7 @@ def namedtuple(typename, field_names, verbose=False, rename=False):
__slots__ = () \n
_fields = %(field_names)r \n
def __new__(_cls, %(argtxt)s):
+ 'Create new instance of %(typename)s(%(argtxt)s)'
return _tuple.__new__(_cls, (%(argtxt)s)) \n
@classmethod
def _make(cls, iterable, new=tuple.__new__, len=len):
@@ -266,7 +305,8 @@ def namedtuple(typename, field_names, verbose=False, rename=False):
raise TypeError('Expected %(numfields)d arguments, got %%d' %% len(result))
return result \n
def __repr__(self):
- return '%(typename)s(%(reprtxt)s)' %% self \n
+ 'Return a nicely formatted representation string'
+ return self.__class__.__name__ + '(%(reprtxt)s)' %% self \n
def _asdict(self):
'Return a new OrderedDict which maps field names to their values'
return OrderedDict(zip(self._fields, self)) \n
@@ -277,9 +317,10 @@ def namedtuple(typename, field_names, verbose=False, rename=False):
raise ValueError('Got unexpected field names: %%r' %% kwds.keys())
return result \n
def __getnewargs__(self):
+ 'Return self as a plain tuple. Used by copy and pickle.'
return tuple(self) \n\n''' % locals()
for i, name in enumerate(field_names):
- template += ' %s = _property(_itemgetter(%d))\n' % (name, i)
+ template += " %s = _property(_itemgetter(%d), doc='Alias for field number %d')\n" % (name, i, i)
if verbose:
print(template)
@@ -290,7 +331,7 @@ def namedtuple(typename, field_names, verbose=False, rename=False):
try:
exec(template, namespace)
except SyntaxError as e:
- raise SyntaxError(e.msg + ':\n' + template) from e
+ raise SyntaxError(e.msg + ':\n\n' + template)
result = namespace[typename]
# For pickling to work, the __module__ variable needs to be set to the frame
@@ -309,6 +350,17 @@ def namedtuple(typename, field_names, verbose=False, rename=False):
### Counter
########################################################################
+def _count_elements(mapping, iterable):
+ 'Tally elements from the iterable.'
+ mapping_get = mapping.get
+ for elem in iterable:
+ mapping[elem] = mapping_get(elem, 0) + 1
+
+try: # Load C helper function if available
+ from _collections import _count_elements
+except ImportError:
+ pass
+
class Counter(dict):
'''Dict subclass for counting hashable items. Sometimes called a bag
or multiset. Elements are stored as dictionary keys and their counts
@@ -452,12 +504,37 @@ class Counter(dict):
else:
super().update(iterable) # fast path when counter is empty
else:
- self_get = self.get
- for elem in iterable:
- self[elem] = 1 + self_get(elem, 0)
+ _count_elements(self, iterable)
if kwds:
self.update(kwds)
+ def subtract(self, iterable=None, **kwds):
+ '''Like dict.update() but subtracts counts instead of replacing them.
+ Counts can be reduced below zero. Both the inputs and outputs are
+ allowed to contain zero and negative counts.
+
+ Source can be an iterable, a dictionary, or another Counter instance.
+
+ >>> c = Counter('which')
+ >>> c.subtract('witch') # subtract elements from another iterable
+ >>> c.subtract(Counter('watch')) # subtract elements from another counter
+ >>> c['h'] # 2 in which, minus 1 in witch, minus 1 in watch
+ 0
+ >>> c['w'] # 1 in which, minus 1 in witch, minus 1 in watch
+ -1
+
+ '''
+ if iterable is not None:
+ self_get = self.get
+ if isinstance(iterable, Mapping):
+ for elem, count in iterable.items():
+ self[elem] = self_get(elem, 0) - count
+ else:
+ for elem in iterable:
+ self[elem] = self_get(elem, 0) - 1
+ if kwds:
+ self.subtract(kwds)
+
def copy(self):
'Like dict.copy() but returns a Counter instance instead of a dict.'
return Counter(self)
@@ -554,6 +631,106 @@ class Counter(dict):
return result
+########################################################################
+### ChainMap (helper for configparser)
+########################################################################
+
+class _ChainMap(MutableMapping):
+ ''' A ChainMap groups multiple dicts (or other mappings) together
+ to create a single, updateable view.
+
+ The underlying mappings are stored in a list. That list is public and can
+ accessed or updated using the *maps* attribute. There is no other state.
+
+ Lookups search the underlying mappings successively until a key is found.
+ In contrast, writes, updates, and deletions only operate on the first
+ mapping.
+
+ '''
+
+ def __init__(self, *maps):
+ '''Initialize a ChainMap by setting *maps* to the given mappings.
+ If no mappings are provided, a single empty dictionary is used.
+
+ '''
+ self.maps = list(maps) or [{}] # always at least one map
+
+ def __missing__(self, key):
+ raise KeyError(key)
+
+ def __getitem__(self, key):
+ for mapping in self.maps:
+ try:
+ return mapping[key] # can't use 'key in mapping' with defaultdict
+ except KeyError:
+ pass
+ return self.__missing__(key) # support subclasses that define __missing__
+
+ def get(self, key, default=None):
+ return self[key] if key in self else default
+
+ def __len__(self):
+ return len(set().union(*self.maps)) # reuses stored hash values if possible
+
+ def __iter__(self):
+ return iter(set().union(*self.maps))
+
+ def __contains__(self, key):
+ return any(key in m for m in self.maps)
+
+ @_recursive_repr()
+ def __repr__(self):
+ return '{0.__class__.__name__}({1})'.format(
+ self, ', '.join(map(repr, self.maps)))
+
+ @classmethod
+ def fromkeys(cls, iterable, *args):
+ 'Create a ChainMap with a single dict created from the iterable.'
+ return cls(dict.fromkeys(iterable, *args))
+
+ def copy(self):
+ 'New ChainMap or subclass with a new copy of maps[0] and refs to maps[1:]'
+ return self.__class__(self.maps[0].copy(), *self.maps[1:])
+
+ __copy__ = copy
+
+ def new_child(self): # like Django's Context.push()
+ 'New ChainMap with a new dict followed by all previous maps.'
+ return self.__class__({}, *self.maps)
+
+ @property
+ def parents(self): # like Django's Context.pop()
+ 'New ChainMap from maps[1:].'
+ return self.__class__(*self.maps[1:])
+
+ def __setitem__(self, key, value):
+ self.maps[0][key] = value
+
+ def __delitem__(self, key):
+ try:
+ del self.maps[0][key]
+ except KeyError:
+ raise KeyError('Key not found in the first mapping: {!r}'.format(key))
+
+ def popitem(self):
+ 'Remove and return an item pair from maps[0]. Raise KeyError is maps[0] is empty.'
+ try:
+ return self.maps[0].popitem()
+ except KeyError:
+ raise KeyError('No keys found in the first mapping.')
+
+ def pop(self, key, *args):
+ 'Remove *key* from maps[0] and return its value. Raise KeyError if *key* not in maps[0].'
+ try:
+ return self.maps[0].pop(key, *args)
+ except KeyError:
+ raise KeyError('Key not found in the first mapping: {!r}'.format(key))
+
+ def clear(self):
+ 'Clear maps[0], leaving maps[1:] intact.'
+ self.maps[0].clear()
+
+
################################################################################
### UserDict
################################################################################
@@ -795,6 +972,8 @@ class UserString(Sequence):
new = new.data
return self.__class__(self.data.replace(old, new, maxsplit))
def rfind(self, sub, start=0, end=_sys.maxsize):
+ if isinstance(sub, UserString):
+ sub = sub.data
return self.data.rfind(sub, start, end)
def rindex(self, sub, start=0, end=_sys.maxsize):
return self.data.rindex(sub, start, end)
diff --git a/Lib/compileall.py b/Lib/compileall.py
index 2432842..d79a1bb 100644
--- a/Lib/compileall.py
+++ b/Lib/compileall.py
@@ -1,4 +1,4 @@
-"""Module/script to "compile" all .py files to .pyc (or .pyo) file.
+"""Module/script to byte-compile all .py files to .pyc (or .pyo) files.
When called as a script with arguments, this compiles the directories
given as arguments recursively; the -l option prevents it from
@@ -9,29 +9,30 @@ recursing into subdirectories. (Even though it should do so for
packages -- for now, you'll have to deal with packages separately.)
See module py_compile for details of the actual byte-compilation.
-
"""
import os
import sys
+import errno
+import imp
import py_compile
import struct
-import imp
-__all__ = ["compile_dir","compile_path"]
+__all__ = ["compile_dir","compile_file","compile_path"]
-def compile_dir(dir, maxlevels=10, ddir=None,
- force=0, rx=None, quiet=0):
+def compile_dir(dir, maxlevels=10, ddir=None, force=False, rx=None,
+ quiet=False, legacy=False, optimize=-1):
"""Byte-compile all modules in the given directory tree.
Arguments (only dir is required):
dir: the directory to byte-compile
maxlevels: maximum recursion level (default 10)
- ddir: if given, purported directory name (this is the
- directory name that will show up in error messages)
- force: if 1, force compilation, even if timestamps are up-to-date
- quiet: if 1, be quiet during compilation
-
+ ddir: the directory that will be prepended to the path to the
+ file as it is compiled into each byte-code file.
+ force: if True, force compilation, even if timestamps are up-to-date
+ quiet: if True, be quiet during compilation
+ legacy: if True, produce legacy pyc paths instead of PEP 3147 paths
+ optimize: optimization level or -1 for level of the interpreter
"""
if not quiet:
print('Listing', dir, '...')
@@ -43,74 +44,110 @@ def compile_dir(dir, maxlevels=10, ddir=None,
names.sort()
success = 1
for name in names:
+ if name == '__pycache__':
+ continue
fullname = os.path.join(dir, name)
if ddir is not None:
dfile = os.path.join(ddir, name)
else:
dfile = None
- if rx is not None:
- mo = rx.search(fullname)
- if mo:
- continue
- if os.path.isfile(fullname):
- head, tail = name[:-3], name[-3:]
- if tail == '.py':
- if not force:
- try:
- mtime = int(os.stat(fullname).st_mtime)
- expect = struct.pack('<4sl', imp.get_magic(), mtime)
- cfile = fullname + (__debug__ and 'c' or 'o')
- with open(cfile, 'rb') as chandle:
- actual = chandle.read(8)
- if expect == actual:
- continue
- except IOError:
- pass
- if not quiet:
- print('Compiling', fullname, '...')
+ if not os.path.isdir(fullname):
+ if not compile_file(fullname, ddir, force, rx, quiet,
+ legacy, optimize):
+ success = 0
+ elif (maxlevels > 0 and name != os.curdir and name != os.pardir and
+ os.path.isdir(fullname) and not os.path.islink(fullname)):
+ if not compile_dir(fullname, maxlevels - 1, dfile, force, rx,
+ quiet, legacy, optimize):
+ success = 0
+ return success
+
+def compile_file(fullname, ddir=None, force=False, rx=None, quiet=False,
+ legacy=False, optimize=-1):
+ """Byte-compile one file.
+
+ Arguments (only fullname is required):
+
+ fullname: the file to byte-compile
+ ddir: if given, the directory name compiled in to the
+ byte-code file.
+ force: if True, force compilation, even if timestamps are up-to-date
+ quiet: if True, be quiet during compilation
+ legacy: if True, produce legacy pyc paths instead of PEP 3147 paths
+ optimize: optimization level or -1 for level of the interpreter
+ """
+ success = 1
+ name = os.path.basename(fullname)
+ if ddir is not None:
+ dfile = os.path.join(ddir, name)
+ else:
+ dfile = None
+ if rx is not None:
+ mo = rx.search(fullname)
+ if mo:
+ return success
+ if os.path.isfile(fullname):
+ if legacy:
+ cfile = fullname + ('c' if __debug__ else 'o')
+ else:
+ if optimize >= 0:
+ cfile = imp.cache_from_source(fullname,
+ debug_override=not optimize)
+ else:
+ cfile = imp.cache_from_source(fullname)
+ cache_dir = os.path.dirname(cfile)
+ head, tail = name[:-3], name[-3:]
+ if tail == '.py':
+ if not force:
try:
- ok = py_compile.compile(fullname, None, dfile, True)
- except KeyboardInterrupt:
- raise KeyboardInterrupt
- except py_compile.PyCompileError as err:
- if quiet:
- print('*** Error compiling', fullname, '...')
- else:
- print('*** ', end='')
- # escape non-printable characters in msg
- msg = err.msg.encode(sys.stdout.encoding, 'backslashreplace')
- msg = msg.decode(sys.stdout.encoding)
- print(msg)
- success = 0
- except (SyntaxError, UnicodeError, IOError) as e:
- if quiet:
- print('*** Error compiling', fullname, '...')
- else:
- print('*** ', end='')
- print(e.__class__.__name__ + ':', e)
- success = 0
+ mtime = int(os.stat(fullname).st_mtime)
+ expect = struct.pack('<4sl', imp.get_magic(), mtime)
+ with open(cfile, 'rb') as chandle:
+ actual = chandle.read(8)
+ if expect == actual:
+ return success
+ except IOError:
+ pass
+ if not quiet:
+ print('Compiling', fullname, '...')
+ try:
+ ok = py_compile.compile(fullname, cfile, dfile, True,
+ optimize=optimize)
+ except py_compile.PyCompileError as err:
+ if quiet:
+ print('*** Error compiling', fullname, '...')
else:
- if ok == 0:
- success = 0
- elif maxlevels > 0 and \
- name != os.curdir and name != os.pardir and \
- os.path.isdir(fullname) and \
- not os.path.islink(fullname):
- if not compile_dir(fullname, maxlevels - 1, dfile, force, rx,
- quiet):
+ print('*** ', end='')
+ # escape non-printable characters in msg
+ msg = err.msg.encode(sys.stdout.encoding,
+ errors='backslashreplace')
+ msg = msg.decode(sys.stdout.encoding)
+ print(msg)
success = 0
+ except (SyntaxError, UnicodeError, IOError) as e:
+ if quiet:
+ print('*** Error compiling', fullname, '...')
+ else:
+ print('*** ', end='')
+ print(e.__class__.__name__ + ':', e)
+ success = 0
+ else:
+ if ok == 0:
+ success = 0
return success
-def compile_path(skip_curdir=1, maxlevels=0, force=0, quiet=0):
+def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=False,
+ legacy=False, optimize=-1):
"""Byte-compile all module on sys.path.
Arguments (all optional):
skip_curdir: if true, skip current directory (default true)
maxlevels: max recursion level (default 0)
- force: as for compile_dir() (default 0)
- quiet: as for compile_dir() (default 0)
-
+ force: as for compile_dir() (default False)
+ quiet: as for compile_dir() (default False)
+ legacy: as for compile_dir() (default False)
+ optimize: as for compile_dir() (default -1)
"""
success = 1
for dir in sys.path:
@@ -118,56 +155,85 @@ def compile_path(skip_curdir=1, maxlevels=0, force=0, quiet=0):
print('Skipping current directory')
else:
success = success and compile_dir(dir, maxlevels, None,
- force, quiet=quiet)
+ force, quiet=quiet,
+ legacy=legacy, optimize=optimize)
return success
+
def main():
"""Script main program."""
- import getopt
- try:
- opts, args = getopt.getopt(sys.argv[1:], 'lfqd:x:')
- except getopt.error as msg:
- print(msg)
- print("usage: python compileall.py [-l] [-f] [-q] [-d destdir] " \
- "[-x regexp] [directory ...]")
- print("-l: don't recurse down")
- print("-f: force rebuild even if timestamps are up-to-date")
- print("-q: quiet operation")
- print("-d destdir: purported directory name for error messages")
- print(" if no directory arguments, -l sys.path is assumed")
- print("-x regexp: skip files matching the regular expression regexp")
- print(" the regexp is searched for in the full path of the file")
- sys.exit(2)
- maxlevels = 10
- ddir = None
- force = 0
- quiet = 0
- rx = None
- for o, a in opts:
- if o == '-l': maxlevels = 0
- if o == '-d': ddir = a
- if o == '-f': force = 1
- if o == '-q': quiet = 1
- if o == '-x':
- import re
- rx = re.compile(a)
- if ddir:
- if len(args) != 1:
- print("-d destdir require exactly one directory argument")
- sys.exit(2)
- success = 1
+ import argparse
+
+ parser = argparse.ArgumentParser(
+ description='Utilities to support installing Python libraries.')
+ parser.add_argument('-l', action='store_const', const=0,
+ default=10, dest='maxlevels',
+ help="don't recurse into subdirectories")
+ parser.add_argument('-f', action='store_true', dest='force',
+ help='force rebuild even if timestamps are up to date')
+ parser.add_argument('-q', action='store_true', dest='quiet',
+ help='output only error messages')
+ parser.add_argument('-b', action='store_true', dest='legacy',
+ help='use legacy (pre-PEP3147) compiled file locations')
+ parser.add_argument('-d', metavar='DESTDIR', dest='ddir', default=None,
+ help=('directory to prepend to file paths for use in '
+ 'compile time tracebacks and in runtime '
+ 'tracebacks in cases where the source file is '
+ 'unavailable'))
+ parser.add_argument('-x', metavar='REGEXP', dest='rx', default=None,
+ help=('skip files matching the regular expression. '
+ 'The regexp is searched for in the full path '
+ 'to each file considered for compilation.'))
+ parser.add_argument('-i', metavar='FILE', dest='flist',
+ help=('add all the files and directories listed in '
+ 'FILE to the list considered for compilation. '
+ 'If "-", names are read from stdin.'))
+ parser.add_argument('compile_dest', metavar='FILE|DIR', nargs='*',
+ help=('zero or more file and directory names '
+ 'to compile; if no arguments given, defaults '
+ 'to the equivalent of -l sys.path'))
+ args = parser.parse_args()
+
+ compile_dests = args.compile_dest
+
+ if (args.ddir and (len(compile_dests) != 1
+ or not os.path.isdir(compile_dests[0]))):
+ parser.exit('-d destdir requires exactly one directory argument')
+ if args.rx:
+ import re
+ args.rx = re.compile(args.rx)
+
+ # if flist is provided then load it
+ if args.flist:
+ try:
+ with (sys.stdin if args.flist=='-' else open(args.flist)) as f:
+ for line in f:
+ compile_dests.append(line.strip())
+ except EnvironmentError:
+ print("Error reading file list {}".format(args.flist))
+ return False
+
+ success = True
try:
- if args:
- for dir in args:
- if not compile_dir(dir, maxlevels, ddir,
- force, rx, quiet):
- success = 0
+ if compile_dests:
+ for dest in compile_dests:
+ if os.path.isfile(dest):
+ if not compile_file(dest, args.ddir, args.force, args.rx,
+ args.quiet, args.legacy):
+ success = False
+ else:
+ if not compile_dir(dest, args.maxlevels, args.ddir,
+ args.force, args.rx, args.quiet,
+ args.legacy):
+ success = False
+ return success
else:
- success = compile_path()
+ return compile_path(legacy=args.legacy)
except KeyboardInterrupt:
- print("\n[interrupt]")
- success = 0
- return success
+ print("\n[interrupted]")
+ return False
+ return True
+
if __name__ == '__main__':
exit_status = int(not main())
diff --git a/Lib/concurrent/__init__.py b/Lib/concurrent/__init__.py
new file mode 100644
index 0000000..196d378
--- /dev/null
+++ b/Lib/concurrent/__init__.py
@@ -0,0 +1 @@
+# This directory is a Python package.
diff --git a/Lib/concurrent/futures/__init__.py b/Lib/concurrent/futures/__init__.py
new file mode 100644
index 0000000..b5231f8
--- /dev/null
+++ b/Lib/concurrent/futures/__init__.py
@@ -0,0 +1,18 @@
+# Copyright 2009 Brian Quinlan. All Rights Reserved.
+# Licensed to PSF under a Contributor Agreement.
+
+"""Execute computations asynchronously using threads or processes."""
+
+__author__ = 'Brian Quinlan (brian@sweetapp.com)'
+
+from concurrent.futures._base import (FIRST_COMPLETED,
+ FIRST_EXCEPTION,
+ ALL_COMPLETED,
+ CancelledError,
+ TimeoutError,
+ Future,
+ Executor,
+ wait,
+ as_completed)
+from concurrent.futures.process import ProcessPoolExecutor
+from concurrent.futures.thread import ThreadPoolExecutor
diff --git a/Lib/concurrent/futures/_base.py b/Lib/concurrent/futures/_base.py
new file mode 100644
index 0000000..79b91d4
--- /dev/null
+++ b/Lib/concurrent/futures/_base.py
@@ -0,0 +1,567 @@
+# Copyright 2009 Brian Quinlan. All Rights Reserved.
+# Licensed to PSF under a Contributor Agreement.
+
+__author__ = 'Brian Quinlan (brian@sweetapp.com)'
+
+import collections
+import functools
+import logging
+import threading
+import time
+
+FIRST_COMPLETED = 'FIRST_COMPLETED'
+FIRST_EXCEPTION = 'FIRST_EXCEPTION'
+ALL_COMPLETED = 'ALL_COMPLETED'
+_AS_COMPLETED = '_AS_COMPLETED'
+
+# Possible future states (for internal use by the futures package).
+PENDING = 'PENDING'
+RUNNING = 'RUNNING'
+# The future was cancelled by the user...
+CANCELLED = 'CANCELLED'
+# ...and _Waiter.add_cancelled() was called by a worker.
+CANCELLED_AND_NOTIFIED = 'CANCELLED_AND_NOTIFIED'
+FINISHED = 'FINISHED'
+
+_FUTURE_STATES = [
+ PENDING,
+ RUNNING,
+ CANCELLED,
+ CANCELLED_AND_NOTIFIED,
+ FINISHED
+]
+
+_STATE_TO_DESCRIPTION_MAP = {
+ PENDING: "pending",
+ RUNNING: "running",
+ CANCELLED: "cancelled",
+ CANCELLED_AND_NOTIFIED: "cancelled",
+ FINISHED: "finished"
+}
+
+# Logger for internal use by the futures package.
+LOGGER = logging.getLogger("concurrent.futures")
+
+class Error(Exception):
+ """Base class for all future-related exceptions."""
+ pass
+
+class CancelledError(Error):
+ """The Future was cancelled."""
+ pass
+
+class TimeoutError(Error):
+ """The operation exceeded the given deadline."""
+ pass
+
+class _Waiter(object):
+ """Provides the event that wait() and as_completed() block on."""
+ def __init__(self):
+ self.event = threading.Event()
+ self.finished_futures = []
+
+ def add_result(self, future):
+ self.finished_futures.append(future)
+
+ def add_exception(self, future):
+ self.finished_futures.append(future)
+
+ def add_cancelled(self, future):
+ self.finished_futures.append(future)
+
+class _AsCompletedWaiter(_Waiter):
+ """Used by as_completed()."""
+
+ def __init__(self):
+ super(_AsCompletedWaiter, self).__init__()
+ self.lock = threading.Lock()
+
+ def add_result(self, future):
+ with self.lock:
+ super(_AsCompletedWaiter, self).add_result(future)
+ self.event.set()
+
+ def add_exception(self, future):
+ with self.lock:
+ super(_AsCompletedWaiter, self).add_exception(future)
+ self.event.set()
+
+ def add_cancelled(self, future):
+ with self.lock:
+ super(_AsCompletedWaiter, self).add_cancelled(future)
+ self.event.set()
+
+class _FirstCompletedWaiter(_Waiter):
+ """Used by wait(return_when=FIRST_COMPLETED)."""
+
+ def add_result(self, future):
+ super().add_result(future)
+ self.event.set()
+
+ def add_exception(self, future):
+ super().add_exception(future)
+ self.event.set()
+
+ def add_cancelled(self, future):
+ super().add_cancelled(future)
+ self.event.set()
+
+class _AllCompletedWaiter(_Waiter):
+ """Used by wait(return_when=FIRST_EXCEPTION and ALL_COMPLETED)."""
+
+ def __init__(self, num_pending_calls, stop_on_exception):
+ self.num_pending_calls = num_pending_calls
+ self.stop_on_exception = stop_on_exception
+ super().__init__()
+
+ def _decrement_pending_calls(self):
+ self.num_pending_calls -= 1
+ if not self.num_pending_calls:
+ self.event.set()
+
+ def add_result(self, future):
+ super().add_result(future)
+ self._decrement_pending_calls()
+
+ def add_exception(self, future):
+ super().add_exception(future)
+ if self.stop_on_exception:
+ self.event.set()
+ else:
+ self._decrement_pending_calls()
+
+ def add_cancelled(self, future):
+ super().add_cancelled(future)
+ self._decrement_pending_calls()
+
+class _AcquireFutures(object):
+ """A context manager that does an ordered acquire of Future conditions."""
+
+ def __init__(self, futures):
+ self.futures = sorted(futures, key=id)
+
+ def __enter__(self):
+ for future in self.futures:
+ future._condition.acquire()
+
+ def __exit__(self, *args):
+ for future in self.futures:
+ future._condition.release()
+
+def _create_and_install_waiters(fs, return_when):
+ if return_when == _AS_COMPLETED:
+ waiter = _AsCompletedWaiter()
+ elif return_when == FIRST_COMPLETED:
+ waiter = _FirstCompletedWaiter()
+ else:
+ pending_count = sum(
+ f._state not in [CANCELLED_AND_NOTIFIED, FINISHED] for f in fs)
+
+ if return_when == FIRST_EXCEPTION:
+ waiter = _AllCompletedWaiter(pending_count, stop_on_exception=True)
+ elif return_when == ALL_COMPLETED:
+ waiter = _AllCompletedWaiter(pending_count, stop_on_exception=False)
+ else:
+ raise ValueError("Invalid return condition: %r" % return_when)
+
+ for f in fs:
+ f._waiters.append(waiter)
+
+ return waiter
+
+def as_completed(fs, timeout=None):
+ """An iterator over the given futures that yields each as it completes.
+
+ Args:
+ fs: The sequence of Futures (possibly created by different Executors) to
+ iterate over.
+ timeout: The maximum number of seconds to wait. If None, then there
+ is no limit on the wait time.
+
+ Returns:
+ An iterator that yields the given Futures as they complete (finished or
+ cancelled).
+
+ Raises:
+ TimeoutError: If the entire result iterator could not be generated
+ before the given timeout.
+ """
+ if timeout is not None:
+ end_time = timeout + time.time()
+
+ with _AcquireFutures(fs):
+ finished = set(
+ f for f in fs
+ if f._state in [CANCELLED_AND_NOTIFIED, FINISHED])
+ pending = set(fs) - finished
+ waiter = _create_and_install_waiters(fs, _AS_COMPLETED)
+
+ try:
+ for future in finished:
+ yield future
+
+ while pending:
+ if timeout is None:
+ wait_timeout = None
+ else:
+ wait_timeout = end_time - time.time()
+ if wait_timeout < 0:
+ raise TimeoutError(
+ '%d (of %d) futures unfinished' % (
+ len(pending), len(fs)))
+
+ waiter.event.wait(wait_timeout)
+
+ with waiter.lock:
+ finished = waiter.finished_futures
+ waiter.finished_futures = []
+ waiter.event.clear()
+
+ for future in finished:
+ yield future
+ pending.remove(future)
+
+ finally:
+ for f in fs:
+ f._waiters.remove(waiter)
+
+DoneAndNotDoneFutures = collections.namedtuple(
+ 'DoneAndNotDoneFutures', 'done not_done')
+def wait(fs, timeout=None, return_when=ALL_COMPLETED):
+ """Wait for the futures in the given sequence to complete.
+
+ Args:
+ fs: The sequence of Futures (possibly created by different Executors) to
+ wait upon.
+ timeout: The maximum number of seconds to wait. If None, then there
+ is no limit on the wait time.
+ return_when: Indicates when this function should return. The options
+ are:
+
+ FIRST_COMPLETED - Return when any future finishes or is
+ cancelled.
+ FIRST_EXCEPTION - Return when any future finishes by raising an
+ exception. If no future raises an exception
+ then it is equivalent to ALL_COMPLETED.
+ ALL_COMPLETED - Return when all futures finish or are cancelled.
+
+ Returns:
+ A named 2-tuple of sets. The first set, named 'done', contains the
+ futures that completed (is finished or cancelled) before the wait
+ completed. The second set, named 'not_done', contains uncompleted
+ futures.
+ """
+ with _AcquireFutures(fs):
+ done = set(f for f in fs
+ if f._state in [CANCELLED_AND_NOTIFIED, FINISHED])
+ not_done = set(fs) - done
+
+ if (return_when == FIRST_COMPLETED) and done:
+ return DoneAndNotDoneFutures(done, not_done)
+ elif (return_when == FIRST_EXCEPTION) and done:
+ if any(f for f in done
+ if not f.cancelled() and f.exception() is not None):
+ return DoneAndNotDoneFutures(done, not_done)
+
+ if len(done) == len(fs):
+ return DoneAndNotDoneFutures(done, not_done)
+
+ waiter = _create_and_install_waiters(fs, return_when)
+
+ waiter.event.wait(timeout)
+ for f in fs:
+ f._waiters.remove(waiter)
+
+ done.update(waiter.finished_futures)
+ return DoneAndNotDoneFutures(done, set(fs) - done)
+
+class Future(object):
+ """Represents the result of an asynchronous computation."""
+
+ def __init__(self):
+ """Initializes the future. Should not be called by clients."""
+ self._condition = threading.Condition()
+ self._state = PENDING
+ self._result = None
+ self._exception = None
+ self._waiters = []
+ self._done_callbacks = []
+
+ def _invoke_callbacks(self):
+ for callback in self._done_callbacks:
+ try:
+ callback(self)
+ except Exception:
+ LOGGER.exception('exception calling callback for %r', self)
+
+ def __repr__(self):
+ with self._condition:
+ if self._state == FINISHED:
+ if self._exception:
+ return '<Future at %s state=%s raised %s>' % (
+ hex(id(self)),
+ _STATE_TO_DESCRIPTION_MAP[self._state],
+ self._exception.__class__.__name__)
+ else:
+ return '<Future at %s state=%s returned %s>' % (
+ hex(id(self)),
+ _STATE_TO_DESCRIPTION_MAP[self._state],
+ self._result.__class__.__name__)
+ return '<Future at %s state=%s>' % (
+ hex(id(self)),
+ _STATE_TO_DESCRIPTION_MAP[self._state])
+
+ def cancel(self):
+ """Cancel the future if possible.
+
+ Returns True if the future was cancelled, False otherwise. A future
+ cannot be cancelled if it is running or has already completed.
+ """
+ with self._condition:
+ if self._state in [RUNNING, FINISHED]:
+ return False
+
+ if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
+ return True
+
+ self._state = CANCELLED
+ self._condition.notify_all()
+
+ self._invoke_callbacks()
+ return True
+
+ def cancelled(self):
+ """Return True if the future has cancelled."""
+ with self._condition:
+ return self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]
+
+ def running(self):
+ """Return True if the future is currently executing."""
+ with self._condition:
+ return self._state == RUNNING
+
+ def done(self):
+ """Return True of the future was cancelled or finished executing."""
+ with self._condition:
+ return self._state in [CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED]
+
+ def __get_result(self):
+ if self._exception:
+ raise self._exception
+ else:
+ return self._result
+
+ def add_done_callback(self, fn):
+ """Attaches a callable that will be called when the future finishes.
+
+ Args:
+ fn: A callable that will be called with this future as its only
+ argument when the future completes or is cancelled. The callable
+ will always be called by a thread in the same process in which
+ it was added. If the future has already completed or been
+ cancelled then the callable will be called immediately. These
+ callables are called in the order that they were added.
+ """
+ with self._condition:
+ if self._state not in [CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED]:
+ self._done_callbacks.append(fn)
+ return
+ fn(self)
+
+ def result(self, timeout=None):
+ """Return the result of the call that the future represents.
+
+ Args:
+ timeout: The number of seconds to wait for the result if the future
+ isn't done. If None, then there is no limit on the wait time.
+
+ Returns:
+ The result of the call that the future represents.
+
+ Raises:
+ CancelledError: If the future was cancelled.
+ TimeoutError: If the future didn't finish executing before the given
+ timeout.
+ Exception: If the call raised then that exception will be raised.
+ """
+ with self._condition:
+ if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
+ raise CancelledError()
+ elif self._state == FINISHED:
+ return self.__get_result()
+
+ self._condition.wait(timeout)
+
+ if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
+ raise CancelledError()
+ elif self._state == FINISHED:
+ return self.__get_result()
+ else:
+ raise TimeoutError()
+
+ def exception(self, timeout=None):
+ """Return the exception raised by the call that the future represents.
+
+ Args:
+ timeout: The number of seconds to wait for the exception if the
+ future isn't done. If None, then there is no limit on the wait
+ time.
+
+ Returns:
+ The exception raised by the call that the future represents or None
+ if the call completed without raising.
+
+ Raises:
+ CancelledError: If the future was cancelled.
+ TimeoutError: If the future didn't finish executing before the given
+ timeout.
+ """
+
+ with self._condition:
+ if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
+ raise CancelledError()
+ elif self._state == FINISHED:
+ return self._exception
+
+ self._condition.wait(timeout)
+
+ if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
+ raise CancelledError()
+ elif self._state == FINISHED:
+ return self._exception
+ else:
+ raise TimeoutError()
+
+ # The following methods should only be used by Executors and in tests.
+ def set_running_or_notify_cancel(self):
+ """Mark the future as running or process any cancel notifications.
+
+ Should only be used by Executor implementations and unit tests.
+
+ If the future has been cancelled (cancel() was called and returned
+ True) then any threads waiting on the future completing (though calls
+ to as_completed() or wait()) are notified and False is returned.
+
+ If the future was not cancelled then it is put in the running state
+ (future calls to running() will return True) and True is returned.
+
+ This method should be called by Executor implementations before
+ executing the work associated with this future. If this method returns
+ False then the work should not be executed.
+
+ Returns:
+ False if the Future was cancelled, True otherwise.
+
+ Raises:
+ RuntimeError: if this method was already called or if set_result()
+ or set_exception() was called.
+ """
+ with self._condition:
+ if self._state == CANCELLED:
+ self._state = CANCELLED_AND_NOTIFIED
+ for waiter in self._waiters:
+ waiter.add_cancelled(self)
+ # self._condition.notify_all() is not necessary because
+ # self.cancel() triggers a notification.
+ return False
+ elif self._state == PENDING:
+ self._state = RUNNING
+ return True
+ else:
+ LOGGER.critical('Future %s in unexpected state: %s',
+ id(self.future),
+ self.future._state)
+ raise RuntimeError('Future in unexpected state')
+
+ def set_result(self, result):
+ """Sets the return value of work associated with the future.
+
+ Should only be used by Executor implementations and unit tests.
+ """
+ with self._condition:
+ self._result = result
+ self._state = FINISHED
+ for waiter in self._waiters:
+ waiter.add_result(self)
+ self._condition.notify_all()
+ self._invoke_callbacks()
+
+ def set_exception(self, exception):
+ """Sets the result of the future as being the given exception.
+
+ Should only be used by Executor implementations and unit tests.
+ """
+ with self._condition:
+ self._exception = exception
+ self._state = FINISHED
+ for waiter in self._waiters:
+ waiter.add_exception(self)
+ self._condition.notify_all()
+ self._invoke_callbacks()
+
+class Executor(object):
+ """This is an abstract base class for concrete asynchronous executors."""
+
+ def submit(self, fn, *args, **kwargs):
+ """Submits a callable to be executed with the given arguments.
+
+ Schedules the callable to be executed as fn(*args, **kwargs) and returns
+ a Future instance representing the execution of the callable.
+
+ Returns:
+ A Future representing the given call.
+ """
+ raise NotImplementedError()
+
+ def map(self, fn, *iterables, timeout=None):
+ """Returns a iterator equivalent to map(fn, iter).
+
+ Args:
+ fn: A callable that will take take as many arguments as there are
+ passed iterables.
+ timeout: The maximum number of seconds to wait. If None, then there
+ is no limit on the wait time.
+
+ Returns:
+ An iterator equivalent to: map(func, *iterables) but the calls may
+ be evaluated out-of-order.
+
+ Raises:
+ TimeoutError: If the entire result iterator could not be generated
+ before the given timeout.
+ Exception: If fn(*args) raises for any values.
+ """
+ if timeout is not None:
+ end_time = timeout + time.time()
+
+ fs = [self.submit(fn, *args) for args in zip(*iterables)]
+
+ try:
+ for future in fs:
+ if timeout is None:
+ yield future.result()
+ else:
+ yield future.result(end_time - time.time())
+ finally:
+ for future in fs:
+ future.cancel()
+
+ def shutdown(self, wait=True):
+ """Clean-up the resources associated with the Executor.
+
+ It is safe to call this method several times. Otherwise, no other
+ methods can be called after this one.
+
+ Args:
+ wait: If True then shutdown will not return until all running
+ futures have finished executing and the resources used by the
+ executor have been reclaimed.
+ """
+ pass
+
+ def __enter__(self):
+ return self
+
+ def __exit__(self, exc_type, exc_val, exc_tb):
+ self.shutdown(wait=True)
+ return False
diff --git a/Lib/concurrent/futures/process.py b/Lib/concurrent/futures/process.py
new file mode 100644
index 0000000..79c60c3
--- /dev/null
+++ b/Lib/concurrent/futures/process.py
@@ -0,0 +1,363 @@
+# Copyright 2009 Brian Quinlan. All Rights Reserved.
+# Licensed to PSF under a Contributor Agreement.
+
+"""Implements ProcessPoolExecutor.
+
+The follow diagram and text describe the data-flow through the system:
+
+|======================= In-process =====================|== Out-of-process ==|
+
++----------+ +----------+ +--------+ +-----------+ +---------+
+| | => | Work Ids | => | | => | Call Q | => | |
+| | +----------+ | | +-----------+ | |
+| | | ... | | | | ... | | |
+| | | 6 | | | | 5, call() | | |
+| | | 7 | | | | ... | | |
+| Process | | ... | | Local | +-----------+ | Process |
+| Pool | +----------+ | Worker | | #1..n |
+| Executor | | Thread | | |
+| | +----------- + | | +-----------+ | |
+| | <=> | Work Items | <=> | | <= | Result Q | <= | |
+| | +------------+ | | +-----------+ | |
+| | | 6: call() | | | | ... | | |
+| | | future | | | | 4, result | | |
+| | | ... | | | | 3, except | | |
++----------+ +------------+ +--------+ +-----------+ +---------+
+
+Executor.submit() called:
+- creates a uniquely numbered _WorkItem and adds it to the "Work Items" dict
+- adds the id of the _WorkItem to the "Work Ids" queue
+
+Local worker thread:
+- reads work ids from the "Work Ids" queue and looks up the corresponding
+ WorkItem from the "Work Items" dict: if the work item has been cancelled then
+ it is simply removed from the dict, otherwise it is repackaged as a
+ _CallItem and put in the "Call Q". New _CallItems are put in the "Call Q"
+ until "Call Q" is full. NOTE: the size of the "Call Q" is kept small because
+ calls placed in the "Call Q" can no longer be cancelled with Future.cancel().
+- reads _ResultItems from "Result Q", updates the future stored in the
+ "Work Items" dict and deletes the dict entry
+
+Process #1..n:
+- reads _CallItems from "Call Q", executes the calls, and puts the resulting
+ _ResultItems in "Request Q"
+"""
+
+__author__ = 'Brian Quinlan (brian@sweetapp.com)'
+
+import atexit
+from concurrent.futures import _base
+import queue
+import multiprocessing
+import threading
+import weakref
+
+# Workers are created as daemon threads and processes. This is done to allow the
+# interpreter to exit when there are still idle processes in a
+# ProcessPoolExecutor's process pool (i.e. shutdown() was not called). However,
+# allowing workers to die with the interpreter has two undesirable properties:
+# - The workers would still be running during interpretor shutdown,
+# meaning that they would fail in unpredictable ways.
+# - The workers could be killed while evaluating a work item, which could
+# be bad if the callable being evaluated has external side-effects e.g.
+# writing to a file.
+#
+# To work around this problem, an exit handler is installed which tells the
+# workers to exit when their work queues are empty and then waits until the
+# threads/processes finish.
+
+_thread_references = set()
+_shutdown = False
+
+def _python_exit():
+ global _shutdown
+ _shutdown = True
+ for thread_reference in _thread_references:
+ thread = thread_reference()
+ if thread is not None:
+ thread.join()
+
+def _remove_dead_thread_references():
+ """Remove inactive threads from _thread_references.
+
+ Should be called periodically to prevent memory leaks in scenarios such as:
+ >>> while True:
+ >>> ... t = ThreadPoolExecutor(max_workers=5)
+ >>> ... t.map(int, ['1', '2', '3', '4', '5'])
+ """
+ for thread_reference in set(_thread_references):
+ if thread_reference() is None:
+ _thread_references.discard(thread_reference)
+
+# Controls how many more calls than processes will be queued in the call queue.
+# A smaller number will mean that processes spend more time idle waiting for
+# work while a larger number will make Future.cancel() succeed less frequently
+# (Futures in the call queue cannot be cancelled).
+EXTRA_QUEUED_CALLS = 1
+
+class _WorkItem(object):
+ def __init__(self, future, fn, args, kwargs):
+ self.future = future
+ self.fn = fn
+ self.args = args
+ self.kwargs = kwargs
+
+class _ResultItem(object):
+ def __init__(self, work_id, exception=None, result=None):
+ self.work_id = work_id
+ self.exception = exception
+ self.result = result
+
+class _CallItem(object):
+ def __init__(self, work_id, fn, args, kwargs):
+ self.work_id = work_id
+ self.fn = fn
+ self.args = args
+ self.kwargs = kwargs
+
+def _process_worker(call_queue, result_queue, shutdown):
+ """Evaluates calls from call_queue and places the results in result_queue.
+
+ This worker is run in a separate process.
+
+ Args:
+ call_queue: A multiprocessing.Queue of _CallItems that will be read and
+ evaluated by the worker.
+ result_queue: A multiprocessing.Queue of _ResultItems that will written
+ to by the worker.
+ shutdown: A multiprocessing.Event that will be set as a signal to the
+ worker that it should exit when call_queue is empty.
+ """
+ while True:
+ try:
+ call_item = call_queue.get(block=True, timeout=0.1)
+ except queue.Empty:
+ if shutdown.is_set():
+ return
+ else:
+ try:
+ r = call_item.fn(*call_item.args, **call_item.kwargs)
+ except BaseException as e:
+ result_queue.put(_ResultItem(call_item.work_id,
+ exception=e))
+ else:
+ result_queue.put(_ResultItem(call_item.work_id,
+ result=r))
+
+def _add_call_item_to_queue(pending_work_items,
+ work_ids,
+ call_queue):
+ """Fills call_queue with _WorkItems from pending_work_items.
+
+ This function never blocks.
+
+ Args:
+ pending_work_items: A dict mapping work ids to _WorkItems e.g.
+ {5: <_WorkItem...>, 6: <_WorkItem...>, ...}
+ work_ids: A queue.Queue of work ids e.g. Queue([5, 6, ...]). Work ids
+ are consumed and the corresponding _WorkItems from
+ pending_work_items are transformed into _CallItems and put in
+ call_queue.
+ call_queue: A multiprocessing.Queue that will be filled with _CallItems
+ derived from _WorkItems.
+ """
+ while True:
+ if call_queue.full():
+ return
+ try:
+ work_id = work_ids.get(block=False)
+ except queue.Empty:
+ return
+ else:
+ work_item = pending_work_items[work_id]
+
+ if work_item.future.set_running_or_notify_cancel():
+ call_queue.put(_CallItem(work_id,
+ work_item.fn,
+ work_item.args,
+ work_item.kwargs),
+ block=True)
+ else:
+ del pending_work_items[work_id]
+ continue
+
+def _queue_manangement_worker(executor_reference,
+ processes,
+ pending_work_items,
+ work_ids_queue,
+ call_queue,
+ result_queue,
+ shutdown_process_event):
+ """Manages the communication between this process and the worker processes.
+
+ This function is run in a local thread.
+
+ Args:
+ executor_reference: A weakref.ref to the ProcessPoolExecutor that owns
+ this thread. Used to determine if the ProcessPoolExecutor has been
+ garbage collected and that this function can exit.
+ process: A list of the multiprocessing.Process instances used as
+ workers.
+ pending_work_items: A dict mapping work ids to _WorkItems e.g.
+ {5: <_WorkItem...>, 6: <_WorkItem...>, ...}
+ work_ids_queue: A queue.Queue of work ids e.g. Queue([5, 6, ...]).
+ call_queue: A multiprocessing.Queue that will be filled with _CallItems
+ derived from _WorkItems for processing by the process workers.
+ result_queue: A multiprocessing.Queue of _ResultItems generated by the
+ process workers.
+ shutdown_process_event: A multiprocessing.Event used to signal the
+ process workers that they should exit when their work queue is
+ empty.
+ """
+ while True:
+ _add_call_item_to_queue(pending_work_items,
+ work_ids_queue,
+ call_queue)
+
+ try:
+ result_item = result_queue.get(block=True, timeout=0.1)
+ except queue.Empty:
+ executor = executor_reference()
+ # No more work items can be added if:
+ # - The interpreter is shutting down OR
+ # - The executor that owns this worker has been collected OR
+ # - The executor that owns this worker has been shutdown.
+ if _shutdown or executor is None or executor._shutdown_thread:
+ # Since no new work items can be added, it is safe to shutdown
+ # this thread if there are no pending work items.
+ if not pending_work_items:
+ shutdown_process_event.set()
+
+ # If .join() is not called on the created processes then
+ # some multiprocessing.Queue methods may deadlock on Mac OS
+ # X.
+ for p in processes:
+ p.join()
+ return
+ del executor
+ else:
+ work_item = pending_work_items[result_item.work_id]
+ del pending_work_items[result_item.work_id]
+
+ if result_item.exception:
+ work_item.future.set_exception(result_item.exception)
+ else:
+ work_item.future.set_result(result_item.result)
+
+_system_limits_checked = False
+_system_limited = None
+def _check_system_limits():
+ global _system_limits_checked, _system_limited
+ if _system_limits_checked:
+ if _system_limited:
+ raise NotImplementedError(_system_limited)
+ _system_limits_checked = True
+ try:
+ import os
+ nsems_max = os.sysconf("SC_SEM_NSEMS_MAX")
+ except (AttributeError, ValueError):
+ # sysconf not available or setting not available
+ return
+ if nsems_max == -1:
+ # indetermine limit, assume that limit is determined
+ # by available memory only
+ return
+ if nsems_max >= 256:
+ # minimum number of semaphores available
+ # according to POSIX
+ return
+ _system_limited = "system provides too few semaphores (%d available, 256 necessary)" % nsems_max
+ raise NotImplementedError(_system_limited)
+
+class ProcessPoolExecutor(_base.Executor):
+ def __init__(self, max_workers=None):
+ """Initializes a new ProcessPoolExecutor instance.
+
+ Args:
+ max_workers: The maximum number of processes that can be used to
+ execute the given calls. If None or not given then as many
+ worker processes will be created as the machine has processors.
+ """
+ _check_system_limits()
+ _remove_dead_thread_references()
+
+ if max_workers is None:
+ self._max_workers = multiprocessing.cpu_count()
+ else:
+ self._max_workers = max_workers
+
+ # Make the call queue slightly larger than the number of processes to
+ # prevent the worker processes from idling. But don't make it too big
+ # because futures in the call queue cannot be cancelled.
+ self._call_queue = multiprocessing.Queue(self._max_workers +
+ EXTRA_QUEUED_CALLS)
+ self._result_queue = multiprocessing.Queue()
+ self._work_ids = queue.Queue()
+ self._queue_management_thread = None
+ self._processes = set()
+
+ # Shutdown is a two-step process.
+ self._shutdown_thread = False
+ self._shutdown_process_event = multiprocessing.Event()
+ self._shutdown_lock = threading.Lock()
+ self._queue_count = 0
+ self._pending_work_items = {}
+
+ def _start_queue_management_thread(self):
+ if self._queue_management_thread is None:
+ self._queue_management_thread = threading.Thread(
+ target=_queue_manangement_worker,
+ args=(weakref.ref(self),
+ self._processes,
+ self._pending_work_items,
+ self._work_ids,
+ self._call_queue,
+ self._result_queue,
+ self._shutdown_process_event))
+ self._queue_management_thread.daemon = True
+ self._queue_management_thread.start()
+ _thread_references.add(weakref.ref(self._queue_management_thread))
+
+ def _adjust_process_count(self):
+ for _ in range(len(self._processes), self._max_workers):
+ p = multiprocessing.Process(
+ target=_process_worker,
+ args=(self._call_queue,
+ self._result_queue,
+ self._shutdown_process_event))
+ p.start()
+ self._processes.add(p)
+
+ def submit(self, fn, *args, **kwargs):
+ with self._shutdown_lock:
+ if self._shutdown_thread:
+ raise RuntimeError('cannot schedule new futures after shutdown')
+
+ f = _base.Future()
+ w = _WorkItem(f, fn, args, kwargs)
+
+ self._pending_work_items[self._queue_count] = w
+ self._work_ids.put(self._queue_count)
+ self._queue_count += 1
+
+ self._start_queue_management_thread()
+ self._adjust_process_count()
+ return f
+ submit.__doc__ = _base.Executor.submit.__doc__
+
+ def shutdown(self, wait=True):
+ with self._shutdown_lock:
+ self._shutdown_thread = True
+ if wait:
+ if self._queue_management_thread:
+ self._queue_management_thread.join()
+ # To reduce the risk of openning too many files, remove references to
+ # objects that use file descriptors.
+ self._queue_management_thread = None
+ self._call_queue = None
+ self._result_queue = None
+ self._shutdown_process_event = None
+ self._processes = None
+ shutdown.__doc__ = _base.Executor.shutdown.__doc__
+
+atexit.register(_python_exit)
diff --git a/Lib/concurrent/futures/thread.py b/Lib/concurrent/futures/thread.py
new file mode 100644
index 0000000..15736da
--- /dev/null
+++ b/Lib/concurrent/futures/thread.py
@@ -0,0 +1,136 @@
+# Copyright 2009 Brian Quinlan. All Rights Reserved.
+# Licensed to PSF under a Contributor Agreement.
+
+"""Implements ThreadPoolExecutor."""
+
+__author__ = 'Brian Quinlan (brian@sweetapp.com)'
+
+import atexit
+from concurrent.futures import _base
+import queue
+import threading
+import weakref
+
+# Workers are created as daemon threads. This is done to allow the interpreter
+# to exit when there are still idle threads in a ThreadPoolExecutor's thread
+# pool (i.e. shutdown() was not called). However, allowing workers to die with
+# the interpreter has two undesirable properties:
+# - The workers would still be running during interpretor shutdown,
+# meaning that they would fail in unpredictable ways.
+# - The workers could be killed while evaluating a work item, which could
+# be bad if the callable being evaluated has external side-effects e.g.
+# writing to a file.
+#
+# To work around this problem, an exit handler is installed which tells the
+# workers to exit when their work queues are empty and then waits until the
+# threads finish.
+
+_thread_references = set()
+_shutdown = False
+
+def _python_exit():
+ global _shutdown
+ _shutdown = True
+ for thread_reference in _thread_references:
+ thread = thread_reference()
+ if thread is not None:
+ thread.join()
+
+def _remove_dead_thread_references():
+ """Remove inactive threads from _thread_references.
+
+ Should be called periodically to prevent memory leaks in scenarios such as:
+ >>> while True:
+ ... t = ThreadPoolExecutor(max_workers=5)
+ ... t.map(int, ['1', '2', '3', '4', '5'])
+ """
+ for thread_reference in set(_thread_references):
+ if thread_reference() is None:
+ _thread_references.discard(thread_reference)
+
+atexit.register(_python_exit)
+
+class _WorkItem(object):
+ def __init__(self, future, fn, args, kwargs):
+ self.future = future
+ self.fn = fn
+ self.args = args
+ self.kwargs = kwargs
+
+ def run(self):
+ if not self.future.set_running_or_notify_cancel():
+ return
+
+ try:
+ result = self.fn(*self.args, **self.kwargs)
+ except BaseException as e:
+ self.future.set_exception(e)
+ else:
+ self.future.set_result(result)
+
+def _worker(executor_reference, work_queue):
+ try:
+ while True:
+ try:
+ work_item = work_queue.get(block=True, timeout=0.1)
+ except queue.Empty:
+ executor = executor_reference()
+ # Exit if:
+ # - The interpreter is shutting down OR
+ # - The executor that owns the worker has been collected OR
+ # - The executor that owns the worker has been shutdown.
+ if _shutdown or executor is None or executor._shutdown:
+ return
+ del executor
+ else:
+ work_item.run()
+ except BaseException as e:
+ _base.LOGGER.critical('Exception in worker', exc_info=True)
+
+class ThreadPoolExecutor(_base.Executor):
+ def __init__(self, max_workers):
+ """Initializes a new ThreadPoolExecutor instance.
+
+ Args:
+ max_workers: The maximum number of threads that can be used to
+ execute the given calls.
+ """
+ _remove_dead_thread_references()
+
+ self._max_workers = max_workers
+ self._work_queue = queue.Queue()
+ self._threads = set()
+ self._shutdown = False
+ self._shutdown_lock = threading.Lock()
+
+ def submit(self, fn, *args, **kwargs):
+ with self._shutdown_lock:
+ if self._shutdown:
+ raise RuntimeError('cannot schedule new futures after shutdown')
+
+ f = _base.Future()
+ w = _WorkItem(f, fn, args, kwargs)
+
+ self._work_queue.put(w)
+ self._adjust_thread_count()
+ return f
+ submit.__doc__ = _base.Executor.submit.__doc__
+
+ def _adjust_thread_count(self):
+ # TODO(bquinlan): Should avoid creating new threads if there are more
+ # idle threads than items in the work queue.
+ if len(self._threads) < self._max_workers:
+ t = threading.Thread(target=_worker,
+ args=(weakref.ref(self), self._work_queue))
+ t.daemon = True
+ t.start()
+ self._threads.add(t)
+ _thread_references.add(weakref.ref(t))
+
+ def shutdown(self, wait=True):
+ with self._shutdown_lock:
+ self._shutdown = True
+ if wait:
+ for t in self._threads:
+ t.join()
+ shutdown.__doc__ = _base.Executor.shutdown.__doc__
diff --git a/Lib/configparser.py b/Lib/configparser.py
index c7ae270..1bfdac8 100644
--- a/Lib/configparser.py
+++ b/Lib/configparser.py
@@ -1,102 +1,134 @@
"""Configuration file parser.
-A setup file consists of sections, lead by a "[section]" header,
+A configuration file consists of sections, lead by a "[section]" header,
and followed by "name: value" entries, with continuations and such in
the style of RFC 822.
-The option values can contain format strings which refer to other values in
-the same section, or values in a special [DEFAULT] section.
-
-For example:
-
- something: %(dir)s/whatever
-
-would resolve the "%(dir)s" to the value of dir. All reference
-expansions are done late, on demand.
-
Intrinsic defaults can be specified by passing them into the
ConfigParser constructor as a dictionary.
class:
ConfigParser -- responsible for parsing a list of
- configuration files, and managing the parsed database.
+ configuration files, and managing the parsed database.
methods:
- __init__(defaults=None)
- create the parser and specify a dictionary of intrinsic defaults. The
- keys must be strings, the values must be appropriate for %()s string
- interpolation. Note that `__name__' is always an intrinsic default;
- its value is the section's name.
+ __init__(defaults=None, dict_type=_default_dict, allow_no_value=False,
+ delimiters=('=', ':'), comment_prefixes=('#', ';'),
+ inline_comment_prefixes=None, strict=True,
+ empty_lines_in_values=True):
+ Create the parser. When `defaults' is given, it is initialized into the
+ dictionary or intrinsic defaults. The keys must be strings, the values
+ must be appropriate for %()s string interpolation.
+
+ When `dict_type' is given, it will be used to create the dictionary
+ objects for the list of sections, for the options within a section, and
+ for the default values.
+
+ When `delimiters' is given, it will be used as the set of substrings
+ that divide keys from values.
+
+ When `comment_prefixes' is given, it will be used as the set of
+ substrings that prefix comments in empty lines. Comments can be
+ indented.
+
+ When `inline_comment_prefixes' is given, it will be used as the set of
+ substrings that prefix comments in non-empty lines.
+
+ When `strict` is True, the parser won't allow for any section or option
+ duplicates while reading from a single source (file, string or
+ dictionary). Default is True.
+
+ When `empty_lines_in_values' is False (default: True), each empty line
+ marks the end of an option. Otherwise, internal empty lines of
+ a multiline option are kept as part of the value.
+
+ When `allow_no_value' is True (default: False), options without
+ values are accepted; the value presented for these is None.
sections()
- return all the configuration section names, sans DEFAULT
+ Return all the configuration section names, sans DEFAULT.
has_section(section)
- return whether the given section exists
+ Return whether the given section exists.
has_option(section, option)
- return whether the given option exists in the given section
+ Return whether the given option exists in the given section.
options(section)
- return list of configuration options for the named section
+ Return list of configuration options for the named section.
- read(filenames)
- read and parse the list of named configuration files, given by
+ read(filenames, encoding=None)
+ Read and parse the list of named configuration files, given by
name. A single filename is also allowed. Non-existing files
are ignored. Return list of successfully read files.
- readfp(fp, filename=None)
- read and parse one configuration file, given as a file object.
- The filename defaults to fp.name; it is only used in error
- messages (if fp has no `name' attribute, the string `<???>' is used).
+ read_file(f, filename=None)
+ Read and parse one configuration file, given as a file object.
+ The filename defaults to f.name; it is only used in error
+ messages (if f has no `name' attribute, the string `<???>' is used).
+
+ read_string(string)
+ Read configuration from a given string.
- get(section, option, raw=False, vars=None)
- return a string value for the named option. All % interpolations are
+ read_dict(dictionary)
+ Read configuration from a dictionary. Keys are section names,
+ values are dictionaries with keys and values that should be present
+ in the section. If the used dictionary type preserves order, sections
+ and their keys will be added in order. Values are automatically
+ converted to strings.
+
+ get(section, option, raw=False, vars=None, fallback=_UNSET)
+ Return a string value for the named option. All % interpolations are
expanded in the return values, based on the defaults passed into the
constructor and the DEFAULT section. Additional substitutions may be
provided using the `vars' argument, which must be a dictionary whose
- contents override any pre-existing defaults.
+ contents override any pre-existing defaults. If `option' is a key in
+ `vars', the value from `vars' is used.
- getint(section, options)
- like get(), but convert value to an integer
+ getint(section, options, raw=False, vars=None, fallback=_UNSET)
+ Like get(), but convert value to an integer.
- getfloat(section, options)
- like get(), but convert value to a float
+ getfloat(section, options, raw=False, vars=None, fallback=_UNSET)
+ Like get(), but convert value to a float.
- getboolean(section, options)
- like get(), but convert value to a boolean (currently case
+ getboolean(section, options, raw=False, vars=None, fallback=_UNSET)
+ Like get(), but convert value to a boolean (currently case
insensitively defined as 0, false, no, off for False, and 1, true,
yes, on for True). Returns False or True.
- items(section, raw=False, vars=None)
+ items(section=_UNSET, raw=False, vars=None)
+ If section is given, return a list of tuples with (section_name,
+ section_proxy) for each section, including DEFAULTSECT. Otherwise,
return a list of tuples with (name, value) for each option
in the section.
remove_section(section)
- remove the given file section and all its options
+ Remove the given file section and all its options.
remove_option(section, option)
- remove the given option from the given section
+ Remove the given option from the given section.
set(section, option, value)
- set the given option
+ Set the given option.
- write(fp)
- write the configuration state in .ini format
+ write(fp, space_around_delimiters=True)
+ Write the configuration state in .ini format. If
+ `space_around_delimiters' is True (the default), delimiters
+ between keys and values are surrounded by spaces.
"""
-try:
- from collections import OrderedDict as _default_dict
-except ImportError:
- # fallback for setup.py which hasn't yet built _collections
- _default_dict = dict
-
+from collections import MutableMapping, OrderedDict as _default_dict, _ChainMap
+import functools
+import io
+import itertools
import re
+import sys
+import warnings
-__all__ = ["NoSectionError", "DuplicateSectionError", "NoOptionError",
- "InterpolationError", "InterpolationDepthError",
+__all__ = ["NoSectionError", "DuplicateOptionError", "DuplicateSectionError",
+ "NoOptionError", "InterpolationError", "InterpolationDepthError",
"InterpolationSyntaxError", "ParsingError",
"MissingSectionHeaderError",
"ConfigParser", "SafeConfigParser", "RawConfigParser",
@@ -114,12 +146,14 @@ class Error(Exception):
def _get_message(self):
"""Getter for 'message'; needed only to override deprecation in
- BaseException."""
+ BaseException.
+ """
return self.__message
def _set_message(self, value):
"""Setter for 'message'; needed only to override deprecation in
- BaseException."""
+ BaseException.
+ """
self.__message = value
# BaseException.message has been deprecated since Python 2.6. To prevent
@@ -136,19 +170,68 @@ class Error(Exception):
__str__ = __repr__
+
class NoSectionError(Error):
"""Raised when no section matches a requested option."""
def __init__(self, section):
Error.__init__(self, 'No section: %r' % (section,))
self.section = section
+ self.args = (section, )
+
class DuplicateSectionError(Error):
- """Raised when a section is multiply-created."""
+ """Raised when a section is repeated in an input source.
- def __init__(self, section):
- Error.__init__(self, "Section %r already exists" % section)
+ Possible repetitions that raise this exception are: multiple creation
+ using the API or in strict parsers when a section is found more than once
+ in a single input file, string or dictionary.
+ """
+
+ def __init__(self, section, source=None, lineno=None):
+ msg = [repr(section), " already exists"]
+ if source is not None:
+ message = ["While reading from ", source]
+ if lineno is not None:
+ message.append(" [line {0:2d}]".format(lineno))
+ message.append(": section ")
+ message.extend(msg)
+ msg = message
+ else:
+ msg.insert(0, "Section ")
+ Error.__init__(self, "".join(msg))
self.section = section
+ self.source = source
+ self.lineno = lineno
+ self.args = (section, source, lineno)
+
+
+class DuplicateOptionError(Error):
+ """Raised by strict parsers when an option is repeated in an input source.
+
+ Current implementation raises this exception only when an option is found
+ more than once in a single file, string or dictionary.
+ """
+
+ def __init__(self, section, option, source=None, lineno=None):
+ msg = [repr(option), " in section ", repr(section),
+ " already exists"]
+ if source is not None:
+ message = ["While reading from ", source]
+ if lineno is not None:
+ message.append(" [line {0:2d}]".format(lineno))
+ message.append(": option ")
+ message.extend(msg)
+ msg = message
+ else:
+ msg.insert(0, "Option ")
+ Error.__init__(self, "".join(msg))
+ self.section = section
+ self.option = option
+ self.source = source
+ self.lineno = lineno
+ self.args = (section, option, source, lineno)
+
class NoOptionError(Error):
"""A requested option was not found."""
@@ -158,6 +241,8 @@ class NoOptionError(Error):
(option, section))
self.option = option
self.section = section
+ self.args = (option, section)
+
class InterpolationError(Error):
"""Base class for interpolation-related exceptions."""
@@ -166,6 +251,8 @@ class InterpolationError(Error):
Error.__init__(self, msg)
self.option = option
self.section = section
+ self.args = (option, section, msg)
+
class InterpolationMissingOptionError(InterpolationError):
"""A string substitution required a setting which was not available."""
@@ -179,10 +266,16 @@ class InterpolationMissingOptionError(InterpolationError):
% (section, option, reference, rawval))
InterpolationError.__init__(self, option, section, msg)
self.reference = reference
+ self.args = (option, section, rawval, reference)
+
class InterpolationSyntaxError(InterpolationError):
- """Raised when the source text into which substitutions are made
- does not conform to the required syntax."""
+ """Raised when the source text contains invalid syntax.
+
+ Current implementation raises this exception when the source text into
+ which substitutions are made does not conform to the required syntax.
+ """
+
class InterpolationDepthError(InterpolationError):
"""Raised when substitutions are nested too deeply."""
@@ -194,19 +287,52 @@ class InterpolationDepthError(InterpolationError):
"\trawval : %s\n"
% (section, option, rawval))
InterpolationError.__init__(self, option, section, msg)
+ self.args = (option, section, rawval)
+
class ParsingError(Error):
"""Raised when a configuration file does not follow legal syntax."""
- def __init__(self, filename):
- Error.__init__(self, 'File contains parsing errors: %s' % filename)
- self.filename = filename
+ def __init__(self, source=None, filename=None):
+ # Exactly one of `source'/`filename' arguments has to be given.
+ # `filename' kept for compatibility.
+ if filename and source:
+ raise ValueError("Cannot specify both `filename' and `source'. "
+ "Use `source'.")
+ elif not filename and not source:
+ raise ValueError("Required argument `source' not given.")
+ elif filename:
+ source = filename
+ Error.__init__(self, 'Source contains parsing errors: %s' % source)
+ self.source = source
self.errors = []
+ self.args = (source, )
+
+ @property
+ def filename(self):
+ """Deprecated, use `source'."""
+ warnings.warn(
+ "The 'filename' attribute will be removed in future versions. "
+ "Use 'source' instead.",
+ DeprecationWarning, stacklevel=2
+ )
+ return self.source
+
+ @filename.setter
+ def filename(self, value):
+ """Deprecated, user `source'."""
+ warnings.warn(
+ "The 'filename' attribute will be removed in future versions. "
+ "Use 'source' instead.",
+ DeprecationWarning, stacklevel=2
+ )
+ self.source = value
def append(self, lineno, line):
self.errors.append((lineno, line))
self.message += '\n\t[line %2d]: %s' % (lineno, line)
+
class MissingSectionHeaderError(ParsingError):
"""Raised when a key-value pair is found before any section header."""
@@ -215,19 +341,293 @@ class MissingSectionHeaderError(ParsingError):
self,
'File contains no section headers.\nfile: %s, line: %d\n%r' %
(filename, lineno, line))
- self.filename = filename
+ self.source = filename
self.lineno = lineno
self.line = line
+ self.args = (filename, lineno, line)
+
+
+# Used in parser getters to indicate the default behaviour when a specific
+# option is not found it to raise an exception. Created to enable `None' as
+# a valid fallback value.
+_UNSET = object()
+
+
+class Interpolation:
+ """Dummy interpolation that passes the value through with no changes."""
+ def before_get(self, parser, section, option, value, defaults):
+ return value
+
+ def before_set(self, parser, section, option, value):
+ return value
+
+ def before_read(self, parser, section, option, value):
+ return value
+
+ def before_write(self, parser, section, option, value):
+ return value
+
+
+class BasicInterpolation(Interpolation):
+ """Interpolation as implemented in the classic ConfigParser.
+
+ The option values can contain format strings which refer to other values in
+ the same section, or values in the special default section.
+
+ For example:
+
+ something: %(dir)s/whatever
+
+ would resolve the "%(dir)s" to the value of dir. All reference
+ expansions are done late, on demand. If a user needs to use a bare % in
+ a configuration file, she can escape it by writing %%. Other other % usage
+ is considered a user error and raises `InterpolationSyntaxError'."""
+
+ _KEYCRE = re.compile(r"%\(([^)]+)\)s")
+
+ def before_get(self, parser, section, option, value, defaults):
+ L = []
+ self._interpolate_some(parser, option, L, value, section, defaults, 1)
+ return ''.join(L)
+
+ def before_set(self, parser, section, option, value):
+ tmp_value = value.replace('%%', '') # escaped percent signs
+ tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax
+ if '%' in tmp_value:
+ raise ValueError("invalid interpolation syntax in %r at "
+ "position %d" % (value, tmp_value.find('%')))
+ return value
+
+ def _interpolate_some(self, parser, option, accum, rest, section, map,
+ depth):
+ if depth > MAX_INTERPOLATION_DEPTH:
+ raise InterpolationDepthError(option, section, rest)
+ while rest:
+ p = rest.find("%")
+ if p < 0:
+ accum.append(rest)
+ return
+ if p > 0:
+ accum.append(rest[:p])
+ rest = rest[p:]
+ # p is no longer used
+ c = rest[1:2]
+ if c == "%":
+ accum.append("%")
+ rest = rest[2:]
+ elif c == "(":
+ m = self._KEYCRE.match(rest)
+ if m is None:
+ raise InterpolationSyntaxError(option, section,
+ "bad interpolation variable reference %r" % rest)
+ var = parser.optionxform(m.group(1))
+ rest = rest[m.end():]
+ try:
+ v = map[var]
+ except KeyError:
+ raise InterpolationMissingOptionError(
+ option, section, rest, var)
+ if "%" in v:
+ self._interpolate_some(parser, option, accum, v,
+ section, map, depth + 1)
+ else:
+ accum.append(v)
+ else:
+ raise InterpolationSyntaxError(
+ option, section,
+ "'%%' must be followed by '%%' or '(', "
+ "found: %r" % (rest,))
+
+
+class ExtendedInterpolation(Interpolation):
+ """Advanced variant of interpolation, supports the syntax used by
+ `zc.buildout'. Enables interpolation between sections."""
+
+ _KEYCRE = re.compile(r"\$\{([^}]+)\}")
+
+ def before_get(self, parser, section, option, value, defaults):
+ L = []
+ self._interpolate_some(parser, option, L, value, section, defaults, 1)
+ return ''.join(L)
+
+ def before_set(self, parser, section, option, value):
+ tmp_value = value.replace('$$', '') # escaped dollar signs
+ tmp_value = self._KEYCRE.sub('', tmp_value) # valid syntax
+ if '$' in tmp_value:
+ raise ValueError("invalid interpolation syntax in %r at "
+ "position %d" % (value, tmp_value.find('%')))
+ return value
+
+ def _interpolate_some(self, parser, option, accum, rest, section, map,
+ depth):
+ if depth > MAX_INTERPOLATION_DEPTH:
+ raise InterpolationDepthError(option, section, rest)
+ while rest:
+ p = rest.find("$")
+ if p < 0:
+ accum.append(rest)
+ return
+ if p > 0:
+ accum.append(rest[:p])
+ rest = rest[p:]
+ # p is no longer used
+ c = rest[1:2]
+ if c == "$":
+ accum.append("$")
+ rest = rest[2:]
+ elif c == "{":
+ m = self._KEYCRE.match(rest)
+ if m is None:
+ raise InterpolationSyntaxError(option, section,
+ "bad interpolation variable reference %r" % rest)
+ path = parser.optionxform(m.group(1)).split(':')
+ rest = rest[m.end():]
+ sect = section
+ opt = option
+ try:
+ if len(path) == 1:
+ opt = path[0]
+ v = map[opt]
+ elif len(path) == 2:
+ sect = path[0]
+ opt = path[1]
+ v = parser.get(sect, opt, raw=True)
+ else:
+ raise InterpolationSyntaxError(
+ option, section,
+ "More than one ':' found: %r" % (rest,))
+ except (KeyError, NoSectionError, NoOptionError):
+ raise InterpolationMissingOptionError(
+ option, section, rest, ":".join(path))
+ if "$" in v:
+ self._interpolate_some(parser, opt, accum, v, sect,
+ dict(parser.items(sect, raw=True)),
+ depth + 1)
+ else:
+ accum.append(v)
+ else:
+ raise InterpolationSyntaxError(
+ option, section,
+ "'$' must be followed by '$' or '{', "
+ "found: %r" % (rest,))
+
+
+class LegacyInterpolation(Interpolation):
+ """Deprecated interpolation used in old versions of ConfigParser.
+ Use BasicInterpolation or ExtendedInterpolation instead."""
+
+ _KEYCRE = re.compile(r"%\(([^)]*)\)s|.")
+
+ def before_get(self, parser, section, option, value, vars):
+ rawval = value
+ depth = MAX_INTERPOLATION_DEPTH
+ while depth: # Loop through this until it's done
+ depth -= 1
+ if value and "%(" in value:
+ replace = functools.partial(self._interpolation_replace,
+ parser=parser)
+ value = self._KEYCRE.sub(replace, value)
+ try:
+ value = value % vars
+ except KeyError as e:
+ raise InterpolationMissingOptionError(
+ option, section, rawval, e.args[0])
+ else:
+ break
+ if value and "%(" in value:
+ raise InterpolationDepthError(option, section, rawval)
+ return value
+
+ def before_set(self, parser, section, option, value):
+ return value
+
+ @staticmethod
+ def _interpolation_replace(match, parser):
+ s = match.group(1)
+ if s is None:
+ return match.group()
+ else:
+ return "%%(%s)s" % parser.optionxform(s)
+
+
+class RawConfigParser(MutableMapping):
+ """ConfigParser that does not do interpolation."""
+
+ # Regular expressions for parsing section headers and options
+ _SECT_TMPL = r"""
+ \[ # [
+ (?P<header>[^]]+) # very permissive!
+ \] # ]
+ """
+ _OPT_TMPL = r"""
+ (?P<option>.*?) # very permissive!
+ \s*(?P<vi>{delim})\s* # any number of space/tab,
+ # followed by any of the
+ # allowed delimiters,
+ # followed by any space/tab
+ (?P<value>.*)$ # everything up to eol
+ """
+ _OPT_NV_TMPL = r"""
+ (?P<option>.*?) # very permissive!
+ \s*(?: # any number of space/tab,
+ (?P<vi>{delim})\s* # optionally followed by
+ # any of the allowed
+ # delimiters, followed by any
+ # space/tab
+ (?P<value>.*))?$ # everything up to eol
+ """
+ # Interpolation algorithm to be used if the user does not specify another
+ _DEFAULT_INTERPOLATION = Interpolation()
+ # Compiled regular expression for matching sections
+ SECTCRE = re.compile(_SECT_TMPL, re.VERBOSE)
+ # Compiled regular expression for matching options with typical separators
+ OPTCRE = re.compile(_OPT_TMPL.format(delim="=|:"), re.VERBOSE)
+ # Compiled regular expression for matching options with optional values
+ # delimited using typical separators
+ OPTCRE_NV = re.compile(_OPT_NV_TMPL.format(delim="=|:"), re.VERBOSE)
+ # Compiled regular expression for matching leading whitespace in a line
+ NONSPACECRE = re.compile(r"\S")
+ # Possible boolean values in the configuration.
+ BOOLEAN_STATES = {'1': True, 'yes': True, 'true': True, 'on': True,
+ '0': False, 'no': False, 'false': False, 'off': False}
+
+ def __init__(self, defaults=None, dict_type=_default_dict,
+ allow_no_value=False, *, delimiters=('=', ':'),
+ comment_prefixes=('#', ';'), inline_comment_prefixes=None,
+ strict=True, empty_lines_in_values=True,
+ default_section=DEFAULTSECT,
+ interpolation=_UNSET):
-class RawConfigParser:
- def __init__(self, defaults=None, dict_type=_default_dict):
self._dict = dict_type
self._sections = self._dict()
self._defaults = self._dict()
+ self._proxies = self._dict()
+ self._proxies[default_section] = SectionProxy(self, default_section)
if defaults:
for key, value in defaults.items():
self._defaults[self.optionxform(key)] = value
+ self._delimiters = tuple(delimiters)
+ if delimiters == ('=', ':'):
+ self._optcre = self.OPTCRE_NV if allow_no_value else self.OPTCRE
+ else:
+ d = "|".join(re.escape(d) for d in delimiters)
+ if allow_no_value:
+ self._optcre = re.compile(self._OPT_NV_TMPL.format(delim=d),
+ re.VERBOSE)
+ else:
+ self._optcre = re.compile(self._OPT_TMPL.format(delim=d),
+ re.VERBOSE)
+ self._comment_prefixes = tuple(comment_prefixes or ())
+ self._inline_comment_prefixes = tuple(inline_comment_prefixes or ())
+ self._strict = strict
+ self._allow_no_value = allow_no_value
+ self._empty_lines_in_values = empty_lines_in_values
+ if interpolation is _UNSET:
+ self._interpolation = self._DEFAULT_INTERPOLATION
+ else:
+ self._interpolation = interpolation
+ self.default_section=default_section
def defaults(self):
return self._defaults
@@ -241,15 +641,15 @@ class RawConfigParser:
"""Create a new section in the configuration.
Raise DuplicateSectionError if a section by the specified name
- already exists. Raise ValueError if name is DEFAULT or any of it's
- case-insensitive variants.
+ already exists. Raise ValueError if name is DEFAULT.
"""
- if section.lower() == "default":
- raise ValueError('Invalid section name: %s' % section)
+ if section == self.default_section:
+ raise ValueError('Invalid section name: %r' % section)
if section in self._sections:
raise DuplicateSectionError(section)
self._sections[section] = self._dict()
+ self._proxies[section] = SectionProxy(self, section)
def has_section(self, section):
"""Indicate whether the named section is present in the configuration.
@@ -265,11 +665,9 @@ class RawConfigParser:
except KeyError:
raise NoSectionError(section)
opts.update(self._defaults)
- if '__name__' in opts:
- del opts['__name__']
return list(opts.keys())
- def read(self, filenames):
+ def read(self, filenames, encoding=None):
"""Read and parse a filename or a list of filenames.
Files that cannot be opened are silently ignored; this is
@@ -286,83 +684,181 @@ class RawConfigParser:
read_ok = []
for filename in filenames:
try:
- fp = open(filename)
+ with open(filename, encoding=encoding) as fp:
+ self._read(fp, filename)
except IOError:
continue
- self._read(fp, filename)
- fp.close()
read_ok.append(filename)
return read_ok
- def readfp(self, fp, filename=None):
+ def read_file(self, f, source=None):
"""Like read() but the argument must be a file-like object.
- The `fp' argument must have a `readline' method. Optional
- second argument is the `filename', which if not given, is
- taken from fp.name. If fp has no `name' attribute, `<???>' is
- used.
-
+ The `f' argument must have a `readline' method. Optional second
+ argument is the `source' specifying the name of the file being read. If
+ not given, it is taken from f.name. If `f' has no `name' attribute,
+ `<???>' is used.
"""
- if filename is None:
+ if source is None:
try:
- filename = fp.name
+ source = f.name
except AttributeError:
- filename = '<???>'
- self._read(fp, filename)
+ source = '<???>'
+ self._read(f, source)
- def get(self, section, option):
- opt = self.optionxform(option)
- if section not in self._sections:
- if section != DEFAULTSECT:
- raise NoSectionError(section)
- if opt in self._defaults:
- return self._defaults[opt]
+ def read_string(self, string, source='<string>'):
+ """Read configuration from a given string."""
+ sfile = io.StringIO(string)
+ self.read_file(sfile, source)
+
+ def read_dict(self, dictionary, source='<dict>'):
+ """Read configuration from a dictionary.
+
+ Keys are section names, values are dictionaries with keys and values
+ that should be present in the section. If the used dictionary type
+ preserves order, sections and their keys will be added in order.
+
+ All types held in the dictionary are converted to strings during
+ reading, including section names, option names and keys.
+
+ Optional second argument is the `source' specifying the name of the
+ dictionary being read.
+ """
+ elements_added = set()
+ for section, keys in dictionary.items():
+ section = str(section)
+ try:
+ self.add_section(section)
+ except (DuplicateSectionError, ValueError):
+ if self._strict and section in elements_added:
+ raise
+ elements_added.add(section)
+ for key, value in keys.items():
+ key = self.optionxform(str(key))
+ if value is not None:
+ value = str(value)
+ if self._strict and (section, key) in elements_added:
+ raise DuplicateOptionError(section, key, source)
+ elements_added.add((section, key))
+ self.set(section, key, value)
+
+ def readfp(self, fp, filename=None):
+ """Deprecated, use read_file instead."""
+ warnings.warn(
+ "This method will be removed in future versions. "
+ "Use 'parser.read_file()' instead.",
+ DeprecationWarning, stacklevel=2
+ )
+ self.read_file(fp, source=filename)
+
+ def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET):
+ """Get an option value for a given section.
+
+ If `vars' is provided, it must be a dictionary. The option is looked up
+ in `vars' (if provided), `section', and in `DEFAULTSECT' in that order.
+ If the key is not found and `fallback' is provided, it is used as
+ a fallback value. `None' can be provided as a `fallback' value.
+
+ If interpolation is enabled and the optional argument `raw' is False,
+ all interpolations are expanded in the return values.
+
+ Arguments `raw', `vars', and `fallback' are keyword only.
+
+ The section DEFAULT is special.
+ """
+ try:
+ d = self._unify_values(section, vars)
+ except NoSectionError:
+ if fallback is _UNSET:
+ raise
else:
+ return fallback
+ option = self.optionxform(option)
+ try:
+ value = d[option]
+ except KeyError:
+ if fallback is _UNSET:
raise NoOptionError(option, section)
- elif opt in self._sections[section]:
- return self._sections[section][opt]
- elif opt in self._defaults:
- return self._defaults[opt]
+ else:
+ return fallback
+
+ if raw or value is None:
+ return value
else:
- raise NoOptionError(option, section)
+ return self._interpolation.before_get(self, section, option, value,
+ d)
- def items(self, section):
+ def _get(self, section, conv, option, **kwargs):
+ return conv(self.get(section, option, **kwargs))
+
+ def getint(self, section, option, *, raw=False, vars=None,
+ fallback=_UNSET):
try:
- d2 = self._sections[section]
- except KeyError:
- if section != DEFAULTSECT:
- raise NoSectionError(section)
- d2 = self._dict()
- d = self._defaults.copy()
- d.update(d2)
- if "__name__" in d:
- del d["__name__"]
- return d.items()
+ return self._get(section, int, option, raw=raw, vars=vars)
+ except (NoSectionError, NoOptionError):
+ if fallback is _UNSET:
+ raise
+ else:
+ return fallback
- def _get(self, section, conv, option):
- return conv(self.get(section, option))
+ def getfloat(self, section, option, *, raw=False, vars=None,
+ fallback=_UNSET):
+ try:
+ return self._get(section, float, option, raw=raw, vars=vars)
+ except (NoSectionError, NoOptionError):
+ if fallback is _UNSET:
+ raise
+ else:
+ return fallback
- def getint(self, section, option):
- return self._get(section, int, option)
+ def getboolean(self, section, option, *, raw=False, vars=None,
+ fallback=_UNSET):
+ try:
+ return self._get(section, self._convert_to_boolean, option,
+ raw=raw, vars=vars)
+ except (NoSectionError, NoOptionError):
+ if fallback is _UNSET:
+ raise
+ else:
+ return fallback
- def getfloat(self, section, option):
- return self._get(section, float, option)
+ def items(self, section=_UNSET, raw=False, vars=None):
+ """Return a list of (name, value) tuples for each option in a section.
- _boolean_states = {'1': True, 'yes': True, 'true': True, 'on': True,
- '0': False, 'no': False, 'false': False, 'off': False}
+ All % interpolations are expanded in the return values, based on the
+ defaults passed into the constructor, unless the optional argument
+ `raw' is true. Additional substitutions may be provided using the
+ `vars' argument, which must be a dictionary whose contents overrides
+ any pre-existing defaults.
- def getboolean(self, section, option):
- v = self.get(section, option)
- if v.lower() not in self._boolean_states:
- raise ValueError('Not a boolean: %s' % v)
- return self._boolean_states[v.lower()]
+ The section DEFAULT is special.
+ """
+ if section is _UNSET:
+ return super().items()
+ d = self._defaults.copy()
+ try:
+ d.update(self._sections[section])
+ except KeyError:
+ if section != self.default_section:
+ raise NoSectionError(section)
+ # Update with the entry specific variables
+ if vars:
+ for key, value in vars.items():
+ d[self.optionxform(key)] = value
+ value_getter = lambda option: self._interpolation.before_get(self,
+ section, option, d[option], d)
+ if raw:
+ value_getter = lambda option: d[option]
+ return [(option, value_getter(option)) for option in d.keys()]
def optionxform(self, optionstr):
return optionstr.lower()
def has_option(self, section, option):
- """Check for the existence of a given option in a given section."""
- if not section or section == DEFAULTSECT:
+ """Check for the existence of a given option in a given section.
+ If the specified `section' is None or an empty string, DEFAULT is
+ assumed. If the specified `section' does not exist, returns False."""
+ if not section or section == self.default_section:
option = self.optionxform(option)
return option in self._defaults
elif section not in self._sections:
@@ -372,9 +868,12 @@ class RawConfigParser:
return (option in self._sections[section]
or option in self._defaults)
- def set(self, section, option, value):
+ def set(self, section, option, value=None):
"""Set an option."""
- if not section or section == DEFAULTSECT:
+ if value:
+ value = self._interpolation.before_set(self, section, option,
+ value)
+ if not section or section == self.default_section:
sectdict = self._defaults
else:
try:
@@ -383,24 +882,39 @@ class RawConfigParser:
raise NoSectionError(section)
sectdict[self.optionxform(option)] = value
- def write(self, fp):
- """Write an .ini-format representation of the configuration state."""
+ def write(self, fp, space_around_delimiters=True):
+ """Write an .ini-format representation of the configuration state.
+
+ If `space_around_delimiters' is True (the default), delimiters
+ between keys and values are surrounded by spaces.
+ """
+ if space_around_delimiters:
+ d = " {} ".format(self._delimiters[0])
+ else:
+ d = self._delimiters[0]
if self._defaults:
- fp.write("[%s]\n" % DEFAULTSECT)
- for (key, value) in self._defaults.items():
- fp.write("%s = %s\n" % (key, str(value).replace('\n', '\n\t')))
- fp.write("\n")
+ self._write_section(fp, self.default_section,
+ self._defaults.items(), d)
for section in self._sections:
- fp.write("[%s]\n" % section)
- for (key, value) in self._sections[section].items():
- if key != "__name__":
- fp.write("%s = %s\n" %
- (key, str(value).replace('\n', '\n\t')))
- fp.write("\n")
+ self._write_section(fp, section,
+ self._sections[section].items(), d)
+
+ def _write_section(self, fp, section_name, section_items, delimiter):
+ """Write a single section to the specified `fp'."""
+ fp.write("[{}]\n".format(section_name))
+ for key, value in section_items:
+ value = self._interpolation.before_write(self, section_name, key,
+ value)
+ if value is not None or not self._allow_no_value:
+ value = delimiter + str(value).replace('\n', '\n\t')
+ else:
+ value = ""
+ fp.write("{}{}\n".format(key, value))
+ fp.write("\n")
def remove_option(self, section, option):
"""Remove an option."""
- if not section or section == DEFAULTSECT:
+ if not section or section == self.default_section:
sectdict = self._defaults
else:
try:
@@ -418,69 +932,117 @@ class RawConfigParser:
existed = section in self._sections
if existed:
del self._sections[section]
+ del self._proxies[section]
return existed
- #
- # Regular expressions for parsing section headers and options.
- #
- SECTCRE = re.compile(
- r'\[' # [
- r'(?P<header>[^]]+)' # very permissive!
- r'\]' # ]
- )
- OPTCRE = re.compile(
- r'(?P<option>[^:=\s][^:=]*)' # very permissive!
- r'\s*(?P<vi>[:=])\s*' # any number of space/tab,
- # followed by separator
- # (either : or =), followed
- # by any # space/tab
- r'(?P<value>.*)$' # everything up to eol
- )
+ def __getitem__(self, key):
+ if key != self.default_section and not self.has_section(key):
+ raise KeyError(key)
+ return self._proxies[key]
+
+ def __setitem__(self, key, value):
+ # To conform with the mapping protocol, overwrites existing values in
+ # the section.
+
+ # XXX this is not atomic if read_dict fails at any point. Then again,
+ # no update method in configparser is atomic in this implementation.
+ self.remove_section(key)
+ self.read_dict({key: value})
+
+ def __delitem__(self, key):
+ if key == self.default_section:
+ raise ValueError("Cannot remove the default section.")
+ if not self.has_section(key):
+ raise KeyError(key)
+ self.remove_section(key)
+
+ def __contains__(self, key):
+ return key == self.default_section or self.has_section(key)
+
+ def __len__(self):
+ return len(self._sections) + 1 # the default section
+
+ def __iter__(self):
+ # XXX does it break when underlying container state changed?
+ return itertools.chain((self.default_section,), self._sections.keys())
def _read(self, fp, fpname):
- """Parse a sectioned setup file.
-
- The sections in setup file contains a title line at the top,
- indicated by a name in square brackets (`[]'), plus key/value
- options lines, indicated by `name: value' format lines.
- Continuations are represented by an embedded newline then
- leading whitespace. Blank lines, lines beginning with a '#',
- and just about everything else are ignored.
+ """Parse a sectioned configuration file.
+
+ Each section in a configuration file contains a header, indicated by
+ a name in square brackets (`[]'), plus key/value options, indicated by
+ `name' and `value' delimited with a specific substring (`=' or `:' by
+ default).
+
+ Values can span multiple lines, as long as they are indented deeper
+ than the first line of the value. Depending on the parser's mode, blank
+ lines may be treated as parts of multiline values or ignored.
+
+ Configuration files may include comments, prefixed by specific
+ characters (`#' and `;' by default). Comments may appear on their own
+ in an otherwise empty line or may be entered in lines holding values or
+ section names.
"""
- cursect = None # None, or a dictionary
+ elements_added = set()
+ cursect = None # None, or a dictionary
+ sectname = None
optname = None
lineno = 0
- e = None # None, or an exception
- while True:
- line = fp.readline()
- if not line:
- break
- lineno = lineno + 1
- # comment or blank line?
- if line.strip() == '' or line[0] in '#;':
- continue
- if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
- # no leading whitespace
+ indent_level = 0
+ e = None # None, or an exception
+ for lineno, line in enumerate(fp, start=1):
+ comment_start = None
+ # strip inline comments
+ for prefix in self._inline_comment_prefixes:
+ index = line.find(prefix)
+ if index == 0 or (index > 0 and line[index-1].isspace()):
+ comment_start = index
+ break
+ # strip full line comments
+ for prefix in self._comment_prefixes:
+ if line.strip().startswith(prefix):
+ comment_start = 0
+ break
+ value = line[:comment_start].strip()
+ if not value:
+ if self._empty_lines_in_values:
+ # add empty line to the value, but only if there was no
+ # comment on the line
+ if (comment_start is None and
+ cursect is not None and
+ optname and
+ cursect[optname] is not None):
+ cursect[optname].append('') # newlines added at join
+ else:
+ # empty line marks end of value
+ indent_level = sys.maxsize
continue
# continuation line?
- if line[0].isspace() and cursect is not None and optname:
- value = line.strip()
- if value:
- cursect[optname] = "%s\n%s" % (cursect[optname], value)
+ first_nonspace = self.NONSPACECRE.search(line)
+ cur_indent_level = first_nonspace.start() if first_nonspace else 0
+ if (cursect is not None and optname and
+ cur_indent_level > indent_level):
+ cursect[optname].append(value)
# a section header or option header?
else:
+ indent_level = cur_indent_level
# is it a section header?
- mo = self.SECTCRE.match(line)
+ mo = self.SECTCRE.match(value)
if mo:
sectname = mo.group('header')
if sectname in self._sections:
+ if self._strict and sectname in elements_added:
+ raise DuplicateSectionError(sectname, fpname,
+ lineno)
cursect = self._sections[sectname]
- elif sectname == DEFAULTSECT:
+ elements_added.add(sectname)
+ elif sectname == self.default_section:
cursect = self._defaults
else:
cursect = self._dict()
- cursect['__name__'] = sectname
self._sections[sectname] = cursect
+ self._proxies[sectname] = SectionProxy(self, sectname)
+ elements_added.add(sectname)
# So sections can't start with a continuation line
optname = None
# no section header in the file?
@@ -488,253 +1050,197 @@ class RawConfigParser:
raise MissingSectionHeaderError(fpname, lineno, line)
# an option line?
else:
- mo = self.OPTCRE.match(line)
+ mo = self._optcre.match(value)
if mo:
optname, vi, optval = mo.group('option', 'vi', 'value')
- if vi in ('=', ':') and ';' in optval:
- # ';' is a comment delimiter only if it follows
- # a spacing character
- pos = optval.find(';')
- if pos != -1 and optval[pos-1].isspace():
- optval = optval[:pos]
- optval = optval.strip()
- # allow empty values
- if optval == '""':
- optval = ''
+ if not optname:
+ e = self._handle_error(e, fpname, lineno, line)
optname = self.optionxform(optname.rstrip())
- cursect[optname] = optval
+ if (self._strict and
+ (sectname, optname) in elements_added):
+ raise DuplicateOptionError(sectname, optname,
+ fpname, lineno)
+ elements_added.add((sectname, optname))
+ # This check is fine because the OPTCRE cannot
+ # match if it would set optval to None
+ if optval is not None:
+ optval = optval.strip()
+ cursect[optname] = [optval]
+ else:
+ # valueless option handling
+ cursect[optname] = None
else:
- # a non-fatal parsing error occurred. set up the
+ # a non-fatal parsing error occurred. set up the
# exception but keep going. the exception will be
# raised at the end of the file and will contain a
# list of all bogus lines
- if not e:
- e = ParsingError(fpname)
- e.append(lineno, repr(line))
+ e = self._handle_error(e, fpname, lineno, line)
# if any parsing errors occurred, raise an exception
if e:
raise e
+ self._join_multiline_values()
+
+ def _join_multiline_values(self):
+ defaults = self.default_section, self._defaults
+ all_sections = itertools.chain((defaults,),
+ self._sections.items())
+ for section, options in all_sections:
+ for name, val in options.items():
+ if isinstance(val, list):
+ val = '\n'.join(val).rstrip()
+ options[name] = self._interpolation.before_read(self,
+ section,
+ name, val)
+
+ def _handle_error(self, exc, fpname, lineno, line):
+ if not exc:
+ exc = ParsingError(fpname)
+ exc.append(lineno, repr(line))
+ return exc
+
+ def _unify_values(self, section, vars):
+ """Create a sequence of lookups with 'vars' taking priority over
+ the 'section' which takes priority over the DEFAULTSECT.
-class _Chainmap:
- """Combine multiple mappings for successive lookups.
-
- For example, to emulate Python's normal lookup sequence:
-
- import __builtin__
- pylookup = _Chainmap(locals(), globals(), vars(__builtin__))
- """
-
- def __init__(self, *maps):
- self.maps = maps
-
- def __getitem__(self, key):
- for mapping in self.maps:
- try:
- return mapping[key]
- except KeyError:
- pass
- raise KeyError(key)
-
- def __iter__(self):
- seen = set()
- for mapping in self.maps:
- s = set(mapping) - seen
- for elem in s:
- yield elem
- seen.update(s)
-
- def __len__(self):
- s = set()
- s.update(*self.maps)
- return len(s)
-
- def get(self, key, default=None):
+ """
+ sectiondict = {}
try:
- return self[key]
+ sectiondict = self._sections[section]
except KeyError:
- return default
+ if section != self.default_section:
+ raise NoSectionError(section)
+ # Update with the entry specific variables
+ vardict = {}
+ if vars:
+ for key, value in vars.items():
+ if value is not None:
+ value = str(value)
+ vardict[self.optionxform(key)] = value
+ return _ChainMap(vardict, sectiondict, self._defaults)
- def __contains__(self, key):
- try:
- self[key]
- except KeyError:
- return False
- else:
- return True
+ def _convert_to_boolean(self, value):
+ """Return a boolean value translating from other types if necessary.
+ """
+ if value.lower() not in self.BOOLEAN_STATES:
+ raise ValueError('Not a boolean: %s' % value)
+ return self.BOOLEAN_STATES[value.lower()]
+
+ def _validate_value_types(self, *, section="", option="", value=""):
+ """Raises a TypeError for non-string values.
+
+ The only legal non-string value if we allow valueless
+ options is None, so we need to check if the value is a
+ string if:
+ - we do not allow valueless options, or
+ - we allow valueless options but the value is not None
+
+ For compatibility reasons this method is not used in classic set()
+ for RawConfigParsers. It is invoked in every case for mapping protocol
+ access and in ConfigParser.set().
+ """
+ if not isinstance(section, str):
+ raise TypeError("section names must be strings")
+ if not isinstance(option, str):
+ raise TypeError("option keys must be strings")
+ if not self._allow_no_value or value:
+ if not isinstance(value, str):
+ raise TypeError("option values must be strings")
- def keys(self):
- return list(self)
- def items(self):
- return [(k, self[k]) for k in self]
+class ConfigParser(RawConfigParser):
+ """ConfigParser implementing interpolation."""
- def values(self):
- return [self[k] for k in self]
+ _DEFAULT_INTERPOLATION = BasicInterpolation()
- def __eq__(self, other):
- return dict(self.items()) == dict(other.items())
+ def set(self, section, option, value=None):
+ """Set an option. Extends RawConfigParser.set by validating type and
+ interpolation syntax on the value."""
+ self._validate_value_types(option=option, value=value)
+ super().set(section, option, value)
- def __ne__(self, other):
- return not (self == other)
+ def add_section(self, section):
+ """Create a new section in the configuration. Extends
+ RawConfigParser.add_section by validating if the section name is
+ a string."""
+ self._validate_value_types(section=section)
+ super().add_section(section)
-class ConfigParser(RawConfigParser):
+class SafeConfigParser(ConfigParser):
+ """ConfigParser alias for backwards compatibility purposes."""
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ warnings.warn(
+ "The SafeConfigParser class has been renamed to ConfigParser "
+ "in Python 3.2. This alias will be removed in future versions."
+ " Use ConfigParser directly instead.",
+ DeprecationWarning, stacklevel=2
+ )
- def get(self, section, option, raw=False, vars=None):
- """Get an option value for a given section.
- If `vars' is provided, it must be a dictionary. The option is looked up
- in `vars' (if provided), `section', and in `defaults' in that order.
+class SectionProxy(MutableMapping):
+ """A proxy for a single section from a parser."""
- All % interpolations are expanded in the return values, unless the
- optional argument `raw' is true. Values for interpolation keys are
- looked up in the same manner as the option.
+ def __init__(self, parser, name):
+ """Creates a view on a section of the specified `name` in `parser`."""
+ self._parser = parser
+ self._name = name
- The section DEFAULT is special.
- """
- sectiondict = {}
- try:
- sectiondict = self._sections[section]
- except KeyError:
- if section != DEFAULTSECT:
- raise NoSectionError(section)
- # Update with the entry specific variables
- vardict = {}
- if vars:
- for key, value in vars.items():
- vardict[self.optionxform(key)] = value
- d = _Chainmap(vardict, sectiondict, self._defaults)
- option = self.optionxform(option)
- try:
- value = d[option]
- except KeyError:
- raise NoOptionError(option, section)
+ def __repr__(self):
+ return '<Section: {}>'.format(self._name)
- if raw:
- return value
- else:
- return self._interpolate(section, option, value, d)
+ def __getitem__(self, key):
+ if not self._parser.has_option(self._name, key):
+ raise KeyError(key)
+ return self._parser.get(self._name, key)
- def items(self, section, raw=False, vars=None):
- """Return a list of tuples with (name, value) for each option
- in the section.
+ def __setitem__(self, key, value):
+ self._parser._validate_value_types(option=key, value=value)
+ return self._parser.set(self._name, key, value)
- All % interpolations are expanded in the return values, based on the
- defaults passed into the constructor, unless the optional argument
- `raw' is true. Additional substitutions may be provided using the
- `vars' argument, which must be a dictionary whose contents overrides
- any pre-existing defaults.
+ def __delitem__(self, key):
+ if not (self._parser.has_option(self._name, key) and
+ self._parser.remove_option(self._name, key)):
+ raise KeyError(key)
- The section DEFAULT is special.
- """
- d = self._defaults.copy()
- try:
- d.update(self._sections[section])
- except KeyError:
- if section != DEFAULTSECT:
- raise NoSectionError(section)
- # Update with the entry specific variables
- if vars:
- for key, value in vars.items():
- d[self.optionxform(key)] = value
- options = list(d.keys())
- if "__name__" in options:
- options.remove("__name__")
- if raw:
- return [(option, d[option])
- for option in options]
- else:
- return [(option, self._interpolate(section, option, d[option], d))
- for option in options]
+ def __contains__(self, key):
+ return self._parser.has_option(self._name, key)
- def _interpolate(self, section, option, rawval, vars):
- # do the string interpolation
- value = rawval
- depth = MAX_INTERPOLATION_DEPTH
- while depth: # Loop through this until it's done
- depth -= 1
- if "%(" in value:
- value = self._KEYCRE.sub(self._interpolation_replace, value)
- try:
- value = value % vars
- except KeyError as e:
- raise InterpolationMissingOptionError(
- option, section, rawval, e.args[0])
- else:
- break
- if "%(" in value:
- raise InterpolationDepthError(option, section, rawval)
- return value
+ def __len__(self):
+ return len(self._options())
- _KEYCRE = re.compile(r"%\(([^)]*)\)s|.")
+ def __iter__(self):
+ return self._options().__iter__()
- def _interpolation_replace(self, match):
- s = match.group(1)
- if s is None:
- return match.group()
+ def _options(self):
+ if self._name != self._parser.default_section:
+ return self._parser.options(self._name)
else:
- return "%%(%s)s" % self.optionxform(s)
+ return self._parser.defaults()
+ def get(self, option, fallback=None, *, raw=False, vars=None):
+ return self._parser.get(self._name, option, raw=raw, vars=vars,
+ fallback=fallback)
-class SafeConfigParser(ConfigParser):
+ def getint(self, option, fallback=None, *, raw=False, vars=None):
+ return self._parser.getint(self._name, option, raw=raw, vars=vars,
+ fallback=fallback)
- def _interpolate(self, section, option, rawval, vars):
- # do the string interpolation
- L = []
- self._interpolate_some(option, L, rawval, section, vars, 1)
- return ''.join(L)
+ def getfloat(self, option, fallback=None, *, raw=False, vars=None):
+ return self._parser.getfloat(self._name, option, raw=raw, vars=vars,
+ fallback=fallback)
- _interpvar_re = re.compile(r"%\(([^)]+)\)s")
+ def getboolean(self, option, fallback=None, *, raw=False, vars=None):
+ return self._parser.getboolean(self._name, option, raw=raw, vars=vars,
+ fallback=fallback)
- def _interpolate_some(self, option, accum, rest, section, map, depth):
- if depth > MAX_INTERPOLATION_DEPTH:
- raise InterpolationDepthError(option, section, rest)
- while rest:
- p = rest.find("%")
- if p < 0:
- accum.append(rest)
- return
- if p > 0:
- accum.append(rest[:p])
- rest = rest[p:]
- # p is no longer used
- c = rest[1:2]
- if c == "%":
- accum.append("%")
- rest = rest[2:]
- elif c == "(":
- m = self._interpvar_re.match(rest)
- if m is None:
- raise InterpolationSyntaxError(option, section,
- "bad interpolation variable reference %r" % rest)
- var = self.optionxform(m.group(1))
- rest = rest[m.end():]
- try:
- v = map[var]
- except KeyError:
- raise InterpolationMissingOptionError(
- option, section, rest, var)
- if "%" in v:
- self._interpolate_some(option, accum, v,
- section, map, depth + 1)
- else:
- accum.append(v)
- else:
- raise InterpolationSyntaxError(
- option, section,
- "'%%' must be followed by '%%' or '(', found: %r" % (rest,))
-
- def set(self, section, option, value):
- """Set an option. Extend ConfigParser.set: check for string values."""
- if not isinstance(value, str):
- raise TypeError("option values must be strings")
- # check for bad percent signs:
- # first, replace all "good" interpolations
- tmp_value = value.replace('%%', '')
- tmp_value = self._interpvar_re.sub('', tmp_value)
- # then, check if there's a lone percent sign left
- percent_index = tmp_value.find('%')
- if percent_index != -1:
- raise ValueError("invalid interpolation syntax in %r at "
- "position %d" % (value, percent_index))
- ConfigParser.set(self, section, option, value)
+ @property
+ def parser(self):
+ # The parser object of the proxy is read-only.
+ return self._parser
+
+ @property
+ def name(self):
+ # The name of the section on a proxy is read-only.
+ return self._name
diff --git a/Lib/contextlib.py b/Lib/contextlib.py
index bffa0c4..4633cff 100644
--- a/Lib/contextlib.py
+++ b/Lib/contextlib.py
@@ -4,9 +4,20 @@ import sys
from functools import wraps
from warnings import warn
-__all__ = ["contextmanager", "nested", "closing"]
+__all__ = ["contextmanager", "closing", "ContextDecorator"]
-class GeneratorContextManager(object):
+
+class ContextDecorator(object):
+ "A base class or mixin that enables context managers to work as decorators."
+ def __call__(self, func):
+ @wraps(func)
+ def inner(*args, **kwds):
+ with self:
+ return func(*args, **kwds)
+ return inner
+
+
+class _GeneratorContextManager(ContextDecorator):
"""Helper for @contextmanager decorator."""
def __init__(self, gen):
@@ -81,55 +92,10 @@ def contextmanager(func):
"""
@wraps(func)
def helper(*args, **kwds):
- return GeneratorContextManager(func(*args, **kwds))
+ return _GeneratorContextManager(func(*args, **kwds))
return helper
-@contextmanager
-def nested(*managers):
- """Combine multiple context managers into a single nested context manager.
-
- This function has been deprecated in favour of the multiple manager form
- of the with statement.
-
- The one advantage of this function over the multiple manager form of the
- with statement is that argument unpacking allows it to be
- used with a variable number of context managers as follows:
-
- with nested(*managers):
- do_something()
-
- """
- warn("With-statements now directly support multiple context managers",
- DeprecationWarning, 3)
- exits = []
- vars = []
- exc = (None, None, None)
- try:
- for mgr in managers:
- exit = mgr.__exit__
- enter = mgr.__enter__
- vars.append(enter())
- exits.append(exit)
- yield vars
- except:
- exc = sys.exc_info()
- finally:
- while exits:
- exit = exits.pop()
- try:
- if exit(*exc):
- exc = (None, None, None)
- except:
- exc = sys.exc_info()
- if exc != (None, None, None):
- # Don't rely on sys.exc_info() still containing
- # the right information. Another exception may
- # have been raised and caught by an exit method
- # exc[1] already has the __traceback__ attribute populated
- raise exc[1]
-
-
class closing(object):
"""Context to automatically close something at the end of a block.
diff --git a/Lib/copy.py b/Lib/copy.py
index 4b75511..089d101 100644
--- a/Lib/copy.py
+++ b/Lib/copy.py
@@ -239,6 +239,10 @@ d[dict] = _deepcopy_dict
if PyStringMap is not None:
d[PyStringMap] = _deepcopy_dict
+def _deepcopy_method(x, memo): # Copy instance methods
+ return type(x)(x.__func__, deepcopy(x.__self__, memo))
+_deepcopy_dispatch[types.MethodType] = _deepcopy_method
+
def _keep_alive(x, memo):
"""Keeps a reference to the object x in the memo.
diff --git a/Lib/csv.py b/Lib/csv.py
index dbe6db7..8dfc77e 100644
--- a/Lib/csv.py
+++ b/Lib/csv.py
@@ -20,7 +20,7 @@ __all__ = [ "QUOTE_MINIMAL", "QUOTE_ALL", "QUOTE_NONNUMERIC", "QUOTE_NONE",
"unregister_dialect", "__version__", "DictReader", "DictWriter" ]
class Dialect:
- """Describe an Excel dialect.
+ """Describe a CSV dialect.
This must be subclassed (see csv.excel). Valid attributes are:
delimiter, quotechar, escapechar, doublequote, skipinitialspace,
@@ -65,6 +65,16 @@ class excel_tab(excel):
delimiter = '\t'
register_dialect("excel-tab", excel_tab)
+class unix_dialect(Dialect):
+ """Describe the usual properties of Unix-generated CSV files."""
+ delimiter = ','
+ quotechar = '"'
+ doublequote = True
+ skipinitialspace = False
+ lineterminator = '\n'
+ quoting = QUOTE_ALL
+register_dialect("unix", unix_dialect)
+
class DictReader:
def __init__(self, f, fieldnames=None, restkey=None, restval=None,
@@ -127,6 +137,10 @@ class DictWriter:
self.extrasaction = extrasaction
self.writer = writer(f, dialect, *args, **kwds)
+ def writeheader(self):
+ header = dict(zip(self.fieldnames, self.fieldnames))
+ self.writerow(header)
+
def _dict_to_list(self, rowdict):
if self.extrasaction == "raise":
wrong_fields = [k for k in rowdict if k not in self.fieldnames]
diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py
index 1283f28..71686e7 100644
--- a/Lib/ctypes/__init__.py
+++ b/Lib/ctypes/__init__.py
@@ -259,41 +259,31 @@ class c_bool(_SimpleCData):
from _ctypes import POINTER, pointer, _pointer_type_cache
-try:
- from _ctypes import set_conversion_mode
-except ImportError:
- pass
-else:
- if _os.name in ("nt", "ce"):
- set_conversion_mode("mbcs", "ignore")
- else:
- set_conversion_mode("ascii", "strict")
+class c_wchar_p(_SimpleCData):
+ _type_ = "Z"
- class c_wchar_p(_SimpleCData):
- _type_ = "Z"
+class c_wchar(_SimpleCData):
+ _type_ = "u"
- class c_wchar(_SimpleCData):
- _type_ = "u"
+POINTER(c_wchar).from_param = c_wchar_p.from_param #_SimpleCData.c_wchar_p_from_param
- POINTER(c_wchar).from_param = c_wchar_p.from_param #_SimpleCData.c_wchar_p_from_param
-
- def create_unicode_buffer(init, size=None):
- """create_unicode_buffer(aString) -> character array
- create_unicode_buffer(anInteger) -> character array
- create_unicode_buffer(aString, anInteger) -> character array
- """
- if isinstance(init, (str, bytes)):
- if size is None:
- size = len(init)+1
- buftype = c_wchar * size
- buf = buftype()
- buf.value = init
- return buf
- elif isinstance(init, int):
- buftype = c_wchar * init
- buf = buftype()
- return buf
- raise TypeError(init)
+def create_unicode_buffer(init, size=None):
+ """create_unicode_buffer(aString) -> character array
+ create_unicode_buffer(anInteger) -> character array
+ create_unicode_buffer(aString, anInteger) -> character array
+ """
+ if isinstance(init, (str, bytes)):
+ if size is None:
+ size = len(init)+1
+ buftype = c_wchar * size
+ buf = buftype()
+ buf.value = init
+ return buf
+ elif isinstance(init, int):
+ buftype = c_wchar * init
+ buf = buftype()
+ return buf
+ raise TypeError(init)
POINTER(c_char).from_param = c_char_p.from_param #_SimpleCData.c_char_p_from_param
@@ -459,10 +449,13 @@ _pointer_type_cache[None] = c_void_p
if sizeof(c_uint) == sizeof(c_void_p):
c_size_t = c_uint
+ c_ssize_t = c_int
elif sizeof(c_ulong) == sizeof(c_void_p):
c_size_t = c_ulong
+ c_ssize_t = c_long
elif sizeof(c_ulonglong) == sizeof(c_void_p):
c_size_t = c_ulonglong
+ c_ssize_t = c_longlong
# functions
diff --git a/Lib/ctypes/test/test_arrays.py b/Lib/ctypes/test/test_arrays.py
index d11de28..f2a9f07 100644
--- a/Lib/ctypes/test/test_arrays.py
+++ b/Lib/ctypes/test/test_arrays.py
@@ -42,7 +42,7 @@ class ArrayTestCase(unittest.TestCase):
CharArray = ARRAY(c_char, 3)
- ca = CharArray("a", "b", "c")
+ ca = CharArray(b"a", b"b", b"c")
# Should this work? It doesn't:
# CharArray("abc")
@@ -89,7 +89,7 @@ class ArrayTestCase(unittest.TestCase):
def test_from_address(self):
# Failed with 0.9.8, reported by JUrner
- p = create_string_buffer("foo")
+ p = create_string_buffer(b"foo")
sz = (c_char * 3).from_address(addressof(p))
self.assertEqual(sz[:], b"foo")
self.assertEqual(sz[::], b"foo")
diff --git a/Lib/ctypes/test/test_bitfields.py b/Lib/ctypes/test/test_bitfields.py
index 4eb9571..9e0825c 100644
--- a/Lib/ctypes/test/test_bitfields.py
+++ b/Lib/ctypes/test/test_bitfields.py
@@ -37,14 +37,14 @@ class C_Test(unittest.TestCase):
for name in "ABCDEFGHI":
b = BITS()
setattr(b, name, i)
- self.assertEqual((name, i, getattr(b, name)), (name, i, func(byref(b), name)))
+ self.assertEqual(getattr(b, name), func(byref(b), name.encode('ascii')))
def test_shorts(self):
for i in range(256):
for name in "MNOPQRS":
b = BITS()
setattr(b, name, i)
- self.assertEqual((name, i, getattr(b, name)), (name, i, func(byref(b), name)))
+ self.assertEqual(getattr(b, name), func(byref(b), name.encode('ascii')))
signed_int_types = (c_byte, c_short, c_int, c_long, c_longlong)
unsigned_int_types = (c_ubyte, c_ushort, c_uint, c_ulong, c_ulonglong)
diff --git a/Lib/ctypes/test/test_buffers.py b/Lib/ctypes/test/test_buffers.py
index 3f8a587..c19c05a 100644
--- a/Lib/ctypes/test/test_buffers.py
+++ b/Lib/ctypes/test/test_buffers.py
@@ -9,19 +9,7 @@ class StringBufferTestCase(unittest.TestCase):
self.assertEqual(sizeof(b), 32 * sizeof(c_char))
self.assertTrue(type(b[0]) is bytes)
- b = create_string_buffer("abc")
- self.assertEqual(len(b), 4) # trailing nul char
- self.assertEqual(sizeof(b), 4 * sizeof(c_char))
- self.assertTrue(type(b[0]) is bytes)
- self.assertEqual(b[0], b"a")
- self.assertEqual(b[:], b"abc\0")
- self.assertEqual(b[::], b"abc\0")
- self.assertEqual(b[::-1], b"\0cba")
- self.assertEqual(b[::2], b"ac")
- self.assertEqual(b[::5], b"a")
-
- def test_string_conversion(self):
- b = create_string_buffer("abc")
+ b = create_string_buffer(b"abc")
self.assertEqual(len(b), 4) # trailing nul char
self.assertEqual(sizeof(b), 4 * sizeof(c_char))
self.assertTrue(type(b[0]) is bytes)
diff --git a/Lib/ctypes/test/test_bytes.py b/Lib/ctypes/test/test_bytes.py
index 374b2d7..ee49c45 100644
--- a/Lib/ctypes/test/test_bytes.py
+++ b/Lib/ctypes/test/test_bytes.py
@@ -11,34 +11,30 @@ class BytesTest(unittest.TestCase):
(c_char * 3)(b"a", b"b", b"c")
def test_c_wchar(self):
- x = c_wchar(b"x")
- x.value = b"y"
- c_wchar.from_param(b"x")
- (c_wchar * 3)(b"a", b"b", b"c")
+ x = c_wchar("x")
+ x.value = "y"
+ c_wchar.from_param("x")
+ (c_wchar * 3)("a", "b", "c")
def test_c_char_p(self):
- c_char_p("foo bar")
c_char_p(b"foo bar")
def test_c_wchar_p(self):
c_wchar_p("foo bar")
- c_wchar_p(b"foo bar")
def test_struct(self):
class X(Structure):
_fields_ = [("a", c_char * 3)]
- X("abc")
x = X(b"abc")
- self.assertEqual(x.a, "abc")
- self.assertEqual(type(x.a), str)
+ self.assertEqual(x.a, b"abc")
+ self.assertEqual(type(x.a), bytes)
def test_struct_W(self):
class X(Structure):
_fields_ = [("a", c_wchar * 3)]
- X("abc")
- x = X(b"abc")
+ x = X("abc")
self.assertEqual(x.a, "abc")
self.assertEqual(type(x.a), str)
@@ -49,7 +45,6 @@ class BytesTest(unittest.TestCase):
_type_ = "X"
BSTR("abc")
- BSTR(b"abc")
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/ctypes/test/test_callbacks.py b/Lib/ctypes/test/test_callbacks.py
index dabcde6..8801ccd 100644
--- a/Lib/ctypes/test/test_callbacks.py
+++ b/Lib/ctypes/test/test_callbacks.py
@@ -166,6 +166,40 @@ class SampleCallbacksTestCase(unittest.TestCase):
self.assertLess(diff, 0.01, "%s not less than 0.01" % diff)
+ def test_issue_8959_a(self):
+ from ctypes.util import find_library
+ libc_path = find_library("c")
+ if not libc_path:
+ return # cannot test
+ libc = CDLL(libc_path)
+
+ @CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
+ def cmp_func(a, b):
+ return a[0] - b[0]
+
+ array = (c_int * 5)(5, 1, 99, 7, 33)
+
+ libc.qsort(array, len(array), sizeof(c_int), cmp_func)
+ self.assertEqual(array[:], [1, 5, 7, 33, 99])
+
+ try:
+ WINFUNCTYPE
+ except NameError:
+ pass
+ else:
+ def test_issue_8959_b(self):
+ from ctypes.wintypes import BOOL, HWND, LPARAM
+ global windowCount
+ windowCount = 0
+
+ @WINFUNCTYPE(BOOL, HWND, LPARAM)
+ def EnumWindowsCallbackFunc(hwnd, lParam):
+ global windowCount
+ windowCount += 1
+ return True #Allow windows to keep enumerating
+
+ windll.user32.EnumWindows(EnumWindowsCallbackFunc, 0)
+
def test_callback_register_int(self):
# Issue #8275: buggy handling of callback args under Win64
# NOTE: should be run on release builds as well
diff --git a/Lib/ctypes/test/test_cast.py b/Lib/ctypes/test/test_cast.py
index 4c4a2e8..702de3c 100644
--- a/Lib/ctypes/test/test_cast.py
+++ b/Lib/ctypes/test/test_cast.py
@@ -33,17 +33,17 @@ class Test(unittest.TestCase):
def test_p2a_objects(self):
array = (c_char_p * 5)()
self.assertEqual(array._objects, None)
- array[0] = "foo bar"
+ array[0] = b"foo bar"
self.assertEqual(array._objects, {'0': b"foo bar"})
p = cast(array, POINTER(c_char_p))
# array and p share a common _objects attribute
self.assertTrue(p._objects is array._objects)
self.assertEqual(array._objects, {'0': b"foo bar", id(array): array})
- p[0] = "spam spam"
+ p[0] = b"spam spam"
self.assertEqual(p._objects, {'0': b"spam spam", id(array): array})
self.assertTrue(array._objects is p._objects)
- p[1] = "foo bar"
+ p[1] = b"foo bar"
self.assertEqual(p._objects, {'1': b'foo bar', '0': b"spam spam", id(array): array})
self.assertTrue(array._objects is p._objects)
@@ -71,7 +71,7 @@ class Test(unittest.TestCase):
def test_char_p(self):
# This didn't work: bad argument to internal function
- s = c_char_p("hiho")
+ s = c_char_p(b"hiho")
self.assertEqual(cast(cast(s, c_void_p), c_char_p).value,
b"hiho")
diff --git a/Lib/ctypes/test/test_cfuncs.py b/Lib/ctypes/test/test_cfuncs.py
index 493cbe9..f4bd3b1 100644
--- a/Lib/ctypes/test/test_cfuncs.py
+++ b/Lib/ctypes/test/test_cfuncs.py
@@ -107,7 +107,7 @@ class CFunctions(unittest.TestCase):
def test_ulong_plus(self):
self._dll.tf_bL.restype = c_ulong
self._dll.tf_bL.argtypes = (c_char, c_ulong)
- self.assertEqual(self._dll.tf_bL(' ', 4294967295), 1431655765)
+ self.assertEqual(self._dll.tf_bL(b' ', 4294967295), 1431655765)
self.assertEqual(self.U(), 4294967295)
def test_longlong(self):
diff --git a/Lib/ctypes/test/test_errno.py b/Lib/ctypes/test/test_errno.py
index 0254c3b..4690a0d 100644
--- a/Lib/ctypes/test/test_errno.py
+++ b/Lib/ctypes/test/test_errno.py
@@ -1,27 +1,31 @@
import unittest, os, errno
from ctypes import *
from ctypes.util import find_library
-import threading
+try:
+ import threading
+except ImportError:
+ threading = None
class Test(unittest.TestCase):
def test_open(self):
libc_name = find_library("c")
- if libc_name is not None:
- libc = CDLL(libc_name, use_errno=True)
- if os.name == "nt":
- libc_open = libc._open
- else:
- libc_open = libc.open
+ if libc_name is None:
+ raise unittest.SkipTest("Unable to find C library")
+ libc = CDLL(libc_name, use_errno=True)
+ if os.name == "nt":
+ libc_open = libc._open
+ else:
+ libc_open = libc.open
- libc_open.argtypes = c_char_p, c_int
+ libc_open.argtypes = c_char_p, c_int
- self.assertEqual(libc_open("", 0), -1)
- self.assertEqual(get_errno(), errno.ENOENT)
-
- self.assertEqual(set_errno(32), errno.ENOENT)
- self.assertEqual(get_errno(), 32)
+ self.assertEqual(libc_open(b"", 0), -1)
+ self.assertEqual(get_errno(), errno.ENOENT)
+ self.assertEqual(set_errno(32), errno.ENOENT)
+ self.assertEqual(get_errno(), 32)
+ if threading:
def _worker():
set_errno(0)
@@ -31,7 +35,7 @@ class Test(unittest.TestCase):
else:
libc_open = libc.open
libc_open.argtypes = c_char_p, c_int
- self.assertEqual(libc_open("", 0), -1)
+ self.assertEqual(libc_open(b"", 0), -1)
self.assertEqual(get_errno(), 0)
t = threading.Thread(target=_worker)
@@ -41,36 +45,35 @@ class Test(unittest.TestCase):
self.assertEqual(get_errno(), 32)
set_errno(0)
- if os.name == "nt":
-
- def test_GetLastError(self):
- dll = WinDLL("kernel32", use_last_error=True)
- GetModuleHandle = dll.GetModuleHandleA
- GetModuleHandle.argtypes = [c_wchar_p]
+ @unittest.skipUnless(os.name == "nt", 'Test specific to Windows')
+ def test_GetLastError(self):
+ dll = WinDLL("kernel32", use_last_error=True)
+ GetModuleHandle = dll.GetModuleHandleA
+ GetModuleHandle.argtypes = [c_wchar_p]
- self.assertEqual(0, GetModuleHandle("foo"))
- self.assertEqual(get_last_error(), 126)
+ self.assertEqual(0, GetModuleHandle("foo"))
+ self.assertEqual(get_last_error(), 126)
- self.assertEqual(set_last_error(32), 126)
- self.assertEqual(get_last_error(), 32)
+ self.assertEqual(set_last_error(32), 126)
+ self.assertEqual(get_last_error(), 32)
- def _worker():
- set_last_error(0)
+ def _worker():
+ set_last_error(0)
- dll = WinDLL("kernel32", use_last_error=False)
- GetModuleHandle = dll.GetModuleHandleW
- GetModuleHandle.argtypes = [c_wchar_p]
- GetModuleHandle("bar")
+ dll = WinDLL("kernel32", use_last_error=False)
+ GetModuleHandle = dll.GetModuleHandleW
+ GetModuleHandle.argtypes = [c_wchar_p]
+ GetModuleHandle("bar")
- self.assertEqual(get_last_error(), 0)
+ self.assertEqual(get_last_error(), 0)
- t = threading.Thread(target=_worker)
- t.start()
- t.join()
+ t = threading.Thread(target=_worker)
+ t.start()
+ t.join()
- self.assertEqual(get_last_error(), 32)
+ self.assertEqual(get_last_error(), 32)
- set_last_error(0)
+ set_last_error(0)
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/ctypes/test/test_internals.py b/Lib/ctypes/test/test_internals.py
index 03d820e..cbf2e05 100644
--- a/Lib/ctypes/test/test_internals.py
+++ b/Lib/ctypes/test/test_internals.py
@@ -23,16 +23,16 @@ class ObjectsTestCase(unittest.TestCase):
def test_ints(self):
i = 42000123
- rc = grc(i)
+ refcnt = grc(i)
ci = c_int(i)
- self.assertEqual(rc, grc(i))
+ self.assertEqual(refcnt, grc(i))
self.assertEqual(ci._objects, None)
def test_c_char_p(self):
s = b"Hello, World"
- rc = grc(s)
+ refcnt = grc(s)
cs = c_char_p(s)
- self.assertEqual(rc + 1, grc(s))
+ self.assertEqual(refcnt + 1, grc(s))
self.assertSame(cs._objects, s)
def test_simple_struct(self):
@@ -70,19 +70,17 @@ class ObjectsTestCase(unittest.TestCase):
class Y(Structure):
_fields_ = [("x", X), ("y", X)]
- s1 = "Hello, World"
- s2 = "Hallo, Welt"
+ s1 = b"Hello, World"
+ s2 = b"Hallo, Welt"
x = X()
x.a = s1
x.b = s2
- self.assertEqual(x._objects, {"0": bytes(s1, "ascii"),
- "1": bytes(s2, "ascii")})
+ self.assertEqual(x._objects, {"0": s1, "1": s2})
y = Y()
y.x = x
- self.assertEqual(y._objects, {"0": {"0": bytes(s1, "ascii"),
- "1": bytes(s2, "ascii")}})
+ self.assertEqual(y._objects, {"0": {"0": s1, "1": s2}})
## x = y.x
## del y
## print x._b_base_._objects
diff --git a/Lib/ctypes/test/test_keeprefs.py b/Lib/ctypes/test/test_keeprefs.py
index fafef65..db8adfb 100644
--- a/Lib/ctypes/test/test_keeprefs.py
+++ b/Lib/ctypes/test/test_keeprefs.py
@@ -13,9 +13,9 @@ class SimpleTestCase(unittest.TestCase):
def test_ccharp(self):
x = c_char_p()
self.assertEqual(x._objects, None)
- x.value = "abc"
+ x.value = b"abc"
self.assertEqual(x._objects, b"abc")
- x = c_char_p("spam")
+ x = c_char_p(b"spam")
self.assertEqual(x._objects, b"spam")
class StructureTestCase(unittest.TestCase):
@@ -37,8 +37,8 @@ class StructureTestCase(unittest.TestCase):
x = X()
self.assertEqual(x._objects, None)
- x.a = "spam"
- x.b = "foo"
+ x.a = b"spam"
+ x.b = b"foo"
self.assertEqual(x._objects, {"0": b"spam", "1": b"foo"})
def test_struct_struct(self):
diff --git a/Lib/ctypes/test/test_libc.py b/Lib/ctypes/test/test_libc.py
index cce409f..56285b5 100644
--- a/Lib/ctypes/test/test_libc.py
+++ b/Lib/ctypes/test/test_libc.py
@@ -25,7 +25,7 @@ class LibTest(unittest.TestCase):
def sort(a, b):
return three_way_cmp(a[0], b[0])
- chars = create_string_buffer("spam, spam, and spam")
+ chars = create_string_buffer(b"spam, spam, and spam")
lib.my_qsort(chars, len(chars)-1, sizeof(c_char), comparefunc(sort))
self.assertEqual(chars.raw, b" ,,aaaadmmmnpppsss\x00")
diff --git a/Lib/ctypes/test/test_loading.py b/Lib/ctypes/test/test_loading.py
index 07b69ec..4029b46 100644
--- a/Lib/ctypes/test/test_loading.py
+++ b/Lib/ctypes/test/test_loading.py
@@ -97,7 +97,7 @@ class LoaderTest(unittest.TestCase):
self.assertEqual(0, advapi32.CloseEventLog(None))
windll.kernel32.GetProcAddress.argtypes = c_void_p, c_char_p
windll.kernel32.GetProcAddress.restype = c_void_p
- proc = windll.kernel32.GetProcAddress(advapi32._handle, "CloseEventLog")
+ proc = windll.kernel32.GetProcAddress(advapi32._handle, b"CloseEventLog")
self.assertTrue(proc)
# This is the real test: call the function via 'call_function'
self.assertEqual(0, call_function(proc, (None,)))
diff --git a/Lib/ctypes/test/test_objects.py b/Lib/ctypes/test/test_objects.py
index 750d904..f075c20 100644
--- a/Lib/ctypes/test/test_objects.py
+++ b/Lib/ctypes/test/test_objects.py
@@ -20,7 +20,7 @@ None
The memory block stores pointers to strings, and the strings itself
assigned from Python must be kept.
->>> array[4] = 'foo bar'
+>>> array[4] = b'foo bar'
>>> array._objects
{'4': b'foo bar'}
>>> array[4]
@@ -45,7 +45,7 @@ of 'x' ('_b_base_' is either None, or the root object owning the memory block):
<ctypes.test.test_objects.X object at 0x...>
>>>
->>> x.array[0] = 'spam spam spam'
+>>> x.array[0] = b'spam spam spam'
>>> x._objects
{'0:2': b'spam spam spam'}
>>> x.array._b_base_._objects
diff --git a/Lib/ctypes/test/test_parameters.py b/Lib/ctypes/test/test_parameters.py
index 6a3f33d..e83fd9a 100644
--- a/Lib/ctypes/test/test_parameters.py
+++ b/Lib/ctypes/test/test_parameters.py
@@ -19,7 +19,6 @@ class SimpleTypesTestCase(unittest.TestCase):
else:
set_conversion_mode(*self.prev_conv_mode)
-
def test_subclasses(self):
from ctypes import c_void_p, c_char_p
# ctypes 0.9.5 and before did overwrite from_param in SimpleType_new
@@ -58,14 +57,13 @@ class SimpleTypesTestCase(unittest.TestCase):
self.assertTrue(c_char_p.from_param(s)._obj is s)
# new in 0.9.1: convert (encode) unicode to ascii
- self.assertEqual(c_char_p.from_param("123")._obj, b"123")
- self.assertRaises(UnicodeEncodeError, c_char_p.from_param, "123\377")
-
+ self.assertEqual(c_char_p.from_param(b"123")._obj, b"123")
+ self.assertRaises(TypeError, c_char_p.from_param, "123\377")
self.assertRaises(TypeError, c_char_p.from_param, 42)
# calling c_char_p.from_param with a c_char_p instance
# returns the argument itself:
- a = c_char_p("123")
+ a = c_char_p(b"123")
self.assertTrue(c_char_p.from_param(a) is a)
def test_cw_strings(self):
@@ -82,7 +80,7 @@ class SimpleTypesTestCase(unittest.TestCase):
# new in 0.9.1: convert (decode) ascii to unicode
self.assertEqual(c_wchar_p.from_param("123")._obj, "123")
- self.assertRaises(UnicodeDecodeError, c_wchar_p.from_param, b"123\377")
+ self.assertRaises(TypeError, c_wchar_p.from_param, b"123\377")
pa = c_wchar_p.from_param(c_wchar_p("123"))
self.assertEqual(type(pa), c_wchar_p)
diff --git a/Lib/ctypes/test/test_prototypes.py b/Lib/ctypes/test/test_prototypes.py
index d2e4c0b..6ef1b1b 100644
--- a/Lib/ctypes/test/test_prototypes.py
+++ b/Lib/ctypes/test/test_prototypes.py
@@ -127,7 +127,7 @@ class CharPointersTestCase(unittest.TestCase):
self.assertEqual(None, func(c_char_p(None)))
self.assertEqual(b"123", func(c_buffer(b"123")))
- ca = c_char("a")
+ ca = c_char(b"a")
self.assertEqual(ord(b"a"), func(pointer(ca))[0])
self.assertEqual(ord(b"a"), func(byref(ca))[0])
diff --git a/Lib/ctypes/test/test_python_api.py b/Lib/ctypes/test/test_python_api.py
index b74767a..1f4c603 100644
--- a/Lib/ctypes/test/test_python_api.py
+++ b/Lib/ctypes/test/test_python_api.py
@@ -72,10 +72,10 @@ class PythonAPITestCase(unittest.TestCase):
PyOS_snprintf.argtypes = POINTER(c_char), c_size_t, c_char_p
buf = c_buffer(256)
- PyOS_snprintf(buf, sizeof(buf), "Hello from %s", b"ctypes")
+ PyOS_snprintf(buf, sizeof(buf), b"Hello from %s", b"ctypes")
self.assertEqual(buf.value, b"Hello from ctypes")
- PyOS_snprintf(buf, sizeof(buf), "Hello from %s (%d, %d, %d)", b"ctypes", 1, 2, 3)
+ PyOS_snprintf(buf, sizeof(buf), b"Hello from %s (%d, %d, %d)", b"ctypes", 1, 2, 3)
self.assertEqual(buf.value, b"Hello from ctypes (1, 2, 3)")
# not enough arguments
diff --git a/Lib/ctypes/test/test_random_things.py b/Lib/ctypes/test/test_random_things.py
index 7bb9db8..515acf5 100644
--- a/Lib/ctypes/test/test_random_things.py
+++ b/Lib/ctypes/test/test_random_things.py
@@ -18,7 +18,7 @@ if sys.platform == "win32":
windll.kernel32.GetProcAddress.restype = c_void_p
hdll = windll.kernel32.LoadLibraryA(b"kernel32")
- funcaddr = windll.kernel32.GetProcAddress(hdll, "GetModuleHandleA")
+ funcaddr = windll.kernel32.GetProcAddress(hdll, b"GetModuleHandleA")
self.assertEqual(call_function(funcaddr, (None,)),
windll.kernel32.GetModuleHandleA(None))
@@ -66,7 +66,7 @@ class CallbackTracbackTestCase(unittest.TestCase):
def test_TypeErrorDivisionError(self):
cb = CFUNCTYPE(c_int, c_char_p)(callback_func)
- out = self.capture_stderr(cb, "spam")
+ out = self.capture_stderr(cb, b"spam")
self.assertEqual(out.splitlines()[-1],
"TypeError: "
"unsupported operand type(s) for /: 'int' and 'bytes'")
diff --git a/Lib/ctypes/test/test_repr.py b/Lib/ctypes/test/test_repr.py
index 9a1e238..60a2c80 100644
--- a/Lib/ctypes/test/test_repr.py
+++ b/Lib/ctypes/test/test_repr.py
@@ -22,8 +22,8 @@ class ReprTest(unittest.TestCase):
self.assertEqual("<X object at", repr(typ(42))[:12])
def test_char(self):
- self.assertEqual("c_char(b'x')", repr(c_char('x')))
- self.assertEqual("<X object at", repr(X('x'))[:12])
+ self.assertEqual("c_char(b'x')", repr(c_char(b'x')))
+ self.assertEqual("<X object at", repr(X(b'x'))[:12])
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/ctypes/test/test_returnfuncptrs.py b/Lib/ctypes/test/test_returnfuncptrs.py
index 11da07b..af1cadd 100644
--- a/Lib/ctypes/test/test_returnfuncptrs.py
+++ b/Lib/ctypes/test/test_returnfuncptrs.py
@@ -28,10 +28,10 @@ class ReturnFuncPtrTestCase(unittest.TestCase):
# _CFuncPtr instances are now callable with an integer argument
# which denotes a function address:
strchr = CFUNCTYPE(c_char_p, c_char_p, c_char)(addr)
- self.assertTrue(strchr("abcdef", "b"), "bcdef")
- self.assertEqual(strchr("abcdef", "x"), None)
- self.assertRaises(ArgumentError, strchr, "abcdef", 3.0)
- self.assertRaises(TypeError, strchr, "abcdef")
+ self.assertTrue(strchr(b"abcdef", b"b"), "bcdef")
+ self.assertEqual(strchr(b"abcdef", b"x"), None)
+ self.assertRaises(ArgumentError, strchr, b"abcdef", 3.0)
+ self.assertRaises(TypeError, strchr, b"abcdef")
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/ctypes/test/test_sizes.py b/Lib/ctypes/test/test_sizes.py
index 0509cbb..f9b5e97 100644
--- a/Lib/ctypes/test/test_sizes.py
+++ b/Lib/ctypes/test/test_sizes.py
@@ -1,8 +1,11 @@
# Test specifically-sized containers.
-import unittest
from ctypes import *
+import sys
+import unittest
+
+
class SizesTestCase(unittest.TestCase):
def test_8(self):
self.assertEqual(1, sizeof(c_int8))
@@ -23,5 +26,9 @@ class SizesTestCase(unittest.TestCase):
def test_size_t(self):
self.assertEqual(sizeof(c_void_p), sizeof(c_size_t))
+ def test_ssize_t(self):
+ self.assertEqual(sizeof(c_void_p), sizeof(c_ssize_t))
+
+
if __name__ == "__main__":
unittest.main()
diff --git a/Lib/ctypes/test/test_stringptr.py b/Lib/ctypes/test/test_stringptr.py
index 8b16dcb..3d25fa5 100644
--- a/Lib/ctypes/test/test_stringptr.py
+++ b/Lib/ctypes/test/test_stringptr.py
@@ -14,7 +14,7 @@ class StringPtrTestCase(unittest.TestCase):
# NULL pointer access
self.assertRaises(ValueError, getattr, x.str, "contents")
- b = c_buffer("Hello, World")
+ b = c_buffer(b"Hello, World")
from sys import getrefcount as grc
self.assertEqual(grc(b), 2)
x.str = b
@@ -63,8 +63,8 @@ class StringPtrTestCase(unittest.TestCase):
# So we must keep a reference to buf separately
strchr.restype = POINTER(c_char)
- buf = c_buffer("abcdef")
- r = strchr(buf, "c")
+ buf = c_buffer(b"abcdef")
+ r = strchr(buf, b"c")
x = r[0], r[1], r[2], r[3], r[4]
self.assertEqual(x, (b"c", b"d", b"e", b"f", b"\000"))
del buf
diff --git a/Lib/ctypes/test/test_strings.py b/Lib/ctypes/test/test_strings.py
index 3cd1f84..1a9bdbc 100644
--- a/Lib/ctypes/test/test_strings.py
+++ b/Lib/ctypes/test/test_strings.py
@@ -5,23 +5,23 @@ class StringArrayTestCase(unittest.TestCase):
def test(self):
BUF = c_char * 4
- buf = BUF("a", "b", "c")
+ buf = BUF(b"a", b"b", b"c")
self.assertEqual(buf.value, b"abc")
self.assertEqual(buf.raw, b"abc\000")
- buf.value = "ABCD"
+ buf.value = b"ABCD"
self.assertEqual(buf.value, b"ABCD")
self.assertEqual(buf.raw, b"ABCD")
- buf.value = "x"
+ buf.value = b"x"
self.assertEqual(buf.value, b"x")
self.assertEqual(buf.raw, b"x\000CD")
- buf[1] = "Z"
+ buf[1] = b"Z"
self.assertEqual(buf.value, b"xZCD")
self.assertEqual(buf.raw, b"xZCD")
- self.assertRaises(ValueError, setattr, buf, "value", "aaaaaaaa")
+ self.assertRaises(ValueError, setattr, buf, "value", b"aaaaaaaa")
self.assertRaises(TypeError, setattr, buf, "value", 42)
def test_c_buffer_value(self):
@@ -74,6 +74,13 @@ else:
buf[1] = "Z"
self.assertEqual(buf.value, "xZCD")
+ @unittest.skipIf(sizeof(c_wchar) < 4,
+ "sizeof(wchar_t) is smaller than 4 bytes")
+ def test_nonbmp(self):
+ u = chr(0x10ffff)
+ w = c_wchar(u)
+ self.assertEqual(w.value, u)
+
class StringTestCase(unittest.TestCase):
def XX_test_basic_strings(self):
cs = c_string("abcdef")
diff --git a/Lib/ctypes/test/test_structures.py b/Lib/ctypes/test/test_structures.py
index c58d949..536ea50 100644
--- a/Lib/ctypes/test/test_structures.py
+++ b/Lib/ctypes/test/test_structures.py
@@ -205,15 +205,15 @@ class StructureTestCase(unittest.TestCase):
("age", c_int)]
self.assertRaises(TypeError, Person, 42)
- self.assertRaises(ValueError, Person, "asldkjaslkdjaslkdj")
+ self.assertRaises(ValueError, Person, b"asldkjaslkdjaslkdj")
self.assertRaises(TypeError, Person, "Name", "HI")
# short enough
- self.assertEqual(Person("12345", 5).name, "12345")
+ self.assertEqual(Person(b"12345", 5).name, b"12345")
# exact fit
- self.assertEqual(Person("123456", 5).name, "123456")
+ self.assertEqual(Person(b"123456", 5).name, b"123456")
# too long
- self.assertRaises(ValueError, Person, "1234567", 5)
+ self.assertRaises(ValueError, Person, b"1234567", 5)
def test_conflicting_initializers(self):
class POINT(Structure):
@@ -267,11 +267,11 @@ class StructureTestCase(unittest.TestCase):
("phone", Phone),
("age", c_int)]
- p = Person("Someone", ("1234", "5678"), 5)
+ p = Person(b"Someone", (b"1234", b"5678"), 5)
- self.assertEqual(p.name, "Someone")
- self.assertEqual(p.phone.areacode, "1234")
- self.assertEqual(p.phone.number, "5678")
+ self.assertEqual(p.name, b"Someone")
+ self.assertEqual(p.phone.areacode, b"1234")
+ self.assertEqual(p.phone.number, b"5678")
self.assertEqual(p.age, 5)
def test_structures_with_wchar(self):
@@ -284,8 +284,8 @@ class StructureTestCase(unittest.TestCase):
_fields_ = [("name", c_wchar * 12),
("age", c_int)]
- p = PersonW("Someone")
- self.assertEqual(p.name, "Someone")
+ p = PersonW("Someone \xe9")
+ self.assertEqual(p.name, "Someone \xe9")
self.assertEqual(PersonW("1234567890").name, "1234567890")
self.assertEqual(PersonW("12345678901").name, "12345678901")
@@ -304,13 +304,13 @@ class StructureTestCase(unittest.TestCase):
("phone", Phone),
("age", c_int)]
- cls, msg = self.get_except(Person, "Someone", (1, 2))
+ cls, msg = self.get_except(Person, b"Someone", (1, 2))
self.assertEqual(cls, RuntimeError)
self.assertEqual(msg,
"(Phone) <class 'TypeError'>: "
"expected string, int found")
- cls, msg = self.get_except(Person, "Someone", ("a", "b", "c"))
+ cls, msg = self.get_except(Person, b"Someone", (b"a", b"b", b"c"))
self.assertEqual(cls, RuntimeError)
if issubclass(Exception, object):
self.assertEqual(msg,
diff --git a/Lib/ctypes/test/test_unicode.py b/Lib/ctypes/test/test_unicode.py
index b4d3df3..c3b2d48 100644
--- a/Lib/ctypes/test/test_unicode.py
+++ b/Lib/ctypes/test/test_unicode.py
@@ -7,122 +7,53 @@ except AttributeError:
pass
else:
import _ctypes_test
- dll = ctypes.CDLL(_ctypes_test.__file__)
- wcslen = dll.my_wcslen
- wcslen.argtypes = [ctypes.c_wchar_p]
-
class UnicodeTestCase(unittest.TestCase):
- def setUp(self):
- self.prev_conv_mode = ctypes.set_conversion_mode("ascii", "strict")
-
- def tearDown(self):
- ctypes.set_conversion_mode(*self.prev_conv_mode)
+ def test_wcslen(self):
+ dll = ctypes.CDLL(_ctypes_test.__file__)
+ wcslen = dll.my_wcslen
+ wcslen.argtypes = [ctypes.c_wchar_p]
- def test_ascii_strict(self):
- ctypes.set_conversion_mode("ascii", "strict")
- # no conversions take place with unicode arguments
self.assertEqual(wcslen("abc"), 3)
self.assertEqual(wcslen("ab\u2070"), 3)
- # string args are converted
- self.assertEqual(wcslen("abc"), 3)
self.assertRaises(ctypes.ArgumentError, wcslen, b"ab\xe4")
- def test_ascii_replace(self):
- ctypes.set_conversion_mode("ascii", "replace")
- self.assertEqual(wcslen("abc"), 3)
- self.assertEqual(wcslen("ab\u2070"), 3)
- self.assertEqual(wcslen("abc"), 3)
- self.assertEqual(wcslen("ab\xe4"), 3)
-
- def test_ascii_ignore(self):
- ctypes.set_conversion_mode("ascii", "ignore")
- self.assertEqual(wcslen("abc"), 3)
- self.assertEqual(wcslen("ab\u2070"), 3)
- # ignore error mode skips non-ascii characters
- self.assertEqual(wcslen("abc"), 3)
- self.assertEqual(wcslen(b"\xe4\xf6\xfc\xdf"), 0)
-
- def test_latin1_strict(self):
- ctypes.set_conversion_mode("latin-1", "strict")
- self.assertEqual(wcslen("abc"), 3)
- self.assertEqual(wcslen("ab\u2070"), 3)
- self.assertEqual(wcslen("abc"), 3)
- self.assertEqual(wcslen("\xe4\xf6\xfc\xdf"), 4)
-
def test_buffers(self):
- ctypes.set_conversion_mode("ascii", "strict")
buf = ctypes.create_unicode_buffer("abc")
self.assertEqual(len(buf), 3+1)
- ctypes.set_conversion_mode("ascii", "replace")
- buf = ctypes.create_unicode_buffer(b"ab\xe4\xf6\xfc")
- self.assertEqual(buf[:], "ab\uFFFD\uFFFD\uFFFD\0")
- self.assertEqual(buf[::], "ab\uFFFD\uFFFD\uFFFD\0")
- self.assertEqual(buf[::-1], "\0\uFFFD\uFFFD\uFFFDba")
- self.assertEqual(buf[::2], "a\uFFFD\uFFFD")
+ buf = ctypes.create_unicode_buffer("ab\xe4\xf6\xfc")
+ self.assertEqual(buf[:], "ab\xe4\xf6\xfc\0")
+ self.assertEqual(buf[::], "ab\xe4\xf6\xfc\0")
+ self.assertEqual(buf[::-1], '\x00\xfc\xf6\xe4ba')
+ self.assertEqual(buf[::2], 'a\xe4\xfc')
self.assertEqual(buf[6:5:-1], "")
- ctypes.set_conversion_mode("ascii", "ignore")
- buf = ctypes.create_unicode_buffer(b"ab\xe4\xf6\xfc")
- # is that correct? not sure. But with 'ignore', you get what you pay for..
- self.assertEqual(buf[:], "ab\0\0\0\0")
- self.assertEqual(buf[::], "ab\0\0\0\0")
- self.assertEqual(buf[::-1], "\0\0\0\0ba")
- self.assertEqual(buf[::2], "a\0\0")
- self.assertEqual(buf[6:5:-1], "")
-
- import _ctypes_test
func = ctypes.CDLL(_ctypes_test.__file__)._testfunc_p_p
class StringTestCase(UnicodeTestCase):
def setUp(self):
- self.prev_conv_mode = ctypes.set_conversion_mode("ascii", "strict")
func.argtypes = [ctypes.c_char_p]
func.restype = ctypes.c_char_p
def tearDown(self):
- ctypes.set_conversion_mode(*self.prev_conv_mode)
func.argtypes = None
func.restype = ctypes.c_int
- def test_ascii_replace(self):
- ctypes.set_conversion_mode("ascii", "strict")
- self.assertEqual(func("abc"), "abc")
- self.assertEqual(func("abc"), "abc")
- self.assertRaises(ctypes.ArgumentError, func, "ab\xe4")
-
- def test_ascii_ignore(self):
- ctypes.set_conversion_mode("ascii", "ignore")
- self.assertEqual(func("abc"), b"abc")
- self.assertEqual(func("abc"), b"abc")
- self.assertEqual(func("\xe4\xf6\xfc\xdf"), b"")
-
- def test_ascii_replace(self):
- ctypes.set_conversion_mode("ascii", "replace")
- self.assertEqual(func("abc"), b"abc")
- self.assertEqual(func("abc"), b"abc")
- self.assertEqual(func("\xe4\xf6\xfc\xdf"), b"????")
+ def test_func(self):
+ self.assertEqual(func(b"abc\xe4"), b"abc\xe4")
def test_buffers(self):
- ctypes.set_conversion_mode("ascii", "strict")
- buf = ctypes.create_string_buffer("abc")
+ buf = ctypes.create_string_buffer(b"abc")
self.assertEqual(len(buf), 3+1)
- ctypes.set_conversion_mode("ascii", "replace")
- buf = ctypes.create_string_buffer("ab\xe4\xf6\xfc")
- self.assertEqual(buf[:], b"ab???\0")
- self.assertEqual(buf[::], b"ab???\0")
- self.assertEqual(buf[::-1], b"\0???ba")
- self.assertEqual(buf[::2], b"a??")
+ buf = ctypes.create_string_buffer(b"ab\xe4\xf6\xfc")
+ self.assertEqual(buf[:], b"ab\xe4\xf6\xfc\0")
+ self.assertEqual(buf[::], b"ab\xe4\xf6\xfc\0")
+ self.assertEqual(buf[::-1], b'\x00\xfc\xf6\xe4ba')
+ self.assertEqual(buf[::2], b'a\xe4\xfc')
self.assertEqual(buf[6:5:-1], b"")
- ctypes.set_conversion_mode("ascii", "ignore")
- buf = ctypes.create_string_buffer("ab\xe4\xf6\xfc")
- # is that correct? not sure. But with 'ignore', you get what you pay for..
- self.assertEqual(buf[:], b"ab\0\0\0\0")
- self.assertEqual(buf[::], b"ab\0\0\0\0")
- self.assertEqual(buf[::-1], b"\0\0\0\0ba")
if __name__ == '__main__':
unittest.main()
diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py
index 52bc296..1881e89 100644
--- a/Lib/ctypes/util.py
+++ b/Lib/ctypes/util.py
@@ -1,4 +1,5 @@
import sys, os
+import contextlib
# find_library(name) returns the pathname of a library, or None.
if os.name == "nt":
@@ -117,11 +118,8 @@ elif os.name == "posix":
if not f:
return None
cmd = "/usr/ccs/bin/dump -Lpv 2>/dev/null " + f
- f = os.popen(cmd)
- try:
+ with contextlib.closing(os.popen(cmd)) as f:
data = f.read()
- finally:
- f.close()
res = re.search(r'\[.*\]\sSONAME\s+([^\s]+)', data)
if not res:
return None
@@ -138,11 +136,8 @@ elif os.name == "posix":
rv = f.close()
if rv == 10:
raise OSError('objdump command not found')
- f = os.popen(cmd)
- try:
+ with contextlib.closing(os.popen(cmd)) as f:
data = f.read()
- finally:
- f.close()
res = re.search(r'\sSONAME\s+([^\s]+)', data)
if not res:
return None
@@ -166,11 +161,8 @@ elif os.name == "posix":
def find_library(name):
ename = re.escape(name)
expr = r':-l%s\.\S+ => \S*/(lib%s\.\S+)' % (ename, ename)
- f = os.popen('/sbin/ldconfig -r 2>/dev/null')
- try:
+ with contextlib.closing(os.popen('/sbin/ldconfig -r 2>/dev/null')) as f:
data = f.read()
- finally:
- f.close()
res = re.findall(expr, data)
if not res:
return _get_soname(_findLib_gcc(name))
@@ -182,20 +174,14 @@ elif os.name == "posix":
def _findLib_ldconfig(name):
# XXX assuming GLIBC's ldconfig (with option -p)
expr = r'/[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name)
- f = os.popen('/sbin/ldconfig -p 2>/dev/null')
- try:
+ with contextlib.closing(os.popen('/sbin/ldconfig -p 2>/dev/null')) as f:
data = f.read()
- finally:
- f.close()
res = re.search(expr, data)
if not res:
# Hm, this works only for libs needed by the python executable.
cmd = 'ldd %s 2>/dev/null' % sys.executable
- f = os.popen(cmd)
- try:
+ with contextlib.closing(os.popen(cmd)) as f:
data = f.read()
- finally:
- f.close()
res = re.search(expr, data)
if not res:
return None
@@ -219,11 +205,8 @@ elif os.name == "posix":
# XXX assuming GLIBC's ldconfig (with option -p)
expr = r'(\S+)\s+\((%s(?:, OS ABI:[^\)]*)?)\)[^/]*(/[^\(\)\s]*lib%s\.[^\(\)\s]*)' \
% (abi_type, re.escape(name))
- f = os.popen('LC_ALL=C LANG=C /sbin/ldconfig -p 2>/dev/null')
- try:
+ with contextlib.closing(os.popen('LC_ALL=C LANG=C /sbin/ldconfig -p 2>/dev/null')) as f:
data = f.read()
- finally:
- f.close()
res = re.search(expr, data)
if not res:
return None
diff --git a/Lib/ctypes/wintypes.py b/Lib/ctypes/wintypes.py
index e7f569c..c619d27 100644
--- a/Lib/ctypes/wintypes.py
+++ b/Lib/ctypes/wintypes.py
@@ -1,49 +1,50 @@
# The most useful windows datatypes
-from ctypes import *
+import ctypes
-BYTE = c_byte
-WORD = c_ushort
-DWORD = c_ulong
+BYTE = ctypes.c_byte
+WORD = ctypes.c_ushort
+DWORD = ctypes.c_ulong
-WCHAR = c_wchar
-UINT = c_uint
-INT = c_int
+#UCHAR = ctypes.c_uchar
+CHAR = ctypes.c_char
+WCHAR = ctypes.c_wchar
+UINT = ctypes.c_uint
+INT = ctypes.c_int
-DOUBLE = c_double
-FLOAT = c_float
+DOUBLE = ctypes.c_double
+FLOAT = ctypes.c_float
BOOLEAN = BYTE
-BOOL = c_long
+BOOL = ctypes.c_long
-from ctypes import _SimpleCData
-class VARIANT_BOOL(_SimpleCData):
+class VARIANT_BOOL(ctypes._SimpleCData):
_type_ = "v"
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.value)
-ULONG = c_ulong
-LONG = c_long
+ULONG = ctypes.c_ulong
+LONG = ctypes.c_long
-USHORT = c_ushort
-SHORT = c_short
+USHORT = ctypes.c_ushort
+SHORT = ctypes.c_short
# in the windows header files, these are structures.
-_LARGE_INTEGER = LARGE_INTEGER = c_longlong
-_ULARGE_INTEGER = ULARGE_INTEGER = c_ulonglong
+_LARGE_INTEGER = LARGE_INTEGER = ctypes.c_longlong
+_ULARGE_INTEGER = ULARGE_INTEGER = ctypes.c_ulonglong
-LPCOLESTR = LPOLESTR = OLESTR = c_wchar_p
-LPCWSTR = LPWSTR = c_wchar_p
-LPCSTR = LPSTR = c_char_p
-LPCVOID = LPVOID = c_void_p
+LPCOLESTR = LPOLESTR = OLESTR = ctypes.c_wchar_p
+LPCWSTR = LPWSTR = ctypes.c_wchar_p
+LPCSTR = LPSTR = ctypes.c_char_p
+LPCVOID = LPVOID = ctypes.c_void_p
# WPARAM is defined as UINT_PTR (unsigned type)
# LPARAM is defined as LONG_PTR (signed type)
-if sizeof(c_long) == sizeof(c_void_p):
- WPARAM = c_ulong
- LPARAM = c_long
-elif sizeof(c_longlong) == sizeof(c_void_p):
- WPARAM = c_ulonglong
- LPARAM = c_longlong
+if ctypes.sizeof(ctypes.c_long) == ctypes.sizeof(ctypes.c_void_p):
+ WPARAM = ctypes.c_ulong
+ LPARAM = ctypes.c_long
+elif ctypes.sizeof(ctypes.c_longlong) == ctypes.sizeof(ctypes.c_void_p):
+ WPARAM = ctypes.c_ulonglong
+ LPARAM = ctypes.c_longlong
ATOM = WORD
LANGID = WORD
@@ -56,7 +57,7 @@ LCID = DWORD
################################################################
# HANDLE types
-HANDLE = c_void_p # in the header files: void *
+HANDLE = ctypes.c_void_p # in the header files: void *
HACCEL = HANDLE
HBITMAP = HANDLE
@@ -93,45 +94,45 @@ SERVICE_STATUS_HANDLE = HANDLE
################################################################
# Some important structure definitions
-class RECT(Structure):
- _fields_ = [("left", c_long),
- ("top", c_long),
- ("right", c_long),
- ("bottom", c_long)]
+class RECT(ctypes.Structure):
+ _fields_ = [("left", LONG),
+ ("top", LONG),
+ ("right", LONG),
+ ("bottom", LONG)]
tagRECT = _RECTL = RECTL = RECT
-class _SMALL_RECT(Structure):
- _fields_ = [('Left', c_short),
- ('Top', c_short),
- ('Right', c_short),
- ('Bottom', c_short)]
+class _SMALL_RECT(ctypes.Structure):
+ _fields_ = [('Left', SHORT),
+ ('Top', SHORT),
+ ('Right', SHORT),
+ ('Bottom', SHORT)]
SMALL_RECT = _SMALL_RECT
-class _COORD(Structure):
- _fields_ = [('X', c_short),
- ('Y', c_short)]
+class _COORD(ctypes.Structure):
+ _fields_ = [('X', SHORT),
+ ('Y', SHORT)]
-class POINT(Structure):
- _fields_ = [("x", c_long),
- ("y", c_long)]
+class POINT(ctypes.Structure):
+ _fields_ = [("x", LONG),
+ ("y", LONG)]
tagPOINT = _POINTL = POINTL = POINT
-class SIZE(Structure):
- _fields_ = [("cx", c_long),
- ("cy", c_long)]
+class SIZE(ctypes.Structure):
+ _fields_ = [("cx", LONG),
+ ("cy", LONG)]
tagSIZE = SIZEL = SIZE
def RGB(red, green, blue):
return red + (green << 8) + (blue << 16)
-class FILETIME(Structure):
+class FILETIME(ctypes.Structure):
_fields_ = [("dwLowDateTime", DWORD),
("dwHighDateTime", DWORD)]
_FILETIME = FILETIME
-class MSG(Structure):
+class MSG(ctypes.Structure):
_fields_ = [("hWnd", HWND),
- ("message", c_uint),
+ ("message", UINT),
("wParam", WPARAM),
("lParam", LPARAM),
("time", DWORD),
@@ -139,7 +140,7 @@ class MSG(Structure):
tagMSG = MSG
MAX_PATH = 260
-class WIN32_FIND_DATAA(Structure):
+class WIN32_FIND_DATAA(ctypes.Structure):
_fields_ = [("dwFileAttributes", DWORD),
("ftCreationTime", FILETIME),
("ftLastAccessTime", FILETIME),
@@ -148,10 +149,10 @@ class WIN32_FIND_DATAA(Structure):
("nFileSizeLow", DWORD),
("dwReserved0", DWORD),
("dwReserved1", DWORD),
- ("cFileName", c_char * MAX_PATH),
- ("cAlternateFileName", c_char * 14)]
+ ("cFileName", CHAR * MAX_PATH),
+ ("cAlternateFileName", CHAR * 14)]
-class WIN32_FIND_DATAW(Structure):
+class WIN32_FIND_DATAW(ctypes.Structure):
_fields_ = [("dwFileAttributes", DWORD),
("ftCreationTime", FILETIME),
("ftLastAccessTime", FILETIME),
@@ -160,22 +161,42 @@ class WIN32_FIND_DATAW(Structure):
("nFileSizeLow", DWORD),
("dwReserved0", DWORD),
("dwReserved1", DWORD),
- ("cFileName", c_wchar * MAX_PATH),
- ("cAlternateFileName", c_wchar * 14)]
-
-__all__ = ['ATOM', 'BOOL', 'BOOLEAN', 'BYTE', 'COLORREF', 'DOUBLE', 'DWORD',
- 'FILETIME', 'FLOAT', 'HACCEL', 'HANDLE', 'HBITMAP', 'HBRUSH',
- 'HCOLORSPACE', 'HDC', 'HDESK', 'HDWP', 'HENHMETAFILE', 'HFONT',
- 'HGDIOBJ', 'HGLOBAL', 'HHOOK', 'HICON', 'HINSTANCE', 'HKEY',
- 'HKL', 'HLOCAL', 'HMENU', 'HMETAFILE', 'HMODULE', 'HMONITOR',
- 'HPALETTE', 'HPEN', 'HRGN', 'HRSRC', 'HSTR', 'HTASK', 'HWINSTA',
- 'HWND', 'INT', 'LANGID', 'LARGE_INTEGER', 'LCID', 'LCTYPE',
- 'LGRPID', 'LONG', 'LPARAM', 'LPCOLESTR', 'LPCSTR', 'LPCVOID',
- 'LPCWSTR', 'LPOLESTR', 'LPSTR', 'LPVOID', 'LPWSTR', 'MAX_PATH',
- 'MSG', 'OLESTR', 'POINT', 'POINTL', 'RECT', 'RECTL', 'RGB',
- 'SC_HANDLE', 'SERVICE_STATUS_HANDLE', 'SHORT', 'SIZE', 'SIZEL',
- 'SMALL_RECT', 'UINT', 'ULARGE_INTEGER', 'ULONG', 'USHORT',
- 'VARIANT_BOOL', 'WCHAR', 'WIN32_FIND_DATAA', 'WIN32_FIND_DATAW',
- 'WORD', 'WPARAM', '_COORD', '_FILETIME', '_LARGE_INTEGER',
- '_POINTL', '_RECTL', '_SMALL_RECT', '_ULARGE_INTEGER', 'tagMSG',
- 'tagPOINT', 'tagRECT', 'tagSIZE']
+ ("cFileName", WCHAR * MAX_PATH),
+ ("cAlternateFileName", WCHAR * 14)]
+
+################################################################
+# Pointer types
+
+LPBOOL = PBOOL = ctypes.POINTER(BOOL)
+PBOOLEAN = ctypes.POINTER(BOOLEAN)
+LPBYTE = PBYTE = ctypes.POINTER(BYTE)
+PCHAR = ctypes.POINTER(CHAR)
+LPCOLORREF = ctypes.POINTER(COLORREF)
+LPDWORD = PDWORD = ctypes.POINTER(DWORD)
+LPFILETIME = PFILETIME = ctypes.POINTER(FILETIME)
+PFLOAT = ctypes.POINTER(FLOAT)
+LPHANDLE = PHANDLE = ctypes.POINTER(HANDLE)
+PHKEY = ctypes.POINTER(HKEY)
+LPHKL = ctypes.POINTER(HKL)
+LPINT = PINT = ctypes.POINTER(INT)
+PLARGE_INTEGER = ctypes.POINTER(LARGE_INTEGER)
+PLCID = ctypes.POINTER(LCID)
+LPLONG = PLONG = ctypes.POINTER(LONG)
+LPMSG = PMSG = ctypes.POINTER(MSG)
+LPPOINT = PPOINT = ctypes.POINTER(POINT)
+PPOINTL = ctypes.POINTER(POINTL)
+LPRECT = PRECT = ctypes.POINTER(RECT)
+LPRECTL = PRECTL = ctypes.POINTER(RECTL)
+LPSC_HANDLE = ctypes.POINTER(SC_HANDLE)
+PSHORT = ctypes.POINTER(SHORT)
+LPSIZE = PSIZE = ctypes.POINTER(SIZE)
+LPSIZEL = PSIZEL = ctypes.POINTER(SIZEL)
+PSMALL_RECT = ctypes.POINTER(SMALL_RECT)
+LPUINT = PUINT = ctypes.POINTER(UINT)
+PULARGE_INTEGER = ctypes.POINTER(ULARGE_INTEGER)
+PULONG = ctypes.POINTER(ULONG)
+PUSHORT = ctypes.POINTER(USHORT)
+PWCHAR = ctypes.POINTER(WCHAR)
+LPWIN32_FIND_DATAA = PWIN32_FIND_DATAA = ctypes.POINTER(WIN32_FIND_DATAA)
+LPWIN32_FIND_DATAW = PWIN32_FIND_DATAW = ctypes.POINTER(WIN32_FIND_DATAW)
+LPWORD = PWORD = ctypes.POINTER(WORD)
diff --git a/Lib/datetime.py b/Lib/datetime.py
new file mode 100644
index 0000000..47e54ec
--- /dev/null
+++ b/Lib/datetime.py
@@ -0,0 +1,2108 @@
+"""Concrete date/time and related types -- prototype implemented in Python.
+
+See http://www.zope.org/Members/fdrake/DateTimeWiki/FrontPage
+
+See also http://dir.yahoo.com/Reference/calendars/
+
+For a primer on DST, including many current DST rules, see
+http://webexhibits.org/daylightsaving/
+
+For more about DST than you ever wanted to know, see
+ftp://elsie.nci.nih.gov/pub/
+
+Sources for time zone and DST data: http://www.twinsun.com/tz/tz-link.htm
+
+This was originally copied from the sandbox of the CPython CVS repository.
+Thanks to Tim Peters for suggesting using it.
+"""
+
+import time as _time
+import math as _math
+
+def _cmp(x, y):
+ return 0 if x == y else 1 if x > y else -1
+
+MINYEAR = 1
+MAXYEAR = 9999
+_MAXORDINAL = 3652059 # date.max.toordinal()
+
+# Utility functions, adapted from Python's Demo/classes/Dates.py, which
+# also assumes the current Gregorian calendar indefinitely extended in
+# both directions. Difference: Dates.py calls January 1 of year 0 day
+# number 1. The code here calls January 1 of year 1 day number 1. This is
+# to match the definition of the "proleptic Gregorian" calendar in Dershowitz
+# and Reingold's "Calendrical Calculations", where it's the base calendar
+# for all computations. See the book for algorithms for converting between
+# proleptic Gregorian ordinals and many other calendar systems.
+
+_DAYS_IN_MONTH = [None, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
+
+_DAYS_BEFORE_MONTH = [None]
+dbm = 0
+for dim in _DAYS_IN_MONTH[1:]:
+ _DAYS_BEFORE_MONTH.append(dbm)
+ dbm += dim
+del dbm, dim
+
+def _is_leap(year):
+ "year -> 1 if leap year, else 0."
+ return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
+
+def _days_before_year(year):
+ "year -> number of days before January 1st of year."
+ y = year - 1
+ return y*365 + y//4 - y//100 + y//400
+
+def _days_in_month(year, month):
+ "year, month -> number of days in that month in that year."
+ assert 1 <= month <= 12, month
+ if month == 2 and _is_leap(year):
+ return 29
+ return _DAYS_IN_MONTH[month]
+
+def _days_before_month(year, month):
+ "year, month -> number of days in year preceeding first day of month."
+ assert 1 <= month <= 12, 'month must be in 1..12'
+ return _DAYS_BEFORE_MONTH[month] + (month > 2 and _is_leap(year))
+
+def _ymd2ord(year, month, day):
+ "year, month, day -> ordinal, considering 01-Jan-0001 as day 1."
+ assert 1 <= month <= 12, 'month must be in 1..12'
+ dim = _days_in_month(year, month)
+ assert 1 <= day <= dim, ('day must be in 1..%d' % dim)
+ return (_days_before_year(year) +
+ _days_before_month(year, month) +
+ day)
+
+_DI400Y = _days_before_year(401) # number of days in 400 years
+_DI100Y = _days_before_year(101) # " " " " 100 "
+_DI4Y = _days_before_year(5) # " " " " 4 "
+
+# A 4-year cycle has an extra leap day over what we'd get from pasting
+# together 4 single years.
+assert _DI4Y == 4 * 365 + 1
+
+# Similarly, a 400-year cycle has an extra leap day over what we'd get from
+# pasting together 4 100-year cycles.
+assert _DI400Y == 4 * _DI100Y + 1
+
+# OTOH, a 100-year cycle has one fewer leap day than we'd get from
+# pasting together 25 4-year cycles.
+assert _DI100Y == 25 * _DI4Y - 1
+
+def _ord2ymd(n):
+ "ordinal -> (year, month, day), considering 01-Jan-0001 as day 1."
+
+ # n is a 1-based index, starting at 1-Jan-1. The pattern of leap years
+ # repeats exactly every 400 years. The basic strategy is to find the
+ # closest 400-year boundary at or before n, then work with the offset
+ # from that boundary to n. Life is much clearer if we subtract 1 from
+ # n first -- then the values of n at 400-year boundaries are exactly
+ # those divisible by _DI400Y:
+ #
+ # D M Y n n-1
+ # -- --- ---- ---------- ----------------
+ # 31 Dec -400 -_DI400Y -_DI400Y -1
+ # 1 Jan -399 -_DI400Y +1 -_DI400Y 400-year boundary
+ # ...
+ # 30 Dec 000 -1 -2
+ # 31 Dec 000 0 -1
+ # 1 Jan 001 1 0 400-year boundary
+ # 2 Jan 001 2 1
+ # 3 Jan 001 3 2
+ # ...
+ # 31 Dec 400 _DI400Y _DI400Y -1
+ # 1 Jan 401 _DI400Y +1 _DI400Y 400-year boundary
+ n -= 1
+ n400, n = divmod(n, _DI400Y)
+ year = n400 * 400 + 1 # ..., -399, 1, 401, ...
+
+ # Now n is the (non-negative) offset, in days, from January 1 of year, to
+ # the desired date. Now compute how many 100-year cycles precede n.
+ # Note that it's possible for n100 to equal 4! In that case 4 full
+ # 100-year cycles precede the desired day, which implies the desired
+ # day is December 31 at the end of a 400-year cycle.
+ n100, n = divmod(n, _DI100Y)
+
+ # Now compute how many 4-year cycles precede it.
+ n4, n = divmod(n, _DI4Y)
+
+ # And now how many single years. Again n1 can be 4, and again meaning
+ # that the desired day is December 31 at the end of the 4-year cycle.
+ n1, n = divmod(n, 365)
+
+ year += n100 * 100 + n4 * 4 + n1
+ if n1 == 4 or n100 == 4:
+ assert n == 0
+ return year-1, 12, 31
+
+ # Now the year is correct, and n is the offset from January 1. We find
+ # the month via an estimate that's either exact or one too large.
+ leapyear = n1 == 3 and (n4 != 24 or n100 == 3)
+ assert leapyear == _is_leap(year)
+ month = (n + 50) >> 5
+ preceding = _DAYS_BEFORE_MONTH[month] + (month > 2 and leapyear)
+ if preceding > n: # estimate is too large
+ month -= 1
+ preceding -= _DAYS_IN_MONTH[month] + (month == 2 and leapyear)
+ n -= preceding
+ assert 0 <= n < _days_in_month(year, month)
+
+ # Now the year and month are correct, and n is the offset from the
+ # start of that month: we're done!
+ return year, month, n+1
+
+# Month and day names. For localized versions, see the calendar module.
+_MONTHNAMES = [None, "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+_DAYNAMES = [None, "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]
+
+
+def _build_struct_time(y, m, d, hh, mm, ss, dstflag):
+ wday = (_ymd2ord(y, m, d) + 6) % 7
+ dnum = _days_before_month(y, m) + d
+ return _time.struct_time((y, m, d, hh, mm, ss, wday, dnum, dstflag))
+
+def _format_time(hh, mm, ss, us):
+ # Skip trailing microseconds when us==0.
+ result = "%02d:%02d:%02d" % (hh, mm, ss)
+ if us:
+ result += ".%06d" % us
+ return result
+
+# Correctly substitute for %z and %Z escapes in strftime formats.
+def _wrap_strftime(object, format, timetuple):
+ year = timetuple[0]
+ if year < 1000:
+ raise ValueError("year=%d is before 1000; the datetime strftime() "
+ "methods require year >= 1000" % year)
+ # Don't call utcoffset() or tzname() unless actually needed.
+ freplace = None # the string to use for %f
+ zreplace = None # the string to use for %z
+ Zreplace = None # the string to use for %Z
+
+ # Scan format for %z and %Z escapes, replacing as needed.
+ newformat = []
+ push = newformat.append
+ i, n = 0, len(format)
+ while i < n:
+ ch = format[i]
+ i += 1
+ if ch == '%':
+ if i < n:
+ ch = format[i]
+ i += 1
+ if ch == 'f':
+ if freplace is None:
+ freplace = '%06d' % getattr(object,
+ 'microsecond', 0)
+ newformat.append(freplace)
+ elif ch == 'z':
+ if zreplace is None:
+ zreplace = ""
+ if hasattr(object, "utcoffset"):
+ offset = object.utcoffset()
+ if offset is not None:
+ sign = '+'
+ if offset.days < 0:
+ offset = -offset
+ sign = '-'
+ h, m = divmod(offset, timedelta(hours=1))
+ assert not m % timedelta(minutes=1), "whole minute"
+ m //= timedelta(minutes=1)
+ zreplace = '%c%02d%02d' % (sign, h, m)
+ assert '%' not in zreplace
+ newformat.append(zreplace)
+ elif ch == 'Z':
+ if Zreplace is None:
+ Zreplace = ""
+ if hasattr(object, "tzname"):
+ s = object.tzname()
+ if s is not None:
+ # strftime is going to have at this: escape %
+ Zreplace = s.replace('%', '%%')
+ newformat.append(Zreplace)
+ else:
+ push('%')
+ push(ch)
+ else:
+ push('%')
+ else:
+ push(ch)
+ newformat = "".join(newformat)
+ return _time.strftime(newformat, timetuple)
+
+def _call_tzinfo_method(tzinfo, methname, tzinfoarg):
+ if tzinfo is None:
+ return None
+ return getattr(tzinfo, methname)(tzinfoarg)
+
+# Just raise TypeError if the arg isn't None or a string.
+def _check_tzname(name):
+ if name is not None and not isinstance(name, str):
+ raise TypeError("tzinfo.tzname() must return None or string, "
+ "not '%s'" % type(name))
+
+# name is the offset-producing method, "utcoffset" or "dst".
+# offset is what it returned.
+# If offset isn't None or timedelta, raises TypeError.
+# If offset is None, returns None.
+# Else offset is checked for being in range, and a whole # of minutes.
+# If it is, its integer value is returned. Else ValueError is raised.
+def _check_utc_offset(name, offset):
+ assert name in ("utcoffset", "dst")
+ if offset is None:
+ return
+ if not isinstance(offset, timedelta):
+ raise TypeError("tzinfo.%s() must return None "
+ "or timedelta, not '%s'" % (name, type(offset)))
+ if offset % timedelta(minutes=1) or offset.microseconds:
+ raise ValueError("tzinfo.%s() must return a whole number "
+ "of minutes, got %s" % (name, offset))
+ if not -timedelta(1) < offset < timedelta(1):
+ raise ValueError("%s()=%s, must be must be strictly between"
+ " -timedelta(hours=24) and timedelta(hours=24)"
+ % (name, offset))
+
+def _check_date_fields(year, month, day):
+ if not isinstance(year, int):
+ raise TypeError('int expected')
+ if not MINYEAR <= year <= MAXYEAR:
+ raise ValueError('year must be in %d..%d' % (MINYEAR, MAXYEAR), year)
+ if not 1 <= month <= 12:
+ raise ValueError('month must be in 1..12', month)
+ dim = _days_in_month(year, month)
+ if not 1 <= day <= dim:
+ raise ValueError('day must be in 1..%d' % dim, day)
+
+def _check_time_fields(hour, minute, second, microsecond):
+ if not isinstance(hour, int):
+ raise TypeError('int expected')
+ if not 0 <= hour <= 23:
+ raise ValueError('hour must be in 0..23', hour)
+ if not 0 <= minute <= 59:
+ raise ValueError('minute must be in 0..59', minute)
+ if not 0 <= second <= 59:
+ raise ValueError('second must be in 0..59', second)
+ if not 0 <= microsecond <= 999999:
+ raise ValueError('microsecond must be in 0..999999', microsecond)
+
+def _check_tzinfo_arg(tz):
+ if tz is not None and not isinstance(tz, tzinfo):
+ raise TypeError("tzinfo argument must be None or of a tzinfo subclass")
+
+def _cmperror(x, y):
+ raise TypeError("can't compare '%s' to '%s'" % (
+ type(x).__name__, type(y).__name__))
+
+class timedelta:
+ """Represent the difference between two datetime objects.
+
+ Supported operators:
+
+ - add, subtract timedelta
+ - unary plus, minus, abs
+ - compare to timedelta
+ - multiply, divide by int/long
+
+ In addition, datetime supports subtraction of two datetime objects
+ returning a timedelta, and addition or subtraction of a datetime
+ and a timedelta giving a datetime.
+
+ Representation: (days, seconds, microseconds). Why? Because I
+ felt like it.
+ """
+ __slots__ = '_days', '_seconds', '_microseconds'
+
+ def __new__(cls, days=0, seconds=0, microseconds=0,
+ milliseconds=0, minutes=0, hours=0, weeks=0):
+ # Doing this efficiently and accurately in C is going to be difficult
+ # and error-prone, due to ubiquitous overflow possibilities, and that
+ # C double doesn't have enough bits of precision to represent
+ # microseconds over 10K years faithfully. The code here tries to make
+ # explicit where go-fast assumptions can be relied on, in order to
+ # guide the C implementation; it's way more convoluted than speed-
+ # ignoring auto-overflow-to-long idiomatic Python could be.
+
+ # XXX Check that all inputs are ints or floats.
+
+ # Final values, all integer.
+ # s and us fit in 32-bit signed ints; d isn't bounded.
+ d = s = us = 0
+
+ # Normalize everything to days, seconds, microseconds.
+ days += weeks*7
+ seconds += minutes*60 + hours*3600
+ microseconds += milliseconds*1000
+
+ # Get rid of all fractions, and normalize s and us.
+ # Take a deep breath <wink>.
+ if isinstance(days, float):
+ dayfrac, days = _math.modf(days)
+ daysecondsfrac, daysecondswhole = _math.modf(dayfrac * (24.*3600.))
+ assert daysecondswhole == int(daysecondswhole) # can't overflow
+ s = int(daysecondswhole)
+ assert days == int(days)
+ d = int(days)
+ else:
+ daysecondsfrac = 0.0
+ d = days
+ assert isinstance(daysecondsfrac, float)
+ assert abs(daysecondsfrac) <= 1.0
+ assert isinstance(d, int)
+ assert abs(s) <= 24 * 3600
+ # days isn't referenced again before redefinition
+
+ if isinstance(seconds, float):
+ secondsfrac, seconds = _math.modf(seconds)
+ assert seconds == int(seconds)
+ seconds = int(seconds)
+ secondsfrac += daysecondsfrac
+ assert abs(secondsfrac) <= 2.0
+ else:
+ secondsfrac = daysecondsfrac
+ # daysecondsfrac isn't referenced again
+ assert isinstance(secondsfrac, float)
+ assert abs(secondsfrac) <= 2.0
+
+ assert isinstance(seconds, int)
+ days, seconds = divmod(seconds, 24*3600)
+ d += days
+ s += int(seconds) # can't overflow
+ assert isinstance(s, int)
+ assert abs(s) <= 2 * 24 * 3600
+ # seconds isn't referenced again before redefinition
+
+ usdouble = secondsfrac * 1e6
+ assert abs(usdouble) < 2.1e6 # exact value not critical
+ # secondsfrac isn't referenced again
+
+ if isinstance(microseconds, float):
+ microseconds += usdouble
+ microseconds = round(microseconds, 0)
+ seconds, microseconds = divmod(microseconds, 1e6)
+ assert microseconds == int(microseconds)
+ assert seconds == int(seconds)
+ days, seconds = divmod(seconds, 24.*3600.)
+ assert days == int(days)
+ assert seconds == int(seconds)
+ d += int(days)
+ s += int(seconds) # can't overflow
+ assert isinstance(s, int)
+ assert abs(s) <= 3 * 24 * 3600
+ else:
+ seconds, microseconds = divmod(microseconds, 1000000)
+ days, seconds = divmod(seconds, 24*3600)
+ d += days
+ s += int(seconds) # can't overflow
+ assert isinstance(s, int)
+ assert abs(s) <= 3 * 24 * 3600
+ microseconds = float(microseconds)
+ microseconds += usdouble
+ microseconds = round(microseconds, 0)
+ assert abs(s) <= 3 * 24 * 3600
+ assert abs(microseconds) < 3.1e6
+
+ # Just a little bit of carrying possible for microseconds and seconds.
+ assert isinstance(microseconds, float)
+ assert int(microseconds) == microseconds
+ us = int(microseconds)
+ seconds, us = divmod(us, 1000000)
+ s += seconds # cant't overflow
+ assert isinstance(s, int)
+ days, s = divmod(s, 24*3600)
+ d += days
+
+ assert isinstance(d, int)
+ assert isinstance(s, int) and 0 <= s < 24*3600
+ assert isinstance(us, int) and 0 <= us < 1000000
+
+ self = object.__new__(cls)
+
+ self._days = d
+ self._seconds = s
+ self._microseconds = us
+ if abs(d) > 999999999:
+ raise OverflowError("timedelta # of days is too large: %d" % d)
+
+ return self
+
+ def __repr__(self):
+ if self._microseconds:
+ return "%s(%d, %d, %d)" % ('datetime.' + self.__class__.__name__,
+ self._days,
+ self._seconds,
+ self._microseconds)
+ if self._seconds:
+ return "%s(%d, %d)" % ('datetime.' + self.__class__.__name__,
+ self._days,
+ self._seconds)
+ return "%s(%d)" % ('datetime.' + self.__class__.__name__, self._days)
+
+ def __str__(self):
+ mm, ss = divmod(self._seconds, 60)
+ hh, mm = divmod(mm, 60)
+ s = "%d:%02d:%02d" % (hh, mm, ss)
+ if self._days:
+ def plural(n):
+ return n, abs(n) != 1 and "s" or ""
+ s = ("%d day%s, " % plural(self._days)) + s
+ if self._microseconds:
+ s = s + ".%06d" % self._microseconds
+ return s
+
+ def total_seconds(self):
+ """Total seconds in the duration."""
+ return ((self.days * 86400 + self.seconds)*10**6 +
+ self.microseconds) / 10**6
+
+ # Read-only field accessors
+ @property
+ def days(self):
+ """days"""
+ return self._days
+
+ @property
+ def seconds(self):
+ """seconds"""
+ return self._seconds
+
+ @property
+ def microseconds(self):
+ """microseconds"""
+ return self._microseconds
+
+ def __add__(self, other):
+ if isinstance(other, timedelta):
+ # for CPython compatibility, we cannot use
+ # our __class__ here, but need a real timedelta
+ return timedelta(self._days + other._days,
+ self._seconds + other._seconds,
+ self._microseconds + other._microseconds)
+ return NotImplemented
+
+ __radd__ = __add__
+
+ def __sub__(self, other):
+ if isinstance(other, timedelta):
+ return self + -other
+ return NotImplemented
+
+ def __rsub__(self, other):
+ if isinstance(other, timedelta):
+ return -self + other
+ return NotImplemented
+
+ def __neg__(self):
+ # for CPython compatibility, we cannot use
+ # our __class__ here, but need a real timedelta
+ return timedelta(-self._days,
+ -self._seconds,
+ -self._microseconds)
+
+ def __pos__(self):
+ return self
+
+ def __abs__(self):
+ if self._days < 0:
+ return -self
+ else:
+ return self
+
+ def __mul__(self, other):
+ if isinstance(other, int):
+ # for CPython compatibility, we cannot use
+ # our __class__ here, but need a real timedelta
+ return timedelta(self._days * other,
+ self._seconds * other,
+ self._microseconds * other)
+ if isinstance(other, float):
+ a, b = other.as_integer_ratio()
+ return self * a / b
+ return NotImplemented
+
+ __rmul__ = __mul__
+
+ def _to_microseconds(self):
+ return ((self._days * (24*3600) + self._seconds) * 1000000 +
+ self._microseconds)
+
+ def __floordiv__(self, other):
+ if not isinstance(other, (int, timedelta)):
+ return NotImplemented
+ usec = self._to_microseconds()
+ if isinstance(other, timedelta):
+ return usec // other._to_microseconds()
+ if isinstance(other, int):
+ return timedelta(0, 0, usec // other)
+
+ def __truediv__(self, other):
+ if not isinstance(other, (int, float, timedelta)):
+ return NotImplemented
+ usec = self._to_microseconds()
+ if isinstance(other, timedelta):
+ return usec / other._to_microseconds()
+ if isinstance(other, int):
+ return timedelta(0, 0, usec / other)
+ if isinstance(other, float):
+ a, b = other.as_integer_ratio()
+ return timedelta(0, 0, b * usec / a)
+
+ def __mod__(self, other):
+ if isinstance(other, timedelta):
+ r = self._to_microseconds() % other._to_microseconds()
+ return timedelta(0, 0, r)
+ return NotImplemented
+
+ def __divmod__(self, other):
+ if isinstance(other, timedelta):
+ q, r = divmod(self._to_microseconds(),
+ other._to_microseconds())
+ return q, timedelta(0, 0, r)
+ return NotImplemented
+
+ # Comparisons of timedelta objects with other.
+
+ def __eq__(self, other):
+ if isinstance(other, timedelta):
+ return self._cmp(other) == 0
+ else:
+ return False
+
+ def __ne__(self, other):
+ if isinstance(other, timedelta):
+ return self._cmp(other) != 0
+ else:
+ return True
+
+ def __le__(self, other):
+ if isinstance(other, timedelta):
+ return self._cmp(other) <= 0
+ else:
+ _cmperror(self, other)
+
+ def __lt__(self, other):
+ if isinstance(other, timedelta):
+ return self._cmp(other) < 0
+ else:
+ _cmperror(self, other)
+
+ def __ge__(self, other):
+ if isinstance(other, timedelta):
+ return self._cmp(other) >= 0
+ else:
+ _cmperror(self, other)
+
+ def __gt__(self, other):
+ if isinstance(other, timedelta):
+ return self._cmp(other) > 0
+ else:
+ _cmperror(self, other)
+
+ def _cmp(self, other):
+ assert isinstance(other, timedelta)
+ return _cmp(self._getstate(), other._getstate())
+
+ def __hash__(self):
+ return hash(self._getstate())
+
+ def __bool__(self):
+ return (self._days != 0 or
+ self._seconds != 0 or
+ self._microseconds != 0)
+
+ # Pickle support.
+
+ def _getstate(self):
+ return (self._days, self._seconds, self._microseconds)
+
+ def __reduce__(self):
+ return (self.__class__, self._getstate())
+
+timedelta.min = timedelta(-999999999)
+timedelta.max = timedelta(days=999999999, hours=23, minutes=59, seconds=59,
+ microseconds=999999)
+timedelta.resolution = timedelta(microseconds=1)
+
+class date:
+ """Concrete date type.
+
+ Constructors:
+
+ __new__()
+ fromtimestamp()
+ today()
+ fromordinal()
+
+ Operators:
+
+ __repr__, __str__
+ __cmp__, __hash__
+ __add__, __radd__, __sub__ (add/radd only with timedelta arg)
+
+ Methods:
+
+ timetuple()
+ toordinal()
+ weekday()
+ isoweekday(), isocalendar(), isoformat()
+ ctime()
+ strftime()
+
+ Properties (readonly):
+ year, month, day
+ """
+ __slots__ = '_year', '_month', '_day'
+
+ def __new__(cls, year, month=None, day=None):
+ """Constructor.
+
+ Arguments:
+
+ year, month, day (required, base 1)
+ """
+ if (isinstance(year, bytes) and len(year) == 4 and
+ 1 <= year[2] <= 12 and month is None): # Month is sane
+ # Pickle support
+ self = object.__new__(cls)
+ self.__setstate(year)
+ return self
+ _check_date_fields(year, month, day)
+ self = object.__new__(cls)
+ self._year = year
+ self._month = month
+ self._day = day
+ return self
+
+ # Additional constructors
+
+ @classmethod
+ def fromtimestamp(cls, t):
+ "Construct a date from a POSIX timestamp (like time.time())."
+ y, m, d, hh, mm, ss, weekday, jday, dst = _time.localtime(t)
+ return cls(y, m, d)
+
+ @classmethod
+ def today(cls):
+ "Construct a date from time.time()."
+ t = _time.time()
+ return cls.fromtimestamp(t)
+
+ @classmethod
+ def fromordinal(cls, n):
+ """Contruct a date from a proleptic Gregorian ordinal.
+
+ January 1 of year 1 is day 1. Only the year, month and day are
+ non-zero in the result.
+ """
+ y, m, d = _ord2ymd(n)
+ return cls(y, m, d)
+
+ # Conversions to string
+
+ def __repr__(self):
+ """Convert to formal string, for repr().
+
+ >>> dt = datetime(2010, 1, 1)
+ >>> repr(dt)
+ 'datetime.datetime(2010, 1, 1, 0, 0)'
+
+ >>> dt = datetime(2010, 1, 1, tzinfo=timezone.utc)
+ >>> repr(dt)
+ 'datetime.datetime(2010, 1, 1, 0, 0, tzinfo=datetime.timezone.utc)'
+ """
+ return "%s(%d, %d, %d)" % ('datetime.' + self.__class__.__name__,
+ self._year,
+ self._month,
+ self._day)
+ # XXX These shouldn't depend on time.localtime(), because that
+ # clips the usable dates to [1970 .. 2038). At least ctime() is
+ # easily done without using strftime() -- that's better too because
+ # strftime("%c", ...) is locale specific.
+
+
+ def ctime(self):
+ "Return ctime() style string."
+ weekday = self.toordinal() % 7 or 7
+ return "%s %s %2d 00:00:00 %04d" % (
+ _DAYNAMES[weekday],
+ _MONTHNAMES[self._month],
+ self._day, self._year)
+
+ def strftime(self, fmt):
+ "Format using strftime()."
+ return _wrap_strftime(self, fmt, self.timetuple())
+
+ def __format__(self, fmt):
+ if len(fmt) != 0:
+ return self.strftime(fmt)
+ return str(self)
+
+ def isoformat(self):
+ """Return the date formatted according to ISO.
+
+ This is 'YYYY-MM-DD'.
+
+ References:
+ - http://www.w3.org/TR/NOTE-datetime
+ - http://www.cl.cam.ac.uk/~mgk25/iso-time.html
+ """
+ return "%04d-%02d-%02d" % (self._year, self._month, self._day)
+
+ __str__ = isoformat
+
+ # Read-only field accessors
+ @property
+ def year(self):
+ """year (1-9999)"""
+ return self._year
+
+ @property
+ def month(self):
+ """month (1-12)"""
+ return self._month
+
+ @property
+ def day(self):
+ """day (1-31)"""
+ return self._day
+
+ # Standard conversions, __cmp__, __hash__ (and helpers)
+
+ def timetuple(self):
+ "Return local time tuple compatible with time.localtime()."
+ return _build_struct_time(self._year, self._month, self._day,
+ 0, 0, 0, -1)
+
+ def toordinal(self):
+ """Return proleptic Gregorian ordinal for the year, month and day.
+
+ January 1 of year 1 is day 1. Only the year, month and day values
+ contribute to the result.
+ """
+ return _ymd2ord(self._year, self._month, self._day)
+
+ def replace(self, year=None, month=None, day=None):
+ """Return a new date with new values for the specified fields."""
+ if year is None:
+ year = self._year
+ if month is None:
+ month = self._month
+ if day is None:
+ day = self._day
+ _check_date_fields(year, month, day)
+ return date(year, month, day)
+
+ # Comparisons of date objects with other.
+
+ def __eq__(self, other):
+ if isinstance(other, date):
+ return self._cmp(other) == 0
+ return NotImplemented
+
+ def __ne__(self, other):
+ if isinstance(other, date):
+ return self._cmp(other) != 0
+ return NotImplemented
+
+ def __le__(self, other):
+ if isinstance(other, date):
+ return self._cmp(other) <= 0
+ return NotImplemented
+
+ def __lt__(self, other):
+ if isinstance(other, date):
+ return self._cmp(other) < 0
+ return NotImplemented
+
+ def __ge__(self, other):
+ if isinstance(other, date):
+ return self._cmp(other) >= 0
+ return NotImplemented
+
+ def __gt__(self, other):
+ if isinstance(other, date):
+ return self._cmp(other) > 0
+ return NotImplemented
+
+ def _cmp(self, other):
+ assert isinstance(other, date)
+ y, m, d = self._year, self._month, self._day
+ y2, m2, d2 = other._year, other._month, other._day
+ return _cmp((y, m, d), (y2, m2, d2))
+
+ def __hash__(self):
+ "Hash."
+ return hash(self._getstate())
+
+ # Computations
+
+ def __add__(self, other):
+ "Add a date to a timedelta."
+ if isinstance(other, timedelta):
+ o = self.toordinal() + other.days
+ if 0 < o <= _MAXORDINAL:
+ return date.fromordinal(o)
+ raise OverflowError("result out of range")
+ return NotImplemented
+
+ __radd__ = __add__
+
+ def __sub__(self, other):
+ """Subtract two dates, or a date and a timedelta."""
+ if isinstance(other, timedelta):
+ return self + timedelta(-other.days)
+ if isinstance(other, date):
+ days1 = self.toordinal()
+ days2 = other.toordinal()
+ return timedelta(days1 - days2)
+ return NotImplemented
+
+ def weekday(self):
+ "Return day of the week, where Monday == 0 ... Sunday == 6."
+ return (self.toordinal() + 6) % 7
+
+ # Day-of-the-week and week-of-the-year, according to ISO
+
+ def isoweekday(self):
+ "Return day of the week, where Monday == 1 ... Sunday == 7."
+ # 1-Jan-0001 is a Monday
+ return self.toordinal() % 7 or 7
+
+ def isocalendar(self):
+ """Return a 3-tuple containing ISO year, week number, and weekday.
+
+ The first ISO week of the year is the (Mon-Sun) week
+ containing the year's first Thursday; everything else derives
+ from that.
+
+ The first week is 1; Monday is 1 ... Sunday is 7.
+
+ ISO calendar algorithm taken from
+ http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm
+ """
+ year = self._year
+ week1monday = _isoweek1monday(year)
+ today = _ymd2ord(self._year, self._month, self._day)
+ # Internally, week and day have origin 0
+ week, day = divmod(today - week1monday, 7)
+ if week < 0:
+ year -= 1
+ week1monday = _isoweek1monday(year)
+ week, day = divmod(today - week1monday, 7)
+ elif week >= 52:
+ if today >= _isoweek1monday(year+1):
+ year += 1
+ week = 0
+ return year, week+1, day+1
+
+ # Pickle support.
+
+ def _getstate(self):
+ yhi, ylo = divmod(self._year, 256)
+ return bytes([yhi, ylo, self._month, self._day]),
+
+ def __setstate(self, string):
+ if len(string) != 4 or not (1 <= string[2] <= 12):
+ raise TypeError("not enough arguments")
+ yhi, ylo, self._month, self._day = string
+ self._year = yhi * 256 + ylo
+
+ def __reduce__(self):
+ return (self.__class__, self._getstate())
+
+_date_class = date # so functions w/ args named "date" can get at the class
+
+date.min = date(1, 1, 1)
+date.max = date(9999, 12, 31)
+date.resolution = timedelta(days=1)
+
+class tzinfo:
+ """Abstract base class for time zone info classes.
+
+ Subclasses must override the name(), utcoffset() and dst() methods.
+ """
+ __slots__ = ()
+ def tzname(self, dt):
+ "datetime -> string name of time zone."
+ raise NotImplementedError("tzinfo subclass must override tzname()")
+
+ def utcoffset(self, dt):
+ "datetime -> minutes east of UTC (negative for west of UTC)"
+ raise NotImplementedError("tzinfo subclass must override utcoffset()")
+
+ def dst(self, dt):
+ """datetime -> DST offset in minutes east of UTC.
+
+ Return 0 if DST not in effect. utcoffset() must include the DST
+ offset.
+ """
+ raise NotImplementedError("tzinfo subclass must override dst()")
+
+ def fromutc(self, dt):
+ "datetime in UTC -> datetime in local time."
+
+ if not isinstance(dt, datetime):
+ raise TypeError("fromutc() requires a datetime argument")
+ if dt.tzinfo is not self:
+ raise ValueError("dt.tzinfo is not self")
+
+ dtoff = dt.utcoffset()
+ if dtoff is None:
+ raise ValueError("fromutc() requires a non-None utcoffset() "
+ "result")
+
+ # See the long comment block at the end of this file for an
+ # explanation of this algorithm.
+ dtdst = dt.dst()
+ if dtdst is None:
+ raise ValueError("fromutc() requires a non-None dst() result")
+ delta = dtoff - dtdst
+ if delta:
+ dt += delta
+ dtdst = dt.dst()
+ if dtdst is None:
+ raise ValueError("fromutc(): dt.dst gave inconsistent "
+ "results; cannot convert")
+ return dt + dtdst
+
+ # Pickle support.
+
+ def __reduce__(self):
+ getinitargs = getattr(self, "__getinitargs__", None)
+ if getinitargs:
+ args = getinitargs()
+ else:
+ args = ()
+ getstate = getattr(self, "__getstate__", None)
+ if getstate:
+ state = getstate()
+ else:
+ state = getattr(self, "__dict__", None) or None
+ if state is None:
+ return (self.__class__, args)
+ else:
+ return (self.__class__, args, state)
+
+_tzinfo_class = tzinfo
+
+class time:
+ """Time with time zone.
+
+ Constructors:
+
+ __new__()
+
+ Operators:
+
+ __repr__, __str__
+ __cmp__, __hash__
+
+ Methods:
+
+ strftime()
+ isoformat()
+ utcoffset()
+ tzname()
+ dst()
+
+ Properties (readonly):
+ hour, minute, second, microsecond, tzinfo
+ """
+
+ def __new__(cls, hour=0, minute=0, second=0, microsecond=0, tzinfo=None):
+ """Constructor.
+
+ Arguments:
+
+ hour, minute (required)
+ second, microsecond (default to zero)
+ tzinfo (default to None)
+ """
+ self = object.__new__(cls)
+ if isinstance(hour, bytes) and len(hour) == 6:
+ # Pickle support
+ self.__setstate(hour, minute or None)
+ return self
+ _check_tzinfo_arg(tzinfo)
+ _check_time_fields(hour, minute, second, microsecond)
+ self._hour = hour
+ self._minute = minute
+ self._second = second
+ self._microsecond = microsecond
+ self._tzinfo = tzinfo
+ return self
+
+ # Read-only field accessors
+ @property
+ def hour(self):
+ """hour (0-23)"""
+ return self._hour
+
+ @property
+ def minute(self):
+ """minute (0-59)"""
+ return self._minute
+
+ @property
+ def second(self):
+ """second (0-59)"""
+ return self._second
+
+ @property
+ def microsecond(self):
+ """microsecond (0-999999)"""
+ return self._microsecond
+
+ @property
+ def tzinfo(self):
+ """timezone info object"""
+ return self._tzinfo
+
+ # Standard conversions, __hash__ (and helpers)
+
+ # Comparisons of time objects with other.
+
+ def __eq__(self, other):
+ if isinstance(other, time):
+ return self._cmp(other) == 0
+ else:
+ return False
+
+ def __ne__(self, other):
+ if isinstance(other, time):
+ return self._cmp(other) != 0
+ else:
+ return True
+
+ def __le__(self, other):
+ if isinstance(other, time):
+ return self._cmp(other) <= 0
+ else:
+ _cmperror(self, other)
+
+ def __lt__(self, other):
+ if isinstance(other, time):
+ return self._cmp(other) < 0
+ else:
+ _cmperror(self, other)
+
+ def __ge__(self, other):
+ if isinstance(other, time):
+ return self._cmp(other) >= 0
+ else:
+ _cmperror(self, other)
+
+ def __gt__(self, other):
+ if isinstance(other, time):
+ return self._cmp(other) > 0
+ else:
+ _cmperror(self, other)
+
+ def _cmp(self, other):
+ assert isinstance(other, time)
+ mytz = self._tzinfo
+ ottz = other._tzinfo
+ myoff = otoff = None
+
+ if mytz is ottz:
+ base_compare = True
+ else:
+ myoff = self.utcoffset()
+ otoff = other.utcoffset()
+ base_compare = myoff == otoff
+
+ if base_compare:
+ return _cmp((self._hour, self._minute, self._second,
+ self._microsecond),
+ (other._hour, other._minute, other._second,
+ other._microsecond))
+ if myoff is None or otoff is None:
+ raise TypeError("cannot compare naive and aware times")
+ myhhmm = self._hour * 60 + self._minute - myoff//timedelta(minutes=1)
+ othhmm = other._hour * 60 + other._minute - otoff//timedelta(minutes=1)
+ return _cmp((myhhmm, self._second, self._microsecond),
+ (othhmm, other._second, other._microsecond))
+
+ def __hash__(self):
+ """Hash."""
+ tzoff = self.utcoffset()
+ if not tzoff: # zero or None
+ return hash(self._getstate()[0])
+ h, m = divmod(timedelta(hours=self.hour, minutes=self.minute) - tzoff,
+ timedelta(hours=1))
+ assert not m % timedelta(minutes=1), "whole minute"
+ m //= timedelta(minutes=1)
+ if 0 <= h < 24:
+ return hash(time(h, m, self.second, self.microsecond))
+ return hash((h, m, self.second, self.microsecond))
+
+ # Conversion to string
+
+ def _tzstr(self, sep=":"):
+ """Return formatted timezone offset (+xx:xx) or None."""
+ off = self.utcoffset()
+ if off is not None:
+ if off.days < 0:
+ sign = "-"
+ off = -off
+ else:
+ sign = "+"
+ hh, mm = divmod(off, timedelta(hours=1))
+ assert not mm % timedelta(minutes=1), "whole minute"
+ mm //= timedelta(minutes=1)
+ assert 0 <= hh < 24
+ off = "%s%02d%s%02d" % (sign, hh, sep, mm)
+ return off
+
+ def __repr__(self):
+ """Convert to formal string, for repr()."""
+ if self._microsecond != 0:
+ s = ", %d, %d" % (self._second, self._microsecond)
+ elif self._second != 0:
+ s = ", %d" % self._second
+ else:
+ s = ""
+ s= "%s(%d, %d%s)" % ('datetime.' + self.__class__.__name__,
+ self._hour, self._minute, s)
+ if self._tzinfo is not None:
+ assert s[-1:] == ")"
+ s = s[:-1] + ", tzinfo=%r" % self._tzinfo + ")"
+ return s
+
+ def isoformat(self):
+ """Return the time formatted according to ISO.
+
+ This is 'HH:MM:SS.mmmmmm+zz:zz', or 'HH:MM:SS+zz:zz' if
+ self.microsecond == 0.
+ """
+ s = _format_time(self._hour, self._minute, self._second,
+ self._microsecond)
+ tz = self._tzstr()
+ if tz:
+ s += tz
+ return s
+
+ __str__ = isoformat
+
+ def strftime(self, fmt):
+ """Format using strftime(). The date part of the timestamp passed
+ to underlying strftime should not be used.
+ """
+ # The year must be >= 1000 else Python's strftime implementation
+ # can raise a bogus exception.
+ timetuple = (1900, 1, 1,
+ self._hour, self._minute, self._second,
+ 0, 1, -1)
+ return _wrap_strftime(self, fmt, timetuple)
+
+ def __format__(self, fmt):
+ if len(fmt) != 0:
+ return self.strftime(fmt)
+ return str(self)
+
+ # Timezone functions
+
+ def utcoffset(self):
+ """Return the timezone offset in minutes east of UTC (negative west of
+ UTC)."""
+ if self._tzinfo is None:
+ return None
+ offset = self._tzinfo.utcoffset(None)
+ _check_utc_offset("utcoffset", offset)
+ return offset
+
+ def tzname(self):
+ """Return the timezone name.
+
+ Note that the name is 100% informational -- there's no requirement that
+ it mean anything in particular. For example, "GMT", "UTC", "-500",
+ "-5:00", "EDT", "US/Eastern", "America/New York" are all valid replies.
+ """
+ if self._tzinfo is None:
+ return None
+ name = self._tzinfo.tzname(None)
+ _check_tzname(name)
+ return name
+
+ def dst(self):
+ """Return 0 if DST is not in effect, or the DST offset (in minutes
+ eastward) if DST is in effect.
+
+ This is purely informational; the DST offset has already been added to
+ the UTC offset returned by utcoffset() if applicable, so there's no
+ need to consult dst() unless you're interested in displaying the DST
+ info.
+ """
+ if self._tzinfo is None:
+ return None
+ offset = self._tzinfo.dst(None)
+ _check_utc_offset("dst", offset)
+ return offset
+
+ def replace(self, hour=None, minute=None, second=None, microsecond=None,
+ tzinfo=True):
+ """Return a new time with new values for the specified fields."""
+ if hour is None:
+ hour = self.hour
+ if minute is None:
+ minute = self.minute
+ if second is None:
+ second = self.second
+ if microsecond is None:
+ microsecond = self.microsecond
+ if tzinfo is True:
+ tzinfo = self.tzinfo
+ _check_time_fields(hour, minute, second, microsecond)
+ _check_tzinfo_arg(tzinfo)
+ return time(hour, minute, second, microsecond, tzinfo)
+
+ def __bool__(self):
+ if self.second or self.microsecond:
+ return True
+ offset = self.utcoffset() or timedelta(0)
+ return timedelta(hours=self.hour, minutes=self.minute) != offset
+
+ # Pickle support.
+
+ def _getstate(self):
+ us2, us3 = divmod(self._microsecond, 256)
+ us1, us2 = divmod(us2, 256)
+ basestate = bytes([self._hour, self._minute, self._second,
+ us1, us2, us3])
+ if self._tzinfo is None:
+ return (basestate,)
+ else:
+ return (basestate, self._tzinfo)
+
+ def __setstate(self, string, tzinfo):
+ if len(string) != 6 or string[0] >= 24:
+ raise TypeError("an integer is required")
+ (self._hour, self._minute, self._second,
+ us1, us2, us3) = string
+ self._microsecond = (((us1 << 8) | us2) << 8) | us3
+ if tzinfo is None or isinstance(tzinfo, _tzinfo_class):
+ self._tzinfo = tzinfo
+ else:
+ raise TypeError("bad tzinfo state arg %r" % tzinfo)
+
+ def __reduce__(self):
+ return (time, self._getstate())
+
+_time_class = time # so functions w/ args named "time" can get at the class
+
+time.min = time(0, 0, 0)
+time.max = time(23, 59, 59, 999999)
+time.resolution = timedelta(microseconds=1)
+
+class datetime(date):
+ """datetime(year, month, day[, hour[, minute[, second[, microsecond[,tzinfo]]]]])
+
+ The year, month and day arguments are required. tzinfo may be None, or an
+ instance of a tzinfo subclass. The remaining arguments may be ints or longs.
+ """
+
+ __slots__ = date.__slots__ + (
+ '_hour', '_minute', '_second',
+ '_microsecond', '_tzinfo')
+ def __new__(cls, year, month=None, day=None, hour=0, minute=0, second=0,
+ microsecond=0, tzinfo=None):
+ if isinstance(year, bytes) and len(year) == 10:
+ # Pickle support
+ self = date.__new__(cls, year[:4])
+ self.__setstate(year, month)
+ return self
+ _check_tzinfo_arg(tzinfo)
+ _check_time_fields(hour, minute, second, microsecond)
+ self = date.__new__(cls, year, month, day)
+ self._hour = hour
+ self._minute = minute
+ self._second = second
+ self._microsecond = microsecond
+ self._tzinfo = tzinfo
+ return self
+
+ # Read-only field accessors
+ @property
+ def hour(self):
+ """hour (0-23)"""
+ return self._hour
+
+ @property
+ def minute(self):
+ """minute (0-59)"""
+ return self._minute
+
+ @property
+ def second(self):
+ """second (0-59)"""
+ return self._second
+
+ @property
+ def microsecond(self):
+ """microsecond (0-999999)"""
+ return self._microsecond
+
+ @property
+ def tzinfo(self):
+ """timezone info object"""
+ return self._tzinfo
+
+ @classmethod
+ def fromtimestamp(cls, t, tz=None):
+ """Construct a datetime from a POSIX timestamp (like time.time()).
+
+ A timezone info object may be passed in as well.
+ """
+
+ _check_tzinfo_arg(tz)
+
+ converter = _time.localtime if tz is None else _time.gmtime
+
+ t, frac = divmod(t, 1.0)
+ us = round(frac * 1e6)
+
+ # If timestamp is less than one microsecond smaller than a
+ # full second, us can be rounded up to 1000000. In this case,
+ # roll over to seconds, otherwise, ValueError is raised
+ # by the constructor.
+ if us == 1000000:
+ t += 1
+ us = 0
+ y, m, d, hh, mm, ss, weekday, jday, dst = converter(t)
+ ss = min(ss, 59) # clamp out leap seconds if the platform has them
+ result = cls(y, m, d, hh, mm, ss, us, tz)
+ if tz is not None:
+ result = tz.fromutc(result)
+ return result
+
+ @classmethod
+ def utcfromtimestamp(cls, t):
+ "Construct a UTC datetime from a POSIX timestamp (like time.time())."
+ t, frac = divmod(t, 1.0)
+ us = round(frac * 1e6)
+
+ # If timestamp is less than one microsecond smaller than a
+ # full second, us can be rounded up to 1000000. In this case,
+ # roll over to seconds, otherwise, ValueError is raised
+ # by the constructor.
+ if us == 1000000:
+ t += 1
+ us = 0
+ y, m, d, hh, mm, ss, weekday, jday, dst = _time.gmtime(t)
+ ss = min(ss, 59) # clamp out leap seconds if the platform has them
+ return cls(y, m, d, hh, mm, ss, us)
+
+ # XXX This is supposed to do better than we *can* do by using time.time(),
+ # XXX if the platform supports a more accurate way. The C implementation
+ # XXX uses gettimeofday on platforms that have it, but that isn't
+ # XXX available from Python. So now() may return different results
+ # XXX across the implementations.
+ @classmethod
+ def now(cls, tz=None):
+ "Construct a datetime from time.time() and optional time zone info."
+ t = _time.time()
+ return cls.fromtimestamp(t, tz)
+
+ @classmethod
+ def utcnow(cls):
+ "Construct a UTC datetime from time.time()."
+ t = _time.time()
+ return cls.utcfromtimestamp(t)
+
+ @classmethod
+ def combine(cls, date, time):
+ "Construct a datetime from a given date and a given time."
+ if not isinstance(date, _date_class):
+ raise TypeError("date argument must be a date instance")
+ if not isinstance(time, _time_class):
+ raise TypeError("time argument must be a time instance")
+ return cls(date.year, date.month, date.day,
+ time.hour, time.minute, time.second, time.microsecond,
+ time.tzinfo)
+
+ def timetuple(self):
+ "Return local time tuple compatible with time.localtime()."
+ dst = self.dst()
+ if dst is None:
+ dst = -1
+ elif dst:
+ dst = 1
+ else:
+ dst = 0
+ return _build_struct_time(self.year, self.month, self.day,
+ self.hour, self.minute, self.second,
+ dst)
+
+ def utctimetuple(self):
+ "Return UTC time tuple compatible with time.gmtime()."
+ offset = self.utcoffset()
+ if offset:
+ self -= offset
+ y, m, d = self.year, self.month, self.day
+ hh, mm, ss = self.hour, self.minute, self.second
+ return _build_struct_time(y, m, d, hh, mm, ss, 0)
+
+ def date(self):
+ "Return the date part."
+ return date(self._year, self._month, self._day)
+
+ def time(self):
+ "Return the time part, with tzinfo None."
+ return time(self.hour, self.minute, self.second, self.microsecond)
+
+ def timetz(self):
+ "Return the time part, with same tzinfo."
+ return time(self.hour, self.minute, self.second, self.microsecond,
+ self._tzinfo)
+
+ def replace(self, year=None, month=None, day=None, hour=None,
+ minute=None, second=None, microsecond=None, tzinfo=True):
+ """Return a new datetime with new values for the specified fields."""
+ if year is None:
+ year = self.year
+ if month is None:
+ month = self.month
+ if day is None:
+ day = self.day
+ if hour is None:
+ hour = self.hour
+ if minute is None:
+ minute = self.minute
+ if second is None:
+ second = self.second
+ if microsecond is None:
+ microsecond = self.microsecond
+ if tzinfo is True:
+ tzinfo = self.tzinfo
+ _check_date_fields(year, month, day)
+ _check_time_fields(hour, minute, second, microsecond)
+ _check_tzinfo_arg(tzinfo)
+ return datetime(year, month, day, hour, minute, second,
+ microsecond, tzinfo)
+
+ def astimezone(self, tz):
+ if not isinstance(tz, tzinfo):
+ raise TypeError("tz argument must be an instance of tzinfo")
+
+ mytz = self.tzinfo
+ if mytz is None:
+ raise ValueError("astimezone() requires an aware datetime")
+
+ if tz is mytz:
+ return self
+
+ # Convert self to UTC, and attach the new time zone object.
+ myoffset = self.utcoffset()
+ if myoffset is None:
+ raise ValueError("astimezone() requires an aware datetime")
+ utc = (self - myoffset).replace(tzinfo=tz)
+
+ # Convert from UTC to tz's local time.
+ return tz.fromutc(utc)
+
+ # Ways to produce a string.
+
+ def ctime(self):
+ "Return ctime() style string."
+ weekday = self.toordinal() % 7 or 7
+ return "%s %s %2d %02d:%02d:%02d %04d" % (
+ _DAYNAMES[weekday],
+ _MONTHNAMES[self._month],
+ self._day,
+ self._hour, self._minute, self._second,
+ self._year)
+
+ def isoformat(self, sep='T'):
+ """Return the time formatted according to ISO.
+
+ This is 'YYYY-MM-DD HH:MM:SS.mmmmmm', or 'YYYY-MM-DD HH:MM:SS' if
+ self.microsecond == 0.
+
+ If self.tzinfo is not None, the UTC offset is also attached, giving
+ 'YYYY-MM-DD HH:MM:SS.mmmmmm+HH:MM' or 'YYYY-MM-DD HH:MM:SS+HH:MM'.
+
+ Optional argument sep specifies the separator between date and
+ time, default 'T'.
+ """
+ s = ("%04d-%02d-%02d%c" % (self._year, self._month, self._day,
+ sep) +
+ _format_time(self._hour, self._minute, self._second,
+ self._microsecond))
+ off = self.utcoffset()
+ if off is not None:
+ if off.days < 0:
+ sign = "-"
+ off = -off
+ else:
+ sign = "+"
+ hh, mm = divmod(off, timedelta(hours=1))
+ assert not mm % timedelta(minutes=1), "whole minute"
+ mm //= timedelta(minutes=1)
+ s += "%s%02d:%02d" % (sign, hh, mm)
+ return s
+
+ def __repr__(self):
+ """Convert to formal string, for repr()."""
+ L = [self._year, self._month, self._day, # These are never zero
+ self._hour, self._minute, self._second, self._microsecond]
+ if L[-1] == 0:
+ del L[-1]
+ if L[-1] == 0:
+ del L[-1]
+ s = ", ".join(map(str, L))
+ s = "%s(%s)" % ('datetime.' + self.__class__.__name__, s)
+ if self._tzinfo is not None:
+ assert s[-1:] == ")"
+ s = s[:-1] + ", tzinfo=%r" % self._tzinfo + ")"
+ return s
+
+ def __str__(self):
+ "Convert to string, for str()."
+ return self.isoformat(sep=' ')
+
+ @classmethod
+ def strptime(cls, date_string, format):
+ 'string, format -> new datetime parsed from a string (like time.strptime()).'
+ import _strptime
+ return _strptime._strptime_datetime(cls, date_string, format)
+
+ def utcoffset(self):
+ """Return the timezone offset in minutes east of UTC (negative west of
+ UTC)."""
+ if self._tzinfo is None:
+ return None
+ offset = self._tzinfo.utcoffset(self)
+ _check_utc_offset("utcoffset", offset)
+ return offset
+
+ def tzname(self):
+ """Return the timezone name.
+
+ Note that the name is 100% informational -- there's no requirement that
+ it mean anything in particular. For example, "GMT", "UTC", "-500",
+ "-5:00", "EDT", "US/Eastern", "America/New York" are all valid replies.
+ """
+ name = _call_tzinfo_method(self._tzinfo, "tzname", self)
+ _check_tzname(name)
+ return name
+
+ def dst(self):
+ """Return 0 if DST is not in effect, or the DST offset (in minutes
+ eastward) if DST is in effect.
+
+ This is purely informational; the DST offset has already been added to
+ the UTC offset returned by utcoffset() if applicable, so there's no
+ need to consult dst() unless you're interested in displaying the DST
+ info.
+ """
+ if self._tzinfo is None:
+ return None
+ offset = self._tzinfo.dst(self)
+ _check_utc_offset("dst", offset)
+ return offset
+
+ # Comparisons of datetime objects with other.
+
+ def __eq__(self, other):
+ if isinstance(other, datetime):
+ return self._cmp(other) == 0
+ elif not isinstance(other, date):
+ return NotImplemented
+ else:
+ return False
+
+ def __ne__(self, other):
+ if isinstance(other, datetime):
+ return self._cmp(other) != 0
+ elif not isinstance(other, date):
+ return NotImplemented
+ else:
+ return True
+
+ def __le__(self, other):
+ if isinstance(other, datetime):
+ return self._cmp(other) <= 0
+ elif not isinstance(other, date):
+ return NotImplemented
+ else:
+ _cmperror(self, other)
+
+ def __lt__(self, other):
+ if isinstance(other, datetime):
+ return self._cmp(other) < 0
+ elif not isinstance(other, date):
+ return NotImplemented
+ else:
+ _cmperror(self, other)
+
+ def __ge__(self, other):
+ if isinstance(other, datetime):
+ return self._cmp(other) >= 0
+ elif not isinstance(other, date):
+ return NotImplemented
+ else:
+ _cmperror(self, other)
+
+ def __gt__(self, other):
+ if isinstance(other, datetime):
+ return self._cmp(other) > 0
+ elif not isinstance(other, date):
+ return NotImplemented
+ else:
+ _cmperror(self, other)
+
+ def _cmp(self, other):
+ assert isinstance(other, datetime)
+ mytz = self._tzinfo
+ ottz = other._tzinfo
+ myoff = otoff = None
+
+ if mytz is ottz:
+ base_compare = True
+ else:
+ if mytz is not None:
+ myoff = self.utcoffset()
+ if ottz is not None:
+ otoff = other.utcoffset()
+ base_compare = myoff == otoff
+
+ if base_compare:
+ return _cmp((self._year, self._month, self._day,
+ self._hour, self._minute, self._second,
+ self._microsecond),
+ (other._year, other._month, other._day,
+ other._hour, other._minute, other._second,
+ other._microsecond))
+ if myoff is None or otoff is None:
+ raise TypeError("cannot compare naive and aware datetimes")
+ # XXX What follows could be done more efficiently...
+ diff = self - other # this will take offsets into account
+ if diff.days < 0:
+ return -1
+ return diff and 1 or 0
+
+ def __add__(self, other):
+ "Add a datetime and a timedelta."
+ if not isinstance(other, timedelta):
+ return NotImplemented
+ delta = timedelta(self.toordinal(),
+ hours=self._hour,
+ minutes=self._minute,
+ seconds=self._second,
+ microseconds=self._microsecond)
+ delta += other
+ hour, rem = divmod(delta.seconds, 3600)
+ minute, second = divmod(rem, 60)
+ if 0 < delta.days <= _MAXORDINAL:
+ return datetime.combine(date.fromordinal(delta.days),
+ time(hour, minute, second,
+ delta.microseconds,
+ tzinfo=self._tzinfo))
+ raise OverflowError("result out of range")
+
+ __radd__ = __add__
+
+ def __sub__(self, other):
+ "Subtract two datetimes, or a datetime and a timedelta."
+ if not isinstance(other, datetime):
+ if isinstance(other, timedelta):
+ return self + -other
+ return NotImplemented
+
+ days1 = self.toordinal()
+ days2 = other.toordinal()
+ secs1 = self._second + self._minute * 60 + self._hour * 3600
+ secs2 = other._second + other._minute * 60 + other._hour * 3600
+ base = timedelta(days1 - days2,
+ secs1 - secs2,
+ self._microsecond - other._microsecond)
+ if self._tzinfo is other._tzinfo:
+ return base
+ myoff = self.utcoffset()
+ otoff = other.utcoffset()
+ if myoff == otoff:
+ return base
+ if myoff is None or otoff is None:
+ raise TypeError("cannot mix naive and timezone-aware time")
+ return base + otoff - myoff
+
+ def __hash__(self):
+ tzoff = self.utcoffset()
+ if tzoff is None:
+ return hash(self._getstate()[0])
+ days = _ymd2ord(self.year, self.month, self.day)
+ seconds = self.hour * 3600 + self.minute * 60 + self.second
+ return hash(timedelta(days, seconds, self.microsecond) - tzoff)
+
+ # Pickle support.
+
+ def _getstate(self):
+ yhi, ylo = divmod(self._year, 256)
+ us2, us3 = divmod(self._microsecond, 256)
+ us1, us2 = divmod(us2, 256)
+ basestate = bytes([yhi, ylo, self._month, self._day,
+ self._hour, self._minute, self._second,
+ us1, us2, us3])
+ if self._tzinfo is None:
+ return (basestate,)
+ else:
+ return (basestate, self._tzinfo)
+
+ def __setstate(self, string, tzinfo):
+ (yhi, ylo, self._month, self._day, self._hour,
+ self._minute, self._second, us1, us2, us3) = string
+ self._year = yhi * 256 + ylo
+ self._microsecond = (((us1 << 8) | us2) << 8) | us3
+ if tzinfo is None or isinstance(tzinfo, _tzinfo_class):
+ self._tzinfo = tzinfo
+ else:
+ raise TypeError("bad tzinfo state arg %r" % tzinfo)
+
+ def __reduce__(self):
+ return (self.__class__, self._getstate())
+
+
+datetime.min = datetime(1, 1, 1)
+datetime.max = datetime(9999, 12, 31, 23, 59, 59, 999999)
+datetime.resolution = timedelta(microseconds=1)
+
+
+def _isoweek1monday(year):
+ # Helper to calculate the day number of the Monday starting week 1
+ # XXX This could be done more efficiently
+ THURSDAY = 3
+ firstday = _ymd2ord(year, 1, 1)
+ firstweekday = (firstday + 6) % 7 # See weekday() above
+ week1monday = firstday - firstweekday
+ if firstweekday > THURSDAY:
+ week1monday += 7
+ return week1monday
+
+class timezone(tzinfo):
+ __slots__ = '_offset', '_name'
+
+ # Sentinel value to disallow None
+ _Omitted = object()
+ def __new__(cls, offset, name=_Omitted):
+ if not isinstance(offset, timedelta):
+ raise TypeError("offset must be a timedelta")
+ if name is cls._Omitted:
+ if not offset:
+ return cls.utc
+ name = None
+ elif not isinstance(name, str):
+ raise TypeError("name must be a string")
+ if not cls._minoffset <= offset <= cls._maxoffset:
+ raise ValueError("offset must be a timedelta"
+ " strictly between -timedelta(hours=24) and"
+ " timedelta(hours=24).")
+ if (offset.microseconds != 0 or
+ offset.seconds % 60 != 0):
+ raise ValueError("offset must be a timedelta"
+ " representing a whole number of minutes")
+ return cls._create(offset, name)
+
+ @classmethod
+ def _create(cls, offset, name=None):
+ self = tzinfo.__new__(cls)
+ self._offset = offset
+ self._name = name
+ return self
+
+ def __getinitargs__(self):
+ """pickle support"""
+ if self._name is None:
+ return (self._offset,)
+ return (self._offset, self._name)
+
+ def __eq__(self, other):
+ return self._offset == other._offset
+
+ def __hash__(self):
+ return hash(self._offset)
+
+ def __repr__(self):
+ """Convert to formal string, for repr().
+
+ >>> tz = timezone.utc
+ >>> repr(tz)
+ 'datetime.timezone.utc'
+ >>> tz = timezone(timedelta(hours=-5), 'EST')
+ >>> repr(tz)
+ "datetime.timezone(datetime.timedelta(-1, 68400), 'EST')"
+ """
+ if self is self.utc:
+ return 'datetime.timezone.utc'
+ if self._name is None:
+ return "%s(%r)" % ('datetime.' + self.__class__.__name__,
+ self._offset)
+ return "%s(%r, %r)" % ('datetime.' + self.__class__.__name__,
+ self._offset, self._name)
+
+ def __str__(self):
+ return self.tzname(None)
+
+ def utcoffset(self, dt):
+ if isinstance(dt, datetime) or dt is None:
+ return self._offset
+ raise TypeError("utcoffset() argument must be a datetime instance"
+ " or None")
+
+ def tzname(self, dt):
+ if isinstance(dt, datetime) or dt is None:
+ if self._name is None:
+ return self._name_from_offset(self._offset)
+ return self._name
+ raise TypeError("tzname() argument must be a datetime instance"
+ " or None")
+
+ def dst(self, dt):
+ if isinstance(dt, datetime) or dt is None:
+ return None
+ raise TypeError("dst() argument must be a datetime instance"
+ " or None")
+
+ def fromutc(self, dt):
+ if isinstance(dt, datetime):
+ if dt.tzinfo is not self:
+ raise ValueError("fromutc: dt.tzinfo "
+ "is not self")
+ return dt + self._offset
+ raise TypeError("fromutc() argument must be a datetime instance"
+ " or None")
+
+ _maxoffset = timedelta(hours=23, minutes=59)
+ _minoffset = -_maxoffset
+
+ @staticmethod
+ def _name_from_offset(delta):
+ if delta < timedelta(0):
+ sign = '-'
+ delta = -delta
+ else:
+ sign = '+'
+ hours, rest = divmod(delta, timedelta(hours=1))
+ minutes = rest // timedelta(minutes=1)
+ return 'UTC{}{:02d}:{:02d}'.format(sign, hours, minutes)
+
+timezone.utc = timezone._create(timedelta(0))
+timezone.min = timezone._create(timezone._minoffset)
+timezone.max = timezone._create(timezone._maxoffset)
+
+"""
+Some time zone algebra. For a datetime x, let
+ x.n = x stripped of its timezone -- its naive time.
+ x.o = x.utcoffset(), and assuming that doesn't raise an exception or
+ return None
+ x.d = x.dst(), and assuming that doesn't raise an exception or
+ return None
+ x.s = x's standard offset, x.o - x.d
+
+Now some derived rules, where k is a duration (timedelta).
+
+1. x.o = x.s + x.d
+ This follows from the definition of x.s.
+
+2. If x and y have the same tzinfo member, x.s = y.s.
+ This is actually a requirement, an assumption we need to make about
+ sane tzinfo classes.
+
+3. The naive UTC time corresponding to x is x.n - x.o.
+ This is again a requirement for a sane tzinfo class.
+
+4. (x+k).s = x.s
+ This follows from #2, and that datimetimetz+timedelta preserves tzinfo.
+
+5. (x+k).n = x.n + k
+ Again follows from how arithmetic is defined.
+
+Now we can explain tz.fromutc(x). Let's assume it's an interesting case
+(meaning that the various tzinfo methods exist, and don't blow up or return
+None when called).
+
+The function wants to return a datetime y with timezone tz, equivalent to x.
+x is already in UTC.
+
+By #3, we want
+
+ y.n - y.o = x.n [1]
+
+The algorithm starts by attaching tz to x.n, and calling that y. So
+x.n = y.n at the start. Then it wants to add a duration k to y, so that [1]
+becomes true; in effect, we want to solve [2] for k:
+
+ (y+k).n - (y+k).o = x.n [2]
+
+By #1, this is the same as
+
+ (y+k).n - ((y+k).s + (y+k).d) = x.n [3]
+
+By #5, (y+k).n = y.n + k, which equals x.n + k because x.n=y.n at the start.
+Substituting that into [3],
+
+ x.n + k - (y+k).s - (y+k).d = x.n; the x.n terms cancel, leaving
+ k - (y+k).s - (y+k).d = 0; rearranging,
+ k = (y+k).s - (y+k).d; by #4, (y+k).s == y.s, so
+ k = y.s - (y+k).d
+
+On the RHS, (y+k).d can't be computed directly, but y.s can be, and we
+approximate k by ignoring the (y+k).d term at first. Note that k can't be
+very large, since all offset-returning methods return a duration of magnitude
+less than 24 hours. For that reason, if y is firmly in std time, (y+k).d must
+be 0, so ignoring it has no consequence then.
+
+In any case, the new value is
+
+ z = y + y.s [4]
+
+It's helpful to step back at look at [4] from a higher level: it's simply
+mapping from UTC to tz's standard time.
+
+At this point, if
+
+ z.n - z.o = x.n [5]
+
+we have an equivalent time, and are almost done. The insecurity here is
+at the start of daylight time. Picture US Eastern for concreteness. The wall
+time jumps from 1:59 to 3:00, and wall hours of the form 2:MM don't make good
+sense then. The docs ask that an Eastern tzinfo class consider such a time to
+be EDT (because it's "after 2"), which is a redundant spelling of 1:MM EST
+on the day DST starts. We want to return the 1:MM EST spelling because that's
+the only spelling that makes sense on the local wall clock.
+
+In fact, if [5] holds at this point, we do have the standard-time spelling,
+but that takes a bit of proof. We first prove a stronger result. What's the
+difference between the LHS and RHS of [5]? Let
+
+ diff = x.n - (z.n - z.o) [6]
+
+Now
+ z.n = by [4]
+ (y + y.s).n = by #5
+ y.n + y.s = since y.n = x.n
+ x.n + y.s = since z and y are have the same tzinfo member,
+ y.s = z.s by #2
+ x.n + z.s
+
+Plugging that back into [6] gives
+
+ diff =
+ x.n - ((x.n + z.s) - z.o) = expanding
+ x.n - x.n - z.s + z.o = cancelling
+ - z.s + z.o = by #2
+ z.d
+
+So diff = z.d.
+
+If [5] is true now, diff = 0, so z.d = 0 too, and we have the standard-time
+spelling we wanted in the endcase described above. We're done. Contrarily,
+if z.d = 0, then we have a UTC equivalent, and are also done.
+
+If [5] is not true now, diff = z.d != 0, and z.d is the offset we need to
+add to z (in effect, z is in tz's standard time, and we need to shift the
+local clock into tz's daylight time).
+
+Let
+
+ z' = z + z.d = z + diff [7]
+
+and we can again ask whether
+
+ z'.n - z'.o = x.n [8]
+
+If so, we're done. If not, the tzinfo class is insane, according to the
+assumptions we've made. This also requires a bit of proof. As before, let's
+compute the difference between the LHS and RHS of [8] (and skipping some of
+the justifications for the kinds of substitutions we've done several times
+already):
+
+ diff' = x.n - (z'.n - z'.o) = replacing z'.n via [7]
+ x.n - (z.n + diff - z'.o) = replacing diff via [6]
+ x.n - (z.n + x.n - (z.n - z.o) - z'.o) =
+ x.n - z.n - x.n + z.n - z.o + z'.o = cancel x.n
+ - z.n + z.n - z.o + z'.o = cancel z.n
+ - z.o + z'.o = #1 twice
+ -z.s - z.d + z'.s + z'.d = z and z' have same tzinfo
+ z'.d - z.d
+
+So z' is UTC-equivalent to x iff z'.d = z.d at this point. If they are equal,
+we've found the UTC-equivalent so are done. In fact, we stop with [7] and
+return z', not bothering to compute z'.d.
+
+How could z.d and z'd differ? z' = z + z.d [7], so merely moving z' by
+a dst() offset, and starting *from* a time already in DST (we know z.d != 0),
+would have to change the result dst() returns: we start in DST, and moving
+a little further into it takes us out of DST.
+
+There isn't a sane case where this can happen. The closest it gets is at
+the end of DST, where there's an hour in UTC with no spelling in a hybrid
+tzinfo class. In US Eastern, that's 5:MM UTC = 0:MM EST = 1:MM EDT. During
+that hour, on an Eastern clock 1:MM is taken as being in standard time (6:MM
+UTC) because the docs insist on that, but 0:MM is taken as being in daylight
+time (4:MM UTC). There is no local time mapping to 5:MM UTC. The local
+clock jumps from 1:59 back to 1:00 again, and repeats the 1:MM hour in
+standard time. Since that's what the local clock *does*, we want to map both
+UTC hours 5:MM and 6:MM to 1:MM Eastern. The result is ambiguous
+in local time, but so it goes -- it's the way the local clock works.
+
+When x = 5:MM UTC is the input to this algorithm, x.o=0, y.o=-5 and y.d=0,
+so z=0:MM. z.d=60 (minutes) then, so [5] doesn't hold and we keep going.
+z' = z + z.d = 1:MM then, and z'.d=0, and z'.d - z.d = -60 != 0 so [8]
+(correctly) concludes that z' is not UTC-equivalent to x.
+
+Because we know z.d said z was in daylight time (else [5] would have held and
+we would have stopped then), and we know z.d != z'.d (else [8] would have held
+and we we have stopped then), and there are only 2 possible values dst() can
+return in Eastern, it follows that z'.d must be 0 (which it is in the example,
+but the reasoning doesn't depend on the example -- it depends on there being
+two possible dst() outcomes, one zero and the other non-zero). Therefore
+z' must be in standard time, and is the spelling we want in this case.
+
+Note again that z' is not UTC-equivalent as far as the hybrid tzinfo class is
+concerned (because it takes z' as being in standard time rather than the
+daylight time we intend here), but returning it gives the real-life "local
+clock repeats an hour" behavior when mapping the "unspellable" UTC hour into
+tz.
+
+When the input is 6:MM, z=1:MM and z.d=0, and we stop at once, again with
+the 1:MM standard time spelling we want.
+
+So how can this break? One of the assumptions must be violated. Two
+possibilities:
+
+1) [2] effectively says that y.s is invariant across all y belong to a given
+ time zone. This isn't true if, for political reasons or continental drift,
+ a region decides to change its base offset from UTC.
+
+2) There may be versions of "double daylight" time where the tail end of
+ the analysis gives up a step too early. I haven't thought about that
+ enough to say.
+
+In any case, it's clear that the default fromutc() is strong enough to handle
+"almost all" time zones: so long as the standard offset is invariant, it
+doesn't matter if daylight time transition points change from year to year, or
+if daylight time is skipped in some years; it doesn't matter how large or
+small dst() may get within its bounds; and it doesn't even matter if some
+perverse time zone returns a negative dst()). So a breaking case must be
+pretty bizarre, and a tzinfo subclass can override fromutc() if it is.
+"""
+try:
+ from _datetime import *
+except ImportError:
+ pass
+else:
+ # Clean up unused names
+ del (_DAYNAMES, _DAYS_BEFORE_MONTH, _DAYS_IN_MONTH,
+ _DI100Y, _DI400Y, _DI4Y, _MAXORDINAL, _MONTHNAMES,
+ _build_struct_time, _call_tzinfo_method, _check_date_fields,
+ _check_time_fields, _check_tzinfo_arg, _check_tzname,
+ _check_utc_offset, _cmp, _cmperror, _date_class, _days_before_month,
+ _days_before_year, _days_in_month, _format_time, _is_leap,
+ _isoweek1monday, _math, _ord2ymd, _time, _time_class, _tzinfo_class,
+ _wrap_strftime, _ymd2ord)
+ # XXX Since import * above excludes names that start with _,
+ # docstring does not get overwritten. In the future, it may be
+ # appropriate to maintain a single module level docstring and
+ # remove the following line.
+ from _datetime import __doc__
diff --git a/Lib/dbm/__init__.py b/Lib/dbm/__init__.py
index 99c1637..6e890f3 100644
--- a/Lib/dbm/__init__.py
+++ b/Lib/dbm/__init__.py
@@ -5,12 +5,11 @@ Use
import dbm
d = dbm.open(file, 'w', 0o666)
-The returned object is a dbm.bsd, dbm.gnu, dbm.ndbm or dbm.dumb
-object, dependent on the type of database being opened (determined by
-the whichdb function) in the case of an existing dbm. If the dbm does
-not exist and the create or new flag ('c' or 'n') was specified, the
-dbm type will be determined by the availability of the modules (tested
-in the above order).
+The returned object is a dbm.gnu, dbm.ndbm or dbm.dumb object, dependent on the
+type of database being opened (determined by the whichdb function) in the case
+of an existing dbm. If the dbm does not exist and the create or new flag ('c'
+or 'n') was specified, the dbm type will be determined by the availability of
+the modules (tested in the above order).
It has the following interface (key and data are strings):
@@ -36,7 +35,7 @@ Note: 'r' and 'w' fail if the database doesn't exist; 'c' creates it
only if it doesn't exist; and 'n' always creates a new database.
"""
-__all__ = ['open', 'whichdb', 'error', 'error']
+__all__ = ['open', 'whichdb', 'error']
import io
import os
@@ -47,7 +46,7 @@ import sys
class error(Exception):
pass
-_names = ['dbm.bsd', 'dbm.gnu', 'dbm.ndbm', 'dbm.dumb']
+_names = ['dbm.gnu', 'dbm.ndbm', 'dbm.dumb']
_defaultmod = None
_modules = {}
@@ -168,10 +167,6 @@ def whichdb(filename):
if magic == 0x13579ace:
return "dbm.gnu"
- ## Check for old Berkeley db hash file format v2
- #if magic in (0x00061561, 0x61150600):
- # return "bsddb185" # not supported anymore
-
# Later versions of Berkeley db hash file have a 12-byte pad in
# front of the file type
try:
@@ -179,10 +174,6 @@ def whichdb(filename):
except struct.error:
return ""
- ## Check for BSD hash
- #if magic in (0x00061561, 0x61150600):
- # return "dbm.bsd"
-
# Unknown
return ""
diff --git a/Lib/dbm/dumb.py b/Lib/dbm/dumb.py
index 4d804da..cfb9123 100644
--- a/Lib/dbm/dumb.py
+++ b/Lib/dbm/dumb.py
@@ -203,7 +203,7 @@ class _Database(collections.MutableMapping):
# The blocks used by the associated value are lost.
del self._index[key]
# XXX It's unclear why we do a _commit() here (the code always
- # XXX has, so I'm not changing it). _setitem__ doesn't try to
+ # XXX has, so I'm not changing it). __setitem__ doesn't try to
# XXX keep the directory file in synch. Why should we? Or
# XXX why shouldn't __setitem__?
self._commit()
@@ -232,7 +232,7 @@ class _Database(collections.MutableMapping):
__del__ = close
- def _chmod (self, file):
+ def _chmod(self, file):
if hasattr(self._os, 'chmod'):
self._os.chmod(file, self._mode)
diff --git a/Lib/decimal.py b/Lib/decimal.py
index 5d517ed..e6b70ca 100644
--- a/Lib/decimal.py
+++ b/Lib/decimal.py
@@ -31,7 +31,8 @@ issues associated with binary floating point. The package is especially
useful for financial applications or for contexts where users have
expectations that are at odds with binary floating point (for instance,
in binary floating point, 1.00 % 0.1 gives 0.09999999999999995 instead
-of the expected Decimal('0.00') returned by decimal floating point).
+of 0.0; Decimal('1.00') % Decimal('0.1') returns the expected
+Decimal('0.00')).
Here are some examples of using the decimal module:
@@ -131,6 +132,7 @@ __all__ = [
]
__version__ = '1.70' # Highest version of the spec this complies with
+ # See http://speleotrove.com/decimal/
import copy as _copy
import math as _math
@@ -648,8 +650,12 @@ class Decimal(object):
return self
if isinstance(value, float):
- raise TypeError("Cannot convert float in Decimal constructor. "
- "Use from_float class method.")
+ value = Decimal.from_float(value)
+ self._exp = value._exp
+ self._sign = value._sign
+ self._int = value._int
+ self._is_special = value._is_special
+ return self
raise TypeError("Cannot convert %r to Decimal" % value)
@@ -845,8 +851,11 @@ class Decimal(object):
# subject of what should happen for a comparison involving a NaN.
# We take the following approach:
#
- # == comparisons involving a NaN always return False
- # != comparisons involving a NaN always return True
+ # == comparisons involving a quiet NaN always return False
+ # != comparisons involving a quiet NaN always return True
+ # == or != comparisons involving a signaling NaN signal
+ # InvalidOperation, and return False or True as above if the
+ # InvalidOperation is not trapped.
# <, >, <= and >= comparisons involving a (quiet or signaling)
# NaN signal InvalidOperation, and return False if the
# InvalidOperation is not trapped.
@@ -854,25 +863,25 @@ class Decimal(object):
# This behavior is designed to conform as closely as possible to
# that specified by IEEE 754.
- def __eq__(self, other):
- other = _convert_other(other)
+ def __eq__(self, other, context=None):
+ self, other = _convert_for_comparison(self, other, equality_op=True)
if other is NotImplemented:
return other
- if self.is_nan() or other.is_nan():
+ if self._check_nans(other, context):
return False
return self._cmp(other) == 0
- def __ne__(self, other):
- other = _convert_other(other)
+ def __ne__(self, other, context=None):
+ self, other = _convert_for_comparison(self, other, equality_op=True)
if other is NotImplemented:
return other
- if self.is_nan() or other.is_nan():
+ if self._check_nans(other, context):
return True
return self._cmp(other) != 0
def __lt__(self, other, context=None):
- other = _convert_other(other)
+ self, other = _convert_for_comparison(self, other)
if other is NotImplemented:
return other
ans = self._compare_check_nans(other, context)
@@ -881,7 +890,7 @@ class Decimal(object):
return self._cmp(other) < 0
def __le__(self, other, context=None):
- other = _convert_other(other)
+ self, other = _convert_for_comparison(self, other)
if other is NotImplemented:
return other
ans = self._compare_check_nans(other, context)
@@ -890,7 +899,7 @@ class Decimal(object):
return self._cmp(other) <= 0
def __gt__(self, other, context=None):
- other = _convert_other(other)
+ self, other = _convert_for_comparison(self, other)
if other is NotImplemented:
return other
ans = self._compare_check_nans(other, context)
@@ -899,7 +908,7 @@ class Decimal(object):
return self._cmp(other) > 0
def __ge__(self, other, context=None):
- other = _convert_other(other)
+ self, other = _convert_for_comparison(self, other)
if other is NotImplemented:
return other
ans = self._compare_check_nans(other, context)
@@ -928,33 +937,29 @@ class Decimal(object):
def __hash__(self):
"""x.__hash__() <==> hash(x)"""
- # Decimal integers must hash the same as the ints
- #
- # The hash of a nonspecial noninteger Decimal must depend only
- # on the value of that Decimal, and not on its representation.
- # For example: hash(Decimal('100E-1')) == hash(Decimal('10')).
+
+ # In order to make sure that the hash of a Decimal instance
+ # agrees with the hash of a numerically equal integer, float
+ # or Fraction, we follow the rules for numeric hashes outlined
+ # in the documentation. (See library docs, 'Built-in Types').
if self._is_special:
- if self._isnan():
- raise TypeError('Cannot hash a NaN value.')
- return hash(str(self))
- if not self:
- return 0
- if self._isinteger():
- op = _WorkRep(self.to_integral_value())
- # to make computation feasible for Decimals with large
- # exponent, we use the fact that hash(n) == hash(m) for
- # any two nonzero integers n and m such that (i) n and m
- # have the same sign, and (ii) n is congruent to m modulo
- # 2**64-1. So we can replace hash((-1)**s*c*10**e) with
- # hash((-1)**s*c*pow(10, e, 2**64-1).
- return hash((-1)**op.sign*op.int*pow(10, op.exp, 2**64-1))
- # The value of a nonzero nonspecial Decimal instance is
- # faithfully represented by the triple consisting of its sign,
- # its adjusted exponent, and its coefficient with trailing
- # zeros removed.
- return hash((self._sign,
- self._exp+len(self._int),
- self._int.rstrip('0')))
+ if self.is_snan():
+ raise TypeError('Cannot hash a signaling NaN value.')
+ elif self.is_nan():
+ return _PyHASH_NAN
+ else:
+ if self._sign:
+ return -_PyHASH_INF
+ else:
+ return _PyHASH_INF
+
+ if self._exp >= 0:
+ exp_hash = pow(10, self._exp, _PyHASH_MODULUS)
+ else:
+ exp_hash = pow(_PyHASH_10INV, -self._exp, _PyHASH_MODULUS)
+ hash_ = int(self._int) * exp_hash % _PyHASH_MODULUS
+ ans = hash_ if self >= 0 else -hash_
+ return -2 if ans == -1 else ans
def as_tuple(self):
"""Represents the number as a triple tuple.
@@ -1585,9 +1590,9 @@ class Decimal(object):
"""Decapitate the payload of a NaN to fit the context"""
payload = self._int
- # maximum length of payload is precision if _clamp=0,
- # precision-1 if _clamp=1.
- max_payload_len = context.prec - context._clamp
+ # maximum length of payload is precision if clamp=0,
+ # precision-1 if clamp=1.
+ max_payload_len = context.prec - context.clamp
if len(payload) > max_payload_len:
payload = payload[len(payload)-max_payload_len:].lstrip('0')
return _dec_from_triple(self._sign, payload, self._exp, True)
@@ -1612,11 +1617,11 @@ class Decimal(object):
return Decimal(self)
# if self is zero then exponent should be between Etiny and
- # Emax if _clamp==0, and between Etiny and Etop if _clamp==1.
+ # Emax if clamp==0, and between Etiny and Etop if clamp==1.
Etiny = context.Etiny()
Etop = context.Etop()
if not self:
- exp_max = [context.Emax, Etop][context._clamp]
+ exp_max = [context.Emax, Etop][context.clamp]
new_exp = min(max(self._exp, Etiny), exp_max)
if new_exp != self._exp:
context._raise_error(Clamped)
@@ -1676,8 +1681,8 @@ class Decimal(object):
if self_is_subnormal:
context._raise_error(Subnormal)
- # fold down if _clamp == 1 and self has too few digits
- if context._clamp == 1 and self._exp > Etop:
+ # fold down if clamp == 1 and self has too few digits
+ if context.clamp == 1 and self._exp > Etop:
context._raise_error(Clamped)
self_padded = self._int + '0'*(self._exp - Etop)
return _dec_from_triple(self._sign, self_padded, Etop)
@@ -2428,7 +2433,7 @@ class Decimal(object):
if not dup:
return _dec_from_triple(dup._sign, '0', 0)
- exp_max = [context.Emax, context.Etop()][context._clamp]
+ exp_max = [context.Emax, context.Etop()][context.clamp]
end = len(dup._int)
exp = dup._exp
while dup._int[end-1] == '0' and exp < exp_max:
@@ -2942,6 +2947,7 @@ class Decimal(object):
def copy_sign(self, other):
"""Returns self with the sign of other."""
+ other = _convert_other(other, raiseit=True)
return _dec_from_triple(other._sign, self._int,
self._exp, self._is_special)
@@ -3646,12 +3652,12 @@ class Decimal(object):
return (self.__class__, (str(self),))
def __copy__(self):
- if type(self) == Decimal:
+ if type(self) is Decimal:
return self # I'm immutable; therefore I am my own clone
return self.__class__(str(self))
def __deepcopy__(self, memo):
- if type(self) == Decimal:
+ if type(self) is Decimal:
return self # My components are also immutable
return self.__class__(str(self))
@@ -3804,13 +3810,13 @@ class Context(object):
Emax - Maximum exponent
capitals - If 1, 1*10^1 is printed as 1E+1.
If 0, printed as 1e1
- _clamp - If 1, change exponents if too high (Default 0)
+ clamp - If 1, change exponents if too high (Default 0)
"""
def __init__(self, prec=None, rounding=None,
traps=None, flags=None,
Emin=None, Emax=None,
- capitals=None, _clamp=0,
+ capitals=None, clamp=None,
_ignored_flags=None):
# Set defaults; for everything except flags and _ignored_flags,
# inherit from DefaultContext.
@@ -3824,7 +3830,7 @@ class Context(object):
self.Emin = Emin if Emin is not None else dc.Emin
self.Emax = Emax if Emax is not None else dc.Emax
self.capitals = capitals if capitals is not None else dc.capitals
- self._clamp = _clamp if _clamp is not None else dc._clamp
+ self.clamp = clamp if clamp is not None else dc.clamp
if _ignored_flags is None:
self._ignored_flags = []
@@ -3849,7 +3855,8 @@ class Context(object):
"""Show the current context."""
s = []
s.append('Context(prec=%(prec)d, rounding=%(rounding)s, '
- 'Emin=%(Emin)d, Emax=%(Emax)d, capitals=%(capitals)d'
+ 'Emin=%(Emin)d, Emax=%(Emax)d, capitals=%(capitals)d, '
+ 'clamp=%(clamp)d'
% vars(self))
names = [f.__name__ for f, v in self.flags.items() if v]
s.append('flags=[' + ', '.join(names) + ']')
@@ -3866,17 +3873,39 @@ class Context(object):
"""Returns a shallow copy from self."""
nc = Context(self.prec, self.rounding, self.traps,
self.flags, self.Emin, self.Emax,
- self.capitals, self._clamp, self._ignored_flags)
+ self.capitals, self.clamp, self._ignored_flags)
return nc
def copy(self):
"""Returns a deep copy from self."""
nc = Context(self.prec, self.rounding, self.traps.copy(),
self.flags.copy(), self.Emin, self.Emax,
- self.capitals, self._clamp, self._ignored_flags)
+ self.capitals, self.clamp, self._ignored_flags)
return nc
__copy__ = copy
+ # _clamp is provided for backwards compatibility with third-party
+ # code. May be removed in Python >= 3.3.
+ def _get_clamp(self):
+ "_clamp mirrors the clamp attribute. Its use is deprecated."
+ import warnings
+ warnings.warn('Use of the _clamp attribute is deprecated. '
+ 'Please use clamp instead.',
+ DeprecationWarning)
+ return self.clamp
+
+ def _set_clamp(self, clamp):
+ "_clamp mirrors the clamp attribute. Its use is deprecated."
+ import warnings
+ warnings.warn('Use of the _clamp attribute is deprecated. '
+ 'Please use clamp instead.',
+ DeprecationWarning)
+ self.clamp = clamp
+
+ # don't bother with _del_clamp; no sane 3rd party code should
+ # be deleting the _clamp attribute
+ _clamp = property(_get_clamp, _set_clamp)
+
def _raise_error(self, condition, explanation = None, *args):
"""Handles an error
@@ -3959,7 +3988,7 @@ class Context(object):
"permitted.")
d = Decimal(num, context=self)
- if d._isnan() and len(d._int) > self.prec - self._clamp:
+ if d._isnan() and len(d._int) > self.prec - self.clamp:
return self._raise_error(ConversionSyntax,
"diagnostic info too long in NaN")
return d._fix(self)
@@ -3997,7 +4026,10 @@ class Context(object):
Decimal('101.5')
>>> ExtendedContext.abs(Decimal('-101.5'))
Decimal('101.5')
+ >>> ExtendedContext.abs(-1)
+ Decimal('1')
"""
+ a = _convert_other(a, raiseit=True)
return a.__abs__(context=self)
def add(self, a, b):
@@ -4007,8 +4039,19 @@ class Context(object):
Decimal('19.00')
>>> ExtendedContext.add(Decimal('1E+2'), Decimal('1.01E+4'))
Decimal('1.02E+4')
+ >>> ExtendedContext.add(1, Decimal(2))
+ Decimal('3')
+ >>> ExtendedContext.add(Decimal(8), 5)
+ Decimal('13')
+ >>> ExtendedContext.add(5, 5)
+ Decimal('10')
"""
- return a.__add__(b, context=self)
+ a = _convert_other(a, raiseit=True)
+ r = a.__add__(b, context=self)
+ if r is NotImplemented:
+ raise TypeError("Unable to convert %s to Decimal" % b)
+ else:
+ return r
def _apply(self, a):
return str(a._fix(self))
@@ -4050,7 +4093,14 @@ class Context(object):
Decimal('1')
>>> ExtendedContext.compare(Decimal('-3'), Decimal('2.1'))
Decimal('-1')
+ >>> ExtendedContext.compare(1, 2)
+ Decimal('-1')
+ >>> ExtendedContext.compare(Decimal(1), 2)
+ Decimal('-1')
+ >>> ExtendedContext.compare(1, Decimal(2))
+ Decimal('-1')
"""
+ a = _convert_other(a, raiseit=True)
return a.compare(b, context=self)
def compare_signal(self, a, b):
@@ -4078,7 +4128,14 @@ class Context(object):
Decimal('NaN')
>>> print(c.flags[InvalidOperation])
1
+ >>> c.compare_signal(-1, 2)
+ Decimal('-1')
+ >>> c.compare_signal(Decimal(-1), 2)
+ Decimal('-1')
+ >>> c.compare_signal(-1, Decimal(2))
+ Decimal('-1')
"""
+ a = _convert_other(a, raiseit=True)
return a.compare_signal(b, context=self)
def compare_total(self, a, b):
@@ -4100,7 +4157,14 @@ class Context(object):
Decimal('1')
>>> ExtendedContext.compare_total(Decimal('12.3'), Decimal('NaN'))
Decimal('-1')
+ >>> ExtendedContext.compare_total(1, 2)
+ Decimal('-1')
+ >>> ExtendedContext.compare_total(Decimal(1), 2)
+ Decimal('-1')
+ >>> ExtendedContext.compare_total(1, Decimal(2))
+ Decimal('-1')
"""
+ a = _convert_other(a, raiseit=True)
return a.compare_total(b)
def compare_total_mag(self, a, b):
@@ -4108,6 +4172,7 @@ class Context(object):
Like compare_total, but with operand's sign ignored and assumed to be 0.
"""
+ a = _convert_other(a, raiseit=True)
return a.compare_total_mag(b)
def copy_abs(self, a):
@@ -4117,17 +4182,23 @@ class Context(object):
Decimal('2.1')
>>> ExtendedContext.copy_abs(Decimal('-100'))
Decimal('100')
+ >>> ExtendedContext.copy_abs(-1)
+ Decimal('1')
"""
+ a = _convert_other(a, raiseit=True)
return a.copy_abs()
def copy_decimal(self, a):
- """Returns a copy of the decimal objet.
+ """Returns a copy of the decimal object.
>>> ExtendedContext.copy_decimal(Decimal('2.1'))
Decimal('2.1')
>>> ExtendedContext.copy_decimal(Decimal('-1.00'))
Decimal('-1.00')
+ >>> ExtendedContext.copy_decimal(1)
+ Decimal('1')
"""
+ a = _convert_other(a, raiseit=True)
return Decimal(a)
def copy_negate(self, a):
@@ -4137,7 +4208,10 @@ class Context(object):
Decimal('-101.5')
>>> ExtendedContext.copy_negate(Decimal('-101.5'))
Decimal('101.5')
+ >>> ExtendedContext.copy_negate(1)
+ Decimal('-1')
"""
+ a = _convert_other(a, raiseit=True)
return a.copy_negate()
def copy_sign(self, a, b):
@@ -4154,7 +4228,14 @@ class Context(object):
Decimal('-1.50')
>>> ExtendedContext.copy_sign(Decimal('-1.50'), Decimal('-7.33'))
Decimal('-1.50')
+ >>> ExtendedContext.copy_sign(1, -2)
+ Decimal('-1')
+ >>> ExtendedContext.copy_sign(Decimal(1), -2)
+ Decimal('-1')
+ >>> ExtendedContext.copy_sign(1, Decimal(-2))
+ Decimal('-1')
"""
+ a = _convert_other(a, raiseit=True)
return a.copy_sign(b)
def divide(self, a, b):
@@ -4180,8 +4261,19 @@ class Context(object):
Decimal('1000')
>>> ExtendedContext.divide(Decimal('2.40E+6'), Decimal('2'))
Decimal('1.20E+6')
+ >>> ExtendedContext.divide(5, 5)
+ Decimal('1')
+ >>> ExtendedContext.divide(Decimal(5), 5)
+ Decimal('1')
+ >>> ExtendedContext.divide(5, Decimal(5))
+ Decimal('1')
"""
- return a.__truediv__(b, context=self)
+ a = _convert_other(a, raiseit=True)
+ r = a.__truediv__(b, context=self)
+ if r is NotImplemented:
+ raise TypeError("Unable to convert %s to Decimal" % b)
+ else:
+ return r
def divide_int(self, a, b):
"""Divides two numbers and returns the integer part of the result.
@@ -4192,18 +4284,40 @@ class Context(object):
Decimal('3')
>>> ExtendedContext.divide_int(Decimal('1'), Decimal('0.3'))
Decimal('3')
+ >>> ExtendedContext.divide_int(10, 3)
+ Decimal('3')
+ >>> ExtendedContext.divide_int(Decimal(10), 3)
+ Decimal('3')
+ >>> ExtendedContext.divide_int(10, Decimal(3))
+ Decimal('3')
"""
- return a.__floordiv__(b, context=self)
+ a = _convert_other(a, raiseit=True)
+ r = a.__floordiv__(b, context=self)
+ if r is NotImplemented:
+ raise TypeError("Unable to convert %s to Decimal" % b)
+ else:
+ return r
def divmod(self, a, b):
- """Return (a // b, a % b)
+ """Return (a // b, a % b).
>>> ExtendedContext.divmod(Decimal(8), Decimal(3))
(Decimal('2'), Decimal('2'))
>>> ExtendedContext.divmod(Decimal(8), Decimal(4))
(Decimal('2'), Decimal('0'))
+ >>> ExtendedContext.divmod(8, 4)
+ (Decimal('2'), Decimal('0'))
+ >>> ExtendedContext.divmod(Decimal(8), 4)
+ (Decimal('2'), Decimal('0'))
+ >>> ExtendedContext.divmod(8, Decimal(4))
+ (Decimal('2'), Decimal('0'))
"""
- return a.__divmod__(b, context=self)
+ a = _convert_other(a, raiseit=True)
+ r = a.__divmod__(b, context=self)
+ if r is NotImplemented:
+ raise TypeError("Unable to convert %s to Decimal" % b)
+ else:
+ return r
def exp(self, a):
"""Returns e ** a.
@@ -4223,7 +4337,10 @@ class Context(object):
Decimal('2.00000000')
>>> c.exp(Decimal('+Infinity'))
Decimal('Infinity')
+ >>> c.exp(10)
+ Decimal('22026.4658')
"""
+ a =_convert_other(a, raiseit=True)
return a.exp(context=self)
def fma(self, a, b, c):
@@ -4239,7 +4356,14 @@ class Context(object):
Decimal('-8')
>>> ExtendedContext.fma(Decimal('888565290'), Decimal('1557.96930'), Decimal('-86087.7578'))
Decimal('1.38435736E+12')
+ >>> ExtendedContext.fma(1, 3, 4)
+ Decimal('7')
+ >>> ExtendedContext.fma(1, Decimal(3), 4)
+ Decimal('7')
+ >>> ExtendedContext.fma(1, 3, Decimal(4))
+ Decimal('7')
"""
+ a = _convert_other(a, raiseit=True)
return a.fma(b, c, context=self)
def is_canonical(self, a):
@@ -4269,7 +4393,10 @@ class Context(object):
False
>>> ExtendedContext.is_finite(Decimal('NaN'))
False
+ >>> ExtendedContext.is_finite(1)
+ True
"""
+ a = _convert_other(a, raiseit=True)
return a.is_finite()
def is_infinite(self, a):
@@ -4281,7 +4408,10 @@ class Context(object):
True
>>> ExtendedContext.is_infinite(Decimal('NaN'))
False
+ >>> ExtendedContext.is_infinite(1)
+ False
"""
+ a = _convert_other(a, raiseit=True)
return a.is_infinite()
def is_nan(self, a):
@@ -4294,7 +4424,10 @@ class Context(object):
True
>>> ExtendedContext.is_nan(Decimal('-sNaN'))
True
+ >>> ExtendedContext.is_nan(1)
+ False
"""
+ a = _convert_other(a, raiseit=True)
return a.is_nan()
def is_normal(self, a):
@@ -4314,7 +4447,10 @@ class Context(object):
False
>>> c.is_normal(Decimal('NaN'))
False
+ >>> c.is_normal(1)
+ True
"""
+ a = _convert_other(a, raiseit=True)
return a.is_normal(context=self)
def is_qnan(self, a):
@@ -4326,7 +4462,10 @@ class Context(object):
True
>>> ExtendedContext.is_qnan(Decimal('sNaN'))
False
+ >>> ExtendedContext.is_qnan(1)
+ False
"""
+ a = _convert_other(a, raiseit=True)
return a.is_qnan()
def is_signed(self, a):
@@ -4338,7 +4477,12 @@ class Context(object):
True
>>> ExtendedContext.is_signed(Decimal('-0'))
True
+ >>> ExtendedContext.is_signed(8)
+ False
+ >>> ExtendedContext.is_signed(-8)
+ True
"""
+ a = _convert_other(a, raiseit=True)
return a.is_signed()
def is_snan(self, a):
@@ -4351,7 +4495,10 @@ class Context(object):
False
>>> ExtendedContext.is_snan(Decimal('sNaN'))
True
+ >>> ExtendedContext.is_snan(1)
+ False
"""
+ a = _convert_other(a, raiseit=True)
return a.is_snan()
def is_subnormal(self, a):
@@ -4370,7 +4517,10 @@ class Context(object):
False
>>> c.is_subnormal(Decimal('NaN'))
False
+ >>> c.is_subnormal(1)
+ False
"""
+ a = _convert_other(a, raiseit=True)
return a.is_subnormal(context=self)
def is_zero(self, a):
@@ -4382,7 +4532,12 @@ class Context(object):
False
>>> ExtendedContext.is_zero(Decimal('-0E+2'))
True
+ >>> ExtendedContext.is_zero(1)
+ False
+ >>> ExtendedContext.is_zero(0)
+ True
"""
+ a = _convert_other(a, raiseit=True)
return a.is_zero()
def ln(self, a):
@@ -4401,7 +4556,10 @@ class Context(object):
Decimal('2.30258509')
>>> c.ln(Decimal('+Infinity'))
Decimal('Infinity')
+ >>> c.ln(1)
+ Decimal('0')
"""
+ a = _convert_other(a, raiseit=True)
return a.ln(context=self)
def log10(self, a):
@@ -4424,7 +4582,12 @@ class Context(object):
Decimal('1.84509804')
>>> c.log10(Decimal('+Infinity'))
Decimal('Infinity')
+ >>> c.log10(0)
+ Decimal('-Infinity')
+ >>> c.log10(1)
+ Decimal('0')
"""
+ a = _convert_other(a, raiseit=True)
return a.log10(context=self)
def logb(self, a):
@@ -4443,7 +4606,14 @@ class Context(object):
Decimal('-2')
>>> ExtendedContext.logb(Decimal('0'))
Decimal('-Infinity')
+ >>> ExtendedContext.logb(1)
+ Decimal('0')
+ >>> ExtendedContext.logb(10)
+ Decimal('1')
+ >>> ExtendedContext.logb(100)
+ Decimal('2')
"""
+ a = _convert_other(a, raiseit=True)
return a.logb(context=self)
def logical_and(self, a, b):
@@ -4463,7 +4633,14 @@ class Context(object):
Decimal('1000')
>>> ExtendedContext.logical_and(Decimal('1111'), Decimal('10'))
Decimal('10')
+ >>> ExtendedContext.logical_and(110, 1101)
+ Decimal('100')
+ >>> ExtendedContext.logical_and(Decimal(110), 1101)
+ Decimal('100')
+ >>> ExtendedContext.logical_and(110, Decimal(1101))
+ Decimal('100')
"""
+ a = _convert_other(a, raiseit=True)
return a.logical_and(b, context=self)
def logical_invert(self, a):
@@ -4479,7 +4656,10 @@ class Context(object):
Decimal('0')
>>> ExtendedContext.logical_invert(Decimal('101010101'))
Decimal('10101010')
+ >>> ExtendedContext.logical_invert(1101)
+ Decimal('111110010')
"""
+ a = _convert_other(a, raiseit=True)
return a.logical_invert(context=self)
def logical_or(self, a, b):
@@ -4499,7 +4679,14 @@ class Context(object):
Decimal('1110')
>>> ExtendedContext.logical_or(Decimal('1110'), Decimal('10'))
Decimal('1110')
+ >>> ExtendedContext.logical_or(110, 1101)
+ Decimal('1111')
+ >>> ExtendedContext.logical_or(Decimal(110), 1101)
+ Decimal('1111')
+ >>> ExtendedContext.logical_or(110, Decimal(1101))
+ Decimal('1111')
"""
+ a = _convert_other(a, raiseit=True)
return a.logical_or(b, context=self)
def logical_xor(self, a, b):
@@ -4519,10 +4706,17 @@ class Context(object):
Decimal('110')
>>> ExtendedContext.logical_xor(Decimal('1111'), Decimal('10'))
Decimal('1101')
+ >>> ExtendedContext.logical_xor(110, 1101)
+ Decimal('1011')
+ >>> ExtendedContext.logical_xor(Decimal(110), 1101)
+ Decimal('1011')
+ >>> ExtendedContext.logical_xor(110, Decimal(1101))
+ Decimal('1011')
"""
+ a = _convert_other(a, raiseit=True)
return a.logical_xor(b, context=self)
- def max(self, a,b):
+ def max(self, a, b):
"""max compares two values numerically and returns the maximum.
If either operand is a NaN then the general rules apply.
@@ -4539,14 +4733,34 @@ class Context(object):
Decimal('1')
>>> ExtendedContext.max(Decimal('7'), Decimal('NaN'))
Decimal('7')
+ >>> ExtendedContext.max(1, 2)
+ Decimal('2')
+ >>> ExtendedContext.max(Decimal(1), 2)
+ Decimal('2')
+ >>> ExtendedContext.max(1, Decimal(2))
+ Decimal('2')
"""
+ a = _convert_other(a, raiseit=True)
return a.max(b, context=self)
def max_mag(self, a, b):
- """Compares the values numerically with their sign ignored."""
+ """Compares the values numerically with their sign ignored.
+
+ >>> ExtendedContext.max_mag(Decimal('7'), Decimal('NaN'))
+ Decimal('7')
+ >>> ExtendedContext.max_mag(Decimal('7'), Decimal('-10'))
+ Decimal('-10')
+ >>> ExtendedContext.max_mag(1, -2)
+ Decimal('-2')
+ >>> ExtendedContext.max_mag(Decimal(1), -2)
+ Decimal('-2')
+ >>> ExtendedContext.max_mag(1, Decimal(-2))
+ Decimal('-2')
+ """
+ a = _convert_other(a, raiseit=True)
return a.max_mag(b, context=self)
- def min(self, a,b):
+ def min(self, a, b):
"""min compares two values numerically and returns the minimum.
If either operand is a NaN then the general rules apply.
@@ -4563,11 +4777,31 @@ class Context(object):
Decimal('1.0')
>>> ExtendedContext.min(Decimal('7'), Decimal('NaN'))
Decimal('7')
+ >>> ExtendedContext.min(1, 2)
+ Decimal('1')
+ >>> ExtendedContext.min(Decimal(1), 2)
+ Decimal('1')
+ >>> ExtendedContext.min(1, Decimal(29))
+ Decimal('1')
"""
+ a = _convert_other(a, raiseit=True)
return a.min(b, context=self)
def min_mag(self, a, b):
- """Compares the values numerically with their sign ignored."""
+ """Compares the values numerically with their sign ignored.
+
+ >>> ExtendedContext.min_mag(Decimal('3'), Decimal('-2'))
+ Decimal('-2')
+ >>> ExtendedContext.min_mag(Decimal('-3'), Decimal('NaN'))
+ Decimal('-3')
+ >>> ExtendedContext.min_mag(1, -2)
+ Decimal('1')
+ >>> ExtendedContext.min_mag(Decimal(1), -2)
+ Decimal('1')
+ >>> ExtendedContext.min_mag(1, Decimal(-2))
+ Decimal('1')
+ """
+ a = _convert_other(a, raiseit=True)
return a.min_mag(b, context=self)
def minus(self, a):
@@ -4581,16 +4815,19 @@ class Context(object):
Decimal('-1.3')
>>> ExtendedContext.minus(Decimal('-1.3'))
Decimal('1.3')
+ >>> ExtendedContext.minus(1)
+ Decimal('-1')
"""
+ a = _convert_other(a, raiseit=True)
return a.__neg__(context=self)
def multiply(self, a, b):
"""multiply multiplies two operands.
If either operand is a special value then the general rules apply.
- Otherwise, the operands are multiplied together ('long multiplication'),
- resulting in a number which may be as long as the sum of the lengths
- of the two operands.
+ Otherwise, the operands are multiplied together
+ ('long multiplication'), resulting in a number which may be as long as
+ the sum of the lengths of the two operands.
>>> ExtendedContext.multiply(Decimal('1.20'), Decimal('3'))
Decimal('3.60')
@@ -4602,8 +4839,19 @@ class Context(object):
Decimal('-0.0')
>>> ExtendedContext.multiply(Decimal('654321'), Decimal('654321'))
Decimal('4.28135971E+11')
+ >>> ExtendedContext.multiply(7, 7)
+ Decimal('49')
+ >>> ExtendedContext.multiply(Decimal(7), 7)
+ Decimal('49')
+ >>> ExtendedContext.multiply(7, Decimal(7))
+ Decimal('49')
"""
- return a.__mul__(b, context=self)
+ a = _convert_other(a, raiseit=True)
+ r = a.__mul__(b, context=self)
+ if r is NotImplemented:
+ raise TypeError("Unable to convert %s to Decimal" % b)
+ else:
+ return r
def next_minus(self, a):
"""Returns the largest representable number smaller than a.
@@ -4619,7 +4867,10 @@ class Context(object):
Decimal('-1.00000004')
>>> c.next_minus(Decimal('Infinity'))
Decimal('9.99999999E+999')
+ >>> c.next_minus(1)
+ Decimal('0.999999999')
"""
+ a = _convert_other(a, raiseit=True)
return a.next_minus(context=self)
def next_plus(self, a):
@@ -4636,7 +4887,10 @@ class Context(object):
Decimal('-1.00000002')
>>> c.next_plus(Decimal('-Infinity'))
Decimal('-9.99999999E+999')
+ >>> c.next_plus(1)
+ Decimal('1.00000001')
"""
+ a = _convert_other(a, raiseit=True)
return a.next_plus(context=self)
def next_toward(self, a, b):
@@ -4664,7 +4918,14 @@ class Context(object):
Decimal('-1.00000004')
>>> c.next_toward(Decimal('0.00'), Decimal('-0.0000'))
Decimal('-0.00')
+ >>> c.next_toward(0, 1)
+ Decimal('1E-1007')
+ >>> c.next_toward(Decimal(0), 1)
+ Decimal('1E-1007')
+ >>> c.next_toward(0, Decimal(1))
+ Decimal('1E-1007')
"""
+ a = _convert_other(a, raiseit=True)
return a.next_toward(b, context=self)
def normalize(self, a):
@@ -4685,7 +4946,10 @@ class Context(object):
Decimal('1.2E+2')
>>> ExtendedContext.normalize(Decimal('0.00'))
Decimal('0')
+ >>> ExtendedContext.normalize(6)
+ Decimal('6')
"""
+ a = _convert_other(a, raiseit=True)
return a.normalize(context=self)
def number_class(self, a):
@@ -4732,7 +4996,10 @@ class Context(object):
'NaN'
>>> c.number_class(Decimal('sNaN'))
'sNaN'
+ >>> c.number_class(123)
+ '+Normal'
"""
+ a = _convert_other(a, raiseit=True)
return a.number_class(context=self)
def plus(self, a):
@@ -4746,7 +5013,10 @@ class Context(object):
Decimal('1.3')
>>> ExtendedContext.plus(Decimal('-1.3'))
Decimal('-1.3')
+ >>> ExtendedContext.plus(-1)
+ Decimal('-1')
"""
+ a = _convert_other(a, raiseit=True)
return a.__pos__(context=self)
def power(self, a, b, modulo=None):
@@ -4815,8 +5085,19 @@ class Context(object):
Decimal('-0')
>>> c.power(Decimal('-23'), Decimal('0'), Decimal('65537'))
Decimal('1')
+ >>> ExtendedContext.power(7, 7)
+ Decimal('823543')
+ >>> ExtendedContext.power(Decimal(7), 7)
+ Decimal('823543')
+ >>> ExtendedContext.power(7, Decimal(7), 2)
+ Decimal('1')
"""
- return a.__pow__(b, modulo, context=self)
+ a = _convert_other(a, raiseit=True)
+ r = a.__pow__(b, modulo, context=self)
+ if r is NotImplemented:
+ raise TypeError("Unable to convert %s to Decimal" % b)
+ else:
+ return r
def quantize(self, a, b):
"""Returns a value equal to 'a' (rounded), having the exponent of 'b'.
@@ -4866,7 +5147,14 @@ class Context(object):
Decimal('2.2E+2')
>>> ExtendedContext.quantize(Decimal('217'), Decimal('1e+2'))
Decimal('2E+2')
+ >>> ExtendedContext.quantize(1, 2)
+ Decimal('1')
+ >>> ExtendedContext.quantize(Decimal(1), 2)
+ Decimal('1')
+ >>> ExtendedContext.quantize(1, Decimal(2))
+ Decimal('1')
"""
+ a = _convert_other(a, raiseit=True)
return a.quantize(b, context=self)
def radix(self):
@@ -4901,8 +5189,19 @@ class Context(object):
Decimal('0.1')
>>> ExtendedContext.remainder(Decimal('3.6'), Decimal('1.3'))
Decimal('1.0')
+ >>> ExtendedContext.remainder(22, 6)
+ Decimal('4')
+ >>> ExtendedContext.remainder(Decimal(22), 6)
+ Decimal('4')
+ >>> ExtendedContext.remainder(22, Decimal(6))
+ Decimal('4')
"""
- return a.__mod__(b, context=self)
+ a = _convert_other(a, raiseit=True)
+ r = a.__mod__(b, context=self)
+ if r is NotImplemented:
+ raise TypeError("Unable to convert %s to Decimal" % b)
+ else:
+ return r
def remainder_near(self, a, b):
"""Returns to be "a - b * n", where n is the integer nearest the exact
@@ -4928,7 +5227,14 @@ class Context(object):
Decimal('0.1')
>>> ExtendedContext.remainder_near(Decimal('3.6'), Decimal('1.3'))
Decimal('-0.3')
+ >>> ExtendedContext.remainder_near(3, 11)
+ Decimal('3')
+ >>> ExtendedContext.remainder_near(Decimal(3), 11)
+ Decimal('3')
+ >>> ExtendedContext.remainder_near(3, Decimal(11))
+ Decimal('3')
"""
+ a = _convert_other(a, raiseit=True)
return a.remainder_near(b, context=self)
def rotate(self, a, b):
@@ -4950,7 +5256,14 @@ class Context(object):
Decimal('123456789')
>>> ExtendedContext.rotate(Decimal('123456789'), Decimal('+2'))
Decimal('345678912')
+ >>> ExtendedContext.rotate(1333333, 1)
+ Decimal('13333330')
+ >>> ExtendedContext.rotate(Decimal(1333333), 1)
+ Decimal('13333330')
+ >>> ExtendedContext.rotate(1333333, Decimal(1))
+ Decimal('13333330')
"""
+ a = _convert_other(a, raiseit=True)
return a.rotate(b, context=self)
def same_quantum(self, a, b):
@@ -4967,7 +5280,14 @@ class Context(object):
False
>>> ExtendedContext.same_quantum(Decimal('Inf'), Decimal('-Inf'))
True
+ >>> ExtendedContext.same_quantum(10000, -1)
+ True
+ >>> ExtendedContext.same_quantum(Decimal(10000), -1)
+ True
+ >>> ExtendedContext.same_quantum(10000, Decimal(-1))
+ True
"""
+ a = _convert_other(a, raiseit=True)
return a.same_quantum(b)
def scaleb (self, a, b):
@@ -4979,8 +5299,15 @@ class Context(object):
Decimal('7.50')
>>> ExtendedContext.scaleb(Decimal('7.50'), Decimal('3'))
Decimal('7.50E+3')
+ >>> ExtendedContext.scaleb(1, 4)
+ Decimal('1E+4')
+ >>> ExtendedContext.scaleb(Decimal(1), 4)
+ Decimal('1E+4')
+ >>> ExtendedContext.scaleb(1, Decimal(4))
+ Decimal('1E+4')
"""
- return a.scaleb (b, context=self)
+ a = _convert_other(a, raiseit=True)
+ return a.scaleb(b, context=self)
def shift(self, a, b):
"""Returns a shifted copy of a, b times.
@@ -5002,7 +5329,14 @@ class Context(object):
Decimal('123456789')
>>> ExtendedContext.shift(Decimal('123456789'), Decimal('+2'))
Decimal('345678900')
+ >>> ExtendedContext.shift(88888888, 2)
+ Decimal('888888800')
+ >>> ExtendedContext.shift(Decimal(88888888), 2)
+ Decimal('888888800')
+ >>> ExtendedContext.shift(88888888, Decimal(2))
+ Decimal('888888800')
"""
+ a = _convert_other(a, raiseit=True)
return a.shift(b, context=self)
def sqrt(self, a):
@@ -5029,9 +5363,12 @@ class Context(object):
Decimal('2.64575131')
>>> ExtendedContext.sqrt(Decimal('10'))
Decimal('3.16227766')
+ >>> ExtendedContext.sqrt(2)
+ Decimal('1.41421356')
>>> ExtendedContext.prec
9
"""
+ a = _convert_other(a, raiseit=True)
return a.sqrt(context=self)
def subtract(self, a, b):
@@ -5043,14 +5380,26 @@ class Context(object):
Decimal('0.00')
>>> ExtendedContext.subtract(Decimal('1.3'), Decimal('2.07'))
Decimal('-0.77')
+ >>> ExtendedContext.subtract(8, 5)
+ Decimal('3')
+ >>> ExtendedContext.subtract(Decimal(8), 5)
+ Decimal('3')
+ >>> ExtendedContext.subtract(8, Decimal(5))
+ Decimal('3')
"""
- return a.__sub__(b, context=self)
+ a = _convert_other(a, raiseit=True)
+ r = a.__sub__(b, context=self)
+ if r is NotImplemented:
+ raise TypeError("Unable to convert %s to Decimal" % b)
+ else:
+ return r
def to_eng_string(self, a):
"""Converts a number to a string, using scientific notation.
The operation is not affected by the context.
"""
+ a = _convert_other(a, raiseit=True)
return a.to_eng_string(context=self)
def to_sci_string(self, a):
@@ -5058,6 +5407,7 @@ class Context(object):
The operation is not affected by the context.
"""
+ a = _convert_other(a, raiseit=True)
return a.__str__(context=self)
def to_integral_exact(self, a):
@@ -5087,6 +5437,7 @@ class Context(object):
>>> ExtendedContext.to_integral_exact(Decimal('-Inf'))
Decimal('-Infinity')
"""
+ a = _convert_other(a, raiseit=True)
return a.to_integral_exact(context=self)
def to_integral_value(self, a):
@@ -5115,6 +5466,7 @@ class Context(object):
>>> ExtendedContext.to_integral_value(Decimal('-Inf'))
Decimal('-Infinity')
"""
+ a = _convert_other(a, raiseit=True)
return a.to_integral_value(context=self)
# the method name changed, but we provide also the old one, for compatibility
@@ -5178,23 +5530,7 @@ def _normalize(op1, op2, prec = 0):
##### Integer arithmetic functions used by ln, log10, exp and __pow__ #####
-# This function from Tim Peters was taken from here:
-# http://mail.python.org/pipermail/python-list/1999-July/007758.html
-# The correction being in the function definition is for speed, and
-# the whole function is not resolved with math.log because of avoiding
-# the use of floats.
-def _nbits(n, correction = {
- '0': 4, '1': 3, '2': 2, '3': 2,
- '4': 1, '5': 1, '6': 1, '7': 1,
- '8': 0, '9': 0, 'a': 0, 'b': 0,
- 'c': 0, 'd': 0, 'e': 0, 'f': 0}):
- """Number of bits in binary representation of the positive integer n,
- or 0 if n == 0.
- """
- if n < 0:
- raise ValueError("The argument to _nbits should be nonnegative.")
- hex_n = "%x" % n
- return 4*len(hex_n) - correction[hex_n[0]]
+_nbits = int.bit_length
def _sqrt_nearest(n, a):
"""Closest integer to the square root of the positive integer n. a is
@@ -5516,19 +5852,56 @@ def _log10_lb(c, correction = {
##### Helper Functions ####################################################
-def _convert_other(other, raiseit=False):
+def _convert_other(other, raiseit=False, allow_float=False):
"""Convert other to Decimal.
Verifies that it's ok to use in an implicit construction.
+ If allow_float is true, allow conversion from float; this
+ is used in the comparison methods (__eq__ and friends).
+
"""
if isinstance(other, Decimal):
return other
if isinstance(other, int):
return Decimal(other)
+ if allow_float and isinstance(other, float):
+ return Decimal.from_float(other)
+
if raiseit:
raise TypeError("Unable to convert %s to Decimal" % other)
return NotImplemented
+def _convert_for_comparison(self, other, equality_op=False):
+ """Given a Decimal instance self and a Python object other, return
+ a pair (s, o) of Decimal instances such that "s op o" is
+ equivalent to "self op other" for any of the 6 comparison
+ operators "op".
+
+ """
+ if isinstance(other, Decimal):
+ return self, other
+
+ # Comparison with a Rational instance (also includes integers):
+ # self op n/d <=> self*d op n (for n and d integers, d positive).
+ # A NaN or infinity can be left unchanged without affecting the
+ # comparison result.
+ if isinstance(other, _numbers.Rational):
+ if not self._is_special:
+ self = _dec_from_triple(self._sign,
+ str(int(self._int) * other.denominator),
+ self._exp)
+ return self, Decimal(other.numerator)
+
+ # Comparisons with float and complex types. == and != comparisons
+ # with complex numbers should succeed, returning either True or False
+ # as appropriate. Other comparisons return NotImplemented.
+ if equality_op and isinstance(other, _numbers.Complex) and other.imag == 0:
+ other = other.real
+ if isinstance(other, float):
+ return self, Decimal.from_float(other)
+ return NotImplemented, NotImplemented
+
+
##### Setup Specific Contexts ############################################
# The default context prototype used by Context()
@@ -5540,7 +5913,8 @@ DefaultContext = Context(
flags=[],
Emax=999999999,
Emin=-999999999,
- capitals=1
+ capitals=1,
+ clamp=0
)
# Pre-made alternate contexts offered by the specification
@@ -5605,7 +5979,7 @@ _exact_half = re.compile('50*$').match
#
# A format specifier for Decimal looks like:
#
-# [[fill]align][sign][0][minimumwidth][,][.precision][type]
+# [[fill]align][sign][#][0][minimumwidth][,][.precision][type]
_parse_format_specifier_regex = re.compile(r"""\A
(?:
@@ -5613,6 +5987,7 @@ _parse_format_specifier_regex = re.compile(r"""\A
(?P<align>[<>=^])
)?
(?P<sign>[-+ ])?
+(?P<alt>\#)?
(?P<zeropad>0)?
(?P<minimumwidth>(?!0)\d+)?
(?P<thousands_sep>,)?
@@ -5670,7 +6045,10 @@ def _parse_format_specifier(format_spec, _localeconv=None):
raise ValueError("Alignment conflicts with '0' in "
"format specifier: " + format_spec)
format_dict['fill'] = fill or ' '
- format_dict['align'] = align or '<'
+ # PEP 3101 originally specified that the default alignment should
+ # be left; it was later agreed that right-aligned makes more sense
+ # for numeric types. See http://bugs.python.org/issue6857.
+ format_dict['align'] = align or '>'
# default sign handling: '-' for negative, '' for positive
if format_dict['sign'] is None:
@@ -5825,7 +6203,7 @@ def _format_number(is_negative, intpart, fracpart, exp, spec):
sign = _format_sign(is_negative, spec)
- if fracpart:
+ if fracpart or spec['alt']:
fracpart = spec['decimal_point'] + fracpart
if exp != 0 or spec['type'] in 'eE':
@@ -5856,8 +6234,19 @@ _NegativeOne = Decimal(-1)
# _SignedInfinity[sign] is infinity w/ that sign
_SignedInfinity = (_Infinity, _NegativeInfinity)
+# Constants related to the hash implementation; hash(x) is based
+# on the reduction of x modulo _PyHASH_MODULUS
+import sys
+_PyHASH_MODULUS = sys.hash_info.modulus
+# hash values to use for positive and negative infinities, and nans
+_PyHASH_INF = sys.hash_info.inf
+_PyHASH_NAN = sys.hash_info.nan
+del sys
+
+# _PyHASH_10INV is the inverse of 10 modulo the prime _PyHASH_MODULUS
+_PyHASH_10INV = pow(10, _PyHASH_MODULUS - 2, _PyHASH_MODULUS)
if __name__ == '__main__':
- import doctest, sys
- doctest.testmod(sys.modules[__name__])
+ import doctest, decimal
+ doctest.testmod(decimal)
diff --git a/Lib/difflib.py b/Lib/difflib.py
index 24a58a6..003e72e 100644
--- a/Lib/difflib.py
+++ b/Lib/difflib.py
@@ -1,4 +1,4 @@
-#! /usr/bin/env python
+#! /usr/bin/env python3
"""
Module difflib -- helpers for computing deltas between objects.
@@ -32,6 +32,7 @@ __all__ = ['get_close_matches', 'ndiff', 'restore', 'SequenceMatcher',
'Differ','IS_CHARACTER_JUNK', 'IS_LINE_JUNK', 'context_diff',
'unified_diff', 'HtmlDiff', 'Match']
+import warnings
import heapq
from collections import namedtuple as _namedtuple
@@ -150,7 +151,7 @@ class SequenceMatcher:
Return an upper bound on ratio() very quickly.
"""
- def __init__(self, isjunk=None, a='', b=''):
+ def __init__(self, isjunk=None, a='', b='', autojunk=True):
"""Construct a SequenceMatcher.
Optional arg isjunk is None (the default), or a one-argument
@@ -168,6 +169,10 @@ class SequenceMatcher:
Optional arg b is the second of two sequences to be compared. By
default, an empty string. The elements of b must be hashable. See
also .set_seqs() and .set_seq2().
+
+ Optional arg autojunk should be set to False to disable the
+ "automatic junk heuristic" that treats popular elements as junk
+ (see module documentation for more information).
"""
# Members:
@@ -178,7 +183,7 @@ class SequenceMatcher:
# we need to do to 'a' to change it into 'b'?"
# b2j
# for x in b, b2j[x] is a list of the indices (into b)
- # at which x appears; junk elements do not appear
+ # at which x appears; junk and popular elements do not appear
# fullbcount
# for x in b, fullbcount[x] == the number of times x
# appears in b; only materialized if really needed (used
@@ -200,17 +205,14 @@ class SequenceMatcher:
# subtle but helpful effects on the algorithm, which I'll
# get around to writing up someday <0.9 wink>.
# DON'T USE! Only __chain_b uses this. Use isbjunk.
- # isbjunk
- # for x in b, isbjunk(x) == isjunk(x) but much faster;
- # it's really the __contains__ method of a hidden dict.
- # DOES NOT WORK for x in a!
- # isbpopular
- # for x in b, isbpopular(x) is true iff b is reasonably long
- # (at least 200 elements) and x accounts for more than 1% of
- # its elements. DOES NOT WORK for x in a!
+ # bjunk
+ # the items in b for which isjunk is True.
+ # bpopular
+ # nonjunk items in b treated as junk by the heuristic (if used).
self.isjunk = isjunk
self.a = self.b = None
+ self.autojunk = autojunk
self.set_seqs(a, b)
def set_seqs(self, a, b):
@@ -287,7 +289,7 @@ class SequenceMatcher:
# from starting any matching block at a junk element ...
# also creates the fast isbjunk function ...
# b2j also does not contain entries for "popular" elements, meaning
- # elements that account for more than 1% of the total elements, and
+ # elements that account for more than 1 + 1% of the total elements, and
# when the sequence is reasonably large (>= 200 elements); this can
# be viewed as an adaptive notion of semi-junk, and yields an enormous
# speedup when, e.g., comparing program files with hundreds of
@@ -308,44 +310,46 @@ class SequenceMatcher:
# out the junk later is much cheaper than building b2j "right"
# from the start.
b = self.b
- n = len(b)
self.b2j = b2j = {}
- populardict = {}
- for i, elt in enumerate(b):
- if elt in b2j:
- indices = b2j[elt]
- if n >= 200 and len(indices) * 100 > n:
- populardict[elt] = 1
- del indices[:]
- else:
- indices.append(i)
- else:
- b2j[elt] = [i]
- # Purge leftover indices for popular elements.
- for elt in populardict:
- del b2j[elt]
+ for i, elt in enumerate(b):
+ indices = b2j.setdefault(elt, [])
+ indices.append(i)
- # Now b2j.keys() contains elements uniquely, and especially when
- # the sequence is a string, that's usually a good deal smaller
- # than len(string). The difference is the number of isjunk calls
- # saved.
+ # Purge junk elements
+ self.bjunk = junk = set()
isjunk = self.isjunk
- junkdict = {}
if isjunk:
- for d in populardict, b2j:
- for elt in list(d.keys()):
- if isjunk(elt):
- junkdict[elt] = 1
- del d[elt]
-
- # Now for x in b, isjunk(x) == x in junkdict, but the
- # latter is much faster. Note too that while there may be a
- # lot of junk in the sequence, the number of *unique* junk
- # elements is probably small. So the memory burden of keeping
- # this dict alive is likely trivial compared to the size of b2j.
- self.isbjunk = junkdict.__contains__
- self.isbpopular = populardict.__contains__
+ for elt in b2j.keys():
+ if isjunk(elt):
+ junk.add(elt)
+ for elt in junk: # separate loop avoids separate list of keys
+ del b2j[elt]
+
+ # Purge popular elements that are not junk
+ self.bpopular = popular = set()
+ n = len(b)
+ if self.autojunk and n >= 200:
+ ntest = n // 100 + 1
+ for elt, idxs in b2j.items():
+ if len(idxs) > ntest:
+ popular.add(elt)
+ for elt in popular: # ditto; as fast for 1% deletion
+ del b2j[elt]
+
+ def isbjunk(self, item):
+ "Deprecated; use 'item in SequenceMatcher().bjunk'."
+ warnings.warn("'SequenceMatcher().isbjunk(item)' is deprecated;\n"
+ "use 'item in SMinstance.bjunk' instead.",
+ DeprecationWarning, 2)
+ return item in self.bjunk
+
+ def isbpopular(self, item):
+ "Deprecated; use 'item in SequenceMatcher().bpopular'."
+ warnings.warn("'SequenceMatcher().isbpopular(item)' is deprecated;\n"
+ "use 'item in SMinstance.bpopular' instead.",
+ DeprecationWarning, 2)
+ return item in self.bpopular
def find_longest_match(self, alo, ahi, blo, bhi):
"""Find longest matching block in a[alo:ahi] and b[blo:bhi].
@@ -403,7 +407,7 @@ class SequenceMatcher:
# Windiff ends up at the same place as diff, but by pairing up
# the unique 'b's and then matching the first two 'a's.
- a, b, b2j, isbjunk = self.a, self.b, self.b2j, self.isbjunk
+ a, b, b2j, isbjunk = self.a, self.b, self.b2j, self.bjunk.__contains__
besti, bestj, bestsize = alo, blo, 0
# find longest junk-free match
# during an iteration of the loop, j2len[j] = length of longest
@@ -1160,18 +1164,18 @@ def unified_diff(a, b, fromfile='', tofile='', fromfiledate='',
The unidiff format normally has a header for filenames and modification
times. Any or all of these may be specified using strings for
- 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'. The modification
- times are normally expressed in the format returned by time.ctime().
+ 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.
+ The modification times are normally expressed in the ISO 8601 format.
Example:
>>> for line in unified_diff('one two three four'.split(),
... 'zero one tree four'.split(), 'Original', 'Current',
- ... 'Sat Jan 26 23:30:50 1991', 'Fri Jun 06 10:20:52 2003',
+ ... '2005-01-26 23:30:50', '2010-04-02 10:20:52',
... lineterm=''):
- ... print(line)
- --- Original Sat Jan 26 23:30:50 1991
- +++ Current Fri Jun 06 10:20:52 2003
+ ... print(line) # doctest: +NORMALIZE_WHITESPACE
+ --- Original 2005-01-26 23:30:50
+ +++ Current 2010-04-02 10:20:52
@@ -1,4 +1,4 @@
+zero
one
@@ -1184,8 +1188,10 @@ def unified_diff(a, b, fromfile='', tofile='', fromfiledate='',
started = False
for group in SequenceMatcher(None,a,b).get_grouped_opcodes(n):
if not started:
- yield '--- %s %s%s' % (fromfile, fromfiledate, lineterm)
- yield '+++ %s %s%s' % (tofile, tofiledate, lineterm)
+ fromdate = '\t%s' % fromfiledate if fromfiledate else ''
+ todate = '\t%s' % tofiledate if tofiledate else ''
+ yield '--- %s%s%s' % (fromfile, fromdate, lineterm)
+ yield '+++ %s%s%s' % (tofile, todate, lineterm)
started = True
i1, i2, j1, j2 = group[0][1], group[-1][2], group[0][3], group[-1][4]
yield "@@ -%d,%d +%d,%d @@%s" % (i1+1, i2-i1, j1+1, j2-j1, lineterm)
@@ -1223,17 +1229,16 @@ def context_diff(a, b, fromfile='', tofile='',
The context diff format normally has a header for filenames and
modification times. Any or all of these may be specified using
strings for 'fromfile', 'tofile', 'fromfiledate', and 'tofiledate'.
- The modification times are normally expressed in the format returned
- by time.ctime(). If not specified, the strings default to blanks.
+ The modification times are normally expressed in the ISO 8601 format.
+ If not specified, the strings default to blanks.
Example:
>>> print(''.join(context_diff('one\ntwo\nthree\nfour\n'.splitlines(1),
- ... 'zero\none\ntree\nfour\n'.splitlines(1), 'Original', 'Current',
- ... 'Sat Jan 26 23:30:50 1991', 'Fri Jun 06 10:22:46 2003')),
+ ... 'zero\none\ntree\nfour\n'.splitlines(1), 'Original', 'Current')),
... end="")
- *** Original Sat Jan 26 23:30:50 1991
- --- Current Fri Jun 06 10:22:46 2003
+ *** Original
+ --- Current
***************
*** 1,4 ****
one
@@ -1251,8 +1256,10 @@ def context_diff(a, b, fromfile='', tofile='',
prefixmap = {'insert':'+ ', 'delete':'- ', 'replace':'! ', 'equal':' '}
for group in SequenceMatcher(None,a,b).get_grouped_opcodes(n):
if not started:
- yield '*** %s %s%s' % (fromfile, fromfiledate, lineterm)
- yield '--- %s %s%s' % (tofile, tofiledate, lineterm)
+ fromdate = '\t%s' % fromfiledate if fromfiledate else ''
+ todate = '\t%s' % tofiledate if tofiledate else ''
+ yield '*** %s%s%s' % (fromfile, fromdate, lineterm)
+ yield '--- %s%s%s' % (tofile, todate, lineterm)
started = True
yield '***************%s' % (lineterm,)
diff --git a/Lib/dis.py b/Lib/dis.py
index 2b400dc..f64bae6 100644
--- a/Lib/dis.py
+++ b/Lib/dis.py
@@ -6,10 +6,25 @@ import types
from opcode import *
from opcode import __all__ as _opcodes_all
-__all__ = ["dis", "disassemble", "distb", "disco",
- "findlinestarts", "findlabels"] + _opcodes_all
+__all__ = ["code_info", "dis", "disassemble", "distb", "disco",
+ "findlinestarts", "findlabels", "show_code"] + _opcodes_all
del _opcodes_all
+_have_code = (types.MethodType, types.FunctionType, types.CodeType, type)
+
+def _try_compile(source, name):
+ """Attempts to compile the given source, first as an expression and
+ then as a statement if the first approach fails.
+
+ Utility function to accept strings in functions that otherwise
+ expect code objects
+ """
+ try:
+ c = compile(source, name, 'eval')
+ except SyntaxError:
+ c = compile(source, name, 'exec')
+ return c
+
def dis(x=None):
"""Disassemble classes, methods, functions, or code.
@@ -19,25 +34,26 @@ def dis(x=None):
if x is None:
distb()
return
- if hasattr(x, '__func__'):
+ if hasattr(x, '__func__'): # Method
x = x.__func__
- if hasattr(x, '__code__'):
+ if hasattr(x, '__code__'): # Function
x = x.__code__
- if hasattr(x, '__dict__'):
+ if hasattr(x, '__dict__'): # Class or module
items = sorted(x.__dict__.items())
for name, x1 in items:
- if isinstance(x1, (types.MethodType, types.FunctionType,
- types.CodeType, type)):
+ if isinstance(x1, _have_code):
print("Disassembly of %s:" % name)
try:
dis(x1)
except TypeError as msg:
print("Sorry:", msg)
print()
- elif hasattr(x, 'co_code'):
+ elif hasattr(x, 'co_code'): # Code object
disassemble(x)
- elif isinstance(x, (bytes, bytearray)):
- disassemble_string(x)
+ elif isinstance(x, (bytes, bytearray)): # Raw bytecode
+ _disassemble_bytes(x)
+ elif isinstance(x, str): # Source code
+ _disassemble_str(x)
else:
raise TypeError("don't know how to disassemble %s objects" %
type(x).__name__)
@@ -52,9 +68,10 @@ def distb(tb=None):
while tb.tb_next: tb = tb.tb_next
disassemble(tb.tb_frame.f_code, tb.tb_lasti)
-# XXX This duplicates information from code.h, also duplicated in inspect.py.
-# XXX Maybe this ought to be put in a central location, like opcode.py?
-flag2name = {
+# The inspect module interrogates this dictionary to build its
+# list of CO_* constants. It is also used by pretty_flags to
+# turn the co_flags field into a human readable list.
+COMPILER_FLAG_NAMES = {
1: "OPTIMIZED",
2: "NEWLOCALS",
4: "VARARGS",
@@ -70,7 +87,7 @@ def pretty_flags(flags):
for i in range(32):
flag = 1<<i
if flags & flag:
- names.append(flag2name.get(flag, hex(flag)))
+ names.append(COMPILER_FLAG_NAMES.get(flag, hex(flag)))
flags ^= flag
if not flags:
break
@@ -78,35 +95,54 @@ def pretty_flags(flags):
names.append(hex(flags))
return ", ".join(names)
-def show_code(co):
- """Show details about a code object."""
- print("Name: ", co.co_name)
- print("Filename: ", co.co_filename)
- print("Argument count: ", co.co_argcount)
- print("Kw-only arguments:", co.co_kwonlyargcount)
- print("Number of locals: ", co.co_nlocals)
- print("Stack size: ", co.co_stacksize)
- print("Flags: ", pretty_flags(co.co_flags))
+def code_info(x):
+ """Formatted details of methods, functions, or code."""
+ if hasattr(x, '__func__'): # Method
+ x = x.__func__
+ if hasattr(x, '__code__'): # Function
+ x = x.__code__
+ if isinstance(x, str): # Source code
+ x = _try_compile(x, "<code_info>")
+ if hasattr(x, 'co_code'): # Code object
+ return _format_code_info(x)
+ else:
+ raise TypeError("don't know how to disassemble %s objects" %
+ type(x).__name__)
+
+def _format_code_info(co):
+ lines = []
+ lines.append("Name: %s" % co.co_name)
+ lines.append("Filename: %s" % co.co_filename)
+ lines.append("Argument count: %s" % co.co_argcount)
+ lines.append("Kw-only arguments: %s" % co.co_kwonlyargcount)
+ lines.append("Number of locals: %s" % co.co_nlocals)
+ lines.append("Stack size: %s" % co.co_stacksize)
+ lines.append("Flags: %s" % pretty_flags(co.co_flags))
if co.co_consts:
- print("Constants:")
+ lines.append("Constants:")
for i_c in enumerate(co.co_consts):
- print("%4d: %r" % i_c)
+ lines.append("%4d: %r" % i_c)
if co.co_names:
- print("Names:")
+ lines.append("Names:")
for i_n in enumerate(co.co_names):
- print("%4d: %s" % i_n)
+ lines.append("%4d: %s" % i_n)
if co.co_varnames:
- print("Variable names:")
+ lines.append("Variable names:")
for i_n in enumerate(co.co_varnames):
- print("%4d: %s" % i_n)
+ lines.append("%4d: %s" % i_n)
if co.co_freevars:
- print("Free variables:")
+ lines.append("Free variables:")
for i_n in enumerate(co.co_freevars):
- print("%4d: %s" % i_n)
+ lines.append("%4d: %s" % i_n)
if co.co_cellvars:
- print("Cell variables:")
+ lines.append("Cell variables:")
for i_n in enumerate(co.co_cellvars):
- print("%4d: %s" % i_n)
+ lines.append("%4d: %s" % i_n)
+ return "\n".join(lines)
+
+def show_code(co):
+ """Print details of methods, functions, or code to stdout."""
+ print(code_info(co))
def disassemble(co, lasti=-1):
"""Disassemble a code object."""
@@ -156,7 +192,7 @@ def disassemble(co, lasti=-1):
print('(' + free[oparg] + ')', end=' ')
print()
-def disassemble_string(code, lasti=-1, varnames=None, names=None,
+def _disassemble_bytes(code, lasti=-1, varnames=None, names=None,
constants=None):
labels = findlabels(code)
n = len(code)
@@ -195,6 +231,10 @@ def disassemble_string(code, lasti=-1, varnames=None, names=None,
print('(' + cmp_op[oparg] + ')', end=' ')
print()
+def _disassemble_str(source):
+ """Compile the source string, then disassemble the code object."""
+ disassemble(_try_compile(source, '<dis>'))
+
disco = disassemble # XXX For backwards compatibility
def findlabels(code):
diff --git a/Lib/distutils/__init__.py b/Lib/distutils/__init__.py
index 6f55f01..49b6d51 100644
--- a/Lib/distutils/__init__.py
+++ b/Lib/distutils/__init__.py
@@ -15,5 +15,5 @@ __revision__ = "$Id$"
# Updated automatically by the Python release process.
#
#--start constants--
-__version__ = "3.1.3"
+__version__ = "3.2"
#--end constants--
diff --git a/Lib/distutils/command/bdist_msi.py b/Lib/distutils/command/bdist_msi.py
index 8a458d8..b11957a 100644
--- a/Lib/distutils/command/bdist_msi.py
+++ b/Lib/distutils/command/bdist_msi.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
# Copyright (C) 2005, 2006 Martin von Löwis
# Licensed to PSF under a Contributor Agreement.
# The bdist_wininst command proper
diff --git a/Lib/distutils/command/build_ext.py b/Lib/distutils/command/build_ext.py
index 502b39a..fb31648 100644
--- a/Lib/distutils/command/build_ext.py
+++ b/Lib/distutils/command/build_ext.py
@@ -754,9 +754,9 @@ class build_ext(Command):
else:
from distutils import sysconfig
if sysconfig.get_config_var('Py_ENABLE_SHARED'):
- template = "python%d.%d"
- pythonlib = (template %
- (sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff))
+ pythonlib = 'python{}.{}{}'.format(
+ sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff,
+ sys.abiflags)
return ext.libraries + [pythonlib]
else:
return ext.libraries
diff --git a/Lib/distutils/command/install.py b/Lib/distutils/command/install.py
index 2a905d9..bdc3a09 100644
--- a/Lib/distutils/command/install.py
+++ b/Lib/distutils/command/install.py
@@ -48,7 +48,7 @@ INSTALL_SCHEMES = {
'unix_prefix': {
'purelib': '$base/lib/python$py_version_short/site-packages',
'platlib': '$platbase/lib/python$py_version_short/site-packages',
- 'headers': '$base/include/python$py_version_short/$dist_name',
+ 'headers': '$base/include/python$py_version_short$abiflags/$dist_name',
'scripts': '$base/bin',
'data' : '$base',
},
@@ -60,14 +60,6 @@ INSTALL_SCHEMES = {
'data' : '$base',
},
'nt': WINDOWS_SCHEME,
- 'mac': {
- 'purelib': '$base/Lib/site-packages',
- 'platlib': '$base/Lib/site-packages',
- 'headers': '$base/Include/$dist_name',
- 'scripts': '$base/Scripts',
- 'data' : '$base',
- },
-
'os2': {
'purelib': '$base/Lib/site-packages',
'platlib': '$base/Lib/site-packages',
@@ -90,15 +82,8 @@ if HAS_USER_SITE:
INSTALL_SCHEMES['unix_user'] = {
'purelib': '$usersite',
'platlib': '$usersite',
- 'headers': '$userbase/include/python$py_version_short/$dist_name',
- 'scripts': '$userbase/bin',
- 'data' : '$userbase',
- }
-
- INSTALL_SCHEMES['mac_user'] = {
- 'purelib': '$usersite',
- 'platlib': '$usersite',
- 'headers': '$userbase/$py_version_short/include/$dist_name',
+ 'headers':
+ '$userbase/include/python$py_version_short$abiflags/$dist_name',
'scripts': '$userbase/bin',
'data' : '$userbase',
}
@@ -328,6 +313,11 @@ class install(Command):
py_version = sys.version.split()[0]
(prefix, exec_prefix) = get_config_vars('prefix', 'exec_prefix')
+ try:
+ abiflags = sys.abiflags
+ except AttributeError:
+ # sys.abiflags may not be defined on all platforms.
+ abiflags = ''
self.config_vars = {'dist_name': self.distribution.get_name(),
'dist_version': self.distribution.get_version(),
'dist_fullname': self.distribution.get_fullname(),
@@ -338,6 +328,7 @@ class install(Command):
'prefix': prefix,
'sys_exec_prefix': exec_prefix,
'exec_prefix': exec_prefix,
+ 'abiflags': abiflags,
}
if HAS_USER_SITE:
diff --git a/Lib/distutils/file_util.py b/Lib/distutils/file_util.py
index c36e712..e1eb932 100644
--- a/Lib/distutils/file_util.py
+++ b/Lib/distutils/file_util.py
@@ -130,15 +130,6 @@ def copy_file(src, dst, preserve_mode=1, preserve_times=1, update=0,
if dry_run:
return (dst, 1)
- # On Mac OS, use the native file copy routine
- if os.name == 'mac':
- import macostools
- try:
- macostools.copy(src, dst, 0, preserve_times)
- except os.error as exc:
- raise DistutilsFileError(
- "could not copy '%s' to '%s': %s" % (src, dst, exc.args[-1]))
-
# If linking (hard or symbolic), use the appropriate system call
# (Unix only, of course, but that's the caller's responsibility)
elif link == 'hard':
diff --git a/Lib/distutils/sysconfig.py b/Lib/distutils/sysconfig.py
index 9842d26..897b7d6 100644
--- a/Lib/distutils/sysconfig.py
+++ b/Lib/distutils/sysconfig.py
@@ -11,7 +11,6 @@ Email: <fdrake@acm.org>
__revision__ = "$Id$"
-import io
import os
import re
import sys
@@ -25,7 +24,7 @@ EXEC_PREFIX = os.path.normpath(sys.exec_prefix)
# Path to the base directory of the project. On Windows the binary may
# live in project/PCBuild9. If we're dealing with an x64 Windows build,
# it'll live in project/PCbuild/amd64.
-project_base = os.path.dirname(os.path.realpath(sys.executable))
+project_base = os.path.dirname(os.path.abspath(sys.executable))
if os.name == "nt" and "pcbuild" in project_base[-8:].lower():
project_base = os.path.abspath(os.path.join(project_base, os.path.pardir))
# PC/VS7.1
@@ -49,6 +48,18 @@ def _python_build():
return False
python_build = _python_build()
+# Calculate the build qualifier flags if they are defined. Adding the flags
+# to the include and lib directories only makes sense for an installation, not
+# an in-source build.
+build_flags = ''
+try:
+ if not python_build:
+ build_flags = sys.abiflags
+except AttributeError:
+ # It's not a configure-based build, so the sys module doesn't have
+ # this attribute, which is fine.
+ pass
+
def get_python_version():
"""Return a string containing the major and minor Python version,
leaving off the patchlevel. Sample return values could be '1.5'
@@ -77,20 +88,16 @@ def get_python_inc(plat_specific=0, prefix=None):
# the build directory may not be the source directory, we
# must use "srcdir" from the makefile to find the "Include"
# directory.
- base = os.path.dirname(os.path.realpath(sys.executable))
+ base = os.path.dirname(os.path.abspath(sys.executable))
if plat_specific:
return base
else:
incdir = os.path.join(get_config_var('srcdir'), 'Include')
return os.path.normpath(incdir)
- return os.path.join(prefix, "include", "python" + get_python_version())
+ python_dir = 'python' + get_python_version() + build_flags
+ return os.path.join(prefix, "include", python_dir)
elif os.name == "nt":
return os.path.join(prefix, "include")
- elif os.name == "mac":
- if plat_specific:
- return os.path.join(prefix, "Mac", "Include")
- else:
- return os.path.join(prefix, "Include")
elif os.name == "os2":
return os.path.join(prefix, "Include")
else:
@@ -131,17 +138,6 @@ def get_python_lib(plat_specific=0, standard_lib=0, prefix=None):
return prefix
else:
return os.path.join(prefix, "Lib", "site-packages")
- elif os.name == "mac":
- if plat_specific:
- if standard_lib:
- return os.path.join(prefix, "Lib", "lib-dynload")
- else:
- return os.path.join(prefix, "Lib", "site-packages")
- else:
- if standard_lib:
- return os.path.join(prefix, "Lib")
- else:
- return os.path.join(prefix, "Lib", "site-packages")
elif os.name == "os2":
if standard_lib:
return os.path.join(prefix, "Lib")
@@ -223,10 +219,10 @@ def get_config_h_filename():
def get_makefile_filename():
"""Return full pathname of installed Makefile from the Python build."""
if python_build:
- return os.path.join(os.path.dirname(os.path.realpath(sys.executable)),
- "Makefile")
+ return os.path.join(os.path.dirname(sys.executable), "Makefile")
lib_dir = get_python_lib(plat_specific=1, standard_lib=1)
- return os.path.join(lib_dir, "config", "Makefile")
+ config_file = 'config-{}{}'.format(get_python_version(), build_flags)
+ return os.path.join(lib_dir, config_file, 'Makefile')
def parse_config_h(fp, g=None):
@@ -272,7 +268,7 @@ def parse_makefile(fn, g=None):
used instead of a new dictionary.
"""
from distutils.text_file import TextFile
- fp = TextFile(fn, strip_comments=1, skip_blanks=1, join_lines=1)
+ fp = TextFile(fn, strip_comments=1, skip_blanks=1, join_lines=1, errors="surrogateescape")
if g is None:
g = {}
@@ -301,6 +297,12 @@ def parse_makefile(fn, g=None):
else:
done[n] = v
+ # Variables with a 'PY_' prefix in the makefile. These need to
+ # be made available without that prefix through sysconfig.
+ # Special care is needed to ensure that variable expansion works, even
+ # if the expansion uses the name without a prefix.
+ renamed_variables = ('CFLAGS', 'LDFLAGS', 'CPPFLAGS')
+
# do variable interpolation here
while notdone:
for name in list(notdone):
@@ -317,6 +319,16 @@ def parse_makefile(fn, g=None):
elif n in os.environ:
# do it like make: fall back to environment
item = os.environ[n]
+
+ elif n in renamed_variables:
+ if name.startswith('PY_') and name[3:] in renamed_variables:
+ item = ""
+
+ elif 'PY_' + n in notdone:
+ found = False
+
+ else:
+ item = str(done['PY_' + n])
else:
done[n] = item = ""
if found:
@@ -331,12 +343,24 @@ def parse_makefile(fn, g=None):
else:
done[name] = value
del notdone[name]
+
+ if name.startswith('PY_') \
+ and name[3:] in renamed_variables:
+
+ name = name[3:]
+ if name not in done:
+ done[name] = value
else:
# bogus variable reference; just drop it since we can't deal
del notdone[name]
fp.close()
+ # strip spurious spaces
+ for k, v in done.items():
+ if isinstance(v, str):
+ done[k] = v.strip()
+
# save the results in the global dictionary
g.update(done)
return g
@@ -386,7 +410,8 @@ def _init_posix():
# load the installed pyconfig.h:
try:
filename = get_config_h_filename()
- parse_config_h(io.open(filename), g)
+ with open(filename) as file:
+ parse_config_h(file, g)
except IOError as msg:
my_msg = "invalid Python installation: unable to open %s" % filename
if hasattr(msg, "strerror"):
@@ -443,34 +468,8 @@ def _init_nt():
g['SO'] = '.pyd'
g['EXE'] = ".exe"
g['VERSION'] = get_python_version().replace(".", "")
- g['BINDIR'] = os.path.dirname(os.path.realpath(sys.executable))
-
- global _config_vars
- _config_vars = g
-
-
-def _init_mac():
- """Initialize the module as appropriate for Macintosh systems"""
- g = {}
- # set basic install directories
- g['LIBDEST'] = get_python_lib(plat_specific=0, standard_lib=1)
- g['BINLIBDEST'] = get_python_lib(plat_specific=1, standard_lib=1)
-
- # XXX hmmm.. a normal install puts include files here
- g['INCLUDEPY'] = get_python_inc(plat_specific=0)
-
- import MacOS
- if not hasattr(MacOS, 'runtimemodel'):
- g['SO'] = '.ppc.slb'
- else:
- g['SO'] = '.%s.slb' % MacOS.runtimemodel
-
- # XXX are these used anywhere?
- g['install_lib'] = os.path.join(EXEC_PREFIX, "Lib")
- g['install_platlib'] = os.path.join(EXEC_PREFIX, "Mac", "Lib")
+ g['BINDIR'] = os.path.dirname(os.path.abspath(sys.executable))
- # These are used by the extension module build
- g['srcdir'] = ':'
global _config_vars
_config_vars = g
diff --git a/Lib/distutils/tests/test_bdist_msi.py b/Lib/distutils/tests/test_bdist_msi.py
new file mode 100644
index 0000000..9308c79
--- /dev/null
+++ b/Lib/distutils/tests/test_bdist_msi.py
@@ -0,0 +1,25 @@
+"""Tests for distutils.command.bdist_msi."""
+import unittest
+import sys
+
+from test.support import run_unittest
+
+from distutils.tests import support
+
+@unittest.skipUnless(sys.platform=="win32", "These tests are only for win32")
+class BDistMSITestCase(support.TempdirManager,
+ support.LoggingSilencer,
+ unittest.TestCase):
+
+ def test_minimal(self):
+ # minimal test XXX need more tests
+ from distutils.command.bdist_msi import bdist_msi
+ pkg_pth, dist = self.create_dist()
+ cmd = bdist_msi(dist)
+ cmd.ensure_finalized()
+
+def test_suite():
+ return unittest.makeSuite(BDistMSITestCase)
+
+if __name__ == '__main__':
+ run_unittest(test_suite())
diff --git a/Lib/distutils/tests/test_build.py b/Lib/distutils/tests/test_build.py
new file mode 100644
index 0000000..3391f36
--- /dev/null
+++ b/Lib/distutils/tests/test_build.py
@@ -0,0 +1,55 @@
+"""Tests for distutils.command.build."""
+import unittest
+import os
+import sys
+from test.support import run_unittest
+
+from distutils.command.build import build
+from distutils.tests import support
+from sysconfig import get_platform
+
+class BuildTestCase(support.TempdirManager,
+ support.LoggingSilencer,
+ unittest.TestCase):
+
+ def test_finalize_options(self):
+ pkg_dir, dist = self.create_dist()
+ cmd = build(dist)
+ cmd.finalize_options()
+
+ # if not specified, plat_name gets the current platform
+ self.assertEqual(cmd.plat_name, get_platform())
+
+ # build_purelib is build + lib
+ wanted = os.path.join(cmd.build_base, 'lib')
+ self.assertEqual(cmd.build_purelib, wanted)
+
+ # build_platlib is 'build/lib.platform-x.x[-pydebug]'
+ # examples:
+ # build/lib.macosx-10.3-i386-2.7
+ plat_spec = '.%s-%s' % (cmd.plat_name, sys.version[0:3])
+ if hasattr(sys, 'gettotalrefcount'):
+ self.assertTrue(cmd.build_platlib.endswith('-pydebug'))
+ plat_spec += '-pydebug'
+ wanted = os.path.join(cmd.build_base, 'lib' + plat_spec)
+ self.assertEqual(cmd.build_platlib, wanted)
+
+ # by default, build_lib = build_purelib
+ self.assertEqual(cmd.build_lib, cmd.build_purelib)
+
+ # build_temp is build/temp.<plat>
+ wanted = os.path.join(cmd.build_base, 'temp' + plat_spec)
+ self.assertEqual(cmd.build_temp, wanted)
+
+ # build_scripts is build/scripts-x.x
+ wanted = os.path.join(cmd.build_base, 'scripts-' + sys.version[0:3])
+ self.assertEqual(cmd.build_scripts, wanted)
+
+ # executable is os.path.normpath(sys.executable)
+ self.assertEqual(cmd.executable, os.path.normpath(sys.executable))
+
+def test_suite():
+ return unittest.makeSuite(BuildTestCase)
+
+if __name__ == "__main__":
+ run_unittest(test_suite())
diff --git a/Lib/distutils/tests/test_build_ext.py b/Lib/distutils/tests/test_build_ext.py
index 11844d6..dcba75f 100644
--- a/Lib/distutils/tests/test_build_ext.py
+++ b/Lib/distutils/tests/test_build_ext.py
@@ -1,17 +1,16 @@
import sys
import os
-import tempfile
import shutil
from io import StringIO
-from distutils.core import Extension, Distribution
+from distutils.core import Distribution
from distutils.command.build_ext import build_ext
from distutils import sysconfig
from distutils.tests.support import TempdirManager
from distutils.tests.support import LoggingSilencer
from distutils.extension import Extension
-from distutils.errors import (UnknownFileError, DistutilsSetupError,
- CompileError)
+from distutils.errors import (
+ CompileError, DistutilsSetupError, UnknownFileError)
import unittest
from test import support
@@ -256,7 +255,8 @@ class BuildExtTestCase(TempdirManager,
cmd.finalize_options()
#'extensions' option must be a list of Extension instances
- self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, 'foo')
+ self.assertRaises(DistutilsSetupError,
+ cmd.check_extensions_list, 'foo')
# each element of 'ext_modules' option must be an
# Extension instance or 2-tuple
@@ -318,7 +318,7 @@ class BuildExtTestCase(TempdirManager,
def test_get_outputs(self):
tmp_dir = self.mkdtemp()
c_file = os.path.join(tmp_dir, 'foo.c')
- self.write_file(c_file, 'void PyInit_foo(void) {};\n')
+ self.write_file(c_file, 'void PyInit_foo(void) {}\n')
ext = Extension('foo', [c_file], optional=False)
dist = Distribution({'name': 'xx',
'ext_modules': [ext]})
@@ -345,8 +345,8 @@ class BuildExtTestCase(TempdirManager,
finally:
os.chdir(old_wd)
self.assertTrue(os.path.exists(so_file))
- self.assertEqual(os.path.splitext(so_file)[-1],
- sysconfig.get_config_var('SO'))
+ so_ext = sysconfig.get_config_var('SO')
+ self.assertTrue(so_file.endswith(so_ext))
so_dir = os.path.dirname(so_file)
self.assertEqual(so_dir, other_tmp_dir)
@@ -355,8 +355,7 @@ class BuildExtTestCase(TempdirManager,
cmd.run()
so_file = cmd.get_outputs()[0]
self.assertTrue(os.path.exists(so_file))
- self.assertEqual(os.path.splitext(so_file)[-1],
- sysconfig.get_config_var('SO'))
+ self.assertTrue(so_file.endswith(so_ext))
so_dir = os.path.dirname(so_file)
self.assertEqual(so_dir, cmd.build_lib)
diff --git a/Lib/distutils/tests/test_clean.py b/Lib/distutils/tests/test_clean.py
index eb8958b..eb8958b 100755..100644
--- a/Lib/distutils/tests/test_clean.py
+++ b/Lib/distutils/tests/test_clean.py
diff --git a/Lib/distutils/tests/test_dep_util.py b/Lib/distutils/tests/test_dep_util.py
new file mode 100644
index 0000000..3e1c366
--- /dev/null
+++ b/Lib/distutils/tests/test_dep_util.py
@@ -0,0 +1,81 @@
+"""Tests for distutils.dep_util."""
+import unittest
+import os
+import time
+
+from distutils.dep_util import newer, newer_pairwise, newer_group
+from distutils.errors import DistutilsFileError
+from distutils.tests import support
+from test.support import run_unittest
+
+class DepUtilTestCase(support.TempdirManager, unittest.TestCase):
+
+ def test_newer(self):
+
+ tmpdir = self.mkdtemp()
+ new_file = os.path.join(tmpdir, 'new')
+ old_file = os.path.abspath(__file__)
+
+ # Raise DistutilsFileError if 'new_file' does not exist.
+ self.assertRaises(DistutilsFileError, newer, new_file, old_file)
+
+ # Return true if 'new_file' exists and is more recently modified than
+ # 'old_file', or if 'new_file' exists and 'old_file' doesn't.
+ self.write_file(new_file)
+ self.assertTrue(newer(new_file, 'I_dont_exist'))
+ self.assertTrue(newer(new_file, old_file))
+
+ # Return false if both exist and 'old_file' is the same age or younger
+ # than 'new_file'.
+ self.assertFalse(newer(old_file, new_file))
+
+ def test_newer_pairwise(self):
+ tmpdir = self.mkdtemp()
+ sources = os.path.join(tmpdir, 'sources')
+ targets = os.path.join(tmpdir, 'targets')
+ os.mkdir(sources)
+ os.mkdir(targets)
+ one = os.path.join(sources, 'one')
+ two = os.path.join(sources, 'two')
+ three = os.path.abspath(__file__) # I am the old file
+ four = os.path.join(targets, 'four')
+ self.write_file(one)
+ self.write_file(two)
+ self.write_file(four)
+
+ self.assertEqual(newer_pairwise([one, two], [three, four]),
+ ([one],[three]))
+
+ def test_newer_group(self):
+ tmpdir = self.mkdtemp()
+ sources = os.path.join(tmpdir, 'sources')
+ os.mkdir(sources)
+ one = os.path.join(sources, 'one')
+ two = os.path.join(sources, 'two')
+ three = os.path.join(sources, 'three')
+ old_file = os.path.abspath(__file__)
+
+ # return true if 'old_file' is out-of-date with respect to any file
+ # listed in 'sources'.
+ self.write_file(one)
+ self.write_file(two)
+ self.write_file(three)
+ self.assertTrue(newer_group([one, two, three], old_file))
+ self.assertFalse(newer_group([one, two, old_file], three))
+
+ # missing handling
+ os.remove(one)
+ self.assertRaises(OSError, newer_group, [one, two, old_file], three)
+
+ self.assertFalse(newer_group([one, two, old_file], three,
+ missing='ignore'))
+
+ self.assertTrue(newer_group([one, two, old_file], three,
+ missing='newer'))
+
+
+def test_suite():
+ return unittest.makeSuite(DepUtilTestCase)
+
+if __name__ == "__main__":
+ run_unittest(test_suite())
diff --git a/Lib/distutils/tests/test_dist.py b/Lib/distutils/tests/test_dist.py
index 1624f00..a20d6c8 100644
--- a/Lib/distutils/tests/test_dist.py
+++ b/Lib/distutils/tests/test_dist.py
@@ -1,4 +1,3 @@
-# -*- coding: utf8 -*-
"""Tests for distutils.dist."""
import os
import io
diff --git a/Lib/distutils/tests/test_sysconfig.py b/Lib/distutils/tests/test_sysconfig.py
index 41414bb..fbe26bf 100644
--- a/Lib/distutils/tests/test_sysconfig.py
+++ b/Lib/distutils/tests/test_sysconfig.py
@@ -83,7 +83,7 @@ class SysconfigTestCase(support.EnvironGuard,
fd.close()
d = sysconfig.parse_makefile(self.makefile)
self.assertEqual(d, {'CONFIG_ARGS': "'--arg1=optarg1' 'ENV=LIB'",
- 'OTHER': 'foo'})
+ 'OTHER': 'foo'})
def test_parse_makefile_literal_dollar(self):
self.makefile = TESTFN
@@ -98,6 +98,15 @@ class SysconfigTestCase(support.EnvironGuard,
'OTHER': 'foo'})
+ def test_sysconfig_module(self):
+ import sysconfig as global_sysconfig
+ self.assertEqual(global_sysconfig.get_config_var('CFLAGS'), sysconfig.get_config_var('CFLAGS'))
+ self.assertEqual(global_sysconfig.get_config_var('LDFLAGS'), sysconfig.get_config_var('LDFLAGS'))
+ self.assertEqual(global_sysconfig.get_config_var('LDSHARED'),sysconfig.get_config_var('LDSHARED'))
+ self.assertEqual(global_sysconfig.get_config_var('CC'), sysconfig.get_config_var('CC'))
+
+
+
def test_suite():
suite = unittest.TestSuite()
suite.addTest(unittest.makeSuite(SysconfigTestCase))
diff --git a/Lib/distutils/text_file.py b/Lib/distutils/text_file.py
index 97459fb..454725c 100644
--- a/Lib/distutils/text_file.py
+++ b/Lib/distutils/text_file.py
@@ -58,6 +58,8 @@ class TextFile:
collapse_join [default: false]
strip leading whitespace from lines that are joined to their
predecessor; only matters if (join_lines and not lstrip_ws)
+ errors [default: 'strict']
+ error handler used to decode the file content
Note that since 'rstrip_ws' can strip the trailing newline, the
semantics of 'readline()' must differ from those of the builtin file
@@ -72,6 +74,7 @@ class TextFile:
'rstrip_ws': 1,
'join_lines': 0,
'collapse_join': 0,
+ 'errors': 'strict',
}
def __init__(self, filename=None, file=None, **options):
@@ -111,7 +114,7 @@ class TextFile:
"""Open a new file named 'filename'. This overrides both the
'filename' and 'file' arguments to the constructor."""
self.filename = filename
- self.file = io.open(self.filename, 'r')
+ self.file = io.open(self.filename, 'r', errors=self.errors)
self.current_line = 0
def close(self):
diff --git a/Lib/distutils/util.py b/Lib/distutils/util.py
index 3081245..ce3cd6c 100644
--- a/Lib/distutils/util.py
+++ b/Lib/distutils/util.py
@@ -235,15 +235,6 @@ def change_root (new_root, pathname):
path = path[1:]
return os.path.join(new_root, path)
- elif os.name == 'mac':
- if not os.path.isabs(pathname):
- return os.path.join(new_root, pathname)
- else:
- # Chop off volume name from start of path
- elements = pathname.split(":", 1)
- pathname = ":" + elements[1]
- return os.path.join(new_root, pathname)
-
else:
raise DistutilsPlatformError("nothing known about platform '%s'" % os.name)
diff --git a/Lib/doctest.py b/Lib/doctest.py
index fae333e..9eba4e0 100644
--- a/Lib/doctest.py
+++ b/Lib/doctest.py
@@ -318,7 +318,8 @@ class _OutputRedirectingPdb(pdb.Pdb):
def __init__(self, out):
self.__out = out
self.__debugger_used = False
- pdb.Pdb.__init__(self, stdout=out)
+ # do not play signal games in the pdb
+ pdb.Pdb.__init__(self, stdout=out, nosigint=True)
# still use input() to get user input
self.use_rawinput = 1
@@ -1280,9 +1281,9 @@ class DocTestRunner:
# Another chance if they didn't care about the detail.
elif self.optionflags & IGNORE_EXCEPTION_DETAIL:
- m1 = re.match(r'[^:]*:', example.exc_msg)
- m2 = re.match(r'[^:]*:', exc_msg)
- if m1 and m2 and check(m1.group(0), m2.group(0),
+ m1 = re.match(r'(?:[^:]*\.)?([^:]*:)', example.exc_msg)
+ m2 = re.match(r'(?:[^:]*\.)?([^:]*:)', exc_msg)
+ if m1 and m2 and check(m1.group(1), m2.group(1),
self.optionflags):
outcome = SUCCESS
@@ -1320,7 +1321,7 @@ class DocTestRunner:
self.tries += t
__LINECACHE_FILENAME_RE = re.compile(r'<doctest '
- r'(?P<name>[\w\.]+)'
+ r'(?P<name>.+)'
r'\[(?P<examplenum>\d+)\]>$')
def __patched_linecache_getlines(self, filename, module_globals=None):
m = self.__LINECACHE_FILENAME_RE.match(filename)
@@ -2207,6 +2208,19 @@ class DocTestCase(unittest.TestCase):
def shortDescription(self):
return "Doctest: " + self._dt_test.name
+class SkipDocTestCase(DocTestCase):
+ def __init__(self):
+ DocTestCase.__init__(self, None)
+
+ def setUp(self):
+ self.skipTest("DocTestSuite will not work with -O2 and above")
+
+ def test_skip(self):
+ pass
+
+ def shortDescription(self):
+ return "Skipping tests from %s" % module.__name__
+
def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None,
**options):
"""
@@ -2249,13 +2263,20 @@ def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None,
module = _normalize_module(module)
tests = test_finder.find(module, globs=globs, extraglobs=extraglobs)
- if not tests:
+
+ if not tests and sys.flags.optimize >=2:
+ # Skip doctests when running with -O2
+ suite = unittest.TestSuite()
+ suite.addTest(SkipDocTestCase())
+ return suite
+ elif not tests:
# Why do we want to do this? Because it reveals a bug that might
# otherwise be hidden.
raise ValueError(module, "has no tests")
tests.sort()
suite = unittest.TestSuite()
+
for test in tests:
if len(test.examples) == 0:
continue
@@ -2508,14 +2529,16 @@ def debug_script(src, pm=False, globs=None):
exec(f.read(), globs, globs)
except:
print(sys.exc_info()[1])
- pdb.post_mortem(sys.exc_info()[2])
+ p = pdb.Pdb(nosigint=True)
+ p.reset()
+ p.interaction(None, sys.exc_info()[2])
else:
fp = open(srcfilename)
try:
script = fp.read()
finally:
fp.close()
- pdb.run("exec(%r)" % script, globs, globs)
+ pdb.Pdb(nosigint=True).run("exec(%r)" % script, globs, globs)
finally:
os.remove(srcfilename)
diff --git a/Lib/email/__init__.py b/Lib/email/__init__.py
index 8702212..bd316fd 100644
--- a/Lib/email/__init__.py
+++ b/Lib/email/__init__.py
@@ -4,7 +4,7 @@
"""A package for parsing, handling, and generating email messages."""
-__version__ = '5.0.0'
+__version__ = '5.1.0'
__all__ = [
'base64mime',
@@ -16,7 +16,9 @@ __all__ = [
'iterators',
'message',
'message_from_file',
+ 'message_from_binary_file',
'message_from_string',
+ 'message_from_bytes',
'mime',
'parser',
'quoprimime',
@@ -36,6 +38,13 @@ def message_from_string(s, *args, **kws):
from email.parser import Parser
return Parser(*args, **kws).parsestr(s)
+def message_from_bytes(s, *args, **kws):
+ """Parse a bytes string into a Message object model.
+
+ Optional _class and strict are passed to the Parser constructor.
+ """
+ from email.parser import BytesParser
+ return BytesParser(*args, **kws).parsebytes(s)
def message_from_file(fp, *args, **kws):
"""Read a file and parse its contents into a Message object model.
@@ -44,3 +53,11 @@ def message_from_file(fp, *args, **kws):
"""
from email.parser import Parser
return Parser(*args, **kws).parse(fp)
+
+def message_from_binary_file(fp, *args, **kws):
+ """Read a binary file and parse its contents into a Message object model.
+
+ Optional _class and strict are passed to the Parser constructor.
+ """
+ from email.parser import BytesParser
+ return BytesParser(*args, **kws).parse(fp)
diff --git a/Lib/email/_parseaddr.py b/Lib/email/_parseaddr.py
index 3bd4ba4..41694f9 100644
--- a/Lib/email/_parseaddr.py
+++ b/Lib/email/_parseaddr.py
@@ -64,8 +64,10 @@ def parsedate_tz(data):
if len(data) == 4:
s = data[3]
i = s.find('+')
+ if i == -1:
+ i = s.find('-')
if i > 0:
- data[3:] = [s[:i], s[i+1:]]
+ data[3:] = [s[:i], s[i:]]
else:
data.append('') # Dummy tz
if len(data) < 5:
@@ -199,14 +201,18 @@ class AddrlistClass:
self.commentlist = []
def gotonext(self):
- """Parse up to the start of the next address."""
+ """Skip white space and extract comments."""
+ wslist = []
while self.pos < len(self.field):
if self.field[self.pos] in self.LWS + '\n\r':
+ if self.field[self.pos] not in '\n\r':
+ wslist.append(self.field[self.pos])
self.pos += 1
elif self.field[self.pos] == '(':
self.commentlist.append(self.getcomment())
else:
break
+ return EMPTYSTRING.join(wslist)
def getaddrlist(self):
"""Parse all addresses.
@@ -319,16 +325,24 @@ class AddrlistClass:
self.gotonext()
while self.pos < len(self.field):
+ preserve_ws = True
if self.field[self.pos] == '.':
+ if aslist and not aslist[-1].strip():
+ aslist.pop()
aslist.append('.')
self.pos += 1
+ preserve_ws = False
elif self.field[self.pos] == '"':
aslist.append('"%s"' % quote(self.getquote()))
elif self.field[self.pos] in self.atomends:
+ if aslist and not aslist[-1].strip():
+ aslist.pop()
break
else:
aslist.append(self.getatom())
- self.gotonext()
+ ws = self.gotonext()
+ if preserve_ws and ws:
+ aslist.append(ws)
if self.pos >= len(self.field) or self.field[self.pos] != '@':
return EMPTYSTRING.join(aslist)
diff --git a/Lib/email/base64mime.py b/Lib/email/base64mime.py
index 28e2542..f3bbac1 100644
--- a/Lib/email/base64mime.py
+++ b/Lib/email/base64mime.py
@@ -20,7 +20,7 @@ in To:, From:, Cc:, etc. fields, as well as Subject: lines.
This module does not do the line wrapping or end-of-line character conversion
necessary for proper internationalized headers; it only does dumb encoding and
-decoding. To deal with the various line wrapping issues, use the email.Header
+decoding. To deal with the various line wrapping issues, use the email.header
module.
"""
@@ -74,12 +74,12 @@ def header_encode(header_bytes, charset='iso-8859-1'):
def body_encode(s, maxlinelen=76, eol=NL):
- """Encode a string with base64.
+ r"""Encode a string with base64.
Each line will be wrapped at, at most, maxlinelen characters (defaults to
76 characters).
- Each line of encoded text will end with eol, which defaults to "\\n". Set
+ Each line of encoded text will end with eol, which defaults to "\n". Set
this to "\r\n" if you will be using the result of this function directly
in an email.
"""
@@ -104,7 +104,7 @@ def decode(string):
This function does not parse a full MIME header value encoded with
base64 (like =?iso-8895-1?b?bmloISBuaWgh?=) -- please use the high
- level email.Header class for that functionality.
+ level email.header class for that functionality.
"""
if not string:
return bytes()
diff --git a/Lib/email/charset.py b/Lib/email/charset.py
index 898beed..f22be2c 100644
--- a/Lib/email/charset.py
+++ b/Lib/email/charset.py
@@ -28,6 +28,7 @@ SHORTEST = 3 # the shorter of QP and base64, but only for headers
RFC2047_CHROME_LEN = 7
DEFAULT_CHARSET = 'us-ascii'
+UNKNOWN8BIT = 'unknown-8bit'
EMPTYSTRING = ''
@@ -153,6 +154,16 @@ def add_codec(charset, codecname):
+# Convenience function for encoding strings, taking into account
+# that they might be unknown-8bit (ie: have surrogate-escaped bytes)
+def _encode(string, codec):
+ if codec == UNKNOWN8BIT:
+ return string.encode('ascii', 'surrogateescape')
+ else:
+ return string.encode(codec)
+
+
+
class Charset:
"""Map character sets to their email properties.
@@ -252,7 +263,7 @@ class Charset:
Returns "quoted-printable" if self.body_encoding is QP.
Returns "base64" if self.body_encoding is BASE64.
- Returns "7bit" otherwise.
+ Returns conversion function otherwise.
"""
assert self.body_encoding != SHORTEST
if self.body_encoding == QP:
@@ -282,8 +293,7 @@ class Charset:
:return: The encoded string, with RFC 2047 chrome.
"""
codec = self.output_codec or 'us-ascii'
- charset = self.get_output_charset()
- header_bytes = string.encode(codec)
+ header_bytes = _encode(string, codec)
# 7bit/8bit encodings return the string unchanged (modulo conversions)
encoder_module = self._get_encoder(header_bytes)
if encoder_module is None:
@@ -309,9 +319,9 @@ class Charset:
"""
# See which encoding we should use.
codec = self.output_codec or 'us-ascii'
- header_bytes = string.encode(codec)
+ header_bytes = _encode(string, codec)
encoder_module = self._get_encoder(header_bytes)
- encoder = partial(encoder_module.header_encode, charset=str(self))
+ encoder = partial(encoder_module.header_encode, charset=codec)
# Calculate the number of characters that the RFC 2047 chrome will
# contribute to each line.
charset = self.get_output_charset()
@@ -333,7 +343,7 @@ class Charset:
for character in string:
current_line.append(character)
this_line = EMPTYSTRING.join(current_line)
- length = encoder_module.header_length(this_line.encode(charset))
+ length = encoder_module.header_length(_encode(this_line, charset))
if length > maxlen:
# This last character doesn't fit so pop it off.
current_line.pop()
@@ -343,12 +353,12 @@ class Charset:
else:
separator = (' ' if lines else '')
joined_line = EMPTYSTRING.join(current_line)
- header_bytes = joined_line.encode(codec)
+ header_bytes = _encode(joined_line, codec)
lines.append(encoder(header_bytes))
current_line = [character]
maxlen = next(maxlengths) - extra
joined_line = EMPTYSTRING.join(current_line)
- header_bytes = joined_line.encode(codec)
+ header_bytes = _encode(joined_line, codec)
lines.append(encoder(header_bytes))
return lines
@@ -371,7 +381,10 @@ class Charset:
"""Body-encode a string by converting it first to bytes.
The type of encoding (base64 or quoted-printable) will be based on
- self.body_encoding.
+ self.body_encoding. If body_encoding is None, we assume the
+ output charset is a 7bit encoding, so re-encoding the decoded
+ string using the ascii codec produces the correct string version
+ of the content.
"""
# 7bit/8bit encodings return the string unchanged (module conversions)
if self.body_encoding is BASE64:
@@ -381,4 +394,6 @@ class Charset:
elif self.body_encoding is QP:
return email.quoprimime.body_encode(string)
else:
+ if isinstance(string, str):
+ string = string.encode(self.output_charset).decode('ascii')
return string
diff --git a/Lib/email/encoders.py b/Lib/email/encoders.py
index dfaac58..e5c099f 100644
--- a/Lib/email/encoders.py
+++ b/Lib/email/encoders.py
@@ -54,10 +54,13 @@ def encode_7or8bit(msg):
# There's no payload. For backwards compatibility we use 7bit
msg['Content-Transfer-Encoding'] = '7bit'
return
- # We play a trick to make this go fast. If encoding to ASCII succeeds, we
- # know the data must be 7bit, otherwise treat it as 8bit.
+ # We play a trick to make this go fast. If encoding/decode to ASCII
+ # succeeds, we know the data must be 7bit, otherwise treat it as 8bit.
try:
- orig.encode('ascii')
+ if isinstance(orig, str):
+ orig.encode('ascii')
+ else:
+ orig.decode('ascii')
except UnicodeError:
# iso-2022-* is non-ASCII but still 7-bit
charset = msg.get_charset()
diff --git a/Lib/email/feedparser.py b/Lib/email/feedparser.py
index 8db70b3..de8750d 100644
--- a/Lib/email/feedparser.py
+++ b/Lib/email/feedparser.py
@@ -482,3 +482,10 @@ class FeedParser:
if lastheader:
# XXX reconsider the joining of folded lines
self._cur[lastheader] = EMPTYSTRING.join(lastvalue).rstrip('\r\n')
+
+
+class BytesFeedParser(FeedParser):
+ """Like FeedParser, but feed accepts bytes."""
+
+ def feed(self, data):
+ super().feed(data.decode('ascii', 'surrogateescape'))
diff --git a/Lib/email/generator.py b/Lib/email/generator.py
index cc30aff..f0e7a95 100644
--- a/Lib/email/generator.py
+++ b/Lib/email/generator.py
@@ -12,11 +12,12 @@ import time
import random
import warnings
-from io import StringIO
+from io import StringIO, BytesIO
from email.header import Header
+from email.message import _has_surrogates
UNDERSCORE = '_'
-NL = '\n'
+NL = '\n' # XXX: no longer used by the code below.
fcre = re.compile(r'^From ', re.MULTILINE)
@@ -57,8 +58,8 @@ class Generator:
# Just delegate to the file object
self._fp.write(s)
- def flatten(self, msg, unixfrom=False):
- """Print the message object tree rooted at msg to the output file
+ def flatten(self, msg, unixfrom=False, linesep='\n'):
+ r"""Print the message object tree rooted at msg to the output file
specified when the Generator instance was created.
unixfrom is a flag that forces the printing of a Unix From_ delimiter
@@ -67,12 +68,26 @@ class Generator:
is False to inhibit the printing of any From_ delimiter.
Note that for subobjects, no From_ line is printed.
+
+ linesep specifies the characters used to indicate a new line in
+ the output. The default value is the most useful for typical
+ Python applications, but it can be set to \r\n to produce RFC-compliant
+ line separators when needed.
+
"""
+ # We use the _XXX constants for operating on data that comes directly
+ # from the msg, and _encoded_XXX constants for operating on data that
+ # has already been converted (to bytes in the BytesGenerator) and
+ # inserted into a temporary buffer.
+ self._NL = linesep
+ self._encoded_NL = self._encode(linesep)
+ self._EMPTY = ''
+ self._encoded_EMTPY = self._encode('')
if unixfrom:
ufrom = msg.get_unixfrom()
if not ufrom:
ufrom = 'From nobody ' + time.ctime(time.time())
- print(ufrom, file=self._fp)
+ self.write(ufrom + self._NL)
self._write(msg)
def clone(self, fp):
@@ -83,6 +98,27 @@ class Generator:
# Protected interface - undocumented ;/
#
+ # Note that we use 'self.write' when what we are writing is coming from
+ # the source, and self._fp.write when what we are writing is coming from a
+ # buffer (because the Bytes subclass has already had a chance to transform
+ # the data in its write method in that case). This is an entirely
+ # pragmatic split determined by experiment; we could be more general by
+ # always using write and having the Bytes subclass write method detect when
+ # it has already transformed the input; but, since this whole thing is a
+ # hack anyway this seems good enough.
+
+ # Similarly, we have _XXX and _encoded_XXX attributes that are used on
+ # source and buffer data, respectively.
+ _encoded_EMPTY = ''
+
+ def _new_buffer(self):
+ # BytesGenerator overrides this to return BytesIO.
+ return StringIO()
+
+ def _encode(self, s):
+ # BytesGenerator overrides this to encode strings to bytes.
+ return s
+
def _write(self, msg):
# We can't write the headers yet because of the following scenario:
# say a multipart message includes the boundary string somewhere in
@@ -91,13 +127,13 @@ class Generator:
# parameter.
#
# The way we do this, so as to make the _handle_*() methods simpler,
- # is to cache any subpart writes into a StringIO. The we write the
- # headers and the StringIO contents. That way, subpart handlers can
+ # is to cache any subpart writes into a buffer. The we write the
+ # headers and the buffer contents. That way, subpart handlers can
# Do The Right Thing, and can still modify the Content-Type: header if
# necessary.
oldfp = self._fp
try:
- self._fp = sfp = StringIO()
+ self._fp = sfp = self._new_buffer()
self._dispatch(msg)
finally:
self._fp = oldfp
@@ -132,16 +168,17 @@ class Generator:
def _write_headers(self, msg):
for h, v in msg.items():
- print('%s:' % h, end=' ', file=self._fp)
+ self.write('%s: ' % h)
if isinstance(v, Header):
- print(v.encode(maxlinelen=self._maxheaderlen), file=self._fp)
+ self.write(v.encode(
+ maxlinelen=self._maxheaderlen, linesep=self._NL)+self._NL)
else:
# Header's got lots of smarts, so use it.
header = Header(v, maxlinelen=self._maxheaderlen,
header_name=h)
- print(header.encode(), file=self._fp)
+ self.write(header.encode(linesep=self._NL)+self._NL)
# A blank line always separates headers from body
- print(file=self._fp)
+ self.write(self._NL)
#
# Handlers for writing types and subtypes
@@ -153,9 +190,15 @@ class Generator:
return
if not isinstance(payload, str):
raise TypeError('string payload expected: %s' % type(payload))
+ if _has_surrogates(msg._payload):
+ charset = msg.get_param('charset')
+ if charset is not None:
+ del msg['content-transfer-encoding']
+ msg.set_payload(payload, charset)
+ payload = msg.get_payload()
if self._mangle_from_:
payload = fcre.sub('>From ', payload)
- self._fp.write(payload)
+ self.write(payload)
# Default body handler
_writeBody = _handle_text
@@ -170,29 +213,29 @@ class Generator:
subparts = []
elif isinstance(subparts, str):
# e.g. a non-strict parse of a message with no starting boundary.
- self._fp.write(subparts)
+ self.write(subparts)
return
elif not isinstance(subparts, list):
# Scalar payload
subparts = [subparts]
for part in subparts:
- s = StringIO()
+ s = self._new_buffer()
g = self.clone(s)
- g.flatten(part, unixfrom=False)
+ g.flatten(part, unixfrom=False, linesep=self._NL)
msgtexts.append(s.getvalue())
# BAW: What about boundaries that are wrapped in double-quotes?
boundary = msg.get_boundary()
if not boundary:
# Create a boundary that doesn't appear in any of the
# message texts.
- alltext = NL.join(msgtexts)
- boundary = _make_boundary(alltext)
+ alltext = self._encoded_NL.join(msgtexts)
+ boundary = self._make_boundary(alltext)
msg.set_boundary(boundary)
# If there's a preamble, write it out, with a trailing CRLF
if msg.preamble is not None:
- print(msg.preamble, file=self._fp)
+ self.write(msg.preamble + self._NL)
# dash-boundary transport-padding CRLF
- print('--' + boundary, file=self._fp)
+ self.write('--' + boundary + self._NL)
# body-part
if msgtexts:
self._fp.write(msgtexts.pop(0))
@@ -201,14 +244,14 @@ class Generator:
# --> CRLF body-part
for body_part in msgtexts:
# delimiter transport-padding CRLF
- print('\n--' + boundary, file=self._fp)
+ self.write(self._NL + '--' + boundary + self._NL)
# body-part
self._fp.write(body_part)
# close-delimiter transport-padding
- self._fp.write('\n--' + boundary + '--')
+ self.write(self._NL + '--' + boundary + '--')
if msg.epilogue is not None:
- print(file=self._fp)
- self._fp.write(msg.epilogue)
+ self.write(self._NL)
+ self.write(msg.epilogue)
def _handle_multipart_signed(self, msg):
# The contents of signed parts has to stay unmodified in order to keep
@@ -227,23 +270,23 @@ class Generator:
# block and the boundary. Sigh.
blocks = []
for part in msg.get_payload():
- s = StringIO()
+ s = self._new_buffer()
g = self.clone(s)
- g.flatten(part, unixfrom=False)
+ g.flatten(part, unixfrom=False, linesep=self._NL)
text = s.getvalue()
- lines = text.split('\n')
+ lines = text.split(self._encoded_NL)
# Strip off the unnecessary trailing empty line
- if lines and lines[-1] == '':
- blocks.append(NL.join(lines[:-1]))
+ if lines and lines[-1] == self._encoded_EMPTY:
+ blocks.append(self._encoded_NL.join(lines[:-1]))
else:
blocks.append(text)
# Now join all the blocks with an empty line. This has the lovely
# effect of separating each block with an empty line, but not adding
# an extra one after the last one.
- self._fp.write(NL.join(blocks))
+ self._fp.write(self._encoded_NL.join(blocks))
def _handle_message(self, msg):
- s = StringIO()
+ s = self._new_buffer()
g = self.clone(s)
# The payload of a message/rfc822 part should be a multipart sequence
# of length 1. The zeroth element of the list should be the Message
@@ -256,10 +299,98 @@ class Generator:
# in that case we just emit the string body.
payload = msg.get_payload()
if isinstance(payload, list):
- g.flatten(msg.get_payload(0), unixfrom=False)
+ g.flatten(msg.get_payload(0), unixfrom=False, linesep=self._NL)
payload = s.getvalue()
self._fp.write(payload)
+ # This used to be a module level function; we use a classmethod for this
+ # and _compile_re so we can continue to provide the module level function
+ # for backward compatibility by doing
+ # _make_boudary = Generator._make_boundary
+ # at the end of the module. It *is* internal, so we could drop that...
+ @classmethod
+ def _make_boundary(cls, text=None):
+ # Craft a random boundary. If text is given, ensure that the chosen
+ # boundary doesn't appear in the text.
+ token = random.randrange(sys.maxsize)
+ boundary = ('=' * 15) + (_fmt % token) + '=='
+ if text is None:
+ return boundary
+ b = boundary
+ counter = 0
+ while True:
+ cre = cls._compile_re('^--' + re.escape(b) + '(--)?$', re.MULTILINE)
+ if not cre.search(text):
+ break
+ b = boundary + '.' + str(counter)
+ counter += 1
+ return b
+
+ @classmethod
+ def _compile_re(cls, s, flags):
+ return re.compile(s, flags)
+
+
+class BytesGenerator(Generator):
+ """Generates a bytes version of a Message object tree.
+
+ Functionally identical to the base Generator except that the output is
+ bytes and not string. When surrogates were used in the input to encode
+ bytes, these are decoded back to bytes for output.
+
+ The outfp object must accept bytes in its write method.
+ """
+
+ # Bytes versions of this constant for use in manipulating data from
+ # the BytesIO buffer.
+ _encoded_EMPTY = b''
+
+ def write(self, s):
+ self._fp.write(s.encode('ascii', 'surrogateescape'))
+
+ def _new_buffer(self):
+ return BytesIO()
+
+ def _encode(self, s):
+ return s.encode('ascii')
+
+ def _write_headers(self, msg):
+ # This is almost the same as the string version, except for handling
+ # strings with 8bit bytes.
+ for h, v in msg._headers:
+ self.write('%s: ' % h)
+ if isinstance(v, Header):
+ self.write(v.encode(maxlinelen=self._maxheaderlen)+NL)
+ elif _has_surrogates(v):
+ # If we have raw 8bit data in a byte string, we have no idea
+ # what the encoding is. There is no safe way to split this
+ # string. If it's ascii-subset, then we could do a normal
+ # ascii split, but if it's multibyte then we could break the
+ # string. There's no way to know so the least harm seems to
+ # be to not split the string and risk it being too long.
+ self.write(v+NL)
+ else:
+ # Header's got lots of smarts and this string is safe...
+ header = Header(v, maxlinelen=self._maxheaderlen,
+ header_name=h)
+ self.write(header.encode(linesep=self._NL)+self._NL)
+ # A blank line always separates headers from body
+ self.write(self._NL)
+
+ def _handle_text(self, msg):
+ # If the string has surrogates the original source was bytes, so
+ # just write it back out.
+ if msg._payload is None:
+ return
+ if _has_surrogates(msg._payload):
+ self.write(msg._payload)
+ else:
+ super(BytesGenerator,self)._handle_text(msg)
+
+ @classmethod
+ def _compile_re(cls, s, flags):
+ return re.compile(s.encode('ascii'), flags)
+
_FMT = '[Non-text (%(type)s) part of message omitted, filename %(filename)s]'
@@ -320,23 +451,9 @@ class DecodedGenerator(Generator):
-# Helper
+# Helper used by Generator._make_boundary
_width = len(repr(sys.maxsize-1))
_fmt = '%%0%dd' % _width
-def _make_boundary(text=None):
- # Craft a random boundary. If text is given, ensure that the chosen
- # boundary doesn't appear in the text.
- token = random.randrange(sys.maxsize)
- boundary = ('=' * 15) + (_fmt % token) + '=='
- if text is None:
- return boundary
- b = boundary
- counter = 0
- while True:
- cre = re.compile('^--' + re.escape(b) + '(--)?$', re.MULTILINE)
- if not cre.search(text):
- break
- b = boundary + '.' + str(counter)
- counter += 1
- return b
+# Backward compatibility
+_make_boundary = Generator._make_boundary
diff --git a/Lib/email/header.py b/Lib/email/header.py
index da739d5..8c32514 100644
--- a/Lib/email/header.py
+++ b/Lib/email/header.py
@@ -17,7 +17,8 @@ import email.quoprimime
import email.base64mime
from email.errors import HeaderParseError
-from email.charset import Charset
+from email import charset as _charset
+Charset = _charset.Charset
NL = '\n'
SPACE = ' '
@@ -65,7 +66,7 @@ def decode_header(header):
otherwise a lower-case string containing the name of the character set
specified in the encoded string.
- An email.Errors.HeaderParseError may be raised when certain decoding error
+ An email.errors.HeaderParseError may be raised when certain decoding error
occurs (e.g. a base64 decoding exception).
"""
# If no encoding, just return the header with no charset.
@@ -214,6 +215,9 @@ class Header:
# from a charset to None/us-ascii, or from None/us-ascii to a
# charset. Only do this for the second and subsequent chunks.
nextcs = charset
+ if nextcs == _charset.UNKNOWN8BIT:
+ original_bytes = string.encode('ascii', 'surrogateescape')
+ string = original_bytes.decode('ascii', 'replace')
if uchunks:
if lastcs not in (None, 'us-ascii'):
if nextcs in (None, 'us-ascii'):
@@ -267,11 +271,12 @@ class Header:
# Ensure that the bytes we're storing can be decoded to the output
# character set, otherwise an early error is thrown.
output_charset = charset.output_codec or 'us-ascii'
- s.encode(output_charset, errors)
+ if output_charset != _charset.UNKNOWN8BIT:
+ s.encode(output_charset, errors)
self._chunks.append((s, charset))
- def encode(self, splitchars=';, \t', maxlinelen=None):
- """Encode a message header into an RFC-compliant format.
+ def encode(self, splitchars=';, \t', maxlinelen=None, linesep='\n'):
+ r"""Encode a message header into an RFC-compliant format.
There are many issues involved in converting a given string for use in
an email header. Only certain character sets are readable in most
@@ -291,6 +296,11 @@ class Header:
Optional splitchars is a string containing characters to split long
ASCII lines on, in rough support of RFC 2822's `highest level
syntactic breaks'. This doesn't affect RFC 2047 encoded lines.
+
+ Optional linesep is a string to be used to separate the lines of
+ the value. The default value is the most useful for typical
+ Python applications, but it can be set to \r\n to produce RFC-compliant
+ line separators when needed.
"""
self._normalize()
if maxlinelen is None:
@@ -314,7 +324,7 @@ class Header:
if len(lines) > 1:
formatter.newline()
formatter.add_transition()
- value = str(formatter)
+ value = formatter._str(linesep)
if _embeded_header.search(value):
raise HeaderParseError("header value appears to contain "
"an embedded header: {!r}".format(value))
@@ -349,9 +359,12 @@ class _ValueFormatter:
self._lines = []
self._current_line = _Accumulator(headerlen)
- def __str__(self):
+ def _str(self, linesep):
self.newline()
- return NL.join(self._lines)
+ return linesep.join(self._lines)
+
+ def __str__(self):
+ return self._str(NL)
def newline(self):
end_of_line = self._current_line.pop()
diff --git a/Lib/email/message.py b/Lib/email/message.py
index d30f109..2713bc5 100644
--- a/Lib/email/message.py
+++ b/Lib/email/message.py
@@ -16,7 +16,9 @@ from io import BytesIO, StringIO
# Intrapackage imports
from email import utils
from email import errors
-from email.charset import Charset
+from email import header
+from email import charset as _charset
+Charset = _charset.Charset
SEMISPACE = '; '
@@ -24,8 +26,25 @@ SEMISPACE = '; '
# existence of which force quoting of the parameter value.
tspecials = re.compile(r'[ \(\)<>@,;:\\"/\[\]\?=]')
+# How to figure out if we are processing strings that come from a byte
+# source with undecodable characters.
+_has_surrogates = re.compile(
+ '([^\ud800-\udbff]|\A)[\udc00-\udfff]([^\udc00-\udfff]|\Z)').search
+
# Helper functions
+def _sanitize_header(name, value):
+ # If the header value contains surrogates, return a Header using
+ # the unknown-8bit charset to encode the bytes as encoded words.
+ if not isinstance(value, str):
+ # Assume it is already a header object
+ return value
+ if _has_surrogates(value):
+ return header.Header(value, charset=_charset.UNKNOWN8BIT,
+ header_name=name)
+ else:
+ return value
+
def _splitparam(param):
# Split header parameters. BAW: this may be too simple. It isn't
# strictly RFC 2045 (section 5.1) compliant, but it catches most headers
@@ -48,17 +67,19 @@ def _formatparam(param, value=None, quote=True):
if value is not None and len(value) > 0:
# A tuple is used for RFC 2231 encoded parameter values where items
# are (charset, language, value). charset is a string, not a Charset
- # instance.
+ # instance. RFC 2231 encoded values are never quoted, per RFC.
if isinstance(value, tuple):
# Encode as per RFC 2231
param += '*'
value = utils.encode_rfc2231(value[2], value[0], value[1])
+ return '%s=%s' % (param, value)
else:
try:
value.encode('ascii')
except UnicodeEncodeError:
param += '*'
value = utils.encode_rfc2231(value, 'utf-8', '')
+ return '%s=%s' % (param, value)
# BAW: Please check this. I think that if quote is set it should
# force quoting even if not necessary.
if quote or tspecials.search(value):
@@ -193,43 +214,72 @@ class Message:
If the message is a multipart and the decode flag is True, then None
is returned.
"""
- if i is None:
- payload = self._payload
- elif not isinstance(self._payload, list):
+ # Here is the logic table for this code, based on the email5.0.0 code:
+ # i decode is_multipart result
+ # ------ ------ ------------ ------------------------------
+ # None True True None
+ # i True True None
+ # None False True _payload (a list)
+ # i False True _payload element i (a Message)
+ # i False False error (not a list)
+ # i True False error (not a list)
+ # None False False _payload
+ # None True False _payload decoded (bytes)
+ # Note that Barry planned to factor out the 'decode' case, but that
+ # isn't so easy now that we handle the 8 bit data, which needs to be
+ # converted in both the decode and non-decode path.
+ if self.is_multipart():
+ if decode:
+ return None
+ if i is None:
+ return self._payload
+ else:
+ return self._payload[i]
+ # For backward compatibility, Use isinstance and this error message
+ # instead of the more logical is_multipart test.
+ if i is not None and not isinstance(self._payload, list):
raise TypeError('Expected list, got %s' % type(self._payload))
- else:
- payload = self._payload[i]
+ payload = self._payload
+ cte = self.get('content-transfer-encoding', '').lower()
+ # payload may be bytes here.
+ if isinstance(payload, str):
+ if _has_surrogates(payload):
+ bpayload = payload.encode('ascii', 'surrogateescape')
+ if not decode:
+ try:
+ payload = bpayload.decode(self.get_param('charset', 'ascii'), 'replace')
+ except LookupError:
+ payload = bpayload.decode('ascii', 'replace')
+ elif decode:
+ try:
+ bpayload = payload.encode('ascii')
+ except UnicodeError:
+ # This won't happen for RFC compliant messages (messages
+ # containing only ASCII codepoints in the unicode input).
+ # If it does happen, turn the string into bytes in a way
+ # guaranteed not to fail.
+ bpayload = payload.encode('raw-unicode-escape')
if not decode:
return payload
- # Decoded payloads always return bytes. XXX split this part out into
- # a new method called .get_decoded_payload().
- if self.is_multipart():
- return None
- cte = self.get('content-transfer-encoding', '').lower()
if cte == 'quoted-printable':
- return utils._qdecode(payload)
+ return utils._qdecode(bpayload)
elif cte == 'base64':
try:
- if isinstance(payload, str):
- payload = payload.encode('raw-unicode-escape')
- return base64.b64decode(payload)
- #return utils._bdecode(payload)
+ return base64.b64decode(bpayload)
except binascii.Error:
# Incorrect padding
- pass
+ return bpayload
elif cte in ('x-uuencode', 'uuencode', 'uue', 'x-uue'):
- in_file = BytesIO(payload.encode('raw-unicode-escape'))
+ in_file = BytesIO(bpayload)
out_file = BytesIO()
try:
uu.decode(in_file, out_file, quiet=True)
return out_file.getvalue()
except uu.Error:
# Some decoding problem
- pass
- # Is there a better way to do this? We can't use the bytes
- # constructor.
+ return bpayload
if isinstance(payload, str):
- return payload.encode('raw-unicode-escape')
+ return bpayload
return payload
def set_payload(self, payload, charset=None):
@@ -348,7 +398,7 @@ class Message:
Any fields deleted and re-inserted are always appended to the header
list.
"""
- return [v for k, v in self._headers]
+ return [_sanitize_header(k, v) for k, v in self._headers]
def items(self):
"""Get all the message's header fields and values.
@@ -358,7 +408,7 @@ class Message:
Any fields deleted and re-inserted are always appended to the header
list.
"""
- return self._headers[:]
+ return [(k, _sanitize_header(k, v)) for k, v in self._headers]
def get(self, name, failobj=None):
"""Get a header value.
@@ -369,7 +419,7 @@ class Message:
name = name.lower()
for k, v in self._headers:
if k.lower() == name:
- return v
+ return _sanitize_header(k, v)
return failobj
#
@@ -389,7 +439,7 @@ class Message:
name = name.lower()
for k, v in self._headers:
if k.lower() == name:
- values.append(v)
+ values.append(_sanitize_header(k, v))
if not values:
return failobj
return values
diff --git a/Lib/email/parser.py b/Lib/email/parser.py
index 06014e2..6caaff5 100644
--- a/Lib/email/parser.py
+++ b/Lib/email/parser.py
@@ -7,7 +7,7 @@
__all__ = ['Parser', 'HeaderParser']
import warnings
-from io import StringIO
+from io import StringIO, TextIOWrapper
from email.feedparser import FeedParser
from email.message import Message
@@ -89,3 +89,48 @@ class HeaderParser(Parser):
def parsestr(self, text, headersonly=True):
return Parser.parsestr(self, text, True)
+
+
+class BytesParser:
+
+ def __init__(self, *args, **kw):
+ """Parser of binary RFC 2822 and MIME email messages.
+
+ Creates an in-memory object tree representing the email message, which
+ can then be manipulated and turned over to a Generator to return the
+ textual representation of the message.
+
+ The input must be formatted as a block of RFC 2822 headers and header
+ continuation lines, optionally preceeded by a `Unix-from' header. The
+ header block is terminated either by the end of the input or by a
+ blank line.
+
+ _class is the class to instantiate for new message objects when they
+ must be created. This class must have a constructor that can take
+ zero arguments. Default is Message.Message.
+ """
+ self.parser = Parser(*args, **kw)
+
+ def parse(self, fp, headersonly=False):
+ """Create a message structure from the data in a binary file.
+
+ Reads all the data from the file and returns the root of the message
+ structure. Optional headersonly is a flag specifying whether to stop
+ parsing after reading the headers or not. The default is False,
+ meaning it parses the entire contents of the file.
+ """
+ fp = TextIOWrapper(fp, encoding='ascii', errors='surrogateescape')
+ with fp:
+ return self.parser.parse(fp, headersonly)
+
+
+ def parsebytes(self, text, headersonly=False):
+ """Create a message structure from a byte string.
+
+ Returns the root of the message structure. Optional headersonly is a
+ flag specifying whether to stop parsing after reading the headers or
+ not. The default is False, meaning it parses the entire contents of
+ the file.
+ """
+ text = text.decode('ASCII', errors='surrogateescape')
+ return self.parser.parsestr(text, headersonly)
diff --git a/Lib/email/quoprimime.py b/Lib/email/quoprimime.py
index 85efc08..168dfff 100644
--- a/Lib/email/quoprimime.py
+++ b/Lib/email/quoprimime.py
@@ -11,7 +11,7 @@ character set, but that includes some 8-bit characters that are normally not
allowed in email bodies or headers.
Quoted-printable is very space-inefficient for encoding binary files; use the
-email.base64MIME module for that instead.
+email.base64mime module for that instead.
This module provides an interface to encode and decode both headers and bodies
with quoted-printable encoding.
@@ -23,7 +23,7 @@ in To:/From:/Cc: etc. fields, as well as Subject: lines.
This module does not do the line wrapping or end-of-line character
conversion necessary for proper internationalized headers; it only
does dumb encoding and decoding. To deal with the various line
-wrapping issues, use the email.Header module.
+wrapping issues, use the email.header module.
"""
__all__ = [
@@ -291,7 +291,7 @@ def header_decode(s):
This function does not parse a full MIME header value encoded with
quoted-printable (like =?iso-8895-1?q?Hello_World?=) -- please use
- the high level email.Header class for that functionality.
+ the high level email.header class for that functionality.
"""
s = s.replace('_', ' ')
return re.sub(r'=[a-fA-F0-9]{2}', _unquote_match, s, re.ASCII)
diff --git a/Lib/email/test/data/msg_10.txt b/Lib/email/test/data/msg_10.txt
index bd30d13..0790396 100644
--- a/Lib/email/test/data/msg_10.txt
+++ b/Lib/email/test/data/msg_10.txt
@@ -26,6 +26,13 @@ VGhpcyBpcyBhIEJhc2U2NCBlbmNvZGVkIG1lc3NhZ2Uu
--BOUNDARY
Content-Type: text/plain; charset="iso-8859-1"
+Content-Transfer-Encoding: Base64
+
+VGhpcyBpcyBhIEJhc2U2NCBlbmNvZGVkIG1lc3NhZ2UuCg==
+
+
+--BOUNDARY
+Content-Type: text/plain; charset="iso-8859-1"
This has no Content-Transfer-Encoding: header.
diff --git a/Lib/email/test/data/msg_26.txt b/Lib/email/test/data/msg_26.txt
index 6c71bce..58efaa9 100644
--- a/Lib/email/test/data/msg_26.txt
+++ b/Lib/email/test/data/msg_26.txt
@@ -24,7 +24,8 @@ Simple email with attachment.
--1618492860--2051301190--113853680
-Content-Type: application/riscos; name="clock.bmp,69c"; type=BMP; load=&fff69c4b; exec=&355dd4d1; access=&03
+Content-Type: application/riscos; name="clock.bmp,69c"; type=BMP;
+ load=&fff69c4b; exec=&355dd4d1; access=&03
Content-Disposition: attachment; filename="clock.bmp"
Content-Transfer-Encoding: base64
diff --git a/Lib/email/test/test_email.py b/Lib/email/test/test_email.py
index 4268a25..dcb2e95 100644
--- a/Lib/email/test/test_email.py
+++ b/Lib/email/test/test_email.py
@@ -3,6 +3,7 @@
# email package unit tests
import os
+import re
import sys
import time
import base64
@@ -11,7 +12,7 @@ import unittest
import warnings
import textwrap
-from io import StringIO
+from io import StringIO, BytesIO
from itertools import chain
import email
@@ -35,7 +36,7 @@ from email import iterators
from email import base64mime
from email import quoprimime
-from test.support import findfile, run_unittest
+from test.support import findfile, run_unittest, unlink
from email.test import __file__ as landmark
@@ -193,8 +194,8 @@ class TestMessageAPI(TestEmailBase):
def test_message_rfc822_only(self):
# Issue 7970: message/rfc822 not in multipart parsed by
# HeaderParser caused an exception when flattened.
- fp = openfile(findfile('msg_46.txt'))
- msgdata = fp.read()
+ with openfile(findfile('msg_46.txt')) as fp:
+ msgdata = fp.read()
parser = HeaderParser()
msg = parser.parsestr(msgdata)
out = StringIO()
@@ -216,8 +217,12 @@ class TestMessageAPI(TestEmailBase):
# Subpart 3 is base64
eq(msg.get_payload(2).get_payload(decode=True),
b'This is a Base64 encoded message.')
- # Subpart 4 has no Content-Transfer-Encoding: header.
+ # Subpart 4 is base64 with a trailing newline, which
+ # used to be stripped (issue 7143).
eq(msg.get_payload(3).get_payload(decode=True),
+ b'This is a Base64 encoded message.\n')
+ # Subpart 5 has no Content-Transfer-Encoding: header.
+ eq(msg.get_payload(4).get_payload(decode=True),
b'This has no Content-Transfer-Encoding: header.\n')
def test_get_decoded_uu_payload(self):
@@ -529,7 +534,7 @@ class TestMessageAPI(TestEmailBase):
msg.add_header('Content-Disposition', 'attachment',
filename="Fußballer.ppt")
self.assertEqual(
- 'attachment; filename*="utf-8\'\'Fu%C3%9Fballer.ppt"',
+ 'attachment; filename*=utf-8\'\'Fu%C3%9Fballer.ppt',
msg['Content-Disposition'])
def test_nonascii_add_header_via_triple(self):
@@ -537,9 +542,24 @@ class TestMessageAPI(TestEmailBase):
msg.add_header('Content-Disposition', 'attachment',
filename=('iso-8859-1', '', 'Fußballer.ppt'))
self.assertEqual(
- 'attachment; filename*="iso-8859-1\'\'Fu%DFballer.ppt"',
+ 'attachment; filename*=iso-8859-1\'\'Fu%DFballer.ppt',
msg['Content-Disposition'])
+ def test_ascii_add_header_with_tspecial(self):
+ msg = Message()
+ msg.add_header('Content-Disposition', 'attachment',
+ filename="windows [filename].ppt")
+ self.assertEqual(
+ 'attachment; filename="windows [filename].ppt"',
+ msg['Content-Disposition'])
+
+ def test_nonascii_add_header_with_tspecial(self):
+ msg = Message()
+ msg.add_header('Content-Disposition', 'attachment',
+ filename="Fußballer [filename].ppt")
+ self.assertEqual(
+ "attachment; filename*=utf-8''Fu%C3%9Fballer%20%5Bfilename%5D.ppt",
+ msg['Content-Disposition'])
# Issue 5871: reject an attempt to embed a header inside a header value
# (header injection attack).
@@ -714,6 +734,20 @@ wasnipoop; giraffes="very-long-necked-animals";
wasnipoop; giraffes="very-long-necked-animals";
\tspooge="yummy"; hippos="gargantuan"; marshmallows="gooey"''')
+ def test_header_encode_with_different_output_charset(self):
+ h = Header('æ–‡', 'euc-jp')
+ self.assertEqual(h.encode(), "=?iso-2022-jp?b?GyRCSjgbKEI=?=")
+
+ def test_long_header_encode_with_different_output_charset(self):
+ h = Header(b'test-ja \xa4\xd8\xc5\xea\xb9\xc6\xa4\xb5\xa4\xec\xa4'
+ b'\xbf\xa5\xe1\xa1\xbc\xa5\xeb\xa4\xcf\xbb\xca\xb2\xf1\xbc\xd4'
+ b'\xa4\xce\xbe\xb5\xc7\xa7\xa4\xf2\xc2\xd4\xa4\xc3\xa4\xc6\xa4'
+ b'\xa4\xa4\xde\xa4\xb9'.decode('euc-jp'), 'euc-jp')
+ res = """\
+=?iso-2022-jp?b?dGVzdC1qYSAbJEIkWEVqOUYkNSRsJD8lYSE8JWskTztKMnE8VCROPjUbKEI=?=
+ =?iso-2022-jp?b?GyRCRyckckJUJEMkRiQkJF4kORsoQg==?="""
+ self.assertEqual(h.encode(), res)
+
def test_header_splitter(self):
eq = self.ndiffAssertEqual
msg = MIMEText('')
@@ -2040,17 +2074,20 @@ message 2
# should be identical. Note: that we ignore the Unix-From since that may
# contain a changed date.
class TestIdempotent(TestEmailBase):
+
+ linesep = '\n'
+
def _msgobj(self, filename):
with openfile(filename) as fp:
data = fp.read()
msg = email.message_from_string(data)
return msg, data
- def _idempotent(self, msg, text):
+ def _idempotent(self, msg, text, unixfrom=False):
eq = self.ndiffAssertEqual
s = StringIO()
g = Generator(s, maxheaderlen=0)
- g.flatten(msg)
+ g.flatten(msg, unixfrom=unixfrom)
eq(text, s.getvalue())
def test_parse_text_message(self):
@@ -2137,6 +2174,14 @@ class TestIdempotent(TestEmailBase):
msg, text = self._msgobj('msg_36.txt')
self._idempotent(msg, text)
+ def test_message_delivery_status(self):
+ msg, text = self._msgobj('msg_43.txt')
+ self._idempotent(msg, text, unixfrom=True)
+
+ def test_message_signed_idempotent(self):
+ msg, text = self._msgobj('msg_45.txt')
+ self._idempotent(msg, text)
+
def test_content_type(self):
eq = self.assertEqual
unless = self.assertTrue
@@ -2149,16 +2194,16 @@ class TestIdempotent(TestEmailBase):
params[pk] = pv
eq(params['report-type'], 'delivery-status')
eq(params['boundary'], 'D1690A7AC1.996856090/mail.example.com')
- eq(msg.preamble, 'This is a MIME-encapsulated message.\n')
- eq(msg.epilogue, '\n')
+ eq(msg.preamble, 'This is a MIME-encapsulated message.' + self.linesep)
+ eq(msg.epilogue, self.linesep)
eq(len(msg.get_payload()), 3)
# Make sure the subparts are what we expect
msg1 = msg.get_payload(0)
eq(msg1.get_content_type(), 'text/plain')
- eq(msg1.get_payload(), 'Yadda yadda yadda\n')
+ eq(msg1.get_payload(), 'Yadda yadda yadda' + self.linesep)
msg2 = msg.get_payload(1)
eq(msg2.get_content_type(), 'text/plain')
- eq(msg2.get_payload(), 'Yadda yadda yadda\n')
+ eq(msg2.get_payload(), 'Yadda yadda yadda' + self.linesep)
msg3 = msg.get_payload(2)
eq(msg3.get_content_type(), 'message/rfc822')
self.assertTrue(isinstance(msg3, Message))
@@ -2167,7 +2212,7 @@ class TestIdempotent(TestEmailBase):
eq(len(payload), 1)
msg4 = payload[0]
unless(isinstance(msg4, Message))
- eq(msg4.get_payload(), 'Yadda yadda yadda\n')
+ eq(msg4.get_payload(), 'Yadda yadda yadda' + self.linesep)
def test_parser(self):
eq = self.assertEqual
@@ -2184,7 +2229,7 @@ class TestIdempotent(TestEmailBase):
self.assertTrue(isinstance(msg1, Message))
eq(msg1.get_content_type(), 'text/plain')
self.assertTrue(isinstance(msg1.get_payload(), str))
- eq(msg1.get_payload(), '\n')
+ eq(msg1.get_payload(), self.linesep)
@@ -2253,7 +2298,8 @@ class TestMiscellaneous(TestEmailBase):
all.sort()
self.assertEqual(all, [
'base64mime', 'charset', 'encoders', 'errors', 'generator',
- 'header', 'iterators', 'message', 'message_from_file',
+ 'header', 'iterators', 'message', 'message_from_binary_file',
+ 'message_from_bytes', 'message_from_file',
'message_from_string', 'mime', 'parser',
'quoprimime', 'utils',
])
@@ -2296,6 +2342,16 @@ class TestMiscellaneous(TestEmailBase):
eq(utils.parsedate_tz('5 Feb 2003 13:47:26 -0800'),
(2003, 2, 5, 13, 47, 26, 0, 1, -1, -28800))
+ def test_parsedate_no_space_before_positive_offset(self):
+ self.assertEqual(utils.parsedate_tz('Wed, 3 Apr 2002 14:58:26+0800'),
+ (2002, 4, 3, 14, 58, 26, 0, 1, -1, 28800))
+
+ def test_parsedate_no_space_before_negative_offset(self):
+ # Issue 1155362: we already handled '+' for this case.
+ self.assertEqual(utils.parsedate_tz('Wed, 3 Apr 2002 14:58:26-0800'),
+ (2002, 4, 3, 14, 58, 26, 0, 1, -1, -28800))
+
+
def test_parsedate_acceptable_to_time_functions(self):
eq = self.assertEqual
timetup = utils.parsedate('5 Feb 2003 13:47:26 -0800')
@@ -2372,6 +2428,24 @@ class TestMiscellaneous(TestEmailBase):
eq(utils.parseaddr('"\\\\"example\\\\" example"@example.com'),
('', '"\\\\"example\\\\" example"@example.com'))
+ def test_parseaddr_preserves_spaces_in_local_part(self):
+ # issue 9286. A normal RFC5322 local part should not contain any
+ # folding white space, but legacy local parts can (they are a sequence
+ # of atoms, not dotatoms). On the other hand we strip whitespace from
+ # before the @ and around dots, on the assumption that the whitespace
+ # around the punctuation is a mistake in what would otherwise be
+ # an RFC5322 local part. Leading whitespace is, usual, stripped as well.
+ self.assertEqual(('', "merwok wok@xample.com"),
+ utils.parseaddr("merwok wok@xample.com"))
+ self.assertEqual(('', "merwok wok@xample.com"),
+ utils.parseaddr("merwok wok@xample.com"))
+ self.assertEqual(('', "merwok wok@xample.com"),
+ utils.parseaddr(" merwok wok @xample.com"))
+ self.assertEqual(('', 'merwok"wok" wok@xample.com'),
+ utils.parseaddr('merwok"wok" wok@xample.com'))
+ self.assertEqual(('', 'merwok.wok.wok@xample.com'),
+ utils.parseaddr('merwok. wok . wok@xample.com'))
+
def test_multiline_from_comment(self):
x = """\
Foo
@@ -2510,6 +2584,10 @@ multipart/report
text/rfc822-headers
""")
+ def test_make_msgid_domain(self):
+ self.assertEqual(
+ email.utils.make_msgid(domain='testdomain-string')[-19:],
+ '@testdomain-string>')
# Test the iterator/generators
@@ -2661,6 +2739,18 @@ Here's the message body
part2 = msg.get_payload(1)
eq(part2.get_content_type(), 'application/riscos')
+ def test_crlf_flatten(self):
+ # Using newline='\n' preserves the crlfs in this input file.
+ with openfile('msg_26.txt', newline='\n') as fp:
+ text = fp.read()
+ msg = email.message_from_string(text)
+ s = StringIO()
+ g = Generator(s)
+ g.flatten(msg, linesep='\r\n')
+ self.assertEqual(s.getvalue(), text)
+
+ maxDiff = None
+
def test_multipart_digest_with_extra_mime_headers(self):
eq = self.assertEqual
neq = self.ndiffAssertEqual
@@ -2754,6 +2844,318 @@ Here's the message body
self.assertTrue(msg.get_payload(0).get_payload().endswith('\r\n'))
+class Test8BitBytesHandling(unittest.TestCase):
+ # In Python3 all input is string, but that doesn't work if the actual input
+ # uses an 8bit transfer encoding. To hack around that, in email 5.1 we
+ # decode byte streams using the surrogateescape error handler, and
+ # reconvert to binary at appropriate places if we detect surrogates. This
+ # doesn't allow us to transform headers with 8bit bytes (they get munged),
+ # but it does allow us to parse and preserve them, and to decode body
+ # parts that use an 8bit CTE.
+
+ bodytest_msg = textwrap.dedent("""\
+ From: foo@bar.com
+ To: baz
+ Mime-Version: 1.0
+ Content-Type: text/plain; charset={charset}
+ Content-Transfer-Encoding: {cte}
+
+ {bodyline}
+ """)
+
+ def test_known_8bit_CTE(self):
+ m = self.bodytest_msg.format(charset='utf-8',
+ cte='8bit',
+ bodyline='pöstal').encode('utf-8')
+ msg = email.message_from_bytes(m)
+ self.assertEqual(msg.get_payload(), "pöstal\n")
+ self.assertEqual(msg.get_payload(decode=True),
+ "pöstal\n".encode('utf-8'))
+
+ def test_unknown_8bit_CTE(self):
+ m = self.bodytest_msg.format(charset='notavalidcharset',
+ cte='8bit',
+ bodyline='pöstal').encode('utf-8')
+ msg = email.message_from_bytes(m)
+ self.assertEqual(msg.get_payload(), "p\uFFFD\uFFFDstal\n")
+ self.assertEqual(msg.get_payload(decode=True),
+ "pöstal\n".encode('utf-8'))
+
+ def test_8bit_in_quopri_body(self):
+ # This is non-RFC compliant data...without 'decode' the library code
+ # decodes the body using the charset from the headers, and because the
+ # source byte really is utf-8 this works. This is likely to fail
+ # against real dirty data (ie: produce mojibake), but the data is
+ # invalid anyway so it is as good a guess as any. But this means that
+ # this test just confirms the current behavior; that behavior is not
+ # necessarily the best possible behavior. With 'decode' it is
+ # returning the raw bytes, so that test should be of correct behavior,
+ # or at least produce the same result that email4 did.
+ m = self.bodytest_msg.format(charset='utf-8',
+ cte='quoted-printable',
+ bodyline='p=C3=B6stál').encode('utf-8')
+ msg = email.message_from_bytes(m)
+ self.assertEqual(msg.get_payload(), 'p=C3=B6stál\n')
+ self.assertEqual(msg.get_payload(decode=True),
+ 'pöstál\n'.encode('utf-8'))
+
+ def test_invalid_8bit_in_non_8bit_cte_uses_replace(self):
+ # This is similar to the previous test, but proves that if the 8bit
+ # byte is undecodeable in the specified charset, it gets replaced
+ # by the unicode 'unknown' character. Again, this may or may not
+ # be the ideal behavior. Note that if decode=False none of the
+ # decoders will get involved, so this is the only test we need
+ # for this behavior.
+ m = self.bodytest_msg.format(charset='ascii',
+ cte='quoted-printable',
+ bodyline='p=C3=B6stál').encode('utf-8')
+ msg = email.message_from_bytes(m)
+ self.assertEqual(msg.get_payload(), 'p=C3=B6st\uFFFD\uFFFDl\n')
+ self.assertEqual(msg.get_payload(decode=True),
+ 'pöstál\n'.encode('utf-8'))
+
+ def test_8bit_in_base64_body(self):
+ # Sticking an 8bit byte in a base64 block makes it undecodable by
+ # normal means, so the block is returned undecoded, but as bytes.
+ m = self.bodytest_msg.format(charset='utf-8',
+ cte='base64',
+ bodyline='cMO2c3RhbAá=').encode('utf-8')
+ msg = email.message_from_bytes(m)
+ self.assertEqual(msg.get_payload(decode=True),
+ 'cMO2c3RhbAá=\n'.encode('utf-8'))
+
+ def test_8bit_in_uuencode_body(self):
+ # Sticking an 8bit byte in a uuencode block makes it undecodable by
+ # normal means, so the block is returned undecoded, but as bytes.
+ m = self.bodytest_msg.format(charset='utf-8',
+ cte='uuencode',
+ bodyline='<,.V<W1A; á ').encode('utf-8')
+ msg = email.message_from_bytes(m)
+ self.assertEqual(msg.get_payload(decode=True),
+ '<,.V<W1A; á \n'.encode('utf-8'))
+
+
+ headertest_headers = (
+ ('From: foo@bar.com', ('From', 'foo@bar.com')),
+ ('To: báz', ('To', '=?unknown-8bit?q?b=C3=A1z?=')),
+ ('Subject: Maintenant je vous présente mon collègue, le pouf célèbre\n'
+ '\tJean de Baddie',
+ ('Subject', '=?unknown-8bit?q?Maintenant_je_vous_pr=C3=A9sente_mon_'
+ 'coll=C3=A8gue=2C_le_pouf_c=C3=A9l=C3=A8bre?=\n'
+ ' =?unknown-8bit?q?_Jean_de_Baddie?=')),
+ ('From: göst', ('From', '=?unknown-8bit?b?Z8O2c3Q=?=')),
+ )
+ headertest_msg = ('\n'.join([src for (src, _) in headertest_headers]) +
+ '\nYes, they are flying.\n').encode('utf-8')
+
+ def test_get_8bit_header(self):
+ msg = email.message_from_bytes(self.headertest_msg)
+ self.assertEqual(str(msg.get('to')), 'b\uFFFD\uFFFDz')
+ self.assertEqual(str(msg['to']), 'b\uFFFD\uFFFDz')
+
+ def test_print_8bit_headers(self):
+ msg = email.message_from_bytes(self.headertest_msg)
+ self.assertEqual(str(msg),
+ textwrap.dedent("""\
+ From: {}
+ To: {}
+ Subject: {}
+ From: {}
+
+ Yes, they are flying.
+ """).format(*[expected[1] for (_, expected) in
+ self.headertest_headers]))
+
+ def test_values_with_8bit_headers(self):
+ msg = email.message_from_bytes(self.headertest_msg)
+ self.assertListEqual([str(x) for x in msg.values()],
+ ['foo@bar.com',
+ 'b\uFFFD\uFFFDz',
+ 'Maintenant je vous pr\uFFFD\uFFFDsente mon '
+ 'coll\uFFFD\uFFFDgue, le pouf '
+ 'c\uFFFD\uFFFDl\uFFFD\uFFFDbre\n'
+ '\tJean de Baddie',
+ "g\uFFFD\uFFFDst"])
+
+ def test_items_with_8bit_headers(self):
+ msg = email.message_from_bytes(self.headertest_msg)
+ self.assertListEqual([(str(x), str(y)) for (x, y) in msg.items()],
+ [('From', 'foo@bar.com'),
+ ('To', 'b\uFFFD\uFFFDz'),
+ ('Subject', 'Maintenant je vous '
+ 'pr\uFFFD\uFFFDsente '
+ 'mon coll\uFFFD\uFFFDgue, le pouf '
+ 'c\uFFFD\uFFFDl\uFFFD\uFFFDbre\n'
+ '\tJean de Baddie'),
+ ('From', 'g\uFFFD\uFFFDst')])
+
+ def test_get_all_with_8bit_headers(self):
+ msg = email.message_from_bytes(self.headertest_msg)
+ self.assertListEqual([str(x) for x in msg.get_all('from')],
+ ['foo@bar.com',
+ 'g\uFFFD\uFFFDst'])
+
+ non_latin_bin_msg = textwrap.dedent("""\
+ From: foo@bar.com
+ To: báz
+ Subject: Maintenant je vous présente mon collègue, le pouf célèbre
+ \tJean de Baddie
+ Mime-Version: 1.0
+ Content-Type: text/plain; charset="utf-8"
+ Content-Transfer-Encoding: 8bit
+
+ Да, они летÑÑ‚.
+ """).encode('utf-8')
+
+ def test_bytes_generator(self):
+ msg = email.message_from_bytes(self.non_latin_bin_msg)
+ out = BytesIO()
+ email.generator.BytesGenerator(out).flatten(msg)
+ self.assertEqual(out.getvalue(), self.non_latin_bin_msg)
+
+ def test_bytes_generator_handles_None_body(self):
+ #Issue 11019
+ msg = email.message.Message()
+ out = BytesIO()
+ email.generator.BytesGenerator(out).flatten(msg)
+ self.assertEqual(out.getvalue(), b"\n")
+
+ non_latin_bin_msg_as7bit_wrapped = textwrap.dedent("""\
+ From: foo@bar.com
+ To: =?unknown-8bit?q?b=C3=A1z?=
+ Subject: =?unknown-8bit?q?Maintenant_je_vous_pr=C3=A9sente_mon_coll=C3=A8gue?=
+ =?unknown-8bit?q?=2C_le_pouf_c=C3=A9l=C3=A8bre?=
+ =?unknown-8bit?q?_Jean_de_Baddie?=
+ Mime-Version: 1.0
+ Content-Type: text/plain; charset="utf-8"
+ Content-Transfer-Encoding: base64
+
+ 0JTQsCwg0L7QvdC4INC70LXRgtGP0YIuCg==
+ """)
+
+ def test_generator_handles_8bit(self):
+ msg = email.message_from_bytes(self.non_latin_bin_msg)
+ out = StringIO()
+ email.generator.Generator(out).flatten(msg)
+ self.assertEqual(out.getvalue(), self.non_latin_bin_msg_as7bit_wrapped)
+
+ def test_bytes_generator_with_unix_from(self):
+ # The unixfrom contains a current date, so we can't check it
+ # literally. Just make sure the first word is 'From' and the
+ # rest of the message matches the input.
+ msg = email.message_from_bytes(self.non_latin_bin_msg)
+ out = BytesIO()
+ email.generator.BytesGenerator(out).flatten(msg, unixfrom=True)
+ lines = out.getvalue().split(b'\n')
+ self.assertEqual(lines[0].split()[0], b'From')
+ self.assertEqual(b'\n'.join(lines[1:]), self.non_latin_bin_msg)
+
+ non_latin_bin_msg_as7bit = non_latin_bin_msg_as7bit_wrapped.split('\n')
+ non_latin_bin_msg_as7bit[2:4] = [
+ 'Subject: =?unknown-8bit?q?Maintenant_je_vous_pr=C3=A9sente_mon_'
+ 'coll=C3=A8gue=2C_le_pouf_c=C3=A9l=C3=A8bre?=']
+ non_latin_bin_msg_as7bit = '\n'.join(non_latin_bin_msg_as7bit)
+
+ def test_message_from_binary_file(self):
+ fn = 'test.msg'
+ self.addCleanup(unlink, fn)
+ with open(fn, 'wb') as testfile:
+ testfile.write(self.non_latin_bin_msg)
+ with open(fn, 'rb') as testfile:
+ m = email.parser.BytesParser().parse(testfile)
+ self.assertEqual(str(m), self.non_latin_bin_msg_as7bit)
+
+ latin_bin_msg = textwrap.dedent("""\
+ From: foo@bar.com
+ To: Dinsdale
+ Subject: Nudge nudge, wink, wink
+ Mime-Version: 1.0
+ Content-Type: text/plain; charset="latin-1"
+ Content-Transfer-Encoding: 8bit
+
+ oh là là, know what I mean, know what I mean?
+ """).encode('latin-1')
+
+ latin_bin_msg_as7bit = textwrap.dedent("""\
+ From: foo@bar.com
+ To: Dinsdale
+ Subject: Nudge nudge, wink, wink
+ Mime-Version: 1.0
+ Content-Type: text/plain; charset="iso-8859-1"
+ Content-Transfer-Encoding: quoted-printable
+
+ oh l=E0 l=E0, know what I mean, know what I mean?
+ """)
+
+ def test_string_generator_reencodes_to_quopri_when_appropriate(self):
+ m = email.message_from_bytes(self.latin_bin_msg)
+ self.assertEqual(str(m), self.latin_bin_msg_as7bit)
+
+ def test_decoded_generator_emits_unicode_body(self):
+ m = email.message_from_bytes(self.latin_bin_msg)
+ out = StringIO()
+ email.generator.DecodedGenerator(out).flatten(m)
+ #DecodedHeader output contains an extra blank line compared
+ #to the input message. RDM: not sure if this is a bug or not,
+ #but it is not specific to the 8bit->7bit conversion.
+ self.assertEqual(out.getvalue(),
+ self.latin_bin_msg.decode('latin-1')+'\n')
+
+ def test_bytes_feedparser(self):
+ bfp = email.feedparser.BytesFeedParser()
+ for i in range(0, len(self.latin_bin_msg), 10):
+ bfp.feed(self.latin_bin_msg[i:i+10])
+ m = bfp.close()
+ self.assertEqual(str(m), self.latin_bin_msg_as7bit)
+
+ def test_crlf_flatten(self):
+ with openfile('msg_26.txt', 'rb') as fp:
+ text = fp.read()
+ msg = email.message_from_bytes(text)
+ s = BytesIO()
+ g = email.generator.BytesGenerator(s)
+ g.flatten(msg, linesep='\r\n')
+ self.assertEqual(s.getvalue(), text)
+ maxDiff = None
+
+
+class BaseTestBytesGeneratorIdempotent:
+
+ maxDiff = None
+
+ def _msgobj(self, filename):
+ with openfile(filename, 'rb') as fp:
+ data = fp.read()
+ data = self.normalize_linesep_regex.sub(self.blinesep, data)
+ msg = email.message_from_bytes(data)
+ return msg, data
+
+ def _idempotent(self, msg, data, unixfrom=False):
+ b = BytesIO()
+ g = email.generator.BytesGenerator(b, maxheaderlen=0)
+ g.flatten(msg, unixfrom=unixfrom, linesep=self.linesep)
+ self.assertByteStringsEqual(data, b.getvalue())
+
+ def assertByteStringsEqual(self, str1, str2):
+ # Not using self.blinesep here is intentional. This way the output
+ # is more useful when the failure results in mixed line endings.
+ self.assertListEqual(str1.split(b'\n'), str2.split(b'\n'))
+
+
+class TestBytesGeneratorIdempotentNL(BaseTestBytesGeneratorIdempotent,
+ TestIdempotent):
+ linesep = '\n'
+ blinesep = b'\n'
+ normalize_linesep_regex = re.compile(br'\r\n')
+
+
+class TestBytesGeneratorIdempotentCRLF(BaseTestBytesGeneratorIdempotent,
+ TestIdempotent):
+ linesep = '\r\n'
+ blinesep = b'\r\n'
+ normalize_linesep_regex = re.compile(br'(?<!\r)\n')
+
+
class TestBase64(unittest.TestCase):
def test_len(self):
eq = self.assertEqual
@@ -2973,9 +3375,9 @@ class TestCharset(unittest.TestCase):
# built-in encodings where the header encoding is QP but the body
# encoding is not.
from email import charset as CharsetModule
- CharsetModule.add_charset('fake', CharsetModule.QP, None)
+ CharsetModule.add_charset('fake', CharsetModule.QP, None, 'utf-8')
c = Charset('fake')
- eq('hello w\xf6rld', c.body_encode('hello w\xf6rld'))
+ eq('hello world', c.body_encode('hello world'))
def test_unicode_charset_name(self):
charset = Charset('us-ascii')
@@ -3342,7 +3744,7 @@ To: bbb@zzz.org
Subject: This is a test message
Date: Fri, 4 May 2001 14:05:44 -0400
Content-Type: text/plain; charset=us-ascii;
- title*="us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21"
+ title*=us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21
Hi,
@@ -3372,7 +3774,7 @@ To: bbb@zzz.org
Subject: This is a test message
Date: Fri, 4 May 2001 14:05:44 -0400
Content-Type: text/plain; charset="us-ascii";
- title*="us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21"
+ title*=us-ascii'en'This%20is%20even%20more%20%2A%2A%2Afun%2A%2A%2A%20isn%27t%20it%21
Hi,
@@ -3387,6 +3789,32 @@ Do you like this message?
msg = self._msgobj('msg_32.txt')
eq(msg.get_content_charset(), 'us-ascii')
+ def test_rfc2231_parse_rfc_quoting(self):
+ m = textwrap.dedent('''\
+ Content-Disposition: inline;
+ \tfilename*0*=''This%20is%20even%20more%20;
+ \tfilename*1*=%2A%2A%2Afun%2A%2A%2A%20;
+ \tfilename*2="is it not.pdf"
+
+ ''')
+ msg = email.message_from_string(m)
+ self.assertEqual(msg.get_filename(),
+ 'This is even more ***fun*** is it not.pdf')
+ self.assertEqual(m, msg.as_string())
+
+ def test_rfc2231_parse_extra_quoting(self):
+ m = textwrap.dedent('''\
+ Content-Disposition: inline;
+ \tfilename*0*="''This%20is%20even%20more%20";
+ \tfilename*1*="%2A%2A%2Afun%2A%2A%2A%20";
+ \tfilename*2="is it not.pdf"
+
+ ''')
+ msg = email.message_from_string(m)
+ self.assertEqual(msg.get_filename(),
+ 'This is even more ***fun*** is it not.pdf')
+ self.assertEqual(m, msg.as_string())
+
def test_rfc2231_no_language_or_charset(self):
m = '''\
Content-Transfer-Encoding: 8bit
diff --git a/Lib/email/test/test_email_codecs.py b/Lib/email/test/test_email_codecs.py
index acc19c3..ca85f57 100644
--- a/Lib/email/test/test_email_codecs.py
+++ b/Lib/email/test/test_email_codecs.py
@@ -13,7 +13,7 @@ from email.message import Message
# We're compatible with Python 2.3, but it doesn't have the built-in Asian
# codecs, so we have to skip all these tests.
try:
- str('foo', 'euc-jp')
+ str(b'foo', 'euc-jp')
except LookupError:
raise unittest.SkipTest
@@ -22,11 +22,14 @@ except LookupError:
class TestEmailAsianCodecs(TestEmailBase):
def test_japanese_codecs(self):
eq = self.ndiffAssertEqual
- j = Charset("euc-jp")
- g = Charset("iso-8859-1")
+ jcode = "euc-jp"
+ gcode = "iso-8859-1"
+ j = Charset(jcode)
+ g = Charset(gcode)
h = Header("Hello World!")
- jhello = '\xa5\xcf\xa5\xed\xa1\xbc\xa5\xef\xa1\xbc\xa5\xeb\xa5\xc9\xa1\xaa'
- ghello = 'Gr\xfc\xdf Gott!'
+ jhello = str(b'\xa5\xcf\xa5\xed\xa1\xbc\xa5\xef\xa1\xbc'
+ b'\xa5\xeb\xa5\xc9\xa1\xaa', jcode)
+ ghello = str(b'Gr\xfc\xdf Gott!', gcode)
h.append(jhello, j)
h.append(ghello, g)
# BAW: This used to -- and maybe should -- fold the two iso-8859-1
@@ -36,13 +39,17 @@ class TestEmailAsianCodecs(TestEmailBase):
# encoded word.
eq(h.encode(), """\
Hello World! =?iso-2022-jp?b?GyRCJU8lbSE8JW8hPCVrJUkhKhsoQg==?=
- =?iso-8859-1?q?Gr=FC=DF?= =?iso-8859-1?q?_Gott!?=""")
+ =?iso-8859-1?q?Gr=FC=DF_Gott!?=""")
eq(decode_header(h.encode()),
- [('Hello World!', None),
- ('\x1b$B%O%m!<%o!<%k%I!*\x1b(B', 'iso-2022-jp'),
- ('Gr\xfc\xdf Gott!', 'iso-8859-1')])
- int = 'test-ja \xa4\xd8\xc5\xea\xb9\xc6\xa4\xb5\xa4\xec\xa4\xbf\xa5\xe1\xa1\xbc\xa5\xeb\xa4\xcf\xbb\xca\xb2\xf1\xbc\xd4\xa4\xce\xbe\xb5\xc7\xa7\xa4\xf2\xc2\xd4\xa4\xc3\xa4\xc6\xa4\xa4\xa4\xde\xa4\xb9'
- h = Header(int, j, header_name="Subject")
+ [(b'Hello World!', None),
+ (b'\x1b$B%O%m!<%o!<%k%I!*\x1b(B', 'iso-2022-jp'),
+ (b'Gr\xfc\xdf Gott!', gcode)])
+ subject_bytes = (b'test-ja \xa4\xd8\xc5\xea\xb9\xc6\xa4\xb5'
+ b'\xa4\xec\xa4\xbf\xa5\xe1\xa1\xbc\xa5\xeb\xa4\xcf\xbb\xca\xb2'
+ b'\xf1\xbc\xd4\xa4\xce\xbe\xb5\xc7\xa7\xa4\xf2\xc2\xd4\xa4\xc3'
+ b'\xa4\xc6\xa4\xa4\xa4\xde\xa4\xb9')
+ subject = str(subject_bytes, jcode)
+ h = Header(subject, j, header_name="Subject")
# test a very long header
enc = h.encode()
# TK: splitting point may differ by codec design and/or Header encoding
@@ -50,15 +57,24 @@ Hello World! =?iso-2022-jp?b?GyRCJU8lbSE8JW8hPCVrJUkhKhsoQg==?=
=?iso-2022-jp?b?dGVzdC1qYSAbJEIkWEVqOUYkNSRsJD8lYSE8JWskTztKGyhC?=
=?iso-2022-jp?b?GyRCMnE8VCROPjVHJyRyQlQkQyRGJCQkXiQ5GyhC?=""")
# TK: full decode comparison
- eq(h.__unicode__().encode('euc-jp'), int)
+ eq(str(h).encode(jcode), subject_bytes)
+
+ def test_payload_encoding_utf8(self):
+ jhello = str(b'\xa5\xcf\xa5\xed\xa1\xbc\xa5\xef\xa1\xbc'
+ b'\xa5\xeb\xa5\xc9\xa1\xaa', 'euc-jp')
+ msg = Message()
+ msg.set_payload(jhello, 'utf-8')
+ ustr = msg.get_payload(decode=True).decode(msg.get_content_charset())
+ self.assertEqual(jhello, ustr)
def test_payload_encoding(self):
- jhello = '\xa5\xcf\xa5\xed\xa1\xbc\xa5\xef\xa1\xbc\xa5\xeb\xa5\xc9\xa1\xaa'
jcode = 'euc-jp'
+ jhello = str(b'\xa5\xcf\xa5\xed\xa1\xbc\xa5\xef\xa1\xbc'
+ b'\xa5\xeb\xa5\xc9\xa1\xaa', jcode)
msg = Message()
msg.set_payload(jhello, jcode)
- ustr = str(msg.get_payload(), msg.get_content_charset())
- self.assertEqual(jhello, ustr.encode(jcode))
+ ustr = msg.get_payload(decode=True).decode(msg.get_content_charset())
+ self.assertEqual(jhello, ustr)
diff --git a/Lib/email/test/test_email_torture.py b/Lib/email/test/test_email_torture.py
index 57233bf..544b1bb 100644
--- a/Lib/email/test/test_email_torture.py
+++ b/Lib/email/test/test_email_torture.py
@@ -13,11 +13,11 @@ from io import StringIO
from types import ListType
from email.test.test_email import TestEmailBase
-from test.support import TestSkipped
+from test.support import TestSkipped, run_unittest
import email
from email import __file__ as testfile
-from email.Iterators import _structure
+from email.iterators import _structure
def openfile(filename):
from os.path import join, dirname, abspath
@@ -128,7 +128,7 @@ def suite():
def test_main():
for testclass in _testclasses():
- support.run_unittest(testclass)
+ run_unittest(testclass)
diff --git a/Lib/email/utils.py b/Lib/email/utils.py
index 5f40bac..ac4da37 100644
--- a/Lib/email/utils.py
+++ b/Lib/email/utils.py
@@ -148,13 +148,15 @@ def formatdate(timeval=None, localtime=False, usegmt=False):
-def make_msgid(idstring=None):
+def make_msgid(idstring=None, domain=None):
"""Returns a string suitable for RFC 2822 compliant Message-ID, e.g:
<20020201195627.33539.96671@nightshade.la.mastaler.com>
Optional idstring if given is a string used to strengthen the
- uniqueness of the message id.
+ uniqueness of the message id. Optional domain if given provides the
+ portion of the message id after the '@'. It defaults to the locally
+ defined hostname.
"""
timeval = time.time()
utcdate = time.strftime('%Y%m%d%H%M%S', time.gmtime(timeval))
@@ -164,8 +166,9 @@ def make_msgid(idstring=None):
idstring = ''
else:
idstring = '.' + idstring
- idhost = socket.getfqdn()
- msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, idhost)
+ if domain is None:
+ domain = socket.getfqdn()
+ msgid = '<%s.%s.%s%s@%s>' % (utcdate, pid, randint, idstring, domain)
return msgid
diff --git a/Lib/encodings/__init__.py b/Lib/encodings/__init__.py
index d72eae9..b189bd9 100644
--- a/Lib/encodings/__init__.py
+++ b/Lib/encodings/__init__.py
@@ -10,7 +10,7 @@
Each codec module must export the following interface:
* getregentry() -> codecs.CodecInfo object
- The getregentry() API must a CodecInfo object with encoder, decoder,
+ The getregentry() API must return a CodecInfo object with encoder, decoder,
incrementalencoder, incrementaldecoder, streamwriter and streamreader
atttributes which adhere to the Python Codec Interface Standard.
diff --git a/Lib/encodings/aliases.py b/Lib/encodings/aliases.py
index 4c35588..235deb5 100644
--- a/Lib/encodings/aliases.py
+++ b/Lib/encodings/aliases.py
@@ -146,6 +146,11 @@ aliases = {
'csibm857' : 'cp857',
'ibm857' : 'cp857',
+ # cp858 codec
+ '858' : 'cp858',
+ 'csibm858' : 'cp858',
+ 'ibm858' : 'cp858',
+
# cp860 codec
'860' : 'cp860',
'csibm860' : 'cp860',
@@ -430,6 +435,7 @@ aliases = {
'maclatin2' : 'mac_latin2',
# mac_roman codec
+ 'macintosh' : 'mac_roman',
'macroman' : 'mac_roman',
# mac_turkish codec
@@ -442,7 +448,7 @@ aliases = {
'csptcp154' : 'ptcp154',
'pt154' : 'ptcp154',
'cp154' : 'ptcp154',
- 'cyrillic-asian' : 'ptcp154',
+ 'cyrillic_asian' : 'ptcp154',
## quopri_codec codec
#'quopri' : 'quopri_codec',
diff --git a/Lib/encodings/base64_codec.py b/Lib/encodings/base64_codec.py
new file mode 100644
index 0000000..321a961
--- /dev/null
+++ b/Lib/encodings/base64_codec.py
@@ -0,0 +1,55 @@
+"""Python 'base64_codec' Codec - base64 content transfer encoding.
+
+This codec de/encodes from bytes to bytes and is therefore usable with
+bytes.transform() and bytes.untransform().
+
+Written by Marc-Andre Lemburg (mal@lemburg.com).
+"""
+
+import codecs
+import base64
+
+### Codec APIs
+
+def base64_encode(input, errors='strict'):
+ assert errors == 'strict'
+ return (base64.encodebytes(input), len(input))
+
+def base64_decode(input, errors='strict'):
+ assert errors == 'strict'
+ return (base64.decodebytes(input), len(input))
+
+class Codec(codecs.Codec):
+ def encode(self, input, errors='strict'):
+ return base64_encode(input, errors)
+ def decode(self, input, errors='strict'):
+ return base64_decode(input, errors)
+
+class IncrementalEncoder(codecs.IncrementalEncoder):
+ def encode(self, input, final=False):
+ assert self.errors == 'strict'
+ return base64.encodebytes(input)
+
+class IncrementalDecoder(codecs.IncrementalDecoder):
+ def decode(self, input, final=False):
+ assert self.errors == 'strict'
+ return base64.decodebytes(input)
+
+class StreamWriter(Codec, codecs.StreamWriter):
+ charbuffertype = bytes
+
+class StreamReader(Codec, codecs.StreamReader):
+ charbuffertype = bytes
+
+### encodings module API
+
+def getregentry():
+ return codecs.CodecInfo(
+ name='base64',
+ encode=base64_encode,
+ decode=base64_decode,
+ incrementalencoder=IncrementalEncoder,
+ incrementaldecoder=IncrementalDecoder,
+ streamwriter=StreamWriter,
+ streamreader=StreamReader,
+ )
diff --git a/Lib/encodings/bz2_codec.py b/Lib/encodings/bz2_codec.py
new file mode 100644
index 0000000..e65d226
--- /dev/null
+++ b/Lib/encodings/bz2_codec.py
@@ -0,0 +1,77 @@
+"""Python 'bz2_codec' Codec - bz2 compression encoding.
+
+This codec de/encodes from bytes to bytes and is therefore usable with
+bytes.transform() and bytes.untransform().
+
+Adapted by Raymond Hettinger from zlib_codec.py which was written
+by Marc-Andre Lemburg (mal@lemburg.com).
+"""
+
+import codecs
+import bz2 # this codec needs the optional bz2 module !
+
+### Codec APIs
+
+def bz2_encode(input, errors='strict'):
+ assert errors == 'strict'
+ return (bz2.compress(input), len(input))
+
+def bz2_decode(input, errors='strict'):
+ assert errors == 'strict'
+ return (bz2.decompress(input), len(input))
+
+class Codec(codecs.Codec):
+ def encode(self, input, errors='strict'):
+ return bz2_encode(input, errors)
+ def decode(self, input, errors='strict'):
+ return bz2_decode(input, errors)
+
+class IncrementalEncoder(codecs.IncrementalEncoder):
+ def __init__(self, errors='strict'):
+ assert errors == 'strict'
+ self.errors = errors
+ self.compressobj = bz2.BZ2Compressor()
+
+ def encode(self, input, final=False):
+ if final:
+ c = self.compressobj.compress(input)
+ return c + self.compressobj.flush()
+ else:
+ return self.compressobj.compress(input)
+
+ def reset(self):
+ self.compressobj = bz2.BZ2Compressor()
+
+class IncrementalDecoder(codecs.IncrementalDecoder):
+ def __init__(self, errors='strict'):
+ assert errors == 'strict'
+ self.errors = errors
+ self.decompressobj = bz2.BZ2Decompressor()
+
+ def decode(self, input, final=False):
+ try:
+ return self.decompressobj.decompress(input)
+ except EOFError:
+ return ''
+
+ def reset(self):
+ self.decompressobj = bz2.BZ2Decompressor()
+
+class StreamWriter(Codec, codecs.StreamWriter):
+ charbuffertype = bytes
+
+class StreamReader(Codec, codecs.StreamReader):
+ charbuffertype = bytes
+
+### encodings module API
+
+def getregentry():
+ return codecs.CodecInfo(
+ name="bz2",
+ encode=bz2_encode,
+ decode=bz2_decode,
+ incrementalencoder=IncrementalEncoder,
+ incrementaldecoder=IncrementalDecoder,
+ streamwriter=StreamWriter,
+ streamreader=StreamReader,
+ )
diff --git a/Lib/encodings/cp720.py b/Lib/encodings/cp720.py
new file mode 100644
index 0000000..96d6096
--- /dev/null
+++ b/Lib/encodings/cp720.py
@@ -0,0 +1,309 @@
+"""Python Character Mapping Codec cp720 generated on Windows:
+Vista 6.0.6002 SP2 Multiprocessor Free with the command:
+ python Tools/unicode/genwincodec.py 720
+"""#"
+
+
+import codecs
+
+### Codec APIs
+
+class Codec(codecs.Codec):
+
+ def encode(self,input,errors='strict'):
+ return codecs.charmap_encode(input,errors,encoding_table)
+
+ def decode(self,input,errors='strict'):
+ return codecs.charmap_decode(input,errors,decoding_table)
+
+class IncrementalEncoder(codecs.IncrementalEncoder):
+ def encode(self, input, final=False):
+ return codecs.charmap_encode(input,self.errors,encoding_table)[0]
+
+class IncrementalDecoder(codecs.IncrementalDecoder):
+ def decode(self, input, final=False):
+ return codecs.charmap_decode(input,self.errors,decoding_table)[0]
+
+class StreamWriter(Codec,codecs.StreamWriter):
+ pass
+
+class StreamReader(Codec,codecs.StreamReader):
+ pass
+
+### encodings module API
+
+def getregentry():
+ return codecs.CodecInfo(
+ name='cp720',
+ encode=Codec().encode,
+ decode=Codec().decode,
+ incrementalencoder=IncrementalEncoder,
+ incrementaldecoder=IncrementalDecoder,
+ streamreader=StreamReader,
+ streamwriter=StreamWriter,
+ )
+
+
+### Decoding Table
+
+decoding_table = (
+ '\x00' # 0x00 -> CONTROL CHARACTER
+ '\x01' # 0x01 -> CONTROL CHARACTER
+ '\x02' # 0x02 -> CONTROL CHARACTER
+ '\x03' # 0x03 -> CONTROL CHARACTER
+ '\x04' # 0x04 -> CONTROL CHARACTER
+ '\x05' # 0x05 -> CONTROL CHARACTER
+ '\x06' # 0x06 -> CONTROL CHARACTER
+ '\x07' # 0x07 -> CONTROL CHARACTER
+ '\x08' # 0x08 -> CONTROL CHARACTER
+ '\t' # 0x09 -> CONTROL CHARACTER
+ '\n' # 0x0A -> CONTROL CHARACTER
+ '\x0b' # 0x0B -> CONTROL CHARACTER
+ '\x0c' # 0x0C -> CONTROL CHARACTER
+ '\r' # 0x0D -> CONTROL CHARACTER
+ '\x0e' # 0x0E -> CONTROL CHARACTER
+ '\x0f' # 0x0F -> CONTROL CHARACTER
+ '\x10' # 0x10 -> CONTROL CHARACTER
+ '\x11' # 0x11 -> CONTROL CHARACTER
+ '\x12' # 0x12 -> CONTROL CHARACTER
+ '\x13' # 0x13 -> CONTROL CHARACTER
+ '\x14' # 0x14 -> CONTROL CHARACTER
+ '\x15' # 0x15 -> CONTROL CHARACTER
+ '\x16' # 0x16 -> CONTROL CHARACTER
+ '\x17' # 0x17 -> CONTROL CHARACTER
+ '\x18' # 0x18 -> CONTROL CHARACTER
+ '\x19' # 0x19 -> CONTROL CHARACTER
+ '\x1a' # 0x1A -> CONTROL CHARACTER
+ '\x1b' # 0x1B -> CONTROL CHARACTER
+ '\x1c' # 0x1C -> CONTROL CHARACTER
+ '\x1d' # 0x1D -> CONTROL CHARACTER
+ '\x1e' # 0x1E -> CONTROL CHARACTER
+ '\x1f' # 0x1F -> CONTROL CHARACTER
+ ' ' # 0x20 -> SPACE
+ '!' # 0x21 -> EXCLAMATION MARK
+ '"' # 0x22 -> QUOTATION MARK
+ '#' # 0x23 -> NUMBER SIGN
+ '$' # 0x24 -> DOLLAR SIGN
+ '%' # 0x25 -> PERCENT SIGN
+ '&' # 0x26 -> AMPERSAND
+ "'" # 0x27 -> APOSTROPHE
+ '(' # 0x28 -> LEFT PARENTHESIS
+ ')' # 0x29 -> RIGHT PARENTHESIS
+ '*' # 0x2A -> ASTERISK
+ '+' # 0x2B -> PLUS SIGN
+ ',' # 0x2C -> COMMA
+ '-' # 0x2D -> HYPHEN-MINUS
+ '.' # 0x2E -> FULL STOP
+ '/' # 0x2F -> SOLIDUS
+ '0' # 0x30 -> DIGIT ZERO
+ '1' # 0x31 -> DIGIT ONE
+ '2' # 0x32 -> DIGIT TWO
+ '3' # 0x33 -> DIGIT THREE
+ '4' # 0x34 -> DIGIT FOUR
+ '5' # 0x35 -> DIGIT FIVE
+ '6' # 0x36 -> DIGIT SIX
+ '7' # 0x37 -> DIGIT SEVEN
+ '8' # 0x38 -> DIGIT EIGHT
+ '9' # 0x39 -> DIGIT NINE
+ ':' # 0x3A -> COLON
+ ';' # 0x3B -> SEMICOLON
+ '<' # 0x3C -> LESS-THAN SIGN
+ '=' # 0x3D -> EQUALS SIGN
+ '>' # 0x3E -> GREATER-THAN SIGN
+ '?' # 0x3F -> QUESTION MARK
+ '@' # 0x40 -> COMMERCIAL AT
+ 'A' # 0x41 -> LATIN CAPITAL LETTER A
+ 'B' # 0x42 -> LATIN CAPITAL LETTER B
+ 'C' # 0x43 -> LATIN CAPITAL LETTER C
+ 'D' # 0x44 -> LATIN CAPITAL LETTER D
+ 'E' # 0x45 -> LATIN CAPITAL LETTER E
+ 'F' # 0x46 -> LATIN CAPITAL LETTER F
+ 'G' # 0x47 -> LATIN CAPITAL LETTER G
+ 'H' # 0x48 -> LATIN CAPITAL LETTER H
+ 'I' # 0x49 -> LATIN CAPITAL LETTER I
+ 'J' # 0x4A -> LATIN CAPITAL LETTER J
+ 'K' # 0x4B -> LATIN CAPITAL LETTER K
+ 'L' # 0x4C -> LATIN CAPITAL LETTER L
+ 'M' # 0x4D -> LATIN CAPITAL LETTER M
+ 'N' # 0x4E -> LATIN CAPITAL LETTER N
+ 'O' # 0x4F -> LATIN CAPITAL LETTER O
+ 'P' # 0x50 -> LATIN CAPITAL LETTER P
+ 'Q' # 0x51 -> LATIN CAPITAL LETTER Q
+ 'R' # 0x52 -> LATIN CAPITAL LETTER R
+ 'S' # 0x53 -> LATIN CAPITAL LETTER S
+ 'T' # 0x54 -> LATIN CAPITAL LETTER T
+ 'U' # 0x55 -> LATIN CAPITAL LETTER U
+ 'V' # 0x56 -> LATIN CAPITAL LETTER V
+ 'W' # 0x57 -> LATIN CAPITAL LETTER W
+ 'X' # 0x58 -> LATIN CAPITAL LETTER X
+ 'Y' # 0x59 -> LATIN CAPITAL LETTER Y
+ 'Z' # 0x5A -> LATIN CAPITAL LETTER Z
+ '[' # 0x5B -> LEFT SQUARE BRACKET
+ '\\' # 0x5C -> REVERSE SOLIDUS
+ ']' # 0x5D -> RIGHT SQUARE BRACKET
+ '^' # 0x5E -> CIRCUMFLEX ACCENT
+ '_' # 0x5F -> LOW LINE
+ '`' # 0x60 -> GRAVE ACCENT
+ 'a' # 0x61 -> LATIN SMALL LETTER A
+ 'b' # 0x62 -> LATIN SMALL LETTER B
+ 'c' # 0x63 -> LATIN SMALL LETTER C
+ 'd' # 0x64 -> LATIN SMALL LETTER D
+ 'e' # 0x65 -> LATIN SMALL LETTER E
+ 'f' # 0x66 -> LATIN SMALL LETTER F
+ 'g' # 0x67 -> LATIN SMALL LETTER G
+ 'h' # 0x68 -> LATIN SMALL LETTER H
+ 'i' # 0x69 -> LATIN SMALL LETTER I
+ 'j' # 0x6A -> LATIN SMALL LETTER J
+ 'k' # 0x6B -> LATIN SMALL LETTER K
+ 'l' # 0x6C -> LATIN SMALL LETTER L
+ 'm' # 0x6D -> LATIN SMALL LETTER M
+ 'n' # 0x6E -> LATIN SMALL LETTER N
+ 'o' # 0x6F -> LATIN SMALL LETTER O
+ 'p' # 0x70 -> LATIN SMALL LETTER P
+ 'q' # 0x71 -> LATIN SMALL LETTER Q
+ 'r' # 0x72 -> LATIN SMALL LETTER R
+ 's' # 0x73 -> LATIN SMALL LETTER S
+ 't' # 0x74 -> LATIN SMALL LETTER T
+ 'u' # 0x75 -> LATIN SMALL LETTER U
+ 'v' # 0x76 -> LATIN SMALL LETTER V
+ 'w' # 0x77 -> LATIN SMALL LETTER W
+ 'x' # 0x78 -> LATIN SMALL LETTER X
+ 'y' # 0x79 -> LATIN SMALL LETTER Y
+ 'z' # 0x7A -> LATIN SMALL LETTER Z
+ '{' # 0x7B -> LEFT CURLY BRACKET
+ '|' # 0x7C -> VERTICAL LINE
+ '}' # 0x7D -> RIGHT CURLY BRACKET
+ '~' # 0x7E -> TILDE
+ '\x7f' # 0x7F -> CONTROL CHARACTER
+ '\x80'
+ '\x81'
+ '\xe9' # 0x82 -> LATIN SMALL LETTER E WITH ACUTE
+ '\xe2' # 0x83 -> LATIN SMALL LETTER A WITH CIRCUMFLEX
+ '\x84'
+ '\xe0' # 0x85 -> LATIN SMALL LETTER A WITH GRAVE
+ '\x86'
+ '\xe7' # 0x87 -> LATIN SMALL LETTER C WITH CEDILLA
+ '\xea' # 0x88 -> LATIN SMALL LETTER E WITH CIRCUMFLEX
+ '\xeb' # 0x89 -> LATIN SMALL LETTER E WITH DIAERESIS
+ '\xe8' # 0x8A -> LATIN SMALL LETTER E WITH GRAVE
+ '\xef' # 0x8B -> LATIN SMALL LETTER I WITH DIAERESIS
+ '\xee' # 0x8C -> LATIN SMALL LETTER I WITH CIRCUMFLEX
+ '\x8d'
+ '\x8e'
+ '\x8f'
+ '\x90'
+ '\u0651' # 0x91 -> ARABIC SHADDA
+ '\u0652' # 0x92 -> ARABIC SUKUN
+ '\xf4' # 0x93 -> LATIN SMALL LETTER O WITH CIRCUMFLEX
+ '\xa4' # 0x94 -> CURRENCY SIGN
+ '\u0640' # 0x95 -> ARABIC TATWEEL
+ '\xfb' # 0x96 -> LATIN SMALL LETTER U WITH CIRCUMFLEX
+ '\xf9' # 0x97 -> LATIN SMALL LETTER U WITH GRAVE
+ '\u0621' # 0x98 -> ARABIC LETTER HAMZA
+ '\u0622' # 0x99 -> ARABIC LETTER ALEF WITH MADDA ABOVE
+ '\u0623' # 0x9A -> ARABIC LETTER ALEF WITH HAMZA ABOVE
+ '\u0624' # 0x9B -> ARABIC LETTER WAW WITH HAMZA ABOVE
+ '\xa3' # 0x9C -> POUND SIGN
+ '\u0625' # 0x9D -> ARABIC LETTER ALEF WITH HAMZA BELOW
+ '\u0626' # 0x9E -> ARABIC LETTER YEH WITH HAMZA ABOVE
+ '\u0627' # 0x9F -> ARABIC LETTER ALEF
+ '\u0628' # 0xA0 -> ARABIC LETTER BEH
+ '\u0629' # 0xA1 -> ARABIC LETTER TEH MARBUTA
+ '\u062a' # 0xA2 -> ARABIC LETTER TEH
+ '\u062b' # 0xA3 -> ARABIC LETTER THEH
+ '\u062c' # 0xA4 -> ARABIC LETTER JEEM
+ '\u062d' # 0xA5 -> ARABIC LETTER HAH
+ '\u062e' # 0xA6 -> ARABIC LETTER KHAH
+ '\u062f' # 0xA7 -> ARABIC LETTER DAL
+ '\u0630' # 0xA8 -> ARABIC LETTER THAL
+ '\u0631' # 0xA9 -> ARABIC LETTER REH
+ '\u0632' # 0xAA -> ARABIC LETTER ZAIN
+ '\u0633' # 0xAB -> ARABIC LETTER SEEN
+ '\u0634' # 0xAC -> ARABIC LETTER SHEEN
+ '\u0635' # 0xAD -> ARABIC LETTER SAD
+ '\xab' # 0xAE -> LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+ '\xbb' # 0xAF -> RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+ '\u2591' # 0xB0 -> LIGHT SHADE
+ '\u2592' # 0xB1 -> MEDIUM SHADE
+ '\u2593' # 0xB2 -> DARK SHADE
+ '\u2502' # 0xB3 -> BOX DRAWINGS LIGHT VERTICAL
+ '\u2524' # 0xB4 -> BOX DRAWINGS LIGHT VERTICAL AND LEFT
+ '\u2561' # 0xB5 -> BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE
+ '\u2562' # 0xB6 -> BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE
+ '\u2556' # 0xB7 -> BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE
+ '\u2555' # 0xB8 -> BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE
+ '\u2563' # 0xB9 -> BOX DRAWINGS DOUBLE VERTICAL AND LEFT
+ '\u2551' # 0xBA -> BOX DRAWINGS DOUBLE VERTICAL
+ '\u2557' # 0xBB -> BOX DRAWINGS DOUBLE DOWN AND LEFT
+ '\u255d' # 0xBC -> BOX DRAWINGS DOUBLE UP AND LEFT
+ '\u255c' # 0xBD -> BOX DRAWINGS UP DOUBLE AND LEFT SINGLE
+ '\u255b' # 0xBE -> BOX DRAWINGS UP SINGLE AND LEFT DOUBLE
+ '\u2510' # 0xBF -> BOX DRAWINGS LIGHT DOWN AND LEFT
+ '\u2514' # 0xC0 -> BOX DRAWINGS LIGHT UP AND RIGHT
+ '\u2534' # 0xC1 -> BOX DRAWINGS LIGHT UP AND HORIZONTAL
+ '\u252c' # 0xC2 -> BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+ '\u251c' # 0xC3 -> BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+ '\u2500' # 0xC4 -> BOX DRAWINGS LIGHT HORIZONTAL
+ '\u253c' # 0xC5 -> BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+ '\u255e' # 0xC6 -> BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE
+ '\u255f' # 0xC7 -> BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE
+ '\u255a' # 0xC8 -> BOX DRAWINGS DOUBLE UP AND RIGHT
+ '\u2554' # 0xC9 -> BOX DRAWINGS DOUBLE DOWN AND RIGHT
+ '\u2569' # 0xCA -> BOX DRAWINGS DOUBLE UP AND HORIZONTAL
+ '\u2566' # 0xCB -> BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
+ '\u2560' # 0xCC -> BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
+ '\u2550' # 0xCD -> BOX DRAWINGS DOUBLE HORIZONTAL
+ '\u256c' # 0xCE -> BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
+ '\u2567' # 0xCF -> BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE
+ '\u2568' # 0xD0 -> BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE
+ '\u2564' # 0xD1 -> BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE
+ '\u2565' # 0xD2 -> BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE
+ '\u2559' # 0xD3 -> BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE
+ '\u2558' # 0xD4 -> BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE
+ '\u2552' # 0xD5 -> BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE
+ '\u2553' # 0xD6 -> BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE
+ '\u256b' # 0xD7 -> BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE
+ '\u256a' # 0xD8 -> BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE
+ '\u2518' # 0xD9 -> BOX DRAWINGS LIGHT UP AND LEFT
+ '\u250c' # 0xDA -> BOX DRAWINGS LIGHT DOWN AND RIGHT
+ '\u2588' # 0xDB -> FULL BLOCK
+ '\u2584' # 0xDC -> LOWER HALF BLOCK
+ '\u258c' # 0xDD -> LEFT HALF BLOCK
+ '\u2590' # 0xDE -> RIGHT HALF BLOCK
+ '\u2580' # 0xDF -> UPPER HALF BLOCK
+ '\u0636' # 0xE0 -> ARABIC LETTER DAD
+ '\u0637' # 0xE1 -> ARABIC LETTER TAH
+ '\u0638' # 0xE2 -> ARABIC LETTER ZAH
+ '\u0639' # 0xE3 -> ARABIC LETTER AIN
+ '\u063a' # 0xE4 -> ARABIC LETTER GHAIN
+ '\u0641' # 0xE5 -> ARABIC LETTER FEH
+ '\xb5' # 0xE6 -> MICRO SIGN
+ '\u0642' # 0xE7 -> ARABIC LETTER QAF
+ '\u0643' # 0xE8 -> ARABIC LETTER KAF
+ '\u0644' # 0xE9 -> ARABIC LETTER LAM
+ '\u0645' # 0xEA -> ARABIC LETTER MEEM
+ '\u0646' # 0xEB -> ARABIC LETTER NOON
+ '\u0647' # 0xEC -> ARABIC LETTER HEH
+ '\u0648' # 0xED -> ARABIC LETTER WAW
+ '\u0649' # 0xEE -> ARABIC LETTER ALEF MAKSURA
+ '\u064a' # 0xEF -> ARABIC LETTER YEH
+ '\u2261' # 0xF0 -> IDENTICAL TO
+ '\u064b' # 0xF1 -> ARABIC FATHATAN
+ '\u064c' # 0xF2 -> ARABIC DAMMATAN
+ '\u064d' # 0xF3 -> ARABIC KASRATAN
+ '\u064e' # 0xF4 -> ARABIC FATHA
+ '\u064f' # 0xF5 -> ARABIC DAMMA
+ '\u0650' # 0xF6 -> ARABIC KASRA
+ '\u2248' # 0xF7 -> ALMOST EQUAL TO
+ '\xb0' # 0xF8 -> DEGREE SIGN
+ '\u2219' # 0xF9 -> BULLET OPERATOR
+ '\xb7' # 0xFA -> MIDDLE DOT
+ '\u221a' # 0xFB -> SQUARE ROOT
+ '\u207f' # 0xFC -> SUPERSCRIPT LATIN SMALL LETTER N
+ '\xb2' # 0xFD -> SUPERSCRIPT TWO
+ '\u25a0' # 0xFE -> BLACK SQUARE
+ '\xa0' # 0xFF -> NO-BREAK SPACE
+)
+
+### Encoding table
+encoding_table=codecs.charmap_build(decoding_table)
diff --git a/Lib/encodings/cp858.py b/Lib/encodings/cp858.py
new file mode 100644
index 0000000..7579f52
--- /dev/null
+++ b/Lib/encodings/cp858.py
@@ -0,0 +1,698 @@
+""" Python Character Mapping Codec for CP858, modified from cp850.
+
+"""
+
+import codecs
+
+### Codec APIs
+
+class Codec(codecs.Codec):
+
+ def encode(self,input,errors='strict'):
+ return codecs.charmap_encode(input,errors,encoding_map)
+
+ def decode(self,input,errors='strict'):
+ return codecs.charmap_decode(input,errors,decoding_table)
+
+class IncrementalEncoder(codecs.IncrementalEncoder):
+ def encode(self, input, final=False):
+ return codecs.charmap_encode(input,self.errors,encoding_map)[0]
+
+class IncrementalDecoder(codecs.IncrementalDecoder):
+ def decode(self, input, final=False):
+ return codecs.charmap_decode(input,self.errors,decoding_table)[0]
+
+class StreamWriter(Codec,codecs.StreamWriter):
+ pass
+
+class StreamReader(Codec,codecs.StreamReader):
+ pass
+
+### encodings module API
+
+def getregentry():
+ return codecs.CodecInfo(
+ name='cp858',
+ encode=Codec().encode,
+ decode=Codec().decode,
+ incrementalencoder=IncrementalEncoder,
+ incrementaldecoder=IncrementalDecoder,
+ streamreader=StreamReader,
+ streamwriter=StreamWriter,
+ )
+
+### Decoding Map
+
+decoding_map = codecs.make_identity_dict(range(256))
+decoding_map.update({
+ 0x0080: 0x00c7, # LATIN CAPITAL LETTER C WITH CEDILLA
+ 0x0081: 0x00fc, # LATIN SMALL LETTER U WITH DIAERESIS
+ 0x0082: 0x00e9, # LATIN SMALL LETTER E WITH ACUTE
+ 0x0083: 0x00e2, # LATIN SMALL LETTER A WITH CIRCUMFLEX
+ 0x0084: 0x00e4, # LATIN SMALL LETTER A WITH DIAERESIS
+ 0x0085: 0x00e0, # LATIN SMALL LETTER A WITH GRAVE
+ 0x0086: 0x00e5, # LATIN SMALL LETTER A WITH RING ABOVE
+ 0x0087: 0x00e7, # LATIN SMALL LETTER C WITH CEDILLA
+ 0x0088: 0x00ea, # LATIN SMALL LETTER E WITH CIRCUMFLEX
+ 0x0089: 0x00eb, # LATIN SMALL LETTER E WITH DIAERESIS
+ 0x008a: 0x00e8, # LATIN SMALL LETTER E WITH GRAVE
+ 0x008b: 0x00ef, # LATIN SMALL LETTER I WITH DIAERESIS
+ 0x008c: 0x00ee, # LATIN SMALL LETTER I WITH CIRCUMFLEX
+ 0x008d: 0x00ec, # LATIN SMALL LETTER I WITH GRAVE
+ 0x008e: 0x00c4, # LATIN CAPITAL LETTER A WITH DIAERESIS
+ 0x008f: 0x00c5, # LATIN CAPITAL LETTER A WITH RING ABOVE
+ 0x0090: 0x00c9, # LATIN CAPITAL LETTER E WITH ACUTE
+ 0x0091: 0x00e6, # LATIN SMALL LIGATURE AE
+ 0x0092: 0x00c6, # LATIN CAPITAL LIGATURE AE
+ 0x0093: 0x00f4, # LATIN SMALL LETTER O WITH CIRCUMFLEX
+ 0x0094: 0x00f6, # LATIN SMALL LETTER O WITH DIAERESIS
+ 0x0095: 0x00f2, # LATIN SMALL LETTER O WITH GRAVE
+ 0x0096: 0x00fb, # LATIN SMALL LETTER U WITH CIRCUMFLEX
+ 0x0097: 0x00f9, # LATIN SMALL LETTER U WITH GRAVE
+ 0x0098: 0x00ff, # LATIN SMALL LETTER Y WITH DIAERESIS
+ 0x0099: 0x00d6, # LATIN CAPITAL LETTER O WITH DIAERESIS
+ 0x009a: 0x00dc, # LATIN CAPITAL LETTER U WITH DIAERESIS
+ 0x009b: 0x00f8, # LATIN SMALL LETTER O WITH STROKE
+ 0x009c: 0x00a3, # POUND SIGN
+ 0x009d: 0x00d8, # LATIN CAPITAL LETTER O WITH STROKE
+ 0x009e: 0x00d7, # MULTIPLICATION SIGN
+ 0x009f: 0x0192, # LATIN SMALL LETTER F WITH HOOK
+ 0x00a0: 0x00e1, # LATIN SMALL LETTER A WITH ACUTE
+ 0x00a1: 0x00ed, # LATIN SMALL LETTER I WITH ACUTE
+ 0x00a2: 0x00f3, # LATIN SMALL LETTER O WITH ACUTE
+ 0x00a3: 0x00fa, # LATIN SMALL LETTER U WITH ACUTE
+ 0x00a4: 0x00f1, # LATIN SMALL LETTER N WITH TILDE
+ 0x00a5: 0x00d1, # LATIN CAPITAL LETTER N WITH TILDE
+ 0x00a6: 0x00aa, # FEMININE ORDINAL INDICATOR
+ 0x00a7: 0x00ba, # MASCULINE ORDINAL INDICATOR
+ 0x00a8: 0x00bf, # INVERTED QUESTION MARK
+ 0x00a9: 0x00ae, # REGISTERED SIGN
+ 0x00aa: 0x00ac, # NOT SIGN
+ 0x00ab: 0x00bd, # VULGAR FRACTION ONE HALF
+ 0x00ac: 0x00bc, # VULGAR FRACTION ONE QUARTER
+ 0x00ad: 0x00a1, # INVERTED EXCLAMATION MARK
+ 0x00ae: 0x00ab, # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+ 0x00af: 0x00bb, # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+ 0x00b0: 0x2591, # LIGHT SHADE
+ 0x00b1: 0x2592, # MEDIUM SHADE
+ 0x00b2: 0x2593, # DARK SHADE
+ 0x00b3: 0x2502, # BOX DRAWINGS LIGHT VERTICAL
+ 0x00b4: 0x2524, # BOX DRAWINGS LIGHT VERTICAL AND LEFT
+ 0x00b5: 0x00c1, # LATIN CAPITAL LETTER A WITH ACUTE
+ 0x00b6: 0x00c2, # LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+ 0x00b7: 0x00c0, # LATIN CAPITAL LETTER A WITH GRAVE
+ 0x00b8: 0x00a9, # COPYRIGHT SIGN
+ 0x00b9: 0x2563, # BOX DRAWINGS DOUBLE VERTICAL AND LEFT
+ 0x00ba: 0x2551, # BOX DRAWINGS DOUBLE VERTICAL
+ 0x00bb: 0x2557, # BOX DRAWINGS DOUBLE DOWN AND LEFT
+ 0x00bc: 0x255d, # BOX DRAWINGS DOUBLE UP AND LEFT
+ 0x00bd: 0x00a2, # CENT SIGN
+ 0x00be: 0x00a5, # YEN SIGN
+ 0x00bf: 0x2510, # BOX DRAWINGS LIGHT DOWN AND LEFT
+ 0x00c0: 0x2514, # BOX DRAWINGS LIGHT UP AND RIGHT
+ 0x00c1: 0x2534, # BOX DRAWINGS LIGHT UP AND HORIZONTAL
+ 0x00c2: 0x252c, # BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+ 0x00c3: 0x251c, # BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+ 0x00c4: 0x2500, # BOX DRAWINGS LIGHT HORIZONTAL
+ 0x00c5: 0x253c, # BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+ 0x00c6: 0x00e3, # LATIN SMALL LETTER A WITH TILDE
+ 0x00c7: 0x00c3, # LATIN CAPITAL LETTER A WITH TILDE
+ 0x00c8: 0x255a, # BOX DRAWINGS DOUBLE UP AND RIGHT
+ 0x00c9: 0x2554, # BOX DRAWINGS DOUBLE DOWN AND RIGHT
+ 0x00ca: 0x2569, # BOX DRAWINGS DOUBLE UP AND HORIZONTAL
+ 0x00cb: 0x2566, # BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
+ 0x00cc: 0x2560, # BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
+ 0x00cd: 0x2550, # BOX DRAWINGS DOUBLE HORIZONTAL
+ 0x00ce: 0x256c, # BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
+ 0x00cf: 0x00a4, # CURRENCY SIGN
+ 0x00d0: 0x00f0, # LATIN SMALL LETTER ETH
+ 0x00d1: 0x00d0, # LATIN CAPITAL LETTER ETH
+ 0x00d2: 0x00ca, # LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+ 0x00d3: 0x00cb, # LATIN CAPITAL LETTER E WITH DIAERESIS
+ 0x00d4: 0x00c8, # LATIN CAPITAL LETTER E WITH GRAVE
+ 0x00d5: 0x20ac, # EURO SIGN
+ 0x00d6: 0x00cd, # LATIN CAPITAL LETTER I WITH ACUTE
+ 0x00d7: 0x00ce, # LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+ 0x00d8: 0x00cf, # LATIN CAPITAL LETTER I WITH DIAERESIS
+ 0x00d9: 0x2518, # BOX DRAWINGS LIGHT UP AND LEFT
+ 0x00da: 0x250c, # BOX DRAWINGS LIGHT DOWN AND RIGHT
+ 0x00db: 0x2588, # FULL BLOCK
+ 0x00dc: 0x2584, # LOWER HALF BLOCK
+ 0x00dd: 0x00a6, # BROKEN BAR
+ 0x00de: 0x00cc, # LATIN CAPITAL LETTER I WITH GRAVE
+ 0x00df: 0x2580, # UPPER HALF BLOCK
+ 0x00e0: 0x00d3, # LATIN CAPITAL LETTER O WITH ACUTE
+ 0x00e1: 0x00df, # LATIN SMALL LETTER SHARP S
+ 0x00e2: 0x00d4, # LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+ 0x00e3: 0x00d2, # LATIN CAPITAL LETTER O WITH GRAVE
+ 0x00e4: 0x00f5, # LATIN SMALL LETTER O WITH TILDE
+ 0x00e5: 0x00d5, # LATIN CAPITAL LETTER O WITH TILDE
+ 0x00e6: 0x00b5, # MICRO SIGN
+ 0x00e7: 0x00fe, # LATIN SMALL LETTER THORN
+ 0x00e8: 0x00de, # LATIN CAPITAL LETTER THORN
+ 0x00e9: 0x00da, # LATIN CAPITAL LETTER U WITH ACUTE
+ 0x00ea: 0x00db, # LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+ 0x00eb: 0x00d9, # LATIN CAPITAL LETTER U WITH GRAVE
+ 0x00ec: 0x00fd, # LATIN SMALL LETTER Y WITH ACUTE
+ 0x00ed: 0x00dd, # LATIN CAPITAL LETTER Y WITH ACUTE
+ 0x00ee: 0x00af, # MACRON
+ 0x00ef: 0x00b4, # ACUTE ACCENT
+ 0x00f0: 0x00ad, # SOFT HYPHEN
+ 0x00f1: 0x00b1, # PLUS-MINUS SIGN
+ 0x00f2: 0x2017, # DOUBLE LOW LINE
+ 0x00f3: 0x00be, # VULGAR FRACTION THREE QUARTERS
+ 0x00f4: 0x00b6, # PILCROW SIGN
+ 0x00f5: 0x00a7, # SECTION SIGN
+ 0x00f6: 0x00f7, # DIVISION SIGN
+ 0x00f7: 0x00b8, # CEDILLA
+ 0x00f8: 0x00b0, # DEGREE SIGN
+ 0x00f9: 0x00a8, # DIAERESIS
+ 0x00fa: 0x00b7, # MIDDLE DOT
+ 0x00fb: 0x00b9, # SUPERSCRIPT ONE
+ 0x00fc: 0x00b3, # SUPERSCRIPT THREE
+ 0x00fd: 0x00b2, # SUPERSCRIPT TWO
+ 0x00fe: 0x25a0, # BLACK SQUARE
+ 0x00ff: 0x00a0, # NO-BREAK SPACE
+})
+
+### Decoding Table
+
+decoding_table = (
+ '\x00' # 0x0000 -> NULL
+ '\x01' # 0x0001 -> START OF HEADING
+ '\x02' # 0x0002 -> START OF TEXT
+ '\x03' # 0x0003 -> END OF TEXT
+ '\x04' # 0x0004 -> END OF TRANSMISSION
+ '\x05' # 0x0005 -> ENQUIRY
+ '\x06' # 0x0006 -> ACKNOWLEDGE
+ '\x07' # 0x0007 -> BELL
+ '\x08' # 0x0008 -> BACKSPACE
+ '\t' # 0x0009 -> HORIZONTAL TABULATION
+ '\n' # 0x000a -> LINE FEED
+ '\x0b' # 0x000b -> VERTICAL TABULATION
+ '\x0c' # 0x000c -> FORM FEED
+ '\r' # 0x000d -> CARRIAGE RETURN
+ '\x0e' # 0x000e -> SHIFT OUT
+ '\x0f' # 0x000f -> SHIFT IN
+ '\x10' # 0x0010 -> DATA LINK ESCAPE
+ '\x11' # 0x0011 -> DEVICE CONTROL ONE
+ '\x12' # 0x0012 -> DEVICE CONTROL TWO
+ '\x13' # 0x0013 -> DEVICE CONTROL THREE
+ '\x14' # 0x0014 -> DEVICE CONTROL FOUR
+ '\x15' # 0x0015 -> NEGATIVE ACKNOWLEDGE
+ '\x16' # 0x0016 -> SYNCHRONOUS IDLE
+ '\x17' # 0x0017 -> END OF TRANSMISSION BLOCK
+ '\x18' # 0x0018 -> CANCEL
+ '\x19' # 0x0019 -> END OF MEDIUM
+ '\x1a' # 0x001a -> SUBSTITUTE
+ '\x1b' # 0x001b -> ESCAPE
+ '\x1c' # 0x001c -> FILE SEPARATOR
+ '\x1d' # 0x001d -> GROUP SEPARATOR
+ '\x1e' # 0x001e -> RECORD SEPARATOR
+ '\x1f' # 0x001f -> UNIT SEPARATOR
+ ' ' # 0x0020 -> SPACE
+ '!' # 0x0021 -> EXCLAMATION MARK
+ '"' # 0x0022 -> QUOTATION MARK
+ '#' # 0x0023 -> NUMBER SIGN
+ '$' # 0x0024 -> DOLLAR SIGN
+ '%' # 0x0025 -> PERCENT SIGN
+ '&' # 0x0026 -> AMPERSAND
+ "'" # 0x0027 -> APOSTROPHE
+ '(' # 0x0028 -> LEFT PARENTHESIS
+ ')' # 0x0029 -> RIGHT PARENTHESIS
+ '*' # 0x002a -> ASTERISK
+ '+' # 0x002b -> PLUS SIGN
+ ',' # 0x002c -> COMMA
+ '-' # 0x002d -> HYPHEN-MINUS
+ '.' # 0x002e -> FULL STOP
+ '/' # 0x002f -> SOLIDUS
+ '0' # 0x0030 -> DIGIT ZERO
+ '1' # 0x0031 -> DIGIT ONE
+ '2' # 0x0032 -> DIGIT TWO
+ '3' # 0x0033 -> DIGIT THREE
+ '4' # 0x0034 -> DIGIT FOUR
+ '5' # 0x0035 -> DIGIT FIVE
+ '6' # 0x0036 -> DIGIT SIX
+ '7' # 0x0037 -> DIGIT SEVEN
+ '8' # 0x0038 -> DIGIT EIGHT
+ '9' # 0x0039 -> DIGIT NINE
+ ':' # 0x003a -> COLON
+ ';' # 0x003b -> SEMICOLON
+ '<' # 0x003c -> LESS-THAN SIGN
+ '=' # 0x003d -> EQUALS SIGN
+ '>' # 0x003e -> GREATER-THAN SIGN
+ '?' # 0x003f -> QUESTION MARK
+ '@' # 0x0040 -> COMMERCIAL AT
+ 'A' # 0x0041 -> LATIN CAPITAL LETTER A
+ 'B' # 0x0042 -> LATIN CAPITAL LETTER B
+ 'C' # 0x0043 -> LATIN CAPITAL LETTER C
+ 'D' # 0x0044 -> LATIN CAPITAL LETTER D
+ 'E' # 0x0045 -> LATIN CAPITAL LETTER E
+ 'F' # 0x0046 -> LATIN CAPITAL LETTER F
+ 'G' # 0x0047 -> LATIN CAPITAL LETTER G
+ 'H' # 0x0048 -> LATIN CAPITAL LETTER H
+ 'I' # 0x0049 -> LATIN CAPITAL LETTER I
+ 'J' # 0x004a -> LATIN CAPITAL LETTER J
+ 'K' # 0x004b -> LATIN CAPITAL LETTER K
+ 'L' # 0x004c -> LATIN CAPITAL LETTER L
+ 'M' # 0x004d -> LATIN CAPITAL LETTER M
+ 'N' # 0x004e -> LATIN CAPITAL LETTER N
+ 'O' # 0x004f -> LATIN CAPITAL LETTER O
+ 'P' # 0x0050 -> LATIN CAPITAL LETTER P
+ 'Q' # 0x0051 -> LATIN CAPITAL LETTER Q
+ 'R' # 0x0052 -> LATIN CAPITAL LETTER R
+ 'S' # 0x0053 -> LATIN CAPITAL LETTER S
+ 'T' # 0x0054 -> LATIN CAPITAL LETTER T
+ 'U' # 0x0055 -> LATIN CAPITAL LETTER U
+ 'V' # 0x0056 -> LATIN CAPITAL LETTER V
+ 'W' # 0x0057 -> LATIN CAPITAL LETTER W
+ 'X' # 0x0058 -> LATIN CAPITAL LETTER X
+ 'Y' # 0x0059 -> LATIN CAPITAL LETTER Y
+ 'Z' # 0x005a -> LATIN CAPITAL LETTER Z
+ '[' # 0x005b -> LEFT SQUARE BRACKET
+ '\\' # 0x005c -> REVERSE SOLIDUS
+ ']' # 0x005d -> RIGHT SQUARE BRACKET
+ '^' # 0x005e -> CIRCUMFLEX ACCENT
+ '_' # 0x005f -> LOW LINE
+ '`' # 0x0060 -> GRAVE ACCENT
+ 'a' # 0x0061 -> LATIN SMALL LETTER A
+ 'b' # 0x0062 -> LATIN SMALL LETTER B
+ 'c' # 0x0063 -> LATIN SMALL LETTER C
+ 'd' # 0x0064 -> LATIN SMALL LETTER D
+ 'e' # 0x0065 -> LATIN SMALL LETTER E
+ 'f' # 0x0066 -> LATIN SMALL LETTER F
+ 'g' # 0x0067 -> LATIN SMALL LETTER G
+ 'h' # 0x0068 -> LATIN SMALL LETTER H
+ 'i' # 0x0069 -> LATIN SMALL LETTER I
+ 'j' # 0x006a -> LATIN SMALL LETTER J
+ 'k' # 0x006b -> LATIN SMALL LETTER K
+ 'l' # 0x006c -> LATIN SMALL LETTER L
+ 'm' # 0x006d -> LATIN SMALL LETTER M
+ 'n' # 0x006e -> LATIN SMALL LETTER N
+ 'o' # 0x006f -> LATIN SMALL LETTER O
+ 'p' # 0x0070 -> LATIN SMALL LETTER P
+ 'q' # 0x0071 -> LATIN SMALL LETTER Q
+ 'r' # 0x0072 -> LATIN SMALL LETTER R
+ 's' # 0x0073 -> LATIN SMALL LETTER S
+ 't' # 0x0074 -> LATIN SMALL LETTER T
+ 'u' # 0x0075 -> LATIN SMALL LETTER U
+ 'v' # 0x0076 -> LATIN SMALL LETTER V
+ 'w' # 0x0077 -> LATIN SMALL LETTER W
+ 'x' # 0x0078 -> LATIN SMALL LETTER X
+ 'y' # 0x0079 -> LATIN SMALL LETTER Y
+ 'z' # 0x007a -> LATIN SMALL LETTER Z
+ '{' # 0x007b -> LEFT CURLY BRACKET
+ '|' # 0x007c -> VERTICAL LINE
+ '}' # 0x007d -> RIGHT CURLY BRACKET
+ '~' # 0x007e -> TILDE
+ '\x7f' # 0x007f -> DELETE
+ '\xc7' # 0x0080 -> LATIN CAPITAL LETTER C WITH CEDILLA
+ '\xfc' # 0x0081 -> LATIN SMALL LETTER U WITH DIAERESIS
+ '\xe9' # 0x0082 -> LATIN SMALL LETTER E WITH ACUTE
+ '\xe2' # 0x0083 -> LATIN SMALL LETTER A WITH CIRCUMFLEX
+ '\xe4' # 0x0084 -> LATIN SMALL LETTER A WITH DIAERESIS
+ '\xe0' # 0x0085 -> LATIN SMALL LETTER A WITH GRAVE
+ '\xe5' # 0x0086 -> LATIN SMALL LETTER A WITH RING ABOVE
+ '\xe7' # 0x0087 -> LATIN SMALL LETTER C WITH CEDILLA
+ '\xea' # 0x0088 -> LATIN SMALL LETTER E WITH CIRCUMFLEX
+ '\xeb' # 0x0089 -> LATIN SMALL LETTER E WITH DIAERESIS
+ '\xe8' # 0x008a -> LATIN SMALL LETTER E WITH GRAVE
+ '\xef' # 0x008b -> LATIN SMALL LETTER I WITH DIAERESIS
+ '\xee' # 0x008c -> LATIN SMALL LETTER I WITH CIRCUMFLEX
+ '\xec' # 0x008d -> LATIN SMALL LETTER I WITH GRAVE
+ '\xc4' # 0x008e -> LATIN CAPITAL LETTER A WITH DIAERESIS
+ '\xc5' # 0x008f -> LATIN CAPITAL LETTER A WITH RING ABOVE
+ '\xc9' # 0x0090 -> LATIN CAPITAL LETTER E WITH ACUTE
+ '\xe6' # 0x0091 -> LATIN SMALL LIGATURE AE
+ '\xc6' # 0x0092 -> LATIN CAPITAL LIGATURE AE
+ '\xf4' # 0x0093 -> LATIN SMALL LETTER O WITH CIRCUMFLEX
+ '\xf6' # 0x0094 -> LATIN SMALL LETTER O WITH DIAERESIS
+ '\xf2' # 0x0095 -> LATIN SMALL LETTER O WITH GRAVE
+ '\xfb' # 0x0096 -> LATIN SMALL LETTER U WITH CIRCUMFLEX
+ '\xf9' # 0x0097 -> LATIN SMALL LETTER U WITH GRAVE
+ '\xff' # 0x0098 -> LATIN SMALL LETTER Y WITH DIAERESIS
+ '\xd6' # 0x0099 -> LATIN CAPITAL LETTER O WITH DIAERESIS
+ '\xdc' # 0x009a -> LATIN CAPITAL LETTER U WITH DIAERESIS
+ '\xf8' # 0x009b -> LATIN SMALL LETTER O WITH STROKE
+ '\xa3' # 0x009c -> POUND SIGN
+ '\xd8' # 0x009d -> LATIN CAPITAL LETTER O WITH STROKE
+ '\xd7' # 0x009e -> MULTIPLICATION SIGN
+ '\u0192' # 0x009f -> LATIN SMALL LETTER F WITH HOOK
+ '\xe1' # 0x00a0 -> LATIN SMALL LETTER A WITH ACUTE
+ '\xed' # 0x00a1 -> LATIN SMALL LETTER I WITH ACUTE
+ '\xf3' # 0x00a2 -> LATIN SMALL LETTER O WITH ACUTE
+ '\xfa' # 0x00a3 -> LATIN SMALL LETTER U WITH ACUTE
+ '\xf1' # 0x00a4 -> LATIN SMALL LETTER N WITH TILDE
+ '\xd1' # 0x00a5 -> LATIN CAPITAL LETTER N WITH TILDE
+ '\xaa' # 0x00a6 -> FEMININE ORDINAL INDICATOR
+ '\xba' # 0x00a7 -> MASCULINE ORDINAL INDICATOR
+ '\xbf' # 0x00a8 -> INVERTED QUESTION MARK
+ '\xae' # 0x00a9 -> REGISTERED SIGN
+ '\xac' # 0x00aa -> NOT SIGN
+ '\xbd' # 0x00ab -> VULGAR FRACTION ONE HALF
+ '\xbc' # 0x00ac -> VULGAR FRACTION ONE QUARTER
+ '\xa1' # 0x00ad -> INVERTED EXCLAMATION MARK
+ '\xab' # 0x00ae -> LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+ '\xbb' # 0x00af -> RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+ '\u2591' # 0x00b0 -> LIGHT SHADE
+ '\u2592' # 0x00b1 -> MEDIUM SHADE
+ '\u2593' # 0x00b2 -> DARK SHADE
+ '\u2502' # 0x00b3 -> BOX DRAWINGS LIGHT VERTICAL
+ '\u2524' # 0x00b4 -> BOX DRAWINGS LIGHT VERTICAL AND LEFT
+ '\xc1' # 0x00b5 -> LATIN CAPITAL LETTER A WITH ACUTE
+ '\xc2' # 0x00b6 -> LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+ '\xc0' # 0x00b7 -> LATIN CAPITAL LETTER A WITH GRAVE
+ '\xa9' # 0x00b8 -> COPYRIGHT SIGN
+ '\u2563' # 0x00b9 -> BOX DRAWINGS DOUBLE VERTICAL AND LEFT
+ '\u2551' # 0x00ba -> BOX DRAWINGS DOUBLE VERTICAL
+ '\u2557' # 0x00bb -> BOX DRAWINGS DOUBLE DOWN AND LEFT
+ '\u255d' # 0x00bc -> BOX DRAWINGS DOUBLE UP AND LEFT
+ '\xa2' # 0x00bd -> CENT SIGN
+ '\xa5' # 0x00be -> YEN SIGN
+ '\u2510' # 0x00bf -> BOX DRAWINGS LIGHT DOWN AND LEFT
+ '\u2514' # 0x00c0 -> BOX DRAWINGS LIGHT UP AND RIGHT
+ '\u2534' # 0x00c1 -> BOX DRAWINGS LIGHT UP AND HORIZONTAL
+ '\u252c' # 0x00c2 -> BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+ '\u251c' # 0x00c3 -> BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+ '\u2500' # 0x00c4 -> BOX DRAWINGS LIGHT HORIZONTAL
+ '\u253c' # 0x00c5 -> BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+ '\xe3' # 0x00c6 -> LATIN SMALL LETTER A WITH TILDE
+ '\xc3' # 0x00c7 -> LATIN CAPITAL LETTER A WITH TILDE
+ '\u255a' # 0x00c8 -> BOX DRAWINGS DOUBLE UP AND RIGHT
+ '\u2554' # 0x00c9 -> BOX DRAWINGS DOUBLE DOWN AND RIGHT
+ '\u2569' # 0x00ca -> BOX DRAWINGS DOUBLE UP AND HORIZONTAL
+ '\u2566' # 0x00cb -> BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
+ '\u2560' # 0x00cc -> BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
+ '\u2550' # 0x00cd -> BOX DRAWINGS DOUBLE HORIZONTAL
+ '\u256c' # 0x00ce -> BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
+ '\xa4' # 0x00cf -> CURRENCY SIGN
+ '\xf0' # 0x00d0 -> LATIN SMALL LETTER ETH
+ '\xd0' # 0x00d1 -> LATIN CAPITAL LETTER ETH
+ '\xca' # 0x00d2 -> LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+ '\xcb' # 0x00d3 -> LATIN CAPITAL LETTER E WITH DIAERESIS
+ '\xc8' # 0x00d4 -> LATIN CAPITAL LETTER E WITH GRAVE
+ '\u20ac' # 0x00d5 -> EURO SIGN
+ '\xcd' # 0x00d6 -> LATIN CAPITAL LETTER I WITH ACUTE
+ '\xce' # 0x00d7 -> LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+ '\xcf' # 0x00d8 -> LATIN CAPITAL LETTER I WITH DIAERESIS
+ '\u2518' # 0x00d9 -> BOX DRAWINGS LIGHT UP AND LEFT
+ '\u250c' # 0x00da -> BOX DRAWINGS LIGHT DOWN AND RIGHT
+ '\u2588' # 0x00db -> FULL BLOCK
+ '\u2584' # 0x00dc -> LOWER HALF BLOCK
+ '\xa6' # 0x00dd -> BROKEN BAR
+ '\xcc' # 0x00de -> LATIN CAPITAL LETTER I WITH GRAVE
+ '\u2580' # 0x00df -> UPPER HALF BLOCK
+ '\xd3' # 0x00e0 -> LATIN CAPITAL LETTER O WITH ACUTE
+ '\xdf' # 0x00e1 -> LATIN SMALL LETTER SHARP S
+ '\xd4' # 0x00e2 -> LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+ '\xd2' # 0x00e3 -> LATIN CAPITAL LETTER O WITH GRAVE
+ '\xf5' # 0x00e4 -> LATIN SMALL LETTER O WITH TILDE
+ '\xd5' # 0x00e5 -> LATIN CAPITAL LETTER O WITH TILDE
+ '\xb5' # 0x00e6 -> MICRO SIGN
+ '\xfe' # 0x00e7 -> LATIN SMALL LETTER THORN
+ '\xde' # 0x00e8 -> LATIN CAPITAL LETTER THORN
+ '\xda' # 0x00e9 -> LATIN CAPITAL LETTER U WITH ACUTE
+ '\xdb' # 0x00ea -> LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+ '\xd9' # 0x00eb -> LATIN CAPITAL LETTER U WITH GRAVE
+ '\xfd' # 0x00ec -> LATIN SMALL LETTER Y WITH ACUTE
+ '\xdd' # 0x00ed -> LATIN CAPITAL LETTER Y WITH ACUTE
+ '\xaf' # 0x00ee -> MACRON
+ '\xb4' # 0x00ef -> ACUTE ACCENT
+ '\xad' # 0x00f0 -> SOFT HYPHEN
+ '\xb1' # 0x00f1 -> PLUS-MINUS SIGN
+ '\u2017' # 0x00f2 -> DOUBLE LOW LINE
+ '\xbe' # 0x00f3 -> VULGAR FRACTION THREE QUARTERS
+ '\xb6' # 0x00f4 -> PILCROW SIGN
+ '\xa7' # 0x00f5 -> SECTION SIGN
+ '\xf7' # 0x00f6 -> DIVISION SIGN
+ '\xb8' # 0x00f7 -> CEDILLA
+ '\xb0' # 0x00f8 -> DEGREE SIGN
+ '\xa8' # 0x00f9 -> DIAERESIS
+ '\xb7' # 0x00fa -> MIDDLE DOT
+ '\xb9' # 0x00fb -> SUPERSCRIPT ONE
+ '\xb3' # 0x00fc -> SUPERSCRIPT THREE
+ '\xb2' # 0x00fd -> SUPERSCRIPT TWO
+ '\u25a0' # 0x00fe -> BLACK SQUARE
+ '\xa0' # 0x00ff -> NO-BREAK SPACE
+)
+
+### Encoding Map
+
+encoding_map = {
+ 0x0000: 0x0000, # NULL
+ 0x0001: 0x0001, # START OF HEADING
+ 0x0002: 0x0002, # START OF TEXT
+ 0x0003: 0x0003, # END OF TEXT
+ 0x0004: 0x0004, # END OF TRANSMISSION
+ 0x0005: 0x0005, # ENQUIRY
+ 0x0006: 0x0006, # ACKNOWLEDGE
+ 0x0007: 0x0007, # BELL
+ 0x0008: 0x0008, # BACKSPACE
+ 0x0009: 0x0009, # HORIZONTAL TABULATION
+ 0x000a: 0x000a, # LINE FEED
+ 0x000b: 0x000b, # VERTICAL TABULATION
+ 0x000c: 0x000c, # FORM FEED
+ 0x000d: 0x000d, # CARRIAGE RETURN
+ 0x000e: 0x000e, # SHIFT OUT
+ 0x000f: 0x000f, # SHIFT IN
+ 0x0010: 0x0010, # DATA LINK ESCAPE
+ 0x0011: 0x0011, # DEVICE CONTROL ONE
+ 0x0012: 0x0012, # DEVICE CONTROL TWO
+ 0x0013: 0x0013, # DEVICE CONTROL THREE
+ 0x0014: 0x0014, # DEVICE CONTROL FOUR
+ 0x0015: 0x0015, # NEGATIVE ACKNOWLEDGE
+ 0x0016: 0x0016, # SYNCHRONOUS IDLE
+ 0x0017: 0x0017, # END OF TRANSMISSION BLOCK
+ 0x0018: 0x0018, # CANCEL
+ 0x0019: 0x0019, # END OF MEDIUM
+ 0x001a: 0x001a, # SUBSTITUTE
+ 0x001b: 0x001b, # ESCAPE
+ 0x001c: 0x001c, # FILE SEPARATOR
+ 0x001d: 0x001d, # GROUP SEPARATOR
+ 0x001e: 0x001e, # RECORD SEPARATOR
+ 0x001f: 0x001f, # UNIT SEPARATOR
+ 0x0020: 0x0020, # SPACE
+ 0x0021: 0x0021, # EXCLAMATION MARK
+ 0x0022: 0x0022, # QUOTATION MARK
+ 0x0023: 0x0023, # NUMBER SIGN
+ 0x0024: 0x0024, # DOLLAR SIGN
+ 0x0025: 0x0025, # PERCENT SIGN
+ 0x0026: 0x0026, # AMPERSAND
+ 0x0027: 0x0027, # APOSTROPHE
+ 0x0028: 0x0028, # LEFT PARENTHESIS
+ 0x0029: 0x0029, # RIGHT PARENTHESIS
+ 0x002a: 0x002a, # ASTERISK
+ 0x002b: 0x002b, # PLUS SIGN
+ 0x002c: 0x002c, # COMMA
+ 0x002d: 0x002d, # HYPHEN-MINUS
+ 0x002e: 0x002e, # FULL STOP
+ 0x002f: 0x002f, # SOLIDUS
+ 0x0030: 0x0030, # DIGIT ZERO
+ 0x0031: 0x0031, # DIGIT ONE
+ 0x0032: 0x0032, # DIGIT TWO
+ 0x0033: 0x0033, # DIGIT THREE
+ 0x0034: 0x0034, # DIGIT FOUR
+ 0x0035: 0x0035, # DIGIT FIVE
+ 0x0036: 0x0036, # DIGIT SIX
+ 0x0037: 0x0037, # DIGIT SEVEN
+ 0x0038: 0x0038, # DIGIT EIGHT
+ 0x0039: 0x0039, # DIGIT NINE
+ 0x003a: 0x003a, # COLON
+ 0x003b: 0x003b, # SEMICOLON
+ 0x003c: 0x003c, # LESS-THAN SIGN
+ 0x003d: 0x003d, # EQUALS SIGN
+ 0x003e: 0x003e, # GREATER-THAN SIGN
+ 0x003f: 0x003f, # QUESTION MARK
+ 0x0040: 0x0040, # COMMERCIAL AT
+ 0x0041: 0x0041, # LATIN CAPITAL LETTER A
+ 0x0042: 0x0042, # LATIN CAPITAL LETTER B
+ 0x0043: 0x0043, # LATIN CAPITAL LETTER C
+ 0x0044: 0x0044, # LATIN CAPITAL LETTER D
+ 0x0045: 0x0045, # LATIN CAPITAL LETTER E
+ 0x0046: 0x0046, # LATIN CAPITAL LETTER F
+ 0x0047: 0x0047, # LATIN CAPITAL LETTER G
+ 0x0048: 0x0048, # LATIN CAPITAL LETTER H
+ 0x0049: 0x0049, # LATIN CAPITAL LETTER I
+ 0x004a: 0x004a, # LATIN CAPITAL LETTER J
+ 0x004b: 0x004b, # LATIN CAPITAL LETTER K
+ 0x004c: 0x004c, # LATIN CAPITAL LETTER L
+ 0x004d: 0x004d, # LATIN CAPITAL LETTER M
+ 0x004e: 0x004e, # LATIN CAPITAL LETTER N
+ 0x004f: 0x004f, # LATIN CAPITAL LETTER O
+ 0x0050: 0x0050, # LATIN CAPITAL LETTER P
+ 0x0051: 0x0051, # LATIN CAPITAL LETTER Q
+ 0x0052: 0x0052, # LATIN CAPITAL LETTER R
+ 0x0053: 0x0053, # LATIN CAPITAL LETTER S
+ 0x0054: 0x0054, # LATIN CAPITAL LETTER T
+ 0x0055: 0x0055, # LATIN CAPITAL LETTER U
+ 0x0056: 0x0056, # LATIN CAPITAL LETTER V
+ 0x0057: 0x0057, # LATIN CAPITAL LETTER W
+ 0x0058: 0x0058, # LATIN CAPITAL LETTER X
+ 0x0059: 0x0059, # LATIN CAPITAL LETTER Y
+ 0x005a: 0x005a, # LATIN CAPITAL LETTER Z
+ 0x005b: 0x005b, # LEFT SQUARE BRACKET
+ 0x005c: 0x005c, # REVERSE SOLIDUS
+ 0x005d: 0x005d, # RIGHT SQUARE BRACKET
+ 0x005e: 0x005e, # CIRCUMFLEX ACCENT
+ 0x005f: 0x005f, # LOW LINE
+ 0x0060: 0x0060, # GRAVE ACCENT
+ 0x0061: 0x0061, # LATIN SMALL LETTER A
+ 0x0062: 0x0062, # LATIN SMALL LETTER B
+ 0x0063: 0x0063, # LATIN SMALL LETTER C
+ 0x0064: 0x0064, # LATIN SMALL LETTER D
+ 0x0065: 0x0065, # LATIN SMALL LETTER E
+ 0x0066: 0x0066, # LATIN SMALL LETTER F
+ 0x0067: 0x0067, # LATIN SMALL LETTER G
+ 0x0068: 0x0068, # LATIN SMALL LETTER H
+ 0x0069: 0x0069, # LATIN SMALL LETTER I
+ 0x006a: 0x006a, # LATIN SMALL LETTER J
+ 0x006b: 0x006b, # LATIN SMALL LETTER K
+ 0x006c: 0x006c, # LATIN SMALL LETTER L
+ 0x006d: 0x006d, # LATIN SMALL LETTER M
+ 0x006e: 0x006e, # LATIN SMALL LETTER N
+ 0x006f: 0x006f, # LATIN SMALL LETTER O
+ 0x0070: 0x0070, # LATIN SMALL LETTER P
+ 0x0071: 0x0071, # LATIN SMALL LETTER Q
+ 0x0072: 0x0072, # LATIN SMALL LETTER R
+ 0x0073: 0x0073, # LATIN SMALL LETTER S
+ 0x0074: 0x0074, # LATIN SMALL LETTER T
+ 0x0075: 0x0075, # LATIN SMALL LETTER U
+ 0x0076: 0x0076, # LATIN SMALL LETTER V
+ 0x0077: 0x0077, # LATIN SMALL LETTER W
+ 0x0078: 0x0078, # LATIN SMALL LETTER X
+ 0x0079: 0x0079, # LATIN SMALL LETTER Y
+ 0x007a: 0x007a, # LATIN SMALL LETTER Z
+ 0x007b: 0x007b, # LEFT CURLY BRACKET
+ 0x007c: 0x007c, # VERTICAL LINE
+ 0x007d: 0x007d, # RIGHT CURLY BRACKET
+ 0x007e: 0x007e, # TILDE
+ 0x007f: 0x007f, # DELETE
+ 0x00a0: 0x00ff, # NO-BREAK SPACE
+ 0x00a1: 0x00ad, # INVERTED EXCLAMATION MARK
+ 0x00a2: 0x00bd, # CENT SIGN
+ 0x00a3: 0x009c, # POUND SIGN
+ 0x00a4: 0x00cf, # CURRENCY SIGN
+ 0x00a5: 0x00be, # YEN SIGN
+ 0x00a6: 0x00dd, # BROKEN BAR
+ 0x00a7: 0x00f5, # SECTION SIGN
+ 0x00a8: 0x00f9, # DIAERESIS
+ 0x00a9: 0x00b8, # COPYRIGHT SIGN
+ 0x00aa: 0x00a6, # FEMININE ORDINAL INDICATOR
+ 0x00ab: 0x00ae, # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+ 0x00ac: 0x00aa, # NOT SIGN
+ 0x00ad: 0x00f0, # SOFT HYPHEN
+ 0x00ae: 0x00a9, # REGISTERED SIGN
+ 0x00af: 0x00ee, # MACRON
+ 0x00b0: 0x00f8, # DEGREE SIGN
+ 0x00b1: 0x00f1, # PLUS-MINUS SIGN
+ 0x00b2: 0x00fd, # SUPERSCRIPT TWO
+ 0x00b3: 0x00fc, # SUPERSCRIPT THREE
+ 0x00b4: 0x00ef, # ACUTE ACCENT
+ 0x00b5: 0x00e6, # MICRO SIGN
+ 0x00b6: 0x00f4, # PILCROW SIGN
+ 0x00b7: 0x00fa, # MIDDLE DOT
+ 0x00b8: 0x00f7, # CEDILLA
+ 0x00b9: 0x00fb, # SUPERSCRIPT ONE
+ 0x00ba: 0x00a7, # MASCULINE ORDINAL INDICATOR
+ 0x00bb: 0x00af, # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+ 0x00bc: 0x00ac, # VULGAR FRACTION ONE QUARTER
+ 0x00bd: 0x00ab, # VULGAR FRACTION ONE HALF
+ 0x00be: 0x00f3, # VULGAR FRACTION THREE QUARTERS
+ 0x00bf: 0x00a8, # INVERTED QUESTION MARK
+ 0x00c0: 0x00b7, # LATIN CAPITAL LETTER A WITH GRAVE
+ 0x00c1: 0x00b5, # LATIN CAPITAL LETTER A WITH ACUTE
+ 0x00c2: 0x00b6, # LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+ 0x00c3: 0x00c7, # LATIN CAPITAL LETTER A WITH TILDE
+ 0x00c4: 0x008e, # LATIN CAPITAL LETTER A WITH DIAERESIS
+ 0x00c5: 0x008f, # LATIN CAPITAL LETTER A WITH RING ABOVE
+ 0x00c6: 0x0092, # LATIN CAPITAL LIGATURE AE
+ 0x00c7: 0x0080, # LATIN CAPITAL LETTER C WITH CEDILLA
+ 0x00c8: 0x00d4, # LATIN CAPITAL LETTER E WITH GRAVE
+ 0x00c9: 0x0090, # LATIN CAPITAL LETTER E WITH ACUTE
+ 0x00ca: 0x00d2, # LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+ 0x00cb: 0x00d3, # LATIN CAPITAL LETTER E WITH DIAERESIS
+ 0x00cc: 0x00de, # LATIN CAPITAL LETTER I WITH GRAVE
+ 0x00cd: 0x00d6, # LATIN CAPITAL LETTER I WITH ACUTE
+ 0x00ce: 0x00d7, # LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+ 0x00cf: 0x00d8, # LATIN CAPITAL LETTER I WITH DIAERESIS
+ 0x00d0: 0x00d1, # LATIN CAPITAL LETTER ETH
+ 0x00d1: 0x00a5, # LATIN CAPITAL LETTER N WITH TILDE
+ 0x00d2: 0x00e3, # LATIN CAPITAL LETTER O WITH GRAVE
+ 0x00d3: 0x00e0, # LATIN CAPITAL LETTER O WITH ACUTE
+ 0x00d4: 0x00e2, # LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+ 0x00d5: 0x00e5, # LATIN CAPITAL LETTER O WITH TILDE
+ 0x00d6: 0x0099, # LATIN CAPITAL LETTER O WITH DIAERESIS
+ 0x00d7: 0x009e, # MULTIPLICATION SIGN
+ 0x00d8: 0x009d, # LATIN CAPITAL LETTER O WITH STROKE
+ 0x00d9: 0x00eb, # LATIN CAPITAL LETTER U WITH GRAVE
+ 0x00da: 0x00e9, # LATIN CAPITAL LETTER U WITH ACUTE
+ 0x00db: 0x00ea, # LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+ 0x00dc: 0x009a, # LATIN CAPITAL LETTER U WITH DIAERESIS
+ 0x00dd: 0x00ed, # LATIN CAPITAL LETTER Y WITH ACUTE
+ 0x00de: 0x00e8, # LATIN CAPITAL LETTER THORN
+ 0x00df: 0x00e1, # LATIN SMALL LETTER SHARP S
+ 0x00e0: 0x0085, # LATIN SMALL LETTER A WITH GRAVE
+ 0x00e1: 0x00a0, # LATIN SMALL LETTER A WITH ACUTE
+ 0x00e2: 0x0083, # LATIN SMALL LETTER A WITH CIRCUMFLEX
+ 0x00e3: 0x00c6, # LATIN SMALL LETTER A WITH TILDE
+ 0x00e4: 0x0084, # LATIN SMALL LETTER A WITH DIAERESIS
+ 0x00e5: 0x0086, # LATIN SMALL LETTER A WITH RING ABOVE
+ 0x00e6: 0x0091, # LATIN SMALL LIGATURE AE
+ 0x00e7: 0x0087, # LATIN SMALL LETTER C WITH CEDILLA
+ 0x00e8: 0x008a, # LATIN SMALL LETTER E WITH GRAVE
+ 0x00e9: 0x0082, # LATIN SMALL LETTER E WITH ACUTE
+ 0x00ea: 0x0088, # LATIN SMALL LETTER E WITH CIRCUMFLEX
+ 0x00eb: 0x0089, # LATIN SMALL LETTER E WITH DIAERESIS
+ 0x00ec: 0x008d, # LATIN SMALL LETTER I WITH GRAVE
+ 0x00ed: 0x00a1, # LATIN SMALL LETTER I WITH ACUTE
+ 0x00ee: 0x008c, # LATIN SMALL LETTER I WITH CIRCUMFLEX
+ 0x00ef: 0x008b, # LATIN SMALL LETTER I WITH DIAERESIS
+ 0x00f0: 0x00d0, # LATIN SMALL LETTER ETH
+ 0x00f1: 0x00a4, # LATIN SMALL LETTER N WITH TILDE
+ 0x00f2: 0x0095, # LATIN SMALL LETTER O WITH GRAVE
+ 0x00f3: 0x00a2, # LATIN SMALL LETTER O WITH ACUTE
+ 0x00f4: 0x0093, # LATIN SMALL LETTER O WITH CIRCUMFLEX
+ 0x00f5: 0x00e4, # LATIN SMALL LETTER O WITH TILDE
+ 0x00f6: 0x0094, # LATIN SMALL LETTER O WITH DIAERESIS
+ 0x00f7: 0x00f6, # DIVISION SIGN
+ 0x00f8: 0x009b, # LATIN SMALL LETTER O WITH STROKE
+ 0x00f9: 0x0097, # LATIN SMALL LETTER U WITH GRAVE
+ 0x00fa: 0x00a3, # LATIN SMALL LETTER U WITH ACUTE
+ 0x00fb: 0x0096, # LATIN SMALL LETTER U WITH CIRCUMFLEX
+ 0x00fc: 0x0081, # LATIN SMALL LETTER U WITH DIAERESIS
+ 0x00fd: 0x00ec, # LATIN SMALL LETTER Y WITH ACUTE
+ 0x00fe: 0x00e7, # LATIN SMALL LETTER THORN
+ 0x00ff: 0x0098, # LATIN SMALL LETTER Y WITH DIAERESIS
+ 0x20ac: 0x00d5, # EURO SIGN
+ 0x0192: 0x009f, # LATIN SMALL LETTER F WITH HOOK
+ 0x2017: 0x00f2, # DOUBLE LOW LINE
+ 0x2500: 0x00c4, # BOX DRAWINGS LIGHT HORIZONTAL
+ 0x2502: 0x00b3, # BOX DRAWINGS LIGHT VERTICAL
+ 0x250c: 0x00da, # BOX DRAWINGS LIGHT DOWN AND RIGHT
+ 0x2510: 0x00bf, # BOX DRAWINGS LIGHT DOWN AND LEFT
+ 0x2514: 0x00c0, # BOX DRAWINGS LIGHT UP AND RIGHT
+ 0x2518: 0x00d9, # BOX DRAWINGS LIGHT UP AND LEFT
+ 0x251c: 0x00c3, # BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+ 0x2524: 0x00b4, # BOX DRAWINGS LIGHT VERTICAL AND LEFT
+ 0x252c: 0x00c2, # BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+ 0x2534: 0x00c1, # BOX DRAWINGS LIGHT UP AND HORIZONTAL
+ 0x253c: 0x00c5, # BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+ 0x2550: 0x00cd, # BOX DRAWINGS DOUBLE HORIZONTAL
+ 0x2551: 0x00ba, # BOX DRAWINGS DOUBLE VERTICAL
+ 0x2554: 0x00c9, # BOX DRAWINGS DOUBLE DOWN AND RIGHT
+ 0x2557: 0x00bb, # BOX DRAWINGS DOUBLE DOWN AND LEFT
+ 0x255a: 0x00c8, # BOX DRAWINGS DOUBLE UP AND RIGHT
+ 0x255d: 0x00bc, # BOX DRAWINGS DOUBLE UP AND LEFT
+ 0x2560: 0x00cc, # BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
+ 0x2563: 0x00b9, # BOX DRAWINGS DOUBLE VERTICAL AND LEFT
+ 0x2566: 0x00cb, # BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
+ 0x2569: 0x00ca, # BOX DRAWINGS DOUBLE UP AND HORIZONTAL
+ 0x256c: 0x00ce, # BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
+ 0x2580: 0x00df, # UPPER HALF BLOCK
+ 0x2584: 0x00dc, # LOWER HALF BLOCK
+ 0x2588: 0x00db, # FULL BLOCK
+ 0x2591: 0x00b0, # LIGHT SHADE
+ 0x2592: 0x00b1, # MEDIUM SHADE
+ 0x2593: 0x00b2, # DARK SHADE
+ 0x25a0: 0x00fe, # BLACK SQUARE
+}
diff --git a/Lib/encodings/hex_codec.py b/Lib/encodings/hex_codec.py
new file mode 100644
index 0000000..e003fc3
--- /dev/null
+++ b/Lib/encodings/hex_codec.py
@@ -0,0 +1,55 @@
+"""Python 'hex_codec' Codec - 2-digit hex content transfer encoding.
+
+This codec de/encodes from bytes to bytes and is therefore usable with
+bytes.transform() and bytes.untransform().
+
+Written by Marc-Andre Lemburg (mal@lemburg.com).
+"""
+
+import codecs
+import binascii
+
+### Codec APIs
+
+def hex_encode(input, errors='strict'):
+ assert errors == 'strict'
+ return (binascii.b2a_hex(input), len(input))
+
+def hex_decode(input, errors='strict'):
+ assert errors == 'strict'
+ return (binascii.a2b_hex(input), len(input))
+
+class Codec(codecs.Codec):
+ def encode(self, input, errors='strict'):
+ return hex_encode(input, errors)
+ def decode(self, input, errors='strict'):
+ return hex_decode(input, errors)
+
+class IncrementalEncoder(codecs.IncrementalEncoder):
+ def encode(self, input, final=False):
+ assert self.errors == 'strict'
+ return binascii.b2a_hex(input)
+
+class IncrementalDecoder(codecs.IncrementalDecoder):
+ def decode(self, input, final=False):
+ assert self.errors == 'strict'
+ return binascii.a2b_hex(input)
+
+class StreamWriter(Codec, codecs.StreamWriter):
+ charbuffertype = bytes
+
+class StreamReader(Codec, codecs.StreamReader):
+ charbuffertype = bytes
+
+### encodings module API
+
+def getregentry():
+ return codecs.CodecInfo(
+ name='hex',
+ encode=hex_encode,
+ decode=hex_decode,
+ incrementalencoder=IncrementalEncoder,
+ incrementaldecoder=IncrementalDecoder,
+ streamwriter=StreamWriter,
+ streamreader=StreamReader,
+ )
diff --git a/Lib/encodings/punycode.py b/Lib/encodings/punycode.py
index 8129af2..66c5101 100644
--- a/Lib/encodings/punycode.py
+++ b/Lib/encodings/punycode.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
""" Codec for the Punicode encoding, as specified in RFC 3492
Written by Martin v. Löwis.
diff --git a/Lib/encodings/quopri_codec.py b/Lib/encodings/quopri_codec.py
new file mode 100644
index 0000000..9243fc4
--- /dev/null
+++ b/Lib/encodings/quopri_codec.py
@@ -0,0 +1,56 @@
+"""Codec for quoted-printable encoding.
+
+This codec de/encodes from bytes to bytes and is therefore usable with
+bytes.transform() and bytes.untransform().
+"""
+
+import codecs
+import quopri
+from io import BytesIO
+
+def quopri_encode(input, errors='strict'):
+ assert errors == 'strict'
+ f = BytesIO(input)
+ g = BytesIO()
+ quopri.encode(f, g, 1)
+ return (g.getvalue(), len(input))
+
+def quopri_decode(input, errors='strict'):
+ assert errors == 'strict'
+ f = BytesIO(input)
+ g = BytesIO()
+ quopri.decode(f, g)
+ return (g.getvalue(), len(input))
+
+class Codec(codecs.Codec):
+ def encode(self, input, errors='strict'):
+ return quopri_encode(input, errors)
+ def decode(self, input, errors='strict'):
+ return quopri_decode(input, errors)
+
+class IncrementalEncoder(codecs.IncrementalEncoder):
+ def encode(self, input, final=False):
+ return quopri_encode(input, self.errors)[0]
+
+class IncrementalDecoder(codecs.IncrementalDecoder):
+ def decode(self, input, final=False):
+ return quopri_decode(input, self.errors)[0]
+
+class StreamWriter(Codec, codecs.StreamWriter):
+ charbuffertype = bytes
+
+class StreamReader(Codec, codecs.StreamReader):
+ charbuffertype = bytes
+
+# encodings module API
+
+def getregentry():
+ return codecs.CodecInfo(
+ name='quopri',
+ encode=quopri_encode,
+ decode=quopri_decode,
+ incrementalencoder=IncrementalEncoder,
+ incrementaldecoder=IncrementalDecoder,
+ streamwriter=StreamWriter,
+ streamreader=StreamReader,
+ )
diff --git a/Lib/encodings/rot_13.py b/Lib/encodings/rot_13.py
new file mode 100755
index 0000000..3140c14
--- /dev/null
+++ b/Lib/encodings/rot_13.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python
+""" Python Character Mapping Codec for ROT13.
+
+This codec de/encodes from str to str and is therefore usable with
+str.transform() and str.untransform().
+
+Written by Marc-Andre Lemburg (mal@lemburg.com).
+"""
+
+import codecs
+
+### Codec APIs
+
+class Codec(codecs.Codec):
+ def encode(self, input, errors='strict'):
+ return (input.translate(rot13_map), len(input))
+
+ def decode(self, input, errors='strict'):
+ return (input.translate(rot13_map), len(input))
+
+class IncrementalEncoder(codecs.IncrementalEncoder):
+ def encode(self, input, final=False):
+ return input.translate(rot13_map)
+
+class IncrementalDecoder(codecs.IncrementalDecoder):
+ def decode(self, input, final=False):
+ return input.translate(rot13_map)
+
+class StreamWriter(Codec,codecs.StreamWriter):
+ pass
+
+class StreamReader(Codec,codecs.StreamReader):
+ pass
+
+### encodings module API
+
+def getregentry():
+ return codecs.CodecInfo(
+ name='rot-13',
+ encode=Codec().encode,
+ decode=Codec().decode,
+ incrementalencoder=IncrementalEncoder,
+ incrementaldecoder=IncrementalDecoder,
+ streamwriter=StreamWriter,
+ streamreader=StreamReader,
+ )
+
+### Map
+
+rot13_map = codecs.make_identity_dict(range(256))
+rot13_map.update({
+ 0x0041: 0x004e,
+ 0x0042: 0x004f,
+ 0x0043: 0x0050,
+ 0x0044: 0x0051,
+ 0x0045: 0x0052,
+ 0x0046: 0x0053,
+ 0x0047: 0x0054,
+ 0x0048: 0x0055,
+ 0x0049: 0x0056,
+ 0x004a: 0x0057,
+ 0x004b: 0x0058,
+ 0x004c: 0x0059,
+ 0x004d: 0x005a,
+ 0x004e: 0x0041,
+ 0x004f: 0x0042,
+ 0x0050: 0x0043,
+ 0x0051: 0x0044,
+ 0x0052: 0x0045,
+ 0x0053: 0x0046,
+ 0x0054: 0x0047,
+ 0x0055: 0x0048,
+ 0x0056: 0x0049,
+ 0x0057: 0x004a,
+ 0x0058: 0x004b,
+ 0x0059: 0x004c,
+ 0x005a: 0x004d,
+ 0x0061: 0x006e,
+ 0x0062: 0x006f,
+ 0x0063: 0x0070,
+ 0x0064: 0x0071,
+ 0x0065: 0x0072,
+ 0x0066: 0x0073,
+ 0x0067: 0x0074,
+ 0x0068: 0x0075,
+ 0x0069: 0x0076,
+ 0x006a: 0x0077,
+ 0x006b: 0x0078,
+ 0x006c: 0x0079,
+ 0x006d: 0x007a,
+ 0x006e: 0x0061,
+ 0x006f: 0x0062,
+ 0x0070: 0x0063,
+ 0x0071: 0x0064,
+ 0x0072: 0x0065,
+ 0x0073: 0x0066,
+ 0x0074: 0x0067,
+ 0x0075: 0x0068,
+ 0x0076: 0x0069,
+ 0x0077: 0x006a,
+ 0x0078: 0x006b,
+ 0x0079: 0x006c,
+ 0x007a: 0x006d,
+})
+
+### Filter API
+
+def rot13(infile, outfile):
+ outfile.write(infile.read().encode('rot-13'))
+
+if __name__ == '__main__':
+ import sys
+ rot13(sys.stdin, sys.stdout)
diff --git a/Lib/encodings/uu_codec.py b/Lib/encodings/uu_codec.py
new file mode 100644
index 0000000..69c6f17
--- /dev/null
+++ b/Lib/encodings/uu_codec.py
@@ -0,0 +1,99 @@
+"""Python 'uu_codec' Codec - UU content transfer encoding.
+
+This codec de/encodes from bytes to bytes and is therefore usable with
+bytes.transform() and bytes.untransform().
+
+Written by Marc-Andre Lemburg (mal@lemburg.com). Some details were
+adapted from uu.py which was written by Lance Ellinghouse and
+modified by Jack Jansen and Fredrik Lundh.
+"""
+
+import codecs
+import binascii
+from io import BytesIO
+
+### Codec APIs
+
+def uu_encode(input, errors='strict', filename='<data>', mode=0o666):
+ assert errors == 'strict'
+ infile = BytesIO(input)
+ outfile = BytesIO()
+ read = infile.read
+ write = outfile.write
+
+ # Encode
+ write(('begin %o %s\n' % (mode & 0o777, filename)).encode('ascii'))
+ chunk = read(45)
+ while chunk:
+ write(binascii.b2a_uu(chunk))
+ chunk = read(45)
+ write(b' \nend\n')
+
+ return (outfile.getvalue(), len(input))
+
+def uu_decode(input, errors='strict'):
+ assert errors == 'strict'
+ infile = BytesIO(input)
+ outfile = BytesIO()
+ readline = infile.readline
+ write = outfile.write
+
+ # Find start of encoded data
+ while 1:
+ s = readline()
+ if not s:
+ raise ValueError('Missing "begin" line in input data')
+ if s[:5] == b'begin':
+ break
+
+ # Decode
+ while True:
+ s = readline()
+ if not s or s == b'end\n':
+ break
+ try:
+ data = binascii.a2b_uu(s)
+ except binascii.Error as v:
+ # Workaround for broken uuencoders by /Fredrik Lundh
+ nbytes = (((ord(s[0])-32) & 63) * 4 + 5) / 3
+ data = binascii.a2b_uu(s[:nbytes])
+ #sys.stderr.write("Warning: %s\n" % str(v))
+ write(data)
+ if not s:
+ raise ValueError('Truncated input data')
+
+ return (outfile.getvalue(), len(input))
+
+class Codec(codecs.Codec):
+ def encode(self, input, errors='strict'):
+ return uu_encode(input, errors)
+
+ def decode(self, input, errors='strict'):
+ return uu_decode(input, errors)
+
+class IncrementalEncoder(codecs.IncrementalEncoder):
+ def encode(self, input, final=False):
+ return uu_encode(input, self.errors)[0]
+
+class IncrementalDecoder(codecs.IncrementalDecoder):
+ def decode(self, input, final=False):
+ return uu_decode(input, self.errors)[0]
+
+class StreamWriter(Codec, codecs.StreamWriter):
+ charbuffertype = bytes
+
+class StreamReader(Codec, codecs.StreamReader):
+ charbuffertype = bytes
+
+### encodings module API
+
+def getregentry():
+ return codecs.CodecInfo(
+ name='uu',
+ encode=uu_encode,
+ decode=uu_decode,
+ incrementalencoder=IncrementalEncoder,
+ incrementaldecoder=IncrementalDecoder,
+ streamreader=StreamReader,
+ streamwriter=StreamWriter,
+ )
diff --git a/Lib/encodings/zlib_codec.py b/Lib/encodings/zlib_codec.py
new file mode 100644
index 0000000..e0b9cda
--- /dev/null
+++ b/Lib/encodings/zlib_codec.py
@@ -0,0 +1,77 @@
+"""Python 'zlib_codec' Codec - zlib compression encoding.
+
+This codec de/encodes from bytes to bytes and is therefore usable with
+bytes.transform() and bytes.untransform().
+
+Written by Marc-Andre Lemburg (mal@lemburg.com).
+"""
+
+import codecs
+import zlib # this codec needs the optional zlib module !
+
+### Codec APIs
+
+def zlib_encode(input, errors='strict'):
+ assert errors == 'strict'
+ return (zlib.compress(input), len(input))
+
+def zlib_decode(input, errors='strict'):
+ assert errors == 'strict'
+ return (zlib.decompress(input), len(input))
+
+class Codec(codecs.Codec):
+ def encode(self, input, errors='strict'):
+ return zlib_encode(input, errors)
+ def decode(self, input, errors='strict'):
+ return zlib_decode(input, errors)
+
+class IncrementalEncoder(codecs.IncrementalEncoder):
+ def __init__(self, errors='strict'):
+ assert errors == 'strict'
+ self.errors = errors
+ self.compressobj = zlib.compressobj()
+
+ def encode(self, input, final=False):
+ if final:
+ c = self.compressobj.compress(input)
+ return c + self.compressobj.flush()
+ else:
+ return self.compressobj.compress(input)
+
+ def reset(self):
+ self.compressobj = zlib.compressobj()
+
+class IncrementalDecoder(codecs.IncrementalDecoder):
+ def __init__(self, errors='strict'):
+ assert errors == 'strict'
+ self.errors = errors
+ self.decompressobj = zlib.decompressobj()
+
+ def decode(self, input, final=False):
+ if final:
+ c = self.decompressobj.decompress(input)
+ return c + self.decompressobj.flush()
+ else:
+ return self.decompressobj.decompress(input)
+
+ def reset(self):
+ self.decompressobj = zlib.decompressobj()
+
+class StreamWriter(Codec, codecs.StreamWriter):
+ charbuffertype = bytes
+
+class StreamReader(Codec, codecs.StreamReader):
+ charbuffertype = bytes
+
+### encodings module API
+
+def getregentry():
+ return codecs.CodecInfo(
+ name='zlib',
+ encode=zlib_encode,
+ decode=zlib_decode,
+ incrementalencoder=IncrementalEncoder,
+ incrementaldecoder=IncrementalDecoder,
+ streamreader=StreamReader,
+ streamwriter=StreamWriter,
+ )
diff --git a/Lib/fileinput.py b/Lib/fileinput.py
index 90a600b..a25a021 100644
--- a/Lib/fileinput.py
+++ b/Lib/fileinput.py
@@ -238,6 +238,12 @@ class FileInput:
self.nextfile()
self._files = ()
+ def __enter__(self):
+ return self
+
+ def __exit__(self, type, value, traceback):
+ self.close()
+
def __iter__(self):
return self
diff --git a/Lib/fnmatch.py b/Lib/fnmatch.py
index be1fd1d..726fbe5 100644
--- a/Lib/fnmatch.py
+++ b/Lib/fnmatch.py
@@ -9,20 +9,13 @@ expression. They cache the compiled regular expressions for speed.
The function translate(PATTERN) returns a regular expression
corresponding to PATTERN. (It does not compile it.)
"""
-
+import os
+import posixpath
import re
+import functools
__all__ = ["filter", "fnmatch", "fnmatchcase", "translate"]
-_cache = {} # Maps text patterns to compiled regexen.
-_cacheb = {} # Ditto for bytes patterns.
-_MAXCACHE = 100 # Maximum size of caches
-
-def _purge():
- """Clear the pattern cache"""
- _cache.clear()
- _cacheb.clear()
-
def fnmatch(name, pat):
"""Test whether FILENAME matches PATTERN.
@@ -38,33 +31,25 @@ def fnmatch(name, pat):
if the operating system requires it.
If you don't want this, use fnmatchcase(FILENAME, PATTERN).
"""
-
- import os
name = os.path.normcase(name)
pat = os.path.normcase(pat)
return fnmatchcase(name, pat)
-def _compile_pattern(pat):
- cache = _cacheb if isinstance(pat, bytes) else _cache
- regex = cache.get(pat)
- if regex is None:
- if isinstance(pat, bytes):
- pat_str = str(pat, 'ISO-8859-1')
- res_str = translate(pat_str)
- res = bytes(res_str, 'ISO-8859-1')
- else:
- res = translate(pat)
- if len(cache) >= _MAXCACHE:
- cache.clear()
- cache[pat] = regex = re.compile(res)
- return regex.match
+@functools.lru_cache(maxsize=250)
+def _compile_pattern(pat, is_bytes=False):
+ if is_bytes:
+ pat_str = str(pat, 'ISO-8859-1')
+ res_str = translate(pat_str)
+ res = bytes(res_str, 'ISO-8859-1')
+ else:
+ res = translate(pat)
+ return re.compile(res).match
def filter(names, pat):
- """Return the subset of the list NAMES that match PAT"""
- import os,posixpath
+ """Return the subset of the list NAMES that match PAT."""
result = []
pat = os.path.normcase(pat)
- match = _compile_pattern(pat)
+ match = _compile_pattern(pat, isinstance(pat, bytes))
if os.path is posixpath:
# normcase on posix is NOP. Optimize it away from the loop.
for name in names:
@@ -82,10 +67,10 @@ def fnmatchcase(name, pat):
This is a version of fnmatch() which doesn't case-normalize
its arguments.
"""
-
- match = _compile_pattern(pat)
+ match = _compile_pattern(pat, isinstance(pat, bytes))
return match(name) is not None
+
def translate(pat):
"""Translate a shell PATTERN to a regular expression.
diff --git a/Lib/fractions.py b/Lib/fractions.py
index 27b27f4..8be52d2 100644
--- a/Lib/fractions.py
+++ b/Lib/fractions.py
@@ -3,10 +3,12 @@
"""Fraction, infinite-precision, real numbers."""
+from decimal import Decimal
import math
import numbers
import operator
import re
+import sys
__all__ = ['Fraction', 'gcd']
@@ -22,6 +24,12 @@ def gcd(a, b):
a, b = b, a%b
return a
+# Constants related to the hash implementation; hash(x) is based
+# on the reduction of x modulo the prime _PyHASH_MODULUS.
+_PyHASH_MODULUS = sys.hash_info.modulus
+# Value to be used for rationals that reduce to infinity modulo
+# _PyHASH_MODULUS.
+_PyHASH_INF = sys.hash_info.inf
_RATIONAL_FORMAT = re.compile(r"""
\A\s* # optional whitespace at the start, then
@@ -41,13 +49,21 @@ _RATIONAL_FORMAT = re.compile(r"""
class Fraction(numbers.Rational):
"""This class implements rational numbers.
- Fraction(8, 6) will produce a rational number equivalent to
- 4/3. Both arguments must be Integral. The numerator defaults to 0
- and the denominator defaults to 1 so that Fraction(3) == 3 and
- Fraction() == 0.
+ In the two-argument form of the constructor, Fraction(8, 6) will
+ produce a rational number equivalent to 4/3. Both arguments must
+ be Rational. The numerator defaults to 0 and the denominator
+ defaults to 1 so that Fraction(3) == 3 and Fraction() == 0.
- Fraction can also be constructed from strings of the form
- '[-+]?[0-9]+((/|.)[0-9]+)?', optionally surrounded by spaces.
+ Fractions can also be constructed from:
+
+ - numeric strings similar to those accepted by the
+ float constructor (for example, '-2.3' or '1e10')
+
+ - strings of the form '123/456'
+
+ - float and Decimal instances
+
+ - other Rational instances (including integers)
"""
@@ -57,8 +73,32 @@ class Fraction(numbers.Rational):
def __new__(cls, numerator=0, denominator=None):
"""Constructs a Rational.
- Takes a string like '3/2' or '1.5', another Rational, or a
- numerator/denominator pair.
+ Takes a string like '3/2' or '1.5', another Rational instance, a
+ numerator/denominator pair, or a float.
+
+ Examples
+ --------
+
+ >>> Fraction(10, -8)
+ Fraction(-5, 4)
+ >>> Fraction(Fraction(1, 7), 5)
+ Fraction(1, 35)
+ >>> Fraction(Fraction(1, 7), Fraction(2, 3))
+ Fraction(3, 14)
+ >>> Fraction('314')
+ Fraction(314, 1)
+ >>> Fraction('-35/4')
+ Fraction(-35, 4)
+ >>> Fraction('3.1415') # conversion from numeric string
+ Fraction(6283, 2000)
+ >>> Fraction('-47e-2') # string may include a decimal exponent
+ Fraction(-47, 100)
+ >>> Fraction(1.47) # direct construction from float (exact conversion)
+ Fraction(6620291452234629, 4503599627370496)
+ >>> Fraction(2.25)
+ Fraction(9, 4)
+ >>> Fraction(Decimal('1.47'))
+ Fraction(147, 100)
"""
self = super(Fraction, cls).__new__(cls)
@@ -69,6 +109,19 @@ class Fraction(numbers.Rational):
self._denominator = numerator.denominator
return self
+ elif isinstance(numerator, float):
+ # Exact conversion from float
+ value = Fraction.from_float(numerator)
+ self._numerator = value._numerator
+ self._denominator = value._denominator
+ return self
+
+ elif isinstance(numerator, Decimal):
+ value = Fraction.from_decimal(numerator)
+ self._numerator = value._numerator
+ self._denominator = value._denominator
+ return self
+
elif isinstance(numerator, str):
# Handle construction from strings.
m = _RATIONAL_FORMAT.match(numerator)
@@ -475,23 +528,26 @@ class Fraction(numbers.Rational):
return Fraction(round(self / shift) * shift)
def __hash__(self):
- """hash(self)
-
- Tricky because values that are exactly representable as a
- float must have the same hash as that float.
+ """hash(self)"""
- """
# XXX since this method is expensive, consider caching the result
- if self._denominator == 1:
- # Get integers right.
- return hash(self._numerator)
- # Expensive check, but definitely correct.
- if self == float(self):
- return hash(float(self))
+
+ # In order to make sure that the hash of a Fraction agrees
+ # with the hash of a numerically equal integer, float or
+ # Decimal instance, we follow the rules for numeric hashes
+ # outlined in the documentation. (See library docs, 'Built-in
+ # Types').
+
+ # dinv is the inverse of self._denominator modulo the prime
+ # _PyHASH_MODULUS, or 0 if self._denominator is divisible by
+ # _PyHASH_MODULUS.
+ dinv = pow(self._denominator, _PyHASH_MODULUS - 2, _PyHASH_MODULUS)
+ if not dinv:
+ hash_ = _PyHASH_INF
else:
- # Use tuple's hash to avoid a high collision rate on
- # simple fractions.
- return hash((self._numerator, self._denominator))
+ hash_ = abs(self._numerator) * dinv % _PyHASH_MODULUS
+ result = hash_ if self >= 0 else -hash_
+ return -2 if result == -1 else result
def __eq__(a, b):
"""a == b"""
@@ -526,8 +582,6 @@ class Fraction(numbers.Rational):
if isinstance(other, numbers.Rational):
return op(self._numerator * other.denominator,
self._denominator * other.numerator)
- if isinstance(other, numbers.Complex) and other.imag == 0:
- other = other.real
if isinstance(other, float):
if math.isnan(other) or math.isinf(other):
return op(0.0, other)
diff --git a/Lib/ftplib.py b/Lib/ftplib.py
index ea91c17..7c39887 100644
--- a/Lib/ftplib.py
+++ b/Lib/ftplib.py
@@ -33,17 +33,12 @@ python ftplib.py -d localhost -l -p -l
# Modified by Jack to work on the mac.
# Modified by Siebren to support docstrings and PASV.
# Modified by Phil Schwartz to add storbinary and storlines callbacks.
+# Modified by Giampaolo Rodola' to add TLS support.
#
import os
import sys
-
-# Import SOCKS module if it exists, else standard socket module socket
-try:
- import SOCKS; socket = SOCKS; del SOCKS # import SOCKS as socket
- from socket import getfqdn; socket.getfqdn = getfqdn; del getfqdn
-except ImportError:
- import socket
+import socket
from socket import _GLOBAL_DEFAULT_TIMEOUT
__all__ = ["FTP","Netrc"]
@@ -119,6 +114,20 @@ class FTP:
if user:
self.login(user, passwd, acct)
+ def __enter__(self):
+ return self
+
+ # Context management protocol: try to quit() if active
+ def __exit__(self, *args):
+ if self.sock is not None:
+ try:
+ self.quit()
+ except (socket.error, EOFError):
+ pass
+ finally:
+ if self.sock is not None:
+ self.close()
+
def connect(self, host='', port=0, timeout=-999):
'''Connect to host. Arguments are:
- host: hostname to connect to (string, default previous host)
@@ -162,7 +171,7 @@ class FTP:
def sanitize(self, s):
if s[:5] == 'pass ' or s[:5] == 'PASS ':
i = len(s)
- while i > 5 and s[i-1] in '\r\n':
+ while i > 5 and s[i-1] in {'\r', '\n'}:
i = i-1
s = s[:5] + '*'*(i-5) + s[i:]
return repr(s)
@@ -212,7 +221,7 @@ class FTP:
if self.debugging: print('*resp*', self.sanitize(resp))
self.lastresp = resp[:3]
c = resp[:1]
- if c in ('1', '2', '3'):
+ if c in {'1', '2', '3'}:
return resp
if c == '4':
raise error_temp(resp)
@@ -236,7 +245,7 @@ class FTP:
if self.debugging > 1: print('*put urgent*', self.sanitize(line))
self.sock.sendall(line, MSG_OOB)
resp = self.getmultiline()
- if resp[:3] not in ('426', '225', '226'):
+ if resp[:3] not in {'426', '225', '226'}:
raise error_proto(resp)
def sendcmd(self, cmd):
@@ -352,6 +361,7 @@ class FTP:
conn, sockaddr = sock.accept()
if self.timeout is not _GLOBAL_DEFAULT_TIMEOUT:
conn.settimeout(self.timeout)
+ sock.close()
if resp[:3] == '150':
# this is conditional in case we received a 125
size = parse150(resp)
@@ -366,7 +376,7 @@ class FTP:
if not user: user = 'anonymous'
if not passwd: passwd = ''
if not acct: acct = ''
- if user == 'anonymous' and passwd in ('', '-'):
+ if user == 'anonymous' and passwd in {'', '-'}:
# If there is no anonymous ftp password specified
# then we'll just use anonymous@
# We don't send any other thing because:
@@ -397,13 +407,12 @@ class FTP:
The response code.
"""
self.voidcmd('TYPE I')
- conn = self.transfercmd(cmd, rest)
- while 1:
- data = conn.recv(blocksize)
- if not data:
- break
- callback(data)
- conn.close()
+ with self.transfercmd(cmd, rest) as conn:
+ while 1:
+ data = conn.recv(blocksize)
+ if not data:
+ break
+ callback(data)
return self.voidresp()
def retrlines(self, cmd, callback = None):
@@ -420,23 +429,21 @@ class FTP:
"""
if callback is None: callback = print_line
resp = self.sendcmd('TYPE A')
- conn = self.transfercmd(cmd)
- fp = conn.makefile('r', encoding=self.encoding)
- while 1:
- line = fp.readline()
- if self.debugging > 2: print('*retr*', repr(line))
- if not line:
- break
- if line[-2:] == CRLF:
- line = line[:-2]
- elif line[-1:] == '\n':
- line = line[:-1]
- callback(line)
- fp.close()
- conn.close()
+ with self.transfercmd(cmd) as conn, \
+ conn.makefile('r', encoding=self.encoding) as fp:
+ while 1:
+ line = fp.readline()
+ if self.debugging > 2: print('*retr*', repr(line))
+ if not line:
+ break
+ if line[-2:] == CRLF:
+ line = line[:-2]
+ elif line[-1:] == '\n':
+ line = line[:-1]
+ callback(line)
return self.voidresp()
- def storbinary(self, cmd, fp, blocksize=8192, callback=None):
+ def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None):
"""Store a file in binary mode. A new port is created for you.
Args:
@@ -446,18 +453,18 @@ class FTP:
the connection at once. [default: 8192]
callback: An optional single parameter callable that is called on
on each block of data after it is sent. [default: None]
+ rest: Passed to transfercmd(). [default: None]
Returns:
The response code.
"""
self.voidcmd('TYPE I')
- conn = self.transfercmd(cmd)
- while 1:
- buf = fp.read(blocksize)
- if not buf: break
- conn.sendall(buf)
- if callback: callback(buf)
- conn.close()
+ with self.transfercmd(cmd, rest) as conn:
+ while 1:
+ buf = fp.read(blocksize)
+ if not buf: break
+ conn.sendall(buf)
+ if callback: callback(buf)
return self.voidresp()
def storlines(self, cmd, fp, callback=None):
@@ -473,16 +480,15 @@ class FTP:
The response code.
"""
self.voidcmd('TYPE A')
- conn = self.transfercmd(cmd)
- while 1:
- buf = fp.readline()
- if not buf: break
- if buf[-2:] != B_CRLF:
- if buf[-1] in B_CRLF: buf = buf[:-1]
- buf = buf + B_CRLF
- conn.sendall(buf)
- if callback: callback(buf)
- conn.close()
+ with self.transfercmd(cmd) as conn:
+ while 1:
+ buf = fp.readline()
+ if not buf: break
+ if buf[-2:] != B_CRLF:
+ if buf[-1] in B_CRLF: buf = buf[:-1]
+ buf = buf + B_CRLF
+ conn.sendall(buf)
+ if callback: callback(buf)
return self.voidresp()
def acct(self, password):
@@ -524,7 +530,7 @@ class FTP:
def delete(self, filename):
'''Delete a file.'''
resp = self.sendcmd('DELE ' + filename)
- if resp[:3] in ('250', '200'):
+ if resp[:3] in {'250', '200'}:
return resp
else:
raise error_reply(resp)
@@ -555,7 +561,11 @@ class FTP:
def mkd(self, dirname):
'''Make a directory, return its full pathname.'''
- resp = self.sendcmd('MKD ' + dirname)
+ resp = self.voidcmd('MKD ' + dirname)
+ # fix around non-compliant implementations such as IIS shipped
+ # with Windows server 2003
+ if not resp.startswith('257'):
+ return ''
return parse257(resp)
def rmd(self, dirname):
@@ -564,7 +574,11 @@ class FTP:
def pwd(self):
'''Return current working directory.'''
- resp = self.sendcmd('PWD')
+ resp = self.voidcmd('PWD')
+ # fix around non-compliant implementations such as IIS shipped
+ # with Windows server 2003
+ if not resp.startswith('257'):
+ return ''
return parse257(resp)
def quit(self):
@@ -581,6 +595,196 @@ class FTP:
self.file = self.sock = None
+try:
+ import ssl
+except ImportError:
+ pass
+else:
+ class FTP_TLS(FTP):
+ '''A FTP subclass which adds TLS support to FTP as described
+ in RFC-4217.
+
+ Connect as usual to port 21 implicitly securing the FTP control
+ connection before authenticating.
+
+ Securing the data connection requires user to explicitly ask
+ for it by calling prot_p() method.
+
+ Usage example:
+ >>> from ftplib import FTP_TLS
+ >>> ftps = FTP_TLS('ftp.python.org')
+ >>> ftps.login() # login anonymously previously securing control channel
+ '230 Guest login ok, access restrictions apply.'
+ >>> ftps.prot_p() # switch to secure data connection
+ '200 Protection level set to P'
+ >>> ftps.retrlines('LIST') # list directory content securely
+ total 9
+ drwxr-xr-x 8 root wheel 1024 Jan 3 1994 .
+ drwxr-xr-x 8 root wheel 1024 Jan 3 1994 ..
+ drwxr-xr-x 2 root wheel 1024 Jan 3 1994 bin
+ drwxr-xr-x 2 root wheel 1024 Jan 3 1994 etc
+ d-wxrwxr-x 2 ftp wheel 1024 Sep 5 13:43 incoming
+ drwxr-xr-x 2 root wheel 1024 Nov 17 1993 lib
+ drwxr-xr-x 6 1094 wheel 1024 Sep 13 19:07 pub
+ drwxr-xr-x 3 root wheel 1024 Jan 3 1994 usr
+ -rw-r--r-- 1 root root 312 Aug 1 1994 welcome.msg
+ '226 Transfer complete.'
+ >>> ftps.quit()
+ '221 Goodbye.'
+ >>>
+ '''
+ ssl_version = ssl.PROTOCOL_TLSv1
+
+ def __init__(self, host='', user='', passwd='', acct='', keyfile=None,
+ certfile=None, context=None,
+ timeout=_GLOBAL_DEFAULT_TIMEOUT):
+ if context is not None and keyfile is not None:
+ raise ValueError("context and keyfile arguments are mutually "
+ "exclusive")
+ if context is not None and certfile is not None:
+ raise ValueError("context and certfile arguments are mutually "
+ "exclusive")
+ self.keyfile = keyfile
+ self.certfile = certfile
+ self.context = context
+ self._prot_p = False
+ FTP.__init__(self, host, user, passwd, acct, timeout)
+
+ def login(self, user='', passwd='', acct='', secure=True):
+ if secure and not isinstance(self.sock, ssl.SSLSocket):
+ self.auth()
+ return FTP.login(self, user, passwd, acct)
+
+ def auth(self):
+ '''Set up secure control connection by using TLS/SSL.'''
+ if isinstance(self.sock, ssl.SSLSocket):
+ raise ValueError("Already using TLS")
+ if self.ssl_version == ssl.PROTOCOL_TLSv1:
+ resp = self.voidcmd('AUTH TLS')
+ else:
+ resp = self.voidcmd('AUTH SSL')
+ if self.context is not None:
+ self.sock = self.context.wrap_socket(self.sock)
+ else:
+ self.sock = ssl.wrap_socket(self.sock, self.keyfile,
+ self.certfile,
+ ssl_version=self.ssl_version)
+ self.file = self.sock.makefile(mode='r', encoding=self.encoding)
+ return resp
+
+ def prot_p(self):
+ '''Set up secure data connection.'''
+ # PROT defines whether or not the data channel is to be protected.
+ # Though RFC-2228 defines four possible protection levels,
+ # RFC-4217 only recommends two, Clear and Private.
+ # Clear (PROT C) means that no security is to be used on the
+ # data-channel, Private (PROT P) means that the data-channel
+ # should be protected by TLS.
+ # PBSZ command MUST still be issued, but must have a parameter of
+ # '0' to indicate that no buffering is taking place and the data
+ # connection should not be encapsulated.
+ self.voidcmd('PBSZ 0')
+ resp = self.voidcmd('PROT P')
+ self._prot_p = True
+ return resp
+
+ def prot_c(self):
+ '''Set up clear text data connection.'''
+ resp = self.voidcmd('PROT C')
+ self._prot_p = False
+ return resp
+
+ # --- Overridden FTP methods
+
+ def ntransfercmd(self, cmd, rest=None):
+ conn, size = FTP.ntransfercmd(self, cmd, rest)
+ if self._prot_p:
+ if self.context is not None:
+ conn = self.context.wrap_socket(conn)
+ else:
+ conn = ssl.wrap_socket(conn, self.keyfile, self.certfile,
+ ssl_version=self.ssl_version)
+ return conn, size
+
+ def retrbinary(self, cmd, callback, blocksize=8192, rest=None):
+ self.voidcmd('TYPE I')
+ conn = self.transfercmd(cmd, rest)
+ try:
+ while 1:
+ data = conn.recv(blocksize)
+ if not data:
+ break
+ callback(data)
+ # shutdown ssl layer
+ if isinstance(conn, ssl.SSLSocket):
+ conn.unwrap()
+ finally:
+ conn.close()
+ return self.voidresp()
+
+ def retrlines(self, cmd, callback = None):
+ if callback is None: callback = print_line
+ resp = self.sendcmd('TYPE A')
+ conn = self.transfercmd(cmd)
+ fp = conn.makefile('r', encoding=self.encoding)
+ try:
+ while 1:
+ line = fp.readline()
+ if self.debugging > 2: print('*retr*', repr(line))
+ if not line:
+ break
+ if line[-2:] == CRLF:
+ line = line[:-2]
+ elif line[-1:] == '\n':
+ line = line[:-1]
+ callback(line)
+ # shutdown ssl layer
+ if isinstance(conn, ssl.SSLSocket):
+ conn.unwrap()
+ finally:
+ fp.close()
+ conn.close()
+ return self.voidresp()
+
+ def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None):
+ self.voidcmd('TYPE I')
+ conn = self.transfercmd(cmd, rest)
+ try:
+ while 1:
+ buf = fp.read(blocksize)
+ if not buf: break
+ conn.sendall(buf)
+ if callback: callback(buf)
+ # shutdown ssl layer
+ if isinstance(conn, ssl.SSLSocket):
+ conn.unwrap()
+ finally:
+ conn.close()
+ return self.voidresp()
+
+ def storlines(self, cmd, fp, callback=None):
+ self.voidcmd('TYPE A')
+ conn = self.transfercmd(cmd)
+ try:
+ while 1:
+ buf = fp.readline()
+ if not buf: break
+ if buf[-2:] != B_CRLF:
+ if buf[-1] in B_CRLF: buf = buf[:-1]
+ buf = buf + B_CRLF
+ conn.sendall(buf)
+ if callback: callback(buf)
+ # shutdown ssl layer
+ if isinstance(conn, ssl.SSLSocket):
+ conn.unwrap()
+ finally:
+ conn.close()
+ return self.voidresp()
+
+ __all__.append('FTP_TLS')
+ all_errors = (Error, IOError, EOFError, ssl.SSLError)
+
+
_150_re = None
def parse150(resp):
@@ -689,9 +893,9 @@ def ftpcp(source, sourcename, target, targetname = '', type = 'I'):
# transfer request.
# So: STOR before RETR, because here the target is a "user".
treply = target.sendcmd('STOR ' + targetname)
- if treply[:3] not in ('125', '150'): raise error_proto # RFC 959
+ if treply[:3] not in {'125', '150'}: raise error_proto # RFC 959
sreply = source.sendcmd('RETR ' + sourcename)
- if sreply[:3] not in ('125', '150'): raise error_proto # RFC 959
+ if sreply[:3] not in {'125', '150'}: raise error_proto # RFC 959
source.voidresp()
target.voidresp()
diff --git a/Lib/functools.py b/Lib/functools.py
index 103dd42..03de69a 100644
--- a/Lib/functools.py
+++ b/Lib/functools.py
@@ -4,10 +4,19 @@
# to allow utilities written in Python to be added
# to the functools module.
# Written by Nick Coghlan <ncoghlan at gmail.com>
-# Copyright (C) 2006 Python Software Foundation.
+# and Raymond Hettinger <python at rcn.com>
+# Copyright (C) 2006-2010 Python Software Foundation.
# See C source code for _functools credits/copyright
+__all__ = ['update_wrapper', 'wraps', 'WRAPPER_ASSIGNMENTS', 'WRAPPER_UPDATES',
+ 'total_ordering', 'cmp_to_key', 'lru_cache', 'reduce', 'partial']
+
from _functools import partial, reduce
+from collections import OrderedDict, namedtuple
+try:
+ from _thread import allocate_lock as Lock
+except:
+ from _dummy_thread import allocate_lock as Lock
# update_wrapper() and wraps() are tools to help write
# wrapper functions that can handle naive introspection
@@ -29,9 +38,14 @@ def update_wrapper(wrapper,
are updated with the corresponding attribute from the wrapped
function (defaults to functools.WRAPPER_UPDATES)
"""
+ wrapper.__wrapped__ = wrapped
for attr in assigned:
- if hasattr(wrapped, attr):
- setattr(wrapper, attr, getattr(wrapped, attr))
+ try:
+ value = getattr(wrapped, attr)
+ except AttributeError:
+ pass
+ else:
+ setattr(wrapper, attr, value)
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
# Return the wrapper so this can be used as a decorator via partial()
@@ -50,3 +64,141 @@ def wraps(wrapped,
"""
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
+
+def total_ordering(cls):
+ """Class decorator that fills in missing ordering methods"""
+ convert = {
+ '__lt__': [('__gt__', lambda self, other: not (self < other or self == other)),
+ ('__le__', lambda self, other: self < other or self == other),
+ ('__ge__', lambda self, other: not self < other)],
+ '__le__': [('__ge__', lambda self, other: not self <= other or self == other),
+ ('__lt__', lambda self, other: self <= other and not self == other),
+ ('__gt__', lambda self, other: not self <= other)],
+ '__gt__': [('__lt__', lambda self, other: not (self > other or self == other)),
+ ('__ge__', lambda self, other: self > other or self == other),
+ ('__le__', lambda self, other: not self > other)],
+ '__ge__': [('__le__', lambda self, other: (not self >= other) or self == other),
+ ('__gt__', lambda self, other: self >= other and not self == other),
+ ('__lt__', lambda self, other: not self >= other)]
+ }
+ # Find user-defined comparisons (not those inherited from object).
+ roots = [op for op in convert if getattr(cls, op, None) is not getattr(object, op, None)]
+ if not roots:
+ raise ValueError('must define at least one ordering operation: < > <= >=')
+ root = max(roots) # prefer __lt__ to __le__ to __gt__ to __ge__
+ for opname, opfunc in convert[root]:
+ if opname not in roots:
+ opfunc.__name__ = opname
+ opfunc.__doc__ = getattr(int, opname).__doc__
+ setattr(cls, opname, opfunc)
+ return cls
+
+def cmp_to_key(mycmp):
+ """Convert a cmp= function into a key= function"""
+ class K(object):
+ def __init__(self, obj, *args):
+ self.obj = obj
+ def __lt__(self, other):
+ return mycmp(self.obj, other.obj) < 0
+ def __gt__(self, other):
+ return mycmp(self.obj, other.obj) > 0
+ def __eq__(self, other):
+ return mycmp(self.obj, other.obj) == 0
+ def __le__(self, other):
+ return mycmp(self.obj, other.obj) <= 0
+ def __ge__(self, other):
+ return mycmp(self.obj, other.obj) >= 0
+ def __ne__(self, other):
+ return mycmp(self.obj, other.obj) != 0
+ def __hash__(self):
+ raise TypeError('hash not implemented')
+ return K
+
+_CacheInfo = namedtuple("CacheInfo", "hits misses maxsize currsize")
+
+def lru_cache(maxsize=100):
+ """Least-recently-used cache decorator.
+
+ If *maxsize* is set to None, the LRU features are disabled and the cache
+ can grow without bound.
+
+ Arguments to the cached function must be hashable.
+
+ View the cache statistics named tuple (hits, misses, maxsize, currsize) with
+ f.cache_info(). Clear the cache and statistics with f.cache_clear().
+ Access the underlying function with f.__wrapped__.
+
+ See: http://en.wikipedia.org/wiki/Cache_algorithms#Least_Recently_Used
+
+ """
+ # Users should only access the lru_cache through its public API:
+ # cache_info, cache_clear, and f.__wrapped__
+ # The internals of the lru_cache are encapsulated for thread safety and
+ # to allow the implementation to change (including a possible C version).
+
+ def decorating_function(user_function,
+ tuple=tuple, sorted=sorted, len=len, KeyError=KeyError):
+
+ hits = misses = 0
+ kwd_mark = object() # separates positional and keyword args
+ lock = Lock() # needed because ordereddicts aren't threadsafe
+
+ if maxsize is None:
+ cache = dict() # simple cache without ordering or size limit
+
+ @wraps(user_function)
+ def wrapper(*args, **kwds):
+ nonlocal hits, misses
+ key = args
+ if kwds:
+ key += (kwd_mark,) + tuple(sorted(kwds.items()))
+ try:
+ result = cache[key]
+ hits += 1
+ except KeyError:
+ result = user_function(*args, **kwds)
+ cache[key] = result
+ misses += 1
+ return result
+ else:
+ cache = OrderedDict() # ordered least recent to most recent
+ cache_popitem = cache.popitem
+ cache_renew = cache.move_to_end
+
+ @wraps(user_function)
+ def wrapper(*args, **kwds):
+ nonlocal hits, misses
+ key = args
+ if kwds:
+ key += (kwd_mark,) + tuple(sorted(kwds.items()))
+ try:
+ with lock:
+ result = cache[key]
+ cache_renew(key) # record recent use of this key
+ hits += 1
+ except KeyError:
+ result = user_function(*args, **kwds)
+ with lock:
+ cache[key] = result # record recent use of this key
+ misses += 1
+ if len(cache) > maxsize:
+ cache_popitem(0) # purge least recently used cache entry
+ return result
+
+ def cache_info():
+ """Report cache statistics"""
+ with lock:
+ return _CacheInfo(hits, misses, maxsize, len(cache))
+
+ def cache_clear():
+ """Clear the cache and cache statistics"""
+ nonlocal hits, misses
+ with lock:
+ cache.clear()
+ hits = misses = 0
+
+ wrapper.cache_info = cache_info
+ wrapper.cache_clear = cache_clear
+ return wrapper
+
+ return decorating_function
diff --git a/Lib/genericpath.py b/Lib/genericpath.py
index 41ad234..2174187 100644
--- a/Lib/genericpath.py
+++ b/Lib/genericpath.py
@@ -15,7 +15,7 @@ __all__ = ['commonprefix', 'exists', 'getatime', 'getctime', 'getmtime',
def exists(path):
"""Test whether a path exists. Returns False for broken symbolic links"""
try:
- st = os.stat(path)
+ os.stat(path)
except os.error:
return False
return True
diff --git a/Lib/getopt.py b/Lib/getopt.py
index ac77126..980861d 100644
--- a/Lib/getopt.py
+++ b/Lib/getopt.py
@@ -1,4 +1,3 @@
-# -*- coding: utf-8 -*-
"""Parser for command line options.
This module helps scripts to parse the command line arguments in
diff --git a/Lib/gettext.py b/Lib/gettext.py
index d5321b8..256e331 100644
--- a/Lib/gettext.py
+++ b/Lib/gettext.py
@@ -46,7 +46,7 @@ internationalized, to the local language and cultural habits.
# find this format documented anywhere.
-import locale, copy, os, re, struct, sys
+import locale, copy, io, os, re, struct, sys
from errno import ENOENT
@@ -58,28 +58,13 @@ __all__ = ['NullTranslations', 'GNUTranslations', 'Catalog',
_default_localedir = os.path.join(sys.prefix, 'share', 'locale')
-def test(condition, true, false):
- """
- Implements the C expression:
-
- condition ? true : false
-
- Required to correctly interpret plural forms.
- """
- if condition:
- return true
- else:
- return false
-
-
def c2py(plural):
"""Gets a C expression as used in PO files for plural forms and returns a
Python lambda function that implements an equivalent expression.
"""
# Security check, allow only the "n" identifier
- from io import StringIO
import token, tokenize
- tokens = tokenize.generate_tokens(StringIO(plural).readline)
+ tokens = tokenize.generate_tokens(io.StringIO(plural).readline)
try:
danger = [x for x in tokens if x[0] == token.NAME and x[1] != 'n']
except tokenize.TokenError:
@@ -96,11 +81,11 @@ def c2py(plural):
plural = expr.sub(' not \\1', plural)
# Regular expression and replacement function used to transform
- # "a?b:c" to "test(a,b,c)".
+ # "a?b:c" to "b if a else c".
expr = re.compile(r'(.*?)\?(.*?):(.*)')
def repl(x):
- return "test(%s, %s, %s)" % (x.group(1), x.group(2),
- expr.sub(repl, x.group(3)))
+ return "(%s if %s else %s)" % (x.group(2), x.group(1),
+ expr.sub(repl, x.group(3)))
# Code to transform the plural expression, taking care of parentheses
stack = ['']
@@ -123,36 +108,35 @@ def c2py(plural):
-def _expand_lang(locale):
- from locale import normalize
- locale = normalize(locale)
+def _expand_lang(loc):
+ loc = locale.normalize(loc)
COMPONENT_CODESET = 1 << 0
COMPONENT_TERRITORY = 1 << 1
COMPONENT_MODIFIER = 1 << 2
# split up the locale into its base components
mask = 0
- pos = locale.find('@')
+ pos = loc.find('@')
if pos >= 0:
- modifier = locale[pos:]
- locale = locale[:pos]
+ modifier = loc[pos:]
+ loc = loc[:pos]
mask |= COMPONENT_MODIFIER
else:
modifier = ''
- pos = locale.find('.')
+ pos = loc.find('.')
if pos >= 0:
- codeset = locale[pos:]
- locale = locale[:pos]
+ codeset = loc[pos:]
+ loc = loc[:pos]
mask |= COMPONENT_CODESET
else:
codeset = ''
- pos = locale.find('_')
+ pos = loc.find('_')
if pos >= 0:
- territory = locale[pos:]
- locale = locale[:pos]
+ territory = loc[pos:]
+ loc = loc[:pos]
mask |= COMPONENT_TERRITORY
else:
territory = ''
- language = locale
+ language = loc
ret = []
for i in range(mask+1):
if not (i & ~mask): # if all components for this combo exist ...
diff --git a/Lib/gzip.py b/Lib/gzip.py
index 8a2a718..ba2149e 100644
--- a/Lib/gzip.py
+++ b/Lib/gzip.py
@@ -5,11 +5,12 @@ but random access is not allowed."""
# based on Andrew Kuchling's minigzip.py distributed with the zlib module
-import struct, sys, time
+import struct, sys, time, os
import zlib
import builtins
+import io
-__all__ = ["GzipFile","open"]
+__all__ = ["GzipFile", "open", "compress", "decompress"]
FTEXT, FHCRC, FEXTRA, FNAME, FCOMMENT = 1, 2, 4, 8, 16
@@ -44,7 +45,63 @@ def open(filename, mode="rb", compresslevel=9):
"""
return GzipFile(filename, mode, compresslevel)
-class GzipFile:
+class _PaddedFile:
+ """Minimal read-only file object that prepends a string to the contents
+ of an actual file. Shouldn't be used outside of gzip.py, as it lacks
+ essential functionality."""
+
+ def __init__(self, f, prepend=b''):
+ self._buffer = prepend
+ self._length = len(prepend)
+ self.file = f
+ self._read = 0
+
+ def read(self, size):
+ if self._read is None:
+ return self.file.read(size)
+ if self._read + size <= self._length:
+ read = self._read
+ self._read += size
+ return self._buffer[read:self._read]
+ else:
+ read = self._read
+ self._read = None
+ return self._buffer[read:] + \
+ self.file.read(size-self._length+read)
+
+ def prepend(self, prepend=b'', readprevious=False):
+ if self._read is None:
+ self._buffer = prepend
+ elif readprevious and len(prepend) <= self._read:
+ self._read -= len(prepend)
+ return
+ else:
+ self._buffer = self._buffer[read:] + prepend
+ self._length = len(self._buffer)
+ self._read = 0
+
+ def unused(self):
+ if self._read is None:
+ return b''
+ return self._buffer[self._read:]
+
+ def seek(self, offset, whence=0):
+ # This is only ever called with offset=whence=0
+ if whence == 1 and self._read is not None:
+ if 0 <= offset + self._read <= self._length:
+ self._read += offset
+ return
+ else:
+ offset += self._length - self._read
+ self._read = None
+ self._buffer = None
+ return self.file.seek(offset, whence)
+
+ def __getattr__(self, name):
+ return getattr(self.file, name)
+
+
+class GzipFile(io.BufferedIOBase):
"""The GzipFile class simulates most of the methods of a file object with
the exception of the readinto() and truncate() methods.
@@ -109,11 +166,16 @@ class GzipFile:
self.mode = READ
# Set flag indicating start of a new member
self._new_member = True
+ # Buffer data read from gzip file. extrastart is offset in
+ # stream where buffer starts. extrasize is number of
+ # bytes remaining in buffer from current stream position.
self.extrabuf = b""
self.extrasize = 0
+ self.extrastart = 0
self.name = filename
# Starts small, scales exponentially
self.min_readsize = 100
+ fileobj = _PaddedFile(fileobj)
elif mode[0:1] == 'w' or mode[0:1] == 'a':
self.mode = WRITE
@@ -129,7 +191,6 @@ class GzipFile:
self.fileobj = fileobj
self.offset = 0
self.mtime = mtime
- self.closed = False
if self.mode == WRITE:
self._write_gzip_header()
@@ -143,7 +204,10 @@ class GzipFile:
return self.name
def __repr__(self):
- s = repr(self.fileobj)
+ fileobj = self.fileobj
+ if isinstance(fileobj, _PaddedFile):
+ fileobj = fileobj.file
+ s = repr(fileobj)
return '<gzip ' + s[1:-1] + ' ' + hex(id(self)) + '>'
def _check_closed(self):
@@ -166,7 +230,8 @@ class GzipFile:
try:
# RFC 1952 requires the FNAME field to be Latin-1. Do not
# include filenames that cannot be represented that way.
- fname = self.name.encode('latin-1')
+ fname = os.path.basename(self.name)
+ fname = fname.encode('latin-1')
if fname.endswith(b'.gz'):
fname = fname[:-3]
except UnicodeEncodeError:
@@ -190,6 +255,9 @@ class GzipFile:
def _read_gzip_header(self):
magic = self.fileobj.read(2)
+ if magic == b'':
+ raise EOFError("Reached EOF")
+
if magic != b'\037\213':
raise IOError('Not a gzipped file')
method = ord( self.fileobj.read(1) )
@@ -221,6 +289,10 @@ class GzipFile:
if flag & FHCRC:
self.fileobj.read(2) # Read & discard the 16-bit header CRC
+ unused = self.fileobj.unused()
+ if unused:
+ uncompress = self.decompress.decompress(unused)
+ self._add_read_data(uncompress)
def write(self,data):
self._check_closed()
@@ -230,12 +302,19 @@ class GzipFile:
if self.fileobj is None:
raise ValueError("write() on closed GzipFile object")
+
+ # Convert data type if called by io.BufferedWriter.
+ if isinstance(data, memoryview):
+ data = data.tobytes()
+
if len(data) > 0:
self.size = self.size + len(data)
self.crc = zlib.crc32(data, self.crc) & 0xffffffff
self.fileobj.write( self.compress.compress(data) )
self.offset += len(data)
+ return len(data)
+
def read(self, size=-1):
self._check_closed()
if self.mode != READ:
@@ -262,15 +341,36 @@ class GzipFile:
if size > self.extrasize:
size = self.extrasize
- chunk = self.extrabuf[:size]
- self.extrabuf = self.extrabuf[size:]
+ offset = self.offset - self.extrastart
+ chunk = self.extrabuf[offset: offset + size]
self.extrasize = self.extrasize - size
self.offset += size
return chunk
+ def peek(self, n):
+ if self.mode != READ:
+ import errno
+ raise IOError(errno.EBADF, "peek() on write-only GzipFile object")
+
+ # Do not return ridiculously small buffers, for one common idiom
+ # is to call peek(1) and expect more bytes in return.
+ if n < 100:
+ n = 100
+ if self.extrasize == 0:
+ if self.fileobj is None:
+ return b''
+ try:
+ # 1024 is the same buffering heuristic used in read()
+ self._read(max(n, 1024))
+ except EOFError:
+ pass
+ offset = self.offset - self.extrastart
+ remaining = self.extrasize
+ assert remaining == len(self.extrabuf) - offset
+ return self.extrabuf[offset:offset + n]
+
def _unread(self, buf):
- self.extrabuf = buf + self.extrabuf
self.extrasize = len(buf) + self.extrasize
self.offset -= len(buf)
@@ -281,16 +381,6 @@ class GzipFile:
if self._new_member:
# If the _new_member flag is set, we have to
# jump to the next member, if there is one.
- #
- # First, check if we're at the end of the file;
- # if so, it's time to stop; no more members to read.
- pos = self.fileobj.tell() # Save current position
- self.fileobj.seek(0, 2) # Seek to end of file
- if pos == self.fileobj.tell():
- raise EOFError("Reached EOF")
- else:
- self.fileobj.seek( pos ) # Return to original position
-
self._init_read()
self._read_gzip_header()
self.decompress = zlib.decompressobj(-zlib.MAX_WBITS)
@@ -304,6 +394,9 @@ class GzipFile:
if buf == b"":
uncompress = self.decompress.flush()
+ # Prepend the already read bytes to the fileobj to they can be
+ # seen by _read_eof()
+ self.fileobj.prepend(self.decompress.unused_data, True)
self._read_eof()
self._add_read_data( uncompress )
raise EOFError('Reached EOF')
@@ -315,10 +408,9 @@ class GzipFile:
# Ending case: we've come to the end of a member in the file,
# so seek back to the start of the unused data, finish up
# this member, and read a new gzip header.
- # (The number of bytes to seek back is the length of the unused
- # data, minus 8 because _read_eof() will rewind a further 8 bytes)
- self.fileobj.seek( -len(self.decompress.unused_data)+8, 1)
-
+ # Prepend the already read bytes to the fileobj to they can be
+ # seen by _read_eof() and _read_gzip_header()
+ self.fileobj.prepend(self.decompress.unused_data, True)
# Check the CRC and file size, and set the flag so we read
# a new member on the next call
self._read_eof()
@@ -326,17 +418,17 @@ class GzipFile:
def _add_read_data(self, data):
self.crc = zlib.crc32(data, self.crc) & 0xffffffff
- self.extrabuf = self.extrabuf + data
+ offset = self.offset - self.extrastart
+ self.extrabuf = self.extrabuf[offset:] + data
self.extrasize = self.extrasize + len(data)
+ self.extrastart = self.offset
self.size = self.size + len(data)
def _read_eof(self):
- # We've read to the end of the file, so we have to rewind in order
- # to reread the 8 bytes containing the CRC and the file size.
+ # We've read to the end of the file
# We check the that the computed CRC and size of the
# uncompressed data matches the stored values. Note that the size
# stored is the true file size mod 2**32.
- self.fileobj.seek(-8, 1)
crc32 = read32(self.fileobj)
isize = read32(self.fileobj) # may exceed 2GB
if crc32 != self.crc:
@@ -345,6 +437,19 @@ class GzipFile:
elif isize != (self.size & 0xffffffff):
raise IOError("Incorrect length of data produced")
+ # Gzip files can be padded with zeroes and still have archives.
+ # Consume all zero bytes and set the file position to the first
+ # non-zero byte. See http://www.gzip.org/#faq8
+ c = b"\x00"
+ while c == b"\x00":
+ c = self.fileobj.read(1)
+ if c:
+ self.fileobj.prepend(c, True)
+
+ @property
+ def closed(self):
+ return self.fileobj is None
+
def close(self):
if self.fileobj is None:
return
@@ -359,16 +464,6 @@ class GzipFile:
if self.myfileobj:
self.myfileobj.close()
self.myfileobj = None
- self.closed = True
-
- def __del__(self):
- try:
- if (self.myfileobj is None and
- self.fileobj is None):
- return
- except AttributeError:
- return
- self.close()
def flush(self,zlib_mode=zlib.Z_SYNC_FLUSH):
self._check_closed()
@@ -385,13 +480,6 @@ class GzipFile:
"""
return self.fileobj.fileno()
- def isatty(self):
- return False
-
- def tell(self):
- self._check_closed()
- return self.offset
-
def rewind(self):
'''Return the uncompressed stream file position indicator to the
beginning of the file'''
@@ -401,8 +489,18 @@ class GzipFile:
self._new_member = True
self.extrabuf = b""
self.extrasize = 0
+ self.extrastart = 0
self.offset = 0
+ def readable(self):
+ return self.mode == READ
+
+ def writable(self):
+ return self.mode == WRITE
+
+ def seekable(self):
+ return True
+
def seek(self, offset, whence=0):
if whence:
if whence == 1:
@@ -426,8 +524,18 @@ class GzipFile:
self.read(1024)
self.read(count % 1024)
+ return self.offset
+
def readline(self, size=-1):
if size < 0:
+ # Shortcut common case - newline found in buffer.
+ offset = self.offset - self.extrastart
+ i = self.extrabuf.find(b'\n', offset) + 1
+ if i > 0:
+ self.extrasize -= i - offset
+ self.offset += i - offset
+ return self.extrabuf[offset: i]
+
size = sys.maxsize
readsize = self.min_readsize
else:
@@ -457,41 +565,22 @@ class GzipFile:
self.min_readsize = min(readsize, self.min_readsize * 2, 512)
return b''.join(bufs) # Return resulting line
- def readlines(self, sizehint=0):
- # Negative numbers result in reading all the lines
- if sizehint <= 0:
- sizehint = sys.maxsize
- L = []
- while sizehint > 0:
- line = self.readline()
- if line == b"":
- break
- L.append(line)
- sizehint = sizehint - len(line)
-
- return L
-
- def writelines(self, L):
- for line in L:
- self.write(line)
-
- def __iter__(self):
- return self
- def __next__(self):
- line = self.readline()
- if line:
- return line
- else:
- raise StopIteration
-
- def __enter__(self):
- if self.fileobj is None:
- raise ValueError("I/O operation on closed GzipFile object")
- return self
-
- def __exit__(self, *args):
- self.close()
+def compress(data, compresslevel=9):
+ """Compress data in one shot and return the compressed string.
+ Optional argument is the compression level, in range of 1-9.
+ """
+ buf = io.BytesIO()
+ with GzipFile(fileobj=buf, mode='wb', compresslevel=compresslevel) as f:
+ f.write(data)
+ return buf.getvalue()
+
+def decompress(data):
+ """Decompress a gzip compressed string in one shot.
+ Return the decompressed string.
+ """
+ with GzipFile(fileobj=io.BytesIO(data)) as f:
+ return f.read()
def _test():
diff --git a/Lib/hashlib.py b/Lib/hashlib.py
index 34ed195..0d7e325 100644
--- a/Lib/hashlib.py
+++ b/Lib/hashlib.py
@@ -1,6 +1,4 @@
-# $Id$
-#
-# Copyright (C) 2005-2007 Gregory P. Smith (greg@krypto.org)
+# Copyright (C) 2005-2010 Gregory P. Smith (greg@krypto.org)
# Licensed to PSF under a Contributor Agreement.
#
@@ -15,8 +13,9 @@ than using new(name):
md5(), sha1(), sha224(), sha256(), sha384(), and sha512()
-More algorithms may be available on your platform but the above are
-guaranteed to exist.
+More algorithms may be available on your platform but the above are guaranteed
+to exist. See the algorithms_guaranteed and algorithms_available attributes
+to find out what algorithm names can be passed to new().
NOTE: If you want the adler32 or crc32 hash functions they are available in
the zlib module.
@@ -53,6 +52,16 @@ More condensed:
"""
+# This tuple and __get_builtin_constructor() must be modified if a new
+# always available algorithm is added.
+__always_supported = ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512')
+
+algorithms_guaranteed = set(__always_supported)
+algorithms_available = set(__always_supported)
+
+__all__ = __always_supported + ('new', 'algorithms_guaranteed',
+ 'algorithms_available')
+
def __get_builtin_constructor(name):
if name in ('SHA1', 'sha1'):
@@ -76,7 +85,19 @@ def __get_builtin_constructor(name):
elif bs == '384':
return _sha512.sha384
- raise ValueError("unsupported hash type")
+ raise ValueError('unsupported hash type %s' % name)
+
+
+def __get_openssl_constructor(name):
+ try:
+ f = getattr(_hashlib, 'openssl_' + name)
+ # Allow the C module to raise ValueError. The function will be
+ # defined but the hash not actually available thanks to OpenSSL.
+ f()
+ # Use the C function directly (very fast)
+ return f
+ except (AttributeError, ValueError):
+ return __get_builtin_constructor(name)
def __py_new(name, data=b''):
@@ -102,39 +123,23 @@ def __hash_new(name, data=b''):
try:
import _hashlib
- # use the wrapper of the C implementation
new = __hash_new
-
- for opensslFuncName in filter(lambda n: n.startswith('openssl_'), dir(_hashlib)):
- funcName = opensslFuncName[len('openssl_'):]
- try:
- # try them all, some may not work due to the OpenSSL
- # version not supporting that algorithm.
- f = getattr(_hashlib, opensslFuncName)
- f()
- # Use the C function directly (very fast)
- exec(funcName + ' = f')
- except ValueError:
- try:
- # Use the builtin implementation directly (fast)
- exec(funcName + ' = __get_builtin_constructor(funcName)')
- except ValueError:
- # this one has no builtin implementation, don't define it
- pass
- # clean up our locals
- del f
- del opensslFuncName
- del funcName
-
+ __get_hash = __get_openssl_constructor
+ algorithms_available = algorithms_available.union(
+ _hashlib.openssl_md_meth_names)
except ImportError:
- # We don't have the _hashlib OpenSSL module?
- # use the built in legacy interfaces via a wrapper function
new = __py_new
+ __get_hash = __get_builtin_constructor
+
+for __func_name in __always_supported:
+ # try them all, some may not work due to the OpenSSL
+ # version not supporting that algorithm.
+ try:
+ globals()[__func_name] = __get_hash(__func_name)
+ except ValueError:
+ import logging
+ logging.exception('code for hash %s was not found.', __func_name)
- # lookup the C function to use directly for the named constructors
- md5 = __get_builtin_constructor('md5')
- sha1 = __get_builtin_constructor('sha1')
- sha224 = __get_builtin_constructor('sha224')
- sha256 = __get_builtin_constructor('sha256')
- sha384 = __get_builtin_constructor('sha384')
- sha512 = __get_builtin_constructor('sha512')
+# Cleanup locals()
+del __always_supported, __func_name, __get_hash
+del __py_new, __hash_new, __get_openssl_constructor
diff --git a/Lib/heapq.py b/Lib/heapq.py
index b74818e..464663a 100644
--- a/Lib/heapq.py
+++ b/Lib/heapq.py
@@ -1,5 +1,3 @@
-# -*- coding: latin-1 -*-
-
"""Heap queue algorithm (a.k.a. priority queue).
Heaps are arrays for which a[k] <= a[2*k+1] and a[k] <= a[2*k+2] for
@@ -34,7 +32,7 @@ maintains the heap invariant!
__about__ = """Heap queues
-[explanation by François Pinard]
+[explanation by François Pinard]
Heaps are arrays for which a[k] <= a[2*k+1] and a[k] <= a[2*k+2] for
all k, counting elements from 0. For the sake of comparison,
diff --git a/Lib/hmac.py b/Lib/hmac.py
index 0f59fd4..e878e1a 100644
--- a/Lib/hmac.py
+++ b/Lib/hmac.py
@@ -12,10 +12,6 @@ trans_36 = bytes((x ^ 0x36) for x in range(256))
# hashing module used. Use digest_size from the instance of HMAC instead.
digest_size = None
-# A unique object passed by HMAC.copy() to the HMAC constructor, in order
-# that the latter return very quickly. HMAC("") in contrast is quite
-# expensive.
-_secret_backdoor_key = []
class HMAC:
"""RFC 2104 HMAC class. Also complies with RFC 4231.
@@ -36,9 +32,6 @@ class HMAC:
Note: key and msg must be bytes objects.
"""
- if key is _secret_backdoor_key: # cheap
- return
-
if not isinstance(key, bytes):
raise TypeError("expected bytes, but got %r" % type(key).__name__)
@@ -58,8 +51,6 @@ class HMAC:
if hasattr(self.inner, 'block_size'):
blocksize = self.inner.block_size
if blocksize < 16:
- # Very low blocksize, most likely a legacy value like
- # Lib/sha.py and Lib/md5.py have.
_warnings.warn('block_size of %d seems too small; using our '
'default of %d.' % (blocksize, self.blocksize),
RuntimeWarning, 2)
@@ -79,9 +70,6 @@ class HMAC:
if msg is not None:
self.update(msg)
-## def clear(self):
-## raise NotImplementedError, "clear() method not available in HMAC."
-
def update(self, msg):
"""Update this hashing object with the string msg.
"""
@@ -94,7 +82,8 @@ class HMAC:
An update to this copy won't affect the original object.
"""
- other = self.__class__(_secret_backdoor_key)
+ # Call __new__ directly to avoid the expensive __init__.
+ other = self.__class__.__new__(self.__class__)
other.digest_cons = self.digest_cons
other.digest_size = self.digest_size
other.inner = self.inner.copy()
diff --git a/Lib/html/__init__.py b/Lib/html/__init__.py
index 196d378..335d214 100644
--- a/Lib/html/__init__.py
+++ b/Lib/html/__init__.py
@@ -1 +1,20 @@
-# This directory is a Python package.
+"""
+General functions for HTML manipulation.
+"""
+
+
+_escape_map = {ord('&'): '&amp;', ord('<'): '&lt;', ord('>'): '&gt;'}
+_escape_map_full = {ord('&'): '&amp;', ord('<'): '&lt;', ord('>'): '&gt;',
+ ord('"'): '&quot;', ord('\''): '&#x27;'}
+
+# NB: this is a candidate for a bytes/string polymorphic interface
+
+def escape(s, quote=True):
+ """
+ Replace special characters "&", "<" and ">" to HTML-safe sequences.
+ If the optional flag quote is true (the default), the quotation mark
+ character (") is also translated.
+ """
+ if quote:
+ return s.translate(_escape_map_full)
+ return s.translate(_escape_map)
diff --git a/Lib/html/parser.py b/Lib/html/parser.py
index 3f68e18..21ebbc3 100644
--- a/Lib/html/parser.py
+++ b/Lib/html/parser.py
@@ -24,10 +24,14 @@ starttagopen = re.compile('<[a-zA-Z]')
piclose = re.compile('>')
commentclose = re.compile(r'--\s*>')
tagfind = re.compile('[a-zA-Z][-.a-zA-Z0-9:_]*')
+# Note, the strict one of this pair isn't really strict, but we can't
+# make it correctly strict without breaking backward compatibility.
attrfind = re.compile(
r'\s*([a-zA-Z_][-.:a-zA-Z_0-9]*)(\s*=\s*'
r'(\'[^\']*\'|"[^"]*"|[-a-zA-Z0-9./,:;+*%?!&$\(\)_#=~@]*))?')
-
+attrfind_tolerant = re.compile(
+ r'\s*([a-zA-Z_][-.:a-zA-Z_0-9]*)(\s*=\s*'
+ r'(\'[^\']*\'|"[^"]*"|[^>\s]*))?')
locatestarttagend = re.compile(r"""
<[a-zA-Z][-.a-zA-Z0-9:_]* # tag name
(?:\s+ # whitespace before attribute name
@@ -42,6 +46,21 @@ locatestarttagend = re.compile(r"""
)*
\s* # trailing whitespace
""", re.VERBOSE)
+locatestarttagend_tolerant = re.compile(r"""
+ <[a-zA-Z][-.a-zA-Z0-9:_]* # tag name
+ (?:\s* # optional whitespace before attribute name
+ (?:[a-zA-Z_][-.:a-zA-Z0-9_]* # attribute name
+ (?:\s*=\s* # value indicator
+ (?:'[^']*' # LITA-enclosed value
+ |\"[^\"]*\" # LIT-enclosed value
+ |[^'\">\s]+ # bare value
+ )
+ (?:\s*,)* # possibly followed by a comma
+ )?
+ )
+ )*
+ \s* # trailing whitespace
+""", re.VERBOSE)
endendtag = re.compile('>')
endtagfind = re.compile('</\s*([a-zA-Z][-.a-zA-Z0-9:_]*)\s*>')
@@ -86,9 +105,15 @@ class HTMLParser(_markupbase.ParserBase):
CDATA_CONTENT_ELEMENTS = ("script", "style")
+ def __init__(self, strict=True):
+ """Initialize and reset this instance.
- def __init__(self):
- """Initialize and reset this instance."""
+ If strict is set to True (the default), errors are raised when invalid
+ HTML is encountered. If set to False, an attempt is instead made to
+ continue parsing, making "best guesses" about the intended meaning, in
+ a fashion similar to what browsers typically do.
+ """
+ self.strict = strict
self.reset()
def reset(self):
@@ -160,9 +185,18 @@ class HTMLParser(_markupbase.ParserBase):
else:
break
if k < 0:
- if end:
+ if not end:
+ break
+ if self.strict:
self.error("EOF in middle of construct")
- break
+ k = rawdata.find('>', i + 1)
+ if k < 0:
+ k = rawdata.find('<', i + 1)
+ if k < 0:
+ k = i + 1
+ else:
+ k += 1
+ self.handle_data(rawdata[i:k])
i = self.updatepos(i, k)
elif startswith("&#", i):
match = charref.match(rawdata, i)
@@ -193,7 +227,12 @@ class HTMLParser(_markupbase.ParserBase):
if match:
# match.group() will contain at least 2 chars
if end and match.group() == rawdata[i:]:
- self.error("EOF in middle of entity or char ref")
+ if self.strict:
+ self.error("EOF in middle of entity or char ref")
+ else:
+ if k <= i:
+ k = n
+ i = self.updatepos(i, i + 1)
# incomplete
break
elif (i + 1) < n:
@@ -240,7 +279,10 @@ class HTMLParser(_markupbase.ParserBase):
self.lasttag = tag = rawdata[i+1:k].lower()
while k < endpos:
- m = attrfind.match(rawdata, k)
+ if self.strict:
+ m = attrfind.match(rawdata, k)
+ else:
+ m = attrfind_tolerant.search(rawdata, k)
if not m:
break
attrname, rest, attrvalue = m.group(1, 2, 3)
@@ -262,8 +304,11 @@ class HTMLParser(_markupbase.ParserBase):
- self.__starttag_text.rfind("\n")
else:
offset = offset + len(self.__starttag_text)
- self.error("junk characters in start tag: %r"
- % (rawdata[k:endpos][:20],))
+ if self.strict:
+ self.error("junk characters in start tag: %r"
+ % (rawdata[k:endpos][:20],))
+ self.handle_data(rawdata[i:endpos])
+ return endpos
if end.endswith('/>'):
# XHTML-style empty tag: <span attr="value" />
self.handle_startendtag(tag, attrs)
@@ -277,7 +322,10 @@ class HTMLParser(_markupbase.ParserBase):
# or -1 if incomplete.
def check_for_whole_start_tag(self, i):
rawdata = self.rawdata
- m = locatestarttagend.match(rawdata, i)
+ if self.strict:
+ m = locatestarttagend.match(rawdata, i)
+ else:
+ m = locatestarttagend_tolerant.match(rawdata, i)
if m:
j = m.end()
next = rawdata[j:j+1]
@@ -290,8 +338,13 @@ class HTMLParser(_markupbase.ParserBase):
# buffer boundary
return -1
# else bogus input
- self.updatepos(i, j + 1)
- self.error("malformed empty start tag")
+ if self.strict:
+ self.updatepos(i, j + 1)
+ self.error("malformed empty start tag")
+ if j > i:
+ return j
+ else:
+ return i + 1
if next == "":
# end of input
return -1
@@ -300,8 +353,13 @@ class HTMLParser(_markupbase.ParserBase):
# end of input in or before attribute value, or we have the
# '/' from a '/>' ending
return -1
- self.updatepos(i, j)
- self.error("malformed start tag")
+ if self.strict:
+ self.updatepos(i, j)
+ self.error("malformed start tag")
+ if j > i:
+ return j
+ else:
+ return i + 1
raise AssertionError("we should not get here!")
# Internal -- parse endtag, return end or -1 if incomplete
@@ -314,7 +372,15 @@ class HTMLParser(_markupbase.ParserBase):
j = match.end()
match = endtagfind.match(rawdata, i) # </ + tag + >
if not match:
- self.error("bad end tag: %r" % (rawdata[i:j],))
+ if self.strict:
+ self.error("bad end tag: %r" % (rawdata[i:j],))
+ k = rawdata.find('<', i + 1, j)
+ if k > i:
+ j = k
+ if j <= i:
+ j = i + 1
+ self.handle_data(rawdata[i:j])
+ return j
tag = match.group(1)
self.handle_endtag(tag.lower())
self.clear_cdata_mode()
@@ -358,7 +424,8 @@ class HTMLParser(_markupbase.ParserBase):
pass
def unknown_decl(self, data):
- self.error("unknown declaration: %r" % (data,))
+ if self.strict:
+ self.error("unknown declaration: %r" % (data,))
# Internal -- helper to remove special character quoting
entitydefs = None
diff --git a/Lib/http/client.py b/Lib/http/client.py
index 4a65125..604577c 100644
--- a/Lib/http/client.py
+++ b/Lib/http/client.py
@@ -71,6 +71,7 @@ import email.message
import io
import os
import socket
+import collections
from urllib.parse import urlsplit
import warnings
@@ -257,13 +258,10 @@ def parse_headers(fp, _class=HTTPMessage):
hstring = b''.join(headers).decode('iso-8859-1')
return email.parser.Parser(_class=_class).parsestr(hstring)
-class HTTPResponse(io.RawIOBase):
- # strict: If true, raise BadStatusLine if the status line can't be
- # parsed as a valid HTTP/1.0 or 1.1 status line. By default it is
- # false because it prevents clients from talking to HTTP/0.9
- # servers. Note that a response with a sufficiently corrupted
- # status line will look like an HTTP/0.9 response.
+_strict_sentinel = object()
+
+class HTTPResponse(io.RawIOBase):
# See RFC 2616 sec 19.6 and RFC 1945 sec 6 for details.
@@ -272,7 +270,7 @@ class HTTPResponse(io.RawIOBase):
# text following RFC 2047. The basic status line parsing only
# accepts iso-8859-1.
- def __init__(self, sock, debuglevel=0, strict=0, method=None, url=None):
+ def __init__(self, sock, debuglevel=0, strict=_strict_sentinel, method=None, url=None):
# If the response includes a content-length header, we need to
# make sure that the client doesn't read more than the
# specified number of bytes. If it does, it will block until
@@ -282,7 +280,10 @@ class HTTPResponse(io.RawIOBase):
# clients unless they know what they are doing.
self.fp = sock.makefile("rb")
self.debuglevel = debuglevel
- self.strict = strict
+ if strict is not _strict_sentinel:
+ warnings.warn("the 'strict' argument isn't supported anymore; "
+ "http.client now always assumes HTTP/1.x compliant servers.",
+ DeprecationWarning, 2)
self._method = method
# The HTTPResponse object is returned via urllib. The clients
@@ -304,8 +305,9 @@ class HTTPResponse(io.RawIOBase):
self.will_close = _UNKNOWN # conn will close at end of response
def _read_status(self):
- # Initialize with Simple-Response defaults.
- line = str(self.fp.readline(), "iso-8859-1")
+ line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
+ if len(line) > _MAXLINE:
+ raise LineTooLong("status line")
if self.debuglevel > 0:
print("reply:", repr(line))
if not line:
@@ -313,25 +315,17 @@ class HTTPResponse(io.RawIOBase):
# sending a valid response.
raise BadStatusLine(line)
try:
- [version, status, reason] = line.split(None, 2)
+ version, status, reason = line.split(None, 2)
except ValueError:
try:
- [version, status] = line.split(None, 1)
+ version, status = line.split(None, 1)
reason = ""
except ValueError:
- # empty version will cause next test to fail and status
- # will be treated as 0.9 response.
+ # empty version will cause next test to fail.
version = ""
if not version.startswith("HTTP/"):
- if self.strict:
- self.close()
- raise BadStatusLine(line)
- else:
- # Assume it's a Simple-Response from an 0.9 server.
- # We have to convert the first line back to raw bytes
- # because self.fp.readline() needs to return bytes.
- self.fp = LineAndFileWrapper(bytes(line, "ascii"), self.fp)
- return "HTTP/0.9", 200, ""
+ self.close()
+ raise BadStatusLine(line)
# The status code is a three-digit number
try:
@@ -365,22 +359,14 @@ class HTTPResponse(io.RawIOBase):
self.code = self.status = status
self.reason = reason.strip()
- if version == "HTTP/1.0":
+ if version in ("HTTP/1.0", "HTTP/0.9"):
+ # Some servers might still return "0.9", treat it as 1.0 anyway
self.version = 10
elif version.startswith("HTTP/1."):
self.version = 11 # use HTTP/1.1 code for HTTP/1.x where x>=1
- elif version == "HTTP/0.9":
- self.version = 9
else:
raise UnknownProtocol(version)
- if self.version == 9:
- self.length = None
- self.chunked = False
- self.will_close = True
- self.headers = self.msg = email.message_from_string('')
- return
-
self.headers = self.msg = parse_headers(self.fp)
if self.debuglevel > 0:
@@ -651,11 +637,15 @@ class HTTPConnection:
default_port = HTTP_PORT
auto_open = 1
debuglevel = 0
- strict = 0
- def __init__(self, host, port=None, strict=None,
- timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
+ def __init__(self, host, port=None, strict=_strict_sentinel,
+ timeout=socket._GLOBAL_DEFAULT_TIMEOUT, source_address=None):
+ if strict is not _strict_sentinel:
+ warnings.warn("the 'strict' argument isn't supported anymore; "
+ "http.client now always assumes HTTP/1.x compliant servers.",
+ DeprecationWarning, 2)
self.timeout = timeout
+ self.source_address = source_address
self.sock = None
self._buffer = []
self.__response = None
@@ -666,10 +656,13 @@ class HTTPConnection:
self._tunnel_headers = {}
self._set_hostport(host, port)
- if strict is not None:
- self.strict = strict
- def _set_tunnel(self, host, port=None, headers=None):
+ def set_tunnel(self, host, port=None, headers=None):
+ """ Sets up the host and the port for the HTTP CONNECT Tunnelling.
+
+ The headers argument should be a mapping of extra HTTP headers
+ to send with the CONNECT request.
+ """
self._tunnel_host = host
self._tunnel_port = port
if headers:
@@ -704,12 +697,11 @@ class HTTPConnection:
self.send(connect_bytes)
for header, value in self._tunnel_headers.items():
header_str = "%s: %s\r\n" % (header, value)
- header_bytes = header_str.encode("ascii")
+ header_bytes = header_str.encode("latin1")
self.send(header_bytes)
self.send(b'\r\n')
- response = self.response_class(self.sock, strict = self.strict,
- method = self._method)
+ response = self.response_class(self.sock, method=self._method)
(version, code, message) = response._read_status()
if code != 200:
@@ -726,7 +718,7 @@ class HTTPConnection:
def connect(self):
"""Connect to the host and port specified in __init__."""
self.sock = socket.create_connection((self.host,self.port),
- self.timeout)
+ self.timeout, self.source_address)
if self._tunnel_host:
self._tunnel()
@@ -741,18 +733,17 @@ class HTTPConnection:
self.__state = _CS_IDLE
def send(self, data):
- """Send `data' to the server."""
+ """Send `data' to the server.
+ ``data`` can be a string object, a bytes object, an array object, a
+ file-like object that supports a .read() method, or an iterable object.
+ """
+
if self.sock is None:
if self.auto_open:
self.connect()
else:
raise NotConnected()
- # send the data to the server. if we get a broken pipe, then close
- # the socket. we want to reconnect when somebody tries to send again.
- #
- # NOTE: we DO propagate the error, though, because we cannot simply
- # ignore the error... the caller will know if they can retry.
if self.debuglevel > 0:
print("send:", repr(data))
blocksize = 8192
@@ -778,8 +769,16 @@ class HTTPConnection:
if encode:
datablock = datablock.encode("iso-8859-1")
self.sock.sendall(datablock)
- else:
+
+ try:
self.sock.sendall(data)
+ except TypeError:
+ if isinstance(data, collections.Iterable):
+ for d in data:
+ self.sock.sendall(d)
+ else:
+ raise TypeError("data should be a bytes-like object\
+ or an iterable, got %r " % type(it))
def _output(self, s):
"""Add a line of output to the current request buffer.
@@ -938,7 +937,7 @@ class HTTPConnection:
values = list(values)
for i, one_value in enumerate(values):
if hasattr(one_value, 'encode'):
- values[i] = one_value.encode('ascii')
+ values[i] = one_value.encode('latin1')
elif isinstance(one_value, int):
values[i] = str(one_value).encode('ascii')
value = b'\r\n\t'.join(values)
@@ -1040,11 +1039,9 @@ class HTTPConnection:
if self.debuglevel > 0:
response = self.response_class(self.sock, self.debuglevel,
- strict=self.strict,
method=self._method)
else:
- response = self.response_class(self.sock, strict=self.strict,
- method=self._method)
+ response = self.response_class(self.sock, method=self._method)
response.begin()
assert response.will_close != _UNKNOWN
@@ -1069,30 +1066,50 @@ else:
default_port = HTTPS_PORT
+ # XXX Should key_file and cert_file be deprecated in favour of context?
+
def __init__(self, host, port=None, key_file=None, cert_file=None,
- strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
- HTTPConnection.__init__(self, host, port, strict, timeout)
+ strict=_strict_sentinel, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
+ source_address=None, *, context=None, check_hostname=None):
+ super(HTTPSConnection, self).__init__(host, port, strict, timeout,
+ source_address)
self.key_file = key_file
self.cert_file = cert_file
+ if context is None:
+ # Some reasonable defaults
+ context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ context.options |= ssl.OP_NO_SSLv2
+ will_verify = context.verify_mode != ssl.CERT_NONE
+ if check_hostname is None:
+ check_hostname = will_verify
+ elif check_hostname and not will_verify:
+ raise ValueError("check_hostname needs a SSL context with "
+ "either CERT_OPTIONAL or CERT_REQUIRED")
+ if key_file or cert_file:
+ context.load_cert_chain(cert_file, key_file)
+ self._context = context
+ self._check_hostname = check_hostname
def connect(self):
"Connect to a host on a given (SSL) port."
sock = socket.create_connection((self.host, self.port),
- self.timeout)
+ self.timeout, self.source_address)
if self._tunnel_host:
self.sock = sock
self._tunnel()
- self.sock = ssl.wrap_socket(sock, self.key_file, self.cert_file)
-
-
- def FakeSocket (sock, sslobj):
- warnings.warn("FakeSocket is deprecated, and won't be in 3.x. " +
- "Use the result of ssl.wrap_socket() directly instead.",
- DeprecationWarning, stacklevel=2)
- return sslobj
+ server_hostname = self.host if ssl.HAS_SNI else None
+ self.sock = self._context.wrap_socket(sock,
+ server_hostname=server_hostname)
+ try:
+ if self._check_hostname:
+ ssl.match_hostname(self.sock.getpeercert(), self.host)
+ except Exception:
+ self.sock.shutdown(socket.SHUT_RDWR)
+ self.sock.close()
+ raise
__all__.append("HTTPSConnection")
@@ -1146,6 +1163,8 @@ class ResponseNotReady(ImproperConnectionState):
class BadStatusLine(HTTPException):
def __init__(self, line):
+ if not line:
+ line = repr(line)
self.args = line,
self.line = line
@@ -1156,71 +1175,3 @@ class LineTooLong(HTTPException):
# for backwards compatibility
error = HTTPException
-
-class LineAndFileWrapper:
- """A limited file-like object for HTTP/0.9 responses."""
-
- # The status-line parsing code calls readline(), which normally
- # get the HTTP status line. For a 0.9 response, however, this is
- # actually the first line of the body! Clients need to get a
- # readable file object that contains that line.
-
- def __init__(self, line, file):
- self._line = line
- self._file = file
- self._line_consumed = 0
- self._line_offset = 0
- self._line_left = len(line)
-
- def __getattr__(self, attr):
- return getattr(self._file, attr)
-
- def _done(self):
- # called when the last byte is read from the line. After the
- # call, all read methods are delegated to the underlying file
- # object.
- self._line_consumed = 1
- self.read = self._file.read
- self.readline = self._file.readline
- self.readlines = self._file.readlines
-
- def read(self, amt=None):
- if self._line_consumed:
- return self._file.read(amt)
- assert self._line_left
- if amt is None or amt > self._line_left:
- s = self._line[self._line_offset:]
- self._done()
- if amt is None:
- return s + self._file.read()
- else:
- return s + self._file.read(amt - len(s))
- else:
- assert amt <= self._line_left
- i = self._line_offset
- j = i + amt
- s = self._line[i:j]
- self._line_offset = j
- self._line_left -= amt
- if self._line_left == 0:
- self._done()
- return s
-
- def readline(self):
- if self._line_consumed:
- return self._file.readline()
- assert self._line_left
- s = self._line[self._line_offset:]
- self._done()
- return s
-
- def readlines(self, size=None):
- if self._line_consumed:
- return self._file.readlines(size)
- assert self._line_left
- L = [self._line[self._line_offset:]]
- self._done()
- if size is None:
- return L + self._file.readlines()
- else:
- return L + self._file.readlines(size)
diff --git a/Lib/http/cookiejar.py b/Lib/http/cookiejar.py
index 9b0ee80..7f7f55b 100644
--- a/Lib/http/cookiejar.py
+++ b/Lib/http/cookiejar.py
@@ -436,6 +436,13 @@ def join_header_words(lists):
if attr: headers.append("; ".join(attr))
return ", ".join(headers)
+def strip_quotes(text):
+ if text.startswith('"'):
+ text = text[1:]
+ if text.endswith('"'):
+ text = text[:-1]
+ return text
+
def parse_ns_headers(ns_headers):
"""Ad-hoc parser for Netscape protocol cookie-attributes.
@@ -453,7 +460,7 @@ def parse_ns_headers(ns_headers):
"""
known_attrs = ("expires", "domain", "path", "secure",
# RFC 2109 attrs (may turn up in Netscape cookies, too)
- "port", "max-age")
+ "version", "port", "max-age")
result = []
for ns_header in ns_headers:
@@ -473,12 +480,11 @@ def parse_ns_headers(ns_headers):
k = lc
if k == "version":
# This is an RFC 2109 cookie.
+ v = strip_quotes(v)
version_set = True
if k == "expires":
# convert expires date to seconds since epoch
- if v.startswith('"'): v = v[1:]
- if v.endswith('"'): v = v[:-1]
- v = http2time(v) # None if invalid
+ v = http2time(strip_quotes(v)) # None if invalid
pairs.append((k, v))
if pairs:
@@ -1443,7 +1449,11 @@ class CookieJar:
# set the easy defaults
version = standard.get("version", None)
- if version is not None: version = int(version)
+ if version is not None:
+ try:
+ version = int(version)
+ except ValueError:
+ return None # invalid version, ignore cookie
secure = standard.get("secure", False)
# (discard is also set if expires is Absent)
discard = standard.get("discard", False)
diff --git a/Lib/http/cookies.py b/Lib/http/cookies.py
index 0d9e6d0..93da627 100644
--- a/Lib/http/cookies.py
+++ b/Lib/http/cookies.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
#
####
@@ -46,7 +46,7 @@ At the moment, this is the only documentation.
The Basics
----------
-Importing is easy..
+Importing is easy...
>>> from http import cookies
@@ -127,19 +127,14 @@ the value to a string, when the values are set dictionary-style.
'Set-Cookie: number=7\r\nSet-Cookie: string=seven'
Finis.
-""" #"
-# ^
-# |----helps out font-lock
+"""
#
# Import our required modules
#
+import re
import string
-from pickle import dumps, loads
-
-import re, warnings
-
__all__ = ["CookieError", "BaseCookie", "SimpleCookie"]
_nulljoin = ''.join
@@ -231,17 +226,16 @@ _Translator = {
}
def _quote(str, LegalChars=_LegalChars):
- #
- # If the string does not need to be double-quoted,
- # then just return the string. Otherwise, surround
- # the string in doublequotes and precede quote (with a \)
- # special characters.
- #
+ r"""Quote a string for use in a cookie header.
+
+ If the string does not need to be double-quoted, then just return the
+ string. Otherwise, surround the string in doublequotes and quote
+ (with a \) special characters.
+ """
if all(c in LegalChars for c in str):
return str
else:
- return '"' + _nulljoin( map(_Translator.get, str, str) ) + '"'
-# end _quote
+ return '"' + _nulljoin(_Translator.get(s, s) for s in str) + '"'
_OctalPatt = re.compile(r"\\[0-3][0-7][0-7]")
@@ -250,7 +244,7 @@ _QuotePatt = re.compile(r"[\\].")
def _unquote(str):
# If there aren't any doublequotes,
# then there can't be any special characters. See RFC 2109.
- if len(str) < 2:
+ if len(str) < 2:
return str
if str[0] != '"' or str[-1] != '"':
return str
@@ -269,32 +263,32 @@ def _unquote(str):
n = len(str)
res = []
while 0 <= i < n:
- Omatch = _OctalPatt.search(str, i)
- Qmatch = _QuotePatt.search(str, i)
- if not Omatch and not Qmatch: # Neither matched
+ o_match = _OctalPatt.search(str, i)
+ q_match = _QuotePatt.search(str, i)
+ if not o_match and not q_match: # Neither matched
res.append(str[i:])
break
# else:
j = k = -1
- if Omatch: j = Omatch.start(0)
- if Qmatch: k = Qmatch.start(0)
- if Qmatch and ( not Omatch or k < j ): # QuotePatt matched
+ if o_match:
+ j = o_match.start(0)
+ if q_match:
+ k = q_match.start(0)
+ if q_match and (not o_match or k < j): # QuotePatt matched
res.append(str[i:k])
res.append(str[k+1])
- i = k+2
+ i = k + 2
else: # OctalPatt matched
res.append(str[i:j])
- res.append( chr( int(str[j+1:j+4], 8) ) )
- i = j+4
+ res.append(chr(int(str[j+1:j+4], 8)))
+ i = j + 4
return _nulljoin(res)
-# end _unquote
-
-# The _getdate() routine is used to set the expiration time in
-# the cookie's HTTP header. By default, _getdate() returns the
-# current time in the appropriate "expires" format for a
-# Set-Cookie header. The one optional argument is an offset from
-# now, in seconds. For example, an offset of -3600 means "one hour ago".
-# The offset may be a floating point number.
+
+# The _getdate() routine is used to set the expiration time in the cookie's HTTP
+# header. By default, _getdate() returns the current time in the appropriate
+# "expires" format for a Set-Cookie header. The one optional argument is an
+# offset from now, in seconds. For example, an offset of -3600 means "one hour
+# ago". The offset may be a floating point number.
#
_weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
@@ -311,18 +305,15 @@ def _getdate(future=0, weekdayname=_weekdayname, monthname=_monthname):
(weekdayname[wd], day, monthname[month], year, hh, mm, ss)
-#
-# A class to hold ONE key,value pair.
-# In a cookie, each such pair may have several attributes.
-# so this class is used to keep the attributes associated
-# with the appropriate key,value pair.
-# This class also includes a coded_value attribute, which
-# is used to hold the network representation of the
-# value. This is most useful when Python objects are
-# pickled for network transit.
-#
-
class Morsel(dict):
+ """A class to hold ONE (key, value) pair.
+
+ In a cookie, each such pair may have several attributes, so this class is
+ used to keep the attributes associated with the appropriate key,value pair.
+ This class also includes a coded_value attribute, which is used to hold
+ the network representation of the value. This is most useful when Python
+ objects are pickled for network transit.
+ """
# RFC 2109 lists these attributes as reserved:
# path comment domain
# max-age secure version
@@ -336,35 +327,33 @@ class Morsel(dict):
# This dictionary provides a mapping from the lowercase
# variant on the left to the appropriate traditional
# formatting on the right.
- _reserved = { "expires" : "expires",
- "path" : "Path",
- "comment" : "Comment",
- "domain" : "Domain",
- "max-age" : "Max-Age",
- "secure" : "secure",
- "httponly" : "httponly",
- "version" : "Version",
- }
+ _reserved = {
+ "expires" : "expires",
+ "path" : "Path",
+ "comment" : "Comment",
+ "domain" : "Domain",
+ "max-age" : "Max-Age",
+ "secure" : "secure",
+ "httponly" : "httponly",
+ "version" : "Version",
+ }
def __init__(self):
# Set defaults
self.key = self.value = self.coded_value = None
# Set default attributes
- for K in self._reserved:
- dict.__setitem__(self, K, "")
- # end __init__
+ for key in self._reserved:
+ dict.__setitem__(self, key, "")
def __setitem__(self, K, V):
K = K.lower()
if not K in self._reserved:
raise CookieError("Invalid Attribute %s" % K)
dict.__setitem__(self, K, V)
- # end __setitem__
def isReservedKey(self, K):
return K.lower() in self._reserved
- # end isReservedKey
def set(self, key, val, coded_val, LegalChars=_LegalChars):
# First we verify that the key isn't a reserved word
@@ -375,19 +364,18 @@ class Morsel(dict):
raise CookieError("Illegal key value: %s" % key)
# It's a good key, so save it.
- self.key = key
- self.value = val
- self.coded_value = coded_val
- # end set
+ self.key = key
+ self.value = val
+ self.coded_value = coded_val
- def output(self, attrs=None, header = "Set-Cookie:"):
- return "%s %s" % ( header, self.OutputString(attrs) )
+ def output(self, attrs=None, header="Set-Cookie:"):
+ return "%s %s" % (header, self.OutputString(attrs))
__str__ = output
def __repr__(self):
return '<%s: %s=%s>' % (self.__class__.__name__,
- self.key, repr(self.value) )
+ self.key, repr(self.value))
def js_output(self, attrs=None):
# Print javascript
@@ -397,41 +385,39 @@ class Morsel(dict):
document.cookie = \"%s\";
// end hiding -->
</script>
- """ % ( self.OutputString(attrs).replace('"',r'\"'))
- # end js_output()
+ """ % (self.OutputString(attrs).replace('"', r'\"'))
def OutputString(self, attrs=None):
# Build up our result
#
result = []
- RA = result.append
+ append = result.append
# First, the key=value pair
- RA("%s=%s" % (self.key, self.coded_value))
+ append("%s=%s" % (self.key, self.coded_value))
# Now add any defined attributes
if attrs is None:
attrs = self._reserved
items = sorted(self.items())
- for K,V in items:
- if V == "": continue
- if K not in attrs: continue
- if K == "expires" and type(V) == type(1):
- RA("%s=%s" % (self._reserved[K], _getdate(V)))
- elif K == "max-age" and type(V) == type(1):
- RA("%s=%d" % (self._reserved[K], V))
- elif K == "secure":
- RA(str(self._reserved[K]))
- elif K == "httponly":
- RA(str(self._reserved[K]))
+ for key, value in items:
+ if value == "":
+ continue
+ if key not in attrs:
+ continue
+ if key == "expires" and isinstance(value, int):
+ append("%s=%s" % (self._reserved[key], _getdate(value)))
+ elif key == "max-age" and isinstance(value, int):
+ append("%s=%d" % (self._reserved[key], value))
+ elif key == "secure":
+ append(str(self._reserved[key]))
+ elif key == "httponly":
+ append(str(self._reserved[key]))
else:
- RA("%s=%s" % (self._reserved[K], V))
+ append("%s=%s" % (self._reserved[key], value))
# Return the result
return _semispacejoin(result)
- # end OutputString
-# end Morsel class
-
#
@@ -461,13 +447,11 @@ _CookiePattern = re.compile(r"""
""", re.ASCII) # May be removed if safe.
-# At long last, here is the cookie class.
-# Using this class is almost just like using a dictionary.
-# See this module's docstring for example usage.
+# At long last, here is the cookie class. Using this class is almost just like
+# using a dictionary. See this module's docstring for example usage.
#
class BaseCookie(dict):
- # A container class for a set of Morsels
- #
+ """A container class for a set of Morsels."""
def value_decode(self, val):
"""real_value, coded_value = value_decode(STRING)
@@ -477,7 +461,6 @@ class BaseCookie(dict):
Override this function to modify the behavior of cookies.
"""
return val, val
- # end value_encode
def value_encode(self, val):
"""real_value, coded_value = value_encode(VALUE)
@@ -487,51 +470,46 @@ class BaseCookie(dict):
"""
strval = str(val)
return strval, strval
- # end value_encode
def __init__(self, input=None):
- if input: self.load(input)
- # end __init__
+ if input:
+ self.load(input)
def __set(self, key, real_value, coded_value):
"""Private method for setting a cookie's value"""
M = self.get(key, Morsel())
M.set(key, real_value, coded_value)
dict.__setitem__(self, key, M)
- # end __set
def __setitem__(self, key, value):
"""Dictionary style assignment."""
rval, cval = self.value_encode(value)
self.__set(key, rval, cval)
- # end __setitem__
def output(self, attrs=None, header="Set-Cookie:", sep="\015\012"):
"""Return a string suitable for HTTP."""
result = []
items = sorted(self.items())
- for K,V in items:
- result.append( V.output(attrs, header) )
+ for key, value in items:
+ result.append(value.output(attrs, header))
return sep.join(result)
- # end output
__str__ = output
def __repr__(self):
- L = []
+ l = []
items = sorted(self.items())
- for K,V in items:
- L.append( '%s=%s' % (K,repr(V.value) ) )
- return '<%s: %s>' % (self.__class__.__name__, _spacejoin(L))
+ for key, value in items:
+ l.append('%s=%s' % (key, repr(value.value)))
+ return '<%s: %s>' % (self.__class__.__name__, _spacejoin(l))
def js_output(self, attrs=None):
"""Return a string suitable for JavaScript."""
result = []
items = sorted(self.items())
- for K,V in items:
- result.append( V.js_output(attrs) )
+ for key, value in items:
+ result.append(value.js_output(attrs))
return _nulljoin(result)
- # end js_output
def load(self, rawdata):
"""Load cookies from a string (presumably HTTP_COOKIE) or
@@ -539,16 +517,15 @@ class BaseCookie(dict):
is equivalent to calling:
map(Cookie.__setitem__, d.keys(), d.values())
"""
- if type(rawdata) == type(""):
- self.__ParseString(rawdata)
+ if isinstance(rawdata, str):
+ self.__parse_string(rawdata)
else:
# self.update() wouldn't call our custom __setitem__
- for k, v in rawdata.items():
- self[k] = v
+ for key, value in rawdata.items():
+ self[key] = value
return
- # end load()
- def __ParseString(self, str, patt=_CookiePattern):
+ def __parse_string(self, str, patt=_CookiePattern):
i = 0 # Our starting point
n = len(str) # Length of string
M = None # current morsel
@@ -556,48 +533,39 @@ class BaseCookie(dict):
while 0 <= i < n:
# Start looking for a cookie
match = patt.search(str, i)
- if not match: break # No more cookies
+ if not match:
+ # No more cookies
+ break
- K,V = match.group("key"), match.group("val")
+ key, value = match.group("key"), match.group("val")
i = match.end(0)
# Parse the key, value in case it's metainfo
- if K[0] == "$":
+ if key[0] == "$":
# We ignore attributes which pertain to the cookie
# mechanism as a whole. See RFC 2109.
# (Does anyone care?)
if M:
- M[ K[1:] ] = V
- elif K.lower() in Morsel._reserved:
+ M[key[1:]] = value
+ elif key.lower() in Morsel._reserved:
if M:
- M[ K ] = _unquote(V)
+ M[key] = _unquote(value)
else:
- rval, cval = self.value_decode(V)
- self.__set(K, rval, cval)
- M = self[K]
- # end __ParseString
-# end BaseCookie class
+ rval, cval = self.value_decode(value)
+ self.__set(key, rval, cval)
+ M = self[key]
+
class SimpleCookie(BaseCookie):
- """SimpleCookie
+ """
SimpleCookie supports strings as cookie values. When setting
the value using the dictionary assignment notation, SimpleCookie
calls the builtin str() to convert the value to a string. Values
received from HTTP are kept as strings.
"""
def value_decode(self, val):
- return _unquote( val ), val
+ return _unquote(val), val
+
def value_encode(self, val):
strval = str(val)
- return strval, _quote( strval )
-# end SimpleCookie
-
-#
-###########################################################
-
-def _test():
- import doctest, http.cookies
- return doctest.testmod(http.cookies)
-
-if __name__ == "__main__":
- _test()
+ return strval, _quote(strval)
diff --git a/Lib/http/server.py b/Lib/http/server.py
index 8de604a..543abe0 100644
--- a/Lib/http/server.py
+++ b/Lib/http/server.py
@@ -84,7 +84,7 @@ __version__ = "0.6"
__all__ = ["HTTPServer", "BaseHTTPRequestHandler"]
-import cgi
+import html
import email.message
import email.parser
import http.client
@@ -327,6 +327,30 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
elif (conntype.lower() == 'keep-alive' and
self.protocol_version >= "HTTP/1.1"):
self.close_connection = 0
+ # Examine the headers and look for an Expect directive
+ expect = self.headers.get('Expect', "")
+ if (expect.lower() == "100-continue" and
+ self.protocol_version >= "HTTP/1.1" and
+ self.request_version >= "HTTP/1.1"):
+ if not self.handle_expect_100():
+ return False
+ return True
+
+ def handle_expect_100(self):
+ """Decide what to do with an "Expect: 100-continue" header.
+
+ If the client is expecting a 100 Continue response, we must
+ respond with either a 100 Continue or a final response before
+ waiting for the request body. The default is to always respond
+ with a 100 Continue. You can behave differently (for example,
+ reject unauthorized requests) by overriding this method.
+
+ This method should either return True (possibly after sending
+ a 100 Continue response) or send an error response and return
+ False.
+
+ """
+ self.send_response_only(100)
return True
def handle_one_request(self):
@@ -337,24 +361,32 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
commands such as GET and POST.
"""
- self.raw_requestline = self.rfile.readline(65537)
- if len(self.raw_requestline) > 65536:
- self.requestline = ''
- self.request_version = ''
- self.command = ''
- self.send_error(414)
- return
- if not self.raw_requestline:
+ try:
+ self.raw_requestline = self.rfile.readline(65537)
+ if len(self.raw_requestline) > 65536:
+ self.requestline = ''
+ self.request_version = ''
+ self.command = ''
+ self.send_error(414)
+ return
+ if not self.raw_requestline:
+ self.close_connection = 1
+ return
+ if not self.parse_request():
+ # An error code has been sent, just exit
+ return
+ mname = 'do_' + self.command
+ if not hasattr(self, mname):
+ self.send_error(501, "Unsupported method (%r)" % self.command)
+ return
+ method = getattr(self, mname)
+ method()
+ self.wfile.flush() #actually send the response if not already done.
+ except socket.timeout as e:
+ #a read or a write timed out. Discard this connection
+ self.log_error("Request timed out: %r", e)
self.close_connection = 1
return
- if not self.parse_request(): # An error code has been sent, just exit
- return
- mname = 'do_' + self.command
- if not hasattr(self, mname):
- self.send_error(501, "Unsupported method (%r)" % self.command)
- return
- method = getattr(self, mname)
- method()
def handle(self):
"""Handle multiple requests if necessary."""
@@ -403,6 +435,12 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
"""
self.log_request(code)
+ self.send_response_only(code, message)
+ self.send_header('Server', self.version_string())
+ self.send_header('Date', self.date_time_string())
+
+ def send_response_only(self, code, message=None):
+ """Send the response header only."""
if message is None:
if code in self.responses:
message = self.responses[code][0]
@@ -410,15 +448,15 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
message = ''
if self.request_version != 'HTTP/0.9':
self.wfile.write(("%s %d %s\r\n" %
- (self.protocol_version, code, message)).encode('ASCII', 'strict'))
- # print (self.protocol_version, code, message)
- self.send_header('Server', self.version_string())
- self.send_header('Date', self.date_time_string())
+ (self.protocol_version, code, message)).encode('latin1', 'strict'))
def send_header(self, keyword, value):
"""Send a MIME header."""
if self.request_version != 'HTTP/0.9':
- self.wfile.write(("%s: %s\r\n" % (keyword, value)).encode('ASCII', 'strict'))
+ if not hasattr(self, '_headers_buffer'):
+ self._headers_buffer = []
+ self._headers_buffer.append(
+ ("%s: %s\r\n" % (keyword, value)).encode('latin1', 'strict'))
if keyword.lower() == 'connection':
if value.lower() == 'close':
@@ -429,7 +467,9 @@ class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
def end_headers(self):
"""Send the blank line ending the MIME headers."""
if self.request_version != 'HTTP/0.9':
- self.wfile.write(b"\r\n")
+ self._headers_buffer.append(b"\r\n")
+ self.wfile.write(b"".join(self._headers_buffer))
+ self._headers_buffer = []
def log_request(self, code='-', size='-'):
"""Log an accepted request.
@@ -680,7 +720,7 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
return None
list.sort(key=lambda a: a.lower())
r = []
- displaypath = cgi.escape(urllib.parse.unquote(self.path))
+ displaypath = html.escape(urllib.parse.unquote(self.path))
r.append('<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">')
r.append("<html>\n<title>Directory listing for %s</title>\n" % displaypath)
r.append("<body>\n<h2>Directory listing for %s</h2>\n" % displaypath)
@@ -696,7 +736,7 @@ class SimpleHTTPRequestHandler(BaseHTTPRequestHandler):
displayname = name + "@"
# Note: a link to a directory displays with @ and links with /
r.append('<li><a href="%s">%s</a>\n'
- % (urllib.parse.quote(linkname), cgi.escape(displayname)))
+ % (urllib.parse.quote(linkname), html.escape(displayname)))
r.append("</ul>\n<hr>\n</body>\n</html>\n")
enc = sys.getfilesystemencoding()
encoded = ''.join(r).encode(enc)
@@ -838,7 +878,7 @@ def nobody_uid():
try:
nobody = pwd.getpwnam('nobody')[2]
except KeyError:
- nobody = 1 + max(map(lambda x: x[2], pwd.getpwall()))
+ nobody = 1 + max(x[2] for x in pwd.getpwall())
return nobody
diff --git a/Lib/idlelib/Bindings.py b/Lib/idlelib/Bindings.py
index 74a93d3..ec2720b 100644
--- a/Lib/idlelib/Bindings.py
+++ b/Lib/idlelib/Bindings.py
@@ -98,14 +98,6 @@ if macosxSupport.runningAsOSXApp():
# menu
del menudefs[-1][1][0:2]
- menudefs.insert(0,
- ('application', [
- ('About IDLE', '<<about-idle>>'),
- None,
- ('_Preferences....', '<<open-config-dialog>>'),
- ]))
-
-
default_keydefs = idleConf.GetCurrentKeySet()
del sys
diff --git a/Lib/idlelib/EditorWindow.py b/Lib/idlelib/EditorWindow.py
index 173fad9..a37cb1d 100644
--- a/Lib/idlelib/EditorWindow.py
+++ b/Lib/idlelib/EditorWindow.py
@@ -3,7 +3,6 @@ import os
import re
import string
import imp
-from itertools import count
from tkinter import *
import tkinter.simpledialog as tkSimpleDialog
import tkinter.messagebox as tkMessageBox
@@ -388,7 +387,7 @@ class EditorWindow(object):
underline, label = prepstr(label)
menudict[name] = menu = Menu(mbar, name=name)
mbar.add_cascade(label=label, menu=menu, underline=underline)
- if macosxSupport.runningAsOSXApp():
+ if macosxSupport.isCarbonAquaTk(self.root):
# Insert the application menu
menudict['application'] = menu = Menu(mbar, name='apple')
mbar.add_cascade(label='IDLE', menu=menu)
@@ -802,8 +801,8 @@ class EditorWindow(object):
for instance in self.top.instance_dict:
menu = instance.recent_files_menu
menu.delete(1, END) # clear, and rebuild:
- for i, file in zip(count(), rf_list):
- file_name = file[0:-1] # zap \n
+ for i, file_name in enumerate(rf_list):
+ file_name = file_name.rstrip() # zap \n
# make unicode string to display non-ASCII chars correctly
ufile_name = self._filename_to_unicode(file_name)
callback = instance.__recent_file_callback(file_name)
@@ -1547,7 +1546,12 @@ keynames = {
def get_accelerator(keydefs, eventname):
keylist = keydefs.get(eventname)
- if not keylist:
+ # issue10940: temporary workaround to prevent hang with OS X Cocoa Tk 8.5
+ # if not keylist:
+ if (not keylist) or (macosxSupport.runningAsOSXApp() and eventname in {
+ "<<open-module>>",
+ "<<goto-line>>",
+ "<<change-indentwidth>>"}):
return ""
s = keylist[0]
s = re.sub(r"-[a-z]\b", lambda m: m.group().upper(), s)
diff --git a/Lib/idlelib/NEWS.txt b/Lib/idlelib/NEWS.txt
index 24629c1..037b02a 100644
--- a/Lib/idlelib/NEWS.txt
+++ b/Lib/idlelib/NEWS.txt
@@ -1,7 +1,7 @@
-What's New in IDLE 3.1b1?
+What's New in IDLE 3.1?
=========================
-*Release date: XX-XXX-09*
+*Release date: 27-Jun-09*
- Use of 'filter' in keybindingDialog.py was causing custom key assignment to
fail. Patch 5707 amaury.forgeotdarc.
diff --git a/Lib/idlelib/PyShell.py b/Lib/idlelib/PyShell.py
index 0fa3d76..06c8bba 100644
--- a/Lib/idlelib/PyShell.py
+++ b/Lib/idlelib/PyShell.py
@@ -1,4 +1,4 @@
-#! /usr/bin/env python
+#! /usr/bin/env python3
import os
import os.path
@@ -55,20 +55,21 @@ except ImportError:
else:
def idle_showwarning(message, category, filename, lineno,
file=None, line=None):
- file = warning_stream
+ if file is None:
+ file = warning_stream
try:
- file.write(warnings.formatwarning(message, category, filename,\
+ file.write(warnings.formatwarning(message, category, filename,
lineno, file=file, line=line))
except IOError:
pass ## file (probably __stderr__) is invalid, warning dropped.
warnings.showwarning = idle_showwarning
- def idle_formatwarning(message, category, filename, lineno,
- file=None, line=None):
+ def idle_formatwarning(message, category, filename, lineno, line=None):
"""Format warnings the IDLE way"""
s = "\nWarning (from warnings module):\n"
s += ' File \"%s\", line %s\n' % (filename, lineno)
- line = linecache.getline(filename, lineno).strip() \
- if line is None else line
+ if line is None:
+ line = linecache.getline(filename, lineno)
+ line = line.strip()
if line:
s += " %s\n" % line
s += "%s: %s\n>>> " % (category.__name__, message)
@@ -81,18 +82,17 @@ def extended_linecache_checkcache(filename=None,
Rather than repeating the linecache code, patch it to save the
<pyshell#...> entries, call the original linecache.checkcache()
- (which destroys them), and then restore the saved entries.
+ (skipping them), and then restore the saved entries.
orig_checkcache is bound at definition time to the original
method, allowing it to be patched.
-
"""
cache = linecache.cache
save = {}
- for filename in cache:
- if filename[:1] + filename[-1:] == '<>':
- save[filename] = cache[filename]
- orig_checkcache()
+ for key in list(cache):
+ if key[:1] + key[-1:] == '<>':
+ save[key] = cache.pop(key)
+ orig_checkcache(filename)
cache.update(save)
# Patch linecache.checkcache():
@@ -1417,6 +1417,13 @@ def main():
shell.interp.prepend_syspath(script)
shell.interp.execfile(script)
+ # Check for problematic OS X Tk versions and print a warning message
+ # in the IDLE shell window; this is less intrusive than always opening
+ # a separate window.
+ tkversionwarning = macosxSupport.tkVersionWarning(root)
+ if tkversionwarning:
+ shell.interp.runcommand(''.join(("print('", tkversionwarning, "')")))
+
root.mainloop()
root.destroy()
diff --git a/Lib/idlelib/idlever.py b/Lib/idlelib/idlever.py
index 8ee61a2..5b0907e 100644
--- a/Lib/idlelib/idlever.py
+++ b/Lib/idlelib/idlever.py
@@ -1 +1 @@
-IDLE_VERSION = "3.1.3"
+IDLE_VERSION = "3.2"
diff --git a/Lib/idlelib/macosxSupport.py b/Lib/idlelib/macosxSupport.py
index da519f7..f93ef11 100644
--- a/Lib/idlelib/macosxSupport.py
+++ b/Lib/idlelib/macosxSupport.py
@@ -4,6 +4,10 @@ GUI application (as opposed to an X11 application).
"""
import sys
import tkinter
+from os import path
+
+
+_appbundle = None
def runningAsOSXApp():
"""
@@ -11,7 +15,41 @@ def runningAsOSXApp():
If so, assume that Python was built with Aqua Tcl/Tk rather than
X11 Tcl/Tk.
"""
- return (sys.platform == 'darwin' and '.app' in sys.executable)
+ global _appbundle
+ if _appbundle is None:
+ _appbundle = (sys.platform == 'darwin' and '.app' in sys.executable)
+ return _appbundle
+
+_carbonaquatk = None
+
+def isCarbonAquaTk(root):
+ """
+ Returns True if IDLE is using a Carbon Aqua Tk (instead of the
+ newer Cocoa Aqua Tk).
+ """
+ global _carbonaquatk
+ if _carbonaquatk is None:
+ _carbonaquatk = (runningAsOSXApp() and
+ 'aqua' in root.tk.call('tk', 'windowingsystem') and
+ 'AppKit' not in root.tk.call('winfo', 'server', '.'))
+ return _carbonaquatk
+
+def tkVersionWarning(root):
+ """
+ Returns a string warning message if the Tk version in use appears to
+ be one known to cause problems with IDLE. The Apple Cocoa-based Tk 8.5
+ that was shipped with Mac OS X 10.6.
+ """
+
+ if (runningAsOSXApp() and
+ ('AppKit' in root.tk.call('winfo', 'server', '.')) and
+ (root.tk.call('info', 'patchlevel') == '8.5.7') ):
+ return (r"WARNING: The version of Tcl/Tk (8.5.7) in use may"
+ r" be unstable.\n"
+ r"Visit http://www.python.org/download/mac/tcltk/"
+ r" for current information.")
+ else:
+ return False
def addOpenEventSupport(root, flist):
"""
@@ -73,9 +111,6 @@ def overrideRootMenu(root, flist):
WindowList.add_windows_to_menu(menu)
WindowList.register_callback(postwindowsmenu)
- menudict['application'] = menu = Menu(menubar, name='apple')
- menubar.add_cascade(label='IDLE', menu=menu)
-
def about_dialog(event=None):
from idlelib import aboutDialog
aboutDialog.AboutDialog(root, 'About IDLE')
@@ -91,9 +126,14 @@ def overrideRootMenu(root, flist):
root.instance_dict = flist.inversedict
configDialog.ConfigDialog(root, 'Settings')
+ def help_dialog(event=None):
+ from idlelib import textView
+ fn = path.join(path.abspath(path.dirname(__file__)), 'help.txt')
+ textView.view_file(root, 'Help', fn)
root.bind('<<about-idle>>', about_dialog)
root.bind('<<open-config-dialog>>', config_dialog)
+ root.createcommand('::tk::mac::ShowPreferences', config_dialog)
if flist:
root.bind('<<close-all-windows>>', flist.close_all_callback)
@@ -102,35 +142,29 @@ def overrideRootMenu(root, flist):
# right thing for now.
root.createcommand('exit', flist.close_all_callback)
-
- ###check if Tk version >= 8.4.14; if so, use hard-coded showprefs binding
- tkversion = root.tk.eval('info patchlevel')
- # Note: we cannot check if the string tkversion >= '8.4.14', because
- # the string '8.4.7' is greater than the string '8.4.14'.
- if tuple(map(int, tkversion.split('.'))) >= (8, 4, 14):
- Bindings.menudefs[0] = ('application', [
+ if isCarbonAquaTk(root):
+ # for Carbon AquaTk, replace the default Tk apple menu
+ menudict['application'] = menu = Menu(menubar, name='apple')
+ menubar.add_cascade(label='IDLE', menu=menu)
+ Bindings.menudefs.insert(0,
+ ('application', [
('About IDLE', '<<about-idle>>'),
- None,
- ])
- root.createcommand('::tk::mac::ShowPreferences', config_dialog)
+ None,
+ ]))
+ tkversion = root.tk.eval('info patchlevel')
+ if tuple(map(int, tkversion.split('.'))) < (8, 4, 14):
+ # for earlier AquaTk versions, supply a Preferences menu item
+ Bindings.menudefs[0][1].append(
+ ('_Preferences....', '<<open-config-dialog>>'),
+ )
else:
- for mname, entrylist in Bindings.menudefs:
- menu = menudict.get(mname)
- if not menu:
- continue
- else:
- for entry in entrylist:
- if not entry:
- menu.add_separator()
- else:
- label, eventname = entry
- underline, label = prepstr(label)
- accelerator = get_accelerator(Bindings.default_keydefs,
- eventname)
- def command(text=root, eventname=eventname):
- text.event_generate(eventname)
- menu.add_command(label=label, underline=underline,
- command=command, accelerator=accelerator)
+ # assume Cocoa AquaTk
+ # replace default About dialog with About IDLE one
+ root.createcommand('tkAboutDialog', about_dialog)
+ # replace default "Help" item in Help menu
+ root.createcommand('::tk::mac::ShowHelp', help_dialog)
+ # remove redundant "IDLE Help" from menu
+ del Bindings.menudefs[-1][1][0]
def setupApp(root, flist):
"""
diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py
index fd2cc09..25338ff 100644
--- a/Lib/idlelib/run.py
+++ b/Lib/idlelib/run.py
@@ -25,12 +25,13 @@ except ImportError:
pass
else:
def idle_formatwarning_subproc(message, category, filename, lineno,
- file=None, line=None):
+ line=None):
"""Format warnings the IDLE way"""
s = "\nWarning (from warnings module):\n"
s += ' File \"%s\", line %s\n' % (filename, lineno)
- line = linecache.getline(filename, lineno).strip() \
- if line is None else line
+ if line is None:
+ line = linecache.getline(filename, lineno)
+ line = line.strip()
if line:
s += " %s\n" % line
s += "%s: %s\n" % (category.__name__, message)
diff --git a/Lib/imaplib.py b/Lib/imaplib.py
index fc1e20a..1022e77 100644
--- a/Lib/imaplib.py
+++ b/Lib/imaplib.py
@@ -24,6 +24,12 @@ __version__ = "2.58"
import binascii, errno, random, re, socket, subprocess, sys, time
+try:
+ import ssl
+ HAVE_SSL = True
+except ImportError:
+ HAVE_SSL = False
+
__all__ = ["IMAP4", "IMAP4_stream", "Internaldate2tuple",
"Int2AP", "ParseFlags", "Time2Internaldate"]
@@ -71,6 +77,7 @@ Commands = {
'SETANNOTATION':('AUTH', 'SELECTED'),
'SETQUOTA': ('AUTH', 'SELECTED'),
'SORT': ('SELECTED',),
+ 'STARTTLS': ('NONAUTH',),
'STATUS': ('AUTH', 'SELECTED'),
'STORE': ('SELECTED',),
'SUBSCRIBE': ('AUTH', 'SELECTED'),
@@ -156,11 +163,23 @@ class IMAP4:
self.continuation_response = '' # Last continuation response
self.is_readonly = False # READ-ONLY desired state
self.tagnum = 0
+ self._tls_established = False
# Open socket to server.
self.open(host, port)
+ try:
+ self._connect()
+ except Exception:
+ try:
+ self.shutdown()
+ except socket.error:
+ pass
+ raise
+
+
+ def _connect(self):
# Create unique tag for this session,
# and compile tagged response matcher.
@@ -188,13 +207,7 @@ class IMAP4:
else:
raise self.error(self.welcome)
- typ, dat = self.capability()
- if dat == [None]:
- raise self.error('no CAPABILITY response from server')
- dat = str(dat[-1], "ASCII")
- dat = dat.upper()
- self.capabilities = tuple(dat.split())
-
+ self._get_capabilities()
if __debug__:
if self.debug >= 3:
self._mesg('CAPABILITIES: %r' % (self.capabilities,))
@@ -711,6 +724,30 @@ class IMAP4:
return self._untagged_response(typ, dat, name)
+ def starttls(self, ssl_context=None):
+ name = 'STARTTLS'
+ if not HAVE_SSL:
+ raise self.error('SSL support missing')
+ if self._tls_established:
+ raise self.abort('TLS session already established')
+ if name not in self.capabilities:
+ raise self.abort('TLS not supported by server')
+ # Generate a default SSL context if none was passed.
+ if ssl_context is None:
+ ssl_context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
+ # SSLv2 considered harmful.
+ ssl_context.options |= ssl.OP_NO_SSLv2
+ typ, dat = self._simple_command(name)
+ if typ == 'OK':
+ self.sock = ssl_context.wrap_socket(self.sock)
+ self.file = self.sock.makefile('rb')
+ self._tls_established = True
+ self._get_capabilities()
+ else:
+ raise self.error("Couldn't establish TLS session")
+ return self._untagged_response(typ, dat, name)
+
+
def status(self, mailbox, names):
"""Request named status conditions for mailbox.
@@ -921,6 +958,15 @@ class IMAP4:
return typ, data
+ def _get_capabilities(self):
+ typ, dat = self.capability()
+ if dat == [None]:
+ raise self.error('no CAPABILITY response from server')
+ dat = str(dat[-1], "ASCII")
+ dat = dat.upper()
+ self.capabilities = tuple(dat.split())
+
+
def _get_response(self):
# Read response and store.
@@ -1125,12 +1171,8 @@ class IMAP4:
n -= 1
+if HAVE_SSL:
-try:
- import ssl
-except ImportError:
- pass
-else:
class IMAP4_SSL(IMAP4):
"""IMAP4 client class over SSL connection
@@ -1270,9 +1312,10 @@ Mon2num = {b'Jan': 1, b'Feb': 2, b'Mar': 3, b'Apr': 4, b'May': 5, b'Jun': 6,
b'Jul': 7, b'Aug': 8, b'Sep': 9, b'Oct': 10, b'Nov': 11, b'Dec': 12}
def Internaldate2tuple(resp):
- """Convert IMAP4 INTERNALDATE to UT.
+ """Parse an IMAP4 INTERNALDATE string.
- Returns Python time module tuple.
+ Return corresponding local time. The return value is a
+ time.struct_time tuple or None if the string has wrong format.
"""
mo = InternalDate.match(resp)
@@ -1339,9 +1382,14 @@ def ParseFlags(resp):
def Time2Internaldate(date_time):
- """Convert 'date_time' to IMAP4 INTERNALDATE representation.
+ """Convert date_time to IMAP4 INTERNALDATE representation.
- Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'
+ Return string in form: '"DD-Mmm-YYYY HH:MM:SS +HHMM"'. The
+ date_time argument can be a number (int or float) represening
+ seconds since epoch (as returned by time.time()), a 9-tuple
+ representing local time (as returned by time.localtime()), or a
+ double-quoted string. In the last case, it is assumed to already
+ be in the correct format.
"""
if isinstance(date_time, (int, float)):
diff --git a/Lib/importlib/__init__.py b/Lib/importlib/__init__.py
index 37577ff..2baaf93 100644
--- a/Lib/importlib/__init__.py
+++ b/Lib/importlib/__init__.py
@@ -36,7 +36,7 @@ def _case_ok(directory, check):
"""
if 'PYTHONCASEOK' in os.environ:
return True
- elif check in os.listdir(directory):
+ elif check in os.listdir(directory if directory else os.getcwd()):
return True
return False
diff --git a/Lib/importlib/_bootstrap.py b/Lib/importlib/_bootstrap.py
index 6f60843..425b8bf 100644
--- a/Lib/importlib/_bootstrap.py
+++ b/Lib/importlib/_bootstrap.py
@@ -22,7 +22,7 @@ work. One should use importlib as the public-facing version of this module.
def _path_join(*args):
"""Replacement for os.path.join."""
return path_sep.join(x[:-len(path_sep)] if x.endswith(path_sep) else x
- for x in args)
+ for x in args if x)
def _path_exists(path):
@@ -53,6 +53,8 @@ def _path_isfile(path):
# XXX Could also expose Modules/getpath.c:isdir()
def _path_isdir(path):
"""Replacement for os.path.isdir."""
+ if not path:
+ path = _os.getcwd()
return _path_is_mode_type(path, 0o040000)
@@ -78,20 +80,6 @@ def _path_absolute(path):
return _path_join(_os.getcwd(), path)
-class _closing:
-
- """Simple replacement for contextlib.closing."""
-
- def __init__(self, obj):
- self.obj = obj
-
- def __enter__(self):
- return self.obj
-
- def __exit__(self, *args):
- self.obj.close()
-
-
def _wrap(new, old):
"""Simple substitute for functools.wraps."""
for replace in ['__module__', '__name__', '__doc__']:
@@ -99,6 +87,8 @@ def _wrap(new, old):
new.__dict__.update(old.__dict__)
+code_type = type(_wrap.__code__)
+
# Finder/loader utility code ##################################################
def set_package(fxn):
@@ -138,7 +128,7 @@ def module_for_loader(fxn):
the second argument.
"""
- def decorated(self, fullname):
+ def decorated(self, fullname, *args, **kwargs):
module = sys.modules.get(fullname)
is_reload = bool(module)
if not is_reload:
@@ -148,7 +138,7 @@ def module_for_loader(fxn):
module = imp.new_module(fullname)
sys.modules[fullname] = module
try:
- return fxn(self, module)
+ return fxn(self, module, *args, **kwargs)
except:
if not is_reload:
del sys.modules[fullname]
@@ -301,242 +291,238 @@ class FrozenImporter:
return imp.is_frozen_package(fullname)
-class PyLoader:
+class _LoaderBasics:
- """Loader base class for Python source code.
+ """Base class of common code needed by both SourceLoader and
+ _SourcelessFileLoader."""
- Subclasses need to implement the methods:
+ def is_package(self, fullname):
+ """Concrete implementation of InspectLoader.is_package by checking if
+ the path returned by get_filename has a filename of '__init__.py'."""
+ filename = self.get_filename(fullname).rpartition(path_sep)[2]
+ return filename.rsplit('.', 1)[0] == '__init__'
- - source_path
- - get_data
- - is_package
+ def _bytes_from_bytecode(self, fullname, data, source_mtime):
+ """Return the marshalled bytes from bytecode, verifying the magic
+ number and timestamp along the way.
- """
+ If source_mtime is None then skip the timestamp check.
- @module_for_loader
- def load_module(self, module):
- """Load a source module."""
- return self._load_module(module)
+ """
+ magic = data[:4]
+ raw_timestamp = data[4:8]
+ if len(magic) != 4 or magic != imp.get_magic():
+ raise ImportError("bad magic number in {}".format(fullname))
+ elif len(raw_timestamp) != 4:
+ raise EOFError("bad timestamp in {}".format(fullname))
+ elif source_mtime is not None:
+ if marshal._r_long(raw_timestamp) != source_mtime:
+ raise ImportError("bytecode is stale for {}".format(fullname))
+ # Can't return the code object as errors from marshal loading need to
+ # propagate even when source is available.
+ return data[8:]
- def _load_module(self, module):
- """Initialize a module from source."""
+ @module_for_loader
+ def _load_module(self, module, *, sourceless=False):
+ """Helper for load_module able to handle either source or sourceless
+ loading."""
name = module.__name__
- code_object = self.get_code(module.__name__)
- # __file__ may have been set by the caller, e.g. bytecode path.
- if not hasattr(module, '__file__'):
- module.__file__ = self.source_path(name)
+ code_object = self.get_code(name)
+ module.__file__ = self.get_filename(name)
+ if not sourceless:
+ module.__cached__ = imp.cache_from_source(module.__file__)
+ else:
+ module.__cached__ = module.__file__
+ module.__package__ = name
if self.is_package(name):
- module.__path__ = [module.__file__.rsplit(path_sep, 1)[0]]
- module.__package__ = module.__name__
- if not hasattr(module, '__path__'):
+ module.__path__ = [module.__file__.rsplit(path_sep, 1)[0]]
+ else:
module.__package__ = module.__package__.rpartition('.')[0]
module.__loader__ = self
exec(code_object, module.__dict__)
return module
- def get_code(self, fullname):
- """Get a code object from source."""
- source_path = self.source_path(fullname)
- if source_path is None:
- message = "a source path must exist to load {0}".format(fullname)
- raise ImportError(message)
- source = self.get_data(source_path)
- # Convert to universal newlines.
- line_endings = b'\n'
- for index, c in enumerate(source):
- if c == ord(b'\n'):
- break
- elif c == ord(b'\r'):
- line_endings = b'\r'
- try:
- if source[index+1] == ord(b'\n'):
- line_endings += b'\n'
- except IndexError:
- pass
- break
- if line_endings != b'\n':
- source = source.replace(line_endings, b'\n')
- return compile(source, source_path, 'exec', dont_inherit=True)
- # Never use in implementing import! Imports code within the method.
- def get_source(self, fullname):
- """Return the source code for a module.
+class SourceLoader(_LoaderBasics):
- self.source_path() and self.get_data() are used to implement this
- method.
+ def path_mtime(self, path):
+ """Optional method that returns the modification time (an int) for the
+ specified path, where path is a str.
- """
- path = self.source_path(fullname)
- if path is None:
- return None
- try:
- source_bytes = self.get_data(path)
- except IOError:
- return ImportError("source not available through get_data()")
- import io
- import tokenize
- encoding = tokenize.detect_encoding(io.BytesIO(source_bytes).readline)
- return source_bytes.decode(encoding[0])
+ Implementing this method allows the loader to read bytecode files.
+ """
+ raise NotImplementedError
-class PyPycLoader(PyLoader):
+ def set_data(self, path, data):
+ """Optional method which writes data (bytes) to a file path (a str).
- """Loader base class for Python source and bytecode.
+ Implementing this method allows for the writing of bytecode files.
- Requires implementing the methods needed for PyLoader as well as
- source_mtime, bytecode_path, and write_bytecode.
+ """
+ raise NotImplementedError
- """
- @module_for_loader
- def load_module(self, module):
- """Load a module from source or bytecode."""
- name = module.__name__
- source_path = self.source_path(name)
- bytecode_path = self.bytecode_path(name)
- # get_code can worry about no viable paths existing.
- module.__file__ = source_path or bytecode_path
- return self._load_module(module)
+ def get_source(self, fullname):
+ """Concrete implementation of InspectLoader.get_source."""
+ import tokenize
+ path = self.get_filename(fullname)
+ try:
+ source_bytes = self.get_data(path)
+ except IOError:
+ raise ImportError("source not available through get_data()")
+ encoding = tokenize.detect_encoding(_io.BytesIO(source_bytes).readline)
+ newline_decoder = _io.IncrementalNewlineDecoder(None, True)
+ return newline_decoder.decode(source_bytes.decode(encoding[0]))
def get_code(self, fullname):
- """Get a code object from source or bytecode."""
- # XXX Care enough to make sure this call does not happen if the magic
- # number is bad?
- source_timestamp = self.source_mtime(fullname)
- # Try to use bytecode if it is available.
- bytecode_path = self.bytecode_path(fullname)
- if bytecode_path:
- data = self.get_data(bytecode_path)
+ """Concrete implementation of InspectLoader.get_code.
+
+ Reading of bytecode requires path_mtime to be implemented. To write
+ bytecode, set_data must also be implemented.
+
+ """
+ source_path = self.get_filename(fullname)
+ bytecode_path = imp.cache_from_source(source_path)
+ source_mtime = None
+ if bytecode_path is not None:
try:
- magic = data[:4]
- if len(magic) < 4:
- raise ImportError("bad magic number in {}".format(fullname))
- raw_timestamp = data[4:8]
- if len(raw_timestamp) < 4:
- raise EOFError("bad timestamp in {}".format(fullname))
- pyc_timestamp = marshal._r_long(raw_timestamp)
- bytecode = data[8:]
- # Verify that the magic number is valid.
- if imp.get_magic() != magic:
- raise ImportError("bad magic number in {}".format(fullname))
- # Verify that the bytecode is not stale (only matters when
- # there is source to fall back on.
- if source_timestamp:
- if pyc_timestamp < source_timestamp:
- raise ImportError("bytecode is stale")
- except (ImportError, EOFError):
- # If source is available give it a shot.
- if source_timestamp is not None:
+ source_mtime = self.path_mtime(source_path)
+ except NotImplementedError:
+ pass
+ else:
+ try:
+ data = self.get_data(bytecode_path)
+ except IOError:
pass
else:
- raise
- else:
- # Bytecode seems fine, so try to use it.
- # XXX If the bytecode is ill-formed, would it be beneficial to
- # try for using source if available and issue a warning?
- return marshal.loads(bytecode)
- elif source_timestamp is None:
- raise ImportError("no source or bytecode available to create code "
- "object for {0!r}".format(fullname))
- # Use the source.
- code_object = super().get_code(fullname)
- # Generate bytecode and write it out.
- if not sys.dont_write_bytecode:
+ try:
+ bytes_data = self._bytes_from_bytecode(fullname, data,
+ source_mtime)
+ except (ImportError, EOFError):
+ pass
+ else:
+ found = marshal.loads(bytes_data)
+ if isinstance(found, code_type):
+ return found
+ else:
+ msg = "Non-code object in {}"
+ raise ImportError(msg.format(bytecode_path))
+ source_bytes = self.get_data(source_path)
+ code_object = compile(source_bytes, source_path, 'exec',
+ dont_inherit=True)
+ if (not sys.dont_write_bytecode and bytecode_path is not None and
+ source_mtime is not None):
+ # If e.g. Jython ever implements imp.cache_from_source to have
+ # their own cached file format, this block of code will most likely
+ # throw an exception.
data = bytearray(imp.get_magic())
- data.extend(marshal._w_long(source_timestamp))
+ data.extend(marshal._w_long(source_mtime))
data.extend(marshal.dumps(code_object))
- self.write_bytecode(fullname, data)
+ try:
+ self.set_data(bytecode_path, data)
+ except NotImplementedError:
+ pass
return code_object
+ def load_module(self, fullname):
+ """Concrete implementation of Loader.load_module.
-class _PyFileLoader(PyLoader):
+ Requires ExecutionLoader.get_filename and ResourceLoader.get_data to be
+ implemented to load source code. Use of bytecode is dictated by whether
+ get_code uses/writes bytecode.
- """Load a Python source file."""
+ """
+ return self._load_module(fullname)
- def __init__(self, name, path, is_pkg):
- self._name = name
- self._is_pkg = is_pkg
- # Figure out the base path based on whether it was source or bytecode
- # that was found.
- try:
- self._base_path = _path_without_ext(path, imp.PY_SOURCE)
- except ValueError:
- self._base_path = _path_without_ext(path, imp.PY_COMPILED)
-
- def _find_path(self, ext_type):
- """Find a path from the base path and the specified extension type that
- exists, returning None if one is not found."""
- for suffix in _suffix_list(ext_type):
- path = self._base_path + suffix
- if _path_exists(path):
- return path
- else:
- return None
+
+class _FileLoader:
+
+ """Base file loader class which implements the loader protocol methods that
+ require file system usage."""
+
+ def __init__(self, fullname, path):
+ """Cache the module name and the path to the file found by the
+ finder."""
+ self._name = fullname
+ self._path = path
@_check_name
- def source_path(self, fullname):
- """Return the path to an existing source file for the module, or None
- if one cannot be found."""
- # Not a property so that it is easy to override.
- return self._find_path(imp.PY_SOURCE)
+ def get_filename(self, fullname):
+ """Return the path to the source file as found by the finder."""
+ return self._path
def get_data(self, path):
"""Return the data from path as raw bytes."""
- return _io.FileIO(path, 'r').read() # Assuming bytes.
+ with _io.FileIO(path, 'r') as file:
+ return file.read()
- @_check_name
- def is_package(self, fullname):
- """Return a boolean based on whether the module is a package.
- Raises ImportError (like get_source) if the loader cannot handle the
- package.
+class _SourceFileLoader(_FileLoader, SourceLoader):
- """
- return self._is_pkg
+ """Concrete implementation of SourceLoader using the file system."""
+ def path_mtime(self, path):
+ """Return the modification time for the path."""
+ return int(_os.stat(path).st_mtime)
-class _PyPycFileLoader(PyPycLoader, _PyFileLoader):
+ def set_data(self, path, data):
+ """Write bytes data to a file."""
+ parent, _, filename = path.rpartition(path_sep)
+ path_parts = []
+ # Figure out what directories are missing.
+ while parent and not _path_isdir(parent):
+ parent, _, part = parent.rpartition(path_sep)
+ path_parts.append(part)
+ # Create needed directories.
+ for part in reversed(path_parts):
+ parent = _path_join(parent, part)
+ try:
+ _os.mkdir(parent)
+ except OSError as exc:
+ # Probably another Python process already created the dir.
+ if exc.errno == errno.EEXIST:
+ continue
+ else:
+ raise
+ except IOError as exc:
+ # If can't get proper access, then just forget about writing
+ # the data.
+ if exc.errno == errno.EACCES:
+ return
+ else:
+ raise
+ try:
+ with _io.FileIO(path, 'wb') as file:
+ file.write(data)
+ except IOError as exc:
+ # Don't worry if you can't write bytecode.
+ if exc.errno == errno.EACCES:
+ return
+ else:
+ raise
- """Load a module from a source or bytecode file."""
- @_check_name
- def source_mtime(self, name):
- """Return the modification time of the source for the specified
- module."""
- source_path = self.source_path(name)
- if not source_path:
- return None
- return int(_os.stat(source_path).st_mtime)
+class _SourcelessFileLoader(_FileLoader, _LoaderBasics):
- @_check_name
- def bytecode_path(self, fullname):
- """Return the path to a bytecode file, or None if one does not
- exist."""
- # Not a property for easy overriding.
- return self._find_path(imp.PY_COMPILED)
+ """Loader which handles sourceless file imports."""
- @_check_name
- def write_bytecode(self, name, data):
- """Write out 'data' for the specified module, returning a boolean
- signifying if the write-out actually occurred.
+ def load_module(self, fullname):
+ return self._load_module(fullname, sourceless=True)
- Raises ImportError (just like get_source) if the specified module
- cannot be handled by the loader.
+ def get_code(self, fullname):
+ path = self.get_filename(fullname)
+ data = self.get_data(path)
+ bytes_data = self._bytes_from_bytecode(fullname, data, None)
+ found = marshal.loads(bytes_data)
+ if isinstance(found, code_type):
+ return found
+ else:
+ raise ImportError("Non-code object in {}".format(path))
- """
- bytecode_path = self.bytecode_path(name)
- if not bytecode_path:
- bytecode_path = self._base_path + _suffix_list(imp.PY_COMPILED)[0]
- try:
- # Assuming bytes.
- with _closing(_io.FileIO(bytecode_path, 'w')) as bytecode_file:
- bytecode_file.write(data)
- return True
- except IOError as exc:
- if exc.errno == errno.EACCES:
- return False
- else:
- raise
+ def get_source(self, fullname):
+ """Return None as there is no source code."""
+ return None
class _ExtensionFileLoader:
@@ -547,7 +533,7 @@ class _ExtensionFileLoader:
"""
- def __init__(self, name, path, is_pkg):
+ def __init__(self, name, path):
"""Initialize the loader.
If is_pkg is True then an exception is raised as extension modules
@@ -556,8 +542,6 @@ class _ExtensionFileLoader:
"""
self._name = name
self._path = path
- if is_pkg:
- raise ValueError("extension modules cannot be packages")
@_check_name
@set_package
@@ -655,147 +639,88 @@ class PathFinder:
return None
-class _ChainedFinder:
-
- """Finder that sequentially calls other finders."""
-
- def __init__(self, *finders):
- self._finders = finders
-
- def find_module(self, fullname, path=None):
- for finder in self._finders:
- result = finder.find_module(fullname, path)
- if result:
- return result
- else:
- return None
-
-
class _FileFinder:
- """Base class for file finders.
-
- Subclasses are expected to define the following attributes:
-
- * _suffixes
- Sequence of file suffixes whose order will be followed.
-
- * _possible_package
- True if importer should check for packages.
+ """File-based finder.
- * _loader
- A callable that takes the module name, a file path, and whether
- the path points to a package and returns a loader for the module
- found at that path.
+ Constructor takes a list of objects detailing what file extensions their
+ loader supports along with whether it can be used for a package.
"""
- def __init__(self, path_entry):
- """Initialize an importer for the passed-in sys.path entry (which is
- assumed to have already been verified as an existing directory).
-
- Can be used as an entry on sys.path_hook.
-
- """
- absolute_path = _path_absolute(path_entry)
- if not _path_isdir(absolute_path):
- raise ImportError("only directories are supported")
- self._path_entry = absolute_path
-
- def find_module(self, fullname, path=None):
+ def __init__(self, path, *details):
+ """Initialize with finder details."""
+ packages = []
+ modules = []
+ for detail in details:
+ modules.extend((suffix, detail.loader) for suffix in detail.suffixes)
+ if detail.supports_packages:
+ packages.extend((suffix, detail.loader)
+ for suffix in detail.suffixes)
+ self.packages = packages
+ self.modules = modules
+ self.path = path
+
+ def find_module(self, fullname):
+ """Try to find a loader for the specified module."""
tail_module = fullname.rpartition('.')[2]
- package_directory = None
- if self._possible_package:
- for ext in self._suffixes:
- package_directory = _path_join(self._path_entry, tail_module)
- init_filename = '__init__' + ext
- package_init = _path_join(package_directory, init_filename)
- if (_path_isfile(package_init) and
- _case_ok(self._path_entry, tail_module) and
- _case_ok(package_directory, init_filename)):
- return self._loader(fullname, package_init, True)
- for ext in self._suffixes:
- file_name = tail_module + ext
- file_path = _path_join(self._path_entry, file_name)
- if (_path_isfile(file_path) and
- _case_ok(self._path_entry, file_name)):
- return self._loader(fullname, file_path, False)
- else:
- # Raise a warning if it matches a directory w/o an __init__ file.
- if (package_directory is not None and
- _path_isdir(package_directory) and
- _case_ok(self._path_entry, tail_module)):
- _warnings.warn("Not importing directory %s: missing __init__"
- % package_directory, ImportWarning)
- return None
-
-
-class _PyFileFinder(_FileFinder):
-
- """Importer for source/bytecode files."""
-
- _possible_package = True
- _loader = _PyFileLoader
-
- def __init__(self, path_entry):
- # Lack of imp during class creation means _suffixes is set here.
- # Make sure that Python source files are listed first! Needed for an
- # optimization by the loader.
- self._suffixes = _suffix_list(imp.PY_SOURCE)
- super().__init__(path_entry)
-
-
-class _PyPycFileFinder(_PyFileFinder):
+ base_path = _path_join(self.path, tail_module)
+ if _path_isdir(base_path) and _case_ok(self.path, tail_module):
+ for suffix, loader in self.packages:
+ init_filename = '__init__' + suffix
+ full_path = _path_join(base_path, init_filename)
+ if (_path_isfile(full_path) and
+ _case_ok(base_path, init_filename)):
+ return loader(fullname, full_path)
+ else:
+ msg = "Not importing directory {}: missing __init__"
+ _warnings.warn(msg.format(base_path), ImportWarning)
+ for suffix, loader in self.modules:
+ mod_filename = tail_module + suffix
+ full_path = _path_join(self.path, mod_filename)
+ if _path_isfile(full_path) and _case_ok(self.path, mod_filename):
+ return loader(fullname, full_path)
+ return None
- """Finder for source and bytecode files."""
+class _SourceFinderDetails:
- _loader = _PyPycFileLoader
+ loader = _SourceFileLoader
+ supports_packages = True
- def __init__(self, path_entry):
- super().__init__(path_entry)
- self._suffixes += _suffix_list(imp.PY_COMPILED)
+ def __init__(self):
+ self.suffixes = _suffix_list(imp.PY_SOURCE)
+class _SourcelessFinderDetails:
+ loader = _SourcelessFileLoader
+ supports_packages = True
+ def __init__(self):
+ self.suffixes = _suffix_list(imp.PY_COMPILED)
-class _ExtensionFileFinder(_FileFinder):
- """Importer for extension files."""
+class _ExtensionFinderDetails:
- _possible_package = False
- _loader = _ExtensionFileLoader
+ loader = _ExtensionFileLoader
+ supports_packages = False
- def __init__(self, path_entry):
- # Assigning to _suffixes here instead of at the class level because
- # imp is not imported at the time of class creation.
- self._suffixes = _suffix_list(imp.C_EXTENSION)
- super().__init__(path_entry)
+ def __init__(self):
+ self.suffixes = _suffix_list(imp.C_EXTENSION)
# Import itself ###############################################################
-def _chained_path_hook(*path_hooks):
- """Create a closure which sequentially checks path hooks to see which ones
- (if any) can work with a path."""
- def path_hook(entry):
- """Check to see if 'entry' matches any of the enclosed path hooks."""
- finders = []
- for hook in path_hooks:
- try:
- finder = hook(entry)
- except ImportError:
- continue
- else:
- finders.append(finder)
- if not finders:
- raise ImportError("no finder found")
- else:
- return _ChainedFinder(*finders)
-
- return path_hook
+def _file_path_hook(path):
+ """If the path is a directory, return a file-based finder."""
+ if _path_isdir(path):
+ return _FileFinder(path, _ExtensionFinderDetails(),
+ _SourceFinderDetails(),
+ _SourcelessFinderDetails())
+ else:
+ raise ImportError("only directories are supported")
-_DEFAULT_PATH_HOOK = _chained_path_hook(_ExtensionFileFinder, _PyPycFileFinder)
+_DEFAULT_PATH_HOOK = _file_path_hook
class _DefaultPathFinder(PathFinder):
@@ -833,6 +758,8 @@ class _ImportLockContext:
_IMPLICIT_META_PATH = [BuiltinImporter, FrozenImporter, _DefaultPathFinder]
+_ERR_MSG = 'No module named {}'
+
def _gcd_import(name, package=None, level=0):
"""Import and return the module based on its name, the package the call is
being made from, and the level adjustment.
@@ -880,7 +807,11 @@ def _gcd_import(name, package=None, level=0):
_gcd_import(parent)
# Backwards-compatibility; be nicer to skip the dict lookup.
parent_module = sys.modules[parent]
- path = parent_module.__path__
+ try:
+ path = parent_module.__path__
+ except AttributeError:
+ msg = (_ERR_MSG + '; {} is not a package').format(name, parent)
+ raise ImportError(msg)
meta_path = sys.meta_path + _IMPLICIT_META_PATH
for finder in meta_path:
loader = finder.find_module(name, path)
@@ -888,7 +819,7 @@ def _gcd_import(name, package=None, level=0):
loader.load_module(name)
break
else:
- raise ImportError("No module named {0}".format(name))
+ raise ImportError(_ERR_MSG.format(name))
# Backwards-compatibility; be nicer to skip the dict lookup.
module = sys.modules[name]
if parent:
@@ -918,13 +849,15 @@ def __import__(name, globals={}, locals={}, fromlist=[], level=0):
import (e.g. ``from ..pkg import mod`` would have a 'level' of 2).
"""
+ if not hasattr(name, 'rpartition'):
+ raise TypeError("module name must be str, not {}".format(type(name)))
if level == 0:
module = _gcd_import(name)
else:
- # __package__ is not guaranteed to be defined.
- try:
- package = globals['__package__']
- except KeyError:
+ # __package__ is not guaranteed to be defined or could be set to None
+ # to represent that it's proper value is unknown
+ package = globals.get('__package__')
+ if package is None:
package = globals['__name__']
if '__path__' not in globals:
package = package.rpartition('.')[0]
@@ -944,6 +877,7 @@ def __import__(name, globals={}, locals={}, fromlist=[], level=0):
# If a package was imported, try to import stuff from fromlist.
if hasattr(module, '__path__'):
if '*' in fromlist and hasattr(module, '__all__'):
+ fromlist = list(fromlist)
fromlist.remove('*')
fromlist.extend(module.__all__)
for x in (y for y in fromlist if not hasattr(module,y)):
diff --git a/Lib/importlib/abc.py b/Lib/importlib/abc.py
index 7b89d0b..fa343f8 100644
--- a/Lib/importlib/abc.py
+++ b/Lib/importlib/abc.py
@@ -1,8 +1,16 @@
"""Abstract base classes related to import."""
from . import _bootstrap
from . import machinery
+from . import util
import abc
+import imp
+import io
+import marshal
+import os.path
+import sys
+import tokenize
import types
+import warnings
class Loader(metaclass=abc.ABCMeta):
@@ -10,8 +18,9 @@ class Loader(metaclass=abc.ABCMeta):
"""Abstract base class for import loaders."""
@abc.abstractmethod
- def load_module(self, fullname:str) -> types.ModuleType:
- """Abstract method which when implemented should load a module."""
+ def load_module(self, fullname):
+ """Abstract method which when implemented should load a module.
+ The fullname is a str."""
raise NotImplementedError
@@ -20,8 +29,11 @@ class Finder(metaclass=abc.ABCMeta):
"""Abstract base class for import finders."""
@abc.abstractmethod
- def find_module(self, fullname:str, path:[str]=None) -> Loader:
- """Abstract method which when implemented should find a module."""
+ def find_module(self, fullname, path=None):
+ """Abstract method which when implemented should find a module.
+ The fullname is a str and the optional path is a str or None.
+ Returns a Loader object.
+ """
raise NotImplementedError
Finder.register(machinery.BuiltinImporter)
@@ -39,9 +51,9 @@ class ResourceLoader(Loader):
"""
@abc.abstractmethod
- def get_data(self, path:str) -> bytes:
+ def get_data(self, path):
"""Abstract method which when implemented should return the bytes for
- the specified path."""
+ the specified path. The path must be a str."""
raise NotImplementedError
@@ -55,68 +67,238 @@ class InspectLoader(Loader):
"""
@abc.abstractmethod
- def is_package(self, fullname:str) -> bool:
+ def is_package(self, fullname):
"""Abstract method which when implemented should return whether the
- module is a package."""
- return NotImplementedError
+ module is a package. The fullname is a str. Returns a bool."""
+ raise NotImplementedError
@abc.abstractmethod
- def get_code(self, fullname:str) -> types.CodeType:
+ def get_code(self, fullname):
"""Abstract method which when implemented should return the code object
- for the module"""
- return NotImplementedError
+ for the module. The fullname is a str. Returns a types.CodeType."""
+ raise NotImplementedError
@abc.abstractmethod
- def get_source(self, fullname:str) -> str:
+ def get_source(self, fullname):
"""Abstract method which should return the source code for the
- module."""
- return NotImplementedError
+ module. The fullname is a str. Returns a str."""
+ raise NotImplementedError
InspectLoader.register(machinery.BuiltinImporter)
InspectLoader.register(machinery.FrozenImporter)
-class PyLoader(_bootstrap.PyLoader, ResourceLoader, InspectLoader):
+class ExecutionLoader(InspectLoader):
- """Abstract base class to assist in loading source code by requiring only
- back-end storage methods to be implemented.
+ """Abstract base class for loaders that wish to support the execution of
+ modules as scripts.
- The methods get_code, get_source, and load_module are implemented for the
- user.
+ This ABC represents one of the optional protocols specified in PEP 302.
+
+ """
+
+ @abc.abstractmethod
+ def get_filename(self, fullname):
+ """Abstract method which should return the value that __file__ is to be
+ set to."""
+ raise NotImplementedError
+
+
+class SourceLoader(_bootstrap.SourceLoader, ResourceLoader, ExecutionLoader):
+
+ """Abstract base class for loading source code (and optionally any
+ corresponding bytecode).
+
+ To support loading from source code, the abstractmethods inherited from
+ ResourceLoader and ExecutionLoader need to be implemented. To also support
+ loading from bytecode, the optional methods specified directly by this ABC
+ is required.
+
+ Inherited abstractmethods not implemented in this ABC:
+
+ * ResourceLoader.get_data
+ * ExecutionLoader.get_filename
+
+ """
+
+ def path_mtime(self, path):
+ """Return the (int) modification time for the path (str)."""
+ raise NotImplementedError
+
+ def set_data(self, path, data):
+ """Write the bytes to the path (if possible).
+
+ Accepts a str path and data as bytes.
+
+ Any needed intermediary directories are to be created. If for some
+ reason the file cannot be written because of permissions, fail
+ silently.
+
+ """
+ raise NotImplementedError
+
+
+class PyLoader(SourceLoader):
+
+ """Implement the deprecated PyLoader ABC in terms of SourceLoader.
+
+ This class has been deprecated! It is slated for removal in Python 3.4.
+ If compatibility with Python 3.1 is not needed then implement the
+ SourceLoader ABC instead of this class. If Python 3.1 compatibility is
+ needed, then use the following idiom to have a single class that is
+ compatible with Python 3.1 onwards::
+
+ try:
+ from importlib.abc import SourceLoader
+ except ImportError:
+ from importlib.abc import PyLoader as SourceLoader
+
+
+ class CustomLoader(SourceLoader):
+ def get_filename(self, fullname):
+ # Implement ...
+
+ def source_path(self, fullname):
+ '''Implement source_path in terms of get_filename.'''
+ try:
+ return self.get_filename(fullname)
+ except ImportError:
+ return None
+
+ def is_package(self, fullname):
+ filename = os.path.basename(self.get_filename(fullname))
+ return os.path.splitext(filename)[0] == '__init__'
"""
@abc.abstractmethod
- def source_path(self, fullname:str) -> object:
- """Abstract method which when implemented should return the path to the
- sourced code for the module."""
+ def is_package(self, fullname):
+ raise NotImplementedError
+
+ @abc.abstractmethod
+ def source_path(self, fullname):
+ """Abstract method. Accepts a str module name and returns the path to
+ the source code for the module."""
raise NotImplementedError
+ def get_filename(self, fullname):
+ """Implement get_filename in terms of source_path.
-class PyPycLoader(_bootstrap.PyPycLoader, PyLoader):
+ As get_filename should only return a source file path there is no
+ chance of the path not existing but loading still being possible, so
+ ImportError should propagate instead of being turned into returning
+ None.
+
+ """
+ warnings.warn("importlib.abc.PyLoader is deprecated and is "
+ "slated for removal in Python 3.4; "
+ "use SourceLoader instead. "
+ "See the importlib documentation on how to be "
+ "compatible with Python 3.1 onwards.",
+ PendingDeprecationWarning)
+ path = self.source_path(fullname)
+ if path is None:
+ raise ImportError
+ else:
+ return path
+
+
+class PyPycLoader(PyLoader):
"""Abstract base class to assist in loading source and bytecode by
requiring only back-end storage methods to be implemented.
+ This class has been deprecated! Removal is slated for Python 3.4. Implement
+ the SourceLoader ABC instead. If Python 3.1 compatibility is needed, see
+ PyLoader.
+
The methods get_code, get_source, and load_module are implemented for the
user.
"""
+ def get_filename(self, fullname):
+ """Return the source or bytecode file path."""
+ path = self.source_path(fullname)
+ if path is not None:
+ return path
+ path = self.bytecode_path(fullname)
+ if path is not None:
+ return path
+ raise ImportError("no source or bytecode path available for "
+ "{0!r}".format(fullname))
+
+ def get_code(self, fullname):
+ """Get a code object from source or bytecode."""
+ warnings.warn("importlib.abc.PyPycLoader is deprecated and slated for "
+ "removal in Python 3.4; use SourceLoader instead. "
+ "If Python 3.1 compatibility is required, see the "
+ "latest documentation for PyLoader.",
+ PendingDeprecationWarning)
+ source_timestamp = self.source_mtime(fullname)
+ # Try to use bytecode if it is available.
+ bytecode_path = self.bytecode_path(fullname)
+ if bytecode_path:
+ data = self.get_data(bytecode_path)
+ try:
+ magic = data[:4]
+ if len(magic) < 4:
+ raise ImportError("bad magic number in {}".format(fullname))
+ raw_timestamp = data[4:8]
+ if len(raw_timestamp) < 4:
+ raise EOFError("bad timestamp in {}".format(fullname))
+ pyc_timestamp = marshal._r_long(raw_timestamp)
+ bytecode = data[8:]
+ # Verify that the magic number is valid.
+ if imp.get_magic() != magic:
+ raise ImportError("bad magic number in {}".format(fullname))
+ # Verify that the bytecode is not stale (only matters when
+ # there is source to fall back on.
+ if source_timestamp:
+ if pyc_timestamp < source_timestamp:
+ raise ImportError("bytecode is stale")
+ except (ImportError, EOFError):
+ # If source is available give it a shot.
+ if source_timestamp is not None:
+ pass
+ else:
+ raise
+ else:
+ # Bytecode seems fine, so try to use it.
+ return marshal.loads(bytecode)
+ elif source_timestamp is None:
+ raise ImportError("no source or bytecode available to create code "
+ "object for {0!r}".format(fullname))
+ # Use the source.
+ source_path = self.source_path(fullname)
+ if source_path is None:
+ message = "a source path must exist to load {0}".format(fullname)
+ raise ImportError(message)
+ source = self.get_data(source_path)
+ code_object = compile(source, source_path, 'exec', dont_inherit=True)
+ # Generate bytecode and write it out.
+ if not sys.dont_write_bytecode:
+ data = bytearray(imp.get_magic())
+ data.extend(marshal._w_long(source_timestamp))
+ data.extend(marshal.dumps(code_object))
+ self.write_bytecode(fullname, data)
+ return code_object
+
@abc.abstractmethod
- def source_mtime(self, fullname:str) -> int:
- """Abstract method which when implemented should return the
+ def source_mtime(self, fullname):
+ """Abstract method. Accepts a str filename and returns an int
modification time for the source of the module."""
raise NotImplementedError
@abc.abstractmethod
- def bytecode_path(self, fullname:str) -> object:
- """Abstract method which when implemented should return the path to the
- bytecode for the module."""
+ def bytecode_path(self, fullname):
+ """Abstract method. Accepts a str filename and returns the str pathname
+ to the bytecode for the module."""
raise NotImplementedError
@abc.abstractmethod
- def write_bytecode(self, fullname:str, bytecode:bytes):
- """Abstract method which when implemented should attempt to write the
- bytecode for the module."""
+ def write_bytecode(self, fullname, bytecode):
+ """Abstract method. Accepts a str filename and bytes object
+ representing the bytecode for the module. Returns a boolean
+ representing whether the bytecode was written or not."""
raise NotImplementedError
diff --git a/Lib/importlib/test/__init__.py b/Lib/importlib/test/__init__.py
index bda33e6..e69de29 100644
--- a/Lib/importlib/test/__init__.py
+++ b/Lib/importlib/test/__init__.py
@@ -1,31 +0,0 @@
-import os.path
-import sys
-import unittest
-
-
-def test_suite(package=__package__, directory=os.path.dirname(__file__)):
- suite = unittest.TestSuite()
- for name in os.listdir(directory):
- if name.startswith('.'):
- continue
- path = os.path.join(directory, name)
- if (os.path.isfile(path) and name.startswith('test_') and
- name.endswith('.py')):
- submodule_name = os.path.splitext(name)[0]
- module_name = "{0}.{1}".format(package, submodule_name)
- __import__(module_name, level=0)
- module_tests = unittest.findTestCases(sys.modules[module_name])
- suite.addTest(module_tests)
- elif os.path.isdir(path):
- package_name = "{0}.{1}".format(package, name)
- __import__(package_name, level=0)
- package_tests = getattr(sys.modules[package_name], 'test_suite')()
- suite.addTest(package_tests)
- else:
- continue
- return suite
-
-
-if __name__ == '__main__':
- from test.support import run_unittest
- run_unittest(test_suite('importlib.test'))
diff --git a/Lib/importlib/test/__main__.py b/Lib/importlib/test/__main__.py
new file mode 100644
index 0000000..decc53d
--- /dev/null
+++ b/Lib/importlib/test/__main__.py
@@ -0,0 +1,29 @@
+"""Run importlib's test suite.
+
+Specifying the ``--builtin`` flag will run tests, where applicable, with
+builtins.__import__ instead of importlib.__import__.
+
+"""
+import importlib
+from importlib.test.import_ import util
+import os.path
+from test.support import run_unittest
+import sys
+import unittest
+
+
+def test_main():
+ if '__pycache__' in __file__:
+ parts = __file__.split(os.path.sep)
+ start_dir = sep.join(parts[:-2])
+ else:
+ start_dir = os.path.dirname(__file__)
+ top_dir = os.path.dirname(os.path.dirname(start_dir))
+ test_loader = unittest.TestLoader()
+ if '--builtin' in sys.argv:
+ util.using___import__ = True
+ run_unittest(test_loader.discover(start_dir, top_level_dir=top_dir))
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/benchmark.py b/Lib/importlib/test/benchmark.py
index f709a3c..b5de6c6 100644
--- a/Lib/importlib/test/benchmark.py
+++ b/Lib/importlib/test/benchmark.py
@@ -1,69 +1,159 @@
+"""Benchmark some basic import use-cases.
+
+The assumption is made that this benchmark is run in a fresh interpreter and
+thus has no external changes made to import-related attributes in sys.
+
+"""
from . import util
from .source import util as source_util
-import gc
import decimal
import imp
import importlib
+import os
+import py_compile
import sys
import timeit
-def bench_cache(import_, repeat, number):
- """Measure the time it takes to pull from sys.modules."""
+def bench(name, cleanup=lambda: None, *, seconds=1, repeat=3):
+ """Bench the given statement as many times as necessary until total
+ executions take one second."""
+ stmt = "__import__({!r})".format(name)
+ timer = timeit.Timer(stmt)
+ for x in range(repeat):
+ total_time = 0
+ count = 0
+ while total_time < seconds:
+ try:
+ total_time += timer.timeit(1)
+ finally:
+ cleanup()
+ count += 1
+ else:
+ # One execution too far
+ if total_time > seconds:
+ count -= 1
+ yield count // seconds
+
+def from_cache(seconds, repeat):
+ """sys.modules"""
name = '<benchmark import>'
+ module = imp.new_module(name)
+ module.__file__ = '<test>'
+ module.__package__ = ''
with util.uncache(name):
- module = imp.new_module(name)
sys.modules[name] = module
- runs = []
- for x in range(repeat):
- start_time = timeit.default_timer()
- for y in range(number):
- import_(name)
- end_time = timeit.default_timer()
- runs.append(end_time - start_time)
- return min(runs)
+ for result in bench(name, repeat=repeat, seconds=seconds):
+ yield result
-def bench_importing_source(import_, repeat, number, loc=100000):
- """Measure importing source from disk.
+def builtin_mod(seconds, repeat):
+ """Built-in module"""
+ name = 'errno'
+ if name in sys.modules:
+ del sys.modules[name]
+ # Relying on built-in importer being implicit.
+ for result in bench(name, lambda: sys.modules.pop(name), repeat=repeat,
+ seconds=seconds):
+ yield result
- For worst-case scenario, the line endings are \\r\\n and thus require
- universal newline translation.
- """
- name = '__benchmark'
- with source_util.create_modules(name) as mapping:
- with open(mapping[name], 'w') as file:
- for x in range(loc):
- file.write("{0}\r\n".format(x))
- with util.import_state(path=[mapping['.root']]):
- runs = []
- for x in range(repeat):
- start_time = timeit.default_timer()
- for y in range(number):
- try:
- import_(name)
- finally:
- del sys.modules[name]
- end_time = timeit.default_timer()
- runs.append(end_time - start_time)
- return min(runs)
+def source_wo_bytecode(seconds, repeat):
+ """Source w/o bytecode: simple"""
+ sys.dont_write_bytecode = True
+ try:
+ name = '__importlib_test_benchmark__'
+ # Clears out sys.modules and puts an entry at the front of sys.path.
+ with source_util.create_modules(name) as mapping:
+ assert not os.path.exists(imp.cache_from_source(mapping[name]))
+ for result in bench(name, lambda: sys.modules.pop(name), repeat=repeat,
+ seconds=seconds):
+ yield result
+ finally:
+ sys.dont_write_bytecode = False
-def main(import_):
- args = [('sys.modules', bench_cache, 5, 500000),
- ('source', bench_importing_source, 5, 10000)]
- test_msg = "{test}, {number} times (best of {repeat}):"
- result_msg = "{result:.2f} secs"
- gc.disable()
+def decimal_wo_bytecode(seconds, repeat):
+ """Source w/o bytecode: decimal"""
+ name = 'decimal'
+ decimal_bytecode = imp.cache_from_source(decimal.__file__)
+ if os.path.exists(decimal_bytecode):
+ os.unlink(decimal_bytecode)
+ sys.dont_write_bytecode = True
try:
- for name, meth, repeat, number in args:
- result = meth(import_, repeat, number)
- print(test_msg.format(test=name, repeat=repeat,
- number=number).ljust(40),
- result_msg.format(result=result).rjust(10))
+ for result in bench(name, lambda: sys.modules.pop(name), repeat=repeat,
+ seconds=seconds):
+ yield result
finally:
- gc.enable()
+ sys.dont_write_bytecode = False
+
+
+def source_writing_bytecode(seconds, repeat):
+ """Source writing bytecode: simple"""
+ assert not sys.dont_write_bytecode
+ name = '__importlib_test_benchmark__'
+ with source_util.create_modules(name) as mapping:
+ def cleanup():
+ sys.modules.pop(name)
+ os.unlink(imp.cache_from_source(mapping[name]))
+ for result in bench(name, cleanup, repeat=repeat, seconds=seconds):
+ assert not os.path.exists(imp.cache_from_source(mapping[name]))
+ yield result
+
+
+def decimal_writing_bytecode(seconds, repeat):
+ """Source writing bytecode: decimal"""
+ assert not sys.dont_write_bytecode
+ name = 'decimal'
+ def cleanup():
+ sys.modules.pop(name)
+ os.unlink(imp.cache_from_source(decimal.__file__))
+ for result in bench(name, cleanup, repeat=repeat, seconds=seconds):
+ yield result
+
+
+def source_using_bytecode(seconds, repeat):
+ """Bytecode w/ source: simple"""
+ name = '__importlib_test_benchmark__'
+ with source_util.create_modules(name) as mapping:
+ py_compile.compile(mapping[name])
+ assert os.path.exists(imp.cache_from_source(mapping[name]))
+ for result in bench(name, lambda: sys.modules.pop(name), repeat=repeat,
+ seconds=seconds):
+ yield result
+
+
+def decimal_using_bytecode(seconds, repeat):
+ """Bytecode w/ source: decimal"""
+ name = 'decimal'
+ py_compile.compile(decimal.__file__)
+ for result in bench(name, lambda: sys.modules.pop(name), repeat=repeat,
+ seconds=seconds):
+ yield result
+
+
+def main(import_):
+ __builtins__.__import__ = import_
+ benchmarks = (from_cache, builtin_mod,
+ source_using_bytecode, source_wo_bytecode,
+ source_writing_bytecode,
+ decimal_using_bytecode, decimal_writing_bytecode,
+ decimal_wo_bytecode,)
+ seconds = 1
+ seconds_plural = 's' if seconds > 1 else ''
+ repeat = 3
+ header = "Measuring imports/second over {} second{}, best out of {}\n"
+ print(header.format(seconds, seconds_plural, repeat))
+ for benchmark in benchmarks:
+ print(benchmark.__doc__, "[", end=' ')
+ sys.stdout.flush()
+ results = []
+ for result in benchmark(seconds=seconds, repeat=repeat):
+ results.append(result)
+ print(result, end=' ')
+ sys.stdout.flush()
+ assert not sys.dont_write_bytecode
+ print("]", "best is", format(max(results), ',d'))
if __name__ == '__main__':
@@ -74,7 +164,7 @@ if __name__ == '__main__':
default=False, help="use the built-in __import__")
options, args = parser.parse_args()
if args:
- raise RuntimeError("unrecognized args: {0}".format(args))
+ raise RuntimeError("unrecognized args: {}".format(args))
import_ = __import__
if not options.builtin:
import_ = importlib.__import__
diff --git a/Lib/importlib/test/builtin/test_loader.py b/Lib/importlib/test/builtin/test_loader.py
index dff00ce..1a8539b 100644
--- a/Lib/importlib/test/builtin/test_loader.py
+++ b/Lib/importlib/test/builtin/test_loader.py
@@ -54,13 +54,15 @@ class LoaderTests(abc.LoaderTests):
def test_unloadable(self):
name = 'dssdsdfff'
assert name not in sys.builtin_module_names
- self.assertRaises(ImportError, self.load_module, name)
+ with self.assertRaises(ImportError):
+ self.load_module(name)
def test_already_imported(self):
# Using the name of a module already imported but not a built-in should
# still fail.
assert hasattr(importlib, '__file__') # Not a built-in.
- self.assertRaises(ImportError, self.load_module, 'importlib')
+ with self.assertRaises(ImportError):
+ self.load_module('importlib')
class InspectLoaderTests(unittest.TestCase):
@@ -86,7 +88,8 @@ class InspectLoaderTests(unittest.TestCase):
# Modules not built-in should raise ImportError.
for meth_name in ('get_code', 'get_source', 'is_package'):
method = getattr(machinery.BuiltinImporter, meth_name)
- self.assertRaises(ImportError, method, builtin_util.BAD_NAME)
+ with self.assertRaises(ImportError):
+ method(builtin_util.BAD_NAME)
diff --git a/Lib/importlib/test/extension/test_case_sensitivity.py b/Lib/importlib/test/extension/test_case_sensitivity.py
index 3865539..e062fb6 100644
--- a/Lib/importlib/test/extension/test_case_sensitivity.py
+++ b/Lib/importlib/test/extension/test_case_sensitivity.py
@@ -13,7 +13,8 @@ class ExtensionModuleCaseSensitivityTest(unittest.TestCase):
good_name = ext_util.NAME
bad_name = good_name.upper()
assert good_name != bad_name
- finder = _bootstrap._ExtensionFileFinder(ext_util.PATH)
+ finder = _bootstrap._FileFinder(ext_util.PATH,
+ _bootstrap._ExtensionFinderDetails())
return finder.find_module(bad_name)
def test_case_sensitive(self):
diff --git a/Lib/importlib/test/extension/test_finder.py b/Lib/importlib/test/extension/test_finder.py
index 546a176..ea97483 100644
--- a/Lib/importlib/test/extension/test_finder.py
+++ b/Lib/importlib/test/extension/test_finder.py
@@ -9,7 +9,8 @@ class FinderTests(abc.FinderTests):
"""Test the finder for extension modules."""
def find_module(self, fullname):
- importer = _bootstrap._ExtensionFileFinder(util.PATH)
+ importer = _bootstrap._FileFinder(util.PATH,
+ _bootstrap._ExtensionFinderDetails())
return importer.find_module(fullname)
def test_module(self):
diff --git a/Lib/importlib/test/extension/test_loader.py b/Lib/importlib/test/extension/test_loader.py
index 71841c6..4a783db 100644
--- a/Lib/importlib/test/extension/test_loader.py
+++ b/Lib/importlib/test/extension/test_loader.py
@@ -13,7 +13,7 @@ class LoaderTests(abc.LoaderTests):
def load_module(self, fullname):
loader = _bootstrap._ExtensionFileLoader(ext_util.NAME,
- ext_util.FILEPATH, False)
+ ext_util.FILEPATH)
return loader.load_module(fullname)
def test_module(self):
@@ -46,7 +46,8 @@ class LoaderTests(abc.LoaderTests):
pass
def test_unloadable(self):
- self.assertRaises(ImportError, self.load_module, 'asdfjkl;')
+ with self.assertRaises(ImportError):
+ self.load_module('asdfjkl;')
def test_main():
diff --git a/Lib/importlib/test/extension/test_path_hook.py b/Lib/importlib/test/extension/test_path_hook.py
index bf2f411..4610420 100644
--- a/Lib/importlib/test/extension/test_path_hook.py
+++ b/Lib/importlib/test/extension/test_path_hook.py
@@ -14,7 +14,7 @@ class PathHookTests(unittest.TestCase):
# XXX Should it only work for directories containing an extension module?
def hook(self, entry):
- return _bootstrap._ExtensionFileFinder(entry)
+ return _bootstrap._file_path_hook(entry)
def test_success(self):
# Path hook should handle a directory where a known extension module
diff --git a/Lib/importlib/test/frozen/test_loader.py b/Lib/importlib/test/frozen/test_loader.py
index fa64f30..c05e22c 100644
--- a/Lib/importlib/test/frozen/test_loader.py
+++ b/Lib/importlib/test/frozen/test_loader.py
@@ -51,8 +51,8 @@ class LoaderTests(abc.LoaderTests):
def test_unloadable(self):
assert machinery.FrozenImporter.find_module('_not_real') is None
- self.assertRaises(ImportError, machinery.FrozenImporter.load_module,
- '_not_real')
+ with self.assertRaises(ImportError):
+ machinery.FrozenImporter.load_module('_not_real')
class InspectLoaderTests(unittest.TestCase):
@@ -84,7 +84,8 @@ class InspectLoaderTests(unittest.TestCase):
# Raise ImportError for modules that are not frozen.
for meth_name in ('get_code', 'get_source', 'is_package'):
method = getattr(machinery.FrozenImporter, meth_name)
- self.assertRaises(ImportError, method, 'importlib')
+ with self.assertRaises(ImportError):
+ method('importlib')
def test_main():
diff --git a/Lib/importlib/test/import_/test___package__.py b/Lib/importlib/test/import_/test___package__.py
index 4dc6901..5056ae5 100644
--- a/Lib/importlib/test/import_/test___package__.py
+++ b/Lib/importlib/test/import_/test___package__.py
@@ -19,8 +19,9 @@ class Using__package__(unittest.TestCase):
base = package.rsplit('.', level)[0]
return '{0}.{1}'.format(base, name)
- But since there is no guarantee that __package__ has been set, there has to
- be a way to calculate the attribute's value [__name__]::
+ But since there is no guarantee that __package__ has been set (or not been
+ set to None [None]), there has to be a way to calculate the attribute's value
+ [__name__]::
def calc_package(caller_name, has___path__):
if has__path__:
@@ -43,28 +44,34 @@ class Using__package__(unittest.TestCase):
fromlist=['attr'], level=2)
self.assertEqual(module.__name__, 'pkg')
- def test_using___name__(self):
+ def test_using___name__(self, package_as_None=False):
# [__name__]
+ globals_ = {'__name__': 'pkg.fake', '__path__': []}
+ if package_as_None:
+ globals_['__package__'] = None
with util.mock_modules('pkg.__init__', 'pkg.fake') as importer:
with util.import_state(meta_path=[importer]):
import_util.import_('pkg.fake')
- module = import_util.import_('',
- globals={'__name__': 'pkg.fake',
- '__path__': []},
- fromlist=['attr'], level=2)
+ module = import_util.import_('', globals= globals_,
+ fromlist=['attr'], level=2)
self.assertEqual(module.__name__, 'pkg')
+ def test_None_as___package__(self):
+ # [None]
+ self.test_using___name__(package_as_None=True)
+
def test_bad__package__(self):
globals = {'__package__': '<not real>'}
- self.assertRaises(SystemError, import_util.import_,'', globals, {},
- ['relimport'], 1)
+ with self.assertRaises(SystemError):
+ import_util.import_('', globals, {}, ['relimport'], 1)
def test_bunk__package__(self):
globals = {'__package__': 42}
- self.assertRaises(ValueError, import_util.import_, '', globals, {},
- ['relimport'], 1)
+ with self.assertRaises(ValueError):
+ import_util.import_('', globals, {}, ['relimport'], 1)
+@import_util.importlib_only
class Setting__package__(unittest.TestCase):
"""Because __package__ is a new feature, it is not always set by a loader.
diff --git a/Lib/importlib/test/import_/test_api.py b/Lib/importlib/test/import_/test_api.py
new file mode 100644
index 0000000..9075d42
--- /dev/null
+++ b/Lib/importlib/test/import_/test_api.py
@@ -0,0 +1,22 @@
+from . import util
+import unittest
+
+
+class APITest(unittest.TestCase):
+
+ """Test API-specific details for __import__ (e.g. raising the right
+ exception when passing in an int for the module name)."""
+
+ def test_name_requires_rparition(self):
+ # Raise TypeError if a non-string is passed in for the module name.
+ with self.assertRaises(TypeError):
+ util.import_(42)
+
+
+def test_main():
+ from test.support import run_unittest
+ run_unittest(APITest)
+
+
+if __name__ == '__main__':
+ test_main()
diff --git a/Lib/importlib/test/import_/test_fromlist.py b/Lib/importlib/test/import_/test_fromlist.py
index ddd355e..b903e8e 100644
--- a/Lib/importlib/test/import_/test_fromlist.py
+++ b/Lib/importlib/test/import_/test_fromlist.py
@@ -84,16 +84,23 @@ class HandlingFromlist(unittest.TestCase):
module = import_util.import_('pkg.mod', fromlist=[''])
self.assertEqual(module.__name__, 'pkg.mod')
- def test_using_star(self):
+ def basic_star_test(self, fromlist=['*']):
# [using *]
with util.mock_modules('pkg.__init__', 'pkg.module') as mock:
with util.import_state(meta_path=[mock]):
mock['pkg'].__all__ = ['module']
- module = import_util.import_('pkg', fromlist=['*'])
+ module = import_util.import_('pkg', fromlist=fromlist)
self.assertEqual(module.__name__, 'pkg')
self.assertTrue(hasattr(module, 'module'))
self.assertEqual(module.module.__name__, 'pkg.module')
+ def test_using_star(self):
+ # [using *]
+ self.basic_star_test()
+
+ def test_fromlist_as_tuple(self):
+ self.basic_star_test(('*',))
+
def test_star_with_others(self):
# [using * with others]
context = util.mock_modules('pkg.__init__', 'pkg.module1', 'pkg.module2')
diff --git a/Lib/importlib/test/import_/test_packages.py b/Lib/importlib/test/import_/test_packages.py
index b41c36f..faadc32 100644
--- a/Lib/importlib/test/import_/test_packages.py
+++ b/Lib/importlib/test/import_/test_packages.py
@@ -18,8 +18,14 @@ class ParentModuleTests(unittest.TestCase):
def test_bad_parent(self):
with util.mock_modules('pkg.module') as mock:
with util.import_state(meta_path=[mock]):
- self.assertRaises(ImportError,
- import_util.import_, 'pkg.module')
+ with self.assertRaises(ImportError):
+ import_util.import_('pkg.module')
+
+ def test_module_not_package(self):
+ # Try to import a submodule from a non-package should raise ImportError.
+ assert not hasattr(sys, '__path__')
+ with self.assertRaises(ImportError):
+ import_util.import_('sys.no_submodules_here')
def test_main():
diff --git a/Lib/importlib/test/import_/test_path.py b/Lib/importlib/test/import_/test_path.py
index 0e055b1..2faa231 100644
--- a/Lib/importlib/test/import_/test_path.py
+++ b/Lib/importlib/test/import_/test_path.py
@@ -2,10 +2,10 @@ from importlib import _bootstrap
from importlib import machinery
from .. import util
from . import util as import_util
-from contextlib import nested
import imp
import os
import sys
+import tempfile
from test import support
from types import MethodType
import unittest
@@ -81,23 +81,28 @@ class DefaultPathFinderTests(unittest.TestCase):
def test_implicit_hooks(self):
# Test that the implicit path hooks are used.
- existing_path = os.path.dirname(support.TESTFN)
bad_path = '<path>'
module = '<module>'
assert not os.path.exists(bad_path)
- with util.import_state():
- nothing = _bootstrap._DefaultPathFinder.find_module(module,
- path=[existing_path])
- self.assertTrue(nothing is None)
- self.assertTrue(existing_path in sys.path_importer_cache)
- self.assertTrue(not isinstance(sys.path_importer_cache[existing_path],
- imp.NullImporter))
- nothing = _bootstrap._DefaultPathFinder.find_module(module,
- path=[bad_path])
- self.assertTrue(nothing is None)
- self.assertTrue(bad_path in sys.path_importer_cache)
- self.assertTrue(isinstance(sys.path_importer_cache[bad_path],
- imp.NullImporter))
+ existing_path = tempfile.mkdtemp()
+ try:
+ with util.import_state():
+ nothing = _bootstrap._DefaultPathFinder.find_module(module,
+ path=[existing_path])
+ self.assertTrue(nothing is None)
+ self.assertTrue(existing_path in sys.path_importer_cache)
+ result = isinstance(sys.path_importer_cache[existing_path],
+ imp.NullImporter)
+ self.assertFalse(result)
+ nothing = _bootstrap._DefaultPathFinder.find_module(module,
+ path=[bad_path])
+ self.assertTrue(nothing is None)
+ self.assertTrue(bad_path in sys.path_importer_cache)
+ self.assertTrue(isinstance(sys.path_importer_cache[bad_path],
+ imp.NullImporter))
+ finally:
+ os.rmdir(existing_path)
+
def test_path_importer_cache_has_None(self):
# Test that the default hook is used when sys.path_importer_cache
diff --git a/Lib/importlib/test/import_/test_relative_imports.py b/Lib/importlib/test/import_/test_relative_imports.py
index 5547d4c..a0f6b2d 100644
--- a/Lib/importlib/test/import_/test_relative_imports.py
+++ b/Lib/importlib/test/import_/test_relative_imports.py
@@ -154,8 +154,9 @@ class RelativeImports(unittest.TestCase):
{'__name__': 'pkg', '__path__': ['blah']})
def callback(global_):
import_util.import_('pkg')
- self.assertRaises(ValueError, import_util.import_, '', global_,
- fromlist=['top_level'], level=2)
+ with self.assertRaises(ValueError):
+ import_util.import_('', global_, fromlist=['top_level'],
+ level=2)
self.relative_import_test(create, globals_, callback)
def test_too_high_from_module(self):
@@ -164,13 +165,15 @@ class RelativeImports(unittest.TestCase):
globals_ = {'__package__': 'pkg'}, {'__name__': 'pkg.module'}
def callback(global_):
import_util.import_('pkg')
- self.assertRaises(ValueError, import_util.import_, '', global_,
- fromlist=['top_level'], level=2)
+ with self.assertRaises(ValueError):
+ import_util.import_('', global_, fromlist=['top_level'],
+ level=2)
self.relative_import_test(create, globals_, callback)
def test_empty_name_w_level_0(self):
# [empty name]
- self.assertRaises(ValueError, import_util.import_, '')
+ with self.assertRaises(ValueError):
+ import_util.import_('')
def test_import_from_different_package(self):
# Test importing from a different package than the caller.
diff --git a/Lib/importlib/test/import_/util.py b/Lib/importlib/test/import_/util.py
index 5a1b727..649c5ed 100644
--- a/Lib/importlib/test/import_/util.py
+++ b/Lib/importlib/test/import_/util.py
@@ -1,5 +1,7 @@
import functools
+import importlib
import importlib._bootstrap
+import unittest
using___import__ = False
@@ -10,19 +12,12 @@ def import_(*args, **kwargs):
if using___import__:
return __import__(*args, **kwargs)
else:
- return importlib._bootstrap.__import__(*args, **kwargs)
+ return importlib.__import__(*args, **kwargs)
def importlib_only(fxn):
- """Decorator to mark which tests are not supported by the current
- implementation of __import__()."""
- def inner(*args, **kwargs):
- if using___import__:
- return
- else:
- return fxn(*args, **kwargs)
- functools.update_wrapper(inner, fxn)
- return inner
+ """Decorator to skip a test if using __builtins__.__import__."""
+ return unittest.skipIf(using___import__, "importlib-specific test")(fxn)
def mock_path_hook(*entries, importer):
diff --git a/Lib/importlib/test/regrtest.py b/Lib/importlib/test/regrtest.py
new file mode 100644
index 0000000..b103ae7d
--- /dev/null
+++ b/Lib/importlib/test/regrtest.py
@@ -0,0 +1,35 @@
+"""Run Python's standard test suite using importlib.__import__.
+
+Tests known to fail because of assumptions that importlib (properly)
+invalidates are automatically skipped if the entire test suite is run.
+Otherwise all command-line options valid for test.regrtest are also valid for
+this script.
+
+XXX FAILING
+ * test_import
+ - test_incorrect_code_name
+ file name differing between __file__ and co_filename (r68360 on trunk)
+ - test_import_by_filename
+ exception for trying to import by file name does not match
+
+"""
+import importlib
+import sys
+from test import regrtest
+
+if __name__ == '__main__':
+ __builtins__.__import__ = importlib.__import__
+
+ exclude = ['--exclude',
+ 'test_frozen', # Does not expect __loader__ attribute
+ 'test_pkg', # Does not expect __loader__ attribute
+ 'test_pydoc', # Does not expect __loader__ attribute
+ ]
+
+ # Switching on --exclude implies running all test but the ones listed, so
+ # only use it when one is not running an explicit test
+ if len(sys.argv) == 1:
+ # No programmatic way to specify tests to exclude
+ sys.argv.extend(exclude)
+
+ regrtest.main(quiet=True, verbose2=True)
diff --git a/Lib/importlib/test/source/test_abc_loader.py b/Lib/importlib/test/source/test_abc_loader.py
index 1ce83fb..3245907 100644
--- a/Lib/importlib/test/source/test_abc_loader.py
+++ b/Lib/importlib/test/source/test_abc_loader.py
@@ -1,14 +1,68 @@
import importlib
from importlib import abc
+
from .. import abc as testing_abc
from .. import util
from . import util as source_util
+
import imp
+import inspect
+import io
import marshal
import os
import sys
import types
import unittest
+import warnings
+
+
+class SourceOnlyLoaderMock(abc.SourceLoader):
+
+ # Globals that should be defined for all modules.
+ source = (b"_ = '::'.join([__name__, __file__, __cached__, __package__, "
+ b"repr(__loader__)])")
+
+ def __init__(self, path):
+ self.path = path
+
+ def get_data(self, path):
+ assert self.path == path
+ return self.source
+
+ def get_filename(self, fullname):
+ return self.path
+
+
+class SourceLoaderMock(SourceOnlyLoaderMock):
+
+ source_mtime = 1
+
+ def __init__(self, path, magic=imp.get_magic()):
+ super().__init__(path)
+ self.bytecode_path = imp.cache_from_source(self.path)
+ data = bytearray(magic)
+ data.extend(marshal._w_long(self.source_mtime))
+ code_object = compile(self.source, self.path, 'exec',
+ dont_inherit=True)
+ data.extend(marshal.dumps(code_object))
+ self.bytecode = bytes(data)
+ self.written = {}
+
+ def get_data(self, path):
+ if path == self.path:
+ return super().get_data(path)
+ elif path == self.bytecode_path:
+ return self.bytecode
+ else:
+ raise IOError
+
+ def path_mtime(self, path):
+ assert path == self.path
+ return self.source_mtime
+
+ def set_data(self, path, data):
+ self.written[path] = bytes(data)
+ return path == self.bytecode_path
class PyLoaderMock(abc.PyLoader):
@@ -33,17 +87,42 @@ class PyLoaderMock(abc.PyLoader):
return self.source
def is_package(self, name):
+ filename = os.path.basename(self.get_filename(name))
+ return os.path.splitext(filename)[0] == '__init__'
+
+ def source_path(self, name):
try:
- return '__init__' in self.module_paths[name]
+ return self.module_paths[name]
except KeyError:
raise ImportError
- def source_path(self, name):
+ def get_filename(self, name):
+ """Silence deprecation warning."""
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+ path = super().get_filename(name)
+ assert len(w) == 1
+ assert issubclass(w[0].category, PendingDeprecationWarning)
+ return path
+
+
+class PyLoaderCompatMock(PyLoaderMock):
+
+ """Mock that matches what is suggested to have a loader that is compatible
+ from Python 3.1 onwards."""
+
+ def get_filename(self, fullname):
try:
- return self.module_paths[name]
+ return self.module_paths[fullname]
except KeyError:
raise ImportError
+ def source_path(self, fullname):
+ try:
+ return self.get_filename(fullname)
+ except ImportError:
+ return None
+
class PyPycLoaderMock(abc.PyPycLoader, PyLoaderMock):
@@ -114,6 +193,13 @@ class PyPycLoaderMock(abc.PyPycLoader, PyLoaderMock):
except TypeError:
return '__init__' in self.bytecode_to_path[name]
+ def get_code(self, name):
+ with warnings.catch_warnings(record=True) as w:
+ warnings.simplefilter("always")
+ code_object = super().get_code(name)
+ assert len(w) == 1
+ assert issubclass(w[0].category, PendingDeprecationWarning)
+ return code_object
class PyLoaderTests(testing_abc.LoaderTests):
@@ -183,7 +269,8 @@ class PyLoaderTests(testing_abc.LoaderTests):
mock.source = b"1/0"
with util.uncache(name):
sys.modules[name] = module
- self.assertRaises(ZeroDivisionError, mock.load_module, name)
+ with self.assertRaises(ZeroDivisionError):
+ mock.load_module(name)
self.assertTrue(sys.modules[name] is module)
self.assertTrue(hasattr(module, 'blah'))
return mock
@@ -193,11 +280,20 @@ class PyLoaderTests(testing_abc.LoaderTests):
mock = self.mocker({name: os.path.join('path', 'to', 'mod')})
mock.source = b"1/0"
with util.uncache(name):
- self.assertRaises(ZeroDivisionError, mock.load_module, name)
+ with self.assertRaises(ZeroDivisionError):
+ mock.load_module(name)
self.assertTrue(name not in sys.modules)
return mock
+class PyLoaderCompatTests(PyLoaderTests):
+
+ """Test that the suggested code to make a loader that is compatible from
+ Python 3.1 forward works."""
+
+ mocker = PyLoaderCompatMock
+
+
class PyLoaderInterfaceTests(unittest.TestCase):
"""Tests for importlib.abc.PyLoader to make sure that when source_path()
@@ -207,38 +303,29 @@ class PyLoaderInterfaceTests(unittest.TestCase):
# No source path should lead to ImportError.
name = 'mod'
mock = PyLoaderMock({})
- with util.uncache(name):
- self.assertRaises(ImportError, mock.load_module, name)
+ with util.uncache(name), self.assertRaises(ImportError):
+ mock.load_module(name)
def test_source_path_is_None(self):
name = 'mod'
mock = PyLoaderMock({name: None})
- with util.uncache(name):
- self.assertRaises(ImportError, mock.load_module, name)
-
-
-class PyLoaderGetSourceTests(unittest.TestCase):
-
- """Tests for importlib.abc.PyLoader.get_source()."""
+ with util.uncache(name), self.assertRaises(ImportError):
+ mock.load_module(name)
- def test_default_encoding(self):
- # Should have no problems with UTF-8 text.
+ def test_get_filename_with_source_path(self):
+ # get_filename() should return what source_path() returns.
name = 'mod'
- mock = PyLoaderMock({name: os.path.join('path', 'to', 'mod')})
- source = 'x = "ü"'
- mock.source = source.encode('utf-8')
- returned_source = mock.get_source(name)
- self.assertEqual(returned_source, source)
+ path = os.path.join('path', 'to', 'source')
+ mock = PyLoaderMock({name: path})
+ with util.uncache(name):
+ self.assertEqual(mock.get_filename(name), path)
- def test_decoded_source(self):
- # Decoding should work.
+ def test_get_filename_no_source_path(self):
+ # get_filename() should raise ImportError if source_path returns None.
name = 'mod'
- mock = PyLoaderMock({name: os.path.join('path', 'to', 'mod')})
- source = "# coding: Latin-1\nx='ü'"
- assert source.encode('latin-1') != source.encode('utf-8')
- mock.source = source.encode('latin-1')
- returned_source = mock.get_source(name)
- self.assertEqual(returned_source, source)
+ mock = PyLoaderMock({name: None})
+ with util.uncache(name), self.assertRaises(ImportError):
+ mock.get_filename(name)
class PyPycLoaderTests(PyLoaderTests):
@@ -281,6 +368,38 @@ class PyPycLoaderTests(PyLoaderTests):
super().test_unloadable()
+class PyPycLoaderInterfaceTests(unittest.TestCase):
+
+ """Test for the interface of importlib.abc.PyPycLoader."""
+
+ def get_filename_check(self, src_path, bc_path, expect):
+ name = 'mod'
+ mock = PyPycLoaderMock({name: src_path}, {name: {'path': bc_path}})
+ with util.uncache(name):
+ assert mock.source_path(name) == src_path
+ assert mock.bytecode_path(name) == bc_path
+ self.assertEqual(mock.get_filename(name), expect)
+
+ def test_filename_with_source_bc(self):
+ # When source and bytecode paths present, return the source path.
+ self.get_filename_check('source_path', 'bc_path', 'source_path')
+
+ def test_filename_with_source_no_bc(self):
+ # With source but no bc, return source path.
+ self.get_filename_check('source_path', None, 'source_path')
+
+ def test_filename_with_no_source_bc(self):
+ # With not source but bc, return the bc path.
+ self.get_filename_check(None, 'bc_path', 'bc_path')
+
+ def test_filename_with_no_source_or_bc(self):
+ # With no source or bc, raise ImportError.
+ name = 'mod'
+ mock = PyPycLoaderMock({name: None}, {name: {'path': None}})
+ with util.uncache(name), self.assertRaises(ImportError):
+ mock.get_filename(name)
+
+
class SkipWritingBytecodeTests(unittest.TestCase):
"""Test that bytecode is properly handled based on
@@ -346,20 +465,28 @@ class BadBytecodeFailureTests(unittest.TestCase):
# A bad magic number should lead to an ImportError.
name = 'mod'
bad_magic = b'\x00\x00\x00\x00'
- mock = PyPycLoaderMock({name: None},
- {name: {'path': os.path.join('path', 'to', 'mod'),
- 'magic': bad_magic}})
- with util.uncache(name):
- self.assertRaises(ImportError, mock.load_module, name)
+ bc = {name:
+ {'path': os.path.join('path', 'to', 'mod'),
+ 'magic': bad_magic}}
+ mock = PyPycLoaderMock({name: None}, bc)
+ with util.uncache(name), self.assertRaises(ImportError):
+ mock.load_module(name)
+
+ def test_no_bytecode(self):
+ # Missing code object bytecode should lead to an EOFError.
+ name = 'mod'
+ bc = {name: {'path': os.path.join('path', 'to', 'mod'), 'bc': b''}}
+ mock = PyPycLoaderMock({name: None}, bc)
+ with util.uncache(name), self.assertRaises(EOFError):
+ mock.load_module(name)
def test_bad_bytecode(self):
- # Bad code object bytecode should lead to an ImportError.
+ # Malformed code object bytecode should lead to a ValueError.
name = 'mod'
- mock = PyPycLoaderMock({name: None},
- {name: {'path': os.path.join('path', 'to', 'mod'),
- 'bc': b''}})
- with util.uncache(name):
- self.assertRaises(EOFError, mock.load_module, name)
+ bc = {name: {'path': os.path.join('path', 'to', 'mod'), 'bc': b'1234'}}
+ mock = PyPycLoaderMock({name: None}, bc)
+ with util.uncache(name), self.assertRaises(ValueError):
+ mock.load_module(name)
def raise_ImportError(*args, **kwargs):
@@ -387,16 +514,16 @@ class MissingPathsTests(unittest.TestCase):
# If all *_path methods return None, raise ImportError.
name = 'mod'
mock = PyPycLoaderMock({name: None})
- with util.uncache(name):
- self.assertRaises(ImportError, mock.load_module, name)
+ with util.uncache(name), self.assertRaises(ImportError):
+ mock.load_module(name)
def test_source_path_ImportError(self):
# An ImportError from source_path should trigger an ImportError.
name = 'mod'
mock = PyPycLoaderMock({}, {name: {'path': os.path.join('path', 'to',
'mod')}})
- with util.uncache(name):
- self.assertRaises(ImportError, mock.load_module, name)
+ with util.uncache(name), self.assertRaises(ImportError):
+ mock.load_module(name)
def test_bytecode_path_ImportError(self):
# An ImportError from bytecode_path should trigger an ImportError.
@@ -404,16 +531,345 @@ class MissingPathsTests(unittest.TestCase):
mock = PyPycLoaderMock({name: os.path.join('path', 'to', 'mod')})
bad_meth = types.MethodType(raise_ImportError, mock)
mock.bytecode_path = bad_meth
- with util.uncache(name):
- self.assertRaises(ImportError, mock.load_module, name)
+ with util.uncache(name), self.assertRaises(ImportError):
+ mock.load_module(name)
+
+
+class SourceLoaderTestHarness(unittest.TestCase):
+
+ def setUp(self, *, is_package=True, **kwargs):
+ self.package = 'pkg'
+ if is_package:
+ self.path = os.path.join(self.package, '__init__.py')
+ self.name = self.package
+ else:
+ module_name = 'mod'
+ self.path = os.path.join(self.package, '.'.join(['mod', 'py']))
+ self.name = '.'.join([self.package, module_name])
+ self.cached = imp.cache_from_source(self.path)
+ self.loader = self.loader_mock(self.path, **kwargs)
+
+ def verify_module(self, module):
+ self.assertEqual(module.__name__, self.name)
+ self.assertEqual(module.__file__, self.path)
+ self.assertEqual(module.__cached__, self.cached)
+ self.assertEqual(module.__package__, self.package)
+ self.assertEqual(module.__loader__, self.loader)
+ values = module._.split('::')
+ self.assertEqual(values[0], self.name)
+ self.assertEqual(values[1], self.path)
+ self.assertEqual(values[2], self.cached)
+ self.assertEqual(values[3], self.package)
+ self.assertEqual(values[4], repr(self.loader))
+
+ def verify_code(self, code_object):
+ module = imp.new_module(self.name)
+ module.__file__ = self.path
+ module.__cached__ = self.cached
+ module.__package__ = self.package
+ module.__loader__ = self.loader
+ module.__path__ = []
+ exec(code_object, module.__dict__)
+ self.verify_module(module)
+
+
+class SourceOnlyLoaderTests(SourceLoaderTestHarness):
+
+ """Test importlib.abc.SourceLoader for source-only loading.
+
+ Reload testing is subsumed by the tests for
+ importlib.util.module_for_loader.
+
+ """
+
+ loader_mock = SourceOnlyLoaderMock
+
+ def test_get_source(self):
+ # Verify the source code is returned as a string.
+ # If an IOError is raised by get_data then raise ImportError.
+ expected_source = self.loader.source.decode('utf-8')
+ self.assertEqual(self.loader.get_source(self.name), expected_source)
+ def raise_IOError(path):
+ raise IOError
+ self.loader.get_data = raise_IOError
+ with self.assertRaises(ImportError):
+ self.loader.get_source(self.name)
+
+ def test_is_package(self):
+ # Properly detect when loading a package.
+ self.setUp(is_package=True)
+ self.assertTrue(self.loader.is_package(self.name))
+ self.setUp(is_package=False)
+ self.assertFalse(self.loader.is_package(self.name))
+
+ def test_get_code(self):
+ # Verify the code object is created.
+ code_object = self.loader.get_code(self.name)
+ self.verify_code(code_object)
+
+ def test_load_module(self):
+ # Loading a module should set __name__, __loader__, __package__,
+ # __path__ (for packages), __file__, and __cached__.
+ # The module should also be put into sys.modules.
+ with util.uncache(self.name):
+ module = self.loader.load_module(self.name)
+ self.verify_module(module)
+ self.assertEqual(module.__path__, [os.path.dirname(self.path)])
+ self.assertTrue(self.name in sys.modules)
+
+ def test_package_settings(self):
+ # __package__ needs to be set, while __path__ is set on if the module
+ # is a package.
+ # Testing the values for a package are covered by test_load_module.
+ self.setUp(is_package=False)
+ with util.uncache(self.name):
+ module = self.loader.load_module(self.name)
+ self.verify_module(module)
+ self.assertTrue(not hasattr(module, '__path__'))
+
+ def test_get_source_encoding(self):
+ # Source is considered encoded in UTF-8 by default unless otherwise
+ # specified by an encoding line.
+ source = "_ = 'ü'"
+ self.loader.source = source.encode('utf-8')
+ returned_source = self.loader.get_source(self.name)
+ self.assertEqual(returned_source, source)
+ source = "# coding: latin-1\n_ = ü"
+ self.loader.source = source.encode('latin-1')
+ returned_source = self.loader.get_source(self.name)
+ self.assertEqual(returned_source, source)
+
+
+@unittest.skipIf(sys.dont_write_bytecode, "sys.dont_write_bytecode is true")
+class SourceLoaderBytecodeTests(SourceLoaderTestHarness):
+
+ """Test importlib.abc.SourceLoader's use of bytecode.
+
+ Source-only testing handled by SourceOnlyLoaderTests.
+
+ """
+
+ loader_mock = SourceLoaderMock
+
+ def verify_code(self, code_object, *, bytecode_written=False):
+ super().verify_code(code_object)
+ if bytecode_written:
+ self.assertIn(self.cached, self.loader.written)
+ data = bytearray(imp.get_magic())
+ data.extend(marshal._w_long(self.loader.source_mtime))
+ data.extend(marshal.dumps(code_object))
+ self.assertEqual(self.loader.written[self.cached], bytes(data))
+
+ def test_code_with_everything(self):
+ # When everything should work.
+ code_object = self.loader.get_code(self.name)
+ self.verify_code(code_object)
+
+ def test_no_bytecode(self):
+ # If no bytecode exists then move on to the source.
+ self.loader.bytecode_path = "<does not exist>"
+ # Sanity check
+ with self.assertRaises(IOError):
+ bytecode_path = imp.cache_from_source(self.path)
+ self.loader.get_data(bytecode_path)
+ code_object = self.loader.get_code(self.name)
+ self.verify_code(code_object, bytecode_written=True)
+
+ def test_code_bad_timestamp(self):
+ # Bytecode is only used when the timestamp matches the source EXACTLY.
+ for source_mtime in (0, 2):
+ assert source_mtime != self.loader.source_mtime
+ original = self.loader.source_mtime
+ self.loader.source_mtime = source_mtime
+ # If bytecode is used then EOFError would be raised by marshal.
+ self.loader.bytecode = self.loader.bytecode[8:]
+ code_object = self.loader.get_code(self.name)
+ self.verify_code(code_object, bytecode_written=True)
+ self.loader.source_mtime = original
+
+ def test_code_bad_magic(self):
+ # Skip over bytecode with a bad magic number.
+ self.setUp(magic=b'0000')
+ # If bytecode is used then EOFError would be raised by marshal.
+ self.loader.bytecode = self.loader.bytecode[8:]
+ code_object = self.loader.get_code(self.name)
+ self.verify_code(code_object, bytecode_written=True)
+
+ def test_dont_write_bytecode(self):
+ # Bytecode is not written if sys.dont_write_bytecode is true.
+ # Can assume it is false already thanks to the skipIf class decorator.
+ try:
+ sys.dont_write_bytecode = True
+ self.loader.bytecode_path = "<does not exist>"
+ code_object = self.loader.get_code(self.name)
+ self.assertNotIn(self.cached, self.loader.written)
+ finally:
+ sys.dont_write_bytecode = False
+
+ def test_no_set_data(self):
+ # If set_data is not defined, one can still read bytecode.
+ self.setUp(magic=b'0000')
+ original_set_data = self.loader.__class__.set_data
+ try:
+ del self.loader.__class__.set_data
+ code_object = self.loader.get_code(self.name)
+ self.verify_code(code_object)
+ finally:
+ self.loader.__class__.set_data = original_set_data
+
+ def test_set_data_raises_exceptions(self):
+ # Raising NotImplementedError or IOError is okay for set_data.
+ def raise_exception(exc):
+ def closure(*args, **kwargs):
+ raise exc
+ return closure
+
+ self.setUp(magic=b'0000')
+ self.loader.set_data = raise_exception(NotImplementedError)
+ code_object = self.loader.get_code(self.name)
+ self.verify_code(code_object)
+
+
+class SourceLoaderGetSourceTests(unittest.TestCase):
+
+ """Tests for importlib.abc.SourceLoader.get_source()."""
+
+ def test_default_encoding(self):
+ # Should have no problems with UTF-8 text.
+ name = 'mod'
+ mock = SourceOnlyLoaderMock('mod.file')
+ source = 'x = "ü"'
+ mock.source = source.encode('utf-8')
+ returned_source = mock.get_source(name)
+ self.assertEqual(returned_source, source)
+
+ def test_decoded_source(self):
+ # Decoding should work.
+ name = 'mod'
+ mock = SourceOnlyLoaderMock("mod.file")
+ source = "# coding: Latin-1\nx='ü'"
+ assert source.encode('latin-1') != source.encode('utf-8')
+ mock.source = source.encode('latin-1')
+ returned_source = mock.get_source(name)
+ self.assertEqual(returned_source, source)
+
+ def test_universal_newlines(self):
+ # PEP 302 says universal newlines should be used.
+ name = 'mod'
+ mock = SourceOnlyLoaderMock('mod.file')
+ source = "x = 42\r\ny = -13\r\n"
+ mock.source = source.encode('utf-8')
+ expect = io.IncrementalNewlineDecoder(None, True).decode(source)
+ self.assertEqual(mock.get_source(name), expect)
+
+class AbstractMethodImplTests(unittest.TestCase):
+
+ """Test the concrete abstractmethod implementations."""
+
+ class Loader(abc.Loader):
+ def load_module(self, fullname):
+ super().load_module(fullname)
+
+ class Finder(abc.Finder):
+ def find_module(self, _):
+ super().find_module(_)
+
+ class ResourceLoader(Loader, abc.ResourceLoader):
+ def get_data(self, _):
+ super().get_data(_)
+
+ class InspectLoader(Loader, abc.InspectLoader):
+ def is_package(self, _):
+ super().is_package(_)
+
+ def get_code(self, _):
+ super().get_code(_)
+
+ def get_source(self, _):
+ super().get_source(_)
+
+ class ExecutionLoader(InspectLoader, abc.ExecutionLoader):
+ def get_filename(self, _):
+ super().get_filename(_)
+
+ class SourceLoader(ResourceLoader, ExecutionLoader, abc.SourceLoader):
+ pass
+
+ class PyLoader(ResourceLoader, InspectLoader, abc.PyLoader):
+ def source_path(self, _):
+ super().source_path(_)
+
+ class PyPycLoader(PyLoader, abc.PyPycLoader):
+ def bytecode_path(self, _):
+ super().bytecode_path(_)
+
+ def source_mtime(self, _):
+ super().source_mtime(_)
+
+ def write_bytecode(self, _, _2):
+ super().write_bytecode(_, _2)
+
+ def raises_NotImplementedError(self, ins, *args):
+ for method_name in args:
+ method = getattr(ins, method_name)
+ arg_count = len(inspect.getfullargspec(method)[0]) - 1
+ args = [''] * arg_count
+ try:
+ method(*args)
+ except NotImplementedError:
+ pass
+ else:
+ msg = "{}.{} did not raise NotImplementedError"
+ self.fail(msg.format(ins.__class__.__name__, method_name))
+
+ def test_Loader(self):
+ self.raises_NotImplementedError(self.Loader(), 'load_module')
+
+ # XXX misplaced; should be somewhere else
+ def test_Finder(self):
+ self.raises_NotImplementedError(self.Finder(), 'find_module')
+
+ def test_ResourceLoader(self):
+ self.raises_NotImplementedError(self.ResourceLoader(), 'load_module',
+ 'get_data')
+
+ def test_InspectLoader(self):
+ self.raises_NotImplementedError(self.InspectLoader(), 'load_module',
+ 'is_package', 'get_code', 'get_source')
+
+ def test_ExecutionLoader(self):
+ self.raises_NotImplementedError(self.ExecutionLoader(), 'load_module',
+ 'is_package', 'get_code', 'get_source',
+ 'get_filename')
+
+ def test_SourceLoader(self):
+ ins = self.SourceLoader()
+ # Required abstractmethods.
+ self.raises_NotImplementedError(ins, 'get_filename', 'get_data')
+ # Optional abstractmethods.
+ self.raises_NotImplementedError(ins,'path_mtime', 'set_data')
+
+ def test_PyLoader(self):
+ self.raises_NotImplementedError(self.PyLoader(), 'source_path',
+ 'get_data', 'is_package')
+
+ def test_PyPycLoader(self):
+ self.raises_NotImplementedError(self.PyPycLoader(), 'source_path',
+ 'source_mtime', 'bytecode_path',
+ 'write_bytecode')
def test_main():
from test.support import run_unittest
- run_unittest(PyLoaderTests, PyLoaderInterfaceTests, PyLoaderGetSourceTests,
- PyPycLoaderTests, SkipWritingBytecodeTests,
- RegeneratedBytecodeTests, BadBytecodeFailureTests,
- MissingPathsTests)
+ run_unittest(PyLoaderTests, PyLoaderCompatTests,
+ PyLoaderInterfaceTests,
+ PyPycLoaderTests, PyPycLoaderInterfaceTests,
+ SkipWritingBytecodeTests, RegeneratedBytecodeTests,
+ BadBytecodeFailureTests, MissingPathsTests,
+ SourceOnlyLoaderTests,
+ SourceLoaderBytecodeTests,
+ SourceLoaderGetSourceTests,
+ AbstractMethodImplTests)
if __name__ == '__main__':
diff --git a/Lib/importlib/test/source/test_case_sensitivity.py b/Lib/importlib/test/source/test_case_sensitivity.py
index 6fad881..73777de 100644
--- a/Lib/importlib/test/source/test_case_sensitivity.py
+++ b/Lib/importlib/test/source/test_case_sensitivity.py
@@ -19,7 +19,9 @@ class CaseSensitivityTest(unittest.TestCase):
assert name != name.lower()
def find(self, path):
- finder = _bootstrap._PyPycFileFinder(path)
+ finder = _bootstrap._FileFinder(path,
+ _bootstrap._SourceFinderDetails(),
+ _bootstrap._SourcelessFinderDetails())
return finder.find_module(self.name)
def sensitivity_test(self):
@@ -27,7 +29,7 @@ class CaseSensitivityTest(unittest.TestCase):
sensitive_pkg = 'sensitive.{0}'.format(self.name)
insensitive_pkg = 'insensitive.{0}'.format(self.name.lower())
context = source_util.create_modules(insensitive_pkg, sensitive_pkg)
- with context as mapping:
+ with context as mapping:
sensitive_path = os.path.join(mapping['.root'], 'sensitive')
insensitive_path = os.path.join(mapping['.root'], 'insensitive')
return self.find(sensitive_path), self.find(insensitive_path)
@@ -37,7 +39,7 @@ class CaseSensitivityTest(unittest.TestCase):
env.unset('PYTHONCASEOK')
sensitive, insensitive = self.sensitivity_test()
self.assertTrue(hasattr(sensitive, 'load_module'))
- self.assertIn(self.name, sensitive._base_path)
+ self.assertIn(self.name, sensitive.get_filename(self.name))
self.assertIsNone(insensitive)
def test_insensitive(self):
@@ -45,9 +47,9 @@ class CaseSensitivityTest(unittest.TestCase):
env.set('PYTHONCASEOK', '1')
sensitive, insensitive = self.sensitivity_test()
self.assertTrue(hasattr(sensitive, 'load_module'))
- self.assertIn(self.name, sensitive._base_path)
+ self.assertIn(self.name, sensitive.get_filename(self.name))
self.assertTrue(hasattr(insensitive, 'load_module'))
- self.assertIn(self.name, insensitive._base_path)
+ self.assertIn(self.name, insensitive.get_filename(self.name))
def test_main():
diff --git a/Lib/importlib/test/source/test_file_loader.py b/Lib/importlib/test/source/test_file_loader.py
index 145d076..0ffe78d 100644
--- a/Lib/importlib/test/source/test_file_loader.py
+++ b/Lib/importlib/test/source/test_file_loader.py
@@ -1,15 +1,20 @@
import importlib
from importlib import _bootstrap
from .. import abc
+from .. import util
from . import util as source_util
import imp
+import marshal
import os
import py_compile
+import shutil
import stat
import sys
import unittest
+from test.support import make_legacy_pyc
+
class SimpleTest(unittest.TestCase):
@@ -21,8 +26,7 @@ class SimpleTest(unittest.TestCase):
# [basic]
def test_module(self):
with source_util.create_modules('_temp') as mapping:
- loader = _bootstrap._PyPycFileLoader('_temp', mapping['_temp'],
- False)
+ loader = _bootstrap._SourceFileLoader('_temp', mapping['_temp'])
module = loader.load_module('_temp')
self.assertTrue('_temp' in sys.modules)
check = {'__name__': '_temp', '__file__': mapping['_temp'],
@@ -32,9 +36,8 @@ class SimpleTest(unittest.TestCase):
def test_package(self):
with source_util.create_modules('_pkg.__init__') as mapping:
- loader = _bootstrap._PyPycFileLoader('_pkg',
- mapping['_pkg.__init__'],
- True)
+ loader = _bootstrap._SourceFileLoader('_pkg',
+ mapping['_pkg.__init__'])
module = loader.load_module('_pkg')
self.assertTrue('_pkg' in sys.modules)
check = {'__name__': '_pkg', '__file__': mapping['_pkg.__init__'],
@@ -46,8 +49,8 @@ class SimpleTest(unittest.TestCase):
def test_lacking_parent(self):
with source_util.create_modules('_pkg.__init__', '_pkg.mod')as mapping:
- loader = _bootstrap._PyPycFileLoader('_pkg.mod',
- mapping['_pkg.mod'], False)
+ loader = _bootstrap._SourceFileLoader('_pkg.mod',
+ mapping['_pkg.mod'])
module = loader.load_module('_pkg.mod')
self.assertTrue('_pkg.mod' in sys.modules)
check = {'__name__': '_pkg.mod', '__file__': mapping['_pkg.mod'],
@@ -61,8 +64,7 @@ class SimpleTest(unittest.TestCase):
def test_module_reuse(self):
with source_util.create_modules('_temp') as mapping:
- loader = _bootstrap._PyPycFileLoader('_temp', mapping['_temp'],
- False)
+ loader = _bootstrap._SourceFileLoader('_temp', mapping['_temp'])
module = loader.load_module('_temp')
module_id = id(module)
module_dict_id = id(module.__dict__)
@@ -72,7 +74,7 @@ class SimpleTest(unittest.TestCase):
# everything that has happened above can be too fast;
# force an mtime on the source that is guaranteed to be different
# than the original mtime.
- loader.source_mtime = self.fake_mtime(loader.source_mtime)
+ loader.path_mtime = self.fake_mtime(loader.path_mtime)
module = loader.load_module('_temp')
self.assertTrue('testing_var' in module.__dict__,
"'testing_var' not in "
@@ -92,9 +94,9 @@ class SimpleTest(unittest.TestCase):
setattr(orig_module, attr, value)
with open(mapping[name], 'w') as file:
file.write('+++ bad syntax +++')
- loader = _bootstrap._PyPycFileLoader('_temp', mapping['_temp'],
- False)
- self.assertRaises(SyntaxError, loader.load_module, name)
+ loader = _bootstrap._SourceFileLoader('_temp', mapping['_temp'])
+ with self.assertRaises(SyntaxError):
+ loader.load_module(name)
for attr in attributes:
self.assertEqual(getattr(orig_module, attr), value)
@@ -103,16 +105,34 @@ class SimpleTest(unittest.TestCase):
with source_util.create_modules('_temp') as mapping:
with open(mapping['_temp'], 'w') as file:
file.write('=')
- loader = _bootstrap._PyPycFileLoader('_temp', mapping['_temp'],
- False)
- self.assertRaises(SyntaxError, loader.load_module, '_temp')
+ loader = _bootstrap._SourceFileLoader('_temp', mapping['_temp'])
+ with self.assertRaises(SyntaxError):
+ loader.load_module('_temp')
self.assertTrue('_temp' not in sys.modules)
+ def test_file_from_empty_string_dir(self):
+ # Loading a module found from an empty string entry on sys.path should
+ # not only work, but keep all attributes relative.
+ file_path = '_temp.py'
+ with open(file_path, 'w') as file:
+ file.write("# test file for importlib")
+ try:
+ with util.uncache('_temp'):
+ loader = _bootstrap._SourceFileLoader('_temp', file_path)
+ mod = loader.load_module('_temp')
+ self.assertEqual(file_path, mod.__file__)
+ self.assertEqual(imp.cache_from_source(file_path),
+ mod.__cached__)
+ finally:
+ os.unlink(file_path)
+ pycache = os.path.dirname(imp.cache_from_source(file_path))
+ shutil.rmtree(pycache)
+
class BadBytecodeTest(unittest.TestCase):
def import_(self, file, module_name):
- loader = _bootstrap._PyPycFileLoader(module_name, file, False)
+ loader = self.loader(module_name, file)
module = loader.load_module(module_name)
self.assertTrue(module_name in sys.modules)
@@ -125,106 +145,162 @@ class BadBytecodeTest(unittest.TestCase):
except KeyError:
pass
py_compile.compile(mapping[name])
- bytecode_path = source_util.bytecode_path(mapping[name])
- with open(bytecode_path, 'rb') as file:
- bc = file.read()
- new_bc = manipulator(bc)
- with open(bytecode_path, 'wb') as file:
- if new_bc:
- file.write(new_bc)
- if del_source:
+ if not del_source:
+ bytecode_path = imp.cache_from_source(mapping[name])
+ else:
os.unlink(mapping[name])
+ bytecode_path = make_legacy_pyc(mapping[name])
+ if manipulator:
+ with open(bytecode_path, 'rb') as file:
+ bc = file.read()
+ new_bc = manipulator(bc)
+ with open(bytecode_path, 'wb') as file:
+ if new_bc is not None:
+ file.write(new_bc)
return bytecode_path
- @source_util.writes_bytecode_files
- def test_empty_file(self):
- # When a .pyc is empty, regenerate it if possible, else raise
- # ImportError.
+ def _test_empty_file(self, test, *, del_source=False):
with source_util.create_modules('_temp') as mapping:
bc_path = self.manipulate_bytecode('_temp', mapping,
- lambda bc: None)
- self.import_(mapping['_temp'], '_temp')
- with open(bc_path, 'rb') as file:
- self.assertGreater(len(file.read()), 8)
- self.manipulate_bytecode('_temp', mapping, lambda bc: None,
- del_source=True)
- with self.assertRaises(ImportError):
- self.import_(mapping['_temp'], '_temp')
+ lambda bc: b'',
+ del_source=del_source)
+ test('_temp', mapping, bc_path)
@source_util.writes_bytecode_files
- def test_partial_magic(self):
+ def _test_partial_magic(self, test, *, del_source=False):
# When their are less than 4 bytes to a .pyc, regenerate it if
# possible, else raise ImportError.
with source_util.create_modules('_temp') as mapping:
bc_path = self.manipulate_bytecode('_temp', mapping,
- lambda bc: bc[:3])
- self.import_(mapping['_temp'], '_temp')
- with open(bc_path, 'rb') as file:
- self.assertGreater(len(file.read()), 8)
- self.manipulate_bytecode('_temp', mapping, lambda bc: bc[:3],
- del_source=True)
+ lambda bc: bc[:3],
+ del_source=del_source)
+ test('_temp', mapping, bc_path)
+
+ def _test_magic_only(self, test, *, del_source=False):
+ with source_util.create_modules('_temp') as mapping:
+ bc_path = self.manipulate_bytecode('_temp', mapping,
+ lambda bc: bc[:4],
+ del_source=del_source)
+ test('_temp', mapping, bc_path)
+
+ def _test_partial_timestamp(self, test, *, del_source=False):
+ with source_util.create_modules('_temp') as mapping:
+ bc_path = self.manipulate_bytecode('_temp', mapping,
+ lambda bc: bc[:7],
+ del_source=del_source)
+ test('_temp', mapping, bc_path)
+
+ def _test_no_marshal(self, *, del_source=False):
+ with source_util.create_modules('_temp') as mapping:
+ bc_path = self.manipulate_bytecode('_temp', mapping,
+ lambda bc: bc[:8],
+ del_source=del_source)
+ file_path = mapping['_temp'] if not del_source else bc_path
+ with self.assertRaises(EOFError):
+ self.import_(file_path, '_temp')
+
+ def _test_non_code_marshal(self, *, del_source=False):
+ with source_util.create_modules('_temp') as mapping:
+ bytecode_path = self.manipulate_bytecode('_temp', mapping,
+ lambda bc: bc[:8] + marshal.dumps(b'abcd'),
+ del_source=del_source)
+ file_path = mapping['_temp'] if not del_source else bytecode_path
with self.assertRaises(ImportError):
- self.import_(mapping['_temp'], '_temp')
+ self.import_(file_path, '_temp')
+
+ def _test_bad_marshal(self, *, del_source=False):
+ with source_util.create_modules('_temp') as mapping:
+ bytecode_path = self.manipulate_bytecode('_temp', mapping,
+ lambda bc: bc[:8] + b'<test>',
+ del_source=del_source)
+ file_path = mapping['_temp'] if not del_source else bytecode_path
+ with self.assertRaises(ValueError):
+ self.import_(file_path, '_temp')
+
+ def _test_bad_magic(self, test, *, del_source=False):
+ with source_util.create_modules('_temp') as mapping:
+ bc_path = self.manipulate_bytecode('_temp', mapping,
+ lambda bc: b'\x00\x00\x00\x00' + bc[4:])
+ test('_temp', mapping, bc_path)
+
+
+class SourceLoaderBadBytecodeTest(BadBytecodeTest):
+
+ loader = _bootstrap._SourceFileLoader
+
+ @source_util.writes_bytecode_files
+ def test_empty_file(self):
+ # When a .pyc is empty, regenerate it if possible, else raise
+ # ImportError.
+ def test(name, mapping, bytecode_path):
+ self.import_(mapping[name], name)
+ with open(bytecode_path, 'rb') as file:
+ self.assertGreater(len(file.read()), 8)
+
+ self._test_empty_file(test)
+
+ def test_partial_magic(self):
+ def test(name, mapping, bytecode_path):
+ self.import_(mapping[name], name)
+ with open(bytecode_path, 'rb') as file:
+ self.assertGreater(len(file.read()), 8)
+
+ self._test_partial_magic(test)
@source_util.writes_bytecode_files
def test_magic_only(self):
# When there is only the magic number, regenerate the .pyc if possible,
# else raise EOFError.
- with source_util.create_modules('_temp') as mapping:
- bc_path = self.manipulate_bytecode('_temp', mapping,
- lambda bc: bc[:4])
- self.import_(mapping['_temp'], '_temp')
- with open(bc_path, 'rb') as file:
+ def test(name, mapping, bytecode_path):
+ self.import_(mapping[name], name)
+ with open(bytecode_path, 'rb') as file:
self.assertGreater(len(file.read()), 8)
- self.manipulate_bytecode('_temp', mapping, lambda bc: bc[:4],
- del_source=True)
- with self.assertRaises(EOFError):
- self.import_(mapping['_temp'], '_temp')
+
+ @source_util.writes_bytecode_files
+ def test_bad_magic(self):
+ # When the magic number is different, the bytecode should be
+ # regenerated.
+ def test(name, mapping, bytecode_path):
+ self.import_(mapping[name], name)
+ with open(bytecode_path, 'rb') as bytecode_file:
+ self.assertEqual(bytecode_file.read(4), imp.get_magic())
+
+ self._test_bad_magic(test)
@source_util.writes_bytecode_files
def test_partial_timestamp(self):
# When the timestamp is partial, regenerate the .pyc, else
# raise EOFError.
- with source_util.create_modules('_temp') as mapping:
- bc_path = self.manipulate_bytecode('_temp', mapping,
- lambda bc: bc[:7])
- self.import_(mapping['_temp'], '_temp')
+ def test(name, mapping, bc_path):
+ self.import_(mapping[name], name)
with open(bc_path, 'rb') as file:
self.assertGreater(len(file.read()), 8)
- self.manipulate_bytecode('_temp', mapping, lambda bc: bc[:7],
- del_source=True)
- with self.assertRaises(EOFError):
- self.import_(mapping['_temp'], '_temp')
@source_util.writes_bytecode_files
def test_no_marshal(self):
# When there is only the magic number and timestamp, raise EOFError.
- with source_util.create_modules('_temp') as mapping:
- bc_path = self.manipulate_bytecode('_temp', mapping,
- lambda bc: bc[:8])
- with self.assertRaises(EOFError):
- self.import_(mapping['_temp'], '_temp')
+ self._test_no_marshal()
@source_util.writes_bytecode_files
- def test_bad_magic(self):
- # When the magic number is different, the bytecode should be
- # regenerated.
- with source_util.create_modules('_temp') as mapping:
- bc_path = self.manipulate_bytecode('_temp', mapping,
- lambda bc: b'\x00\x00\x00\x00' + bc[4:])
- self.import_(mapping['_temp'], '_temp')
- with open(bc_path, 'rb') as bytecode_file:
- self.assertEqual(bytecode_file.read(4), imp.get_magic())
+ def test_non_code_marshal(self):
+ self._test_non_code_marshal()
+ # XXX ImportError when sourceless
+
+ # [bad marshal]
+ @source_util.writes_bytecode_files
+ def test_bad_marshal(self):
+ # Bad marshal data should raise a ValueError.
+ self._test_bad_marshal()
# [bad timestamp]
@source_util.writes_bytecode_files
- def test_bad_bytecode(self):
+ def test_old_timestamp(self):
# When the timestamp is older than the source, bytecode should be
# regenerated.
zeros = b'\x00\x00\x00\x00'
with source_util.create_modules('_temp') as mapping:
py_compile.compile(mapping['_temp'])
- bytecode_path = source_util.bytecode_path(mapping['_temp'])
+ bytecode_path = imp.cache_from_source(mapping['_temp'])
with open(bytecode_path, 'r+b') as bytecode_file:
bytecode_file.seek(4)
bytecode_file.write(zeros)
@@ -235,22 +311,6 @@ class BadBytecodeTest(unittest.TestCase):
bytecode_file.seek(4)
self.assertEqual(bytecode_file.read(4), source_timestamp)
- # [bad marshal]
- @source_util.writes_bytecode_files
- def test_bad_marshal(self):
- # Bad marshal data should raise a ValueError.
- with source_util.create_modules('_temp') as mapping:
- bytecode_path = source_util.bytecode_path(mapping['_temp'])
- source_mtime = os.path.getmtime(mapping['_temp'])
- source_timestamp = importlib._w_long(source_mtime)
- with open(bytecode_path, 'wb') as bytecode_file:
- bytecode_file.write(imp.get_magic())
- bytecode_file.write(source_timestamp)
- bytecode_file.write(b'AAAA')
- self.assertRaises(ValueError, self.import_, mapping['_temp'],
- '_temp')
- self.assertTrue('_temp' not in sys.modules)
-
# [bytecode read-only]
@source_util.writes_bytecode_files
def test_read_only_bytecode(self):
@@ -258,7 +318,7 @@ class BadBytecodeTest(unittest.TestCase):
with source_util.create_modules('_temp') as mapping:
# Create bytecode that will need to be re-created.
py_compile.compile(mapping['_temp'])
- bytecode_path = source_util.bytecode_path(mapping['_temp'])
+ bytecode_path = imp.cache_from_source(mapping['_temp'])
with open(bytecode_path, 'r+b') as bytecode_file:
bytecode_file.seek(0)
bytecode_file.write(b'\x00\x00\x00\x00')
@@ -273,9 +333,57 @@ class BadBytecodeTest(unittest.TestCase):
os.chmod(bytecode_path, stat.S_IWUSR)
+class SourcelessLoaderBadBytecodeTest(BadBytecodeTest):
+
+ loader = _bootstrap._SourcelessFileLoader
+
+ def test_empty_file(self):
+ def test(name, mapping, bytecode_path):
+ with self.assertRaises(ImportError):
+ self.import_(bytecode_path, name)
+
+ self._test_empty_file(test, del_source=True)
+
+ def test_partial_magic(self):
+ def test(name, mapping, bytecode_path):
+ with self.assertRaises(ImportError):
+ self.import_(bytecode_path, name)
+ self._test_partial_magic(test, del_source=True)
+
+ def test_magic_only(self):
+ def test(name, mapping, bytecode_path):
+ with self.assertRaises(EOFError):
+ self.import_(bytecode_path, name)
+
+ self._test_magic_only(test, del_source=True)
+
+ def test_bad_magic(self):
+ def test(name, mapping, bytecode_path):
+ with self.assertRaises(ImportError):
+ self.import_(bytecode_path, name)
+
+ self._test_bad_magic(test, del_source=True)
+
+ def test_partial_timestamp(self):
+ def test(name, mapping, bytecode_path):
+ with self.assertRaises(EOFError):
+ self.import_(bytecode_path, name)
+
+ self._test_partial_timestamp(test, del_source=True)
+
+ def test_no_marshal(self):
+ self._test_no_marshal(del_source=True)
+
+ def test_non_code_marshal(self):
+ self._test_non_code_marshal(del_source=True)
+
+
def test_main():
from test.support import run_unittest
- run_unittest(SimpleTest, BadBytecodeTest)
+ run_unittest(SimpleTest,
+ SourceLoaderBadBytecodeTest,
+ SourcelessLoaderBadBytecodeTest
+ )
if __name__ == '__main__':
diff --git a/Lib/importlib/test/source/test_finder.py b/Lib/importlib/test/source/test_finder.py
index c495c5a..7b9088d 100644
--- a/Lib/importlib/test/source/test_finder.py
+++ b/Lib/importlib/test/source/test_finder.py
@@ -1,7 +1,9 @@
from importlib import _bootstrap
from .. import abc
from . import util as source_util
+from test.support import make_legacy_pyc
import os
+import errno
import py_compile
import unittest
import warnings
@@ -32,7 +34,9 @@ class FinderTests(abc.FinderTests):
"""
def import_(self, root, module):
- finder = _bootstrap._PyPycFileFinder(root)
+ finder = _bootstrap._FileFinder(root,
+ _bootstrap._SourceFinderDetails(),
+ _bootstrap._SourcelessFinderDetails())
return finder.find_module(module)
def run_test(self, test, create=None, *, compile_=None, unlink=None):
@@ -52,6 +56,14 @@ class FinderTests(abc.FinderTests):
if unlink:
for name in unlink:
os.unlink(mapping[name])
+ try:
+ make_legacy_pyc(mapping[name])
+ except OSError as error:
+ # Some tests do not set compile_=True so the source
+ # module will not get compiled and there will be no
+ # PEP 3147 pyc file to rename.
+ if error.errno != errno.ENOENT:
+ raise
loader = self.import_(mapping['.root'], test)
self.assertTrue(hasattr(loader, 'load_module'))
return loader
@@ -60,7 +72,8 @@ class FinderTests(abc.FinderTests):
# [top-level source]
self.run_test('top_level')
# [top-level bc]
- self.run_test('top_level', compile_={'top_level'}, unlink={'top_level'})
+ self.run_test('top_level', compile_={'top_level'},
+ unlink={'top_level'})
# [top-level both]
self.run_test('top_level', compile_={'top_level'})
@@ -97,15 +110,14 @@ class FinderTests(abc.FinderTests):
with context as mapping:
os.unlink(mapping['pkg.sub.__init__'])
pkg_dir = os.path.dirname(mapping['pkg.__init__'])
- self.assertRaises(ImportWarning, self.import_, pkg_dir,
- 'pkg.sub')
+ with self.assertRaises(ImportWarning):
+ self.import_(pkg_dir, 'pkg.sub')
# [package over modules]
def test_package_over_module(self):
- # XXX This is not a blackbox test!
name = '_temp'
loader = self.run_test(name, {'{0}.__init__'.format(name), name})
- self.assertTrue('__init__' in loader._base_path)
+ self.assertTrue('__init__' in loader.get_filename(name))
def test_failure(self):
@@ -117,8 +129,19 @@ class FinderTests(abc.FinderTests):
def test_empty_dir(self):
with warnings.catch_warnings():
warnings.simplefilter("error", ImportWarning)
- self.assertRaises(ImportWarning, self.run_test, 'pkg',
- {'pkg.__init__'}, unlink={'pkg.__init__'})
+ with self.assertRaises(ImportWarning):
+ self.run_test('pkg', {'pkg.__init__'}, unlink={'pkg.__init__'})
+
+ def test_empty_string_for_dir(self):
+ # The empty string from sys.path means to search in the cwd.
+ finder = _bootstrap._FileFinder('', _bootstrap._SourceFinderDetails())
+ with open('mod.py', 'w') as file:
+ file.write("# test file for importlib")
+ try:
+ loader = finder.find_module('mod')
+ self.assertTrue(hasattr(loader, 'load_module'))
+ finally:
+ os.unlink('mod.py')
def test_main():
diff --git a/Lib/importlib/test/source/test_path_hook.py b/Lib/importlib/test/source/test_path_hook.py
index 3efb3be..374f7b6 100644
--- a/Lib/importlib/test/source/test_path_hook.py
+++ b/Lib/importlib/test/source/test_path_hook.py
@@ -8,11 +8,14 @@ class PathHookTest(unittest.TestCase):
"""Test the path hook for source."""
def test_success(self):
- # XXX Only work on existing directories?
with source_util.create_modules('dummy') as mapping:
- self.assertTrue(hasattr(_bootstrap._FileFinder(mapping['.root']),
+ self.assertTrue(hasattr(_bootstrap._file_path_hook(mapping['.root']),
'find_module'))
+ def test_empty_string(self):
+ # The empty string represents the cwd.
+ self.assertTrue(hasattr(_bootstrap._file_path_hook(''), 'find_module'))
+
def test_main():
from test.support import run_unittest
diff --git a/Lib/importlib/test/source/test_source_encoding.py b/Lib/importlib/test/source/test_source_encoding.py
index 3734712..794a3df 100644
--- a/Lib/importlib/test/source/test_source_encoding.py
+++ b/Lib/importlib/test/source/test_source_encoding.py
@@ -33,10 +33,10 @@ class EncodingTest(unittest.TestCase):
def run_test(self, source):
with source_util.create_modules(self.module_name) as mapping:
- with open(mapping[self.module_name], 'wb')as file:
+ with open(mapping[self.module_name], 'wb') as file:
file.write(source)
- loader = _bootstrap._PyPycFileLoader(self.module_name,
- mapping[self.module_name], False)
+ loader = _bootstrap._SourceFileLoader(self.module_name,
+ mapping[self.module_name])
return loader.load_module(self.module_name)
def create_source(self, encoding):
@@ -81,7 +81,8 @@ class EncodingTest(unittest.TestCase):
# [BOM conflict]
def test_bom_conflict(self):
source = codecs.BOM_UTF8 + self.create_source('latin-1')
- self.assertRaises(SyntaxError, self.run_test, source)
+ with self.assertRaises(SyntaxError):
+ self.run_test(source)
class LineEndingTest(unittest.TestCase):
@@ -96,8 +97,8 @@ class LineEndingTest(unittest.TestCase):
with source_util.create_modules(module_name) as mapping:
with open(mapping[module_name], 'wb') as file:
file.write(source)
- loader = _bootstrap._PyPycFileLoader(module_name,
- mapping[module_name], False)
+ loader = _bootstrap._SourceFileLoader(module_name,
+ mapping[module_name])
return loader.load_module(module_name)
# [cr]
diff --git a/Lib/importlib/test/source/util.py b/Lib/importlib/test/source/util.py
index 2b945c5..ae65663 100644
--- a/Lib/importlib/test/source/util.py
+++ b/Lib/importlib/test/source/util.py
@@ -1,5 +1,6 @@
from .. import util
import contextlib
+import errno
import functools
import imp
import os
@@ -26,14 +27,16 @@ def writes_bytecode_files(fxn):
return wrapper
-def bytecode_path(source_path):
- for suffix, _, type_ in imp.get_suffixes():
- if type_ == imp.PY_COMPILED:
- bc_suffix = suffix
- break
- else:
- raise ValueError("no bytecode suffix is defined")
- return os.path.splitext(source_path)[0] + bc_suffix
+def ensure_bytecode_path(bytecode_path):
+ """Ensure that the __pycache__ directory for PEP 3147 pyc file exists.
+
+ :param bytecode_path: File system path to PEP 3147 pyc file.
+ """
+ try:
+ os.mkdir(os.path.dirname(bytecode_path))
+ except OSError as error:
+ if error.errno != errno.EEXIST:
+ raise
@contextlib.contextmanager
diff --git a/Lib/importlib/test/test_abc.py b/Lib/importlib/test/test_abc.py
index 6e09534..0ecbe39 100644
--- a/Lib/importlib/test/test_abc.py
+++ b/Lib/importlib/test/test_abc.py
@@ -53,9 +53,20 @@ class InspectLoader(InheritanceTests, unittest.TestCase):
machinery.FrozenImporter]
+class ExecutionLoader(InheritanceTests, unittest.TestCase):
+
+ superclasses = [abc.InspectLoader]
+ subclasses = [abc.PyLoader]
+
+
+class SourceLoader(InheritanceTests, unittest.TestCase):
+
+ superclasses = [abc.ResourceLoader, abc.ExecutionLoader]
+
+
class PyLoader(InheritanceTests, unittest.TestCase):
- superclasses = [abc.Loader, abc.ResourceLoader, abc.InspectLoader]
+ superclasses = [abc.Loader, abc.ResourceLoader, abc.ExecutionLoader]
class PyPycLoader(InheritanceTests, unittest.TestCase):
diff --git a/Lib/importlib/test/test_api.py b/Lib/importlib/test/test_api.py
index 65f8d04..0ffa3c4 100644
--- a/Lib/importlib/test/test_api.py
+++ b/Lib/importlib/test/test_api.py
@@ -27,7 +27,7 @@ class ImportModuleTests(unittest.TestCase):
self.assertEqual(module.__name__, name)
def test_shallow_relative_package_import(self):
- # Test importing a module from a package through a relatve import.
+ # Test importing a module from a package through a relative import.
pkg_name = 'pkg'
pkg_long_name = '{0}.__init__'.format(pkg_name)
module_name = 'mod'
@@ -63,7 +63,8 @@ class ImportModuleTests(unittest.TestCase):
def test_relative_import_wo_package(self):
# Relative imports cannot happen without the 'package' argument being
# set.
- self.assertRaises(TypeError, importlib.import_module, '.support')
+ with self.assertRaises(TypeError):
+ importlib.import_module('.support')
def test_main():
diff --git a/Lib/importlib/test/test_util.py b/Lib/importlib/test/test_util.py
index 406477d..602447f 100644
--- a/Lib/importlib/test/test_util.py
+++ b/Lib/importlib/test/test_util.py
@@ -40,7 +40,7 @@ class ModuleForLoaderTests(unittest.TestCase):
with test_util.uncache(name):
sys.modules[name] = module
returned_module = self.return_module(name)
- self.assertTrue(sys.modules[name] is returned_module)
+ self.assertIs(returned_module, sys.modules[name])
def test_new_module_failure(self):
# Test that a module is removed from sys.modules if added but an
@@ -57,7 +57,7 @@ class ModuleForLoaderTests(unittest.TestCase):
with test_util.uncache(name):
sys.modules[name] = module
self.raise_exception(name)
- self.assertTrue(sys.modules[name] is module)
+ self.assertIs(module, sys.modules[name])
class SetPackageTests(unittest.TestCase):
diff --git a/Lib/importlib/test/util.py b/Lib/importlib/test/util.py
index 845e380..0c0c84c 100644
--- a/Lib/importlib/test/util.py
+++ b/Lib/importlib/test/util.py
@@ -6,21 +6,22 @@ import unittest
import sys
-def case_insensitive_tests(class_):
+CASE_INSENSITIVE_FS = True
+# Windows is the only OS that is *always* case-insensitive
+# (OS X *can* be case-sensitive).
+if sys.platform not in ('win32', 'cygwin'):
+ changed_name = __file__.upper()
+ if changed_name == __file__:
+ changed_name = __file__.lower()
+ if not os.path.exists(changed_name):
+ CASE_INSENSITIVE_FS = False
+
+
+def case_insensitive_tests(test):
"""Class decorator that nullifies tests requiring a case-insensitive
file system."""
- # Windows is the only OS that is *always* case-insensitive
- # (OS X *can* be case-sensitive).
- if sys.platform not in ('win32', 'cygwin'):
- changed_name = __file__.upper()
- if changed_name == __file__:
- changed_name = __file__.lower()
- if os.path.exists(changed_name):
- return class_
- else:
- return unittest.TestCase
- else:
- return class_
+ return unittest.skipIf(not CASE_INSENSITIVE_FS,
+ "requires a case-insensitive filesystem")(test)
@contextmanager
diff --git a/Lib/importlib/util.py b/Lib/importlib/util.py
index 3abc6a9..7b44fa1 100644
--- a/Lib/importlib/util.py
+++ b/Lib/importlib/util.py
@@ -1,4 +1,5 @@
"""Utility code for constructing importers, etc."""
+
from ._bootstrap import module_for_loader
from ._bootstrap import set_loader
from ._bootstrap import set_package
diff --git a/Lib/inspect.py b/Lib/inspect.py
index ffe05b7..aa951d8 100644
--- a/Lib/inspect.py
+++ b/Lib/inspect.py
@@ -1,4 +1,3 @@
-# -*- coding: iso-8859-1 -*-
"""Get useful information from live Python objects.
This module encapsulates the interface provided by the internal special
@@ -17,7 +16,7 @@ Here are some of the useful functions provided by this module:
getmodule() - determine the module that an object came from
getclasstree() - arrange classes so as to represent their hierarchy
- getargspec(), getargvalues() - get info about function arguments
+ getargspec(), getargvalues(), getcallargs() - get info about function arguments
getfullargspec() - same, with support for Python-3000 features
formatargspec(), formatargvalues() - format an argument spec
getouterframes(), getinnerframes() - get info about frames
@@ -33,17 +32,28 @@ __date__ = '1 Jan 2001'
import sys
import os
import types
+import itertools
import string
import re
-import dis
import imp
import tokenize
import linecache
from operator import attrgetter
from collections import namedtuple
-# These constants are from Include/code.h.
-CO_OPTIMIZED, CO_NEWLOCALS, CO_VARARGS, CO_VARKEYWORDS = 0x1, 0x2, 0x4, 0x8
-CO_NESTED, CO_GENERATOR, CO_NOFREE = 0x10, 0x20, 0x40
+
+# Create constants for the compiler flags in Include/code.h
+# We try to get them from dis to avoid duplication, but fall
+# back to hardcording so the dependency is optional
+try:
+ from dis import COMPILER_FLAG_NAMES as _flag_names
+except ImportError:
+ CO_OPTIMIZED, CO_NEWLOCALS = 0x1, 0x2
+ CO_VARARGS, CO_VARKEYWORDS = 0x4, 0x8
+ CO_NESTED, CO_GENERATOR, CO_NOFREE = 0x10, 0x20, 0x40
+else:
+ mod_dict = globals()
+ for k, v in _flag_names.items():
+ mod_dict["CO_" + v] = k
# See Include/object.h
TPFLAGS_IS_ABSTRACT = 1 << 20
@@ -53,6 +63,7 @@ def ismodule(object):
"""Return true if the object is a module.
Module objects provide these attributes:
+ __cached__ pathname to byte compiled file
__doc__ documentation string
__file__ filename (missing for built-in modules)"""
return isinstance(object, types.ModuleType)
@@ -327,22 +338,10 @@ def classify_class_attrs(cls):
return result
# ----------------------------------------------------------- class helpers
-def _searchbases(cls, accum):
- # Simulate the "classic class" search order.
- if cls in accum:
- return
- accum.append(cls)
- for base in cls.__bases__:
- _searchbases(base, accum)
def getmro(cls):
"Return tuple of base classes (including cls) in method resolution order."
- if hasattr(cls, "__mro__"):
- return cls.__mro__
- else:
- result = []
- _searchbases(cls, result)
- return tuple(result)
+ return cls.__mro__
# -------------------------------------------------- source code extraction
def indentsize(line):
@@ -915,6 +914,71 @@ def formatargvalues(args, varargs, varkw, locals,
specs.append(formatvarkw(varkw) + formatvalue(locals[varkw]))
return '(' + ', '.join(specs) + ')'
+def getcallargs(func, *positional, **named):
+ """Get the mapping of arguments to values.
+
+ A dict is returned, with keys the function argument names (including the
+ names of the * and ** arguments, if any), and values the respective bound
+ values from 'positional' and 'named'."""
+ spec = getfullargspec(func)
+ args, varargs, varkw, defaults, kwonlyargs, kwonlydefaults, ann = spec
+ f_name = func.__name__
+ arg2value = {}
+
+ if ismethod(func) and func.__self__ is not None:
+ # implicit 'self' (or 'cls' for classmethods) argument
+ positional = (func.__self__,) + positional
+ num_pos = len(positional)
+ num_total = num_pos + len(named)
+ num_args = len(args)
+ num_defaults = len(defaults) if defaults else 0
+ for arg, value in zip(args, positional):
+ arg2value[arg] = value
+ if varargs:
+ if num_pos > num_args:
+ arg2value[varargs] = positional[-(num_pos-num_args):]
+ else:
+ arg2value[varargs] = ()
+ elif 0 < num_args < num_pos:
+ raise TypeError('%s() takes %s %d positional %s (%d given)' % (
+ f_name, 'at most' if defaults else 'exactly', num_args,
+ 'arguments' if num_args > 1 else 'argument', num_total))
+ elif num_args == 0 and num_total:
+ raise TypeError('%s() takes no arguments (%d given)' %
+ (f_name, num_total))
+
+ for arg in itertools.chain(args, kwonlyargs):
+ if arg in named:
+ if arg in arg2value:
+ raise TypeError("%s() got multiple values for keyword "
+ "argument '%s'" % (f_name, arg))
+ else:
+ arg2value[arg] = named.pop(arg)
+ for kwonlyarg in kwonlyargs:
+ if kwonlyarg not in arg2value:
+ try:
+ arg2value[kwonlyarg] = kwonlydefaults[kwonlyarg]
+ except KeyError:
+ raise TypeError("%s() needs keyword-only argument %s" %
+ (f_name, kwonlyarg))
+ if defaults: # fill in any missing values with the defaults
+ for arg, value in zip(args[-num_defaults:], defaults):
+ if arg not in arg2value:
+ arg2value[arg] = value
+ if varkw:
+ arg2value[varkw] = named
+ elif named:
+ unexpected = next(iter(named))
+ raise TypeError("%s() got an unexpected keyword argument '%s'" %
+ (f_name, unexpected))
+ unassigned = num_args - len([arg for arg in args if arg in arg2value])
+ if unassigned:
+ num_required = num_args - num_defaults
+ raise TypeError('%s() takes %s %d %s (%d given)' % (
+ f_name, 'at least' if defaults else 'exactly', num_required,
+ 'arguments' if num_required > 1 else 'argument', num_total))
+ return arg2value
+
# -------------------------------------------------- stack frame extraction
Traceback = namedtuple('Traceback', 'filename lineno function code_context index')
@@ -979,10 +1043,9 @@ def getinnerframes(tb, context=1):
tb = tb.tb_next
return framelist
-if hasattr(sys, '_getframe'):
- currentframe = sys._getframe
-else:
- currentframe = lambda _=None: None
+def currentframe():
+ """Return the frame of the caller or None if this is not possible."""
+ return sys._getframe(1) if hasattr(sys, "_getframe") else None
def stack(context=1):
"""Return a list of records for the stack above the caller's frame."""
@@ -991,3 +1054,115 @@ def stack(context=1):
def trace(context=1):
"""Return a list of records for the stack below the current exception."""
return getinnerframes(sys.exc_info()[2], context)
+
+
+# ------------------------------------------------ static version of getattr
+
+_sentinel = object()
+
+def _static_getmro(klass):
+ return type.__dict__['__mro__'].__get__(klass)
+
+def _check_instance(obj, attr):
+ instance_dict = {}
+ try:
+ instance_dict = object.__getattribute__(obj, "__dict__")
+ except AttributeError:
+ pass
+ return dict.get(instance_dict, attr, _sentinel)
+
+
+def _check_class(klass, attr):
+ for entry in _static_getmro(klass):
+ if not _shadowed_dict(type(entry)):
+ try:
+ return entry.__dict__[attr]
+ except KeyError:
+ pass
+ return _sentinel
+
+def _is_type(obj):
+ try:
+ _static_getmro(obj)
+ except TypeError:
+ return False
+ return True
+
+def _shadowed_dict(klass):
+ dict_attr = type.__dict__["__dict__"]
+ for entry in _static_getmro(klass):
+ try:
+ class_dict = dict_attr.__get__(entry)["__dict__"]
+ except KeyError:
+ pass
+ else:
+ if not (type(class_dict) is types.GetSetDescriptorType and
+ class_dict.__name__ == "__dict__" and
+ class_dict.__objclass__ is entry):
+ return True
+ return False
+
+def getattr_static(obj, attr, default=_sentinel):
+ """Retrieve attributes without triggering dynamic lookup via the
+ descriptor protocol, __getattr__ or __getattribute__.
+
+ Note: this function may not be able to retrieve all attributes
+ that getattr can fetch (like dynamically created attributes)
+ and may find attributes that getattr can't (like descriptors
+ that raise AttributeError). It can also return descriptor objects
+ instead of instance members in some cases. See the
+ documentation for details.
+ """
+ instance_result = _sentinel
+ if not _is_type(obj):
+ klass = type(obj)
+ if not _shadowed_dict(klass):
+ instance_result = _check_instance(obj, attr)
+ else:
+ klass = obj
+
+ klass_result = _check_class(klass, attr)
+
+ if instance_result is not _sentinel and klass_result is not _sentinel:
+ if (_check_class(type(klass_result), '__get__') is not _sentinel and
+ _check_class(type(klass_result), '__set__') is not _sentinel):
+ return klass_result
+
+ if instance_result is not _sentinel:
+ return instance_result
+ if klass_result is not _sentinel:
+ return klass_result
+
+ if obj is klass:
+ # for types we check the metaclass too
+ for entry in _static_getmro(type(klass)):
+ try:
+ return entry.__dict__[attr]
+ except KeyError:
+ pass
+ if default is not _sentinel:
+ return default
+ raise AttributeError(attr)
+
+
+GEN_CREATED = 'GEN_CREATED'
+GEN_RUNNING = 'GEN_RUNNING'
+GEN_SUSPENDED = 'GEN_SUSPENDED'
+GEN_CLOSED = 'GEN_CLOSED'
+
+def getgeneratorstate(generator):
+ """Get current state of a generator-iterator.
+
+ Possible states are:
+ GEN_CREATED: Waiting to start execution.
+ GEN_RUNNING: Currently being executed by the interpreter.
+ GEN_SUSPENDED: Currently suspended at a yield expression.
+ GEN_CLOSED: Execution has completed.
+ """
+ if generator.gi_running:
+ return GEN_RUNNING
+ if generator.gi_frame is None:
+ return GEN_CLOSED
+ if generator.gi_frame.f_lasti == -1:
+ return GEN_CREATED
+ return GEN_SUSPENDED
diff --git a/Lib/json/decoder.py b/Lib/json/decoder.py
index d606cbd..5747fa6 100644
--- a/Lib/json/decoder.py
+++ b/Lib/json/decoder.py
@@ -147,10 +147,14 @@ WHITESPACE_STR = ' \t\n\r'
def JSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook,
- _w=WHITESPACE.match, _ws=WHITESPACE_STR):
+ memo=None, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
s, end = s_and_end
pairs = []
pairs_append = pairs.append
+ # Backwards compatibility
+ if memo is None:
+ memo = {}
+ memo_get = memo.setdefault
# Use a slice to prevent IndexError from being raised, the following
# check will raise a more specific ValueError if the string is empty
nextchar = s[end:end + 1]
@@ -167,6 +171,7 @@ def JSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook,
end += 1
while True:
key, end = scanstring(s, end, strict)
+ key = memo_get(key, key)
# To skip some function call overhead we optimize the fast paths where
# the JSON key separator is ": " or just ":".
if s[end:end + 1] != ':':
@@ -214,7 +219,7 @@ def JSONObject(s_and_end, strict, scan_once, object_hook, object_pairs_hook,
pairs = object_hook(pairs)
return pairs, end
-def JSONArray(s_and_end, scan_once, context, _w=WHITESPACE.match):
+def JSONArray(s_and_end, scan_once, _w=WHITESPACE.match, _ws=WHITESPACE_STR):
s, end = s_and_end
values = []
nextchar = s[end:end + 1]
@@ -328,6 +333,7 @@ class JSONDecoder(object):
self.parse_object = JSONObject
self.parse_array = JSONArray
self.parse_string = scanstring
+ self.memo = {}
self.scan_once = make_scanner(self)
diff --git a/Lib/json/encoder.py b/Lib/json/encoder.py
index 1335985..7475f5a 100644
--- a/Lib/json/encoder.py
+++ b/Lib/json/encoder.py
@@ -259,6 +259,9 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
tuple=tuple,
):
+ if _indent is not None and not isinstance(_indent, str):
+ _indent = ' ' * _indent
+
def _iterencode_list(lst, _current_indent_level):
if not lst:
yield '[]'
@@ -271,7 +274,7 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
buf = '['
if _indent is not None:
_current_indent_level += 1
- newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
+ newline_indent = '\n' + _indent * _current_indent_level
separator = _item_separator + newline_indent
buf += newline_indent
else:
@@ -307,7 +310,7 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
yield chunk
if newline_indent is not None:
_current_indent_level -= 1
- yield '\n' + (' ' * (_indent * _current_indent_level))
+ yield '\n' + _indent * _current_indent_level
yield ']'
if markers is not None:
del markers[markerid]
@@ -324,7 +327,7 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
yield '{'
if _indent is not None:
_current_indent_level += 1
- newline_indent = '\n' + (' ' * (_indent * _current_indent_level))
+ newline_indent = '\n' + _indent * _current_indent_level
item_separator = _item_separator + newline_indent
yield newline_indent
else:
@@ -383,7 +386,7 @@ def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
yield chunk
if newline_indent is not None:
_current_indent_level -= 1
- yield '\n' + (' ' * (_indent * _current_indent_level))
+ yield '\n' + _indent * _current_indent_level
yield '}'
if markers is not None:
del markers[markerid]
diff --git a/Lib/json/scanner.py b/Lib/json/scanner.py
index b4f3561..23eef61 100644
--- a/Lib/json/scanner.py
+++ b/Lib/json/scanner.py
@@ -22,6 +22,8 @@ def py_make_scanner(context):
parse_int = context.parse_int
parse_constant = context.parse_constant
object_hook = context.object_hook
+ object_pairs_hook = context.object_pairs_hook
+ memo = context.memo
def _scan_once(string, idx):
try:
@@ -33,7 +35,7 @@ def py_make_scanner(context):
return parse_string(string, idx + 1, strict)
elif nextchar == '{':
return parse_object((string, idx + 1), strict,
- _scan_once, object_hook, object_pairs_hook)
+ _scan_once, object_hook, object_pairs_hook, memo)
elif nextchar == '[':
return parse_array((string, idx + 1), _scan_once)
elif nextchar == 'n' and string[idx:idx + 4] == 'null':
@@ -60,6 +62,12 @@ def py_make_scanner(context):
else:
raise StopIteration
+ def scan_once(string, idx):
+ try:
+ return _scan_once(string, idx)
+ finally:
+ memo.clear()
+
return _scan_once
make_scanner = c_make_scanner or py_make_scanner
diff --git a/Lib/keyword.py b/Lib/keyword.py
index a7abe2b..a3788a6 100755
--- a/Lib/keyword.py
+++ b/Lib/keyword.py
@@ -1,4 +1,4 @@
-#! /usr/bin/env python
+#! /usr/bin/env python3
"""Keywords (from "graminit.c")
@@ -61,21 +61,19 @@ def main():
else: optfile = "Lib/keyword.py"
# scan the source file for keywords
- fp = open(iptfile)
- strprog = re.compile('"([^"]+)"')
- lines = []
- for line in fp:
- if '{1, "' in line:
- match = strprog.search(line)
- if match:
- lines.append(" '" + match.group(1) + "',\n")
- fp.close()
+ with open(iptfile) as fp:
+ strprog = re.compile('"([^"]+)"')
+ lines = []
+ for line in fp:
+ if '{1, "' in line:
+ match = strprog.search(line)
+ if match:
+ lines.append(" '" + match.group(1) + "',\n")
lines.sort()
# load the output skeleton from the target
- fp = open(optfile)
- format = fp.readlines()
- fp.close()
+ with open(optfile) as fp:
+ format = fp.readlines()
# insert the lines of keywords
try:
diff --git a/Lib/lib2to3/pgen2/token.py b/Lib/lib2to3/pgen2/token.py
index 1c81065..6a6d0b6 100755
--- a/Lib/lib2to3/pgen2/token.py
+++ b/Lib/lib2to3/pgen2/token.py
@@ -1,4 +1,4 @@
-#! /usr/bin/env python
+#! /usr/bin/env python3
"""Token constants (from "token.h")."""
diff --git a/Lib/lib2to3/tests/pytree_idempotency.py b/Lib/lib2to3/tests/pytree_idempotency.py
index 414eb4d..a02bbfe 100755
--- a/Lib/lib2to3/tests/pytree_idempotency.py
+++ b/Lib/lib2to3/tests/pytree_idempotency.py
@@ -1,4 +1,4 @@
-#!/usr/bin/env python
+#!/usr/bin/env python3
# Copyright 2006 Google, Inc. All Rights Reserved.
# Licensed to PSF under a Contributor Agreement.
diff --git a/Lib/linecache.py b/Lib/linecache.py
index 974b1d9..c3f2c3f 100644
--- a/Lib/linecache.py
+++ b/Lib/linecache.py
@@ -123,9 +123,7 @@ def updatecache(filename, module_globals=None):
else:
return []
try:
- with open(fullname, 'rb') as fp:
- coding, line = tokenize.detect_encoding(fp.readline)
- with open(fullname, 'r', encoding=coding) as fp:
+ with tokenize.open(fullname) as fp:
lines = fp.readlines()
except IOError:
return []
diff --git a/Lib/locale.py b/Lib/locale.py
index 8c44625..7b987be 100644
--- a/Lib/locale.py
+++ b/Lib/locale.py
@@ -121,12 +121,15 @@ def localeconv():
# Iterate over grouping intervals
def _grouping_intervals(grouping):
+ last_interval = None
for interval in grouping:
# if grouping is -1, we are done
if interval == CHAR_MAX:
return
# 0: re-use last group ad infinitum
if interval == 0:
+ if last_interval is None:
+ raise ValueError("invalid grouping")
while True:
yield last_interval
yield interval
@@ -715,6 +718,28 @@ locale_encoding_alias = {
# updated 'sr_yu.microsoftcp1251@cyrillic' -> 'sr_YU.CP1251' to 'sr_CS.CP1251'
# updated 'sr_yu.utf8@cyrillic' -> 'sr_YU.UTF-8' to 'sr_CS.UTF-8'
# updated 'sr_yu@cyrillic' -> 'sr_YU.ISO8859-5' to 'sr_CS.ISO8859-5'
+#
+# AP 2010-04-12:
+# Updated alias mapping to most recent locale.alias file
+# from X.org distribution using makelocalealias.py.
+#
+# These are the differences compared to the old mapping (Python 2.6.5
+# and older):
+#
+# updated 'ru' -> 'ru_RU.ISO8859-5' to 'ru_RU.UTF-8'
+# updated 'ru_ru' -> 'ru_RU.ISO8859-5' to 'ru_RU.UTF-8'
+# updated 'serbocroatian' -> 'sr_CS.ISO8859-2' to 'sr_RS.UTF-8@latin'
+# updated 'sh' -> 'sr_CS.ISO8859-2' to 'sr_RS.UTF-8@latin'
+# updated 'sh_yu' -> 'sr_CS.ISO8859-2' to 'sr_RS.UTF-8@latin'
+# updated 'sr' -> 'sr_CS.ISO8859-5' to 'sr_RS.UTF-8'
+# updated 'sr@cyrillic' -> 'sr_CS.ISO8859-5' to 'sr_RS.UTF-8'
+# updated 'sr@latn' -> 'sr_CS.ISO8859-2' to 'sr_RS.UTF-8@latin'
+# updated 'sr_cs.utf8@latn' -> 'sr_CS.UTF-8' to 'sr_RS.UTF-8@latin'
+# updated 'sr_cs@latn' -> 'sr_CS.ISO8859-2' to 'sr_RS.UTF-8@latin'
+# updated 'sr_yu' -> 'sr_CS.ISO8859-5' to 'sr_RS.UTF-8@latin'
+# updated 'sr_yu.utf8@cyrillic' -> 'sr_CS.UTF-8' to 'sr_RS.UTF-8'
+# updated 'sr_yu@cyrillic' -> 'sr_CS.ISO8859-5' to 'sr_RS.UTF-8'
+#
locale_alias = {
'a3': 'a3_AZ.KOI8-C',
@@ -766,13 +791,17 @@ locale_alias = {
'ar_ye.iso88596': 'ar_YE.ISO8859-6',
'arabic': 'ar_AA.ISO8859-6',
'arabic.iso88596': 'ar_AA.ISO8859-6',
+ 'as': 'as_IN.UTF-8',
'az': 'az_AZ.ISO8859-9E',
'az_az': 'az_AZ.ISO8859-9E',
'az_az.iso88599e': 'az_AZ.ISO8859-9E',
'be': 'be_BY.CP1251',
+ 'be@latin': 'be_BY.UTF-8@latin',
'be_by': 'be_BY.CP1251',
'be_by.cp1251': 'be_BY.CP1251',
'be_by.microsoftcp1251': 'be_BY.CP1251',
+ 'be_by.utf8@latin': 'be_BY.UTF-8@latin',
+ 'be_by@latin': 'be_BY.UTF-8@latin',
'bg': 'bg_BG.CP1251',
'bg_bg': 'bg_BG.CP1251',
'bg_bg.cp1251': 'bg_BG.CP1251',
@@ -802,12 +831,30 @@ locale_alias = {
'c_c': 'C',
'c_c.c': 'C',
'ca': 'ca_ES.ISO8859-1',
+ 'ca_ad': 'ca_AD.ISO8859-1',
+ 'ca_ad.iso88591': 'ca_AD.ISO8859-1',
+ 'ca_ad.iso885915': 'ca_AD.ISO8859-15',
+ 'ca_ad.iso885915@euro': 'ca_AD.ISO8859-15',
+ 'ca_ad.utf8@euro': 'ca_AD.UTF-8',
+ 'ca_ad@euro': 'ca_AD.ISO8859-15',
'ca_es': 'ca_ES.ISO8859-1',
'ca_es.iso88591': 'ca_ES.ISO8859-1',
'ca_es.iso885915': 'ca_ES.ISO8859-15',
'ca_es.iso885915@euro': 'ca_ES.ISO8859-15',
'ca_es.utf8@euro': 'ca_ES.UTF-8',
'ca_es@euro': 'ca_ES.ISO8859-15',
+ 'ca_fr': 'ca_FR.ISO8859-1',
+ 'ca_fr.iso88591': 'ca_FR.ISO8859-1',
+ 'ca_fr.iso885915': 'ca_FR.ISO8859-15',
+ 'ca_fr.iso885915@euro': 'ca_FR.ISO8859-15',
+ 'ca_fr.utf8@euro': 'ca_FR.UTF-8',
+ 'ca_fr@euro': 'ca_FR.ISO8859-15',
+ 'ca_it': 'ca_IT.ISO8859-1',
+ 'ca_it.iso88591': 'ca_IT.ISO8859-1',
+ 'ca_it.iso885915': 'ca_IT.ISO8859-15',
+ 'ca_it.iso885915@euro': 'ca_IT.ISO8859-15',
+ 'ca_it.utf8@euro': 'ca_IT.UTF-8',
+ 'ca_it@euro': 'ca_IT.ISO8859-15',
'catalan': 'ca_ES.ISO8859-1',
'cextend': 'en_US.ISO8859-1',
'cextend.en': 'en_US.ISO8859-1',
@@ -829,6 +876,7 @@ locale_alias = {
'cz_cz': 'cs_CZ.ISO8859-2',
'czech': 'cs_CZ.ISO8859-2',
'da': 'da_DK.ISO8859-1',
+ 'da.iso885915': 'da_DK.ISO8859-15',
'da_dk': 'da_DK.ISO8859-1',
'da_dk.88591': 'da_DK.ISO8859-1',
'da_dk.885915': 'da_DK.ISO8859-15',
@@ -839,6 +887,7 @@ locale_alias = {
'danish.iso88591': 'da_DK.ISO8859-1',
'dansk': 'da_DK.ISO8859-1',
'de': 'de_DE.ISO8859-1',
+ 'de.iso885915': 'de_DE.ISO8859-15',
'de_at': 'de_AT.ISO8859-1',
'de_at.iso88591': 'de_AT.ISO8859-1',
'de_at.iso885915': 'de_AT.ISO8859-15',
@@ -1020,6 +1069,7 @@ locale_alias = {
'fa_ir': 'fa_IR.UTF-8',
'fa_ir.isiri3342': 'fa_IR.ISIRI-3342',
'fi': 'fi_FI.ISO8859-15',
+ 'fi.iso885915': 'fi_FI.ISO8859-15',
'fi_fi': 'fi_FI.ISO8859-15',
'fi_fi.88591': 'fi_FI.ISO8859-1',
'fi_fi.iso88591': 'fi_FI.ISO8859-1',
@@ -1035,6 +1085,7 @@ locale_alias = {
'fo_fo.iso885915': 'fo_FO.ISO8859-15',
'fo_fo@euro': 'fo_FO.ISO8859-15',
'fr': 'fr_FR.ISO8859-1',
+ 'fr.iso885915': 'fr_FR.ISO8859-15',
'fr_be': 'fr_BE.ISO8859-1',
'fr_be.88591': 'fr_BE.ISO8859-1',
'fr_be.iso88591': 'fr_BE.ISO8859-1',
@@ -1121,6 +1172,7 @@ locale_alias = {
'hi': 'hi_IN.ISCII-DEV',
'hi_in': 'hi_IN.ISCII-DEV',
'hi_in.isciidev': 'hi_IN.ISCII-DEV',
+ 'hne': 'hne_IN.UTF-8',
'hr': 'hr_HR.ISO8859-2',
'hr_hr': 'hr_HR.ISO8859-2',
'hr_hr.iso88592': 'hr_HR.ISO8859-2',
@@ -1147,6 +1199,7 @@ locale_alias = {
'iso_8859_1': 'en_US.ISO8859-1',
'iso_8859_15': 'en_US.ISO8859-15',
'it': 'it_IT.ISO8859-1',
+ 'it.iso885915': 'it_IT.ISO8859-15',
'it_ch': 'it_CH.ISO8859-1',
'it_ch.iso88591': 'it_CH.ISO8859-1',
'it_ch.iso885915': 'it_CH.ISO8859-15',
@@ -1178,6 +1231,7 @@ locale_alias = {
'ja_jp.jis': 'ja_JP.JIS7',
'ja_jp.jis7': 'ja_JP.JIS7',
'ja_jp.mscode': 'ja_JP.SJIS',
+ 'ja_jp.pck': 'ja_JP.SJIS',
'ja_jp.sjis': 'ja_JP.SJIS',
'ja_jp.ujis': 'ja_JP.eucJP',
'japan': 'ja_JP.eucJP',
@@ -1197,6 +1251,7 @@ locale_alias = {
'kl_gl.iso885915': 'kl_GL.ISO8859-15',
'kl_gl@euro': 'kl_GL.ISO8859-15',
'km_kh': 'km_KH.UTF-8',
+ 'kn': 'kn_IN.UTF-8',
'kn_in': 'kn_IN.UTF-8',
'ko': 'ko_KR.eucKR',
'ko_kr': 'ko_KR.eucKR',
@@ -1204,6 +1259,8 @@ locale_alias = {
'ko_kr.euckr': 'ko_KR.eucKR',
'korean': 'ko_KR.eucKR',
'korean.euc': 'ko_KR.eucKR',
+ 'ks': 'ks_IN.UTF-8',
+ 'ks_in@devanagari': 'ks_IN@devanagari.UTF-8',
'kw': 'kw_GB.ISO8859-1',
'kw_gb': 'kw_GB.ISO8859-1',
'kw_gb.iso88591': 'kw_GB.ISO8859-1',
@@ -1226,6 +1283,7 @@ locale_alias = {
'lv_lv': 'lv_LV.ISO8859-13',
'lv_lv.iso885913': 'lv_LV.ISO8859-13',
'lv_lv.iso88594': 'lv_LV.ISO8859-4',
+ 'mai': 'mai_IN.UTF-8',
'mi': 'mi_NZ.ISO8859-1',
'mi_nz': 'mi_NZ.ISO8859-1',
'mi_nz.iso88591': 'mi_NZ.ISO8859-1',
@@ -1234,6 +1292,8 @@ locale_alias = {
'mk_mk.cp1251': 'mk_MK.CP1251',
'mk_mk.iso88595': 'mk_MK.ISO8859-5',
'mk_mk.microsoftcp1251': 'mk_MK.CP1251',
+ 'ml': 'ml_IN.UTF-8',
+ 'mr': 'mr_IN.UTF-8',
'mr_in': 'mr_IN.UTF-8',
'ms': 'ms_MY.ISO8859-1',
'ms_my': 'ms_MY.ISO8859-1',
@@ -1248,6 +1308,7 @@ locale_alias = {
'nb_no.iso885915': 'nb_NO.ISO8859-15',
'nb_no@euro': 'nb_NO.ISO8859-15',
'nl': 'nl_NL.ISO8859-1',
+ 'nl.iso885915': 'nl_NL.ISO8859-15',
'nl_be': 'nl_BE.ISO8859-1',
'nl_be.88591': 'nl_BE.ISO8859-1',
'nl_be.iso88591': 'nl_BE.ISO8859-1',
@@ -1274,6 +1335,8 @@ locale_alias = {
'no_no.88591': 'no_NO.ISO8859-1',
'no_no.iso88591': 'no_NO.ISO8859-1',
'no_no.iso885915': 'no_NO.ISO8859-15',
+ 'no_no.iso88591@bokmal': 'no_NO.ISO8859-1',
+ 'no_no.iso88591@nynorsk': 'no_NO.ISO8859-1',
'no_no@euro': 'no_NO.ISO8859-15',
'norwegian': 'no_NO.ISO8859-1',
'norwegian.iso88591': 'no_NO.ISO8859-1',
@@ -1295,6 +1358,8 @@ locale_alias = {
'oc_fr.iso88591': 'oc_FR.ISO8859-1',
'oc_fr.iso885915': 'oc_FR.ISO8859-15',
'oc_fr@euro': 'oc_FR.ISO8859-15',
+ 'or': 'or_IN.UTF-8',
+ 'pa': 'pa_IN.UTF-8',
'pa_in': 'pa_IN.UTF-8',
'pd': 'pd_US.ISO8859-1',
'pd_de': 'pd_DE.ISO8859-1',
@@ -1322,6 +1387,7 @@ locale_alias = {
'pp_an': 'pp_AN.ISO8859-1',
'pp_an.iso88591': 'pp_AN.ISO8859-1',
'pt': 'pt_PT.ISO8859-1',
+ 'pt.iso885915': 'pt_PT.ISO8859-15',
'pt_br': 'pt_BR.ISO8859-1',
'pt_br.88591': 'pt_BR.ISO8859-1',
'pt_br.iso88591': 'pt_BR.ISO8859-1',
@@ -1338,8 +1404,9 @@ locale_alias = {
'ro_ro': 'ro_RO.ISO8859-2',
'ro_ro.iso88592': 'ro_RO.ISO8859-2',
'romanian': 'ro_RO.ISO8859-2',
- 'ru': 'ru_RU.ISO8859-5',
- 'ru_ru': 'ru_RU.ISO8859-5',
+ 'ru': 'ru_RU.UTF-8',
+ 'ru.koi8r': 'ru_RU.KOI8-R',
+ 'ru_ru': 'ru_RU.UTF-8',
'ru_ru.cp1251': 'ru_RU.CP1251',
'ru_ru.iso88595': 'ru_RU.ISO8859-5',
'ru_ru.koi8r': 'ru_RU.KOI8-R',
@@ -1353,13 +1420,15 @@ locale_alias = {
'rw': 'rw_RW.ISO8859-1',
'rw_rw': 'rw_RW.ISO8859-1',
'rw_rw.iso88591': 'rw_RW.ISO8859-1',
+ 'sd': 'sd_IN@devanagari.UTF-8',
'se_no': 'se_NO.UTF-8',
- 'serbocroatian': 'sr_CS.ISO8859-2',
- 'sh': 'sr_CS.ISO8859-2',
+ 'serbocroatian': 'sr_RS.UTF-8@latin',
+ 'sh': 'sr_RS.UTF-8@latin',
+ 'sh_ba.iso88592@bosnia': 'sr_CS.ISO8859-2',
'sh_hr': 'sh_HR.ISO8859-2',
'sh_hr.iso88592': 'hr_HR.ISO8859-2',
'sh_sp': 'sr_CS.ISO8859-2',
- 'sh_yu': 'sr_CS.ISO8859-2',
+ 'sh_yu': 'sr_RS.UTF-8@latin',
'si': 'si_LK.UTF-8',
'si_lk': 'si_LK.UTF-8',
'sinhala': 'si_LK.UTF-8',
@@ -1382,23 +1451,30 @@ locale_alias = {
'sq': 'sq_AL.ISO8859-2',
'sq_al': 'sq_AL.ISO8859-2',
'sq_al.iso88592': 'sq_AL.ISO8859-2',
- 'sr': 'sr_CS.ISO8859-5',
- 'sr@cyrillic': 'sr_CS.ISO8859-5',
- 'sr@latn': 'sr_CS.ISO8859-2',
+ 'sr': 'sr_RS.UTF-8',
+ 'sr@cyrillic': 'sr_RS.UTF-8',
+ 'sr@latin': 'sr_RS.UTF-8@latin',
+ 'sr@latn': 'sr_RS.UTF-8@latin',
+ 'sr_cs': 'sr_RS.UTF-8',
'sr_cs.iso88592': 'sr_CS.ISO8859-2',
'sr_cs.iso88592@latn': 'sr_CS.ISO8859-2',
'sr_cs.iso88595': 'sr_CS.ISO8859-5',
- 'sr_cs.utf8@latn': 'sr_CS.UTF-8',
- 'sr_cs@latn': 'sr_CS.ISO8859-2',
+ 'sr_cs.utf8@latn': 'sr_RS.UTF-8@latin',
+ 'sr_cs@latn': 'sr_RS.UTF-8@latin',
+ 'sr_me': 'sr_ME.UTF-8',
+ 'sr_rs': 'sr_RS.UTF-8',
+ 'sr_rs.utf8@latn': 'sr_RS.UTF-8@latin',
+ 'sr_rs@latin': 'sr_RS.UTF-8@latin',
+ 'sr_rs@latn': 'sr_RS.UTF-8@latin',
'sr_sp': 'sr_CS.ISO8859-2',
- 'sr_yu': 'sr_CS.ISO8859-5',
+ 'sr_yu': 'sr_RS.UTF-8@latin',
'sr_yu.cp1251@cyrillic': 'sr_CS.CP1251',
'sr_yu.iso88592': 'sr_CS.ISO8859-2',
'sr_yu.iso88595': 'sr_CS.ISO8859-5',
'sr_yu.iso88595@cyrillic': 'sr_CS.ISO8859-5',
'sr_yu.microsoftcp1251@cyrillic': 'sr_CS.CP1251',
- 'sr_yu.utf8@cyrillic': 'sr_CS.UTF-8',
- 'sr_yu@cyrillic': 'sr_CS.ISO8859-5',
+ 'sr_yu.utf8@cyrillic': 'sr_RS.UTF-8',
+ 'sr_yu@cyrillic': 'sr_RS.UTF-8',
'ss': 'ss_ZA.ISO8859-1',
'ss_za': 'ss_ZA.ISO8859-1',
'ss_za.iso88591': 'ss_ZA.ISO8859-1',
@@ -1406,6 +1482,7 @@ locale_alias = {
'st_za': 'st_ZA.ISO8859-1',
'st_za.iso88591': 'st_ZA.ISO8859-1',
'sv': 'sv_SE.ISO8859-1',
+ 'sv.iso885915': 'sv_SE.ISO8859-15',
'sv_fi': 'sv_FI.ISO8859-1',
'sv_fi.iso88591': 'sv_FI.ISO8859-1',
'sv_fi.iso885915': 'sv_FI.ISO8859-15',
@@ -1423,6 +1500,7 @@ locale_alias = {
'ta_in': 'ta_IN.TSCII-0',
'ta_in.tscii': 'ta_IN.TSCII-0',
'ta_in.tscii0': 'ta_IN.TSCII-0',
+ 'te': 'te_IN.UTF-8',
'tg': 'tg_TJ.KOI8-C',
'tg_tj': 'tg_TJ.KOI8-C',
'tg_tj.koi8c': 'tg_TJ.KOI8-C',
@@ -1498,6 +1576,7 @@ locale_alias = {
'zh_cn.gbk': 'zh_CN.gbk',
'zh_hk': 'zh_HK.big5hkscs',
'zh_hk.big5': 'zh_HK.big5',
+ 'zh_hk.big5hk': 'zh_HK.big5hkscs',
'zh_hk.big5hkscs': 'zh_HK.big5hkscs',
'zh_tw': 'zh_TW.big5',
'zh_tw.big5': 'zh_TW.big5',
diff --git a/Lib/logging/__init__.py b/Lib/logging/__init__.py
index 685efeb..e4b34a1 100644
--- a/Lib/logging/__init__.py
+++ b/Lib/logging/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2001-2009 by Vinay Sajip. All Rights Reserved.
+# Copyright 2001-2010 by Vinay Sajip. All Rights Reserved.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose and without fee is hereby granted,
@@ -23,7 +23,8 @@ Copyright (C) 2001-2010 Vinay Sajip. All Rights Reserved.
To use, simply 'import logging' and log away!
"""
-import sys, os, time, io, traceback, warnings
+import sys, os, time, io, traceback, warnings, weakref
+from string import Template
__all__ = ['BASIC_FORMAT', 'BufferingFormatter', 'CRITICAL', 'DEBUG', 'ERROR',
'FATAL', 'FileHandler', 'Filter', 'Formatter', 'Handler', 'INFO',
@@ -31,7 +32,8 @@ __all__ = ['BASIC_FORMAT', 'BufferingFormatter', 'CRITICAL', 'DEBUG', 'ERROR',
'StreamHandler', 'WARN', 'WARNING', 'addLevelName', 'basicConfig',
'captureWarnings', 'critical', 'debug', 'disable', 'error',
'exception', 'fatal', 'getLevelName', 'getLogger', 'getLoggerClass',
- 'info', 'log', 'makeLogRecord', 'setLoggerClass', 'warn', 'warning']
+ 'info', 'log', 'makeLogRecord', 'setLoggerClass', 'warn', 'warning',
+ 'getLogRecordFactory', 'setLogRecordFactory', 'lastResort']
try:
import codecs
@@ -46,15 +48,13 @@ except ImportError:
__author__ = "Vinay Sajip <vinay_sajip@red-dove.com>"
__status__ = "production"
-__version__ = "0.5.0.7"
-__date__ = "20 January 2009"
+__version__ = "0.5.1.2"
+__date__ = "07 February 2010"
#---------------------------------------------------------------------------
# Miscellaneous module data
#---------------------------------------------------------------------------
-_unicode = 'unicode' in dir(__builtins__)
-
#
# _srcfile is used when walking the stack to check when we've got the first
# caller stack frame.
@@ -176,19 +176,34 @@ def addLevelName(level, levelName):
finally:
_releaseLock()
+def _checkLevel(level):
+ if isinstance(level, int):
+ rv = level
+ elif str(level) == level:
+ if level not in _levelNames:
+ raise ValueError("Unknown level: %r" % level)
+ rv = _levelNames[level]
+ else:
+ raise TypeError("Level not an integer or a valid string: %r" % level)
+ return rv
+
#---------------------------------------------------------------------------
# Thread-related stuff
#---------------------------------------------------------------------------
#
#_lock is used to serialize access to shared data structures in this module.
-#This needs to be an RLock because fileConfig() creates Handlers and so
-#might arbitrary user threads. Since Handler.__init__() updates the shared
-#dictionary _handlers, it needs to acquire the lock. But if configuring,
+#This needs to be an RLock because fileConfig() creates and configures
+#Handlers, and so might arbitrary user threads. Since Handler code updates the
+#shared dictionary _handlers, it needs to acquire the lock. But if configuring,
#the lock would already have been acquired - so we need an RLock.
#The same argument applies to Loggers and Manager.loggerDict.
#
-_lock = None
+if thread:
+ _lock = threading.RLock()
+else:
+ _lock = None
+
def _acquireLock():
"""
@@ -196,9 +211,6 @@ def _acquireLock():
This should be released with _releaseLock().
"""
- global _lock
- if (not _lock) and thread:
- _lock = threading.RLock()
if _lock:
_lock.acquire()
@@ -213,7 +225,7 @@ def _releaseLock():
# The logging record
#---------------------------------------------------------------------------
-class LogRecord:
+class LogRecord(object):
"""
A LogRecord instance represents an event being logged.
@@ -226,7 +238,7 @@ class LogRecord:
information to be logged.
"""
def __init__(self, name, level, pathname, lineno,
- msg, args, exc_info, func=None):
+ msg, args, exc_info, func=None, sinfo=None, **kwargs):
"""
Initialize a logging record with interesting information.
"""
@@ -260,6 +272,7 @@ class LogRecord:
self.module = "Unknown module"
self.exc_info = exc_info
self.exc_text = None # used to cache the traceback text
+ self.stack_info = sinfo
self.lineno = lineno
self.funcName = func
self.created = ct
@@ -274,11 +287,17 @@ class LogRecord:
if not logMultiprocessing:
self.processName = None
else:
- try:
- from multiprocessing import current_process
- self.processName = current_process().name
- except ImportError:
- self.processName = None
+ self.processName = 'MainProcess'
+ mp = sys.modules.get('multiprocessing')
+ if mp is not None:
+ # Errors may occur if multiprocessing has not finished loading
+ # yet - e.g. if a custom import hook causes third-party code
+ # to run when multiprocessing calls import. See issue 8200
+ # for an example
+ try:
+ self.processName = mp.current_process().name
+ except StandardError:
+ pass
if logProcesses and hasattr(os, 'getpid'):
self.process = os.getpid()
else:
@@ -295,19 +314,33 @@ class LogRecord:
Return the message for this LogRecord after merging any user-supplied
arguments with the message.
"""
- if not _unicode: #if no unicode support...
- msg = str(self.msg)
- else:
- msg = self.msg
- if not isinstance(msg, str):
- try:
- msg = str(self.msg)
- except UnicodeError:
- msg = self.msg #Defer encoding till later
+ msg = str(self.msg)
if self.args:
msg = msg % self.args
return msg
+#
+# Determine which class to use when instantiating log records.
+#
+_logRecordFactory = LogRecord
+
+def setLogRecordFactory(factory):
+ """
+ Set the factory to be used when instantiating a log record.
+
+ :param factory: A callable which will be called to instantiate
+ a log record.
+ """
+ global _logRecordFactory
+ _logRecordFactory = factory
+
+def getLogRecordFactory():
+ """
+ Return the factory to be used when instantiating a log record.
+ """
+
+ return _logRecordFactory
+
def makeLogRecord(dict):
"""
Make a LogRecord whose attributes are defined by the specified dictionary,
@@ -315,7 +348,7 @@ def makeLogRecord(dict):
a socket connection (which is sent as a dictionary) into a LogRecord
instance.
"""
- rv = LogRecord(None, None, "", 0, "", (), None, None)
+ rv = _logRecordFactory(None, None, "", 0, "", (), None, None)
rv.__dict__.update(dict)
return rv
@@ -323,7 +356,53 @@ def makeLogRecord(dict):
# Formatter classes and functions
#---------------------------------------------------------------------------
-class Formatter:
+class PercentStyle(object):
+
+ default_format = '%(message)s'
+ asctime_format = '%(asctime)s'
+ asctime_search = '%(asctime)'
+
+ def __init__(self, fmt):
+ self._fmt = fmt or self.default_format
+
+ def usesTime(self):
+ return self._fmt.find(self.asctime_search) >= 0
+
+ def format(self, record):
+ return self._fmt % record.__dict__
+
+class StrFormatStyle(PercentStyle):
+ default_format = '{message}'
+ asctime_format = '{asctime}'
+ asctime_search = '{asctime'
+
+ def format(self, record):
+ return self._fmt.format(**record.__dict__)
+
+
+class StringTemplateStyle(PercentStyle):
+ default_format = '${message}'
+ asctime_format = '${asctime}'
+ asctime_search = '${asctime}'
+
+ def __init__(self, fmt):
+ self._fmt = fmt or self.default_format
+ self._tpl = Template(self._fmt)
+
+ def usesTime(self):
+ fmt = self._fmt
+ return fmt.find('$asctime') >= 0 or fmt.find(self.asctime_format) >= 0
+
+ def format(self, record):
+ return self._tpl.substitute(**record.__dict__)
+
+_STYLES = {
+ '%': PercentStyle,
+ '{': StrFormatStyle,
+ '$': StringTemplateStyle
+}
+
+class Formatter(object):
"""
Formatter instances are used to convert a LogRecord to text.
@@ -331,7 +410,7 @@ class Formatter:
responsible for converting a LogRecord to (usually) a string which can
be interpreted by either a human or an external system. The base Formatter
allows a formatting string to be specified. If none is supplied, the
- default value of "%s(message)\\n" is used.
+ default value of "%s(message)" is used.
The Formatter can be initialized with a format string which makes use of
knowledge of the LogRecord attributes - e.g. the default value mentioned
@@ -367,18 +446,26 @@ class Formatter:
converter = time.localtime
- def __init__(self, fmt=None, datefmt=None):
+ def __init__(self, fmt=None, datefmt=None, style='%'):
"""
Initialize the formatter with specified format strings.
Initialize the formatter either with the specified format string, or a
default as described above. Allow for specialized date formatting with
the optional datefmt argument (if omitted, you get the ISO8601 format).
+
+ Use a style parameter of '%', '{' or '$' to specify that you want to
+ use one of %-formatting, :meth:`str.format` (``{}``) formatting or
+ :class:`string.Template` formatting in your format string.
+
+ .. versionchanged: 3.2
+ Added the ``style`` parameter.
"""
- if fmt:
- self._fmt = fmt
- else:
- self._fmt = "%(message)s"
+ if style not in _STYLES:
+ raise ValueError('Style must be one of: %s' % ','.join(
+ _STYLES.keys()))
+ self._style = _STYLES[style](fmt)
+ self._fmt = self._style._fmt
self.datefmt = datefmt
def formatTime(self, record, datefmt=None):
@@ -404,7 +491,7 @@ class Formatter:
s = time.strftime(datefmt, ct)
else:
t = time.strftime("%Y-%m-%d %H:%M:%S", ct)
- s = "%s,%03d" % (t, record.msecs)
+ s = "%s,%03d" % (t, record.msecs) # the use of % here is internal
return s
def formatException(self, ei):
@@ -415,13 +502,39 @@ class Formatter:
traceback.print_exception()
"""
sio = io.StringIO()
- traceback.print_exception(ei[0], ei[1], ei[2], None, sio)
+ tb = ei[2]
+ # See issues #9427, #1553375. Commented out for now.
+ #if getattr(self, 'fullstack', False):
+ # traceback.print_stack(tb.tb_frame.f_back, file=sio)
+ traceback.print_exception(ei[0], ei[1], tb, None, sio)
s = sio.getvalue()
sio.close()
if s[-1:] == "\n":
s = s[:-1]
return s
+ def usesTime(self):
+ """
+ Check if the format uses the creation time of the record.
+ """
+ return self._style.usesTime()
+
+ def formatMessage(self, record):
+ return self._style.format(record)
+
+ def formatStack(self, stack_info):
+ """
+ This method is provided as an extension point for specialized
+ formatting of stack information.
+
+ The input data is a string as returned from a call to
+ :func:`traceback.print_stack`, but with the last trailing newline
+ removed.
+
+ The base implementation just returns the value passed in.
+ """
+ return stack_info
+
def format(self, record):
"""
Format the specified record as text.
@@ -430,15 +543,15 @@ class Formatter:
string formatting operation which yields the returned string.
Before formatting the dictionary, a couple of preparatory steps
are carried out. The message attribute of the record is computed
- using LogRecord.getMessage(). If the formatting string contains
- "%(asctime)", formatTime() is called to format the event time.
- If there is exception information, it is formatted using
- formatException() and appended to the message.
+ using LogRecord.getMessage(). If the formatting string uses the
+ time (as determined by a call to usesTime(), formatTime() is
+ called to format the event time. If there is exception information,
+ it is formatted using formatException() and appended to the message.
"""
record.message = record.getMessage()
- if self._fmt.find("%(asctime)") >= 0:
+ if self.usesTime():
record.asctime = self.formatTime(record, self.datefmt)
- s = self._fmt % record.__dict__
+ s = self.formatMessage(record)
if record.exc_info:
# Cache the traceback text to avoid converting it multiple times
# (it's constant anyway)
@@ -448,6 +561,10 @@ class Formatter:
if s[-1:] != "\n":
s = s + "\n"
s = s + record.exc_text
+ if record.stack_info:
+ if s[-1:] != "\n":
+ s = s + "\n"
+ s = s + self.formatStack(record.stack_info)
return s
#
@@ -455,7 +572,7 @@ class Formatter:
#
_defaultFormatter = Formatter()
-class BufferingFormatter:
+class BufferingFormatter(object):
"""
A formatter suitable for formatting a number of records.
"""
@@ -497,7 +614,7 @@ class BufferingFormatter:
# Filter classes and functions
#---------------------------------------------------------------------------
-class Filter:
+class Filter(object):
"""
Filter instances are used to perform arbitrary filtering of LogRecords.
@@ -534,7 +651,7 @@ class Filter:
return 0
return (record.name[self.nlen] == ".")
-class Filterer:
+class Filterer(object):
"""
A base class for loggers and handlers which allows them to share
common code.
@@ -566,10 +683,18 @@ class Filterer:
The default is to allow the record to be logged; any filter can veto
this and the record is then dropped. Returns a zero value if a record
is to be dropped, else non-zero.
+
+ .. versionchanged: 3.2
+
+ Allow filters to be just callables.
"""
rv = 1
for f in self.filters:
- if not f.filter(record):
+ if hasattr(f, 'filter'):
+ result = f.filter(record)
+ else:
+ result = f(record) # assume callable - will raise if not
+ if not result:
rv = 0
break
return rv
@@ -578,9 +703,34 @@ class Filterer:
# Handler classes and functions
#---------------------------------------------------------------------------
-_handlers = {} #repository of handlers (for flushing when shutdown called)
+_handlers = weakref.WeakValueDictionary() #map of handler names to handlers
_handlerList = [] # added to allow handlers to be removed in reverse of order initialized
+def _removeHandlerRef(wr):
+ """
+ Remove a handler reference from the internal cleanup list.
+ """
+ # This function can be called during module teardown, when globals are
+ # set to None. If _acquireLock is None, assume this is the case and do
+ # nothing.
+ if _acquireLock is not None:
+ _acquireLock()
+ try:
+ if wr in _handlerList:
+ _handlerList.remove(wr)
+ finally:
+ _releaseLock()
+
+def _addHandlerRef(handler):
+ """
+ Add a handler to the internal cleanup list using a weak reference.
+ """
+ _acquireLock()
+ try:
+ _handlerList.append(weakref.ref(handler, _removeHandlerRef))
+ finally:
+ _releaseLock()
+
class Handler(Filterer):
"""
Handler instances dispatch logging events to specific destinations.
@@ -596,16 +746,28 @@ class Handler(Filterer):
and the filter list to empty.
"""
Filterer.__init__(self)
- self.level = level
+ self._name = None
+ self.level = _checkLevel(level)
self.formatter = None
- #get the module data lock, as we're updating a shared structure.
+ # Add the handler to the global _handlerList (for cleanup on shutdown)
+ _addHandlerRef(self)
+ self.createLock()
+
+ def get_name(self):
+ return self._name
+
+ def set_name(self, name):
_acquireLock()
- try: #unlikely to raise an exception, but you never know...
- _handlers[self] = 1
- _handlerList.insert(0, self)
+ try:
+ if self._name in _handlers:
+ del _handlers[self._name]
+ self._name = name
+ if name:
+ _handlers[name] = self
finally:
_releaseLock()
- self.createLock()
+
+ name = property(get_name, set_name)
def createLock(self):
"""
@@ -634,7 +796,7 @@ class Handler(Filterer):
"""
Set the logging level of this handler.
"""
- self.level = level
+ self.level = _checkLevel(level)
def format(self, record):
"""
@@ -696,16 +858,16 @@ class Handler(Filterer):
"""
Tidy up any resources used by the handler.
- This version does removes the handler from an internal list
- of handlers which is closed when shutdown() is called. Subclasses
+ This version removes the handler from an internal map of handlers,
+ _handlers, which is used for handler lookup by name. Subclasses
should ensure that this gets called from overridden close()
methods.
"""
#get the module data lock, as we're updating a shared structure.
_acquireLock()
try: #unlikely to raise an exception, but you never know...
- del _handlers[self]
- _handlerList.remove(self)
+ if self._name and self._name in _handlers:
+ del _handlers[self._name]
finally:
_releaseLock()
@@ -724,7 +886,10 @@ class Handler(Filterer):
if raiseExceptions:
ei = sys.exc_info()
try:
- traceback.print_exception(ei[0], ei[1], ei[2], None, sys.stderr)
+ traceback.print_exception(ei[0], ei[1], ei[2],
+ None, sys.stderr)
+ sys.stderr.write('Logged from file %s, line %s\n' % (
+ record.filename, record.lineno))
except IOError:
pass # see issue 5971
finally:
@@ -737,6 +902,8 @@ class StreamHandler(Handler):
sys.stdout or sys.stderr may be used.
"""
+ terminator = '\n'
+
def __init__(self, stream=None):
"""
Initialize the handler.
@@ -769,28 +936,8 @@ class StreamHandler(Handler):
try:
msg = self.format(record)
stream = self.stream
- fs = "%s\n"
- if not _unicode: #if no unicode support...
- stream.write(fs % msg)
- else:
- try:
- if (isinstance(msg, unicode) and
- getattr(stream, 'encoding', None)):
- fs = fs.decode(stream.encoding)
- try:
- stream.write(fs % msg)
- except UnicodeEncodeError:
- #Printing to terminals sometimes fails. For example,
- #with an encoding of 'cp1251', the above write will
- #work if written to a stream opened or wrapped by
- #the codecs module, but fail when writing to a
- #terminal even when the codepage is set to cp1251.
- #An extra encoding step seems to be needed.
- stream.write((fs % msg).encode(stream.encoding))
- else:
- stream.write(fs % msg)
- except UnicodeError:
- stream.write(fs % msg.encode("UTF-8"))
+ stream.write(msg)
+ stream.write(self.terminator)
self.flush()
except (KeyboardInterrupt, SystemExit):
raise
@@ -853,11 +1000,31 @@ class FileHandler(StreamHandler):
self.stream = self._open()
StreamHandler.emit(self, record)
+class _StderrHandler(StreamHandler):
+ """
+ This class is like a StreamHandler using sys.stderr, but always uses
+ whatever sys.stderr is currently set to rather than the value of
+ sys.stderr at handler construction time.
+ """
+ def __init__(self, level=NOTSET):
+ """
+ Initialize the handler.
+ """
+ Handler.__init__(self, level)
+
+ @property
+ def stream(self):
+ return sys.stderr
+
+
+_defaultLastResort = _StderrHandler(WARNING)
+lastResort = _defaultLastResort
+
#---------------------------------------------------------------------------
# Manager classes and functions
#---------------------------------------------------------------------------
-class PlaceHolder:
+class PlaceHolder(object):
"""
PlaceHolder instances are used in the Manager logger hierarchy to take
the place of nodes for which no loggers have been defined. This class is
@@ -867,16 +1034,13 @@ class PlaceHolder:
"""
Initialize with the specified logger being a child of this placeholder.
"""
- #self.loggers = [alogger]
self.loggerMap = { alogger : None }
def append(self, alogger):
"""
Add the specified logger as a child of this placeholder.
"""
- #if alogger not in self.loggers:
if alogger not in self.loggerMap:
- #self.loggers.append(alogger)
self.loggerMap[alogger] = None
#
@@ -904,7 +1068,7 @@ def getLoggerClass():
return _loggerClass
-class Manager:
+class Manager(object):
"""
There is [under normal circumstances] just one Manager instance, which
holds the hierarchy of loggers.
@@ -915,8 +1079,10 @@ class Manager:
"""
self.root = rootnode
self.disable = 0
- self.emittedNoHandlerWarning = 0
+ self.emittedNoHandlerWarning = False
self.loggerDict = {}
+ self.loggerClass = None
+ self.logRecordFactory = None
def getLogger(self, name):
"""
@@ -936,13 +1102,13 @@ class Manager:
rv = self.loggerDict[name]
if isinstance(rv, PlaceHolder):
ph = rv
- rv = _loggerClass(name)
+ rv = (self.loggerClass or _loggerClass)(name)
rv.manager = self
self.loggerDict[name] = rv
self._fixupChildren(ph, rv)
self._fixupParents(rv)
else:
- rv = _loggerClass(name)
+ rv = (self.loggerClass or _loggerClass)(name)
rv.manager = self
self.loggerDict[name] = rv
self._fixupParents(rv)
@@ -950,6 +1116,23 @@ class Manager:
_releaseLock()
return rv
+ def setLoggerClass(self, klass):
+ """
+ Set the class to be used when instantiating a logger with this Manager.
+ """
+ if klass != Logger:
+ if not issubclass(klass, Logger):
+ raise TypeError("logger not derived from logging.Logger: "
+ + klass.__name__)
+ self.loggerClass = klass
+
+ def setLogRecordFactory(self, factory):
+ """
+ Set the factory to be used when instantiating a log record with this
+ Manager.
+ """
+ self.logRecordFactory = factory
+
def _fixupParents(self, alogger):
"""
Ensure that there are either loggers or placeholders all the way
@@ -1012,7 +1195,7 @@ class Logger(Filterer):
"""
Filterer.__init__(self)
self.name = name
- self.level = level
+ self.level = _checkLevel(level)
self.parent = None
self.propagate = 1
self.handlers = []
@@ -1022,7 +1205,7 @@ class Logger(Filterer):
"""
Set the logging level of this logger.
"""
- self.level = level
+ self.level = _checkLevel(level)
def debug(self, msg, *args, **kwargs):
"""
@@ -1074,11 +1257,12 @@ class Logger(Filterer):
if self.isEnabledFor(ERROR):
self._log(ERROR, msg, args, **kwargs)
- def exception(self, msg, *args):
+ def exception(self, msg, *args, **kwargs):
"""
Convenience method for logging an ERROR with exception information.
"""
- self.error(msg, exc_info=1, *args)
+ kwargs['exc_info'] = True
+ self.error(msg, *args, **kwargs)
def critical(self, msg, *args, **kwargs):
"""
@@ -1111,7 +1295,7 @@ class Logger(Filterer):
if self.isEnabledFor(level):
self._log(level, msg, args, **kwargs)
- def findCaller(self):
+ def findCaller(self, stack_info=False):
"""
Find the stack frame of the caller so that we can note the source
file name, line number and function name.
@@ -1121,23 +1305,34 @@ class Logger(Filterer):
#IronPython isn't run with -X:Frames.
if f is not None:
f = f.f_back
- rv = "(unknown file)", 0, "(unknown function)"
+ rv = "(unknown file)", 0, "(unknown function)", None
while hasattr(f, "f_code"):
co = f.f_code
filename = os.path.normcase(co.co_filename)
if filename == _srcfile:
f = f.f_back
continue
- rv = (filename, f.f_lineno, co.co_name)
+ sinfo = None
+ if stack_info:
+ sio = io.StringIO()
+ sio.write('Stack (most recent call last):\n')
+ traceback.print_stack(f, file=sio)
+ sinfo = sio.getvalue()
+ if sinfo[-1] == '\n':
+ sinfo = sinfo[:-1]
+ sio.close()
+ rv = (co.co_filename, f.f_lineno, co.co_name, sinfo)
break
return rv
- def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None):
+ def makeRecord(self, name, level, fn, lno, msg, args, exc_info,
+ func=None, extra=None, sinfo=None):
"""
A factory method which can be overridden in subclasses to create
specialized LogRecords.
"""
- rv = LogRecord(name, level, fn, lno, msg, args, exc_info, func)
+ rv = _logRecordFactory(name, level, fn, lno, msg, args, exc_info, func,
+ sinfo)
if extra is not None:
for key in extra:
if (key in ["message", "asctime"]) or (key in rv.__dict__):
@@ -1145,17 +1340,18 @@ class Logger(Filterer):
rv.__dict__[key] = extra[key]
return rv
- def _log(self, level, msg, args, exc_info=None, extra=None):
+ def _log(self, level, msg, args, exc_info=None, extra=None, stack_info=False):
"""
Low-level logging routine which creates a LogRecord and then calls
all the handlers of this logger to handle the record.
"""
+ sinfo = None
if _srcfile:
#IronPython doesn't track Python frames, so findCaller throws an
#exception on some versions of IronPython. We trap it here so that
#IronPython can use logging.
try:
- fn, lno, func = self.findCaller()
+ fn, lno, func, sinfo = self.findCaller(stack_info)
except ValueError:
fn, lno, func = "(unknown file)", 0, "(unknown function)"
else:
@@ -1163,7 +1359,8 @@ class Logger(Filterer):
if exc_info:
if not isinstance(exc_info, tuple):
exc_info = sys.exc_info()
- record = self.makeRecord(self.name, level, fn, lno, msg, args, exc_info, func, extra)
+ record = self.makeRecord(self.name, level, fn, lno, msg, args,
+ exc_info, func, extra, sinfo)
self.handle(record)
def handle(self, record):
@@ -1198,6 +1395,28 @@ class Logger(Filterer):
finally:
_releaseLock()
+ def hasHandlers(self):
+ """
+ See if this logger has any handlers configured.
+
+ Loop through all handlers for this logger and its parents in the
+ logger hierarchy. Return True if a handler was found, else False.
+ Stop searching up the hierarchy whenever a logger with the "propagate"
+ attribute set to zero is found - that will be the last logger which
+ is checked for the existence of handlers.
+ """
+ c = self
+ rv = False
+ while c:
+ if c.handlers:
+ rv = True
+ break
+ if not c.propagate:
+ break
+ else:
+ c = c.parent
+ return rv
+
def callHandlers(self, record):
"""
Pass a record to all relevant handlers.
@@ -1219,10 +1438,13 @@ class Logger(Filterer):
c = None #break out
else:
c = c.parent
- if (found == 0) and raiseExceptions and not self.manager.emittedNoHandlerWarning:
- sys.stderr.write("No handlers could be found for logger"
- " \"%s\"\n" % self.name)
- self.manager.emittedNoHandlerWarning = 1
+ if (found == 0):
+ if lastResort:
+ lastResort.handle(record)
+ elif raiseExceptions and not self.manager.emittedNoHandlerWarning:
+ sys.stderr.write("No handlers could be found for logger"
+ " \"%s\"\n" % self.name)
+ self.manager.emittedNoHandlerWarning = True
def getEffectiveLevel(self):
"""
@@ -1246,6 +1468,25 @@ class Logger(Filterer):
return 0
return level >= self.getEffectiveLevel()
+ def getChild(self, suffix):
+ """
+ Get a logger which is a descendant to this one.
+
+ This is a convenience method, such that
+
+ logging.getLogger('abc').getChild('def.ghi')
+
+ is the same as
+
+ logging.getLogger('abc.def.ghi')
+
+ It's useful, for example, when the parent logger is named using
+ __name__ rather than a literal string.
+ """
+ if self.root is not self:
+ suffix = '.'.join((self.name, suffix))
+ return self.manager.getLogger(suffix)
+
class RootLogger(Logger):
"""
A root logger is not that different to any other logger, except that
@@ -1260,7 +1501,7 @@ class RootLogger(Logger):
_loggerClass = Logger
-class LoggerAdapter:
+class LoggerAdapter(object):
"""
An adapter for loggers which makes it easier to specify contextual
information in logging output.
@@ -1293,62 +1534,82 @@ class LoggerAdapter:
kwargs["extra"] = self.extra
return msg, kwargs
+ #
+ # Boilerplate convenience methods
+ #
def debug(self, msg, *args, **kwargs):
"""
- Delegate a debug call to the underlying logger, after adding
- contextual information from this adapter instance.
+ Delegate a debug call to the underlying logger.
"""
- msg, kwargs = self.process(msg, kwargs)
- self.logger.debug(msg, *args, **kwargs)
+ self.log(DEBUG, msg, *args, **kwargs)
def info(self, msg, *args, **kwargs):
"""
- Delegate an info call to the underlying logger, after adding
- contextual information from this adapter instance.
+ Delegate an info call to the underlying logger.
"""
- msg, kwargs = self.process(msg, kwargs)
- self.logger.info(msg, *args, **kwargs)
+ self.log(INFO, msg, *args, **kwargs)
def warning(self, msg, *args, **kwargs):
"""
- Delegate a warning call to the underlying logger, after adding
- contextual information from this adapter instance.
+ Delegate a warning call to the underlying logger.
"""
- msg, kwargs = self.process(msg, kwargs)
- self.logger.warning(msg, *args, **kwargs)
+ self.log(WARNING, msg, *args, **kwargs)
+
+ warn = warning
def error(self, msg, *args, **kwargs):
"""
- Delegate an error call to the underlying logger, after adding
- contextual information from this adapter instance.
+ Delegate an error call to the underlying logger.
"""
- msg, kwargs = self.process(msg, kwargs)
- self.logger.error(msg, *args, **kwargs)
+ self.log(ERROR, msg, *args, **kwargs)
def exception(self, msg, *args, **kwargs):
"""
- Delegate an exception call to the underlying logger, after adding
- contextual information from this adapter instance.
+ Delegate an exception call to the underlying logger.
"""
- msg, kwargs = self.process(msg, kwargs)
kwargs["exc_info"] = 1
- self.logger.error(msg, *args, **kwargs)
+ self.log(ERROR, msg, *args, **kwargs)
def critical(self, msg, *args, **kwargs):
"""
- Delegate a critical call to the underlying logger, after adding
- contextual information from this adapter instance.
+ Delegate a critical call to the underlying logger.
"""
- msg, kwargs = self.process(msg, kwargs)
- self.logger.critical(msg, *args, **kwargs)
+ self.log(CRITICAL, msg, *args, **kwargs)
def log(self, level, msg, *args, **kwargs):
"""
Delegate a log call to the underlying logger, after adding
contextual information from this adapter instance.
"""
- msg, kwargs = self.process(msg, kwargs)
- self.logger.log(level, msg, *args, **kwargs)
+ if self.isEnabledFor(level):
+ msg, kwargs = self.process(msg, kwargs)
+ self.logger._log(level, msg, args, **kwargs)
+
+ def isEnabledFor(self, level):
+ """
+ Is this logger enabled for level 'level'?
+ """
+ if self.logger.manager.disable >= level:
+ return False
+ return level >= self.getEffectiveLevel()
+
+ def setLevel(self, level):
+ """
+ Set the specified level on the underlying logger.
+ """
+ self.logger.setLevel(level)
+
+ def getEffectiveLevel(self):
+ """
+ Get the effective level for the underlying logger.
+ """
+ return self.logger.getEffectiveLevel()
+
+ def hasHandlers(self):
+ """
+ See if the underlying logger has any handlers.
+ """
+ return self.logger.hasHandlers()
root = RootLogger(WARNING)
Logger.root = root
@@ -1381,6 +1642,10 @@ def basicConfig(**kwargs):
(if filemode is unspecified, it defaults to 'a').
format Use the specified format string for the handler.
datefmt Use the specified date/time format.
+ style If a format string is specified, use this to specify the
+ type of format string (possible values '%', '{', '$', for
+ %-formatting, :meth:`str.format` and :class:`string.Template`
+ - defaults to '%').
level Set the root logger level to the specified level.
stream Use the specified stream to initialize the StreamHandler. Note
that this argument is incompatible with 'filename' - if both
@@ -1391,6 +1656,9 @@ def basicConfig(**kwargs):
remembered that StreamHandler does not close its stream (since it may be
using sys.stdout or sys.stderr), whereas FileHandler closes its stream
when the handler is closed.
+
+ .. versionchanged: 3.2
+ Added the ``style`` parameter.
"""
# Add thread safety in case someone mistakenly calls
# basicConfig() from multiple threads
@@ -1406,7 +1674,8 @@ def basicConfig(**kwargs):
hdlr = StreamHandler(stream)
fs = kwargs.get("format", BASIC_FORMAT)
dfs = kwargs.get("datefmt", None)
- fmt = Formatter(fs, dfs)
+ style = kwargs.get("style", '%')
+ fmt = Formatter(fs, dfs, style)
hdlr.setFormatter(fmt)
root.addHandler(hdlr)
level = kwargs.get("level")
@@ -1433,7 +1702,9 @@ def getLogger(name=None):
def critical(msg, *args, **kwargs):
"""
- Log a message with severity 'CRITICAL' on the root logger.
+ Log a message with severity 'CRITICAL' on the root logger. If the logger
+ has no handlers, call basicConfig() to add a console handler with a
+ pre-defined format.
"""
if len(root.handlers) == 0:
basicConfig()
@@ -1443,22 +1714,28 @@ fatal = critical
def error(msg, *args, **kwargs):
"""
- Log a message with severity 'ERROR' on the root logger.
+ Log a message with severity 'ERROR' on the root logger. If the logger has
+ no handlers, call basicConfig() to add a console handler with a pre-defined
+ format.
"""
if len(root.handlers) == 0:
basicConfig()
root.error(msg, *args, **kwargs)
-def exception(msg, *args):
+def exception(msg, *args, **kwargs):
"""
- Log a message with severity 'ERROR' on the root logger,
- with exception information.
+ Log a message with severity 'ERROR' on the root logger, with exception
+ information. If the logger has no handlers, basicConfig() is called to add
+ a console handler with a pre-defined format.
"""
- error(msg, exc_info=1, *args)
+ kwargs['exc_info'] = True
+ error(msg, *args, **kwargs)
def warning(msg, *args, **kwargs):
"""
- Log a message with severity 'WARNING' on the root logger.
+ Log a message with severity 'WARNING' on the root logger. If the logger has
+ no handlers, call basicConfig() to add a console handler with a pre-defined
+ format.
"""
if len(root.handlers) == 0:
basicConfig()
@@ -1468,7 +1745,9 @@ warn = warning
def info(msg, *args, **kwargs):
"""
- Log a message with severity 'INFO' on the root logger.
+ Log a message with severity 'INFO' on the root logger. If the logger has
+ no handlers, call basicConfig() to add a console handler with a pre-defined
+ format.
"""
if len(root.handlers) == 0:
basicConfig()
@@ -1476,7 +1755,9 @@ def info(msg, *args, **kwargs):
def debug(msg, *args, **kwargs):
"""
- Log a message with severity 'DEBUG' on the root logger.
+ Log a message with severity 'DEBUG' on the root logger. If the logger has
+ no handlers, call basicConfig() to add a console handler with a pre-defined
+ format.
"""
if len(root.handlers) == 0:
basicConfig()
@@ -1484,7 +1765,9 @@ def debug(msg, *args, **kwargs):
def log(level, msg, *args, **kwargs):
"""
- Log 'msg % args' with the integer severity 'level' on the root logger.
+ Log 'msg % args' with the integer severity 'level' on the root logger. If
+ the logger has no handlers, call basicConfig() to add a console handler
+ with a pre-defined format.
"""
if len(root.handlers) == 0:
basicConfig()
@@ -1492,7 +1775,7 @@ def log(level, msg, *args, **kwargs):
def disable(level):
"""
- Disable all logging calls less severe than 'level'.
+ Disable all logging calls of severity 'level' and below.
"""
root.manager.disable = level
@@ -1503,32 +1786,32 @@ def shutdown(handlerList=_handlerList):
Should be called at application exit.
"""
- for h in handlerList[:]:
+ for wr in reversed(handlerList[:]):
#errors might occur, for example, if files are locked
#we just ignore them if raiseExceptions is not set
try:
- h.acquire()
- h.flush()
- h.close()
+ h = wr()
+ if h:
+ try:
+ h.acquire()
+ h.flush()
+ h.close()
+ except (IOError, ValueError):
+ # Ignore errors which might be caused
+ # because handlers have been closed but
+ # references to them are still around at
+ # application exit.
+ pass
+ finally:
+ h.release()
except:
if raiseExceptions:
raise
#else, swallow
- finally:
- h.release()
#Let's try and shutdown automatically on application exit...
-try:
- import atexit
- atexit.register(shutdown)
-except ImportError: # for Python versions < 2.0
- def exithook(status, old_exit=sys.exit):
- try:
- shutdown()
- finally:
- old_exit(status)
-
- sys.exit = exithook
+import atexit
+atexit.register(shutdown)
# Null handler
diff --git a/Lib/logging/config.py b/Lib/logging/config.py
index 25f34ec..c7359ca 100644
--- a/Lib/logging/config.py
+++ b/Lib/logging/config.py
@@ -1,4 +1,4 @@
-# Copyright 2001-2007 by Vinay Sajip. All Rights Reserved.
+# Copyright 2001-2010 by Vinay Sajip. All Rights Reserved.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose and without fee is hereby granted,
@@ -24,7 +24,8 @@ Copyright (C) 2001-2010 Vinay Sajip. All Rights Reserved.
To use, simply 'import logging' and log away!
"""
-import sys, logging, logging.handlers, socket, struct, os, traceback
+import sys, logging, logging.handlers, socket, struct, os, traceback, re
+import types, io
try:
import _thread as thread
@@ -57,16 +58,12 @@ def fileConfig(fname, defaults=None, disable_existing_loggers=True):
the ability to select from various pre-canned configurations (if the
developer provides a mechanism to present the choices and load the chosen
configuration).
- In versions of ConfigParser which have the readfp method [typically
- shipped in 2.x versions of Python], you can pass in a file-like object
- rather than a filename, in which case the file-like object will be read
- using readfp.
"""
import configparser
cp = configparser.ConfigParser(defaults)
- if hasattr(cp, 'readfp') and hasattr(fname, 'readline'):
- cp.readfp(fname)
+ if hasattr(fname, 'readline'):
+ cp.read_file(fname)
else:
cp.read(fname)
@@ -101,9 +98,12 @@ def _resolve(name):
def _strip_spaces(alist):
return map(lambda x: x.strip(), alist)
+def _encoded(s):
+ return s if isinstance(s, str) else s.encode('utf-8')
+
def _create_formatters(cp):
"""Create and return formatters"""
- flist = cp.get("formatters", "keys")
+ flist = cp["formatters"]["keys"]
if not len(flist):
return {}
flist = flist.split(",")
@@ -111,20 +111,12 @@ def _create_formatters(cp):
formatters = {}
for form in flist:
sectname = "formatter_%s" % form
- opts = cp.options(sectname)
- if "format" in opts:
- fs = cp.get(sectname, "format", 1)
- else:
- fs = None
- if "datefmt" in opts:
- dfs = cp.get(sectname, "datefmt", 1)
- else:
- dfs = None
+ fs = cp.get(sectname, "format", raw=True, fallback=None)
+ dfs = cp.get(sectname, "datefmt", raw=True, fallback=None)
c = logging.Formatter
- if "class" in opts:
- class_name = cp.get(sectname, "class")
- if class_name:
- c = _resolve(class_name)
+ class_name = cp[sectname].get("class")
+ if class_name:
+ c = _resolve(class_name)
f = c(fs, dfs)
formatters[form] = f
return formatters
@@ -132,7 +124,7 @@ def _create_formatters(cp):
def _install_handlers(cp, formatters):
"""Install and return handlers"""
- hlist = cp.get("handlers", "keys")
+ hlist = cp["handlers"]["keys"]
if not len(hlist):
return {}
hlist = hlist.split(",")
@@ -140,30 +132,23 @@ def _install_handlers(cp, formatters):
handlers = {}
fixups = [] #for inter-handler references
for hand in hlist:
- sectname = "handler_%s" % hand
- klass = cp.get(sectname, "class")
- opts = cp.options(sectname)
- if "formatter" in opts:
- fmt = cp.get(sectname, "formatter")
- else:
- fmt = ""
+ section = cp["handler_%s" % hand]
+ klass = section["class"]
+ fmt = section.get("formatter", "")
try:
klass = eval(klass, vars(logging))
except (AttributeError, NameError):
klass = _resolve(klass)
- args = cp.get(sectname, "args")
+ args = section["args"]
args = eval(args, vars(logging))
h = klass(*args)
- if "level" in opts:
- level = cp.get(sectname, "level")
+ if "level" in section:
+ level = section["level"]
h.setLevel(logging._levelNames[level])
if len(fmt):
h.setFormatter(formatters[fmt])
if issubclass(klass, logging.handlers.MemoryHandler):
- if "target" in opts:
- target = cp.get(sectname,"target")
- else:
- target = ""
+ target = section.get("target", "")
if len(target): #the target handler may not be loaded yet, so keep for later...
fixups.append((h, target))
handlers[hand] = h
@@ -172,25 +157,44 @@ def _install_handlers(cp, formatters):
h.setTarget(handlers[t])
return handlers
+def _handle_existing_loggers(existing, child_loggers, disable_existing):
+ """
+ When (re)configuring logging, handle loggers which were in the previous
+ configuration but are not in the new configuration. There's no point
+ deleting them as other threads may continue to hold references to them;
+ and by disabling them, you stop them doing any logging.
+
+ However, don't disable children of named loggers, as that's probably not
+ what was intended by the user. Also, allow existing loggers to NOT be
+ disabled if disable_existing is false.
+ """
+ root = logging.root
+ for log in existing:
+ logger = root.manager.loggerDict[log]
+ if log in child_loggers:
+ logger.level = logging.NOTSET
+ logger.handlers = []
+ logger.propagate = True
+ elif disable_existing:
+ logger.disabled = True
-def _install_loggers(cp, handlers, disable_existing_loggers):
+def _install_loggers(cp, handlers, disable_existing):
"""Create and install loggers"""
# configure the root first
- llist = cp.get("loggers", "keys")
+ llist = cp["loggers"]["keys"]
llist = llist.split(",")
llist = list(map(lambda x: x.strip(), llist))
llist.remove("root")
- sectname = "logger_root"
+ section = cp["logger_root"]
root = logging.root
log = root
- opts = cp.options(sectname)
- if "level" in opts:
- level = cp.get(sectname, "level")
+ if "level" in section:
+ level = section["level"]
log.setLevel(logging._levelNames[level])
for h in root.handlers[:]:
root.removeHandler(h)
- hlist = cp.get(sectname, "handlers")
+ hlist = section["handlers"]
if len(hlist):
hlist = hlist.split(",")
hlist = _strip_spaces(hlist)
@@ -211,19 +215,15 @@ def _install_loggers(cp, handlers, disable_existing_loggers):
#avoid disabling child loggers of explicitly
#named loggers. With a sorted list it is easier
#to find the child loggers.
- existing.sort()
+ existing.sort(key=_encoded)
#We'll keep the list of existing loggers
#which are children of named loggers here...
child_loggers = []
#now set up the new ones...
for log in llist:
- sectname = "logger_%s" % log
- qn = cp.get(sectname, "qualname")
- opts = cp.options(sectname)
- if "propagate" in opts:
- propagate = cp.getint(sectname, "propagate")
- else:
- propagate = 1
+ section = cp["logger_%s" % log]
+ qn = section["qualname"]
+ propagate = section.getint("propagate", fallback=1)
logger = logging.getLogger(qn)
if qn in existing:
i = existing.index(qn) + 1 # start with the entry after qn
@@ -235,14 +235,14 @@ def _install_loggers(cp, handlers, disable_existing_loggers):
child_loggers.append(existing[i])
i += 1
existing.remove(qn)
- if "level" in opts:
- level = cp.get(sectname, "level")
+ if "level" in section:
+ level = section["level"]
logger.setLevel(logging._levelNames[level])
for h in logger.handlers[:]:
logger.removeHandler(h)
logger.propagate = propagate
logger.disabled = 0
- hlist = cp.get(sectname, "handlers")
+ hlist = section["handlers"]
if len(hlist):
hlist = hlist.split(",")
hlist = _strip_spaces(hlist)
@@ -254,14 +254,526 @@ def _install_loggers(cp, handlers, disable_existing_loggers):
#and by disabling them, you stop them doing any logging.
#However, don't disable children of named loggers, as that's
#probably not what was intended by the user.
- for log in existing:
- logger = root.manager.loggerDict[log]
- if log in child_loggers:
- logger.level = logging.NOTSET
- logger.handlers = []
- logger.propagate = 1
- elif disable_existing_loggers:
- logger.disabled = 1
+ #for log in existing:
+ # logger = root.manager.loggerDict[log]
+ # if log in child_loggers:
+ # logger.level = logging.NOTSET
+ # logger.handlers = []
+ # logger.propagate = 1
+ # elif disable_existing_loggers:
+ # logger.disabled = 1
+ _handle_existing_loggers(existing, child_loggers, disable_existing)
+
+IDENTIFIER = re.compile('^[a-z_][a-z0-9_]*$', re.I)
+
+
+def valid_ident(s):
+ m = IDENTIFIER.match(s)
+ if not m:
+ raise ValueError('Not a valid Python identifier: %r' % s)
+ return True
+
+
+# The ConvertingXXX classes are wrappers around standard Python containers,
+# and they serve to convert any suitable values in the container. The
+# conversion converts base dicts, lists and tuples to their wrapped
+# equivalents, whereas strings which match a conversion format are converted
+# appropriately.
+#
+# Each wrapper should have a configurator attribute holding the actual
+# configurator to use for conversion.
+
+class ConvertingDict(dict):
+ """A converting dictionary wrapper."""
+
+ def __getitem__(self, key):
+ value = dict.__getitem__(self, key)
+ result = self.configurator.convert(value)
+ #If the converted value is different, save for next time
+ if value is not result:
+ self[key] = result
+ if type(result) in (ConvertingDict, ConvertingList,
+ ConvertingTuple):
+ result.parent = self
+ result.key = key
+ return result
+
+ def get(self, key, default=None):
+ value = dict.get(self, key, default)
+ result = self.configurator.convert(value)
+ #If the converted value is different, save for next time
+ if value is not result:
+ self[key] = result
+ if type(result) in (ConvertingDict, ConvertingList,
+ ConvertingTuple):
+ result.parent = self
+ result.key = key
+ return result
+
+ def pop(self, key, default=None):
+ value = dict.pop(self, key, default)
+ result = self.configurator.convert(value)
+ if value is not result:
+ if type(result) in (ConvertingDict, ConvertingList,
+ ConvertingTuple):
+ result.parent = self
+ result.key = key
+ return result
+
+class ConvertingList(list):
+ """A converting list wrapper."""
+ def __getitem__(self, key):
+ value = list.__getitem__(self, key)
+ result = self.configurator.convert(value)
+ #If the converted value is different, save for next time
+ if value is not result:
+ self[key] = result
+ if type(result) in (ConvertingDict, ConvertingList,
+ ConvertingTuple):
+ result.parent = self
+ result.key = key
+ return result
+
+ def pop(self, idx=-1):
+ value = list.pop(self, idx)
+ result = self.configurator.convert(value)
+ if value is not result:
+ if type(result) in (ConvertingDict, ConvertingList,
+ ConvertingTuple):
+ result.parent = self
+ return result
+
+class ConvertingTuple(tuple):
+ """A converting tuple wrapper."""
+ def __getitem__(self, key):
+ value = tuple.__getitem__(self, key)
+ result = self.configurator.convert(value)
+ if value is not result:
+ if type(result) in (ConvertingDict, ConvertingList,
+ ConvertingTuple):
+ result.parent = self
+ result.key = key
+ return result
+
+class BaseConfigurator(object):
+ """
+ The configurator base class which defines some useful defaults.
+ """
+
+ CONVERT_PATTERN = re.compile(r'^(?P<prefix>[a-z]+)://(?P<suffix>.*)$')
+
+ WORD_PATTERN = re.compile(r'^\s*(\w+)\s*')
+ DOT_PATTERN = re.compile(r'^\.\s*(\w+)\s*')
+ INDEX_PATTERN = re.compile(r'^\[\s*(\w+)\s*\]\s*')
+ DIGIT_PATTERN = re.compile(r'^\d+$')
+
+ value_converters = {
+ 'ext' : 'ext_convert',
+ 'cfg' : 'cfg_convert',
+ }
+
+ # We might want to use a different one, e.g. importlib
+ importer = staticmethod(__import__)
+
+ def __init__(self, config):
+ self.config = ConvertingDict(config)
+ self.config.configurator = self
+
+ def resolve(self, s):
+ """
+ Resolve strings to objects using standard import and attribute
+ syntax.
+ """
+ name = s.split('.')
+ used = name.pop(0)
+ try:
+ found = self.importer(used)
+ for frag in name:
+ used += '.' + frag
+ try:
+ found = getattr(found, frag)
+ except AttributeError:
+ self.importer(used)
+ found = getattr(found, frag)
+ return found
+ except ImportError:
+ e, tb = sys.exc_info()[1:]
+ v = ValueError('Cannot resolve %r: %s' % (s, e))
+ v.__cause__, v.__traceback__ = e, tb
+ raise v
+
+ def ext_convert(self, value):
+ """Default converter for the ext:// protocol."""
+ return self.resolve(value)
+
+ def cfg_convert(self, value):
+ """Default converter for the cfg:// protocol."""
+ rest = value
+ m = self.WORD_PATTERN.match(rest)
+ if m is None:
+ raise ValueError("Unable to convert %r" % value)
+ else:
+ rest = rest[m.end():]
+ d = self.config[m.groups()[0]]
+ #print d, rest
+ while rest:
+ m = self.DOT_PATTERN.match(rest)
+ if m:
+ d = d[m.groups()[0]]
+ else:
+ m = self.INDEX_PATTERN.match(rest)
+ if m:
+ idx = m.groups()[0]
+ if not self.DIGIT_PATTERN.match(idx):
+ d = d[idx]
+ else:
+ try:
+ n = int(idx) # try as number first (most likely)
+ d = d[n]
+ except TypeError:
+ d = d[idx]
+ if m:
+ rest = rest[m.end():]
+ else:
+ raise ValueError('Unable to convert '
+ '%r at %r' % (value, rest))
+ #rest should be empty
+ return d
+
+ def convert(self, value):
+ """
+ Convert values to an appropriate type. dicts, lists and tuples are
+ replaced by their converting alternatives. Strings are checked to
+ see if they have a conversion format and are converted if they do.
+ """
+ if not isinstance(value, ConvertingDict) and isinstance(value, dict):
+ value = ConvertingDict(value)
+ value.configurator = self
+ elif not isinstance(value, ConvertingList) and isinstance(value, list):
+ value = ConvertingList(value)
+ value.configurator = self
+ elif not isinstance(value, ConvertingTuple) and\
+ isinstance(value, tuple):
+ value = ConvertingTuple(value)
+ value.configurator = self
+ elif isinstance(value, str): # str for py3k
+ m = self.CONVERT_PATTERN.match(value)
+ if m:
+ d = m.groupdict()
+ prefix = d['prefix']
+ converter = self.value_converters.get(prefix, None)
+ if converter:
+ suffix = d['suffix']
+ converter = getattr(self, converter)
+ value = converter(suffix)
+ return value
+
+ def configure_custom(self, config):
+ """Configure an object with a user-supplied factory."""
+ c = config.pop('()')
+ if not hasattr(c, '__call__'):
+ c = self.resolve(c)
+ props = config.pop('.', None)
+ # Check for valid identifiers
+ kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
+ result = c(**kwargs)
+ if props:
+ for name, value in props.items():
+ setattr(result, name, value)
+ return result
+
+ def as_tuple(self, value):
+ """Utility function which converts lists to tuples."""
+ if isinstance(value, list):
+ value = tuple(value)
+ return value
+
+class DictConfigurator(BaseConfigurator):
+ """
+ Configure logging using a dictionary-like object to describe the
+ configuration.
+ """
+
+ def configure(self):
+ """Do the configuration."""
+
+ config = self.config
+ if 'version' not in config:
+ raise ValueError("dictionary doesn't specify a version")
+ if config['version'] != 1:
+ raise ValueError("Unsupported version: %s" % config['version'])
+ incremental = config.pop('incremental', False)
+ EMPTY_DICT = {}
+ logging._acquireLock()
+ try:
+ if incremental:
+ handlers = config.get('handlers', EMPTY_DICT)
+ for name in handlers:
+ if name not in logging._handlers:
+ raise ValueError('No handler found with '
+ 'name %r' % name)
+ else:
+ try:
+ handler = logging._handlers[name]
+ handler_config = handlers[name]
+ level = handler_config.get('level', None)
+ if level:
+ handler.setLevel(logging._checkLevel(level))
+ except Exception as e:
+ raise ValueError('Unable to configure handler '
+ '%r: %s' % (name, e))
+ loggers = config.get('loggers', EMPTY_DICT)
+ for name in loggers:
+ try:
+ self.configure_logger(name, loggers[name], True)
+ except Exception as e:
+ raise ValueError('Unable to configure logger '
+ '%r: %s' % (name, e))
+ root = config.get('root', None)
+ if root:
+ try:
+ self.configure_root(root, True)
+ except Exception as e:
+ raise ValueError('Unable to configure root '
+ 'logger: %s' % e)
+ else:
+ disable_existing = config.pop('disable_existing_loggers', True)
+
+ logging._handlers.clear()
+ del logging._handlerList[:]
+
+ # Do formatters first - they don't refer to anything else
+ formatters = config.get('formatters', EMPTY_DICT)
+ for name in formatters:
+ try:
+ formatters[name] = self.configure_formatter(
+ formatters[name])
+ except Exception as e:
+ raise ValueError('Unable to configure '
+ 'formatter %r: %s' % (name, e))
+ # Next, do filters - they don't refer to anything else, either
+ filters = config.get('filters', EMPTY_DICT)
+ for name in filters:
+ try:
+ filters[name] = self.configure_filter(filters[name])
+ except Exception as e:
+ raise ValueError('Unable to configure '
+ 'filter %r: %s' % (name, e))
+
+ # Next, do handlers - they refer to formatters and filters
+ # As handlers can refer to other handlers, sort the keys
+ # to allow a deterministic order of configuration
+ handlers = config.get('handlers', EMPTY_DICT)
+ for name in sorted(handlers):
+ try:
+ handler = self.configure_handler(handlers[name])
+ handler.name = name
+ handlers[name] = handler
+ except Exception as e:
+ raise ValueError('Unable to configure handler '
+ '%r: %s' % (name, e))
+ # Next, do loggers - they refer to handlers and filters
+
+ #we don't want to lose the existing loggers,
+ #since other threads may have pointers to them.
+ #existing is set to contain all existing loggers,
+ #and as we go through the new configuration we
+ #remove any which are configured. At the end,
+ #what's left in existing is the set of loggers
+ #which were in the previous configuration but
+ #which are not in the new configuration.
+ root = logging.root
+ existing = list(root.manager.loggerDict.keys())
+ #The list needs to be sorted so that we can
+ #avoid disabling child loggers of explicitly
+ #named loggers. With a sorted list it is easier
+ #to find the child loggers.
+ existing.sort(key=_encoded)
+ #We'll keep the list of existing loggers
+ #which are children of named loggers here...
+ child_loggers = []
+ #now set up the new ones...
+ loggers = config.get('loggers', EMPTY_DICT)
+ for name in loggers:
+ if name in existing:
+ i = existing.index(name) + 1 # look after name
+ prefixed = name + "."
+ pflen = len(prefixed)
+ num_existing = len(existing)
+ while i < num_existing:
+ if existing[i][:pflen] == prefixed:
+ child_loggers.append(existing[i])
+ i += 1
+ existing.remove(name)
+ try:
+ self.configure_logger(name, loggers[name])
+ except Exception as e:
+ raise ValueError('Unable to configure logger '
+ '%r: %s' % (name, e))
+
+ #Disable any old loggers. There's no point deleting
+ #them as other threads may continue to hold references
+ #and by disabling them, you stop them doing any logging.
+ #However, don't disable children of named loggers, as that's
+ #probably not what was intended by the user.
+ #for log in existing:
+ # logger = root.manager.loggerDict[log]
+ # if log in child_loggers:
+ # logger.level = logging.NOTSET
+ # logger.handlers = []
+ # logger.propagate = True
+ # elif disable_existing:
+ # logger.disabled = True
+ _handle_existing_loggers(existing, child_loggers,
+ disable_existing)
+
+ # And finally, do the root logger
+ root = config.get('root', None)
+ if root:
+ try:
+ self.configure_root(root)
+ except Exception as e:
+ raise ValueError('Unable to configure root '
+ 'logger: %s' % e)
+ finally:
+ logging._releaseLock()
+
+ def configure_formatter(self, config):
+ """Configure a formatter from a dictionary."""
+ if '()' in config:
+ factory = config['()'] # for use in exception handler
+ try:
+ result = self.configure_custom(config)
+ except TypeError as te:
+ if "'format'" not in str(te):
+ raise
+ #Name of parameter changed from fmt to format.
+ #Retry with old name.
+ #This is so that code can be used with older Python versions
+ #(e.g. by Django)
+ config['fmt'] = config.pop('format')
+ config['()'] = factory
+ result = self.configure_custom(config)
+ else:
+ fmt = config.get('format', None)
+ dfmt = config.get('datefmt', None)
+ result = logging.Formatter(fmt, dfmt)
+ return result
+
+ def configure_filter(self, config):
+ """Configure a filter from a dictionary."""
+ if '()' in config:
+ result = self.configure_custom(config)
+ else:
+ name = config.get('name', '')
+ result = logging.Filter(name)
+ return result
+
+ def add_filters(self, filterer, filters):
+ """Add filters to a filterer from a list of names."""
+ for f in filters:
+ try:
+ filterer.addFilter(self.config['filters'][f])
+ except Exception as e:
+ raise ValueError('Unable to add filter %r: %s' % (f, e))
+
+ def configure_handler(self, config):
+ """Configure a handler from a dictionary."""
+ formatter = config.pop('formatter', None)
+ if formatter:
+ try:
+ formatter = self.config['formatters'][formatter]
+ except Exception as e:
+ raise ValueError('Unable to set formatter '
+ '%r: %s' % (formatter, e))
+ level = config.pop('level', None)
+ filters = config.pop('filters', None)
+ if '()' in config:
+ c = config.pop('()')
+ if not hasattr(c, '__call__') and hasattr(types, 'ClassType') and type(c) != types.ClassType:
+ c = self.resolve(c)
+ factory = c
+ else:
+ klass = self.resolve(config.pop('class'))
+ #Special case for handler which refers to another handler
+ if issubclass(klass, logging.handlers.MemoryHandler) and\
+ 'target' in config:
+ try:
+ config['target'] = self.config['handlers'][config['target']]
+ except Exception as e:
+ raise ValueError('Unable to set target handler '
+ '%r: %s' % (config['target'], e))
+ elif issubclass(klass, logging.handlers.SMTPHandler) and\
+ 'mailhost' in config:
+ config['mailhost'] = self.as_tuple(config['mailhost'])
+ elif issubclass(klass, logging.handlers.SysLogHandler) and\
+ 'address' in config:
+ config['address'] = self.as_tuple(config['address'])
+ factory = klass
+ kwargs = dict([(k, config[k]) for k in config if valid_ident(k)])
+ try:
+ result = factory(**kwargs)
+ except TypeError as te:
+ if "'stream'" not in str(te):
+ raise
+ #The argument name changed from strm to stream
+ #Retry with old name.
+ #This is so that code can be used with older Python versions
+ #(e.g. by Django)
+ kwargs['strm'] = kwargs.pop('stream')
+ result = factory(**kwargs)
+ if formatter:
+ result.setFormatter(formatter)
+ if level is not None:
+ result.setLevel(logging._checkLevel(level))
+ if filters:
+ self.add_filters(result, filters)
+ return result
+
+ def add_handlers(self, logger, handlers):
+ """Add handlers to a logger from a list of names."""
+ for h in handlers:
+ try:
+ logger.addHandler(self.config['handlers'][h])
+ except Exception as e:
+ raise ValueError('Unable to add handler %r: %s' % (h, e))
+
+ def common_logger_config(self, logger, config, incremental=False):
+ """
+ Perform configuration which is common to root and non-root loggers.
+ """
+ level = config.get('level', None)
+ if level is not None:
+ logger.setLevel(logging._checkLevel(level))
+ if not incremental:
+ #Remove any existing handlers
+ for h in logger.handlers[:]:
+ logger.removeHandler(h)
+ handlers = config.get('handlers', None)
+ if handlers:
+ self.add_handlers(logger, handlers)
+ filters = config.get('filters', None)
+ if filters:
+ self.add_filters(logger, filters)
+
+ def configure_logger(self, name, config, incremental=False):
+ """Configure a non-root logger from a dictionary."""
+ logger = logging.getLogger(name)
+ self.common_logger_config(logger, config, incremental)
+ propagate = config.get('propagate', None)
+ if propagate is not None:
+ logger.propagate = propagate
+
+ def configure_root(self, config, incremental=False):
+ """Configure a root logger from a dictionary."""
+ root = logging.getLogger()
+ self.common_logger_config(root, config, incremental)
+
+dictConfigClass = DictConfigurator
+
+def dictConfig(config):
+ """Configure logging using a dictionary."""
+ dictConfigClass(config).configure()
def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
@@ -301,24 +813,26 @@ def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
chunk = self.connection.recv(slen)
while len(chunk) < slen:
chunk = chunk + conn.recv(slen - len(chunk))
- #Apply new configuration. We'd like to be able to
- #create a StringIO and pass that in, but unfortunately
- #1.5.2 ConfigParser does not support reading file
- #objects, only actual files. So we create a temporary
- #file and remove it later.
- file = tempfile.mktemp(".ini")
- f = open(file, "w")
- f.write(chunk)
- f.close()
+ chunk = chunk.decode("utf-8")
try:
- fileConfig(file)
- except (KeyboardInterrupt, SystemExit):
- raise
+ import json
+ d =json.loads(chunk)
+ assert isinstance(d, dict)
+ dictConfig(d)
except:
- traceback.print_exc()
- os.remove(file)
+ #Apply new configuration.
+
+ file = io.StringIO(chunk)
+ try:
+ fileConfig(file)
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ except:
+ traceback.print_exc()
+ if self.server.ready:
+ self.server.ready.set()
except socket.error as e:
- if not isinstancetype(e.args, tuple):
+ if not isinstance(e.args, tuple):
raise
else:
errcode = e.args[0]
@@ -333,12 +847,13 @@ def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
allow_reuse_address = 1
def __init__(self, host='localhost', port=DEFAULT_LOGGING_CONFIG_PORT,
- handler=None):
+ handler=None, ready=None):
ThreadingTCPServer.__init__(self, (host, port), handler)
logging._acquireLock()
self.abort = 0
logging._releaseLock()
self.timeout = 1
+ self.ready = ready
def serve_until_stopped(self):
import select
@@ -354,17 +869,28 @@ def listen(port=DEFAULT_LOGGING_CONFIG_PORT):
logging._releaseLock()
self.socket.close()
- def serve(rcvr, hdlr, port):
- server = rcvr(port=port, handler=hdlr)
- global _listener
- logging._acquireLock()
- _listener = server
- logging._releaseLock()
- server.serve_until_stopped()
+ class Server(threading.Thread):
+
+ def __init__(self, rcvr, hdlr, port):
+ super(Server, self).__init__()
+ self.rcvr = rcvr
+ self.hdlr = hdlr
+ self.port = port
+ self.ready = threading.Event()
+
+ def run(self):
+ server = self.rcvr(port=self.port, handler=self.hdlr,
+ ready=self.ready)
+ if self.port == 0:
+ self.port = server.server_address[1]
+ self.ready.set()
+ global _listener
+ logging._acquireLock()
+ _listener = server
+ logging._releaseLock()
+ server.serve_until_stopped()
- return threading.Thread(target=serve,
- args=(ConfigSocketReceiver,
- ConfigStreamHandler, port))
+ return Server(ConfigSocketReceiver, ConfigStreamHandler, port)
def stopListening():
"""
diff --git a/Lib/logging/handlers.py b/Lib/logging/handlers.py
index e9dac43..96384bd 100644
--- a/Lib/logging/handlers.py
+++ b/Lib/logging/handlers.py
@@ -1,4 +1,4 @@
-# Copyright 2001-2007 by Vinay Sajip. All Rights Reserved.
+# Copyright 2001-2010 by Vinay Sajip. All Rights Reserved.
#
# Permission to use, copy, modify, and distribute this software and its
# documentation for any purpose and without fee is hereby granted,
@@ -19,7 +19,7 @@ Additional handlers for the logging package for Python. The core package is
based on PEP 282 and comments thereto in comp.lang.python, and influenced by
Apache's log4j system.
-Copyright (C) 2001-2009 Vinay Sajip. All Rights Reserved.
+Copyright (C) 2001-2010 Vinay Sajip. All Rights Reserved.
To use, simply 'import logging.handlers' and log away!
"""
@@ -41,6 +41,7 @@ DEFAULT_UDP_LOGGING_PORT = 9021
DEFAULT_HTTP_LOGGING_PORT = 9022
DEFAULT_SOAP_LOGGING_PORT = 9023
SYSLOG_UDP_PORT = 514
+SYSLOG_TCP_PORT = 514
_MIDNIGHT = 24 * 60 * 60 # number of seconds in a day
@@ -102,8 +103,13 @@ class RotatingFileHandler(BaseRotatingHandler):
If maxBytes is zero, rollover never occurs.
"""
+ # If rotation/rollover is wanted, it doesn't make sense to use another
+ # mode. If for example 'w' were specified, then if there were multiple
+ # runs of the calling application, the logs from previous runs would be
+ # lost if the 'w' is respected, because the log file would be truncated
+ # on each run.
if maxBytes > 0:
- mode = 'a' # doesn't make sense otherwise!
+ mode = 'a'
BaseRotatingHandler.__init__(self, filename, mode, encoding, delay)
self.maxBytes = maxBytes
self.backupCount = backupCount
@@ -120,7 +126,6 @@ class RotatingFileHandler(BaseRotatingHandler):
sfn = "%s.%d" % (self.baseFilename, i)
dfn = "%s.%d" % (self.baseFilename, i + 1)
if os.path.exists(sfn):
- #print "%s -> %s" % (sfn, dfn)
if os.path.exists(dfn):
os.remove(dfn)
os.rename(sfn, dfn)
@@ -128,7 +133,6 @@ class RotatingFileHandler(BaseRotatingHandler):
if os.path.exists(dfn):
os.remove(dfn)
os.rename(self.baseFilename, dfn)
- #print "%s -> %s" % (self.baseFilename, dfn)
self.mode = 'w'
self.stream = self._open()
@@ -156,7 +160,7 @@ class TimedRotatingFileHandler(BaseRotatingHandler):
If backupCount is > 0, when rollover is done, no more than backupCount
files are kept - the oldest ones are deleted.
"""
- def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=0, utc=False):
+ def __init__(self, filename, when='h', interval=1, backupCount=0, encoding=None, delay=False, utc=False):
BaseRotatingHandler.__init__(self, filename, 'a', encoding, delay)
self.when = when.upper()
self.backupCount = backupCount
@@ -278,7 +282,6 @@ class TimedRotatingFileHandler(BaseRotatingHandler):
t = int(time.time())
if t >= self.rolloverAt:
return 1
- #print "No need to rollover: %d, %d" % (t, self.rolloverAt)
return 0
def getFilesToDelete(self):
@@ -326,14 +329,8 @@ class TimedRotatingFileHandler(BaseRotatingHandler):
os.remove(dfn)
os.rename(self.baseFilename, dfn)
if self.backupCount > 0:
- # find the oldest log file and delete it
- #s = glob.glob(self.baseFilename + ".20*")
- #if len(s) > self.backupCount:
- # s.sort()
- # os.remove(s[0])
for s in self.getFilesToDelete():
os.remove(s)
- #print "%s -> %s" % (self.baseFilename, dfn)
self.mode = 'w'
self.stream = self._open()
currentTime = int(time.time())
@@ -634,7 +631,8 @@ class SysLogHandler(logging.Handler):
LOG_NEWS = 7 # network news subsystem
LOG_UUCP = 8 # UUCP subsystem
LOG_CRON = 9 # clock daemon
- LOG_AUTHPRIV = 10 # security/authorization messages (private)
+ LOG_AUTHPRIV = 10 # security/authorization messages (private)
+ LOG_FTP = 11 # FTP daemon
# other codes through 15 reserved for system use
LOG_LOCAL0 = 16 # reserved for local use
@@ -666,6 +664,7 @@ class SysLogHandler(logging.Handler):
"authpriv": LOG_AUTHPRIV,
"cron": LOG_CRON,
"daemon": LOG_DAEMON,
+ "ftp": LOG_FTP,
"kern": LOG_KERN,
"lpr": LOG_LPR,
"mail": LOG_MAIL,
@@ -696,7 +695,8 @@ class SysLogHandler(logging.Handler):
"CRITICAL" : "critical"
}
- def __init__(self, address=('localhost', SYSLOG_UDP_PORT), facility=LOG_USER):
+ def __init__(self, address=('localhost', SYSLOG_UDP_PORT),
+ facility=LOG_USER, socktype=socket.SOCK_DGRAM):
"""
Initialize a handler.
@@ -708,13 +708,16 @@ class SysLogHandler(logging.Handler):
self.address = address
self.facility = facility
+ self.socktype = socktype
+
if isinstance(address, str):
self.unixsocket = 1
self._connect_unixsocket(address)
else:
self.unixsocket = 0
- self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
-
+ self.socket = socket.socket(socket.AF_INET, socktype)
+ if socktype == socket.SOCK_STREAM:
+ self.socket.connect(address)
self.formatter = None
def _connect_unixsocket(self, address):
@@ -727,12 +730,6 @@ class SysLogHandler(logging.Handler):
self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.socket.connect(address)
- # curious: when talking to the unix-domain '/dev/log' socket, a
- # zero-terminator seems to be required. this string is placed
- # into a class variable so that it can be overridden if
- # necessary.
- log_format_string = '<%d>%s\000'
-
def encodePriority(self, facility, priority):
"""
Encode the facility and priority. You can pass in strings or
@@ -771,18 +768,19 @@ class SysLogHandler(logging.Handler):
The record is formatted, and then sent to the syslog server. If
exception information is present, it is NOT sent to the server.
"""
- msg = self.format(record)
+ msg = self.format(record) + '\000'
"""
We need to convert record level to lowercase, maybe this will
change in the future.
"""
- msg = self.log_format_string % (
- self.encodePriority(self.facility,
- self.mapPriority(record.levelname)),
- msg)
+ prio = '<%d>' % self.encodePriority(self.facility,
+ self.mapPriority(record.levelname))
+ prio = prio.encode('utf-8')
+ # Message is a string. Convert to bytes as required by RFC 5424
msg = msg.encode('utf-8')
if codecs:
msg = codecs.BOM_UTF8 + msg
+ msg = prio + msg
try:
if self.unixsocket:
try:
@@ -790,8 +788,10 @@ class SysLogHandler(logging.Handler):
except socket.error:
self._connect_unixsocket(self.address)
self.socket.send(msg)
- else:
+ elif self.socktype == socket.SOCK_DGRAM:
self.socket.sendto(msg, self.address)
+ else:
+ self.socket.sendall(msg)
except (KeyboardInterrupt, SystemExit):
raise
except:
@@ -801,7 +801,8 @@ class SMTPHandler(logging.Handler):
"""
A handler class which sends an SMTP email for each logging event.
"""
- def __init__(self, mailhost, fromaddr, toaddrs, subject, credentials=None):
+ def __init__(self, mailhost, fromaddr, toaddrs, subject,
+ credentials=None, secure=None):
"""
Initialize the handler.
@@ -809,7 +810,12 @@ class SMTPHandler(logging.Handler):
line of the email. To specify a non-standard SMTP port, use the
(host, port) tuple format for the mailhost argument. To specify
authentication credentials, supply a (username, password) tuple
- for the credentials argument.
+ for the credentials argument. To specify the use of a secure
+ protocol (TLS), pass in a tuple for the secure argument. This will
+ only be used when authentication credentials are supplied. The tuple
+ will be either an empty tuple, or a single-value tuple with the name
+ of a keyfile, or a 2-value tuple with the names of the keyfile and
+ certificate file. (This tuple is passed to the `starttls` method).
"""
logging.Handler.__init__(self)
if isinstance(mailhost, tuple):
@@ -825,6 +831,7 @@ class SMTPHandler(logging.Handler):
toaddrs = [toaddrs]
self.toaddrs = toaddrs
self.subject = subject
+ self.secure = secure
def getSubject(self, record):
"""
@@ -835,24 +842,6 @@ class SMTPHandler(logging.Handler):
"""
return self.subject
- weekdayname = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
-
- monthname = [None,
- 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
- 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
-
- def date_time(self):
- """
- Return the current date and time formatted for a MIME header.
- Needed for Python 1.5.2 (no email package available)
- """
- year, month, day, hh, mm, ss, wd, y, z = time.gmtime(time.time())
- s = "%s, %02d %3s %4d %02d:%02d:%02d GMT" % (
- self.weekdayname[wd],
- day, self.monthname[month], year,
- hh, mm, ss)
- return s
-
def emit(self, record):
"""
Emit a record.
@@ -861,10 +850,7 @@ class SMTPHandler(logging.Handler):
"""
try:
import smtplib
- try:
- from email.utils import formatdate
- except ImportError:
- formatdate = self.date_time
+ from email.utils import formatdate
port = self.mailport
if not port:
port = smtplib.SMTP_PORT
@@ -876,6 +862,10 @@ class SMTPHandler(logging.Handler):
self.getSubject(record),
formatdate(), msg)
if self.username:
+ if self.secure is not None:
+ smtp.ehlo()
+ smtp.starttls(*self.secure)
+ smtp.ehlo()
smtp.login(self.username, self.password)
smtp.sendmail(self.fromaddr, self.toaddrs, msg)
smtp.quit()
@@ -989,7 +979,7 @@ class HTTPHandler(logging.Handler):
A class which sends records to a Web server, using either GET or
POST semantics.
"""
- def __init__(self, host, url, method="GET"):
+ def __init__(self, host, url, method="GET", secure=False, credentials=None):
"""
Initialize the instance with the host, the request URL, and the method
("GET" or "POST")
@@ -1001,12 +991,14 @@ class HTTPHandler(logging.Handler):
self.host = host
self.url = url
self.method = method
+ self.secure = secure
+ self.credentials = credentials
def mapLogRecord(self, record):
"""
Default implementation of mapping the log record into a dict
that is sent as the CGI data. Overwrite in your class.
- Contributed by Franz Glasner.
+ Contributed by Franz Glasner.
"""
return record.__dict__
@@ -1019,7 +1011,10 @@ class HTTPHandler(logging.Handler):
try:
import http.client, urllib.parse
host = self.host
- h = http.client.HTTP(host)
+ if self.secure:
+ h = http.client.HTTPSConnection(host)
+ else:
+ h = http.client.HTTPConnection(host)
url = self.url
data = urllib.parse.urlencode(self.mapLogRecord(record))
if self.method == "GET":
@@ -1039,8 +1034,13 @@ class HTTPHandler(logging.Handler):
h.putheader("Content-type",
"application/x-www-form-urlencoded")
h.putheader("Content-length", str(len(data)))
+ if self.credentials:
+ import base64
+ s = ('u%s:%s' % self.credentials).encode('utf-8')
+ s = 'Basic ' + base64.b64encode(s).strip()
+ h.putheader('Authorization', s)
h.endheaders(data if self.method == "POST" else None)
- h.getreply() #can't do anything with the result
+ h.getresponse() #can't do anything with the result
except (KeyboardInterrupt, SystemExit):
raise
except:
@@ -1133,6 +1133,8 @@ class MemoryHandler(BufferingHandler):
For a MemoryHandler, flushing means just sending the buffered
records to the target, if there is one. Override if you want
different behaviour.
+
+ The record buffer is also cleared by this operation.
"""
if self.target:
for record in self.buffer:
@@ -1146,3 +1148,174 @@ class MemoryHandler(BufferingHandler):
self.flush()
self.target = None
BufferingHandler.close(self)
+
+
+class QueueHandler(logging.Handler):
+ """
+ This handler sends events to a queue. Typically, it would be used together
+ with a multiprocessing Queue to centralise logging to file in one process
+ (in a multi-process application), so as to avoid file write contention
+ between processes.
+
+ This code is new in Python 3.2, but this class can be copy pasted into
+ user code for use with earlier Python versions.
+ """
+
+ def __init__(self, queue):
+ """
+ Initialise an instance, using the passed queue.
+ """
+ logging.Handler.__init__(self)
+ self.queue = queue
+
+ def enqueue(self, record):
+ """
+ Enqueue a record.
+
+ The base implementation uses put_nowait. You may want to override
+ this method if you want to use blocking, timeouts or custom queue
+ implementations.
+ """
+ self.queue.put_nowait(record)
+
+ def prepare(self, record):
+ """
+ Prepares a record for queuing. The object returned by this method is
+ enqueued.
+
+ The base implementation formats the record to merge the message
+ and arguments, and removes unpickleable items from the record
+ in-place.
+
+ You might want to override this method if you want to convert
+ the record to a dict or JSON string, or send a modified copy
+ of the record while leaving the original intact.
+ """
+ # The format operation gets traceback text into record.exc_text
+ # (if there's exception data), and also puts the message into
+ # record.message. We can then use this to replace the original
+ # msg + args, as these might be unpickleable. We also zap the
+ # exc_info attribute, as it's no longer needed and, if not None,
+ # will typically not be pickleable.
+ self.format(record)
+ record.msg = record.message
+ record.args = None
+ record.exc_info = None
+ return record
+
+ def emit(self, record):
+ """
+ Emit a record.
+
+ Writes the LogRecord to the queue, preparing it for pickling first.
+ """
+ try:
+ self.enqueue(self.prepare(record))
+ except (KeyboardInterrupt, SystemExit):
+ raise
+ except:
+ self.handleError(record)
+
+class QueueListener(object):
+ """
+ This class implements an internal threaded listener which watches for
+ LogRecords being added to a queue, removes them and passes them to a
+ list of handlers for processing.
+ """
+ _sentinel = None
+
+ def __init__(self, queue, *handlers):
+ """
+ Initialise an instance with the specified queue and
+ handlers.
+ """
+ self.queue = queue
+ self.handlers = handlers
+ self._stop = threading.Event()
+ self._thread = None
+
+ def dequeue(self, block):
+ """
+ Dequeue a record and return it, optionally blocking.
+
+ The base implementation uses get. You may want to override this method
+ if you want to use timeouts or work with custom queue implementations.
+ """
+ return self.queue.get(block)
+
+ def start(self):
+ """
+ Start the listener.
+
+ This starts up a background thread to monitor the queue for
+ LogRecords to process.
+ """
+ self._thread = t = threading.Thread(target=self._monitor)
+ t.setDaemon(True)
+ t.start()
+
+ def prepare(self , record):
+ """
+ Prepare a record for handling.
+
+ This method just returns the passed-in record. You may want to
+ override this method if you need to do any custom marshalling or
+ manipulation of the record before passing it to the handlers.
+ """
+ return record
+
+ def handle(self, record):
+ """
+ Handle a record.
+
+ This just loops through the handlers offering them the record
+ to handle.
+ """
+ record = self.prepare(record)
+ for handler in self.handlers:
+ handler.handle(record)
+
+ def _monitor(self):
+ """
+ Monitor the queue for records, and ask the handler
+ to deal with them.
+
+ This method runs on a separate, internal thread.
+ The thread will terminate if it sees a sentinel object in the queue.
+ """
+ q = self.queue
+ has_task_done = hasattr(q, 'task_done')
+ while not self._stop.isSet():
+ try:
+ record = self.dequeue(True)
+ if record is self._sentinel:
+ break
+ self.handle(record)
+ if has_task_done:
+ q.task_done()
+ except queue.Empty:
+ pass
+ # There might still be records in the queue.
+ while True:
+ try:
+ record = self.dequeue(False)
+ if record is self._sentinel:
+ break
+ self.handle(record)
+ if has_task_done:
+ q.task_done()
+ except queue.Empty:
+ break
+
+ def stop(self):
+ """
+ Stop the listener.
+
+ This asks the thread to terminate, and then waits for it to do so.
+ Note that if you don't call this before your application exits, there
+ may be some records still left on the queue, which won't be processed.
+ """
+ self._stop.set()
+ self.queue.put_nowait(self._sentinel)
+ self._thread.join()
+ self._thread = None
diff --git a/Lib/macpath.py b/Lib/macpath.py
index 3b3e4ff..1615d91 100644
--- a/Lib/macpath.py
+++ b/Lib/macpath.py