From 982d0e3756e61bc0fc6a957564d1dc0e95fac416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Lozier?= Date: Thu, 11 Jun 2026 09:16:08 -0400 Subject: [PATCH 1/5] CPython 3.5.10 --- lib/__future__.py | 6 + lib/_collections_abc.py | 209 +- lib/_compat_pickle.py | 7 + lib/_compression.py | 152 + lib/_dummy_thread.py | 8 + lib/_osx_support.py | 16 +- lib/_pydecimal.py | 6399 ++++++++++++++++ lib/_pyio.py | 513 +- lib/_strptime.py | 13 +- lib/abc.py | 2 +- lib/aifc.py | 37 +- lib/antigravity.py | 2 +- lib/argparse.py | 47 +- lib/ast.py | 3 +- lib/asynchat.py | 3 + lib/asyncio/base_events.py | 392 +- lib/asyncio/base_subprocess.py | 13 +- lib/asyncio/compat.py | 1 + lib/asyncio/coroutines.py | 50 +- lib/asyncio/events.py | 82 +- lib/asyncio/futures.py | 49 +- lib/asyncio/locks.py | 37 +- lib/asyncio/proactor_events.py | 24 +- lib/asyncio/queues.py | 5 +- lib/asyncio/selector_events.py | 280 +- lib/asyncio/sslproto.py | 40 +- lib/asyncio/streams.py | 129 +- lib/asyncio/subprocess.py | 19 +- lib/asyncio/tasks.py | 132 +- lib/asyncio/test_utils.py | 57 +- lib/asyncio/transports.py | 8 + lib/asyncio/unix_events.py | 165 +- lib/asyncio/windows_events.py | 6 +- lib/asyncore.py | 39 +- lib/base64.py | 141 +- lib/binhex.py | 28 +- lib/bz2.py | 237 +- lib/calendar.py | 21 +- lib/cgi.py | 8 +- lib/cgitb.py | 5 +- lib/code.py | 38 +- lib/codecs.py | 18 +- lib/collections/__init__.py | 111 +- lib/compileall.py | 135 +- lib/concurrent/futures/_base.py | 21 +- lib/concurrent/futures/process.py | 82 +- lib/concurrent/futures/thread.py | 12 +- lib/configparser.py | 184 +- lib/contextlib.py | 63 +- lib/copy.py | 13 +- lib/csv.py | 14 +- lib/ctypes/__init__.py | 24 +- lib/ctypes/_endian.py | 2 + lib/ctypes/macholib/README.ctypes | 2 +- lib/ctypes/macholib/dyld.py | 5 +- lib/ctypes/test/test_arrays.py | 12 +- lib/ctypes/test/test_as_parameter.py | 2 +- lib/ctypes/test/test_byteswap.py | 20 + lib/ctypes/test/test_callbacks.py | 11 + lib/ctypes/test/test_find.py | 28 +- lib/ctypes/test/test_frombuffer.py | 10 +- lib/ctypes/test/test_loading.py | 6 +- lib/ctypes/test/test_numbers.py | 2 +- lib/ctypes/test/test_pointers.py | 14 +- lib/ctypes/test/test_prototypes.py | 5 +- lib/ctypes/test/test_slicing.py | 4 +- lib/ctypes/test/test_structures.py | 36 +- lib/ctypes/test/test_values.py | 30 +- lib/ctypes/util.py | 140 +- lib/curses/ascii.py | 22 +- lib/curses/textpad.py | 31 +- lib/datetime.py | 363 +- lib/dbm/__init__.py | 6 +- lib/dbm/dumb.py | 35 +- lib/decimal.py | 6418 +---------------- lib/difflib.py | 122 +- lib/dis.py | 70 +- lib/distutils/_msvccompiler.py | 533 ++ lib/distutils/archive_util.py | 21 +- lib/distutils/ccompiler.py | 6 +- lib/distutils/command/bdist.py | 3 +- lib/distutils/command/bdist_dumb.py | 3 +- lib/distutils/command/bdist_wininst.py | 36 +- lib/distutils/command/build.py | 9 + lib/distutils/command/build_ext.py | 110 +- lib/distutils/command/build_py.py | 4 +- lib/distutils/command/install.py | 2 +- lib/distutils/command/install_lib.py | 16 +- lib/distutils/command/register.py | 6 +- lib/distutils/command/upload.py | 56 +- lib/distutils/command/wininst-14.0-amd64.exe | Bin 0 -> 587776 bytes lib/distutils/command/wininst-14.0.exe | Bin 0 -> 458240 bytes lib/distutils/config.py | 4 +- lib/distutils/core.py | 2 - lib/distutils/dist.py | 69 +- lib/distutils/extension.py | 8 + lib/distutils/filelist.py | 48 +- lib/distutils/msvc9compiler.py | 5 +- lib/distutils/msvccompiler.py | 3 + lib/distutils/spawn.py | 3 - lib/distutils/sysconfig.py | 34 +- lib/distutils/tests/test_archive_util.py | 154 +- lib/distutils/tests/test_bdist.py | 2 +- lib/distutils/tests/test_bdist_rpm.py | 8 +- lib/distutils/tests/test_build_ext.py | 74 +- lib/distutils/tests/test_build_py.py | 4 +- lib/distutils/tests/test_check.py | 16 +- lib/distutils/tests/test_config.py | 28 +- lib/distutils/tests/test_filelist.py | 43 +- lib/distutils/tests/test_install.py | 2 - lib/distutils/tests/test_install_lib.py | 13 +- lib/distutils/tests/test_msvc9compiler.py | 2 +- lib/distutils/tests/test_msvccompiler.py | 108 + lib/distutils/tests/test_register.py | 18 +- lib/distutils/tests/test_sdist.py | 4 +- lib/distutils/tests/test_unixccompiler.py | 2 +- lib/distutils/tests/test_upload.py | 40 +- lib/distutils/unixccompiler.py | 24 + lib/distutils/util.py | 9 +- lib/distutils/version.py | 6 - lib/doctest.py | 26 +- lib/email/__init__.py | 2 - lib/email/_header_value_parser.py | 32 +- lib/email/_parseaddr.py | 11 +- lib/email/_policybase.py | 16 +- lib/email/architecture.rst | 2 +- lib/email/base64mime.py | 2 +- lib/email/charset.py | 3 - lib/email/contentmanager.py | 9 +- lib/email/feedparser.py | 58 +- lib/email/generator.py | 39 +- lib/email/header.py | 3 - lib/email/headerregistry.py | 15 +- lib/email/message.py | 34 +- lib/email/mime/text.py | 3 +- lib/email/parser.py | 4 +- lib/email/policy.py | 24 +- lib/email/quoprimime.py | 2 +- lib/email/utils.py | 2 +- lib/encodings/aliases.py | 5 + lib/encodings/cp65001.py | 9 +- lib/encodings/hp_roman8.py | 2 + lib/encodings/koi8_t.py | 308 + lib/encodings/kz1048.py | 307 + lib/encodings/utf_16.py | 2 +- lib/encodings/utf_32.py | 2 +- lib/encodings/uu_codec.py | 4 + lib/enum.py | 34 +- lib/fileinput.py | 177 +- lib/formatter.py | 4 +- lib/fractions.py | 73 +- lib/ftplib.py | 117 +- lib/functools.py | 302 +- lib/genericpath.py | 13 + lib/getpass.py | 3 +- lib/gettext.py | 55 +- lib/glob.py | 63 +- lib/gzip.py | 506 +- lib/heapq.py | 351 +- lib/html/entities.py | 3 + lib/html/parser.py | 116 +- lib/http/__init__.py | 135 +- lib/http/client.py | 454 +- lib/http/cookiejar.py | 32 +- lib/http/cookies.py | 255 +- lib/http/server.py | 185 +- lib/idlelib/AutoComplete.py | 4 +- lib/idlelib/Bindings.py | 2 + lib/idlelib/CREDITS.txt | 2 +- lib/idlelib/CallTipWindow.py | 2 +- lib/idlelib/ChangeLog | 12 +- lib/idlelib/CodeContext.py | 8 +- lib/idlelib/ColorDelegator.py | 25 + lib/idlelib/Debugger.py | 2 +- lib/idlelib/Delegator.py | 12 +- lib/idlelib/EditorWindow.py | 25 +- lib/idlelib/HISTORY.txt | 8 +- lib/idlelib/IOBinding.py | 99 +- lib/idlelib/MultiCall.py | 2 +- lib/idlelib/NEWS.txt | 819 +-- lib/idlelib/NEWS2x.txt | 660 ++ lib/idlelib/ParenMatch.py | 2 +- lib/idlelib/Percolator.py | 49 +- lib/idlelib/PyShell.py | 15 +- lib/idlelib/README.txt | 13 +- lib/idlelib/ReplaceDialog.py | 38 +- lib/idlelib/SearchDialog.py | 28 +- lib/idlelib/UndoDelegator.py | 25 +- lib/idlelib/WidgetRedirector.py | 10 +- lib/idlelib/__init__.py | 1 + lib/idlelib/aboutDialog.py | 11 +- lib/idlelib/configDialog.py | 21 +- lib/idlelib/configHandler.py | 33 +- lib/idlelib/configHelpSourceEdit.py | 48 +- lib/idlelib/help.html | 234 +- lib/idlelib/help.py | 30 +- lib/idlelib/idle.py | 14 +- lib/idlelib/idle_test/README.txt | 138 +- lib/idlelib/idle_test/__init__.py | 6 + lib/idlelib/idle_test/htest.py | 3 + lib/idlelib/idle_test/mock_tk.py | 5 + lib/idlelib/idle_test/test_autocomplete.py | 5 +- lib/idlelib/idle_test/test_autoexpand.py | 2 +- lib/idlelib/idle_test/test_config_help.py | 106 + lib/idlelib/idle_test/test_configdialog.py | 23 +- lib/idlelib/idle_test/test_delegator.py | 23 +- lib/idlelib/idle_test/test_editmenu.py | 72 + lib/idlelib/idle_test/test_formatparagraph.py | 5 +- lib/idlelib/idle_test/test_help_about.py | 52 + lib/idlelib/idle_test/test_hyperparser.py | 1 + lib/idlelib/idle_test/test_idlehistory.py | 1 + lib/idlelib/idle_test/test_parenmatch.py | 13 +- lib/idlelib/idle_test/test_percolator.py | 118 + lib/idlelib/idle_test/test_replacedialog.py | 293 + lib/idlelib/idle_test/test_searchdialog.py | 80 + lib/idlelib/idle_test/test_searchengine.py | 2 +- lib/idlelib/idle_test/test_textview.py | 15 +- lib/idlelib/idle_test/test_undodelegator.py | 135 + lib/idlelib/idle_test/test_warning.py | 9 - lib/idlelib/idle_test/test_widgetredir.py | 31 +- lib/idlelib/rpc.py | 2 +- lib/idlelib/run.py | 6 + lib/idlelib/textView.py | 4 + lib/imaplib.py | 96 +- lib/imghdr.py | 12 + lib/imp.py | 85 +- lib/importlib/__init__.py | 31 +- lib/importlib/_bootstrap.py | 1736 +---- lib/importlib/_bootstrap_external.py | 1461 ++++ lib/importlib/abc.py | 28 +- lib/importlib/machinery.py | 18 +- lib/importlib/util.py | 106 +- lib/inspect.py | 789 +- lib/ipaddress.py | 599 +- lib/json/__init__.py | 15 +- lib/json/decoder.py | 86 +- lib/json/encoder.py | 29 +- lib/json/tool.py | 39 +- lib/lib2to3/Grammar.txt | 31 +- lib/lib2to3/btm_utils.py | 2 +- lib/lib2to3/fixer_base.py | 3 +- lib/lib2to3/fixes/fix_apply.py | 11 + lib/lib2to3/fixes/fix_callable.py | 37 - lib/lib2to3/fixes/fix_intern.py | 10 + lib/lib2to3/fixes/fix_metaclass.py | 2 +- lib/lib2to3/fixes/fix_reload.py | 10 + lib/lib2to3/patcomp.py | 2 +- lib/lib2to3/pgen2/driver.py | 15 +- lib/lib2to3/pgen2/grammar.py | 28 +- lib/lib2to3/pgen2/pgen.py | 8 +- lib/lib2to3/pgen2/token.py | 6 +- lib/lib2to3/pgen2/tokenize.py | 80 +- lib/lib2to3/refactor.py | 2 +- lib/lib2to3/tests/pytree_idempotency.py | 4 +- lib/lib2to3/tests/support.py | 6 +- lib/lib2to3/tests/test_fixers.py | 96 +- lib/lib2to3/tests/test_parser.py | 161 +- lib/lib2to3/tests/test_refactor.py | 5 +- lib/linecache.py | 89 +- lib/locale.py | 36 +- lib/logging/__init__.py | 41 +- lib/logging/config.py | 10 +- lib/logging/handlers.py | 71 +- lib/lzma.py | 226 +- lib/macpath.py | 34 +- lib/mailbox.py | 16 +- lib/mailcap.py | 28 +- lib/mimetypes.py | 2 + lib/modulefinder.py | 46 +- lib/msilib/__init__.py | 13 +- lib/msilib/schema.py | 2 +- lib/multiprocessing/connection.py | 35 +- lib/multiprocessing/context.py | 2 +- lib/multiprocessing/dummy/__init__.py | 2 +- lib/multiprocessing/dummy/connection.py | 5 +- lib/multiprocessing/forkserver.py | 48 +- lib/multiprocessing/heap.py | 20 +- lib/multiprocessing/managers.py | 39 +- lib/multiprocessing/pool.py | 113 +- lib/multiprocessing/popen_fork.py | 3 - lib/multiprocessing/process.py | 10 +- lib/multiprocessing/queues.py | 59 +- lib/multiprocessing/sharedctypes.py | 26 +- lib/multiprocessing/spawn.py | 4 +- lib/multiprocessing/synchronize.py | 32 +- lib/multiprocessing/util.py | 73 +- lib/nntplib.py | 8 +- lib/ntpath.py | 325 +- lib/opcode.py | 21 +- lib/operator.py | 69 +- lib/optparse.py | 4 +- lib/os.py | 134 +- lib/pathlib.py | 142 +- lib/pdb.py | 16 +- lib/pickle.py | 47 +- lib/pickletools.py | 10 +- lib/pkgutil.py | 35 +- lib/platform.py | 180 +- lib/plistlib.py | 9 +- lib/poplib.py | 16 +- lib/posixpath.py | 76 +- lib/pprint.py | 459 +- lib/pstats.py | 18 +- lib/py_compile.py | 19 +- lib/pyclbr.py | 8 +- lib/pydoc.py | 62 +- lib/pydoc_data/topics.py | 2679 +++---- lib/queue.py | 5 +- lib/random.py | 19 +- lib/re.py | 48 +- lib/reprlib.py | 13 +- lib/rlcompleter.py | 10 +- lib/runpy.py | 81 +- lib/sched.py | 6 +- lib/selectors.py | 90 +- lib/shlex.py | 5 +- lib/shutil.py | 189 +- lib/signal.py | 79 + lib/site.py | 29 +- lib/smtpd.py | 287 +- lib/smtplib.py | 241 +- lib/sndhdr.py | 7 +- lib/socket.py | 212 +- lib/socketserver.py | 111 +- lib/sqlite3/test/dbapi.py | 306 +- lib/sqlite3/test/factory.py | 38 +- lib/sqlite3/test/hooks.py | 58 +- lib/sqlite3/test/regression.py | 101 +- lib/sqlite3/test/transactions.py | 33 +- lib/sqlite3/test/types.py | 65 +- lib/sqlite3/test/userfunctions.py | 93 +- lib/sre_compile.py | 184 +- lib/sre_constants.py | 270 +- lib/sre_parse.py | 630 +- lib/ssl.py | 345 +- lib/stat.py | 23 + lib/statistics.py | 1 - lib/string.py | 13 +- lib/subprocess.py | 699 +- lib/symbol.py | 155 +- lib/symtable.py | 9 +- lib/sysconfig.py | 26 +- lib/tabnanny.py | 2 +- lib/tarfile.py | 163 +- lib/telnetlib.py | 9 +- lib/tempfile.py | 169 +- lib/test/185test.db | Bin 16384 -> 0 bytes lib/test/_test_multiprocessing.py | 306 +- lib/test/allsans.pem | 81 + lib/test/badsyntax_async1.py | 2 + lib/test/badsyntax_async2.py | 2 + lib/test/badsyntax_async3.py | 2 + lib/test/badsyntax_async4.py | 2 + lib/test/badsyntax_async5.py | 2 + lib/test/badsyntax_async6.py | 2 + lib/test/badsyntax_async7.py | 2 + lib/test/badsyntax_async8.py | 2 + lib/test/bisect.py | 167 + lib/test/buffer_tests.py | 218 - lib/test/bytecode_helper.py | 4 +- lib/test/capath/efa5f9c3.0 | 34 + lib/test/cfgparser.2 | 4 +- lib/test/check_soundcard.vbs | 13 - lib/test/datetimetester.py | 132 +- lib/test/dh1024.pem | 7 - lib/test/eintrdata/eintr_tester.py | 490 ++ lib/test/exception_hierarchy.txt | 2 + lib/test/ffdh3072.pem | 41 + lib/test/fork_wait.py | 7 +- lib/test/imghdrdata/python.exr | Bin 0 -> 2635 bytes lib/test/imghdrdata/python.webp | Bin 0 -> 432 bytes lib/test/imp_dummy.py | 3 + lib/test/inspect_fodder.py | 22 +- lib/test/inspect_fodder2.py | 28 + lib/test/keycert.passwd.pem | 98 +- lib/test/keycert.pem | 89 +- lib/test/keycert2.pem | 89 +- lib/test/keycert3.pem | 211 +- lib/test/keycert4.pem | 211 +- lib/test/list_tests.py | 23 +- lib/test/lock_tests.py | 36 +- lib/test/mailcap.txt | 2 +- lib/test/make_ssl_certs.py | 144 +- lib/test/mock_socket.py | 15 +- lib/test/mod_generics_cache.py | 14 + lib/test/mp_preload.py | 18 + lib/test/pickletester.py | 155 +- lib/test/pycacert.pem | 139 +- lib/test/pycakey.pem | 64 +- lib/test/pystone.py | 10 +- lib/test/re_tests.py | 6 +- lib/test/recursion.tar | Bin 0 -> 516 bytes lib/test/regrtest.py | 611 +- lib/test/revocation.crl | 19 +- lib/test/selfsigned_pythontestdotnet.pem | 46 +- lib/test/seq_tests.py | 5 + lib/test/sndhdrdata/README | 7 - lib/test/ssl_cert.pem | 37 +- lib/test/ssl_key.passwd.pem | 60 +- lib/test/ssl_key.pem | 52 +- lib/test/ssl_servers.py | 10 +- lib/test/ssltests.py | 27 +- lib/test/string_tests.py | 160 +- lib/test/support/__init__.py | 262 +- lib/test/{ => support}/script_helper.py | 71 +- lib/test/test___all__.py | 11 - lib/test/test___future__.py | 6 +- lib/test/test__opcode.py | 7 +- lib/test/test__osx_support.py | 6 +- lib/test/test_aifc.py | 18 +- lib/test/test_argparse.py | 167 +- lib/test/test_array.py | 95 +- lib/test/test_asdl_parser.py | 122 + lib/test/test_ast.py | 94 +- lib/test/test_asynchat.py | 13 +- lib/test/test_asyncio/test_base_events.py | 294 +- lib/test/test_asyncio/test_events.py | 372 +- lib/test/test_asyncio/test_futures.py | 124 +- lib/test/test_asyncio/test_locks.py | 55 +- lib/test/test_asyncio/test_pep492.py | 232 + lib/test/test_asyncio/test_proactor_events.py | 3 + lib/test/test_asyncio/test_queues.py | 1 + lib/test/test_asyncio/test_selector_events.py | 168 +- lib/test/test_asyncio/test_sslproto.py | 57 +- lib/test/test_asyncio/test_streams.py | 3 +- lib/test/test_asyncio/test_subprocess.py | 55 +- lib/test/test_asyncio/test_tasks.py | 309 +- lib/test/test_asyncio/test_unix_events.py | 128 +- lib/test/test_asyncio/test_windows_events.py | 1 + lib/test/test_asyncore.py | 30 +- lib/test/test_atexit.py | 17 +- lib/test/test_augassign.py | 21 +- lib/test/test_base64.py | 57 +- lib/test/test_bigmem.py | 2 +- lib/test/test_binascii.py | 124 +- lib/test/test_binop.py | 8 +- lib/test/test_bool.py | 4 + lib/test/test_buffer.py | 107 +- lib/test/test_builtin.py | 204 +- lib/test/test_bytes.py | 327 +- lib/test/test_bz2.py | 198 +- lib/test/test_calendar.py | 26 +- lib/test/test_call.py | 7 +- lib/test/test_capi.py | 176 +- lib/test/test_cgi.py | 18 +- lib/test/test_cgitb.py | 9 +- lib/test/test_charmapcodec.py | 5 +- lib/test/test_class.py | 47 +- lib/test/test_cmath.py | 47 +- lib/test/test_cmd_line.py | 13 +- lib/test/test_cmd_line_script.py | 234 +- lib/test/test_code.py | 54 +- lib/test/test_code_module.py | 36 +- lib/test/test_codeccallbacks.py | 122 +- lib/test/test_codecencodings_cn.py | 15 +- lib/test/test_codecencodings_hk.py | 5 +- lib/test/test_codecencodings_iso2022.py | 5 +- lib/test/test_codecencodings_jp.py | 5 +- lib/test/test_codecencodings_kr.py | 5 +- lib/test/test_codecencodings_tw.py | 5 +- lib/test/test_codecs.py | 122 +- lib/test/test_codeop.py | 8 +- lib/test/test_collections.py | 354 +- lib/test/test_compare.py | 6 +- lib/test/test_compile.py | 115 +- lib/test/test_compileall.py | 122 +- lib/test/test_complex.py | 31 + lib/test/test_concurrent_futures.py | 63 +- lib/test/test_configparser.py | 301 +- lib/test/test_contains.py | 6 +- lib/test/test_contextlib.py | 151 +- lib/test/test_copy.py | 150 +- lib/test/test_copyreg.py | 7 +- lib/test/test_coroutines.py | 1806 +++++ lib/test/test_cprofile.py | 10 +- lib/test/test_crashers.py | 7 +- lib/test/test_crypt.py | 2 +- lib/test/test_csv.py | 46 +- lib/test/test_curses.py | 272 +- lib/test/test_datetime.py | 10 +- lib/test/test_dbm.py | 17 +- lib/test/test_dbm_dumb.py | 24 + lib/test/test_dbm_ndbm.py | 14 + lib/test/test_decimal.py | 191 +- lib/test/test_decorators.py | 9 +- lib/test/test_defaultdict.py | 13 +- lib/test/test_deque.py | 256 +- lib/test/test_descr.py | 254 +- lib/test/test_dict.py | 35 +- lib/test/test_dictviews.py | 34 +- lib/test/test_difflib.py | 182 +- lib/test/test_difflib_expect.html | 2 +- lib/test/test_dis.py | 276 +- lib/test/test_doctest.py | 100 +- lib/test/test_docxmlrpc.py | 61 +- lib/test/test_dummy_thread.py | 172 +- lib/test/test_dummy_threading.py | 6 +- lib/test/test_dynamic.py | 8 +- lib/test/test_dynamicclassattribute.py | 6 +- lib/test/test_eintr.py | 22 + lib/test/test_email/__init__.py | 12 +- .../test_email/test__header_value_parser.py | 31 +- lib/test/test_email/test_asian_codecs.py | 6 +- lib/test/test_email/test_contentmanager.py | 2 +- lib/test/test_email/test_email.py | 75 +- lib/test/test_email/test_generator.py | 51 + lib/test/test_email/test_headerregistry.py | 19 + lib/test/test_email/test_inversion.py | 23 +- lib/test/test_email/test_message.py | 20 +- lib/test/test_email/test_parser.py | 41 + lib/test/test_email/test_policy.py | 8 +- lib/test/test_email/torture_test.py | 15 +- lib/test/test_ensurepip.py | 2 +- lib/test/test_enum.py | 172 +- lib/test/test_enumerate.py | 13 +- lib/test/test_eof.py | 5 +- lib/test/test_epoll.py | 7 +- lib/test/test_errno.py | 7 +- lib/test/test_exception_variations.py | 6 +- lib/test/test_exceptions.py | 120 +- lib/test/test_extcall.py | 105 +- lib/test/test_faulthandler.py | 206 +- lib/test/test_file_eintr.py | 46 +- lib/test/test_fileinput.py | 81 +- lib/test/test_fileio.py | 203 +- lib/test/test_finalization.py | 5 +- lib/test/test_float.py | 67 +- lib/test/test_flufl.py | 7 +- lib/test/test_fnmatch.py | 69 +- lib/test/test_fork1.py | 13 +- lib/test/test_format.py | 436 +- lib/test/test_fractions.py | 56 +- lib/test/test_frame.py | 5 +- lib/test/test_ftplib.py | 29 +- lib/test/test_funcattrs.py | 10 +- lib/test/test_functools.py | 540 +- lib/test/test_future.py | 63 +- lib/test/test_gc.py | 28 +- lib/test/test_gdb.py | 48 +- lib/test/test_generators.py | 158 +- lib/test/test_genericpath.py | 49 +- lib/test/test_getargs2.py | 405 +- lib/test/test_getopt.py | 7 +- lib/test/test_gettext.py | 202 +- lib/test/test_glob.py | 137 +- lib/test/test_grammar.py | 115 +- lib/test/test_grp.py | 7 +- lib/test/test_gzip.py | 40 +- lib/test/test_hash.py | 2 +- lib/test/test_hashlib.py | 5 +- lib/test/test_heapq.py | 25 +- lib/test/test_hmac.py | 11 +- lib/test/test_html.py | 1 - lib/test/test_htmlparser.py | 76 +- lib/test/test_http_cookiejar.py | 43 +- lib/test/test_http_cookies.py | 219 +- lib/test/test_httplib.py | 727 +- lib/test/test_httpservers.py | 270 +- lib/test/test_idle.py | 21 +- lib/test/test_imaplib.py | 551 +- lib/test/test_imghdr.py | 4 +- lib/test/test_imp.py | 143 +- .../__init__.py} | 144 +- lib/test/test_import/__main__.py | 3 + .../data/circular_imports/basic.py | 2 + .../data/circular_imports/basic2.py | 1 + .../data/circular_imports/indirect.py | 1 + .../data/circular_imports/rebinding.py | 3 + .../data/circular_imports/rebinding2.py | 3 + .../data/circular_imports/subpackage.py | 2 + .../circular_imports/subpkg/subpackage2.py | 2 + .../data/circular_imports/subpkg/util.py | 2 + .../test_import/data/circular_imports/util.py | 2 + .../test_import/data/package2/submodule1.py | 3 + .../test_import/data/package2/submodule2.py | 0 .../test_importlib/builtin/test_finder.py | 33 +- .../test_importlib/builtin/test_loader.py | 38 +- lib/test/test_importlib/builtin/util.py | 7 - .../extension/test_case_sensitivity.py | 30 +- .../test_importlib/extension/test_finder.py | 15 +- .../test_importlib/extension/test_loader.py | 227 +- .../extension/test_path_hook.py | 13 +- lib/test/test_importlib/extension/util.py | 19 - lib/test/test_importlib/frozen/test_finder.py | 12 +- lib/test/test_importlib/frozen/test_loader.py | 17 +- .../test_importlib/import_/test___loader__.py | 15 +- .../import_/test___package__.py | 21 +- lib/test/test_importlib/import_/test_api.py | 17 +- .../test_importlib/import_/test_caching.py | 9 +- .../test_importlib/import_/test_fromlist.py | 13 +- .../test_importlib/import_/test_meta_path.py | 22 +- .../test_importlib/import_/test_packages.py | 7 +- lib/test/test_importlib/import_/test_path.py | 91 +- .../import_/test_relative_imports.py | 17 +- lib/test/test_importlib/import_/util.py | 20 - .../source/test_case_sensitivity.py | 29 +- .../test_importlib/source/test_file_loader.py | 215 +- lib/test/test_importlib/source/test_finder.py | 28 +- .../test_importlib/source/test_path_hook.py | 8 +- .../source/test_source_encoding.py | 35 +- lib/test/test_importlib/source/util.py | 96 - lib/test/test_importlib/test_abc.py | 292 +- lib/test/test_importlib/test_api.py | 99 +- lib/test/test_importlib/test_lazy.py | 143 + lib/test/test_importlib/test_locks.py | 178 +- lib/test/test_importlib/test_spec.py | 256 +- lib/test/test_importlib/test_util.py | 384 +- lib/test/test_importlib/test_windows.py | 100 +- lib/test/test_importlib/util.py | 197 +- lib/test/test_inspect.py | 565 +- lib/test/test_int.py | 5 +- lib/test/test_int_literal.py | 6 +- lib/test/test_io.py | 421 +- lib/test/test_ioctl.py | 2 +- lib/test/test_ipaddress.py | 293 +- lib/test/test_isinstance.py | 21 +- lib/test/test_iter.py | 51 + lib/test/test_itertools.py | 88 +- lib/test/test_json/__init__.py | 4 + lib/test/test_json/test_decode.py | 10 +- .../test_json/test_encode_basestring_ascii.py | 3 - lib/test/test_json/test_fail.py | 59 +- lib/test/test_json/test_recursion.py | 12 +- lib/test/test_json/test_scanstring.py | 2 +- lib/test/test_json/test_speedups.py | 26 + lib/test/test_json/test_tool.py | 43 +- lib/test/test_keywordonlyarg.py | 6 +- lib/test/test_kqueue.py | 8 +- lib/test/test_linecache.py | 43 + lib/test/test_list.py | 108 +- lib/test/test_locale.py | 59 + lib/test/test_logging.py | 336 +- lib/test/test_long.py | 26 +- lib/test/test_longexp.py | 8 +- lib/test/test_lzma.py | 163 +- lib/test/test_macpath.py | 2 + lib/test/test_mailcap.py | 75 +- lib/test/test_marshal.py | 2 +- lib/test/test_math.py | 195 +- lib/test/test_memoryio.py | 96 +- lib/test/test_memoryview.py | 48 +- lib/test/test_mimetypes.py | 8 +- lib/test/test_minidom.py | 22 +- lib/test/test_mmap.py | 25 +- lib/test/test_module.py | 36 +- lib/test/test_modulefinder.py | 13 + lib/test/test_msilib.py | 7 +- lib/test/test_multibytecodec.py | 4 +- .../test_multiprocessing_main_handling.py | 31 +- lib/test/test_nis.py | 5 +- lib/test/test_nntplib.py | 97 +- lib/test/test_normalization.py | 23 +- lib/test/test_ntpath.py | 69 + lib/test/test_numeric_tower.py | 6 +- lib/test/test_opcodes.py | 6 +- lib/test/test_openpty.py | 6 +- lib/test/test_operator.py | 125 + lib/test/test_ordered_dict.py | 466 +- lib/test/test_os.py | 786 +- lib/test/test_osx_env.py | 2 +- lib/test/test_parser.py | 142 +- lib/test/test_pathlib.py | 280 +- lib/test/test_pdb.py | 25 +- lib/test/test_peepholer.py | 18 +- lib/test/test_pep247.py | 8 +- lib/test/test_pep292.py | 254 - lib/test/test_pep3120.py | 8 +- lib/test/test_pep3131.py | 8 +- lib/test/test_pep3151.py | 14 +- lib/test/test_pep380.py | 8 +- lib/test/test_pep479.py | 34 + lib/test/test_pickle.py | 40 +- lib/test/test_pkg.py | 6 +- lib/test/test_pkgimport.py | 8 +- lib/test/test_pkgutil.py | 87 +- lib/test/test_platform.py | 83 +- lib/test/test_plistlib.py | 22 +- lib/test/test_poll.py | 3 +- lib/test/test_popen.py | 5 +- lib/test/test_poplib.py | 33 +- lib/test/test_posix.py | 42 +- lib/test/test_posixpath.py | 103 +- lib/test/test_pow.py | 8 +- lib/test/test_pprint.py | 456 +- lib/test/test_property.py | 28 +- lib/test/test_pstats.py | 9 +- lib/test/test_pty.py | 34 +- lib/test/test_pulldom.py | 8 +- lib/test/test_pwd.py | 5 +- lib/test/test_py_compile.py | 5 + lib/test/test_pyclbr.py | 9 +- lib/test/test_pydoc.py | 78 +- lib/test/test_pyexpat.py | 24 +- lib/test/test_queue.py | 7 +- lib/test/test_quopri.py | 7 +- lib/test/test_raise.py | 3 - lib/test/test_random.py | 25 +- lib/test/test_range.py | 51 +- lib/test/test_re.py | 580 +- lib/test/test_readline.py | 250 +- lib/test/test_regrtest.py | 682 +- lib/test/test_reprlib.py | 60 +- lib/test/test_resource.py | 14 + lib/test/test_richcmp.py | 29 +- lib/test/test_rlcompleter.py | 3 +- lib/test/test_runpy.py | 81 +- lib/test/test_sax.py | 64 +- lib/test/test_scope.py | 7 +- lib/test/test_script_helper.py | 28 +- lib/test/test_select.py | 5 +- lib/test/test_selectors.py | 64 +- lib/test/test_set.py | 35 +- lib/test/test_shelve.py | 4 + lib/test/test_shlex.py | 18 +- lib/test/test_shutil.py | 151 +- lib/test/test_signal.py | 351 +- lib/test/test_site.py | 44 +- lib/test/test_slice.py | 21 +- lib/test/test_smtpd.py | 492 +- lib/test/test_smtplib.py | 452 +- lib/test/test_smtpnet.py | 5 +- lib/test/test_sndhdr.py | 14 + lib/test/test_socket.py | 502 +- lib/test/test_socketserver.py | 94 +- lib/test/test_sort.py | 21 +- lib/test/test_source_encoding.py | 83 +- lib/test/test_ssl.py | 733 +- lib/test/test_startfile.py | 7 +- lib/test/test_stat.py | 29 + lib/test/test_string.py | 279 +- lib/test/test_stringprep.py | 6 +- lib/test/test_strlit.py | 6 +- lib/test/test_strptime.py | 45 +- lib/test/test_strtod.py | 5 +- lib/test/test_struct.py | 9 +- lib/test/test_structmembers.py | 5 +- lib/test/test_structseq.py | 8 +- lib/test/test_subprocess.py | 316 +- lib/test/test_sundry.py | 2 - lib/test/test_super.py | 12 +- lib/test/test_support.py | 100 +- lib/test/test_symtable.py | 20 +- lib/test/test_syntax.py | 41 +- lib/test/test_sys.py | 231 +- lib/test/test_sys_setprofile.py | 14 +- lib/test/test_sys_settrace.py | 9 + lib/test/test_sysconfig.py | 26 +- lib/test/test_syslog.py | 5 +- lib/test/test_tarfile.py | 346 +- lib/test/test_tcl.py | 28 +- lib/test/test_telnetlib.py | 22 +- lib/test/test_tempfile.py | 192 +- lib/test/test_textwrap.py | 47 + lib/test/test_thread.py | 10 +- lib/test/test_threaded_import.py | 10 +- lib/test/test_threading.py | 66 +- lib/test/test_threading_local.py | 2 +- lib/test/test_time.py | 447 +- lib/test/test_timeit.py | 52 +- lib/test/test_timeout.py | 8 +- lib/test/test_tix.py | 32 + lib/test/test_tk.py | 3 - lib/test/test_tokenize.py | 290 +- lib/test/test_tools/test_fixcid.py | 92 + lib/test/test_tools/test_gprof2html.py | 2 +- lib/test/test_tools/test_i18n.py | 72 + lib/test/test_tools/test_md5sum.py | 2 +- lib/test/test_tools/test_pindent.py | 2 +- lib/test/test_tools/test_reindent.py | 9 +- lib/test/test_tools/test_unparse.py | 5 + lib/test/test_trace.py | 18 +- lib/test/test_traceback.py | 473 +- lib/test/test_tracemalloc.py | 21 +- lib/test/test_ttk_guionly.py | 4 +- lib/test/test_ttk_textonly.py | 3 - lib/test/test_tuple.py | 19 +- lib/test/test_turtle.py | 436 ++ lib/test/test_typechecks.py | 5 - lib/test/test_types.py | 327 +- lib/test/test_typing.py | 2500 +++++++ lib/test/test_ucn.py | 5 +- lib/test/test_unary.py | 7 +- lib/test/test_unicode.py | 391 +- lib/test/test_unicodedata.py | 31 +- lib/test/test_unpack.py | 2 +- lib/test/test_unpack_ex.py | 200 +- lib/test/test_urllib.py | 210 +- lib/test/test_urllib2.py | 400 +- lib/test/test_urllib2_localnet.py | 77 +- lib/test/test_urllib2net.py | 20 +- lib/test/test_urllibnet.py | 70 +- lib/test/test_urlparse.py | 165 +- lib/test/test_userdict.py | 8 +- lib/test/test_userlist.py | 6 +- lib/test/test_uu.py | 9 + lib/test/test_uuid.py | 48 +- lib/test/test_venv.py | 49 +- lib/test/test_wait3.py | 10 +- lib/test/test_wait4.py | 13 +- .../__init__.py} | 187 +- lib/test/test_warnings/__main__.py | 3 + lib/test/test_warnings/data/import_warning.py | 3 + .../data/stacklevel.py} | 0 lib/test/test_weakref.py | 81 +- lib/test/test_weakset.py | 6 +- lib/test/test_webbrowser.py | 25 + lib/test/test_winreg.py | 2 +- lib/test/test_winsound.py | 269 +- lib/test/test_with.py | 18 +- lib/test/test_wsgiref.py | 137 +- lib/test/test_xdrlib.py | 7 +- lib/test/test_xml_etree.py | 223 +- lib/test/test_xml_etree_c.py | 12 +- lib/test/test_xmlrpc.py | 245 +- lib/test/test_zipapp.py | 349 + lib/test/test_zipfile.py | 350 +- lib/test/test_zipfile64.py | 10 +- lib/test/test_zipimport.py | 284 +- lib/test/test_zipimport_support.py | 19 +- lib/test/test_zlib.py | 199 +- lib/test/tf_inherit_check.py | 32 +- ...-latin1-coding-cookie-and-utf8-bom-sig.txt | 2 +- lib/test/wrongcert.pem | 32 + lib/textwrap.py | 40 +- lib/threading.py | 25 +- lib/timeit.py | 113 +- lib/tkinter/__init__.py | 304 +- lib/tkinter/_fix.py | 78 - lib/tkinter/dnd.py | 2 +- lib/tkinter/font.py | 8 +- lib/tkinter/simpledialog.py | 4 +- lib/tkinter/test/runtktests.py | 2 +- .../test_tkinter/test_geometry_managers.py | 6 + lib/tkinter/test/test_tkinter/test_misc.py | 5 + .../test/test_tkinter/test_variables.py | 60 +- lib/tkinter/test/test_tkinter/test_widgets.py | 27 +- lib/tkinter/test/test_ttk/test_extensions.py | 21 +- lib/tkinter/test/test_ttk/test_functions.py | 2 +- lib/tkinter/test/test_ttk/test_widgets.py | 319 +- lib/tkinter/test/widget_tests.py | 27 + lib/tkinter/tix.py | 38 +- lib/tkinter/ttk.py | 10 +- lib/token.py | 25 +- lib/tokenize.py | 88 +- lib/trace.py | 66 +- lib/traceback.py | 535 +- lib/tracemalloc.py | 2 +- lib/turtle.py | 20 +- lib/turtledemo/__main__.py | 5 +- lib/turtledemo/sorting_animate.py | 204 + lib/types.py | 102 +- lib/typing.py | 2419 +++++++ lib/unittest/__init__.py | 13 +- lib/unittest/case.py | 148 +- lib/unittest/loader.py | 266 +- lib/unittest/main.py | 26 +- lib/unittest/mock.py | 101 +- lib/unittest/result.py | 9 +- lib/unittest/runner.py | 10 +- lib/unittest/suite.py | 2 +- lib/unittest/test/support.py | 4 - lib/unittest/test/test_assertions.py | 15 +- lib/unittest/test/test_break.py | 3 + lib/unittest/test/test_case.py | 204 +- lib/unittest/test/test_discovery.py | 366 +- lib/unittest/test/test_loader.py | 433 +- lib/unittest/test/test_program.py | 26 +- lib/unittest/test/test_result.py | 53 +- lib/unittest/test/test_runner.py | 19 +- lib/unittest/test/test_setups.py | 7 +- lib/unittest/test/testmock/testcallable.py | 2 +- lib/unittest/test/testmock/testhelpers.py | 80 +- .../test/testmock/testmagicmethods.py | 11 + lib/unittest/test/testmock/testmock.py | 85 + lib/unittest/test/testmock/testpatch.py | 50 +- lib/unittest/util.py | 2 +- lib/urllib/error.py | 11 +- lib/urllib/parse.py | 278 +- lib/urllib/request.py | 243 +- lib/urllib/robotparser.py | 4 +- lib/uu.py | 7 + lib/uuid.py | 135 +- lib/venv/__init__.py | 34 +- lib/venv/scripts/{posix => common}/activate | 2 +- lib/venv/scripts/nt/Activate.ps1 | 16 +- lib/venv/scripts/posix/activate.csh | 2 +- lib/venv/scripts/posix/activate.fish | 41 +- lib/warnings.py | 73 +- lib/weakref.py | 53 +- lib/webbrowser.py | 29 +- lib/wsgiref/handlers.py | 14 +- lib/wsgiref/headers.py | 8 +- lib/wsgiref/simple_server.py | 19 +- lib/xml/dom/expatbuilder.py | 2 +- lib/xml/dom/minidom.py | 8 +- lib/xml/dom/xmlbuilder.py | 26 +- lib/xml/etree/ElementPath.py | 20 +- lib/xml/etree/ElementTree.py | 31 +- lib/xml/sax/__init__.py | 8 +- lib/xml/sax/expatreader.py | 28 +- lib/xml/sax/saxutils.py | 7 +- lib/xml/sax/xmlreader.py | 4 +- lib/xmlrpc/client.py | 73 +- lib/xmlrpc/server.py | 52 +- lib/zipapp.py | 201 + lib/zipfile.py | 658 +- 906 files changed, 68039 insertions(+), 27563 deletions(-) create mode 100644 lib/_compression.py create mode 100644 lib/_pydecimal.py create mode 100644 lib/distutils/_msvccompiler.py create mode 100644 lib/distutils/command/wininst-14.0-amd64.exe create mode 100644 lib/distutils/command/wininst-14.0.exe create mode 100644 lib/distutils/tests/test_msvccompiler.py create mode 100644 lib/encodings/koi8_t.py create mode 100644 lib/encodings/kz1048.py create mode 100644 lib/idlelib/NEWS2x.txt create mode 100644 lib/idlelib/idle_test/test_config_help.py create mode 100644 lib/idlelib/idle_test/test_editmenu.py create mode 100644 lib/idlelib/idle_test/test_help_about.py create mode 100644 lib/idlelib/idle_test/test_percolator.py create mode 100644 lib/idlelib/idle_test/test_replacedialog.py create mode 100644 lib/idlelib/idle_test/test_searchdialog.py create mode 100644 lib/idlelib/idle_test/test_undodelegator.py create mode 100644 lib/importlib/_bootstrap_external.py delete mode 100644 lib/lib2to3/fixes/fix_callable.py create mode 100644 lib/signal.py delete mode 100644 lib/test/185test.db create mode 100644 lib/test/allsans.pem create mode 100644 lib/test/badsyntax_async1.py create mode 100644 lib/test/badsyntax_async2.py create mode 100644 lib/test/badsyntax_async3.py create mode 100644 lib/test/badsyntax_async4.py create mode 100644 lib/test/badsyntax_async5.py create mode 100644 lib/test/badsyntax_async6.py create mode 100644 lib/test/badsyntax_async7.py create mode 100644 lib/test/badsyntax_async8.py create mode 100644 lib/test/bisect.py delete mode 100644 lib/test/buffer_tests.py create mode 100644 lib/test/capath/efa5f9c3.0 delete mode 100644 lib/test/check_soundcard.vbs delete mode 100644 lib/test/dh1024.pem create mode 100644 lib/test/eintrdata/eintr_tester.py create mode 100644 lib/test/ffdh3072.pem create mode 100644 lib/test/imghdrdata/python.exr create mode 100644 lib/test/imghdrdata/python.webp create mode 100644 lib/test/imp_dummy.py create mode 100644 lib/test/mod_generics_cache.py create mode 100644 lib/test/mp_preload.py create mode 100644 lib/test/recursion.tar rename lib/test/{ => support}/script_helper.py (79%) create mode 100644 lib/test/test_asdl_parser.py create mode 100644 lib/test/test_asyncio/test_pep492.py create mode 100644 lib/test/test_coroutines.py create mode 100644 lib/test/test_eintr.py rename lib/test/{test_import.py => test_import/__init__.py} (88%) create mode 100644 lib/test/test_import/__main__.py create mode 100644 lib/test/test_import/data/circular_imports/basic.py create mode 100644 lib/test/test_import/data/circular_imports/basic2.py create mode 100644 lib/test/test_import/data/circular_imports/indirect.py create mode 100644 lib/test/test_import/data/circular_imports/rebinding.py create mode 100644 lib/test/test_import/data/circular_imports/rebinding2.py create mode 100644 lib/test/test_import/data/circular_imports/subpackage.py create mode 100644 lib/test/test_import/data/circular_imports/subpkg/subpackage2.py create mode 100644 lib/test/test_import/data/circular_imports/subpkg/util.py create mode 100644 lib/test/test_import/data/circular_imports/util.py create mode 100644 lib/test/test_import/data/package2/submodule1.py create mode 100644 lib/test/test_import/data/package2/submodule2.py delete mode 100644 lib/test/test_importlib/builtin/util.py delete mode 100644 lib/test/test_importlib/extension/util.py delete mode 100644 lib/test/test_importlib/import_/util.py delete mode 100644 lib/test/test_importlib/source/util.py create mode 100644 lib/test/test_importlib/test_lazy.py delete mode 100644 lib/test/test_pep292.py create mode 100644 lib/test/test_pep479.py create mode 100644 lib/test/test_tix.py create mode 100644 lib/test/test_tools/test_fixcid.py create mode 100644 lib/test/test_tools/test_i18n.py create mode 100644 lib/test/test_turtle.py create mode 100644 lib/test/test_typing.py rename lib/test/{test_warnings.py => test_warnings/__init__.py} (83%) create mode 100644 lib/test/test_warnings/__main__.py create mode 100644 lib/test/test_warnings/data/import_warning.py rename lib/test/{warning_tests.py => test_warnings/data/stacklevel.py} (100%) create mode 100644 lib/test/test_zipapp.py create mode 100644 lib/test/wrongcert.pem delete mode 100644 lib/tkinter/_fix.py create mode 100644 lib/turtledemo/sorting_animate.py create mode 100644 lib/typing.py rename lib/venv/scripts/{posix => common}/activate (98%) create mode 100644 lib/zipapp.py diff --git a/lib/__future__.py b/lib/__future__.py index 3b2d5ecb..63b2be35 100644 --- a/lib/__future__.py +++ b/lib/__future__.py @@ -56,6 +56,7 @@ "print_function", "unicode_literals", "barry_as_FLUFL", + "generator_stop", ] __all__ = ["all_feature_names"] + all_feature_names @@ -72,6 +73,7 @@ CO_FUTURE_PRINT_FUNCTION = 0x10000 # print function CO_FUTURE_UNICODE_LITERALS = 0x20000 # unicode string literals CO_FUTURE_BARRY_AS_BDFL = 0x40000 +CO_FUTURE_GENERATOR_STOP = 0x80000 # StopIteration becomes RuntimeError in generators class _Feature: def __init__(self, optionalRelease, mandatoryRelease, compiler_flag): @@ -132,3 +134,7 @@ def __repr__(self): barry_as_FLUFL = _Feature((3, 1, 0, "alpha", 2), (3, 9, 0, "alpha", 0), CO_FUTURE_BARRY_AS_BDFL) + +generator_stop = _Feature((3, 5, 0, "beta", 1), + (3, 7, 0, "alpha", 0), + CO_FUTURE_GENERATOR_STOP) diff --git a/lib/_collections_abc.py b/lib/_collections_abc.py index 33b59aba..8bebd69b 100644 --- a/lib/_collections_abc.py +++ b/lib/_collections_abc.py @@ -9,7 +9,8 @@ from abc import ABCMeta, abstractmethod import sys -__all__ = ["Hashable", "Iterable", "Iterator", +__all__ = ["Awaitable", "Coroutine", "AsyncIterable", "AsyncIterator", + "Hashable", "Iterable", "Iterator", "Generator", "Sized", "Container", "Callable", "Set", "MutableSet", "Mapping", "MutableMapping", @@ -28,8 +29,8 @@ # so that they will pass tests like: # it = iter(somebytearray) # assert isinstance(it, Iterable) -# Note: in other implementations, these types many not be distinct -# and they make have their own implementation specific types that +# Note: in other implementations, these types might not be distinct +# and they may have their own implementation specific types that # are not included on this list. bytes_iterator = type(iter(b'')) bytearray_iterator = type(iter(bytearray())) @@ -40,6 +41,7 @@ list_iterator = type(iter([])) list_reverseiterator = type(iter(reversed([]))) range_iterator = type(iter(range(0))) +longrange_iterator = type(iter(range(1 << 1000))) set_iterator = type(iter(set())) str_iterator = type(iter("")) tuple_iterator = type(iter(())) @@ -50,6 +52,13 @@ dict_items = type({}.items()) ## misc ## mappingproxy = type(type.__dict__) +generator = type((lambda: (yield))()) +## coroutine ## +async def _coro(): pass +_coro = _coro() +coroutine = type(_coro) +_coro.close() # Prevent ResourceWarning +del _coro ### ONE-TRICK PONIES ### @@ -73,6 +82,113 @@ def __subclasshook__(cls, C): return NotImplemented +class Awaitable(metaclass=ABCMeta): + + __slots__ = () + + @abstractmethod + def __await__(self): + yield + + @classmethod + def __subclasshook__(cls, C): + if cls is Awaitable: + for B in C.__mro__: + if "__await__" in B.__dict__: + if B.__dict__["__await__"]: + return True + break + return NotImplemented + + +class Coroutine(Awaitable): + + __slots__ = () + + @abstractmethod + def send(self, value): + """Send a value into the coroutine. + Return next yielded value or raise StopIteration. + """ + raise StopIteration + + @abstractmethod + def throw(self, typ, val=None, tb=None): + """Raise an exception in the coroutine. + Return next yielded value or raise StopIteration. + """ + if val is None: + if tb is None: + raise typ + val = typ() + if tb is not None: + val = val.with_traceback(tb) + raise val + + def close(self): + """Raise GeneratorExit inside coroutine. + """ + try: + self.throw(GeneratorExit) + except (GeneratorExit, StopIteration): + pass + else: + raise RuntimeError("coroutine ignored GeneratorExit") + + @classmethod + def __subclasshook__(cls, C): + if cls is Coroutine: + mro = C.__mro__ + for method in ('__await__', 'send', 'throw', 'close'): + for base in mro: + if method in base.__dict__: + break + else: + return NotImplemented + return True + return NotImplemented + + +Coroutine.register(coroutine) + + +class AsyncIterable(metaclass=ABCMeta): + + __slots__ = () + + @abstractmethod + def __aiter__(self): + return AsyncIterator() + + @classmethod + def __subclasshook__(cls, C): + if cls is AsyncIterable: + if any("__aiter__" in B.__dict__ for B in C.__mro__): + return True + return NotImplemented + + +class AsyncIterator(AsyncIterable): + + __slots__ = () + + @abstractmethod + async def __anext__(self): + """Return the next item or raise StopAsyncIteration when exhausted.""" + raise StopAsyncIteration + + def __aiter__(self): + return self + + @classmethod + def __subclasshook__(cls, C): + if cls is AsyncIterator: + if (any("__anext__" in B.__dict__ for B in C.__mro__) and + any("__aiter__" in B.__dict__ for B in C.__mro__)): + return True + return NotImplemented + + class Iterable(metaclass=ABCMeta): __slots__ = () @@ -119,11 +235,70 @@ def __subclasshook__(cls, C): Iterator.register(list_iterator) Iterator.register(list_reverseiterator) Iterator.register(range_iterator) +Iterator.register(longrange_iterator) Iterator.register(set_iterator) Iterator.register(str_iterator) Iterator.register(tuple_iterator) Iterator.register(zip_iterator) + +class Generator(Iterator): + + __slots__ = () + + def __next__(self): + """Return the next item from the generator. + When exhausted, raise StopIteration. + """ + return self.send(None) + + @abstractmethod + def send(self, value): + """Send a value into the generator. + Return next yielded value or raise StopIteration. + """ + raise StopIteration + + @abstractmethod + def throw(self, typ, val=None, tb=None): + """Raise an exception in the generator. + Return next yielded value or raise StopIteration. + """ + if val is None: + if tb is None: + raise typ + val = typ() + if tb is not None: + val = val.with_traceback(tb) + raise val + + def close(self): + """Raise GeneratorExit inside generator. + """ + try: + self.throw(GeneratorExit) + except (GeneratorExit, StopIteration): + pass + else: + raise RuntimeError("generator ignored GeneratorExit") + + @classmethod + def __subclasshook__(cls, C): + if cls is Generator: + mro = C.__mro__ + for method in ('__iter__', '__next__', 'send', 'throw', 'close'): + for base in mro: + if method in base.__dict__: + break + else: + return NotImplemented + return True + return NotImplemented + + +Generator.register(generator) + + class Sized(metaclass=ABCMeta): __slots__ = () @@ -453,6 +628,8 @@ def __eq__(self, other): class MappingView(Sized): + __slots__ = '_mapping', + def __init__(self, mapping): self._mapping = mapping @@ -465,6 +642,8 @@ def __repr__(self): class KeysView(MappingView, Set): + __slots__ = () + @classmethod def _from_iterable(self, it): return set(it) @@ -480,6 +659,8 @@ def __iter__(self): class ItemsView(MappingView, Set): + __slots__ = () + @classmethod def _from_iterable(self, it): return set(it) @@ -502,6 +683,8 @@ def __iter__(self): class ValuesView(MappingView): + __slots__ = () + def __contains__(self, value): for key in self._mapping: if value == self._mapping[key]: @@ -647,13 +830,23 @@ def __reversed__(self): for i in reversed(range(len(self))): yield self[i] - def index(self, value): - '''S.index(value) -> integer -- return first index of value. + def index(self, value, start=0, stop=None): + '''S.index(value, [start, [stop]]) -> integer -- return first index of value. Raises ValueError if the value is not present. ''' - for i, v in enumerate(self): - if v == value: - return i + if start is not None and start < 0: + start = max(len(self) + start, 0) + if stop is not None and stop < 0: + stop += len(self) + + i = start + while stop is None or i < stop: + try: + if self[i] == value: + return i + except IndexError: + break + i += 1 raise ValueError def count(self, value): diff --git a/lib/_compat_pickle.py b/lib/_compat_pickle.py index 6e39d4ae..c0e0443c 100644 --- a/lib/_compat_pickle.py +++ b/lib/_compat_pickle.py @@ -177,6 +177,13 @@ 'DocXMLRPCServer': 'xmlrpc.server', 'SimpleHTTPServer': 'http.server', 'CGIHTTPServer': 'http.server', + # For compatibility with broken pickles saved in old Python 3 versions + 'UserDict': 'collections', + 'UserList': 'collections', + 'UserString': 'collections', + 'whichdb': 'dbm', + 'StringIO': 'io', + 'cStringIO': 'io', }) REVERSE_IMPORT_MAPPING.update({ diff --git a/lib/_compression.py b/lib/_compression.py new file mode 100644 index 00000000..b00f31b4 --- /dev/null +++ b/lib/_compression.py @@ -0,0 +1,152 @@ +"""Internal classes used by the gzip, lzma and bz2 modules""" + +import io + + +BUFFER_SIZE = io.DEFAULT_BUFFER_SIZE # Compressed data read chunk size + + +class BaseStream(io.BufferedIOBase): + """Mode-checking helper functions.""" + + def _check_not_closed(self): + if self.closed: + raise ValueError("I/O operation on closed file") + + def _check_can_read(self): + if not self.readable(): + raise io.UnsupportedOperation("File not open for reading") + + def _check_can_write(self): + if not self.writable(): + raise io.UnsupportedOperation("File not open for writing") + + def _check_can_seek(self): + if not self.readable(): + raise io.UnsupportedOperation("Seeking is only supported " + "on files open for reading") + if not self.seekable(): + raise io.UnsupportedOperation("The underlying file object " + "does not support seeking") + + +class DecompressReader(io.RawIOBase): + """Adapts the decompressor API to a RawIOBase reader API""" + + def readable(self): + return True + + def __init__(self, fp, decomp_factory, trailing_error=(), **decomp_args): + self._fp = fp + self._eof = False + self._pos = 0 # Current offset in decompressed stream + + # Set to size of decompressed stream once it is known, for SEEK_END + self._size = -1 + + # Save the decompressor factory and arguments. + # If the file contains multiple compressed streams, each + # stream will need a separate decompressor object. A new decompressor + # object is also needed when implementing a backwards seek(). + self._decomp_factory = decomp_factory + self._decomp_args = decomp_args + self._decompressor = self._decomp_factory(**self._decomp_args) + + # Exception class to catch from decompressor signifying invalid + # trailing data to ignore + self._trailing_error = trailing_error + + def close(self): + self._decompressor = None + return super().close() + + def seekable(self): + return self._fp.seekable() + + def readinto(self, b): + with memoryview(b) as view, view.cast("B") as byte_view: + data = self.read(len(byte_view)) + byte_view[:len(data)] = data + return len(data) + + def read(self, size=-1): + if size < 0: + return self.readall() + + if not size or self._eof: + return b"" + data = None # Default if EOF is encountered + # Depending on the input data, our call to the decompressor may not + # return any data. In this case, try again after reading another block. + while True: + if self._decompressor.eof: + rawblock = (self._decompressor.unused_data or + self._fp.read(BUFFER_SIZE)) + if not rawblock: + break + # Continue to next stream. + self._decompressor = self._decomp_factory( + **self._decomp_args) + try: + data = self._decompressor.decompress(rawblock, size) + except self._trailing_error: + # Trailing data isn't a valid compressed stream; ignore it. + break + else: + if self._decompressor.needs_input: + rawblock = self._fp.read(BUFFER_SIZE) + if not rawblock: + raise EOFError("Compressed file ended before the " + "end-of-stream marker was reached") + else: + rawblock = b"" + data = self._decompressor.decompress(rawblock, size) + if data: + break + if not data: + self._eof = True + self._size = self._pos + return b"" + self._pos += len(data) + return data + + # Rewind the file to the beginning of the data stream. + def _rewind(self): + self._fp.seek(0) + self._eof = False + self._pos = 0 + self._decompressor = self._decomp_factory(**self._decomp_args) + + def seek(self, offset, whence=io.SEEK_SET): + # Recalculate offset as an absolute file position. + if whence == io.SEEK_SET: + pass + elif whence == io.SEEK_CUR: + offset = self._pos + offset + elif whence == io.SEEK_END: + # Seeking relative to EOF - we need to know the file's size. + if self._size < 0: + while self.read(io.DEFAULT_BUFFER_SIZE): + pass + offset = self._size + offset + else: + raise ValueError("Invalid value for whence: {}".format(whence)) + + # Make it so that offset is the number of bytes to skip forward. + if offset < self._pos: + self._rewind() + else: + offset -= self._pos + + # Read and discard data until we reach the desired position. + while offset > 0: + data = self.read(min(io.DEFAULT_BUFFER_SIZE, offset)) + if not data: + break + offset -= len(data) + + return self._pos + + def tell(self): + """Return the current file position.""" + return self._pos diff --git a/lib/_dummy_thread.py b/lib/_dummy_thread.py index b67cfb95..36e5f38a 100644 --- a/lib/_dummy_thread.py +++ b/lib/_dummy_thread.py @@ -140,6 +140,14 @@ def release(self): def locked(self): return self.locked_status + def __repr__(self): + return "<%s %s.%s object at %s>" % ( + "locked" if self.locked_status else "unlocked", + self.__class__.__module__, + self.__class__.__qualname__, + hex(id(self)) + ) + # Used to signal that interrupt_main was called in a "thread" _interrupt = False # True when not executing in a "thread" diff --git a/lib/_osx_support.py b/lib/_osx_support.py index b07e75d0..03d36c9f 100644 --- a/lib/_osx_support.py +++ b/lib/_osx_support.py @@ -151,13 +151,13 @@ def _find_appropriate_compiler(_config_vars): # can only be found inside Xcode.app if the "Command Line Tools" # are not installed. # - # Futhermore, the compiler that can be used varies between + # Furthermore, the compiler that can be used varies between # Xcode releases. Up to Xcode 4 it was possible to use 'gcc-4.2' # as the compiler, after that 'clang' should be used because # gcc-4.2 is either not present, or a copy of 'llvm-gcc' that # miscompiles Python. - # skip checks if the compiler was overriden with a CC env variable + # skip checks if the compiler was overridden with a CC env variable if 'CC' in os.environ: return _config_vars @@ -193,7 +193,7 @@ def _find_appropriate_compiler(_config_vars): if cc != oldcc: # Found a replacement compiler. # Modify config vars using new compiler, if not already explicitly - # overriden by an env variable, preserving additional arguments. + # overridden by an env variable, preserving additional arguments. for cv in _COMPILER_CONFIG_VARS: if cv in _config_vars and cv not in os.environ: cv_split = _config_vars[cv].split() @@ -207,10 +207,10 @@ def _remove_universal_flags(_config_vars): """Remove all universal build arguments from config vars""" for cv in _UNIVERSAL_CONFIG_VARS: - # Do not alter a config var explicitly overriden by env var + # Do not alter a config var explicitly overridden by env var if cv in _config_vars and cv not in os.environ: flags = _config_vars[cv] - flags = re.sub('-arch\s+\w+\s', ' ', flags, re.ASCII) + flags = re.sub(r'-arch\s+\w+\s', ' ', flags, flags=re.ASCII) flags = re.sub('-isysroot [^ \t]*', ' ', flags) _save_modified_value(_config_vars, cv, flags) @@ -228,7 +228,7 @@ def _remove_unsupported_archs(_config_vars): # build extensions on OSX 10.7 and later with the prebuilt # 32-bit installer on the python.org website. - # skip checks if the compiler was overriden with a CC env variable + # skip checks if the compiler was overridden with a CC env variable if 'CC' in os.environ: return _config_vars @@ -244,7 +244,7 @@ def _remove_unsupported_archs(_config_vars): # across Xcode and compiler versions, there is no reliable way # to be sure why it failed. Assume here it was due to lack of # PPC support and remove the related '-arch' flags from each - # config variables not explicitly overriden by an environment + # config variables not explicitly overridden by an environment # variable. If the error was for some other reason, we hope the # failure will show up again when trying to compile an extension # module. @@ -292,7 +292,7 @@ def _check_for_unavailable_sdk(_config_vars): sdk = m.group(1) if not os.path.exists(sdk): for cv in _UNIVERSAL_CONFIG_VARS: - # Do not alter a config var explicitly overriden by env var + # Do not alter a config var explicitly overridden by env var if cv in _config_vars and cv not in os.environ: flags = _config_vars[cv] flags = re.sub(r'-isysroot\s+\S+(?:\s|$)', ' ', flags) diff --git a/lib/_pydecimal.py b/lib/_pydecimal.py new file mode 100644 index 00000000..a5ea340e --- /dev/null +++ b/lib/_pydecimal.py @@ -0,0 +1,6399 @@ +# Copyright (c) 2004 Python Software Foundation. +# All rights reserved. + +# Written by Eric Price +# and Facundo Batista +# and Raymond Hettinger +# and Aahz +# and Tim Peters + +# This module should be kept in sync with the latest updates of the +# IBM specification as it evolves. Those updates will be treated +# as bug fixes (deviation from the spec is a compatibility, usability +# bug) and will be backported. At this point the spec is stabilizing +# and the updates are becoming fewer, smaller, and less significant. + +""" +This is an implementation of decimal floating point arithmetic based on +the General Decimal Arithmetic Specification: + + http://speleotrove.com/decimal/decarith.html + +and IEEE standard 854-1987: + + http://en.wikipedia.org/wiki/IEEE_854-1987 + +Decimal floating point has finite precision with arbitrarily large bounds. + +The purpose of this module is to support arithmetic using familiar +"schoolhouse" rules and to avoid some of the tricky representation +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 0.0; Decimal('1.00') % Decimal('0.1') returns the expected +Decimal('0.00')). + +Here are some examples of using the decimal module: + +>>> from decimal import * +>>> setcontext(ExtendedContext) +>>> Decimal(0) +Decimal('0') +>>> Decimal('1') +Decimal('1') +>>> Decimal('-.0123') +Decimal('-0.0123') +>>> Decimal(123456) +Decimal('123456') +>>> Decimal('123.45e12345678') +Decimal('1.2345E+12345680') +>>> Decimal('1.33') + Decimal('1.27') +Decimal('2.60') +>>> Decimal('12.34') + Decimal('3.87') - Decimal('18.41') +Decimal('-2.20') +>>> dig = Decimal(1) +>>> print(dig / Decimal(3)) +0.333333333 +>>> getcontext().prec = 18 +>>> print(dig / Decimal(3)) +0.333333333333333333 +>>> print(dig.sqrt()) +1 +>>> print(Decimal(3).sqrt()) +1.73205080756887729 +>>> print(Decimal(3) ** 123) +4.85192780976896427E+58 +>>> inf = Decimal(1) / Decimal(0) +>>> print(inf) +Infinity +>>> neginf = Decimal(-1) / Decimal(0) +>>> print(neginf) +-Infinity +>>> print(neginf + inf) +NaN +>>> print(neginf * inf) +-Infinity +>>> print(dig / 0) +Infinity +>>> getcontext().traps[DivisionByZero] = 1 +>>> print(dig / 0) +Traceback (most recent call last): + ... + ... + ... +decimal.DivisionByZero: x / 0 +>>> c = Context() +>>> c.traps[InvalidOperation] = 0 +>>> print(c.flags[InvalidOperation]) +0 +>>> c.divide(Decimal(0), Decimal(0)) +Decimal('NaN') +>>> c.traps[InvalidOperation] = 1 +>>> print(c.flags[InvalidOperation]) +1 +>>> c.flags[InvalidOperation] = 0 +>>> print(c.flags[InvalidOperation]) +0 +>>> print(c.divide(Decimal(0), Decimal(0))) +Traceback (most recent call last): + ... + ... + ... +decimal.InvalidOperation: 0 / 0 +>>> print(c.flags[InvalidOperation]) +1 +>>> c.flags[InvalidOperation] = 0 +>>> c.traps[InvalidOperation] = 0 +>>> print(c.divide(Decimal(0), Decimal(0))) +NaN +>>> print(c.flags[InvalidOperation]) +1 +>>> +""" + +__all__ = [ + # Two major classes + 'Decimal', 'Context', + + # Named tuple representation + 'DecimalTuple', + + # Contexts + 'DefaultContext', 'BasicContext', 'ExtendedContext', + + # Exceptions + 'DecimalException', 'Clamped', 'InvalidOperation', 'DivisionByZero', + 'Inexact', 'Rounded', 'Subnormal', 'Overflow', 'Underflow', + 'FloatOperation', + + # Exceptional conditions that trigger InvalidOperation + 'DivisionImpossible', 'InvalidContext', 'ConversionSyntax', 'DivisionUndefined', + + # Constants for use in setting up contexts + 'ROUND_DOWN', 'ROUND_HALF_UP', 'ROUND_HALF_EVEN', 'ROUND_CEILING', + 'ROUND_FLOOR', 'ROUND_UP', 'ROUND_HALF_DOWN', 'ROUND_05UP', + + # Functions for manipulating contexts + 'setcontext', 'getcontext', 'localcontext', + + # Limits for the C version for compatibility + 'MAX_PREC', 'MAX_EMAX', 'MIN_EMIN', 'MIN_ETINY', + + # C version: compile time choice that enables the thread local context + 'HAVE_THREADS' +] + +__xname__ = __name__ # sys.modules lookup (--without-threads) +__name__ = 'decimal' # For pickling +__version__ = '1.70' # Highest version of the spec this complies with + # See http://speleotrove.com/decimal/ +__libmpdec_version__ = "2.4.1" # compatible libmpdec version + +import math as _math +import numbers as _numbers +import sys + +try: + from collections import namedtuple as _namedtuple + DecimalTuple = _namedtuple('DecimalTuple', 'sign digits exponent') +except ImportError: + DecimalTuple = lambda *args: args + +# Rounding +ROUND_DOWN = 'ROUND_DOWN' +ROUND_HALF_UP = 'ROUND_HALF_UP' +ROUND_HALF_EVEN = 'ROUND_HALF_EVEN' +ROUND_CEILING = 'ROUND_CEILING' +ROUND_FLOOR = 'ROUND_FLOOR' +ROUND_UP = 'ROUND_UP' +ROUND_HALF_DOWN = 'ROUND_HALF_DOWN' +ROUND_05UP = 'ROUND_05UP' + +# Compatibility with the C version +HAVE_THREADS = True +if sys.maxsize == 2**63-1: + MAX_PREC = 999999999999999999 + MAX_EMAX = 999999999999999999 + MIN_EMIN = -999999999999999999 +else: + MAX_PREC = 425000000 + MAX_EMAX = 425000000 + MIN_EMIN = -425000000 + +MIN_ETINY = MIN_EMIN - (MAX_PREC-1) + +# Errors + +class DecimalException(ArithmeticError): + """Base exception class. + + Used exceptions derive from this. + If an exception derives from another exception besides this (such as + Underflow (Inexact, Rounded, Subnormal) that indicates that it is only + called if the others are present. This isn't actually used for + anything, though. + + handle -- Called when context._raise_error is called and the + trap_enabler is not set. First argument is self, second is the + context. More arguments can be given, those being after + the explanation in _raise_error (For example, + context._raise_error(NewError, '(-x)!', self._sign) would + call NewError().handle(context, self._sign).) + + To define a new exception, it should be sufficient to have it derive + from DecimalException. + """ + def handle(self, context, *args): + pass + + +class Clamped(DecimalException): + """Exponent of a 0 changed to fit bounds. + + This occurs and signals clamped if the exponent of a result has been + altered in order to fit the constraints of a specific concrete + representation. This may occur when the exponent of a zero result would + be outside the bounds of a representation, or when a large normal + number would have an encoded exponent that cannot be represented. In + this latter case, the exponent is reduced to fit and the corresponding + number of zero digits are appended to the coefficient ("fold-down"). + """ + +class InvalidOperation(DecimalException): + """An invalid operation was performed. + + Various bad things cause this: + + Something creates a signaling NaN + -INF + INF + 0 * (+-)INF + (+-)INF / (+-)INF + x % 0 + (+-)INF % x + x._rescale( non-integer ) + sqrt(-x) , x > 0 + 0 ** 0 + x ** (non-integer) + x ** (+-)INF + An operand is invalid + + The result of the operation after these is a quiet positive NaN, + except when the cause is a signaling NaN, in which case the result is + also a quiet NaN, but with the original sign, and an optional + diagnostic information. + """ + def handle(self, context, *args): + if args: + ans = _dec_from_triple(args[0]._sign, args[0]._int, 'n', True) + return ans._fix_nan(context) + return _NaN + +class ConversionSyntax(InvalidOperation): + """Trying to convert badly formed string. + + This occurs and signals invalid-operation if a string is being + converted to a number and it does not conform to the numeric string + syntax. The result is [0,qNaN]. + """ + def handle(self, context, *args): + return _NaN + +class DivisionByZero(DecimalException, ZeroDivisionError): + """Division by 0. + + This occurs and signals division-by-zero if division of a finite number + by zero was attempted (during a divide-integer or divide operation, or a + power operation with negative right-hand operand), and the dividend was + not zero. + + The result of the operation is [sign,inf], where sign is the exclusive + or of the signs of the operands for divide, or is 1 for an odd power of + -0, for power. + """ + + def handle(self, context, sign, *args): + return _SignedInfinity[sign] + +class DivisionImpossible(InvalidOperation): + """Cannot perform the division adequately. + + This occurs and signals invalid-operation if the integer result of a + divide-integer or remainder operation had too many digits (would be + longer than precision). The result is [0,qNaN]. + """ + + def handle(self, context, *args): + return _NaN + +class DivisionUndefined(InvalidOperation, ZeroDivisionError): + """Undefined result of division. + + This occurs and signals invalid-operation if division by zero was + attempted (during a divide-integer, divide, or remainder operation), and + the dividend is also zero. The result is [0,qNaN]. + """ + + def handle(self, context, *args): + return _NaN + +class Inexact(DecimalException): + """Had to round, losing information. + + This occurs and signals inexact whenever the result of an operation is + not exact (that is, it needed to be rounded and any discarded digits + were non-zero), or if an overflow or underflow condition occurs. The + result in all cases is unchanged. + + The inexact signal may be tested (or trapped) to determine if a given + operation (or sequence of operations) was inexact. + """ + +class InvalidContext(InvalidOperation): + """Invalid context. Unknown rounding, for example. + + This occurs and signals invalid-operation if an invalid context was + detected during an operation. This can occur if contexts are not checked + on creation and either the precision exceeds the capability of the + underlying concrete representation or an unknown or unsupported rounding + was specified. These aspects of the context need only be checked when + the values are required to be used. The result is [0,qNaN]. + """ + + def handle(self, context, *args): + return _NaN + +class Rounded(DecimalException): + """Number got rounded (not necessarily changed during rounding). + + This occurs and signals rounded whenever the result of an operation is + rounded (that is, some zero or non-zero digits were discarded from the + coefficient), or if an overflow or underflow condition occurs. The + result in all cases is unchanged. + + The rounded signal may be tested (or trapped) to determine if a given + operation (or sequence of operations) caused a loss of precision. + """ + +class Subnormal(DecimalException): + """Exponent < Emin before rounding. + + This occurs and signals subnormal whenever the result of a conversion or + operation is subnormal (that is, its adjusted exponent is less than + Emin, before any rounding). The result in all cases is unchanged. + + The subnormal signal may be tested (or trapped) to determine if a given + or operation (or sequence of operations) yielded a subnormal result. + """ + +class Overflow(Inexact, Rounded): + """Numerical overflow. + + This occurs and signals overflow if the adjusted exponent of a result + (from a conversion or from an operation that is not an attempt to divide + by zero), after rounding, would be greater than the largest value that + can be handled by the implementation (the value Emax). + + The result depends on the rounding mode: + + For round-half-up and round-half-even (and for round-half-down and + round-up, if implemented), the result of the operation is [sign,inf], + where sign is the sign of the intermediate result. For round-down, the + result is the largest finite number that can be represented in the + current precision, with the sign of the intermediate result. For + round-ceiling, the result is the same as for round-down if the sign of + the intermediate result is 1, or is [0,inf] otherwise. For round-floor, + the result is the same as for round-down if the sign of the intermediate + result is 0, or is [1,inf] otherwise. In all cases, Inexact and Rounded + will also be raised. + """ + + def handle(self, context, sign, *args): + if context.rounding in (ROUND_HALF_UP, ROUND_HALF_EVEN, + ROUND_HALF_DOWN, ROUND_UP): + return _SignedInfinity[sign] + if sign == 0: + if context.rounding == ROUND_CEILING: + return _SignedInfinity[sign] + return _dec_from_triple(sign, '9'*context.prec, + context.Emax-context.prec+1) + if sign == 1: + if context.rounding == ROUND_FLOOR: + return _SignedInfinity[sign] + return _dec_from_triple(sign, '9'*context.prec, + context.Emax-context.prec+1) + + +class Underflow(Inexact, Rounded, Subnormal): + """Numerical underflow with result rounded to 0. + + This occurs and signals underflow if a result is inexact and the + adjusted exponent of the result would be smaller (more negative) than + the smallest value that can be handled by the implementation (the value + Emin). That is, the result is both inexact and subnormal. + + The result after an underflow will be a subnormal number rounded, if + necessary, so that its exponent is not less than Etiny. This may result + in 0 with the sign of the intermediate result and an exponent of Etiny. + + In all cases, Inexact, Rounded, and Subnormal will also be raised. + """ + +class FloatOperation(DecimalException, TypeError): + """Enable stricter semantics for mixing floats and Decimals. + + If the signal is not trapped (default), mixing floats and Decimals is + permitted in the Decimal() constructor, context.create_decimal() and + all comparison operators. Both conversion and comparisons are exact. + Any occurrence of a mixed operation is silently recorded by setting + FloatOperation in the context flags. Explicit conversions with + Decimal.from_float() or context.create_decimal_from_float() do not + set the flag. + + Otherwise (the signal is trapped), only equality comparisons and explicit + conversions are silent. All other mixed operations raise FloatOperation. + """ + +# List of public traps and flags +_signals = [Clamped, DivisionByZero, Inexact, Overflow, Rounded, + Underflow, InvalidOperation, Subnormal, FloatOperation] + +# Map conditions (per the spec) to signals +_condition_map = {ConversionSyntax:InvalidOperation, + DivisionImpossible:InvalidOperation, + DivisionUndefined:InvalidOperation, + InvalidContext:InvalidOperation} + +# Valid rounding modes +_rounding_modes = (ROUND_DOWN, ROUND_HALF_UP, ROUND_HALF_EVEN, ROUND_CEILING, + ROUND_FLOOR, ROUND_UP, ROUND_HALF_DOWN, ROUND_05UP) + +##### Context Functions ################################################## + +# The getcontext() and setcontext() function manage access to a thread-local +# current context. Py2.4 offers direct support for thread locals. If that +# is not available, use threading.current_thread() which is slower but will +# work for older Pythons. If threads are not part of the build, create a +# mock threading object with threading.local() returning the module namespace. + +try: + import threading +except ImportError: + # Python was compiled without threads; create a mock object instead + class MockThreading(object): + def local(self, sys=sys): + return sys.modules[__xname__] + threading = MockThreading() + del MockThreading + +try: + threading.local + +except AttributeError: + + # To fix reloading, force it to create a new context + # Old contexts have different exceptions in their dicts, making problems. + if hasattr(threading.current_thread(), '__decimal_context__'): + del threading.current_thread().__decimal_context__ + + def setcontext(context): + """Set this thread's context to context.""" + if context in (DefaultContext, BasicContext, ExtendedContext): + context = context.copy() + context.clear_flags() + threading.current_thread().__decimal_context__ = context + + def getcontext(): + """Returns this thread's context. + + If this thread does not yet have a context, returns + a new context and sets this thread's context. + New contexts are copies of DefaultContext. + """ + try: + return threading.current_thread().__decimal_context__ + except AttributeError: + context = Context() + threading.current_thread().__decimal_context__ = context + return context + +else: + + local = threading.local() + if hasattr(local, '__decimal_context__'): + del local.__decimal_context__ + + def getcontext(_local=local): + """Returns this thread's context. + + If this thread does not yet have a context, returns + a new context and sets this thread's context. + New contexts are copies of DefaultContext. + """ + try: + return _local.__decimal_context__ + except AttributeError: + context = Context() + _local.__decimal_context__ = context + return context + + def setcontext(context, _local=local): + """Set this thread's context to context.""" + if context in (DefaultContext, BasicContext, ExtendedContext): + context = context.copy() + context.clear_flags() + _local.__decimal_context__ = context + + del threading, local # Don't contaminate the namespace + +def localcontext(ctx=None): + """Return a context manager for a copy of the supplied context + + Uses a copy of the current context if no context is specified + The returned context manager creates a local decimal context + in a with statement: + def sin(x): + with localcontext() as ctx: + ctx.prec += 2 + # Rest of sin calculation algorithm + # uses a precision 2 greater than normal + return +s # Convert result to normal precision + + def sin(x): + with localcontext(ExtendedContext): + # Rest of sin calculation algorithm + # uses the Extended Context from the + # General Decimal Arithmetic Specification + return +s # Convert result to normal context + + >>> setcontext(DefaultContext) + >>> print(getcontext().prec) + 28 + >>> with localcontext(): + ... ctx = getcontext() + ... ctx.prec += 2 + ... print(ctx.prec) + ... + 30 + >>> with localcontext(ExtendedContext): + ... print(getcontext().prec) + ... + 9 + >>> print(getcontext().prec) + 28 + """ + if ctx is None: ctx = getcontext() + return _ContextManager(ctx) + + +##### Decimal class ####################################################### + +# Do not subclass Decimal from numbers.Real and do not register it as such +# (because Decimals are not interoperable with floats). See the notes in +# numbers.py for more detail. + +class Decimal(object): + """Floating point class for decimal arithmetic.""" + + __slots__ = ('_exp','_int','_sign', '_is_special') + # Generally, the value of the Decimal instance is given by + # (-1)**_sign * _int * 10**_exp + # Special values are signified by _is_special == True + + # We're immutable, so use __new__ not __init__ + def __new__(cls, value="0", context=None): + """Create a decimal point instance. + + >>> Decimal('3.14') # string input + Decimal('3.14') + >>> Decimal((0, (3, 1, 4), -2)) # tuple (sign, digit_tuple, exponent) + Decimal('3.14') + >>> Decimal(314) # int + Decimal('314') + >>> Decimal(Decimal(314)) # another decimal instance + Decimal('314') + >>> Decimal(' 3.14 \\n') # leading and trailing whitespace okay + Decimal('3.14') + """ + + # Note that the coefficient, self._int, is actually stored as + # a string rather than as a tuple of digits. This speeds up + # the "digits to integer" and "integer to digits" conversions + # that are used in almost every arithmetic operation on + # Decimals. This is an internal detail: the as_tuple function + # and the Decimal constructor still deal with tuples of + # digits. + + self = object.__new__(cls) + + # From a string + # REs insist on real strings, so we can too. + if isinstance(value, str): + m = _parser(value.strip()) + if m is None: + if context is None: + context = getcontext() + return context._raise_error(ConversionSyntax, + "Invalid literal for Decimal: %r" % value) + + if m.group('sign') == "-": + self._sign = 1 + else: + self._sign = 0 + intpart = m.group('int') + if intpart is not None: + # finite number + fracpart = m.group('frac') or '' + exp = int(m.group('exp') or '0') + self._int = str(int(intpart+fracpart)) + self._exp = exp - len(fracpart) + self._is_special = False + else: + diag = m.group('diag') + if diag is not None: + # NaN + self._int = str(int(diag or '0')).lstrip('0') + if m.group('signal'): + self._exp = 'N' + else: + self._exp = 'n' + else: + # infinity + self._int = '0' + self._exp = 'F' + self._is_special = True + return self + + # From an integer + if isinstance(value, int): + if value >= 0: + self._sign = 0 + else: + self._sign = 1 + self._exp = 0 + self._int = str(abs(value)) + self._is_special = False + return self + + # From another decimal + if isinstance(value, Decimal): + self._exp = value._exp + self._sign = value._sign + self._int = value._int + self._is_special = value._is_special + return self + + # From an internal working value + if isinstance(value, _WorkRep): + self._sign = value.sign + self._int = str(value.int) + self._exp = int(value.exp) + self._is_special = False + return self + + # tuple/list conversion (possibly from as_tuple()) + if isinstance(value, (list,tuple)): + if len(value) != 3: + raise ValueError('Invalid tuple size in creation of Decimal ' + 'from list or tuple. The list or tuple ' + 'should have exactly three elements.') + # process sign. The isinstance test rejects floats + if not (isinstance(value[0], int) and value[0] in (0,1)): + raise ValueError("Invalid sign. The first value in the tuple " + "should be an integer; either 0 for a " + "positive number or 1 for a negative number.") + self._sign = value[0] + if value[2] == 'F': + # infinity: value[1] is ignored + self._int = '0' + self._exp = value[2] + self._is_special = True + else: + # process and validate the digits in value[1] + digits = [] + for digit in value[1]: + if isinstance(digit, int) and 0 <= digit <= 9: + # skip leading zeros + if digits or digit != 0: + digits.append(digit) + else: + raise ValueError("The second value in the tuple must " + "be composed of integers in the range " + "0 through 9.") + if value[2] in ('n', 'N'): + # NaN: digits form the diagnostic + self._int = ''.join(map(str, digits)) + self._exp = value[2] + self._is_special = True + elif isinstance(value[2], int): + # finite number: digits give the coefficient + self._int = ''.join(map(str, digits or [0])) + self._exp = value[2] + self._is_special = False + else: + raise ValueError("The third value in the tuple must " + "be an integer, or one of the " + "strings 'F', 'n', 'N'.") + return self + + if isinstance(value, float): + if context is None: + context = getcontext() + context._raise_error(FloatOperation, + "strict semantics for mixing floats and Decimals are " + "enabled") + 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) + + @classmethod + def from_float(cls, f): + """Converts a float to a decimal number, exactly. + + Note that Decimal.from_float(0.1) is not the same as Decimal('0.1'). + Since 0.1 is not exactly representable in binary floating point, the + value is stored as the nearest representable value which is + 0x1.999999999999ap-4. The exact equivalent of the value in decimal + is 0.1000000000000000055511151231257827021181583404541015625. + + >>> Decimal.from_float(0.1) + Decimal('0.1000000000000000055511151231257827021181583404541015625') + >>> Decimal.from_float(float('nan')) + Decimal('NaN') + >>> Decimal.from_float(float('inf')) + Decimal('Infinity') + >>> Decimal.from_float(-float('inf')) + Decimal('-Infinity') + >>> Decimal.from_float(-0.0) + Decimal('-0') + + """ + if isinstance(f, int): # handle integer inputs + return cls(f) + if not isinstance(f, float): + raise TypeError("argument must be int or float.") + if _math.isinf(f) or _math.isnan(f): + return cls(repr(f)) + if _math.copysign(1.0, f) == 1.0: + sign = 0 + else: + sign = 1 + n, d = abs(f).as_integer_ratio() + k = d.bit_length() - 1 + result = _dec_from_triple(sign, str(n*5**k), -k) + if cls is Decimal: + return result + else: + return cls(result) + + def _isnan(self): + """Returns whether the number is not actually one. + + 0 if a number + 1 if NaN + 2 if sNaN + """ + if self._is_special: + exp = self._exp + if exp == 'n': + return 1 + elif exp == 'N': + return 2 + return 0 + + def _isinfinity(self): + """Returns whether the number is infinite + + 0 if finite or not a number + 1 if +INF + -1 if -INF + """ + if self._exp == 'F': + if self._sign: + return -1 + return 1 + return 0 + + def _check_nans(self, other=None, context=None): + """Returns whether the number is not actually one. + + if self, other are sNaN, signal + if self, other are NaN return nan + return 0 + + Done before operations. + """ + + self_is_nan = self._isnan() + if other is None: + other_is_nan = False + else: + other_is_nan = other._isnan() + + if self_is_nan or other_is_nan: + if context is None: + context = getcontext() + + if self_is_nan == 2: + return context._raise_error(InvalidOperation, 'sNaN', + self) + if other_is_nan == 2: + return context._raise_error(InvalidOperation, 'sNaN', + other) + if self_is_nan: + return self._fix_nan(context) + + return other._fix_nan(context) + return 0 + + def _compare_check_nans(self, other, context): + """Version of _check_nans used for the signaling comparisons + compare_signal, __le__, __lt__, __ge__, __gt__. + + Signal InvalidOperation if either self or other is a (quiet + or signaling) NaN. Signaling NaNs take precedence over quiet + NaNs. + + Return 0 if neither operand is a NaN. + + """ + if context is None: + context = getcontext() + + if self._is_special or other._is_special: + if self.is_snan(): + return context._raise_error(InvalidOperation, + 'comparison involving sNaN', + self) + elif other.is_snan(): + return context._raise_error(InvalidOperation, + 'comparison involving sNaN', + other) + elif self.is_qnan(): + return context._raise_error(InvalidOperation, + 'comparison involving NaN', + self) + elif other.is_qnan(): + return context._raise_error(InvalidOperation, + 'comparison involving NaN', + other) + return 0 + + def __bool__(self): + """Return True if self is nonzero; otherwise return False. + + NaNs and infinities are considered nonzero. + """ + return self._is_special or self._int != '0' + + def _cmp(self, other): + """Compare the two non-NaN decimal instances self and other. + + Returns -1 if self < other, 0 if self == other and 1 + if self > other. This routine is for internal use only.""" + + if self._is_special or other._is_special: + self_inf = self._isinfinity() + other_inf = other._isinfinity() + if self_inf == other_inf: + return 0 + elif self_inf < other_inf: + return -1 + else: + return 1 + + # check for zeros; Decimal('0') == Decimal('-0') + if not self: + if not other: + return 0 + else: + return -((-1)**other._sign) + if not other: + return (-1)**self._sign + + # If different signs, neg one is less + if other._sign < self._sign: + return -1 + if self._sign < other._sign: + return 1 + + self_adjusted = self.adjusted() + other_adjusted = other.adjusted() + if self_adjusted == other_adjusted: + self_padded = self._int + '0'*(self._exp - other._exp) + other_padded = other._int + '0'*(other._exp - self._exp) + if self_padded == other_padded: + return 0 + elif self_padded < other_padded: + return -(-1)**self._sign + else: + return (-1)**self._sign + elif self_adjusted > other_adjusted: + return (-1)**self._sign + else: # self_adjusted < other_adjusted + return -((-1)**self._sign) + + # Note: The Decimal standard doesn't cover rich comparisons for + # Decimals. In particular, the specification is silent on the + # subject of what should happen for a comparison involving a NaN. + # We take the following approach: + # + # == 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. + # + # This behavior is designed to conform as closely as possible to + # that specified by IEEE 754. + + def __eq__(self, other, context=None): + self, other = _convert_for_comparison(self, other, equality_op=True) + if other is NotImplemented: + return other + if self._check_nans(other, context): + return False + return self._cmp(other) == 0 + + def __lt__(self, other, context=None): + self, other = _convert_for_comparison(self, other) + if other is NotImplemented: + return other + ans = self._compare_check_nans(other, context) + if ans: + return False + return self._cmp(other) < 0 + + def __le__(self, other, context=None): + self, other = _convert_for_comparison(self, other) + if other is NotImplemented: + return other + ans = self._compare_check_nans(other, context) + if ans: + return False + return self._cmp(other) <= 0 + + def __gt__(self, other, context=None): + self, other = _convert_for_comparison(self, other) + if other is NotImplemented: + return other + ans = self._compare_check_nans(other, context) + if ans: + return False + return self._cmp(other) > 0 + + def __ge__(self, other, context=None): + self, other = _convert_for_comparison(self, other) + if other is NotImplemented: + return other + ans = self._compare_check_nans(other, context) + if ans: + return False + return self._cmp(other) >= 0 + + def compare(self, other, context=None): + """Compare self to other. Return a decimal value: + + a or b is a NaN ==> Decimal('NaN') + a < b ==> Decimal('-1') + a == b ==> Decimal('0') + a > b ==> Decimal('1') + """ + other = _convert_other(other, raiseit=True) + + # Compare(NaN, NaN) = NaN + if (self._is_special or other and other._is_special): + ans = self._check_nans(other, context) + if ans: + return ans + + return Decimal(self._cmp(other)) + + def __hash__(self): + """x.__hash__() <==> hash(x)""" + + # 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.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. + + To show the internals exactly as they are. + """ + return DecimalTuple(self._sign, tuple(map(int, self._int)), self._exp) + + def __repr__(self): + """Represents the number as an instance of Decimal.""" + # Invariant: eval(repr(d)) == d + return "Decimal('%s')" % str(self) + + def __str__(self, eng=False, context=None): + """Return string representation of the number in scientific notation. + + Captures all of the information in the underlying representation. + """ + + sign = ['', '-'][self._sign] + if self._is_special: + if self._exp == 'F': + return sign + 'Infinity' + elif self._exp == 'n': + return sign + 'NaN' + self._int + else: # self._exp == 'N' + return sign + 'sNaN' + self._int + + # number of digits of self._int to left of decimal point + leftdigits = self._exp + len(self._int) + + # dotplace is number of digits of self._int to the left of the + # decimal point in the mantissa of the output string (that is, + # after adjusting the exponent) + if self._exp <= 0 and leftdigits > -6: + # no exponent required + dotplace = leftdigits + elif not eng: + # usual scientific notation: 1 digit on left of the point + dotplace = 1 + elif self._int == '0': + # engineering notation, zero + dotplace = (leftdigits + 1) % 3 - 1 + else: + # engineering notation, nonzero + dotplace = (leftdigits - 1) % 3 + 1 + + if dotplace <= 0: + intpart = '0' + fracpart = '.' + '0'*(-dotplace) + self._int + elif dotplace >= len(self._int): + intpart = self._int+'0'*(dotplace-len(self._int)) + fracpart = '' + else: + intpart = self._int[:dotplace] + fracpart = '.' + self._int[dotplace:] + if leftdigits == dotplace: + exp = '' + else: + if context is None: + context = getcontext() + exp = ['e', 'E'][context.capitals] + "%+d" % (leftdigits-dotplace) + + return sign + intpart + fracpart + exp + + def to_eng_string(self, context=None): + """Convert to a string, using engineering notation if an exponent is needed. + + Engineering notation has an exponent which is a multiple of 3. This + can leave up to 3 digits to the left of the decimal place and may + require the addition of either one or two trailing zeros. + """ + return self.__str__(eng=True, context=context) + + def __neg__(self, context=None): + """Returns a copy with the sign switched. + + Rounds, if it has reason. + """ + if self._is_special: + ans = self._check_nans(context=context) + if ans: + return ans + + if context is None: + context = getcontext() + + if not self and context.rounding != ROUND_FLOOR: + # -Decimal('0') is Decimal('0'), not Decimal('-0'), except + # in ROUND_FLOOR rounding mode. + ans = self.copy_abs() + else: + ans = self.copy_negate() + + return ans._fix(context) + + def __pos__(self, context=None): + """Returns a copy, unless it is a sNaN. + + Rounds the number (if more than precision digits) + """ + if self._is_special: + ans = self._check_nans(context=context) + if ans: + return ans + + if context is None: + context = getcontext() + + if not self and context.rounding != ROUND_FLOOR: + # + (-0) = 0, except in ROUND_FLOOR rounding mode. + ans = self.copy_abs() + else: + ans = Decimal(self) + + return ans._fix(context) + + def __abs__(self, round=True, context=None): + """Returns the absolute value of self. + + If the keyword argument 'round' is false, do not round. The + expression self.__abs__(round=False) is equivalent to + self.copy_abs(). + """ + if not round: + return self.copy_abs() + + if self._is_special: + ans = self._check_nans(context=context) + if ans: + return ans + + if self._sign: + ans = self.__neg__(context=context) + else: + ans = self.__pos__(context=context) + + return ans + + def __add__(self, other, context=None): + """Returns self + other. + + -INF + INF (or the reverse) cause InvalidOperation errors. + """ + other = _convert_other(other) + if other is NotImplemented: + return other + + if context is None: + context = getcontext() + + if self._is_special or other._is_special: + ans = self._check_nans(other, context) + if ans: + return ans + + if self._isinfinity(): + # If both INF, same sign => same as both, opposite => error. + if self._sign != other._sign and other._isinfinity(): + return context._raise_error(InvalidOperation, '-INF + INF') + return Decimal(self) + if other._isinfinity(): + return Decimal(other) # Can't both be infinity here + + exp = min(self._exp, other._exp) + negativezero = 0 + if context.rounding == ROUND_FLOOR and self._sign != other._sign: + # If the answer is 0, the sign should be negative, in this case. + negativezero = 1 + + if not self and not other: + sign = min(self._sign, other._sign) + if negativezero: + sign = 1 + ans = _dec_from_triple(sign, '0', exp) + ans = ans._fix(context) + return ans + if not self: + exp = max(exp, other._exp - context.prec-1) + ans = other._rescale(exp, context.rounding) + ans = ans._fix(context) + return ans + if not other: + exp = max(exp, self._exp - context.prec-1) + ans = self._rescale(exp, context.rounding) + ans = ans._fix(context) + return ans + + op1 = _WorkRep(self) + op2 = _WorkRep(other) + op1, op2 = _normalize(op1, op2, context.prec) + + result = _WorkRep() + if op1.sign != op2.sign: + # Equal and opposite + if op1.int == op2.int: + ans = _dec_from_triple(negativezero, '0', exp) + ans = ans._fix(context) + return ans + if op1.int < op2.int: + op1, op2 = op2, op1 + # OK, now abs(op1) > abs(op2) + if op1.sign == 1: + result.sign = 1 + op1.sign, op2.sign = op2.sign, op1.sign + else: + result.sign = 0 + # So we know the sign, and op1 > 0. + elif op1.sign == 1: + result.sign = 1 + op1.sign, op2.sign = (0, 0) + else: + result.sign = 0 + # Now, op1 > abs(op2) > 0 + + if op2.sign == 0: + result.int = op1.int + op2.int + else: + result.int = op1.int - op2.int + + result.exp = op1.exp + ans = Decimal(result) + ans = ans._fix(context) + return ans + + __radd__ = __add__ + + def __sub__(self, other, context=None): + """Return self - other""" + other = _convert_other(other) + if other is NotImplemented: + return other + + if self._is_special or other._is_special: + ans = self._check_nans(other, context=context) + if ans: + return ans + + # self - other is computed as self + other.copy_negate() + return self.__add__(other.copy_negate(), context=context) + + def __rsub__(self, other, context=None): + """Return other - self""" + other = _convert_other(other) + if other is NotImplemented: + return other + + return other.__sub__(self, context=context) + + def __mul__(self, other, context=None): + """Return self * other. + + (+-) INF * 0 (or its reverse) raise InvalidOperation. + """ + other = _convert_other(other) + if other is NotImplemented: + return other + + if context is None: + context = getcontext() + + resultsign = self._sign ^ other._sign + + if self._is_special or other._is_special: + ans = self._check_nans(other, context) + if ans: + return ans + + if self._isinfinity(): + if not other: + return context._raise_error(InvalidOperation, '(+-)INF * 0') + return _SignedInfinity[resultsign] + + if other._isinfinity(): + if not self: + return context._raise_error(InvalidOperation, '0 * (+-)INF') + return _SignedInfinity[resultsign] + + resultexp = self._exp + other._exp + + # Special case for multiplying by zero + if not self or not other: + ans = _dec_from_triple(resultsign, '0', resultexp) + # Fixing in case the exponent is out of bounds + ans = ans._fix(context) + return ans + + # Special case for multiplying by power of 10 + if self._int == '1': + ans = _dec_from_triple(resultsign, other._int, resultexp) + ans = ans._fix(context) + return ans + if other._int == '1': + ans = _dec_from_triple(resultsign, self._int, resultexp) + ans = ans._fix(context) + return ans + + op1 = _WorkRep(self) + op2 = _WorkRep(other) + + ans = _dec_from_triple(resultsign, str(op1.int * op2.int), resultexp) + ans = ans._fix(context) + + return ans + __rmul__ = __mul__ + + def __truediv__(self, other, context=None): + """Return self / other.""" + other = _convert_other(other) + if other is NotImplemented: + return NotImplemented + + if context is None: + context = getcontext() + + sign = self._sign ^ other._sign + + if self._is_special or other._is_special: + ans = self._check_nans(other, context) + if ans: + return ans + + if self._isinfinity() and other._isinfinity(): + return context._raise_error(InvalidOperation, '(+-)INF/(+-)INF') + + if self._isinfinity(): + return _SignedInfinity[sign] + + if other._isinfinity(): + context._raise_error(Clamped, 'Division by infinity') + return _dec_from_triple(sign, '0', context.Etiny()) + + # Special cases for zeroes + if not other: + if not self: + return context._raise_error(DivisionUndefined, '0 / 0') + return context._raise_error(DivisionByZero, 'x / 0', sign) + + if not self: + exp = self._exp - other._exp + coeff = 0 + else: + # OK, so neither = 0, INF or NaN + shift = len(other._int) - len(self._int) + context.prec + 1 + exp = self._exp - other._exp - shift + op1 = _WorkRep(self) + op2 = _WorkRep(other) + if shift >= 0: + coeff, remainder = divmod(op1.int * 10**shift, op2.int) + else: + coeff, remainder = divmod(op1.int, op2.int * 10**-shift) + if remainder: + # result is not exact; adjust to ensure correct rounding + if coeff % 5 == 0: + coeff += 1 + else: + # result is exact; get as close to ideal exponent as possible + ideal_exp = self._exp - other._exp + while exp < ideal_exp and coeff % 10 == 0: + coeff //= 10 + exp += 1 + + ans = _dec_from_triple(sign, str(coeff), exp) + return ans._fix(context) + + def _divide(self, other, context): + """Return (self // other, self % other), to context.prec precision. + + Assumes that neither self nor other is a NaN, that self is not + infinite and that other is nonzero. + """ + sign = self._sign ^ other._sign + if other._isinfinity(): + ideal_exp = self._exp + else: + ideal_exp = min(self._exp, other._exp) + + expdiff = self.adjusted() - other.adjusted() + if not self or other._isinfinity() or expdiff <= -2: + return (_dec_from_triple(sign, '0', 0), + self._rescale(ideal_exp, context.rounding)) + if expdiff <= context.prec: + op1 = _WorkRep(self) + op2 = _WorkRep(other) + if op1.exp >= op2.exp: + op1.int *= 10**(op1.exp - op2.exp) + else: + op2.int *= 10**(op2.exp - op1.exp) + q, r = divmod(op1.int, op2.int) + if q < 10**context.prec: + return (_dec_from_triple(sign, str(q), 0), + _dec_from_triple(self._sign, str(r), ideal_exp)) + + # Here the quotient is too large to be representable + ans = context._raise_error(DivisionImpossible, + 'quotient too large in //, % or divmod') + return ans, ans + + def __rtruediv__(self, other, context=None): + """Swaps self/other and returns __truediv__.""" + other = _convert_other(other) + if other is NotImplemented: + return other + return other.__truediv__(self, context=context) + + def __divmod__(self, other, context=None): + """ + Return (self // other, self % other) + """ + other = _convert_other(other) + if other is NotImplemented: + return other + + if context is None: + context = getcontext() + + ans = self._check_nans(other, context) + if ans: + return (ans, ans) + + sign = self._sign ^ other._sign + if self._isinfinity(): + if other._isinfinity(): + ans = context._raise_error(InvalidOperation, 'divmod(INF, INF)') + return ans, ans + else: + return (_SignedInfinity[sign], + context._raise_error(InvalidOperation, 'INF % x')) + + if not other: + if not self: + ans = context._raise_error(DivisionUndefined, 'divmod(0, 0)') + return ans, ans + else: + return (context._raise_error(DivisionByZero, 'x // 0', sign), + context._raise_error(InvalidOperation, 'x % 0')) + + quotient, remainder = self._divide(other, context) + remainder = remainder._fix(context) + return quotient, remainder + + def __rdivmod__(self, other, context=None): + """Swaps self/other and returns __divmod__.""" + other = _convert_other(other) + if other is NotImplemented: + return other + return other.__divmod__(self, context=context) + + def __mod__(self, other, context=None): + """ + self % other + """ + other = _convert_other(other) + if other is NotImplemented: + return other + + if context is None: + context = getcontext() + + ans = self._check_nans(other, context) + if ans: + return ans + + if self._isinfinity(): + return context._raise_error(InvalidOperation, 'INF % x') + elif not other: + if self: + return context._raise_error(InvalidOperation, 'x % 0') + else: + return context._raise_error(DivisionUndefined, '0 % 0') + + remainder = self._divide(other, context)[1] + remainder = remainder._fix(context) + return remainder + + def __rmod__(self, other, context=None): + """Swaps self/other and returns __mod__.""" + other = _convert_other(other) + if other is NotImplemented: + return other + return other.__mod__(self, context=context) + + def remainder_near(self, other, context=None): + """ + Remainder nearest to 0- abs(remainder-near) <= other/2 + """ + if context is None: + context = getcontext() + + other = _convert_other(other, raiseit=True) + + ans = self._check_nans(other, context) + if ans: + return ans + + # self == +/-infinity -> InvalidOperation + if self._isinfinity(): + return context._raise_error(InvalidOperation, + 'remainder_near(infinity, x)') + + # other == 0 -> either InvalidOperation or DivisionUndefined + if not other: + if self: + return context._raise_error(InvalidOperation, + 'remainder_near(x, 0)') + else: + return context._raise_error(DivisionUndefined, + 'remainder_near(0, 0)') + + # other = +/-infinity -> remainder = self + if other._isinfinity(): + ans = Decimal(self) + return ans._fix(context) + + # self = 0 -> remainder = self, with ideal exponent + ideal_exponent = min(self._exp, other._exp) + if not self: + ans = _dec_from_triple(self._sign, '0', ideal_exponent) + return ans._fix(context) + + # catch most cases of large or small quotient + expdiff = self.adjusted() - other.adjusted() + if expdiff >= context.prec + 1: + # expdiff >= prec+1 => abs(self/other) > 10**prec + return context._raise_error(DivisionImpossible) + if expdiff <= -2: + # expdiff <= -2 => abs(self/other) < 0.1 + ans = self._rescale(ideal_exponent, context.rounding) + return ans._fix(context) + + # adjust both arguments to have the same exponent, then divide + op1 = _WorkRep(self) + op2 = _WorkRep(other) + if op1.exp >= op2.exp: + op1.int *= 10**(op1.exp - op2.exp) + else: + op2.int *= 10**(op2.exp - op1.exp) + q, r = divmod(op1.int, op2.int) + # remainder is r*10**ideal_exponent; other is +/-op2.int * + # 10**ideal_exponent. Apply correction to ensure that + # abs(remainder) <= abs(other)/2 + if 2*r + (q&1) > op2.int: + r -= op2.int + q += 1 + + if q >= 10**context.prec: + return context._raise_error(DivisionImpossible) + + # result has same sign as self unless r is negative + sign = self._sign + if r < 0: + sign = 1-sign + r = -r + + ans = _dec_from_triple(sign, str(r), ideal_exponent) + return ans._fix(context) + + def __floordiv__(self, other, context=None): + """self // other""" + other = _convert_other(other) + if other is NotImplemented: + return other + + if context is None: + context = getcontext() + + ans = self._check_nans(other, context) + if ans: + return ans + + if self._isinfinity(): + if other._isinfinity(): + return context._raise_error(InvalidOperation, 'INF // INF') + else: + return _SignedInfinity[self._sign ^ other._sign] + + if not other: + if self: + return context._raise_error(DivisionByZero, 'x // 0', + self._sign ^ other._sign) + else: + return context._raise_error(DivisionUndefined, '0 // 0') + + return self._divide(other, context)[0] + + def __rfloordiv__(self, other, context=None): + """Swaps self/other and returns __floordiv__.""" + other = _convert_other(other) + if other is NotImplemented: + return other + return other.__floordiv__(self, context=context) + + def __float__(self): + """Float representation.""" + if self._isnan(): + if self.is_snan(): + raise ValueError("Cannot convert signaling NaN to float") + s = "-nan" if self._sign else "nan" + else: + s = str(self) + return float(s) + + def __int__(self): + """Converts self to an int, truncating if necessary.""" + if self._is_special: + if self._isnan(): + raise ValueError("Cannot convert NaN to integer") + elif self._isinfinity(): + raise OverflowError("Cannot convert infinity to integer") + s = (-1)**self._sign + if self._exp >= 0: + return s*int(self._int)*10**self._exp + else: + return s*int(self._int[:self._exp] or '0') + + __trunc__ = __int__ + + def real(self): + return self + real = property(real) + + def imag(self): + return Decimal(0) + imag = property(imag) + + def conjugate(self): + return self + + def __complex__(self): + return complex(float(self)) + + def _fix_nan(self, context): + """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 + 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) + return Decimal(self) + + def _fix(self, context): + """Round if it is necessary to keep self within prec precision. + + Rounds and fixes the exponent. Does not raise on a sNaN. + + Arguments: + self - Decimal instance + context - context used. + """ + + if self._is_special: + if self._isnan(): + # decapitate payload if necessary + return self._fix_nan(context) + else: + # self is +/-Infinity; return unaltered + 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. + Etiny = context.Etiny() + Etop = context.Etop() + if not self: + 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) + return _dec_from_triple(self._sign, '0', new_exp) + else: + return Decimal(self) + + # exp_min is the smallest allowable exponent of the result, + # equal to max(self.adjusted()-context.prec+1, Etiny) + exp_min = len(self._int) + self._exp - context.prec + if exp_min > Etop: + # overflow: exp_min > Etop iff self.adjusted() > Emax + ans = context._raise_error(Overflow, 'above Emax', self._sign) + context._raise_error(Inexact) + context._raise_error(Rounded) + return ans + + self_is_subnormal = exp_min < Etiny + if self_is_subnormal: + exp_min = Etiny + + # round if self has too many digits + if self._exp < exp_min: + digits = len(self._int) + self._exp - exp_min + if digits < 0: + self = _dec_from_triple(self._sign, '1', exp_min-1) + digits = 0 + rounding_method = self._pick_rounding_function[context.rounding] + changed = rounding_method(self, digits) + coeff = self._int[:digits] or '0' + if changed > 0: + coeff = str(int(coeff)+1) + if len(coeff) > context.prec: + coeff = coeff[:-1] + exp_min += 1 + + # check whether the rounding pushed the exponent out of range + if exp_min > Etop: + ans = context._raise_error(Overflow, 'above Emax', self._sign) + else: + ans = _dec_from_triple(self._sign, coeff, exp_min) + + # raise the appropriate signals, taking care to respect + # the precedence described in the specification + if changed and self_is_subnormal: + context._raise_error(Underflow) + if self_is_subnormal: + context._raise_error(Subnormal) + if changed: + context._raise_error(Inexact) + context._raise_error(Rounded) + if not ans: + # raise Clamped on underflow to 0 + context._raise_error(Clamped) + return ans + + 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: + context._raise_error(Clamped) + self_padded = self._int + '0'*(self._exp - Etop) + return _dec_from_triple(self._sign, self_padded, Etop) + + # here self was representable to begin with; return unchanged + return Decimal(self) + + # for each of the rounding functions below: + # self is a finite, nonzero Decimal + # prec is an integer satisfying 0 <= prec < len(self._int) + # + # each function returns either -1, 0, or 1, as follows: + # 1 indicates that self should be rounded up (away from zero) + # 0 indicates that self should be truncated, and that all the + # digits to be truncated are zeros (so the value is unchanged) + # -1 indicates that there are nonzero digits to be truncated + + def _round_down(self, prec): + """Also known as round-towards-0, truncate.""" + if _all_zeros(self._int, prec): + return 0 + else: + return -1 + + def _round_up(self, prec): + """Rounds away from 0.""" + return -self._round_down(prec) + + def _round_half_up(self, prec): + """Rounds 5 up (away from 0)""" + if self._int[prec] in '56789': + return 1 + elif _all_zeros(self._int, prec): + return 0 + else: + return -1 + + def _round_half_down(self, prec): + """Round 5 down""" + if _exact_half(self._int, prec): + return -1 + else: + return self._round_half_up(prec) + + def _round_half_even(self, prec): + """Round 5 to even, rest to nearest.""" + if _exact_half(self._int, prec) and \ + (prec == 0 or self._int[prec-1] in '02468'): + return -1 + else: + return self._round_half_up(prec) + + def _round_ceiling(self, prec): + """Rounds up (not away from 0 if negative.)""" + if self._sign: + return self._round_down(prec) + else: + return -self._round_down(prec) + + def _round_floor(self, prec): + """Rounds down (not towards 0 if negative)""" + if not self._sign: + return self._round_down(prec) + else: + return -self._round_down(prec) + + def _round_05up(self, prec): + """Round down unless digit prec-1 is 0 or 5.""" + if prec and self._int[prec-1] not in '05': + return self._round_down(prec) + else: + return -self._round_down(prec) + + _pick_rounding_function = dict( + ROUND_DOWN = _round_down, + ROUND_UP = _round_up, + ROUND_HALF_UP = _round_half_up, + ROUND_HALF_DOWN = _round_half_down, + ROUND_HALF_EVEN = _round_half_even, + ROUND_CEILING = _round_ceiling, + ROUND_FLOOR = _round_floor, + ROUND_05UP = _round_05up, + ) + + def __round__(self, n=None): + """Round self to the nearest integer, or to a given precision. + + If only one argument is supplied, round a finite Decimal + instance self to the nearest integer. If self is infinite or + a NaN then a Python exception is raised. If self is finite + and lies exactly halfway between two integers then it is + rounded to the integer with even last digit. + + >>> round(Decimal('123.456')) + 123 + >>> round(Decimal('-456.789')) + -457 + >>> round(Decimal('-3.0')) + -3 + >>> round(Decimal('2.5')) + 2 + >>> round(Decimal('3.5')) + 4 + >>> round(Decimal('Inf')) + Traceback (most recent call last): + ... + OverflowError: cannot round an infinity + >>> round(Decimal('NaN')) + Traceback (most recent call last): + ... + ValueError: cannot round a NaN + + If a second argument n is supplied, self is rounded to n + decimal places using the rounding mode for the current + context. + + For an integer n, round(self, -n) is exactly equivalent to + self.quantize(Decimal('1En')). + + >>> round(Decimal('123.456'), 0) + Decimal('123') + >>> round(Decimal('123.456'), 2) + Decimal('123.46') + >>> round(Decimal('123.456'), -2) + Decimal('1E+2') + >>> round(Decimal('-Infinity'), 37) + Decimal('NaN') + >>> round(Decimal('sNaN123'), 0) + Decimal('NaN123') + + """ + if n is not None: + # two-argument form: use the equivalent quantize call + if not isinstance(n, int): + raise TypeError('Second argument to round should be integral') + exp = _dec_from_triple(0, '1', -n) + return self.quantize(exp) + + # one-argument form + if self._is_special: + if self.is_nan(): + raise ValueError("cannot round a NaN") + else: + raise OverflowError("cannot round an infinity") + return int(self._rescale(0, ROUND_HALF_EVEN)) + + def __floor__(self): + """Return the floor of self, as an integer. + + For a finite Decimal instance self, return the greatest + integer n such that n <= self. If self is infinite or a NaN + then a Python exception is raised. + + """ + if self._is_special: + if self.is_nan(): + raise ValueError("cannot round a NaN") + else: + raise OverflowError("cannot round an infinity") + return int(self._rescale(0, ROUND_FLOOR)) + + def __ceil__(self): + """Return the ceiling of self, as an integer. + + For a finite Decimal instance self, return the least integer n + such that n >= self. If self is infinite or a NaN then a + Python exception is raised. + + """ + if self._is_special: + if self.is_nan(): + raise ValueError("cannot round a NaN") + else: + raise OverflowError("cannot round an infinity") + return int(self._rescale(0, ROUND_CEILING)) + + def fma(self, other, third, context=None): + """Fused multiply-add. + + Returns self*other+third with no rounding of the intermediate + product self*other. + + self and other are multiplied together, with no rounding of + the result. The third operand is then added to the result, + and a single final rounding is performed. + """ + + other = _convert_other(other, raiseit=True) + third = _convert_other(third, raiseit=True) + + # compute product; raise InvalidOperation if either operand is + # a signaling NaN or if the product is zero times infinity. + if self._is_special or other._is_special: + if context is None: + context = getcontext() + if self._exp == 'N': + return context._raise_error(InvalidOperation, 'sNaN', self) + if other._exp == 'N': + return context._raise_error(InvalidOperation, 'sNaN', other) + if self._exp == 'n': + product = self + elif other._exp == 'n': + product = other + elif self._exp == 'F': + if not other: + return context._raise_error(InvalidOperation, + 'INF * 0 in fma') + product = _SignedInfinity[self._sign ^ other._sign] + elif other._exp == 'F': + if not self: + return context._raise_error(InvalidOperation, + '0 * INF in fma') + product = _SignedInfinity[self._sign ^ other._sign] + else: + product = _dec_from_triple(self._sign ^ other._sign, + str(int(self._int) * int(other._int)), + self._exp + other._exp) + + return product.__add__(third, context) + + def _power_modulo(self, other, modulo, context=None): + """Three argument version of __pow__""" + + other = _convert_other(other) + if other is NotImplemented: + return other + modulo = _convert_other(modulo) + if modulo is NotImplemented: + return modulo + + if context is None: + context = getcontext() + + # deal with NaNs: if there are any sNaNs then first one wins, + # (i.e. behaviour for NaNs is identical to that of fma) + self_is_nan = self._isnan() + other_is_nan = other._isnan() + modulo_is_nan = modulo._isnan() + if self_is_nan or other_is_nan or modulo_is_nan: + if self_is_nan == 2: + return context._raise_error(InvalidOperation, 'sNaN', + self) + if other_is_nan == 2: + return context._raise_error(InvalidOperation, 'sNaN', + other) + if modulo_is_nan == 2: + return context._raise_error(InvalidOperation, 'sNaN', + modulo) + if self_is_nan: + return self._fix_nan(context) + if other_is_nan: + return other._fix_nan(context) + return modulo._fix_nan(context) + + # check inputs: we apply same restrictions as Python's pow() + if not (self._isinteger() and + other._isinteger() and + modulo._isinteger()): + return context._raise_error(InvalidOperation, + 'pow() 3rd argument not allowed ' + 'unless all arguments are integers') + if other < 0: + return context._raise_error(InvalidOperation, + 'pow() 2nd argument cannot be ' + 'negative when 3rd argument specified') + if not modulo: + return context._raise_error(InvalidOperation, + 'pow() 3rd argument cannot be 0') + + # additional restriction for decimal: the modulus must be less + # than 10**prec in absolute value + if modulo.adjusted() >= context.prec: + return context._raise_error(InvalidOperation, + 'insufficient precision: pow() 3rd ' + 'argument must not have more than ' + 'precision digits') + + # define 0**0 == NaN, for consistency with two-argument pow + # (even though it hurts!) + if not other and not self: + return context._raise_error(InvalidOperation, + 'at least one of pow() 1st argument ' + 'and 2nd argument must be nonzero ;' + '0**0 is not defined') + + # compute sign of result + if other._iseven(): + sign = 0 + else: + sign = self._sign + + # convert modulo to a Python integer, and self and other to + # Decimal integers (i.e. force their exponents to be >= 0) + modulo = abs(int(modulo)) + base = _WorkRep(self.to_integral_value()) + exponent = _WorkRep(other.to_integral_value()) + + # compute result using integer pow() + base = (base.int % modulo * pow(10, base.exp, modulo)) % modulo + for i in range(exponent.exp): + base = pow(base, 10, modulo) + base = pow(base, exponent.int, modulo) + + return _dec_from_triple(sign, str(base), 0) + + def _power_exact(self, other, p): + """Attempt to compute self**other exactly. + + Given Decimals self and other and an integer p, attempt to + compute an exact result for the power self**other, with p + digits of precision. Return None if self**other is not + exactly representable in p digits. + + Assumes that elimination of special cases has already been + performed: self and other must both be nonspecial; self must + be positive and not numerically equal to 1; other must be + nonzero. For efficiency, other._exp should not be too large, + so that 10**abs(other._exp) is a feasible calculation.""" + + # In the comments below, we write x for the value of self and y for the + # value of other. Write x = xc*10**xe and abs(y) = yc*10**ye, with xc + # and yc positive integers not divisible by 10. + + # The main purpose of this method is to identify the *failure* + # of x**y to be exactly representable with as little effort as + # possible. So we look for cheap and easy tests that + # eliminate the possibility of x**y being exact. Only if all + # these tests are passed do we go on to actually compute x**y. + + # Here's the main idea. Express y as a rational number m/n, with m and + # n relatively prime and n>0. Then for x**y to be exactly + # representable (at *any* precision), xc must be the nth power of a + # positive integer and xe must be divisible by n. If y is negative + # then additionally xc must be a power of either 2 or 5, hence a power + # of 2**n or 5**n. + # + # There's a limit to how small |y| can be: if y=m/n as above + # then: + # + # (1) if xc != 1 then for the result to be representable we + # need xc**(1/n) >= 2, and hence also xc**|y| >= 2. So + # if |y| <= 1/nbits(xc) then xc < 2**nbits(xc) <= + # 2**(1/|y|), hence xc**|y| < 2 and the result is not + # representable. + # + # (2) if xe != 0, |xe|*(1/n) >= 1, so |xe|*|y| >= 1. Hence if + # |y| < 1/|xe| then the result is not representable. + # + # Note that since x is not equal to 1, at least one of (1) and + # (2) must apply. Now |y| < 1/nbits(xc) iff |yc|*nbits(xc) < + # 10**-ye iff len(str(|yc|*nbits(xc)) <= -ye. + # + # There's also a limit to how large y can be, at least if it's + # positive: the normalized result will have coefficient xc**y, + # so if it's representable then xc**y < 10**p, and y < + # p/log10(xc). Hence if y*log10(xc) >= p then the result is + # not exactly representable. + + # if len(str(abs(yc*xe)) <= -ye then abs(yc*xe) < 10**-ye, + # so |y| < 1/xe and the result is not representable. + # Similarly, len(str(abs(yc)*xc_bits)) <= -ye implies |y| + # < 1/nbits(xc). + + x = _WorkRep(self) + xc, xe = x.int, x.exp + while xc % 10 == 0: + xc //= 10 + xe += 1 + + y = _WorkRep(other) + yc, ye = y.int, y.exp + while yc % 10 == 0: + yc //= 10 + ye += 1 + + # case where xc == 1: result is 10**(xe*y), with xe*y + # required to be an integer + if xc == 1: + xe *= yc + # result is now 10**(xe * 10**ye); xe * 10**ye must be integral + while xe % 10 == 0: + xe //= 10 + ye += 1 + if ye < 0: + return None + exponent = xe * 10**ye + if y.sign == 1: + exponent = -exponent + # if other is a nonnegative integer, use ideal exponent + if other._isinteger() and other._sign == 0: + ideal_exponent = self._exp*int(other) + zeros = min(exponent-ideal_exponent, p-1) + else: + zeros = 0 + return _dec_from_triple(0, '1' + '0'*zeros, exponent-zeros) + + # case where y is negative: xc must be either a power + # of 2 or a power of 5. + if y.sign == 1: + last_digit = xc % 10 + if last_digit in (2,4,6,8): + # quick test for power of 2 + if xc & -xc != xc: + return None + # now xc is a power of 2; e is its exponent + e = _nbits(xc)-1 + + # We now have: + # + # x = 2**e * 10**xe, e > 0, and y < 0. + # + # The exact result is: + # + # x**y = 5**(-e*y) * 10**(e*y + xe*y) + # + # provided that both e*y and xe*y are integers. Note that if + # 5**(-e*y) >= 10**p, then the result can't be expressed + # exactly with p digits of precision. + # + # Using the above, we can guard against large values of ye. + # 93/65 is an upper bound for log(10)/log(5), so if + # + # ye >= len(str(93*p//65)) + # + # then + # + # -e*y >= -y >= 10**ye > 93*p/65 > p*log(10)/log(5), + # + # so 5**(-e*y) >= 10**p, and the coefficient of the result + # can't be expressed in p digits. + + # emax >= largest e such that 5**e < 10**p. + emax = p*93//65 + if ye >= len(str(emax)): + return None + + # Find -e*y and -xe*y; both must be integers + e = _decimal_lshift_exact(e * yc, ye) + xe = _decimal_lshift_exact(xe * yc, ye) + if e is None or xe is None: + return None + + if e > emax: + return None + xc = 5**e + + elif last_digit == 5: + # e >= log_5(xc) if xc is a power of 5; we have + # equality all the way up to xc=5**2658 + e = _nbits(xc)*28//65 + xc, remainder = divmod(5**e, xc) + if remainder: + return None + while xc % 5 == 0: + xc //= 5 + e -= 1 + + # Guard against large values of ye, using the same logic as in + # the 'xc is a power of 2' branch. 10/3 is an upper bound for + # log(10)/log(2). + emax = p*10//3 + if ye >= len(str(emax)): + return None + + e = _decimal_lshift_exact(e * yc, ye) + xe = _decimal_lshift_exact(xe * yc, ye) + if e is None or xe is None: + return None + + if e > emax: + return None + xc = 2**e + else: + return None + + if xc >= 10**p: + return None + xe = -e-xe + return _dec_from_triple(0, str(xc), xe) + + # now y is positive; find m and n such that y = m/n + if ye >= 0: + m, n = yc*10**ye, 1 + else: + if xe != 0 and len(str(abs(yc*xe))) <= -ye: + return None + xc_bits = _nbits(xc) + if xc != 1 and len(str(abs(yc)*xc_bits)) <= -ye: + return None + m, n = yc, 10**(-ye) + while m % 2 == n % 2 == 0: + m //= 2 + n //= 2 + while m % 5 == n % 5 == 0: + m //= 5 + n //= 5 + + # compute nth root of xc*10**xe + if n > 1: + # if 1 < xc < 2**n then xc isn't an nth power + if xc != 1 and xc_bits <= n: + return None + + xe, rem = divmod(xe, n) + if rem != 0: + return None + + # compute nth root of xc using Newton's method + a = 1 << -(-_nbits(xc)//n) # initial estimate + while True: + q, r = divmod(xc, a**(n-1)) + if a <= q: + break + else: + a = (a*(n-1) + q)//n + if not (a == q and r == 0): + return None + xc = a + + # now xc*10**xe is the nth root of the original xc*10**xe + # compute mth power of xc*10**xe + + # if m > p*100//_log10_lb(xc) then m > p/log10(xc), hence xc**m > + # 10**p and the result is not representable. + if xc > 1 and m > p*100//_log10_lb(xc): + return None + xc = xc**m + xe *= m + if xc > 10**p: + return None + + # by this point the result *is* exactly representable + # adjust the exponent to get as close as possible to the ideal + # exponent, if necessary + str_xc = str(xc) + if other._isinteger() and other._sign == 0: + ideal_exponent = self._exp*int(other) + zeros = min(xe-ideal_exponent, p-len(str_xc)) + else: + zeros = 0 + return _dec_from_triple(0, str_xc+'0'*zeros, xe-zeros) + + def __pow__(self, other, modulo=None, context=None): + """Return self ** other [ % modulo]. + + With two arguments, compute self**other. + + With three arguments, compute (self**other) % modulo. For the + three argument form, the following restrictions on the + arguments hold: + + - all three arguments must be integral + - other must be nonnegative + - either self or other (or both) must be nonzero + - modulo must be nonzero and must have at most p digits, + where p is the context precision. + + If any of these restrictions is violated the InvalidOperation + flag is raised. + + The result of pow(self, other, modulo) is identical to the + result that would be obtained by computing (self**other) % + modulo with unbounded precision, but is computed more + efficiently. It is always exact. + """ + + if modulo is not None: + return self._power_modulo(other, modulo, context) + + other = _convert_other(other) + if other is NotImplemented: + return other + + if context is None: + context = getcontext() + + # either argument is a NaN => result is NaN + ans = self._check_nans(other, context) + if ans: + return ans + + # 0**0 = NaN (!), x**0 = 1 for nonzero x (including +/-Infinity) + if not other: + if not self: + return context._raise_error(InvalidOperation, '0 ** 0') + else: + return _One + + # result has sign 1 iff self._sign is 1 and other is an odd integer + result_sign = 0 + if self._sign == 1: + if other._isinteger(): + if not other._iseven(): + result_sign = 1 + else: + # -ve**noninteger = NaN + # (-0)**noninteger = 0**noninteger + if self: + return context._raise_error(InvalidOperation, + 'x ** y with x negative and y not an integer') + # negate self, without doing any unwanted rounding + self = self.copy_negate() + + # 0**(+ve or Inf)= 0; 0**(-ve or -Inf) = Infinity + if not self: + if other._sign == 0: + return _dec_from_triple(result_sign, '0', 0) + else: + return _SignedInfinity[result_sign] + + # Inf**(+ve or Inf) = Inf; Inf**(-ve or -Inf) = 0 + if self._isinfinity(): + if other._sign == 0: + return _SignedInfinity[result_sign] + else: + return _dec_from_triple(result_sign, '0', 0) + + # 1**other = 1, but the choice of exponent and the flags + # depend on the exponent of self, and on whether other is a + # positive integer, a negative integer, or neither + if self == _One: + if other._isinteger(): + # exp = max(self._exp*max(int(other), 0), + # 1-context.prec) but evaluating int(other) directly + # is dangerous until we know other is small (other + # could be 1e999999999) + if other._sign == 1: + multiplier = 0 + elif other > context.prec: + multiplier = context.prec + else: + multiplier = int(other) + + exp = self._exp * multiplier + if exp < 1-context.prec: + exp = 1-context.prec + context._raise_error(Rounded) + else: + context._raise_error(Inexact) + context._raise_error(Rounded) + exp = 1-context.prec + + return _dec_from_triple(result_sign, '1'+'0'*-exp, exp) + + # compute adjusted exponent of self + self_adj = self.adjusted() + + # self ** infinity is infinity if self > 1, 0 if self < 1 + # self ** -infinity is infinity if self < 1, 0 if self > 1 + if other._isinfinity(): + if (other._sign == 0) == (self_adj < 0): + return _dec_from_triple(result_sign, '0', 0) + else: + return _SignedInfinity[result_sign] + + # from here on, the result always goes through the call + # to _fix at the end of this function. + ans = None + exact = False + + # crude test to catch cases of extreme overflow/underflow. If + # log10(self)*other >= 10**bound and bound >= len(str(Emax)) + # then 10**bound >= 10**len(str(Emax)) >= Emax+1 and hence + # self**other >= 10**(Emax+1), so overflow occurs. The test + # for underflow is similar. + bound = self._log10_exp_bound() + other.adjusted() + if (self_adj >= 0) == (other._sign == 0): + # self > 1 and other +ve, or self < 1 and other -ve + # possibility of overflow + if bound >= len(str(context.Emax)): + ans = _dec_from_triple(result_sign, '1', context.Emax+1) + else: + # self > 1 and other -ve, or self < 1 and other +ve + # possibility of underflow to 0 + Etiny = context.Etiny() + if bound >= len(str(-Etiny)): + ans = _dec_from_triple(result_sign, '1', Etiny-1) + + # try for an exact result with precision +1 + if ans is None: + ans = self._power_exact(other, context.prec + 1) + if ans is not None: + if result_sign == 1: + ans = _dec_from_triple(1, ans._int, ans._exp) + exact = True + + # usual case: inexact result, x**y computed directly as exp(y*log(x)) + if ans is None: + p = context.prec + x = _WorkRep(self) + xc, xe = x.int, x.exp + y = _WorkRep(other) + yc, ye = y.int, y.exp + if y.sign == 1: + yc = -yc + + # compute correctly rounded result: start with precision +3, + # then increase precision until result is unambiguously roundable + extra = 3 + while True: + coeff, exp = _dpower(xc, xe, yc, ye, p+extra) + if coeff % (5*10**(len(str(coeff))-p-1)): + break + extra += 3 + + ans = _dec_from_triple(result_sign, str(coeff), exp) + + # unlike exp, ln and log10, the power function respects the + # rounding mode; no need to switch to ROUND_HALF_EVEN here + + # There's a difficulty here when 'other' is not an integer and + # the result is exact. In this case, the specification + # requires that the Inexact flag be raised (in spite of + # exactness), but since the result is exact _fix won't do this + # for us. (Correspondingly, the Underflow signal should also + # be raised for subnormal results.) We can't directly raise + # these signals either before or after calling _fix, since + # that would violate the precedence for signals. So we wrap + # the ._fix call in a temporary context, and reraise + # afterwards. + if exact and not other._isinteger(): + # pad with zeros up to length context.prec+1 if necessary; this + # ensures that the Rounded signal will be raised. + if len(ans._int) <= context.prec: + expdiff = context.prec + 1 - len(ans._int) + ans = _dec_from_triple(ans._sign, ans._int+'0'*expdiff, + ans._exp-expdiff) + + # create a copy of the current context, with cleared flags/traps + newcontext = context.copy() + newcontext.clear_flags() + for exception in _signals: + newcontext.traps[exception] = 0 + + # round in the new context + ans = ans._fix(newcontext) + + # raise Inexact, and if necessary, Underflow + newcontext._raise_error(Inexact) + if newcontext.flags[Subnormal]: + newcontext._raise_error(Underflow) + + # propagate signals to the original context; _fix could + # have raised any of Overflow, Underflow, Subnormal, + # Inexact, Rounded, Clamped. Overflow needs the correct + # arguments. Note that the order of the exceptions is + # important here. + if newcontext.flags[Overflow]: + context._raise_error(Overflow, 'above Emax', ans._sign) + for exception in Underflow, Subnormal, Inexact, Rounded, Clamped: + if newcontext.flags[exception]: + context._raise_error(exception) + + else: + ans = ans._fix(context) + + return ans + + def __rpow__(self, other, context=None): + """Swaps self/other and returns __pow__.""" + other = _convert_other(other) + if other is NotImplemented: + return other + return other.__pow__(self, context=context) + + def normalize(self, context=None): + """Normalize- strip trailing 0s, change anything equal to 0 to 0e0""" + + if context is None: + context = getcontext() + + if self._is_special: + ans = self._check_nans(context=context) + if ans: + return ans + + dup = self._fix(context) + if dup._isinfinity(): + return dup + + if not dup: + return _dec_from_triple(dup._sign, '0', 0) + 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: + exp += 1 + end -= 1 + return _dec_from_triple(dup._sign, dup._int[:end], exp) + + def quantize(self, exp, rounding=None, context=None): + """Quantize self so its exponent is the same as that of exp. + + Similar to self._rescale(exp._exp) but with error checking. + """ + exp = _convert_other(exp, raiseit=True) + + if context is None: + context = getcontext() + if rounding is None: + rounding = context.rounding + + if self._is_special or exp._is_special: + ans = self._check_nans(exp, context) + if ans: + return ans + + if exp._isinfinity() or self._isinfinity(): + if exp._isinfinity() and self._isinfinity(): + return Decimal(self) # if both are inf, it is OK + return context._raise_error(InvalidOperation, + 'quantize with one INF') + + # exp._exp should be between Etiny and Emax + if not (context.Etiny() <= exp._exp <= context.Emax): + return context._raise_error(InvalidOperation, + 'target exponent out of bounds in quantize') + + if not self: + ans = _dec_from_triple(self._sign, '0', exp._exp) + return ans._fix(context) + + self_adjusted = self.adjusted() + if self_adjusted > context.Emax: + return context._raise_error(InvalidOperation, + 'exponent of quantize result too large for current context') + if self_adjusted - exp._exp + 1 > context.prec: + return context._raise_error(InvalidOperation, + 'quantize result has too many digits for current context') + + ans = self._rescale(exp._exp, rounding) + if ans.adjusted() > context.Emax: + return context._raise_error(InvalidOperation, + 'exponent of quantize result too large for current context') + if len(ans._int) > context.prec: + return context._raise_error(InvalidOperation, + 'quantize result has too many digits for current context') + + # raise appropriate flags + if ans and ans.adjusted() < context.Emin: + context._raise_error(Subnormal) + if ans._exp > self._exp: + if ans != self: + context._raise_error(Inexact) + context._raise_error(Rounded) + + # call to fix takes care of any necessary folddown, and + # signals Clamped if necessary + ans = ans._fix(context) + return ans + + def same_quantum(self, other, context=None): + """Return True if self and other have the same exponent; otherwise + return False. + + If either operand is a special value, the following rules are used: + * return True if both operands are infinities + * return True if both operands are NaNs + * otherwise, return False. + """ + other = _convert_other(other, raiseit=True) + if self._is_special or other._is_special: + return (self.is_nan() and other.is_nan() or + self.is_infinite() and other.is_infinite()) + return self._exp == other._exp + + def _rescale(self, exp, rounding): + """Rescale self so that the exponent is exp, either by padding with zeros + or by truncating digits, using the given rounding mode. + + Specials are returned without change. This operation is + quiet: it raises no flags, and uses no information from the + context. + + exp = exp to scale to (an integer) + rounding = rounding mode + """ + if self._is_special: + return Decimal(self) + if not self: + return _dec_from_triple(self._sign, '0', exp) + + if self._exp >= exp: + # pad answer with zeros if necessary + return _dec_from_triple(self._sign, + self._int + '0'*(self._exp - exp), exp) + + # too many digits; round and lose data. If self.adjusted() < + # exp-1, replace self by 10**(exp-1) before rounding + digits = len(self._int) + self._exp - exp + if digits < 0: + self = _dec_from_triple(self._sign, '1', exp-1) + digits = 0 + this_function = self._pick_rounding_function[rounding] + changed = this_function(self, digits) + coeff = self._int[:digits] or '0' + if changed == 1: + coeff = str(int(coeff)+1) + return _dec_from_triple(self._sign, coeff, exp) + + def _round(self, places, rounding): + """Round a nonzero, nonspecial Decimal to a fixed number of + significant figures, using the given rounding mode. + + Infinities, NaNs and zeros are returned unaltered. + + This operation is quiet: it raises no flags, and uses no + information from the context. + + """ + if places <= 0: + raise ValueError("argument should be at least 1 in _round") + if self._is_special or not self: + return Decimal(self) + ans = self._rescale(self.adjusted()+1-places, rounding) + # it can happen that the rescale alters the adjusted exponent; + # for example when rounding 99.97 to 3 significant figures. + # When this happens we end up with an extra 0 at the end of + # the number; a second rescale fixes this. + if ans.adjusted() != self.adjusted(): + ans = ans._rescale(ans.adjusted()+1-places, rounding) + return ans + + def to_integral_exact(self, rounding=None, context=None): + """Rounds to a nearby integer. + + If no rounding mode is specified, take the rounding mode from + the context. This method raises the Rounded and Inexact flags + when appropriate. + + See also: to_integral_value, which does exactly the same as + this method except that it doesn't raise Inexact or Rounded. + """ + if self._is_special: + ans = self._check_nans(context=context) + if ans: + return ans + return Decimal(self) + if self._exp >= 0: + return Decimal(self) + if not self: + return _dec_from_triple(self._sign, '0', 0) + if context is None: + context = getcontext() + if rounding is None: + rounding = context.rounding + ans = self._rescale(0, rounding) + if ans != self: + context._raise_error(Inexact) + context._raise_error(Rounded) + return ans + + def to_integral_value(self, rounding=None, context=None): + """Rounds to the nearest integer, without raising inexact, rounded.""" + if context is None: + context = getcontext() + if rounding is None: + rounding = context.rounding + if self._is_special: + ans = self._check_nans(context=context) + if ans: + return ans + return Decimal(self) + if self._exp >= 0: + return Decimal(self) + else: + return self._rescale(0, rounding) + + # the method name changed, but we provide also the old one, for compatibility + to_integral = to_integral_value + + def sqrt(self, context=None): + """Return the square root of self.""" + if context is None: + context = getcontext() + + if self._is_special: + ans = self._check_nans(context=context) + if ans: + return ans + + if self._isinfinity() and self._sign == 0: + return Decimal(self) + + if not self: + # exponent = self._exp // 2. sqrt(-0) = -0 + ans = _dec_from_triple(self._sign, '0', self._exp // 2) + return ans._fix(context) + + if self._sign == 1: + return context._raise_error(InvalidOperation, 'sqrt(-x), x > 0') + + # At this point self represents a positive number. Let p be + # the desired precision and express self in the form c*100**e + # with c a positive real number and e an integer, c and e + # being chosen so that 100**(p-1) <= c < 100**p. Then the + # (exact) square root of self is sqrt(c)*10**e, and 10**(p-1) + # <= sqrt(c) < 10**p, so the closest representable Decimal at + # precision p is n*10**e where n = round_half_even(sqrt(c)), + # the closest integer to sqrt(c) with the even integer chosen + # in the case of a tie. + # + # To ensure correct rounding in all cases, we use the + # following trick: we compute the square root to an extra + # place (precision p+1 instead of precision p), rounding down. + # Then, if the result is inexact and its last digit is 0 or 5, + # we increase the last digit to 1 or 6 respectively; if it's + # exact we leave the last digit alone. Now the final round to + # p places (or fewer in the case of underflow) will round + # correctly and raise the appropriate flags. + + # use an extra digit of precision + prec = context.prec+1 + + # write argument in the form c*100**e where e = self._exp//2 + # is the 'ideal' exponent, to be used if the square root is + # exactly representable. l is the number of 'digits' of c in + # base 100, so that 100**(l-1) <= c < 100**l. + op = _WorkRep(self) + e = op.exp >> 1 + if op.exp & 1: + c = op.int * 10 + l = (len(self._int) >> 1) + 1 + else: + c = op.int + l = len(self._int)+1 >> 1 + + # rescale so that c has exactly prec base 100 'digits' + shift = prec-l + if shift >= 0: + c *= 100**shift + exact = True + else: + c, remainder = divmod(c, 100**-shift) + exact = not remainder + e -= shift + + # find n = floor(sqrt(c)) using Newton's method + n = 10**prec + while True: + q = c//n + if n <= q: + break + else: + n = n + q >> 1 + exact = exact and n*n == c + + if exact: + # result is exact; rescale to use ideal exponent e + if shift >= 0: + # assert n % 10**shift == 0 + n //= 10**shift + else: + n *= 10**-shift + e += shift + else: + # result is not exact; fix last digit as described above + if n % 5 == 0: + n += 1 + + ans = _dec_from_triple(0, str(n), e) + + # round, and fit to current context + context = context._shallow_copy() + rounding = context._set_rounding(ROUND_HALF_EVEN) + ans = ans._fix(context) + context.rounding = rounding + + return ans + + def max(self, other, context=None): + """Returns the larger value. + + Like max(self, other) except if one is not a number, returns + NaN (and signals if one is sNaN). Also rounds. + """ + other = _convert_other(other, raiseit=True) + + if context is None: + context = getcontext() + + if self._is_special or other._is_special: + # If one operand is a quiet NaN and the other is number, then the + # number is always returned + sn = self._isnan() + on = other._isnan() + if sn or on: + if on == 1 and sn == 0: + return self._fix(context) + if sn == 1 and on == 0: + return other._fix(context) + return self._check_nans(other, context) + + c = self._cmp(other) + if c == 0: + # If both operands are finite and equal in numerical value + # then an ordering is applied: + # + # If the signs differ then max returns the operand with the + # positive sign and min returns the operand with the negative sign + # + # If the signs are the same then the exponent is used to select + # the result. This is exactly the ordering used in compare_total. + c = self.compare_total(other) + + if c == -1: + ans = other + else: + ans = self + + return ans._fix(context) + + def min(self, other, context=None): + """Returns the smaller value. + + Like min(self, other) except if one is not a number, returns + NaN (and signals if one is sNaN). Also rounds. + """ + other = _convert_other(other, raiseit=True) + + if context is None: + context = getcontext() + + if self._is_special or other._is_special: + # If one operand is a quiet NaN and the other is number, then the + # number is always returned + sn = self._isnan() + on = other._isnan() + if sn or on: + if on == 1 and sn == 0: + return self._fix(context) + if sn == 1 and on == 0: + return other._fix(context) + return self._check_nans(other, context) + + c = self._cmp(other) + if c == 0: + c = self.compare_total(other) + + if c == -1: + ans = self + else: + ans = other + + return ans._fix(context) + + def _isinteger(self): + """Returns whether self is an integer""" + if self._is_special: + return False + if self._exp >= 0: + return True + rest = self._int[self._exp:] + return rest == '0'*len(rest) + + def _iseven(self): + """Returns True if self is even. Assumes self is an integer.""" + if not self or self._exp > 0: + return True + return self._int[-1+self._exp] in '02468' + + def adjusted(self): + """Return the adjusted exponent of self""" + try: + return self._exp + len(self._int) - 1 + # If NaN or Infinity, self._exp is string + except TypeError: + return 0 + + def canonical(self): + """Returns the same Decimal object. + + As we do not have different encodings for the same number, the + received object already is in its canonical form. + """ + return self + + def compare_signal(self, other, context=None): + """Compares self to the other operand numerically. + + It's pretty much like compare(), but all NaNs signal, with signaling + NaNs taking precedence over quiet NaNs. + """ + other = _convert_other(other, raiseit = True) + ans = self._compare_check_nans(other, context) + if ans: + return ans + return self.compare(other, context=context) + + def compare_total(self, other, context=None): + """Compares self to other using the abstract representations. + + This is not like the standard compare, which use their numerical + value. Note that a total ordering is defined for all possible abstract + representations. + """ + other = _convert_other(other, raiseit=True) + + # if one is negative and the other is positive, it's easy + if self._sign and not other._sign: + return _NegativeOne + if not self._sign and other._sign: + return _One + sign = self._sign + + # let's handle both NaN types + self_nan = self._isnan() + other_nan = other._isnan() + if self_nan or other_nan: + if self_nan == other_nan: + # compare payloads as though they're integers + self_key = len(self._int), self._int + other_key = len(other._int), other._int + if self_key < other_key: + if sign: + return _One + else: + return _NegativeOne + if self_key > other_key: + if sign: + return _NegativeOne + else: + return _One + return _Zero + + if sign: + if self_nan == 1: + return _NegativeOne + if other_nan == 1: + return _One + if self_nan == 2: + return _NegativeOne + if other_nan == 2: + return _One + else: + if self_nan == 1: + return _One + if other_nan == 1: + return _NegativeOne + if self_nan == 2: + return _One + if other_nan == 2: + return _NegativeOne + + if self < other: + return _NegativeOne + if self > other: + return _One + + if self._exp < other._exp: + if sign: + return _One + else: + return _NegativeOne + if self._exp > other._exp: + if sign: + return _NegativeOne + else: + return _One + return _Zero + + + def compare_total_mag(self, other, context=None): + """Compares self to other using abstract repr., ignoring sign. + + Like compare_total, but with operand's sign ignored and assumed to be 0. + """ + other = _convert_other(other, raiseit=True) + + s = self.copy_abs() + o = other.copy_abs() + return s.compare_total(o) + + def copy_abs(self): + """Returns a copy with the sign set to 0. """ + return _dec_from_triple(0, self._int, self._exp, self._is_special) + + def copy_negate(self): + """Returns a copy with the sign inverted.""" + if self._sign: + return _dec_from_triple(0, self._int, self._exp, self._is_special) + else: + return _dec_from_triple(1, self._int, self._exp, self._is_special) + + def copy_sign(self, other, context=None): + """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) + + def exp(self, context=None): + """Returns e ** self.""" + + if context is None: + context = getcontext() + + # exp(NaN) = NaN + ans = self._check_nans(context=context) + if ans: + return ans + + # exp(-Infinity) = 0 + if self._isinfinity() == -1: + return _Zero + + # exp(0) = 1 + if not self: + return _One + + # exp(Infinity) = Infinity + if self._isinfinity() == 1: + return Decimal(self) + + # the result is now guaranteed to be inexact (the true + # mathematical result is transcendental). There's no need to + # raise Rounded and Inexact here---they'll always be raised as + # a result of the call to _fix. + p = context.prec + adj = self.adjusted() + + # we only need to do any computation for quite a small range + # of adjusted exponents---for example, -29 <= adj <= 10 for + # the default context. For smaller exponent the result is + # indistinguishable from 1 at the given precision, while for + # larger exponent the result either overflows or underflows. + if self._sign == 0 and adj > len(str((context.Emax+1)*3)): + # overflow + ans = _dec_from_triple(0, '1', context.Emax+1) + elif self._sign == 1 and adj > len(str((-context.Etiny()+1)*3)): + # underflow to 0 + ans = _dec_from_triple(0, '1', context.Etiny()-1) + elif self._sign == 0 and adj < -p: + # p+1 digits; final round will raise correct flags + ans = _dec_from_triple(0, '1' + '0'*(p-1) + '1', -p) + elif self._sign == 1 and adj < -p-1: + # p+1 digits; final round will raise correct flags + ans = _dec_from_triple(0, '9'*(p+1), -p-1) + # general case + else: + op = _WorkRep(self) + c, e = op.int, op.exp + if op.sign == 1: + c = -c + + # compute correctly rounded result: increase precision by + # 3 digits at a time until we get an unambiguously + # roundable result + extra = 3 + while True: + coeff, exp = _dexp(c, e, p+extra) + if coeff % (5*10**(len(str(coeff))-p-1)): + break + extra += 3 + + ans = _dec_from_triple(0, str(coeff), exp) + + # at this stage, ans should round correctly with *any* + # rounding mode, not just with ROUND_HALF_EVEN + context = context._shallow_copy() + rounding = context._set_rounding(ROUND_HALF_EVEN) + ans = ans._fix(context) + context.rounding = rounding + + return ans + + def is_canonical(self): + """Return True if self is canonical; otherwise return False. + + Currently, the encoding of a Decimal instance is always + canonical, so this method returns True for any Decimal. + """ + return True + + def is_finite(self): + """Return True if self is finite; otherwise return False. + + A Decimal instance is considered finite if it is neither + infinite nor a NaN. + """ + return not self._is_special + + def is_infinite(self): + """Return True if self is infinite; otherwise return False.""" + return self._exp == 'F' + + def is_nan(self): + """Return True if self is a qNaN or sNaN; otherwise return False.""" + return self._exp in ('n', 'N') + + def is_normal(self, context=None): + """Return True if self is a normal number; otherwise return False.""" + if self._is_special or not self: + return False + if context is None: + context = getcontext() + return context.Emin <= self.adjusted() + + def is_qnan(self): + """Return True if self is a quiet NaN; otherwise return False.""" + return self._exp == 'n' + + def is_signed(self): + """Return True if self is negative; otherwise return False.""" + return self._sign == 1 + + def is_snan(self): + """Return True if self is a signaling NaN; otherwise return False.""" + return self._exp == 'N' + + def is_subnormal(self, context=None): + """Return True if self is subnormal; otherwise return False.""" + if self._is_special or not self: + return False + if context is None: + context = getcontext() + return self.adjusted() < context.Emin + + def is_zero(self): + """Return True if self is a zero; otherwise return False.""" + return not self._is_special and self._int == '0' + + def _ln_exp_bound(self): + """Compute a lower bound for the adjusted exponent of self.ln(). + In other words, compute r such that self.ln() >= 10**r. Assumes + that self is finite and positive and that self != 1. + """ + + # for 0.1 <= x <= 10 we use the inequalities 1-1/x <= ln(x) <= x-1 + adj = self._exp + len(self._int) - 1 + if adj >= 1: + # argument >= 10; we use 23/10 = 2.3 as a lower bound for ln(10) + return len(str(adj*23//10)) - 1 + if adj <= -2: + # argument <= 0.1 + return len(str((-1-adj)*23//10)) - 1 + op = _WorkRep(self) + c, e = op.int, op.exp + if adj == 0: + # 1 < self < 10 + num = str(c-10**-e) + den = str(c) + return len(num) - len(den) - (num < den) + # adj == -1, 0.1 <= self < 1 + return e + len(str(10**-e - c)) - 1 + + + def ln(self, context=None): + """Returns the natural (base e) logarithm of self.""" + + if context is None: + context = getcontext() + + # ln(NaN) = NaN + ans = self._check_nans(context=context) + if ans: + return ans + + # ln(0.0) == -Infinity + if not self: + return _NegativeInfinity + + # ln(Infinity) = Infinity + if self._isinfinity() == 1: + return _Infinity + + # ln(1.0) == 0.0 + if self == _One: + return _Zero + + # ln(negative) raises InvalidOperation + if self._sign == 1: + return context._raise_error(InvalidOperation, + 'ln of a negative value') + + # result is irrational, so necessarily inexact + op = _WorkRep(self) + c, e = op.int, op.exp + p = context.prec + + # correctly rounded result: repeatedly increase precision by 3 + # until we get an unambiguously roundable result + places = p - self._ln_exp_bound() + 2 # at least p+3 places + while True: + coeff = _dlog(c, e, places) + # assert len(str(abs(coeff)))-p >= 1 + if coeff % (5*10**(len(str(abs(coeff)))-p-1)): + break + places += 3 + ans = _dec_from_triple(int(coeff<0), str(abs(coeff)), -places) + + context = context._shallow_copy() + rounding = context._set_rounding(ROUND_HALF_EVEN) + ans = ans._fix(context) + context.rounding = rounding + return ans + + def _log10_exp_bound(self): + """Compute a lower bound for the adjusted exponent of self.log10(). + In other words, find r such that self.log10() >= 10**r. + Assumes that self is finite and positive and that self != 1. + """ + + # For x >= 10 or x < 0.1 we only need a bound on the integer + # part of log10(self), and this comes directly from the + # exponent of x. For 0.1 <= x <= 10 we use the inequalities + # 1-1/x <= log(x) <= x-1. If x > 1 we have |log10(x)| > + # (1-1/x)/2.31 > 0. If x < 1 then |log10(x)| > (1-x)/2.31 > 0 + + adj = self._exp + len(self._int) - 1 + if adj >= 1: + # self >= 10 + return len(str(adj))-1 + if adj <= -2: + # self < 0.1 + return len(str(-1-adj))-1 + op = _WorkRep(self) + c, e = op.int, op.exp + if adj == 0: + # 1 < self < 10 + num = str(c-10**-e) + den = str(231*c) + return len(num) - len(den) - (num < den) + 2 + # adj == -1, 0.1 <= self < 1 + num = str(10**-e-c) + return len(num) + e - (num < "231") - 1 + + def log10(self, context=None): + """Returns the base 10 logarithm of self.""" + + if context is None: + context = getcontext() + + # log10(NaN) = NaN + ans = self._check_nans(context=context) + if ans: + return ans + + # log10(0.0) == -Infinity + if not self: + return _NegativeInfinity + + # log10(Infinity) = Infinity + if self._isinfinity() == 1: + return _Infinity + + # log10(negative or -Infinity) raises InvalidOperation + if self._sign == 1: + return context._raise_error(InvalidOperation, + 'log10 of a negative value') + + # log10(10**n) = n + if self._int[0] == '1' and self._int[1:] == '0'*(len(self._int) - 1): + # answer may need rounding + ans = Decimal(self._exp + len(self._int) - 1) + else: + # result is irrational, so necessarily inexact + op = _WorkRep(self) + c, e = op.int, op.exp + p = context.prec + + # correctly rounded result: repeatedly increase precision + # until result is unambiguously roundable + places = p-self._log10_exp_bound()+2 + while True: + coeff = _dlog10(c, e, places) + # assert len(str(abs(coeff)))-p >= 1 + if coeff % (5*10**(len(str(abs(coeff)))-p-1)): + break + places += 3 + ans = _dec_from_triple(int(coeff<0), str(abs(coeff)), -places) + + context = context._shallow_copy() + rounding = context._set_rounding(ROUND_HALF_EVEN) + ans = ans._fix(context) + context.rounding = rounding + return ans + + def logb(self, context=None): + """ Returns the exponent of the magnitude of self's MSD. + + The result is the integer which is the exponent of the magnitude + of the most significant digit of self (as though it were truncated + to a single digit while maintaining the value of that digit and + without limiting the resulting exponent). + """ + # logb(NaN) = NaN + ans = self._check_nans(context=context) + if ans: + return ans + + if context is None: + context = getcontext() + + # logb(+/-Inf) = +Inf + if self._isinfinity(): + return _Infinity + + # logb(0) = -Inf, DivisionByZero + if not self: + return context._raise_error(DivisionByZero, 'logb(0)', 1) + + # otherwise, simply return the adjusted exponent of self, as a + # Decimal. Note that no attempt is made to fit the result + # into the current context. + ans = Decimal(self.adjusted()) + return ans._fix(context) + + def _islogical(self): + """Return True if self is a logical operand. + + For being logical, it must be a finite number with a sign of 0, + an exponent of 0, and a coefficient whose digits must all be + either 0 or 1. + """ + if self._sign != 0 or self._exp != 0: + return False + for dig in self._int: + if dig not in '01': + return False + return True + + def _fill_logical(self, context, opa, opb): + dif = context.prec - len(opa) + if dif > 0: + opa = '0'*dif + opa + elif dif < 0: + opa = opa[-context.prec:] + dif = context.prec - len(opb) + if dif > 0: + opb = '0'*dif + opb + elif dif < 0: + opb = opb[-context.prec:] + return opa, opb + + def logical_and(self, other, context=None): + """Applies an 'and' operation between self and other's digits.""" + if context is None: + context = getcontext() + + other = _convert_other(other, raiseit=True) + + if not self._islogical() or not other._islogical(): + return context._raise_error(InvalidOperation) + + # fill to context.prec + (opa, opb) = self._fill_logical(context, self._int, other._int) + + # make the operation, and clean starting zeroes + result = "".join([str(int(a)&int(b)) for a,b in zip(opa,opb)]) + return _dec_from_triple(0, result.lstrip('0') or '0', 0) + + def logical_invert(self, context=None): + """Invert all its digits.""" + if context is None: + context = getcontext() + return self.logical_xor(_dec_from_triple(0,'1'*context.prec,0), + context) + + def logical_or(self, other, context=None): + """Applies an 'or' operation between self and other's digits.""" + if context is None: + context = getcontext() + + other = _convert_other(other, raiseit=True) + + if not self._islogical() or not other._islogical(): + return context._raise_error(InvalidOperation) + + # fill to context.prec + (opa, opb) = self._fill_logical(context, self._int, other._int) + + # make the operation, and clean starting zeroes + result = "".join([str(int(a)|int(b)) for a,b in zip(opa,opb)]) + return _dec_from_triple(0, result.lstrip('0') or '0', 0) + + def logical_xor(self, other, context=None): + """Applies an 'xor' operation between self and other's digits.""" + if context is None: + context = getcontext() + + other = _convert_other(other, raiseit=True) + + if not self._islogical() or not other._islogical(): + return context._raise_error(InvalidOperation) + + # fill to context.prec + (opa, opb) = self._fill_logical(context, self._int, other._int) + + # make the operation, and clean starting zeroes + result = "".join([str(int(a)^int(b)) for a,b in zip(opa,opb)]) + return _dec_from_triple(0, result.lstrip('0') or '0', 0) + + def max_mag(self, other, context=None): + """Compares the values numerically with their sign ignored.""" + other = _convert_other(other, raiseit=True) + + if context is None: + context = getcontext() + + if self._is_special or other._is_special: + # If one operand is a quiet NaN and the other is number, then the + # number is always returned + sn = self._isnan() + on = other._isnan() + if sn or on: + if on == 1 and sn == 0: + return self._fix(context) + if sn == 1 and on == 0: + return other._fix(context) + return self._check_nans(other, context) + + c = self.copy_abs()._cmp(other.copy_abs()) + if c == 0: + c = self.compare_total(other) + + if c == -1: + ans = other + else: + ans = self + + return ans._fix(context) + + def min_mag(self, other, context=None): + """Compares the values numerically with their sign ignored.""" + other = _convert_other(other, raiseit=True) + + if context is None: + context = getcontext() + + if self._is_special or other._is_special: + # If one operand is a quiet NaN and the other is number, then the + # number is always returned + sn = self._isnan() + on = other._isnan() + if sn or on: + if on == 1 and sn == 0: + return self._fix(context) + if sn == 1 and on == 0: + return other._fix(context) + return self._check_nans(other, context) + + c = self.copy_abs()._cmp(other.copy_abs()) + if c == 0: + c = self.compare_total(other) + + if c == -1: + ans = self + else: + ans = other + + return ans._fix(context) + + def next_minus(self, context=None): + """Returns the largest representable number smaller than itself.""" + if context is None: + context = getcontext() + + ans = self._check_nans(context=context) + if ans: + return ans + + if self._isinfinity() == -1: + return _NegativeInfinity + if self._isinfinity() == 1: + return _dec_from_triple(0, '9'*context.prec, context.Etop()) + + context = context.copy() + context._set_rounding(ROUND_FLOOR) + context._ignore_all_flags() + new_self = self._fix(context) + if new_self != self: + return new_self + return self.__sub__(_dec_from_triple(0, '1', context.Etiny()-1), + context) + + def next_plus(self, context=None): + """Returns the smallest representable number larger than itself.""" + if context is None: + context = getcontext() + + ans = self._check_nans(context=context) + if ans: + return ans + + if self._isinfinity() == 1: + return _Infinity + if self._isinfinity() == -1: + return _dec_from_triple(1, '9'*context.prec, context.Etop()) + + context = context.copy() + context._set_rounding(ROUND_CEILING) + context._ignore_all_flags() + new_self = self._fix(context) + if new_self != self: + return new_self + return self.__add__(_dec_from_triple(0, '1', context.Etiny()-1), + context) + + def next_toward(self, other, context=None): + """Returns the number closest to self, in the direction towards other. + + The result is the closest representable number to self + (excluding self) that is in the direction towards other, + unless both have the same value. If the two operands are + numerically equal, then the result is a copy of self with the + sign set to be the same as the sign of other. + """ + other = _convert_other(other, raiseit=True) + + if context is None: + context = getcontext() + + ans = self._check_nans(other, context) + if ans: + return ans + + comparison = self._cmp(other) + if comparison == 0: + return self.copy_sign(other) + + if comparison == -1: + ans = self.next_plus(context) + else: # comparison == 1 + ans = self.next_minus(context) + + # decide which flags to raise using value of ans + if ans._isinfinity(): + context._raise_error(Overflow, + 'Infinite result from next_toward', + ans._sign) + context._raise_error(Inexact) + context._raise_error(Rounded) + elif ans.adjusted() < context.Emin: + context._raise_error(Underflow) + context._raise_error(Subnormal) + context._raise_error(Inexact) + context._raise_error(Rounded) + # if precision == 1 then we don't raise Clamped for a + # result 0E-Etiny. + if not ans: + context._raise_error(Clamped) + + return ans + + def number_class(self, context=None): + """Returns an indication of the class of self. + + The class is one of the following strings: + sNaN + NaN + -Infinity + -Normal + -Subnormal + -Zero + +Zero + +Subnormal + +Normal + +Infinity + """ + if self.is_snan(): + return "sNaN" + if self.is_qnan(): + return "NaN" + inf = self._isinfinity() + if inf == 1: + return "+Infinity" + if inf == -1: + return "-Infinity" + if self.is_zero(): + if self._sign: + return "-Zero" + else: + return "+Zero" + if context is None: + context = getcontext() + if self.is_subnormal(context=context): + if self._sign: + return "-Subnormal" + else: + return "+Subnormal" + # just a normal, regular, boring number, :) + if self._sign: + return "-Normal" + else: + return "+Normal" + + def radix(self): + """Just returns 10, as this is Decimal, :)""" + return Decimal(10) + + def rotate(self, other, context=None): + """Returns a rotated copy of self, value-of-other times.""" + if context is None: + context = getcontext() + + other = _convert_other(other, raiseit=True) + + ans = self._check_nans(other, context) + if ans: + return ans + + if other._exp != 0: + return context._raise_error(InvalidOperation) + if not (-context.prec <= int(other) <= context.prec): + return context._raise_error(InvalidOperation) + + if self._isinfinity(): + return Decimal(self) + + # get values, pad if necessary + torot = int(other) + rotdig = self._int + topad = context.prec - len(rotdig) + if topad > 0: + rotdig = '0'*topad + rotdig + elif topad < 0: + rotdig = rotdig[-topad:] + + # let's rotate! + rotated = rotdig[torot:] + rotdig[:torot] + return _dec_from_triple(self._sign, + rotated.lstrip('0') or '0', self._exp) + + def scaleb(self, other, context=None): + """Returns self operand after adding the second value to its exp.""" + if context is None: + context = getcontext() + + other = _convert_other(other, raiseit=True) + + ans = self._check_nans(other, context) + if ans: + return ans + + if other._exp != 0: + return context._raise_error(InvalidOperation) + liminf = -2 * (context.Emax + context.prec) + limsup = 2 * (context.Emax + context.prec) + if not (liminf <= int(other) <= limsup): + return context._raise_error(InvalidOperation) + + if self._isinfinity(): + return Decimal(self) + + d = _dec_from_triple(self._sign, self._int, self._exp + int(other)) + d = d._fix(context) + return d + + def shift(self, other, context=None): + """Returns a shifted copy of self, value-of-other times.""" + if context is None: + context = getcontext() + + other = _convert_other(other, raiseit=True) + + ans = self._check_nans(other, context) + if ans: + return ans + + if other._exp != 0: + return context._raise_error(InvalidOperation) + if not (-context.prec <= int(other) <= context.prec): + return context._raise_error(InvalidOperation) + + if self._isinfinity(): + return Decimal(self) + + # get values, pad if necessary + torot = int(other) + rotdig = self._int + topad = context.prec - len(rotdig) + if topad > 0: + rotdig = '0'*topad + rotdig + elif topad < 0: + rotdig = rotdig[-topad:] + + # let's shift! + if torot < 0: + shifted = rotdig[:torot] + else: + shifted = rotdig + '0'*torot + shifted = shifted[-context.prec:] + + return _dec_from_triple(self._sign, + shifted.lstrip('0') or '0', self._exp) + + # Support for pickling, copy, and deepcopy + def __reduce__(self): + return (self.__class__, (str(self),)) + + def __copy__(self): + 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) is Decimal: + return self # My components are also immutable + return self.__class__(str(self)) + + # PEP 3101 support. the _localeconv keyword argument should be + # considered private: it's provided for ease of testing only. + def __format__(self, specifier, context=None, _localeconv=None): + """Format a Decimal instance according to the given specifier. + + The specifier should be a standard format specifier, with the + form described in PEP 3101. Formatting types 'e', 'E', 'f', + 'F', 'g', 'G', 'n' and '%' are supported. If the formatting + type is omitted it defaults to 'g' or 'G', depending on the + value of context.capitals. + """ + + # Note: PEP 3101 says that if the type is not present then + # there should be at least one digit after the decimal point. + # We take the liberty of ignoring this requirement for + # Decimal---it's presumably there to make sure that + # format(float, '') behaves similarly to str(float). + if context is None: + context = getcontext() + + spec = _parse_format_specifier(specifier, _localeconv=_localeconv) + + # special values don't care about the type or precision + if self._is_special: + sign = _format_sign(self._sign, spec) + body = str(self.copy_abs()) + if spec['type'] == '%': + body += '%' + return _format_align(sign, body, spec) + + # a type of None defaults to 'g' or 'G', depending on context + if spec['type'] is None: + spec['type'] = ['g', 'G'][context.capitals] + + # if type is '%', adjust exponent of self accordingly + if spec['type'] == '%': + self = _dec_from_triple(self._sign, self._int, self._exp+2) + + # round if necessary, taking rounding mode from the context + rounding = context.rounding + precision = spec['precision'] + if precision is not None: + if spec['type'] in 'eE': + self = self._round(precision+1, rounding) + elif spec['type'] in 'fF%': + self = self._rescale(-precision, rounding) + elif spec['type'] in 'gG' and len(self._int) > precision: + self = self._round(precision, rounding) + # special case: zeros with a positive exponent can't be + # represented in fixed point; rescale them to 0e0. + if not self and self._exp > 0 and spec['type'] in 'fF%': + self = self._rescale(0, rounding) + + # figure out placement of the decimal point + leftdigits = self._exp + len(self._int) + if spec['type'] in 'eE': + if not self and precision is not None: + dotplace = 1 - precision + else: + dotplace = 1 + elif spec['type'] in 'fF%': + dotplace = leftdigits + elif spec['type'] in 'gG': + if self._exp <= 0 and leftdigits > -6: + dotplace = leftdigits + else: + dotplace = 1 + + # find digits before and after decimal point, and get exponent + if dotplace < 0: + intpart = '0' + fracpart = '0'*(-dotplace) + self._int + elif dotplace > len(self._int): + intpart = self._int + '0'*(dotplace-len(self._int)) + fracpart = '' + else: + intpart = self._int[:dotplace] or '0' + fracpart = self._int[dotplace:] + exp = leftdigits-dotplace + + # done with the decimal-specific stuff; hand over the rest + # of the formatting to the _format_number function + return _format_number(self._sign, intpart, fracpart, exp, spec) + +def _dec_from_triple(sign, coefficient, exponent, special=False): + """Create a decimal instance directly, without any validation, + normalization (e.g. removal of leading zeros) or argument + conversion. + + This function is for *internal use only*. + """ + + self = object.__new__(Decimal) + self._sign = sign + self._int = coefficient + self._exp = exponent + self._is_special = special + + return self + +# Register Decimal as a kind of Number (an abstract base class). +# However, do not register it as Real (because Decimals are not +# interoperable with floats). +_numbers.Number.register(Decimal) + + +##### Context class ####################################################### + +class _ContextManager(object): + """Context manager class to support localcontext(). + + Sets a copy of the supplied context in __enter__() and restores + the previous decimal context in __exit__() + """ + def __init__(self, new_context): + self.new_context = new_context.copy() + def __enter__(self): + self.saved_context = getcontext() + setcontext(self.new_context) + return self.new_context + def __exit__(self, t, v, tb): + setcontext(self.saved_context) + +class Context(object): + """Contains the context for a Decimal instance. + + Contains: + prec - precision (for use in rounding, division, square roots..) + rounding - rounding type (how you round) + traps - If traps[exception] = 1, then the exception is + raised when it is caused. Otherwise, a value is + substituted in. + flags - When an exception is caused, flags[exception] is set. + (Whether or not the trap_enabler is set) + Should be reset by user of Decimal instance. + Emin - Minimum exponent + 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) + """ + + def __init__(self, prec=None, rounding=None, Emin=None, Emax=None, + capitals=None, clamp=None, flags=None, traps=None, + _ignored_flags=None): + # Set defaults; for everything except flags and _ignored_flags, + # inherit from DefaultContext. + try: + dc = DefaultContext + except NameError: + pass + + self.prec = prec if prec is not None else dc.prec + self.rounding = rounding if rounding is not None else dc.rounding + 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 + + if _ignored_flags is None: + self._ignored_flags = [] + else: + self._ignored_flags = _ignored_flags + + if traps is None: + self.traps = dc.traps.copy() + elif not isinstance(traps, dict): + self.traps = dict((s, int(s in traps)) for s in _signals + traps) + else: + self.traps = traps + + if flags is None: + self.flags = dict.fromkeys(_signals, 0) + elif not isinstance(flags, dict): + self.flags = dict((s, int(s in flags)) for s in _signals + flags) + else: + self.flags = flags + + def _set_integer_check(self, name, value, vmin, vmax): + if not isinstance(value, int): + raise TypeError("%s must be an integer" % name) + if vmin == '-inf': + if value > vmax: + raise ValueError("%s must be in [%s, %d]. got: %s" % (name, vmin, vmax, value)) + elif vmax == 'inf': + if value < vmin: + raise ValueError("%s must be in [%d, %s]. got: %s" % (name, vmin, vmax, value)) + else: + if value < vmin or value > vmax: + raise ValueError("%s must be in [%d, %d]. got %s" % (name, vmin, vmax, value)) + return object.__setattr__(self, name, value) + + def _set_signal_dict(self, name, d): + if not isinstance(d, dict): + raise TypeError("%s must be a signal dict" % d) + for key in d: + if not key in _signals: + raise KeyError("%s is not a valid signal dict" % d) + for key in _signals: + if not key in d: + raise KeyError("%s is not a valid signal dict" % d) + return object.__setattr__(self, name, d) + + def __setattr__(self, name, value): + if name == 'prec': + return self._set_integer_check(name, value, 1, 'inf') + elif name == 'Emin': + return self._set_integer_check(name, value, '-inf', 0) + elif name == 'Emax': + return self._set_integer_check(name, value, 0, 'inf') + elif name == 'capitals': + return self._set_integer_check(name, value, 0, 1) + elif name == 'clamp': + return self._set_integer_check(name, value, 0, 1) + elif name == 'rounding': + if not value in _rounding_modes: + # raise TypeError even for strings to have consistency + # among various implementations. + raise TypeError("%s: invalid rounding mode" % value) + return object.__setattr__(self, name, value) + elif name == 'flags' or name == 'traps': + return self._set_signal_dict(name, value) + elif name == '_ignored_flags': + return object.__setattr__(self, name, value) + else: + raise AttributeError( + "'decimal.Context' object has no attribute '%s'" % name) + + def __delattr__(self, name): + raise AttributeError("%s cannot be deleted" % name) + + # Support for pickling, copy, and deepcopy + def __reduce__(self): + flags = [sig for sig, v in self.flags.items() if v] + traps = [sig for sig, v in self.traps.items() if v] + return (self.__class__, + (self.prec, self.rounding, self.Emin, self.Emax, + self.capitals, self.clamp, flags, traps)) + + def __repr__(self): + """Show the current context.""" + s = [] + s.append('Context(prec=%(prec)d, rounding=%(rounding)s, ' + '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) + ']') + names = [t.__name__ for t, v in self.traps.items() if v] + s.append('traps=[' + ', '.join(names) + ']') + return ', '.join(s) + ')' + + def clear_flags(self): + """Reset all flags to zero""" + for flag in self.flags: + self.flags[flag] = 0 + + def clear_traps(self): + """Reset all traps to zero""" + for flag in self.traps: + self.traps[flag] = 0 + + def _shallow_copy(self): + """Returns a shallow copy from self.""" + nc = Context(self.prec, self.rounding, self.Emin, self.Emax, + self.capitals, self.clamp, self.flags, self.traps, + self._ignored_flags) + return nc + + def copy(self): + """Returns a deep copy from self.""" + nc = Context(self.prec, self.rounding, self.Emin, self.Emax, + self.capitals, self.clamp, + self.flags.copy(), self.traps.copy(), + self._ignored_flags) + return nc + __copy__ = copy + + def _raise_error(self, condition, explanation = None, *args): + """Handles an error + + If the flag is in _ignored_flags, returns the default response. + Otherwise, it sets the flag, then, if the corresponding + trap_enabler is set, it reraises the exception. Otherwise, it returns + the default value after setting the flag. + """ + error = _condition_map.get(condition, condition) + if error in self._ignored_flags: + # Don't touch the flag + return error().handle(self, *args) + + self.flags[error] = 1 + if not self.traps[error]: + # The errors define how to handle themselves. + return condition().handle(self, *args) + + # Errors should only be risked on copies of the context + # self._ignored_flags = [] + raise error(explanation) + + def _ignore_all_flags(self): + """Ignore all flags, if they are raised""" + return self._ignore_flags(*_signals) + + def _ignore_flags(self, *flags): + """Ignore the flags, if they are raised""" + # Do not mutate-- This way, copies of a context leave the original + # alone. + self._ignored_flags = (self._ignored_flags + list(flags)) + return list(flags) + + def _regard_flags(self, *flags): + """Stop ignoring the flags, if they are raised""" + if flags and isinstance(flags[0], (tuple,list)): + flags = flags[0] + for flag in flags: + self._ignored_flags.remove(flag) + + # We inherit object.__hash__, so we must deny this explicitly + __hash__ = None + + def Etiny(self): + """Returns Etiny (= Emin - prec + 1)""" + return int(self.Emin - self.prec + 1) + + def Etop(self): + """Returns maximum exponent (= Emax - prec + 1)""" + return int(self.Emax - self.prec + 1) + + def _set_rounding(self, type): + """Sets the rounding type. + + Sets the rounding type, and returns the current (previous) + rounding type. Often used like: + + context = context.copy() + # so you don't change the calling context + # if an error occurs in the middle. + rounding = context._set_rounding(ROUND_UP) + val = self.__sub__(other, context=context) + context._set_rounding(rounding) + + This will make it round up for that operation. + """ + rounding = self.rounding + self.rounding= type + return rounding + + def create_decimal(self, num='0'): + """Creates a new Decimal instance but using self as context. + + This method implements the to-number operation of the + IBM Decimal specification.""" + + if isinstance(num, str) and num != num.strip(): + return self._raise_error(ConversionSyntax, + "no trailing or leading whitespace is " + "permitted.") + + d = Decimal(num, context=self) + 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) + + def create_decimal_from_float(self, f): + """Creates a new Decimal instance from a float but rounding using self + as the context. + + >>> context = Context(prec=5, rounding=ROUND_DOWN) + >>> context.create_decimal_from_float(3.1415926535897932) + Decimal('3.1415') + >>> context = Context(prec=5, traps=[Inexact]) + >>> context.create_decimal_from_float(3.1415926535897932) + Traceback (most recent call last): + ... + decimal.Inexact: None + + """ + d = Decimal.from_float(f) # An exact conversion + return d._fix(self) # Apply the context rounding + + # Methods + def abs(self, a): + """Returns the absolute value of the operand. + + If the operand is negative, the result is the same as using the minus + operation on the operand. Otherwise, the result is the same as using + the plus operation on the operand. + + >>> ExtendedContext.abs(Decimal('2.1')) + Decimal('2.1') + >>> ExtendedContext.abs(Decimal('-100')) + Decimal('100') + >>> ExtendedContext.abs(Decimal('101.5')) + 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): + """Return the sum of the two operands. + + >>> ExtendedContext.add(Decimal('12'), Decimal('7.00')) + 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') + """ + 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)) + + def canonical(self, a): + """Returns the same Decimal object. + + As we do not have different encodings for the same number, the + received object already is in its canonical form. + + >>> ExtendedContext.canonical(Decimal('2.50')) + Decimal('2.50') + """ + if not isinstance(a, Decimal): + raise TypeError("canonical requires a Decimal as an argument.") + return a.canonical() + + def compare(self, a, b): + """Compares values numerically. + + If the signs of the operands differ, a value representing each operand + ('-1' if the operand is less than zero, '0' if the operand is zero or + negative zero, or '1' if the operand is greater than zero) is used in + place of that operand for the comparison instead of the actual + operand. + + The comparison is then effected by subtracting the second operand from + the first and then returning a value according to the result of the + subtraction: '-1' if the result is less than zero, '0' if the result is + zero or negative zero, or '1' if the result is greater than zero. + + >>> ExtendedContext.compare(Decimal('2.1'), Decimal('3')) + Decimal('-1') + >>> ExtendedContext.compare(Decimal('2.1'), Decimal('2.1')) + Decimal('0') + >>> ExtendedContext.compare(Decimal('2.1'), Decimal('2.10')) + Decimal('0') + >>> ExtendedContext.compare(Decimal('3'), Decimal('2.1')) + Decimal('1') + >>> ExtendedContext.compare(Decimal('2.1'), Decimal('-3')) + 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): + """Compares the values of the two operands numerically. + + It's pretty much like compare(), but all NaNs signal, with signaling + NaNs taking precedence over quiet NaNs. + + >>> c = ExtendedContext + >>> c.compare_signal(Decimal('2.1'), Decimal('3')) + Decimal('-1') + >>> c.compare_signal(Decimal('2.1'), Decimal('2.1')) + Decimal('0') + >>> c.flags[InvalidOperation] = 0 + >>> print(c.flags[InvalidOperation]) + 0 + >>> c.compare_signal(Decimal('NaN'), Decimal('2.1')) + Decimal('NaN') + >>> print(c.flags[InvalidOperation]) + 1 + >>> c.flags[InvalidOperation] = 0 + >>> print(c.flags[InvalidOperation]) + 0 + >>> c.compare_signal(Decimal('sNaN'), Decimal('2.1')) + 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): + """Compares two operands using their abstract representation. + + This is not like the standard compare, which use their numerical + value. Note that a total ordering is defined for all possible abstract + representations. + + >>> ExtendedContext.compare_total(Decimal('12.73'), Decimal('127.9')) + Decimal('-1') + >>> ExtendedContext.compare_total(Decimal('-127'), Decimal('12')) + Decimal('-1') + >>> ExtendedContext.compare_total(Decimal('12.30'), Decimal('12.3')) + Decimal('-1') + >>> ExtendedContext.compare_total(Decimal('12.30'), Decimal('12.30')) + Decimal('0') + >>> ExtendedContext.compare_total(Decimal('12.3'), Decimal('12.300')) + 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): + """Compares two operands using their abstract representation ignoring sign. + + 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): + """Returns a copy of the operand with the sign set to 0. + + >>> ExtendedContext.copy_abs(Decimal('2.1')) + 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 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): + """Returns a copy of the operand with the sign inverted. + + >>> ExtendedContext.copy_negate(Decimal('101.5')) + 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): + """Copies the second operand's sign to the first one. + + In detail, it returns a copy of the first operand with the sign + equal to the sign of the second operand. + + >>> ExtendedContext.copy_sign(Decimal( '1.50'), Decimal('7.33')) + Decimal('1.50') + >>> ExtendedContext.copy_sign(Decimal('-1.50'), Decimal('7.33')) + Decimal('1.50') + >>> ExtendedContext.copy_sign(Decimal( '1.50'), Decimal('-7.33')) + 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): + """Decimal division in a specified context. + + >>> ExtendedContext.divide(Decimal('1'), Decimal('3')) + Decimal('0.333333333') + >>> ExtendedContext.divide(Decimal('2'), Decimal('3')) + Decimal('0.666666667') + >>> ExtendedContext.divide(Decimal('5'), Decimal('2')) + Decimal('2.5') + >>> ExtendedContext.divide(Decimal('1'), Decimal('10')) + Decimal('0.1') + >>> ExtendedContext.divide(Decimal('12'), Decimal('12')) + Decimal('1') + >>> ExtendedContext.divide(Decimal('8.00'), Decimal('2')) + Decimal('4.00') + >>> ExtendedContext.divide(Decimal('2.400'), Decimal('2.0')) + Decimal('1.20') + >>> ExtendedContext.divide(Decimal('1000'), Decimal('100')) + Decimal('10') + >>> ExtendedContext.divide(Decimal('1000'), Decimal('1')) + 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') + """ + 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. + + >>> ExtendedContext.divide_int(Decimal('2'), Decimal('3')) + Decimal('0') + >>> ExtendedContext.divide_int(Decimal('10'), Decimal('3')) + 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') + """ + 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). + + >>> 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')) + """ + 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. + + >>> c = ExtendedContext.copy() + >>> c.Emin = -999 + >>> c.Emax = 999 + >>> c.exp(Decimal('-Infinity')) + Decimal('0') + >>> c.exp(Decimal('-1')) + Decimal('0.367879441') + >>> c.exp(Decimal('0')) + Decimal('1') + >>> c.exp(Decimal('1')) + Decimal('2.71828183') + >>> c.exp(Decimal('0.693147181')) + 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): + """Returns a multiplied by b, plus c. + + The first two operands are multiplied together, using multiply, + the third operand is then added to the result of that + multiplication, using add, all with only one final rounding. + + >>> ExtendedContext.fma(Decimal('3'), Decimal('5'), Decimal('7')) + Decimal('22') + >>> ExtendedContext.fma(Decimal('3'), Decimal('-5'), Decimal('7')) + 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): + """Return True if the operand is canonical; otherwise return False. + + Currently, the encoding of a Decimal instance is always + canonical, so this method returns True for any Decimal. + + >>> ExtendedContext.is_canonical(Decimal('2.50')) + True + """ + if not isinstance(a, Decimal): + raise TypeError("is_canonical requires a Decimal as an argument.") + return a.is_canonical() + + def is_finite(self, a): + """Return True if the operand is finite; otherwise return False. + + A Decimal instance is considered finite if it is neither + infinite nor a NaN. + + >>> ExtendedContext.is_finite(Decimal('2.50')) + True + >>> ExtendedContext.is_finite(Decimal('-0.3')) + True + >>> ExtendedContext.is_finite(Decimal('0')) + True + >>> ExtendedContext.is_finite(Decimal('Inf')) + 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): + """Return True if the operand is infinite; otherwise return False. + + >>> ExtendedContext.is_infinite(Decimal('2.50')) + False + >>> ExtendedContext.is_infinite(Decimal('-Inf')) + 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): + """Return True if the operand is a qNaN or sNaN; + otherwise return False. + + >>> ExtendedContext.is_nan(Decimal('2.50')) + False + >>> ExtendedContext.is_nan(Decimal('NaN')) + 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): + """Return True if the operand is a normal number; + otherwise return False. + + >>> c = ExtendedContext.copy() + >>> c.Emin = -999 + >>> c.Emax = 999 + >>> c.is_normal(Decimal('2.50')) + True + >>> c.is_normal(Decimal('0.1E-999')) + False + >>> c.is_normal(Decimal('0.00')) + False + >>> c.is_normal(Decimal('-Inf')) + 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): + """Return True if the operand is a quiet NaN; otherwise return False. + + >>> ExtendedContext.is_qnan(Decimal('2.50')) + False + >>> ExtendedContext.is_qnan(Decimal('NaN')) + 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): + """Return True if the operand is negative; otherwise return False. + + >>> ExtendedContext.is_signed(Decimal('2.50')) + False + >>> ExtendedContext.is_signed(Decimal('-12')) + 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): + """Return True if the operand is a signaling NaN; + otherwise return False. + + >>> ExtendedContext.is_snan(Decimal('2.50')) + False + >>> ExtendedContext.is_snan(Decimal('NaN')) + 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): + """Return True if the operand is subnormal; otherwise return False. + + >>> c = ExtendedContext.copy() + >>> c.Emin = -999 + >>> c.Emax = 999 + >>> c.is_subnormal(Decimal('2.50')) + False + >>> c.is_subnormal(Decimal('0.1E-999')) + True + >>> c.is_subnormal(Decimal('0.00')) + False + >>> c.is_subnormal(Decimal('-Inf')) + 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): + """Return True if the operand is a zero; otherwise return False. + + >>> ExtendedContext.is_zero(Decimal('0')) + True + >>> ExtendedContext.is_zero(Decimal('2.50')) + 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): + """Returns the natural (base e) logarithm of the operand. + + >>> c = ExtendedContext.copy() + >>> c.Emin = -999 + >>> c.Emax = 999 + >>> c.ln(Decimal('0')) + Decimal('-Infinity') + >>> c.ln(Decimal('1.000')) + Decimal('0') + >>> c.ln(Decimal('2.71828183')) + Decimal('1.00000000') + >>> c.ln(Decimal('10')) + 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): + """Returns the base 10 logarithm of the operand. + + >>> c = ExtendedContext.copy() + >>> c.Emin = -999 + >>> c.Emax = 999 + >>> c.log10(Decimal('0')) + Decimal('-Infinity') + >>> c.log10(Decimal('0.001')) + Decimal('-3') + >>> c.log10(Decimal('1.000')) + Decimal('0') + >>> c.log10(Decimal('2')) + Decimal('0.301029996') + >>> c.log10(Decimal('10')) + Decimal('1') + >>> c.log10(Decimal('70')) + 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): + """ Returns the exponent of the magnitude of the operand's MSD. + + The result is the integer which is the exponent of the magnitude + of the most significant digit of the operand (as though the + operand were truncated to a single digit while maintaining the + value of that digit and without limiting the resulting exponent). + + >>> ExtendedContext.logb(Decimal('250')) + Decimal('2') + >>> ExtendedContext.logb(Decimal('2.50')) + Decimal('0') + >>> ExtendedContext.logb(Decimal('0.03')) + 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): + """Applies the logical operation 'and' between each operand's digits. + + The operands must be both logical numbers. + + >>> ExtendedContext.logical_and(Decimal('0'), Decimal('0')) + Decimal('0') + >>> ExtendedContext.logical_and(Decimal('0'), Decimal('1')) + Decimal('0') + >>> ExtendedContext.logical_and(Decimal('1'), Decimal('0')) + Decimal('0') + >>> ExtendedContext.logical_and(Decimal('1'), Decimal('1')) + Decimal('1') + >>> ExtendedContext.logical_and(Decimal('1100'), Decimal('1010')) + 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): + """Invert all the digits in the operand. + + The operand must be a logical number. + + >>> ExtendedContext.logical_invert(Decimal('0')) + Decimal('111111111') + >>> ExtendedContext.logical_invert(Decimal('1')) + Decimal('111111110') + >>> ExtendedContext.logical_invert(Decimal('111111111')) + 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): + """Applies the logical operation 'or' between each operand's digits. + + The operands must be both logical numbers. + + >>> ExtendedContext.logical_or(Decimal('0'), Decimal('0')) + Decimal('0') + >>> ExtendedContext.logical_or(Decimal('0'), Decimal('1')) + Decimal('1') + >>> ExtendedContext.logical_or(Decimal('1'), Decimal('0')) + Decimal('1') + >>> ExtendedContext.logical_or(Decimal('1'), Decimal('1')) + Decimal('1') + >>> ExtendedContext.logical_or(Decimal('1100'), Decimal('1010')) + 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): + """Applies the logical operation 'xor' between each operand's digits. + + The operands must be both logical numbers. + + >>> ExtendedContext.logical_xor(Decimal('0'), Decimal('0')) + Decimal('0') + >>> ExtendedContext.logical_xor(Decimal('0'), Decimal('1')) + Decimal('1') + >>> ExtendedContext.logical_xor(Decimal('1'), Decimal('0')) + Decimal('1') + >>> ExtendedContext.logical_xor(Decimal('1'), Decimal('1')) + Decimal('0') + >>> ExtendedContext.logical_xor(Decimal('1100'), Decimal('1010')) + 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): + """max compares two values numerically and returns the maximum. + + If either operand is a NaN then the general rules apply. + Otherwise, the operands are compared as though by the compare + operation. If they are numerically equal then the left-hand operand + is chosen as the result. Otherwise the maximum (closer to positive + infinity) of the two operands is chosen as the result. + + >>> ExtendedContext.max(Decimal('3'), Decimal('2')) + Decimal('3') + >>> ExtendedContext.max(Decimal('-10'), Decimal('3')) + Decimal('3') + >>> ExtendedContext.max(Decimal('1.0'), Decimal('1')) + 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. + + >>> 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): + """min compares two values numerically and returns the minimum. + + If either operand is a NaN then the general rules apply. + Otherwise, the operands are compared as though by the compare + operation. If they are numerically equal then the left-hand operand + is chosen as the result. Otherwise the minimum (closer to negative + infinity) of the two operands is chosen as the result. + + >>> ExtendedContext.min(Decimal('3'), Decimal('2')) + Decimal('2') + >>> ExtendedContext.min(Decimal('-10'), Decimal('3')) + Decimal('-10') + >>> ExtendedContext.min(Decimal('1.0'), Decimal('1')) + 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. + + >>> 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): + """Minus corresponds to unary prefix minus in Python. + + The operation is evaluated using the same rules as subtract; the + operation minus(a) is calculated as subtract('0', a) where the '0' + has the same exponent as the operand. + + >>> ExtendedContext.minus(Decimal('1.3')) + 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. + + >>> ExtendedContext.multiply(Decimal('1.20'), Decimal('3')) + Decimal('3.60') + >>> ExtendedContext.multiply(Decimal('7'), Decimal('3')) + Decimal('21') + >>> ExtendedContext.multiply(Decimal('0.9'), Decimal('0.8')) + Decimal('0.72') + >>> ExtendedContext.multiply(Decimal('0.9'), Decimal('-0')) + 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') + """ + 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. + + >>> c = ExtendedContext.copy() + >>> c.Emin = -999 + >>> c.Emax = 999 + >>> ExtendedContext.next_minus(Decimal('1')) + Decimal('0.999999999') + >>> c.next_minus(Decimal('1E-1007')) + Decimal('0E-1007') + >>> ExtendedContext.next_minus(Decimal('-1.00000003')) + 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): + """Returns the smallest representable number larger than a. + + >>> c = ExtendedContext.copy() + >>> c.Emin = -999 + >>> c.Emax = 999 + >>> ExtendedContext.next_plus(Decimal('1')) + Decimal('1.00000001') + >>> c.next_plus(Decimal('-1E-1007')) + Decimal('-0E-1007') + >>> ExtendedContext.next_plus(Decimal('-1.00000003')) + 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): + """Returns the number closest to a, in direction towards b. + + The result is the closest representable number from the first + operand (but not the first operand) that is in the direction + towards the second operand, unless the operands have the same + value. + + >>> c = ExtendedContext.copy() + >>> c.Emin = -999 + >>> c.Emax = 999 + >>> c.next_toward(Decimal('1'), Decimal('2')) + Decimal('1.00000001') + >>> c.next_toward(Decimal('-1E-1007'), Decimal('1')) + Decimal('-0E-1007') + >>> c.next_toward(Decimal('-1.00000003'), Decimal('0')) + Decimal('-1.00000002') + >>> c.next_toward(Decimal('1'), Decimal('0')) + Decimal('0.999999999') + >>> c.next_toward(Decimal('1E-1007'), Decimal('-100')) + Decimal('0E-1007') + >>> c.next_toward(Decimal('-1.00000003'), Decimal('-10')) + 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): + """normalize reduces an operand to its simplest form. + + Essentially a plus operation with all trailing zeros removed from the + result. + + >>> ExtendedContext.normalize(Decimal('2.1')) + Decimal('2.1') + >>> ExtendedContext.normalize(Decimal('-2.0')) + Decimal('-2') + >>> ExtendedContext.normalize(Decimal('1.200')) + Decimal('1.2') + >>> ExtendedContext.normalize(Decimal('-120')) + Decimal('-1.2E+2') + >>> ExtendedContext.normalize(Decimal('120.00')) + 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): + """Returns an indication of the class of the operand. + + The class is one of the following strings: + -sNaN + -NaN + -Infinity + -Normal + -Subnormal + -Zero + +Zero + +Subnormal + +Normal + +Infinity + + >>> c = ExtendedContext.copy() + >>> c.Emin = -999 + >>> c.Emax = 999 + >>> c.number_class(Decimal('Infinity')) + '+Infinity' + >>> c.number_class(Decimal('1E-10')) + '+Normal' + >>> c.number_class(Decimal('2.50')) + '+Normal' + >>> c.number_class(Decimal('0.1E-999')) + '+Subnormal' + >>> c.number_class(Decimal('0')) + '+Zero' + >>> c.number_class(Decimal('-0')) + '-Zero' + >>> c.number_class(Decimal('-0.1E-999')) + '-Subnormal' + >>> c.number_class(Decimal('-1E-10')) + '-Normal' + >>> c.number_class(Decimal('-2.50')) + '-Normal' + >>> c.number_class(Decimal('-Infinity')) + '-Infinity' + >>> c.number_class(Decimal('NaN')) + 'NaN' + >>> c.number_class(Decimal('-NaN')) + '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): + """Plus corresponds to unary prefix plus in Python. + + The operation is evaluated using the same rules as add; the + operation plus(a) is calculated as add('0', a) where the '0' + has the same exponent as the operand. + + >>> ExtendedContext.plus(Decimal('1.3')) + 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): + """Raises a to the power of b, to modulo if given. + + With two arguments, compute a**b. If a is negative then b + must be integral. The result will be inexact unless b is + integral and the result is finite and can be expressed exactly + in 'precision' digits. + + With three arguments, compute (a**b) % modulo. For the + three argument form, the following restrictions on the + arguments hold: + + - all three arguments must be integral + - b must be nonnegative + - at least one of a or b must be nonzero + - modulo must be nonzero and have at most 'precision' digits + + The result of pow(a, b, modulo) is identical to the result + that would be obtained by computing (a**b) % modulo with + unbounded precision, but is computed more efficiently. It is + always exact. + + >>> c = ExtendedContext.copy() + >>> c.Emin = -999 + >>> c.Emax = 999 + >>> c.power(Decimal('2'), Decimal('3')) + Decimal('8') + >>> c.power(Decimal('-2'), Decimal('3')) + Decimal('-8') + >>> c.power(Decimal('2'), Decimal('-3')) + Decimal('0.125') + >>> c.power(Decimal('1.7'), Decimal('8')) + Decimal('69.7575744') + >>> c.power(Decimal('10'), Decimal('0.301029996')) + Decimal('2.00000000') + >>> c.power(Decimal('Infinity'), Decimal('-1')) + Decimal('0') + >>> c.power(Decimal('Infinity'), Decimal('0')) + Decimal('1') + >>> c.power(Decimal('Infinity'), Decimal('1')) + Decimal('Infinity') + >>> c.power(Decimal('-Infinity'), Decimal('-1')) + Decimal('-0') + >>> c.power(Decimal('-Infinity'), Decimal('0')) + Decimal('1') + >>> c.power(Decimal('-Infinity'), Decimal('1')) + Decimal('-Infinity') + >>> c.power(Decimal('-Infinity'), Decimal('2')) + Decimal('Infinity') + >>> c.power(Decimal('0'), Decimal('0')) + Decimal('NaN') + + >>> c.power(Decimal('3'), Decimal('7'), Decimal('16')) + Decimal('11') + >>> c.power(Decimal('-3'), Decimal('7'), Decimal('16')) + Decimal('-11') + >>> c.power(Decimal('-3'), Decimal('8'), Decimal('16')) + Decimal('1') + >>> c.power(Decimal('3'), Decimal('7'), Decimal('-16')) + Decimal('11') + >>> c.power(Decimal('23E12345'), Decimal('67E189'), Decimal('123456789')) + Decimal('11729830') + >>> c.power(Decimal('-0'), Decimal('17'), Decimal('1729')) + 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') + """ + 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'. + + The coefficient of the result is derived from that of the left-hand + operand. It may be rounded using the current rounding setting (if the + exponent is being increased), multiplied by a positive power of ten (if + the exponent is being decreased), or is unchanged (if the exponent is + already equal to that of the right-hand operand). + + Unlike other operations, if the length of the coefficient after the + quantize operation would be greater than precision then an Invalid + operation condition is raised. This guarantees that, unless there is + an error condition, the exponent of the result of a quantize is always + equal to that of the right-hand operand. + + Also unlike other operations, quantize will never raise Underflow, even + if the result is subnormal and inexact. + + >>> ExtendedContext.quantize(Decimal('2.17'), Decimal('0.001')) + Decimal('2.170') + >>> ExtendedContext.quantize(Decimal('2.17'), Decimal('0.01')) + Decimal('2.17') + >>> ExtendedContext.quantize(Decimal('2.17'), Decimal('0.1')) + Decimal('2.2') + >>> ExtendedContext.quantize(Decimal('2.17'), Decimal('1e+0')) + Decimal('2') + >>> ExtendedContext.quantize(Decimal('2.17'), Decimal('1e+1')) + Decimal('0E+1') + >>> ExtendedContext.quantize(Decimal('-Inf'), Decimal('Infinity')) + Decimal('-Infinity') + >>> ExtendedContext.quantize(Decimal('2'), Decimal('Infinity')) + Decimal('NaN') + >>> ExtendedContext.quantize(Decimal('-0.1'), Decimal('1')) + Decimal('-0') + >>> ExtendedContext.quantize(Decimal('-0'), Decimal('1e+5')) + Decimal('-0E+5') + >>> ExtendedContext.quantize(Decimal('+35236450.6'), Decimal('1e-2')) + Decimal('NaN') + >>> ExtendedContext.quantize(Decimal('-35236450.6'), Decimal('1e-2')) + Decimal('NaN') + >>> ExtendedContext.quantize(Decimal('217'), Decimal('1e-1')) + Decimal('217.0') + >>> ExtendedContext.quantize(Decimal('217'), Decimal('1e-0')) + Decimal('217') + >>> ExtendedContext.quantize(Decimal('217'), Decimal('1e+1')) + 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): + """Just returns 10, as this is Decimal, :) + + >>> ExtendedContext.radix() + Decimal('10') + """ + return Decimal(10) + + def remainder(self, a, b): + """Returns the remainder from integer division. + + The result is the residue of the dividend after the operation of + calculating integer division as described for divide-integer, rounded + to precision digits if necessary. The sign of the result, if + non-zero, is the same as that of the original dividend. + + This operation will fail under the same conditions as integer division + (that is, if integer division on the same two operands would fail, the + remainder cannot be calculated). + + >>> ExtendedContext.remainder(Decimal('2.1'), Decimal('3')) + Decimal('2.1') + >>> ExtendedContext.remainder(Decimal('10'), Decimal('3')) + Decimal('1') + >>> ExtendedContext.remainder(Decimal('-10'), Decimal('3')) + Decimal('-1') + >>> ExtendedContext.remainder(Decimal('10.2'), Decimal('1')) + Decimal('0.2') + >>> ExtendedContext.remainder(Decimal('10'), Decimal('0.3')) + 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') + """ + 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 + value of "x / b" (if two integers are equally near then the even one + is chosen). If the result is equal to 0 then its sign will be the + sign of a. + + This operation will fail under the same conditions as integer division + (that is, if integer division on the same two operands would fail, the + remainder cannot be calculated). + + >>> ExtendedContext.remainder_near(Decimal('2.1'), Decimal('3')) + Decimal('-0.9') + >>> ExtendedContext.remainder_near(Decimal('10'), Decimal('6')) + Decimal('-2') + >>> ExtendedContext.remainder_near(Decimal('10'), Decimal('3')) + Decimal('1') + >>> ExtendedContext.remainder_near(Decimal('-10'), Decimal('3')) + Decimal('-1') + >>> ExtendedContext.remainder_near(Decimal('10.2'), Decimal('1')) + Decimal('0.2') + >>> ExtendedContext.remainder_near(Decimal('10'), Decimal('0.3')) + 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): + """Returns a rotated copy of a, b times. + + The coefficient of the result is a rotated copy of the digits in + the coefficient of the first operand. The number of places of + rotation is taken from the absolute value of the second operand, + with the rotation being to the left if the second operand is + positive or to the right otherwise. + + >>> ExtendedContext.rotate(Decimal('34'), Decimal('8')) + Decimal('400000003') + >>> ExtendedContext.rotate(Decimal('12'), Decimal('9')) + Decimal('12') + >>> ExtendedContext.rotate(Decimal('123456789'), Decimal('-2')) + Decimal('891234567') + >>> ExtendedContext.rotate(Decimal('123456789'), Decimal('0')) + 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): + """Returns True if the two operands have the same exponent. + + The result is never affected by either the sign or the coefficient of + either operand. + + >>> ExtendedContext.same_quantum(Decimal('2.17'), Decimal('0.001')) + False + >>> ExtendedContext.same_quantum(Decimal('2.17'), Decimal('0.01')) + True + >>> ExtendedContext.same_quantum(Decimal('2.17'), Decimal('1')) + 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): + """Returns the first operand after adding the second value its exp. + + >>> ExtendedContext.scaleb(Decimal('7.50'), Decimal('-2')) + Decimal('0.0750') + >>> ExtendedContext.scaleb(Decimal('7.50'), Decimal('0')) + 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') + """ + 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. + + The coefficient of the result is a shifted copy of the digits + in the coefficient of the first operand. The number of places + to shift is taken from the absolute value of the second operand, + with the shift being to the left if the second operand is + positive or to the right otherwise. Digits shifted into the + coefficient are zeros. + + >>> ExtendedContext.shift(Decimal('34'), Decimal('8')) + Decimal('400000000') + >>> ExtendedContext.shift(Decimal('12'), Decimal('9')) + Decimal('0') + >>> ExtendedContext.shift(Decimal('123456789'), Decimal('-2')) + Decimal('1234567') + >>> ExtendedContext.shift(Decimal('123456789'), Decimal('0')) + 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): + """Square root of a non-negative number to context precision. + + If the result must be inexact, it is rounded using the round-half-even + algorithm. + + >>> ExtendedContext.sqrt(Decimal('0')) + Decimal('0') + >>> ExtendedContext.sqrt(Decimal('-0')) + Decimal('-0') + >>> ExtendedContext.sqrt(Decimal('0.39')) + Decimal('0.624499800') + >>> ExtendedContext.sqrt(Decimal('100')) + Decimal('10') + >>> ExtendedContext.sqrt(Decimal('1')) + Decimal('1') + >>> ExtendedContext.sqrt(Decimal('1.0')) + Decimal('1.0') + >>> ExtendedContext.sqrt(Decimal('1.00')) + Decimal('1.0') + >>> ExtendedContext.sqrt(Decimal('7')) + 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): + """Return the difference between the two operands. + + >>> ExtendedContext.subtract(Decimal('1.3'), Decimal('1.07')) + Decimal('0.23') + >>> ExtendedContext.subtract(Decimal('1.3'), Decimal('1.30')) + 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') + """ + 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): + """Convert to a string, using engineering notation if an exponent is needed. + + Engineering notation has an exponent which is a multiple of 3. This + can leave up to 3 digits to the left of the decimal place and may + require the addition of either one or two trailing zeros. + + The operation is not affected by the context. + + >>> ExtendedContext.to_eng_string(Decimal('123E+1')) + '1.23E+3' + >>> ExtendedContext.to_eng_string(Decimal('123E+3')) + '123E+3' + >>> ExtendedContext.to_eng_string(Decimal('123E-10')) + '12.3E-9' + >>> ExtendedContext.to_eng_string(Decimal('-123E-12')) + '-123E-12' + >>> ExtendedContext.to_eng_string(Decimal('7E-7')) + '700E-9' + >>> ExtendedContext.to_eng_string(Decimal('7E+1')) + '70' + >>> ExtendedContext.to_eng_string(Decimal('0E+1')) + '0.00E+3' + + """ + a = _convert_other(a, raiseit=True) + return a.to_eng_string(context=self) + + def to_sci_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.__str__(context=self) + + def to_integral_exact(self, a): + """Rounds to an integer. + + When the operand has a negative exponent, the result is the same + as using the quantize() operation using the given operand as the + left-hand-operand, 1E+0 as the right-hand-operand, and the precision + of the operand as the precision setting; Inexact and Rounded flags + are allowed in this operation. The rounding mode is taken from the + context. + + >>> ExtendedContext.to_integral_exact(Decimal('2.1')) + Decimal('2') + >>> ExtendedContext.to_integral_exact(Decimal('100')) + Decimal('100') + >>> ExtendedContext.to_integral_exact(Decimal('100.0')) + Decimal('100') + >>> ExtendedContext.to_integral_exact(Decimal('101.5')) + Decimal('102') + >>> ExtendedContext.to_integral_exact(Decimal('-101.5')) + Decimal('-102') + >>> ExtendedContext.to_integral_exact(Decimal('10E+5')) + Decimal('1.0E+6') + >>> ExtendedContext.to_integral_exact(Decimal('7.89E+77')) + Decimal('7.89E+77') + >>> 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): + """Rounds to an integer. + + When the operand has a negative exponent, the result is the same + as using the quantize() operation using the given operand as the + left-hand-operand, 1E+0 as the right-hand-operand, and the precision + of the operand as the precision setting, except that no flags will + be set. The rounding mode is taken from the context. + + >>> ExtendedContext.to_integral_value(Decimal('2.1')) + Decimal('2') + >>> ExtendedContext.to_integral_value(Decimal('100')) + Decimal('100') + >>> ExtendedContext.to_integral_value(Decimal('100.0')) + Decimal('100') + >>> ExtendedContext.to_integral_value(Decimal('101.5')) + Decimal('102') + >>> ExtendedContext.to_integral_value(Decimal('-101.5')) + Decimal('-102') + >>> ExtendedContext.to_integral_value(Decimal('10E+5')) + Decimal('1.0E+6') + >>> ExtendedContext.to_integral_value(Decimal('7.89E+77')) + Decimal('7.89E+77') + >>> 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 + to_integral = to_integral_value + +class _WorkRep(object): + __slots__ = ('sign','int','exp') + # sign: 0 or 1 + # int: int + # exp: None, int, or string + + def __init__(self, value=None): + if value is None: + self.sign = None + self.int = 0 + self.exp = None + elif isinstance(value, Decimal): + self.sign = value._sign + self.int = int(value._int) + self.exp = value._exp + else: + # assert isinstance(value, tuple) + self.sign = value[0] + self.int = value[1] + self.exp = value[2] + + def __repr__(self): + return "(%r, %r, %r)" % (self.sign, self.int, self.exp) + + __str__ = __repr__ + + + +def _normalize(op1, op2, prec = 0): + """Normalizes op1, op2 to have the same exp and length of coefficient. + + Done during addition. + """ + if op1.exp < op2.exp: + tmp = op2 + other = op1 + else: + tmp = op1 + other = op2 + + # Let exp = min(tmp.exp - 1, tmp.adjusted() - precision - 1). + # Then adding 10**exp to tmp has the same effect (after rounding) + # as adding any positive quantity smaller than 10**exp; similarly + # for subtraction. So if other is smaller than 10**exp we replace + # it with 10**exp. This avoids tmp.exp - other.exp getting too large. + tmp_len = len(str(tmp.int)) + other_len = len(str(other.int)) + exp = tmp.exp + min(-1, tmp_len - prec - 2) + if other_len + other.exp - 1 < exp: + other.int = 1 + other.exp = exp + + tmp.int *= 10 ** (tmp.exp - other.exp) + tmp.exp = other.exp + return op1, op2 + +##### Integer arithmetic functions used by ln, log10, exp and __pow__ ##### + +_nbits = int.bit_length + +def _decimal_lshift_exact(n, e): + """ Given integers n and e, return n * 10**e if it's an integer, else None. + + The computation is designed to avoid computing large powers of 10 + unnecessarily. + + >>> _decimal_lshift_exact(3, 4) + 30000 + >>> _decimal_lshift_exact(300, -999999999) # returns None + + """ + if n == 0: + return 0 + elif e >= 0: + return n * 10**e + else: + # val_n = largest power of 10 dividing n. + str_n = str(abs(n)) + val_n = len(str_n) - len(str_n.rstrip('0')) + return None if val_n < -e else n // 10**-e + +def _sqrt_nearest(n, a): + """Closest integer to the square root of the positive integer n. a is + an initial approximation to the square root. Any positive integer + will do for a, but the closer a is to the square root of n the + faster convergence will be. + + """ + if n <= 0 or a <= 0: + raise ValueError("Both arguments to _sqrt_nearest should be positive.") + + b=0 + while a != b: + b, a = a, a--n//a>>1 + return a + +def _rshift_nearest(x, shift): + """Given an integer x and a nonnegative integer shift, return closest + integer to x / 2**shift; use round-to-even in case of a tie. + + """ + b, q = 1 << shift, x >> shift + return q + (2*(x & (b-1)) + (q&1) > b) + +def _div_nearest(a, b): + """Closest integer to a/b, a and b positive integers; rounds to even + in the case of a tie. + + """ + q, r = divmod(a, b) + return q + (2*r + (q&1) > b) + +def _ilog(x, M, L = 8): + """Integer approximation to M*log(x/M), with absolute error boundable + in terms only of x/M. + + Given positive integers x and M, return an integer approximation to + M * log(x/M). For L = 8 and 0.1 <= x/M <= 10 the difference + between the approximation and the exact result is at most 22. For + L = 8 and 1.0 <= x/M <= 10.0 the difference is at most 15. In + both cases these are upper bounds on the error; it will usually be + much smaller.""" + + # The basic algorithm is the following: let log1p be the function + # log1p(x) = log(1+x). Then log(x/M) = log1p((x-M)/M). We use + # the reduction + # + # log1p(y) = 2*log1p(y/(1+sqrt(1+y))) + # + # repeatedly until the argument to log1p is small (< 2**-L in + # absolute value). For small y we can use the Taylor series + # expansion + # + # log1p(y) ~ y - y**2/2 + y**3/3 - ... - (-y)**T/T + # + # truncating at T such that y**T is small enough. The whole + # computation is carried out in a form of fixed-point arithmetic, + # with a real number z being represented by an integer + # approximation to z*M. To avoid loss of precision, the y below + # is actually an integer approximation to 2**R*y*M, where R is the + # number of reductions performed so far. + + y = x-M + # argument reduction; R = number of reductions performed + R = 0 + while (R <= L and abs(y) << L-R >= M or + R > L and abs(y) >> R-L >= M): + y = _div_nearest((M*y) << 1, + M + _sqrt_nearest(M*(M+_rshift_nearest(y, R)), M)) + R += 1 + + # Taylor series with T terms + T = -int(-10*len(str(M))//(3*L)) + yshift = _rshift_nearest(y, R) + w = _div_nearest(M, T) + for k in range(T-1, 0, -1): + w = _div_nearest(M, k) - _div_nearest(yshift*w, M) + + return _div_nearest(w*y, M) + +def _dlog10(c, e, p): + """Given integers c, e and p with c > 0, p >= 0, compute an integer + approximation to 10**p * log10(c*10**e), with an absolute error of + at most 1. Assumes that c*10**e is not exactly 1.""" + + # increase precision by 2; compensate for this by dividing + # final result by 100 + p += 2 + + # write c*10**e as d*10**f with either: + # f >= 0 and 1 <= d <= 10, or + # f <= 0 and 0.1 <= d <= 1. + # Thus for c*10**e close to 1, f = 0 + l = len(str(c)) + f = e+l - (e+l >= 1) + + if p > 0: + M = 10**p + k = e+p-f + if k >= 0: + c *= 10**k + else: + c = _div_nearest(c, 10**-k) + + log_d = _ilog(c, M) # error < 5 + 22 = 27 + log_10 = _log10_digits(p) # error < 1 + log_d = _div_nearest(log_d*M, log_10) + log_tenpower = f*M # exact + else: + log_d = 0 # error < 2.31 + log_tenpower = _div_nearest(f, 10**-p) # error < 0.5 + + return _div_nearest(log_tenpower+log_d, 100) + +def _dlog(c, e, p): + """Given integers c, e and p with c > 0, compute an integer + approximation to 10**p * log(c*10**e), with an absolute error of + at most 1. Assumes that c*10**e is not exactly 1.""" + + # Increase precision by 2. The precision increase is compensated + # for at the end with a division by 100. + p += 2 + + # rewrite c*10**e as d*10**f with either f >= 0 and 1 <= d <= 10, + # or f <= 0 and 0.1 <= d <= 1. Then we can compute 10**p * log(c*10**e) + # as 10**p * log(d) + 10**p*f * log(10). + l = len(str(c)) + f = e+l - (e+l >= 1) + + # compute approximation to 10**p*log(d), with error < 27 + if p > 0: + k = e+p-f + if k >= 0: + c *= 10**k + else: + c = _div_nearest(c, 10**-k) # error of <= 0.5 in c + + # _ilog magnifies existing error in c by a factor of at most 10 + log_d = _ilog(c, 10**p) # error < 5 + 22 = 27 + else: + # p <= 0: just approximate the whole thing by 0; error < 2.31 + log_d = 0 + + # compute approximation to f*10**p*log(10), with error < 11. + if f: + extra = len(str(abs(f)))-1 + if p + extra >= 0: + # error in f * _log10_digits(p+extra) < |f| * 1 = |f| + # after division, error < |f|/10**extra + 0.5 < 10 + 0.5 < 11 + f_log_ten = _div_nearest(f*_log10_digits(p+extra), 10**extra) + else: + f_log_ten = 0 + else: + f_log_ten = 0 + + # error in sum < 11+27 = 38; error after division < 0.38 + 0.5 < 1 + return _div_nearest(f_log_ten + log_d, 100) + +class _Log10Memoize(object): + """Class to compute, store, and allow retrieval of, digits of the + constant log(10) = 2.302585.... This constant is needed by + Decimal.ln, Decimal.log10, Decimal.exp and Decimal.__pow__.""" + def __init__(self): + self.digits = "23025850929940456840179914546843642076011014886" + + def getdigits(self, p): + """Given an integer p >= 0, return floor(10**p)*log(10). + + For example, self.getdigits(3) returns 2302. + """ + # digits are stored as a string, for quick conversion to + # integer in the case that we've already computed enough + # digits; the stored digits should always be correct + # (truncated, not rounded to nearest). + if p < 0: + raise ValueError("p should be nonnegative") + + if p >= len(self.digits): + # compute p+3, p+6, p+9, ... digits; continue until at + # least one of the extra digits is nonzero + extra = 3 + while True: + # compute p+extra digits, correct to within 1ulp + M = 10**(p+extra+2) + digits = str(_div_nearest(_ilog(10*M, M), 100)) + if digits[-extra:] != '0'*extra: + break + extra += 3 + # keep all reliable digits so far; remove trailing zeros + # and next nonzero digit + self.digits = digits.rstrip('0')[:-1] + return int(self.digits[:p+1]) + +_log10_digits = _Log10Memoize().getdigits + +def _iexp(x, M, L=8): + """Given integers x and M, M > 0, such that x/M is small in absolute + value, compute an integer approximation to M*exp(x/M). For 0 <= + x/M <= 2.4, the absolute error in the result is bounded by 60 (and + is usually much smaller).""" + + # Algorithm: to compute exp(z) for a real number z, first divide z + # by a suitable power R of 2 so that |z/2**R| < 2**-L. Then + # compute expm1(z/2**R) = exp(z/2**R) - 1 using the usual Taylor + # series + # + # expm1(x) = x + x**2/2! + x**3/3! + ... + # + # Now use the identity + # + # expm1(2x) = expm1(x)*(expm1(x)+2) + # + # R times to compute the sequence expm1(z/2**R), + # expm1(z/2**(R-1)), ... , exp(z/2), exp(z). + + # Find R such that x/2**R/M <= 2**-L + R = _nbits((x< M + T = -int(-10*len(str(M))//(3*L)) + y = _div_nearest(x, T) + Mshift = M<= 0: + cshift = c*10**shift + else: + cshift = c//10**-shift + quot, rem = divmod(cshift, _log10_digits(q)) + + # reduce remainder back to original precision + rem = _div_nearest(rem, 10**extra) + + # error in result of _iexp < 120; error after division < 0.62 + return _div_nearest(_iexp(rem, 10**p), 1000), quot - p + 3 + +def _dpower(xc, xe, yc, ye, p): + """Given integers xc, xe, yc and ye representing Decimals x = xc*10**xe and + y = yc*10**ye, compute x**y. Returns a pair of integers (c, e) such that: + + 10**(p-1) <= c <= 10**p, and + (c-1)*10**e < x**y < (c+1)*10**e + + in other words, c*10**e is an approximation to x**y with p digits + of precision, and with an error in c of at most 1. (This is + almost, but not quite, the same as the error being < 1ulp: when c + == 10**(p-1) we can only guarantee error < 10ulp.) + + We assume that: x is positive and not equal to 1, and y is nonzero. + """ + + # Find b such that 10**(b-1) <= |y| <= 10**b + b = len(str(abs(yc))) + ye + + # log(x) = lxc*10**(-p-b-1), to p+b+1 places after the decimal point + lxc = _dlog(xc, xe, p+b+1) + + # compute product y*log(x) = yc*lxc*10**(-p-b-1+ye) = pc*10**(-p-1) + shift = ye-b + if shift >= 0: + pc = lxc*yc*10**shift + else: + pc = _div_nearest(lxc*yc, 10**-shift) + + if pc == 0: + # we prefer a result that isn't exactly 1; this makes it + # easier to compute a correctly rounded result in __pow__ + if ((len(str(xc)) + xe >= 1) == (yc > 0)): # if x**y > 1: + coeff, exp = 10**(p-1)+1, 1-p + else: + coeff, exp = 10**p-1, -p + else: + coeff, exp = _dexp(pc, -(p+1), p+1) + coeff = _div_nearest(coeff, 10) + exp += 1 + + return coeff, exp + +def _log10_lb(c, correction = { + '1': 100, '2': 70, '3': 53, '4': 40, '5': 31, + '6': 23, '7': 16, '8': 10, '9': 5}): + """Compute a lower bound for 100*log10(c) for a positive integer c.""" + if c <= 0: + raise ValueError("The argument to _log10_lb should be nonnegative.") + str_c = str(c) + return 100*len(str_c) - correction[str_c[0]] + +##### Helper Functions #################################################### + +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): + context = getcontext() + if equality_op: + context.flags[FloatOperation] = 1 + else: + context._raise_error(FloatOperation, + "strict semantics for mixing floats and Decimals are enabled") + return self, Decimal.from_float(other) + return NotImplemented, NotImplemented + + +##### Setup Specific Contexts ############################################ + +# The default context prototype used by Context() +# Is mutable, so that new contexts can have different default values + +DefaultContext = Context( + prec=28, rounding=ROUND_HALF_EVEN, + traps=[DivisionByZero, Overflow, InvalidOperation], + flags=[], + Emax=999999, + Emin=-999999, + capitals=1, + clamp=0 +) + +# Pre-made alternate contexts offered by the specification +# Don't change these; the user should be able to select these +# contexts and be able to reproduce results from other implementations +# of the spec. + +BasicContext = Context( + prec=9, rounding=ROUND_HALF_UP, + traps=[DivisionByZero, Overflow, InvalidOperation, Clamped, Underflow], + flags=[], +) + +ExtendedContext = Context( + prec=9, rounding=ROUND_HALF_EVEN, + traps=[], + flags=[], +) + + +##### crud for parsing strings ############################################# +# +# Regular expression used for parsing numeric strings. Additional +# comments: +# +# 1. Uncomment the two '\s*' lines to allow leading and/or trailing +# whitespace. But note that the specification disallows whitespace in +# a numeric string. +# +# 2. For finite numbers (not infinities and NaNs) the body of the +# number between the optional sign and the optional exponent must have +# at least one decimal digit, possibly after the decimal point. The +# lookahead expression '(?=\d|\.\d)' checks this. + +import re +_parser = re.compile(r""" # A numeric string consists of: +# \s* + (?P[-+])? # an optional sign, followed by either... + ( + (?=\d|\.\d) # ...a number (with at least one digit) + (?P\d*) # having a (possibly empty) integer part + (\.(?P\d*))? # followed by an optional fractional part + (E(?P[-+]?\d+))? # followed by an optional exponent, or... + | + Inf(inity)? # ...an infinity, or... + | + (?Ps)? # ...an (optionally signaling) + NaN # NaN + (?P\d*) # with (possibly empty) diagnostic info. + ) +# \s* + \Z +""", re.VERBOSE | re.IGNORECASE).match + +_all_zeros = re.compile('0*$').match +_exact_half = re.compile('50*$').match + +##### PEP3101 support functions ############################################## +# The functions in this section have little to do with the Decimal +# class, and could potentially be reused or adapted for other pure +# Python numeric classes that want to implement __format__ +# +# A format specifier for Decimal looks like: +# +# [[fill]align][sign][#][0][minimumwidth][,][.precision][type] + +_parse_format_specifier_regex = re.compile(r"""\A +(?: + (?P.)? + (?P[<>=^]) +)? +(?P[-+ ])? +(?P\#)? +(?P0)? +(?P(?!0)\d+)? +(?P,)? +(?:\.(?P0|(?!0)\d+))? +(?P[eEfFgGn%])? +\Z +""", re.VERBOSE|re.DOTALL) + +del re + +# The locale module is only needed for the 'n' format specifier. The +# rest of the PEP 3101 code functions quite happily without it, so we +# don't care too much if locale isn't present. +try: + import locale as _locale +except ImportError: + pass + +def _parse_format_specifier(format_spec, _localeconv=None): + """Parse and validate a format specifier. + + Turns a standard numeric format specifier into a dict, with the + following entries: + + fill: fill character to pad field to minimum width + align: alignment type, either '<', '>', '=' or '^' + sign: either '+', '-' or ' ' + minimumwidth: nonnegative integer giving minimum width + zeropad: boolean, indicating whether to pad with zeros + thousands_sep: string to use as thousands separator, or '' + grouping: grouping for thousands separators, in format + used by localeconv + decimal_point: string to use for decimal point + precision: nonnegative integer giving precision, or None + type: one of the characters 'eEfFgG%', or None + + """ + m = _parse_format_specifier_regex.match(format_spec) + if m is None: + raise ValueError("Invalid format specifier: " + format_spec) + + # get the dictionary + format_dict = m.groupdict() + + # zeropad; defaults for fill and alignment. If zero padding + # is requested, the fill and align fields should be absent. + fill = format_dict['fill'] + align = format_dict['align'] + format_dict['zeropad'] = (format_dict['zeropad'] is not None) + if format_dict['zeropad']: + if fill is not None: + raise ValueError("Fill character conflicts with '0'" + " in format specifier: " + format_spec) + if align is not None: + raise ValueError("Alignment conflicts with '0' in " + "format specifier: " + format_spec) + format_dict['fill'] = fill 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: + format_dict['sign'] = '-' + + # minimumwidth defaults to 0; precision remains None if not given + format_dict['minimumwidth'] = int(format_dict['minimumwidth'] or '0') + if format_dict['precision'] is not None: + format_dict['precision'] = int(format_dict['precision']) + + # if format type is 'g' or 'G' then a precision of 0 makes little + # sense; convert it to 1. Same if format type is unspecified. + if format_dict['precision'] == 0: + if format_dict['type'] is None or format_dict['type'] in 'gGn': + format_dict['precision'] = 1 + + # determine thousands separator, grouping, and decimal separator, and + # add appropriate entries to format_dict + if format_dict['type'] == 'n': + # apart from separators, 'n' behaves just like 'g' + format_dict['type'] = 'g' + if _localeconv is None: + _localeconv = _locale.localeconv() + if format_dict['thousands_sep'] is not None: + raise ValueError("Explicit thousands separator conflicts with " + "'n' type in format specifier: " + format_spec) + format_dict['thousands_sep'] = _localeconv['thousands_sep'] + format_dict['grouping'] = _localeconv['grouping'] + format_dict['decimal_point'] = _localeconv['decimal_point'] + else: + if format_dict['thousands_sep'] is None: + format_dict['thousands_sep'] = '' + format_dict['grouping'] = [3, 0] + format_dict['decimal_point'] = '.' + + return format_dict + +def _format_align(sign, body, spec): + """Given an unpadded, non-aligned numeric string 'body' and sign + string 'sign', add padding and alignment conforming to the given + format specifier dictionary 'spec' (as produced by + parse_format_specifier). + + """ + # how much extra space do we have to play with? + minimumwidth = spec['minimumwidth'] + fill = spec['fill'] + padding = fill*(minimumwidth - len(sign) - len(body)) + + align = spec['align'] + if align == '<': + result = sign + body + padding + elif align == '>': + result = padding + sign + body + elif align == '=': + result = sign + padding + body + elif align == '^': + half = len(padding)//2 + result = padding[:half] + sign + body + padding[half:] + else: + raise ValueError('Unrecognised alignment field') + + return result + +def _group_lengths(grouping): + """Convert a localeconv-style grouping into a (possibly infinite) + iterable of integers representing group lengths. + + """ + # The result from localeconv()['grouping'], and the input to this + # function, should be a list of integers in one of the + # following three forms: + # + # (1) an empty list, or + # (2) nonempty list of positive integers + [0] + # (3) list of positive integers + [locale.CHAR_MAX], or + + from itertools import chain, repeat + if not grouping: + return [] + elif grouping[-1] == 0 and len(grouping) >= 2: + return chain(grouping[:-1], repeat(grouping[-2])) + elif grouping[-1] == _locale.CHAR_MAX: + return grouping[:-1] + else: + raise ValueError('unrecognised format for grouping') + +def _insert_thousands_sep(digits, spec, min_width=1): + """Insert thousands separators into a digit string. + + spec is a dictionary whose keys should include 'thousands_sep' and + 'grouping'; typically it's the result of parsing the format + specifier using _parse_format_specifier. + + The min_width keyword argument gives the minimum length of the + result, which will be padded on the left with zeros if necessary. + + If necessary, the zero padding adds an extra '0' on the left to + avoid a leading thousands separator. For example, inserting + commas every three digits in '123456', with min_width=8, gives + '0,123,456', even though that has length 9. + + """ + + sep = spec['thousands_sep'] + grouping = spec['grouping'] + + groups = [] + for l in _group_lengths(grouping): + if l <= 0: + raise ValueError("group length should be positive") + # max(..., 1) forces at least 1 digit to the left of a separator + l = min(max(len(digits), min_width, 1), l) + groups.append('0'*(l - len(digits)) + digits[-l:]) + digits = digits[:-l] + min_width -= l + if not digits and min_width <= 0: + break + min_width -= len(sep) + else: + l = max(len(digits), min_width, 1) + groups.append('0'*(l - len(digits)) + digits[-l:]) + return sep.join(reversed(groups)) + +def _format_sign(is_negative, spec): + """Determine sign character.""" + + if is_negative: + return '-' + elif spec['sign'] in ' +': + return spec['sign'] + else: + return '' + +def _format_number(is_negative, intpart, fracpart, exp, spec): + """Format a number, given the following data: + + is_negative: true if the number is negative, else false + intpart: string of digits that must appear before the decimal point + fracpart: string of digits that must come after the point + exp: exponent, as an integer + spec: dictionary resulting from parsing the format specifier + + This function uses the information in spec to: + insert separators (decimal separator and thousands separators) + format the sign + format the exponent + add trailing '%' for the '%' type + zero-pad if necessary + fill and align if necessary + """ + + sign = _format_sign(is_negative, spec) + + if fracpart or spec['alt']: + fracpart = spec['decimal_point'] + fracpart + + if exp != 0 or spec['type'] in 'eE': + echar = {'E': 'E', 'e': 'e', 'G': 'E', 'g': 'e'}[spec['type']] + fracpart += "{0}{1:+}".format(echar, exp) + if spec['type'] == '%': + fracpart += '%' + + if spec['zeropad']: + min_width = spec['minimumwidth'] - len(fracpart) - len(sign) + else: + min_width = 0 + intpart = _insert_thousands_sep(intpart, spec, min_width) + + return _format_align(sign, intpart+fracpart, spec) + + +##### Useful Constants (internal use only) ################################ + +# Reusable defaults +_Infinity = Decimal('Inf') +_NegativeInfinity = Decimal('-Inf') +_NaN = Decimal('NaN') +_Zero = Decimal(0) +_One = Decimal(1) +_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 +_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 + +# _PyHASH_10INV is the inverse of 10 modulo the prime _PyHASH_MODULUS +_PyHASH_10INV = pow(10, _PyHASH_MODULUS - 2, _PyHASH_MODULUS) +del sys diff --git a/lib/_pyio.py b/lib/_pyio.py index f1e3a792..f2fe4471 100644 --- a/lib/_pyio.py +++ b/lib/_pyio.py @@ -6,11 +6,18 @@ import abc import codecs import errno +import array +import stat +import sys # Import _thread instead of threading to reduce startup cost try: from _thread import allocate_lock as Lock except ImportError: from _dummy_thread import allocate_lock as Lock +if sys.platform in {'win32', 'cygwin'}: + from msvcrt import setmode as _setmode +else: + _setmode = None import io from io import (__all__, SEEK_SET, SEEK_CUR, SEEK_END) @@ -256,7 +263,7 @@ class OpenWrapper: Trick so that open won't become a bound method when stored as a class variable (as dbm.dumb does). - See initstdio() in Python/pythonrun.c. + See initstdio() in Python/pylifecycle.c. """ __doc__ = DocDescriptor() @@ -269,7 +276,7 @@ def __new__(cls, *args, **kwargs): try: UnsupportedOperation = io.UnsupportedOperation except AttributeError: - class UnsupportedOperation(ValueError, OSError): + class UnsupportedOperation(OSError, ValueError): pass @@ -289,8 +296,9 @@ class IOBase(metaclass=abc.ABCMeta): 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 - readinto) needed. Text I/O classes work with str data. + bytes. Other bytes-like objects are accepted as method arguments too. In + some cases (such as readinto), a writable object is required. Text I/O + classes work with str data. Note that calling any method (even inquiries) on a closed stream is undefined. Implementations may raise OSError in this case. @@ -383,7 +391,7 @@ def __del__(self): def seekable(self): """Return a bool indicating whether object supports random access. - If False, seek(), tell() and truncate() will raise UnsupportedOperation. + If False, seek(), tell() and truncate() will raise OSError. This method may need to do a test seek(). """ return False @@ -398,7 +406,7 @@ def _checkSeekable(self, msg=None): def readable(self): """Return a bool indicating whether object was opened for reading. - If False, read() will raise UnsupportedOperation. + If False, read() will raise OSError. """ return False @@ -412,7 +420,7 @@ def _checkReadable(self, msg=None): def writable(self): """Return a bool indicating whether object was opened for writing. - If False, write() and truncate() will raise UnsupportedOperation. + If False, write() and truncate() will raise OSError. """ return False @@ -432,7 +440,7 @@ def closed(self): return self.__closed def _checkClosed(self, msg=None): - """Internal: raise an ValueError if file is closed + """Internal: raise a ValueError if file is closed """ if self.closed: raise ValueError("I/O operation on closed file." @@ -589,7 +597,7 @@ def readall(self): return data def readinto(self, b): - """Read up to len(b) bytes into bytearray b. + """Read bytes into a pre-allocated bytes-like object b. 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. @@ -599,7 +607,8 @@ def readinto(self, b): def write(self, b): """Write the given buffer to the IO stream. - Returns the number of bytes written, which may be less than len(b). + Returns the number of bytes written, which may be less than the + length of b in bytes. """ self._unsupported("write") @@ -652,7 +661,7 @@ def read1(self, size=None): self._unsupported("read1") def readinto(self, b): - """Read up to len(b) bytes into bytearray b. + """Read bytes into a pre-allocated bytes-like object b. Like read(), this may issue multiple reads to the underlying raw stream, unless the latter is 'interactive'. @@ -662,23 +671,40 @@ def readinto(self, b): Raises BlockingIOError if the underlying raw stream has no data at the moment. """ - # XXX This ought to work with anything that supports the buffer API - data = self.read(len(b)) + + return self._readinto(b, read1=False) + + def readinto1(self, b): + """Read bytes into buffer *b*, using at most one system call + + 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. + """ + + return self._readinto(b, read1=True) + + def _readinto(self, b, read1): + if not isinstance(b, memoryview): + b = memoryview(b) + b = b.cast('B') + + if read1: + data = self.read1(len(b)) + else: + data = self.read(len(b)) n = len(data) - try: - b[:n] = data - except TypeError as err: - import array - if not isinstance(b, array.array): - raise err - b[:n] = array.array('b', data) + + b[:n] = data + return n def write(self, b): """Write the given bytes buffer to the IO stream. - Return the number of bytes written, which is never less than - len(b). + Return the number of bytes written, which is always the length of b + in bytes. Raises BlockingIOError if the buffer is full and the underlying raw stream cannot accept more data at the moment. @@ -763,12 +789,6 @@ def detach(self): def seekable(self): return self.raw.seekable() - def readable(self): - return self.raw.readable() - - def writable(self): - return self.raw.writable() - @property def raw(self): return self._raw @@ -790,13 +810,14 @@ def __getstate__(self): .format(self.__class__.__name__)) def __repr__(self): - clsname = self.__class__.__name__ + modname = self.__class__.__module__ + clsname = self.__class__.__qualname__ try: name = self.name except Exception: - return "<_pyio.{0}>".format(clsname) + return "<{}.{}>".format(modname, clsname) else: - return "<_pyio.{0} name={1!r}>".format(clsname, name) + return "<{}.{} name={!r}>".format(modname, clsname, name) ### Lower-level APIs ### @@ -865,7 +886,8 @@ def write(self, b): raise ValueError("write to closed file") if isinstance(b, str): raise TypeError("can't write str to binary stream") - n = len(b) + with memoryview(b) as view: + n = view.nbytes # Size of any bytes-like object if n == 0: return 0 pos = self._pos @@ -957,6 +979,9 @@ def __init__(self, raw, buffer_size=DEFAULT_BUFFER_SIZE): self._reset_read_buf() self._read_lock = Lock() + def readable(self): + return self.raw.readable() + def _reset_read_buf(self): self._read_buf = b"" self._read_pos = 0 @@ -993,10 +1018,7 @@ def _read_unlocked(self, n=None): current_size = 0 while True: # Read until EOF or until read() would block. - try: - chunk = self.raw.read() - except InterruptedError: - continue + chunk = self.raw.read() if chunk in empty_values: nodata_val = chunk break @@ -1015,16 +1037,13 @@ def _read_unlocked(self, n=None): chunks = [buf[pos:]] wanted = max(self.buffer_size, n) while avail < n: - try: - chunk = self.raw.read(wanted) - except InterruptedError: - continue + chunk = self.raw.read(wanted) if chunk in empty_values: nodata_val = chunk break avail += len(chunk) chunks.append(chunk) - # n is more then avail only when an EOF occurred or when + # n is more than avail only when an EOF occurred or when # read() would have blocked. n = min(n, avail) out = b"".join(chunks) @@ -1047,12 +1066,7 @@ def _peek_unlocked(self, n=0): have = len(self._read_buf) - self._read_pos if have < want or have <= 0: to_read = self.buffer_size - have - while True: - try: - current = self.raw.read(to_read) - except InterruptedError: - continue - break + current = self.raw.read(to_read) if current: self._read_buf = self._read_buf[self._read_pos:] + current self._read_pos = 0 @@ -1071,6 +1085,57 @@ def read1(self, size): return self._read_unlocked( min(size, len(self._read_buf) - self._read_pos)) + # Implementing readinto() and readinto1() is not strictly necessary (we + # could rely on the base class that provides an implementation in terms of + # read() and read1()). We do it anyway to keep the _pyio implementation + # similar to the io implementation (which implements the methods for + # performance reasons). + def _readinto(self, buf, read1): + """Read data into *buf* with at most one system call.""" + + # Need to create a memoryview object of type 'b', otherwise + # we may not be able to assign bytes to it, and slicing it + # would create a new object. + if not isinstance(buf, memoryview): + buf = memoryview(buf) + if buf.nbytes == 0: + return 0 + buf = buf.cast('B') + + written = 0 + with self._read_lock: + while written < len(buf): + + # First try to read from internal buffer + avail = min(len(self._read_buf) - self._read_pos, len(buf)) + if avail: + buf[written:written+avail] = \ + self._read_buf[self._read_pos:self._read_pos+avail] + self._read_pos += avail + written += avail + if written == len(buf): + break + + # If remaining space in callers buffer is larger than + # internal buffer, read directly into callers buffer + if len(buf) - written > self.buffer_size: + n = self.raw.readinto(buf[written:]) + if not n: + break # eof + written += n + + # Otherwise refill internal buffer - unless we're + # in read1 mode and already got some data + elif not (read1 and written): + if not self._peek_unlocked(1): + break # eof + + # In readinto1 mode, return as soon as we have some data + if read1 and written: + break + + return written + def tell(self): return _BufferedIOMixin.tell(self) - len(self._read_buf) + self._read_pos @@ -1104,6 +1169,9 @@ def __init__(self, raw, buffer_size=DEFAULT_BUFFER_SIZE): self._write_buf = bytearray() self._write_lock = Lock() + def writable(self): + return self.raw.writable() + def write(self, b): if self.closed: raise ValueError("write to closed file") @@ -1149,8 +1217,6 @@ def _flush_unlocked(self): while self._write_buf: try: n = self.raw.write(self._write_buf) - except InterruptedError: - continue except BlockingIOError: raise RuntimeError("self.raw should implement RawIOBase: it " "should not raise BlockingIOError") @@ -1220,6 +1286,9 @@ def peek(self, size=0): def read1(self, size): return self.reader.read1(size) + def readinto1(self, b): + return self.reader.readinto1(b) + def readable(self): return self.reader.readable() @@ -1304,6 +1373,10 @@ def read1(self, size): self.flush() return BufferedReader.read1(self, size) + def readinto1(self, b): + self.flush() + return BufferedReader.readinto1(self, b) + def write(self, b): if self._read_buf: # Undo readahead @@ -1313,6 +1386,345 @@ def write(self, b): return BufferedWriter.write(self, b) +class FileIO(RawIOBase): + _fd = -1 + _created = False + _readable = False + _writable = False + _appending = False + _seekable = None + _closefd = True + + def __init__(self, file, mode='r', closefd=True, opener=None): + """Open a file. The mode can be 'r' (default), 'w', 'x' or 'a' for reading, + writing, exclusive creation or appending. The file will be created if it + doesn't exist when opened for writing or appending; it will be truncated + when opened for writing. A FileExistsError will be raised if it already + exists when opened for creating. Opening a file for creating implies + writing so this mode behaves in a similar way to 'w'. Add a '+' to the mode + to allow simultaneous reading and writing. A custom opener can be used by + passing a callable as *opener*. The underlying file descriptor for the file + object is then obtained by calling opener with (*name*, *flags*). + *opener* must return an open file descriptor (passing os.open as *opener* + results in functionality similar to passing None). + """ + if self._fd >= 0: + # Have to close the existing file first. + try: + if self._closefd: + os.close(self._fd) + finally: + self._fd = -1 + + if isinstance(file, float): + raise TypeError('integer argument expected, got float') + if isinstance(file, int): + fd = file + if fd < 0: + raise ValueError('negative file descriptor') + else: + fd = -1 + + if not isinstance(mode, str): + raise TypeError('invalid mode: %s' % (mode,)) + if not set(mode) <= set('xrwab+'): + raise ValueError('invalid mode: %s' % (mode,)) + if sum(c in 'rwax' for c in mode) != 1 or mode.count('+') > 1: + raise ValueError('Must have exactly one of create/read/write/append ' + 'mode and at most one plus') + + if 'x' in mode: + self._created = True + self._writable = True + flags = os.O_EXCL | os.O_CREAT + elif 'r' in mode: + self._readable = True + flags = 0 + elif 'w' in mode: + self._writable = True + flags = os.O_CREAT | os.O_TRUNC + elif 'a' in mode: + self._writable = True + self._appending = True + flags = os.O_APPEND | os.O_CREAT + + if '+' in mode: + self._readable = True + self._writable = True + + if self._readable and self._writable: + flags |= os.O_RDWR + elif self._readable: + flags |= os.O_RDONLY + else: + flags |= os.O_WRONLY + + flags |= getattr(os, 'O_BINARY', 0) + + noinherit_flag = (getattr(os, 'O_NOINHERIT', 0) or + getattr(os, 'O_CLOEXEC', 0)) + flags |= noinherit_flag + + owned_fd = None + try: + if fd < 0: + if not closefd: + raise ValueError('Cannot use closefd=False with file name') + if opener is None: + fd = os.open(file, flags, 0o666) + else: + fd = opener(file, flags) + if not isinstance(fd, int): + raise TypeError('expected integer from opener') + if fd < 0: + raise OSError('Negative file descriptor') + owned_fd = fd + if not noinherit_flag: + os.set_inheritable(fd, False) + + self._closefd = closefd + fdfstat = os.fstat(fd) + try: + if stat.S_ISDIR(fdfstat.st_mode): + raise IsADirectoryError(errno.EISDIR, + os.strerror(errno.EISDIR), file) + except AttributeError: + # Ignore the AttribueError if stat.S_ISDIR or errno.EISDIR + # don't exist. + pass + self._blksize = getattr(fdfstat, 'st_blksize', 0) + if self._blksize <= 1: + self._blksize = DEFAULT_BUFFER_SIZE + + if _setmode: + # don't translate newlines (\r\n <=> \n) + _setmode(fd, os.O_BINARY) + + self.name = file + if self._appending: + # For consistent behaviour, we explicitly seek to the + # end of file (otherwise, it might be done only on the + # first write()). + os.lseek(fd, 0, SEEK_END) + except: + if owned_fd is not None: + os.close(owned_fd) + raise + self._fd = fd + + def __del__(self): + if self._fd >= 0 and self._closefd and not self.closed: + import warnings + warnings.warn('unclosed file %r' % (self,), ResourceWarning, + stacklevel=2) + self.close() + + def __getstate__(self): + raise TypeError("cannot serialize '%s' object", self.__class__.__name__) + + def __repr__(self): + class_name = '%s.%s' % (self.__class__.__module__, + self.__class__.__qualname__) + if self.closed: + return '<%s [closed]>' % class_name + try: + name = self.name + except AttributeError: + return ('<%s fd=%d mode=%r closefd=%r>' % + (class_name, self._fd, self.mode, self._closefd)) + else: + return ('<%s name=%r mode=%r closefd=%r>' % + (class_name, name, self.mode, self._closefd)) + + def _checkReadable(self): + if not self._readable: + raise UnsupportedOperation('File not open for reading') + + def _checkWritable(self, msg=None): + if not self._writable: + raise UnsupportedOperation('File not open for writing') + + def read(self, size=None): + """Read at most size bytes, returned as bytes. + + Only makes one system call, so less data may be returned than requested + In non-blocking mode, returns None if no data is available. + Return an empty bytes object at EOF. + """ + self._checkClosed() + self._checkReadable() + if size is None or size < 0: + return self.readall() + try: + return os.read(self._fd, size) + except BlockingIOError: + return None + + def readall(self): + """Read all data from the file, returned as bytes. + + In non-blocking mode, returns as much as is immediately available, + or None if no data is available. Return an empty bytes object at EOF. + """ + self._checkClosed() + self._checkReadable() + bufsize = DEFAULT_BUFFER_SIZE + try: + pos = os.lseek(self._fd, 0, SEEK_CUR) + end = os.fstat(self._fd).st_size + if end >= pos: + bufsize = end - pos + 1 + except OSError: + pass + + result = bytearray() + while True: + if len(result) >= bufsize: + bufsize = len(result) + bufsize += max(bufsize, DEFAULT_BUFFER_SIZE) + n = bufsize - len(result) + try: + chunk = os.read(self._fd, n) + except BlockingIOError: + if result: + break + return None + if not chunk: # reached the end of the file + break + result += chunk + + return bytes(result) + + def readinto(self, b): + """Same as RawIOBase.readinto().""" + m = memoryview(b).cast('B') + data = self.read(len(m)) + n = len(data) + m[:n] = data + return n + + def write(self, b): + """Write bytes b to file, return number written. + + Only makes one system call, so not all of the data may be written. + The number of bytes actually written is returned. In non-blocking mode, + returns None if the write would block. + """ + self._checkClosed() + self._checkWritable() + try: + return os.write(self._fd, b) + except BlockingIOError: + return None + + def seek(self, pos, whence=SEEK_SET): + """Move to new file position. + + Argument offset is a byte count. Optional argument whence defaults to + SEEK_SET or 0 (offset from start of file, offset should be >= 0); other values + are SEEK_CUR or 1 (move relative to current position, positive or negative), + and SEEK_END or 2 (move relative to end of file, usually negative, although + many platforms allow seeking beyond the end of a file). + + Note that not all file objects are seekable. + """ + if isinstance(pos, float): + raise TypeError('an integer is required') + self._checkClosed() + return os.lseek(self._fd, pos, whence) + + def tell(self): + """tell() -> int. Current file position. + + Can raise OSError for non seekable files.""" + self._checkClosed() + return os.lseek(self._fd, 0, SEEK_CUR) + + def truncate(self, size=None): + """Truncate the file to at most size bytes. + + Size defaults to the current file position, as returned by tell(). + The current file position is changed to the value of size. + """ + self._checkClosed() + self._checkWritable() + if size is None: + size = self.tell() + os.ftruncate(self._fd, size) + return size + + def close(self): + """Close the file. + + A closed file cannot be used for further I/O operations. close() may be + called more than once without error. + """ + if not self.closed: + try: + if self._closefd: + os.close(self._fd) + finally: + super().close() + + def seekable(self): + """True if file supports random-access.""" + self._checkClosed() + if self._seekable is None: + try: + self.tell() + except OSError: + self._seekable = False + else: + self._seekable = True + return self._seekable + + def readable(self): + """True if file was opened in a read mode.""" + self._checkClosed() + return self._readable + + def writable(self): + """True if file was opened in a write mode.""" + self._checkClosed() + return self._writable + + def fileno(self): + """Return the underlying file descriptor (an integer).""" + self._checkClosed() + return self._fd + + def isatty(self): + """True if the file is connected to a TTY device.""" + self._checkClosed() + return os.isatty(self._fd) + + @property + def closefd(self): + """True if the file descriptor will be closed by close().""" + return self._closefd + + @property + def mode(self): + """String giving the file mode""" + if self._created: + if self._readable: + return 'xb+' + else: + return 'xb' + elif self._appending: + if self._readable: + return 'ab+' + else: + return 'ab' + elif self._readable: + if self._writable: + return 'rb+' + else: + return 'rb' + else: + return 'wb' + + class TextIOBase(IOBase): """Base class for text I/O. @@ -1566,7 +1978,8 @@ def __init__(self, buffer, encoding=None, errors=None, newline=None, # - "chars_..." for integer variables that count decoded characters def __repr__(self): - result = "<_pyio.TextIOWrapper" + result = "<{}.{}".format(self.__class__.__module__, + self.__class__.__qualname__) try: name = self.name except Exception: diff --git a/lib/_strptime.py b/lib/_strptime.py index 6d648568..f84227be 100644 --- a/lib/_strptime.py +++ b/lib/_strptime.py @@ -171,9 +171,9 @@ def __calc_timezone(self): pass self.tzname = time.tzname self.daylight = time.daylight - no_saving = frozenset(["utc", "gmt", self.tzname[0].lower()]) + no_saving = frozenset({"utc", "gmt", self.tzname[0].lower()}) if self.daylight: - has_saving = frozenset([self.tzname[1].lower()]) + has_saving = frozenset({self.tzname[1].lower()}) else: has_saving = frozenset() self.timezone = (no_saving, has_saving) @@ -257,8 +257,8 @@ def pattern(self, format): # format directives (%m, etc.). regex_chars = re_compile(r"([\\.^$*+?\(\){}\[\]|])") format = regex_chars.sub(r"\\\1", format) - whitespace_replacement = re_compile('\s+') - format = whitespace_replacement.sub('\s+', format) + whitespace_replacement = re_compile(r'\s+') + format = whitespace_replacement.sub(r'\\s+', format) while '%' in format: directive_index = format.index('%')+1 processed_format = "%s%s%s" % (processed_format, @@ -311,7 +311,6 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): global _TimeRE_cache, _regex_cache with _cache_lock: - locale_time = _TimeRE_cache.locale_time if (_getlang() != locale_time.lang or time.tzname != locale_time.tzname or @@ -463,6 +462,10 @@ def _strptime(data_string, format="%a %b %d %H:%M:%S %Y"): week_starts_Mon = True if week_of_year_start == 0 else False julian = _calc_julian_from_U_or_W(year, week_of_year, weekday, week_starts_Mon) + if julian <= 0: + year -= 1 + yday = 366 if calendar.isleap(year) else 365 + julian += yday # Cannot pre-calculate datetime_date() since can change in Julian # calculation and thus could have different value for the day of the week # calculation. diff --git a/lib/abc.py b/lib/abc.py index 0358a469..1cbf96a6 100644 --- a/lib/abc.py +++ b/lib/abc.py @@ -168,7 +168,7 @@ def register(cls, subclass): def _dump_registry(cls, file=None): """Debug helper to print the ABC registry.""" - print("Class: %s.%s" % (cls.__module__, cls.__name__), file=file) + print("Class: %s.%s" % (cls.__module__, cls.__qualname__), file=file) print("Inv.counter: %s" % ABCMeta._abc_invalidation_counter, file=file) for name in sorted(cls.__dict__.keys()): if name.startswith("_abc_"): diff --git a/lib/aifc.py b/lib/aifc.py index 7ebdbeb6..d5764a33 100644 --- a/lib/aifc.py +++ b/lib/aifc.py @@ -294,6 +294,8 @@ class Aifc_read: # _ssnd_chunk -- instantiation of a chunk class for the SSND chunk # _framesize -- size of one frame in the file + _file = None # Set here since __del__ checks it + def initfp(self, file): self._version = 0 self._convert = None @@ -335,9 +337,15 @@ def initfp(self, file): def __init__(self, f): if isinstance(f, str): - f = builtins.open(f, 'rb') - # else, assume it is an open file object already - self.initfp(f) + file_object = builtins.open(f, 'rb') + try: + self.initfp(file_object) + except: + file_object.close() + raise + else: + # assume it is an open file object already + self.initfp(f) def __enter__(self): return self @@ -532,18 +540,23 @@ class Aifc_write: # _datalength -- the size of the audio samples written to the header # _datawritten -- the size of the audio samples actually written + _file = None # Set here since __del__ checks it + def __init__(self, f): if isinstance(f, str): - filename = f - f = builtins.open(f, 'wb') - else: - # else, assume it is an open file object already - filename = '???' - self.initfp(f) - if filename[-5:] == '.aiff': - self._aifc = 0 + file_object = builtins.open(f, 'wb') + try: + self.initfp(file_object) + except: + file_object.close() + raise + + # treat .aiff file extensions as non-compressed audio + if f.endswith('.aiff'): + self._aifc = 0 else: - self._aifc = 1 + # assume it is an open file object already + self.initfp(f) def initfp(self, file): self._file = file diff --git a/lib/antigravity.py b/lib/antigravity.py index 7670187f..9b143680 100644 --- a/lib/antigravity.py +++ b/lib/antigravity.py @@ -2,7 +2,7 @@ import webbrowser import hashlib -webbrowser.open("http://xkcd.com/353/") +webbrowser.open("https://xkcd.com/353/") def geohash(latitude, longitude, datedow): '''Compute geohash() using the Munroe algorithm. diff --git a/lib/argparse.py b/lib/argparse.py index be276bb6..0d881b8d 100644 --- a/lib/argparse.py +++ b/lib/argparse.py @@ -176,7 +176,7 @@ def __init__(self, self._root_section = self._Section(self, None) self._current_section = self._root_section - self._whitespace_matcher = _re.compile(r'\s+') + self._whitespace_matcher = _re.compile(r'\s+', _re.ASCII) self._long_break_matcher = _re.compile(r'\n\n\n+') # =============================== @@ -1209,11 +1209,6 @@ def __eq__(self, other): return NotImplemented return vars(self) == vars(other) - def __ne__(self, other): - if not isinstance(other, Namespace): - return NotImplemented - return not (self == other) - def __contains__(self, key): return key in self.__dict__ @@ -1595,6 +1590,7 @@ class ArgumentParser(_AttributeHolder, _ActionsContainer): - argument_default -- The default value for all arguments - conflict_handler -- String indicating how to handle conflicts - add_help -- Add a -h/-help option + - allow_abbrev -- Allow long options to be abbreviated unambiguously """ def __init__(self, @@ -1608,7 +1604,8 @@ def __init__(self, fromfile_prefix_chars=None, argument_default=None, conflict_handler='error', - add_help=True): + add_help=True, + allow_abbrev=True): superinit = super(ArgumentParser, self).__init__ superinit(description=description, @@ -1626,6 +1623,7 @@ def __init__(self, self.formatter_class = formatter_class self.fromfile_prefix_chars = fromfile_prefix_chars self.add_help = add_help + self.allow_abbrev = allow_abbrev add_group = self.add_argument_group self._positionals = add_group(_('positional arguments')) @@ -2103,23 +2101,24 @@ def _parse_optional(self, arg_string): 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 self.allow_abbrev: + # 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 diff --git a/lib/ast.py b/lib/ast.py index 02c3b286..01704727 100644 --- a/lib/ast.py +++ b/lib/ast.py @@ -194,7 +194,7 @@ def get_docstring(node, clean=True): be found. If the node provided does not have docstrings a TypeError will be raised. """ - if not isinstance(node, (FunctionDef, ClassDef, Module)): + if not isinstance(node, (AsyncFunctionDef, FunctionDef, ClassDef, Module)): raise TypeError("%r can't have docstrings" % node.__class__.__name__) if node.body and isinstance(node.body[0], Expr) and \ isinstance(node.body[0].value, Str): @@ -293,7 +293,6 @@ def visit_Name(self, node): def generic_visit(self, node): for field, old_value in iter_fields(node): - old_value = getattr(node, field, None) if isinstance(old_value, list): new_values = [] for value in old_value: diff --git a/lib/asynchat.py b/lib/asynchat.py index 14c152f0..f728d1b4 100644 --- a/lib/asynchat.py +++ b/lib/asynchat.py @@ -287,6 +287,9 @@ def more(self): class fifo: def __init__(self, list=None): + import warnings + warnings.warn('fifo class will be removed in Python 3.6', + DeprecationWarning, stacklevel=2) if not list: self.list = deque() else: diff --git a/lib/asyncio/base_events.py b/lib/asyncio/base_events.py index 4505732f..26a6f1ef 100644 --- a/lib/asyncio/base_events.py +++ b/lib/asyncio/base_events.py @@ -13,13 +13,10 @@ to modify the meaning of the API call itself. """ - import collections import concurrent.futures -import functools import heapq import inspect -import ipaddress import itertools import logging import os @@ -30,6 +27,7 @@ import traceback import sys import warnings +import weakref from . import compat from . import coroutines @@ -43,9 +41,6 @@ __all__ = ['BaseEventLoop'] -# Argument for default thread pool executor creation. -_MAX_WORKERS = 5 - # Minimum number of _scheduled timer handles before cleanup of # cancelled handles is performed. _MIN_SCHEDULED_TIMER_HANDLES = 100 @@ -54,6 +49,12 @@ # before cleanup of cancelled handles is performed. _MIN_CANCELLED_TIMER_HANDLES_FRACTION = 0.5 +# Exceptions which must not call the exception handler in fatal error +# methods (_fatal_error()) +_FATAL_ERROR_IGNORE = (BrokenPipeError, + ConnectionResetError, ConnectionAbortedError) + + def _format_handle(handle): cb = handle._callback if inspect.ismethod(cb) and isinstance(cb.__self__, tasks.Task): @@ -72,83 +73,104 @@ def _format_pipe(fd): return repr(fd) -# Linux's sock.type is a bitmask that can include extra info about socket. -_SOCKET_TYPE_MASK = 0 -if hasattr(socket, 'SOCK_NONBLOCK'): - _SOCKET_TYPE_MASK |= socket.SOCK_NONBLOCK -if hasattr(socket, 'SOCK_CLOEXEC'): - _SOCKET_TYPE_MASK |= socket.SOCK_CLOEXEC +def _set_reuseport(sock): + if not hasattr(socket, 'SO_REUSEPORT'): + raise ValueError('reuse_port not supported by socket module') + else: + try: + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + except OSError: + raise ValueError('reuse_port not supported by socket module, ' + 'SO_REUSEPORT defined but not implemented.') + + +def _is_stream_socket(sock): + # Linux's socket.type is a bitmask that can include extra info + # about socket, therefore we can't do simple + # `sock_type == socket.SOCK_STREAM`. + return (sock.type & socket.SOCK_STREAM) == socket.SOCK_STREAM + + +def _is_dgram_socket(sock): + # Linux's socket.type is a bitmask that can include extra info + # about socket, therefore we can't do simple + # `sock_type == socket.SOCK_DGRAM`. + return (sock.type & socket.SOCK_DGRAM) == socket.SOCK_DGRAM -@functools.lru_cache(maxsize=1024) def _ipaddr_info(host, port, family, type, proto): - # Try to skip getaddrinfo if "host" is already an IP. Since getaddrinfo - # blocks on an exclusive lock on some platforms, users might handle name - # resolution in their own code and pass in resolved IPs. - if proto not in {0, socket.IPPROTO_TCP, socket.IPPROTO_UDP} or host is None: + # Try to skip getaddrinfo if "host" is already an IP. Users might have + # handled name resolution in their own code and pass in resolved IPs. + if not hasattr(socket, 'inet_pton'): + return + + if proto not in {0, socket.IPPROTO_TCP, socket.IPPROTO_UDP} or \ + host is None: return None - type &= ~_SOCKET_TYPE_MASK if type == socket.SOCK_STREAM: + # Linux only: + # getaddrinfo() can raise when socket.type is a bit mask. + # So if socket.type is a bit mask of SOCK_STREAM, and say + # SOCK_NONBLOCK, we simply return None, which will trigger + # a call to getaddrinfo() letting it process this request. proto = socket.IPPROTO_TCP elif type == socket.SOCK_DGRAM: proto = socket.IPPROTO_UDP else: return None - if hasattr(socket, 'inet_pton'): - if family == socket.AF_UNSPEC: - afs = [socket.AF_INET, socket.AF_INET6] - else: - afs = [family] - - for af in afs: - # Linux's inet_pton doesn't accept an IPv6 zone index after host, - # like '::1%lo0', so strip it. If we happen to make an invalid - # address look valid, we fail later in sock.connect or sock.bind. - try: - if af == socket.AF_INET6: - socket.inet_pton(af, host.partition('%')[0]) - else: - socket.inet_pton(af, host) - return af, type, proto, '', (host, port) - except OSError: - pass - - # "host" is not an IP address. - return None - - # No inet_pton. (On Windows it's only available since Python 3.4.) - # Even though getaddrinfo with AI_NUMERICHOST would be non-blocking, it - # still requires a lock on some platforms, and waiting for that lock could - # block the event loop. Use ipaddress instead, it's just text parsing. - try: - addr = ipaddress.IPv4Address(host) - except ValueError: + if port is None: + port = 0 + elif isinstance(port, bytes) and port == b'': + port = 0 + elif isinstance(port, str) and port == '': + port = 0 + else: + # If port's a service name like "http", don't skip getaddrinfo. try: - addr = ipaddress.IPv6Address(host.partition('%')[0]) - except ValueError: + port = int(port) + except (TypeError, ValueError): return None - af = socket.AF_INET if addr.version == 4 else socket.AF_INET6 - if family not in (socket.AF_UNSPEC, af): - # "host" is wrong IP version for "family". - return None + if family == socket.AF_UNSPEC: + afs = [socket.AF_INET] + if hasattr(socket, 'AF_INET6'): + afs.append(socket.AF_INET6) + else: + afs = [family] - return af, type, proto, '', (host, port) + if isinstance(host, bytes): + host = host.decode('idna') + if '%' in host: + # Linux's inet_pton doesn't accept an IPv6 zone index after host, + # like '::1%lo0'. + return None + for af in afs: + try: + socket.inet_pton(af, host) + # The host has already been resolved. + return af, type, proto, '', (host, port) + except OSError: + pass -def _check_resolved_address(sock, address): - # Ensure that the address is already resolved to avoid the trap of hanging - # the entire event loop when the address requires doing a DNS lookup. + # "host" is not an IP address. + return None - if hasattr(socket, 'AF_UNIX') and sock.family == socket.AF_UNIX: - return +def _ensure_resolved(address, *, family=0, type=socket.SOCK_STREAM, proto=0, + flags=0, loop): host, port = address[:2] - if _ipaddr_info(host, port, sock.family, sock.type, sock.proto) is None: - raise ValueError("address must be resolved (IP address)," - " got host %r" % host) + info = _ipaddr_info(host, port, family, type, proto) + if info is not None: + # "host" is already a resolved IP. + fut = loop.create_future() + fut.set_result([info]) + return fut + else: + return loop.getaddrinfo(host, port, family=family, type=type, + proto=proto, flags=flags) def _run_until_complete_cb(fut): @@ -203,7 +225,7 @@ def _wakeup(self): def wait_closed(self): if self.sockets is None or self._waiters is None: return - waiter = futures.Future(loop=self._loop) + waiter = self._loop.create_future() self._waiters.append(waiter) yield from waiter @@ -232,11 +254,26 @@ def __init__(self): self._task_factory = None self._coroutine_wrapper_set = False + if hasattr(sys, 'get_asyncgen_hooks'): + # Python >= 3.6 + # A weak set of all asynchronous generators that are + # being iterated by the loop. + self._asyncgens = weakref.WeakSet() + else: + self._asyncgens = None + + # Set to True when `loop.shutdown_asyncgens` is called. + self._asyncgens_shutdown_called = False + def __repr__(self): return ('<%s running=%s closed=%s debug=%s>' % (self.__class__.__name__, self.is_running(), self.is_closed(), self.get_debug())) + def create_future(self): + """Create a Future object attached to the loop.""" + return futures.Future(loop=self) + def create_task(self, coro): """Schedule a coroutine object. @@ -319,14 +356,67 @@ def _check_closed(self): if self._closed: raise RuntimeError('Event loop is closed') + def _asyncgen_finalizer_hook(self, agen): + self._asyncgens.discard(agen) + if not self.is_closed(): + self.create_task(agen.aclose()) + # Wake up the loop if the finalizer was called from + # a different thread. + self._write_to_self() + + def _asyncgen_firstiter_hook(self, agen): + if self._asyncgens_shutdown_called: + warnings.warn( + "asynchronous generator {!r} was scheduled after " + "loop.shutdown_asyncgens() call".format(agen), + ResourceWarning, source=self) + + self._asyncgens.add(agen) + + @coroutine + def shutdown_asyncgens(self): + """Shutdown all active asynchronous generators.""" + self._asyncgens_shutdown_called = True + + if self._asyncgens is None or not len(self._asyncgens): + # If Python version is <3.6 or we don't have any asynchronous + # generators alive. + return + + closing_agens = list(self._asyncgens) + self._asyncgens.clear() + + shutdown_coro = tasks.gather( + *[ag.aclose() for ag in closing_agens], + return_exceptions=True, + loop=self) + + results = yield from shutdown_coro + for result, agen in zip(results, closing_agens): + if isinstance(result, Exception): + self.call_exception_handler({ + 'message': 'an error occurred during closing of ' + 'asynchronous generator {!r}'.format(agen), + 'exception': result, + 'asyncgen': agen + }) + def run_forever(self): """Run until stop() is called.""" self._check_closed() if self.is_running(): - raise RuntimeError('Event loop is running.') + raise RuntimeError('This event loop is already running') + if events._get_running_loop() is not None: + raise RuntimeError( + 'Cannot run the event loop while another loop is running') self._set_coroutine_wrapper(self._debug) self._thread_id = threading.get_ident() + if self._asyncgens is not None: + old_agen_hooks = sys.get_asyncgen_hooks() + sys.set_asyncgen_hooks(firstiter=self._asyncgen_firstiter_hook, + finalizer=self._asyncgen_finalizer_hook) try: + events._set_running_loop(self) while True: self._run_once() if self._stopping: @@ -334,7 +424,10 @@ def run_forever(self): finally: self._stopping = False self._thread_id = None + events._set_running_loop(None) self._set_coroutine_wrapper(False) + if self._asyncgens is not None: + sys.set_asyncgen_hooks(*old_agen_hooks) def run_until_complete(self, future): """Run until the Future is done. @@ -349,7 +442,7 @@ def run_until_complete(self, future): """ self._check_closed() - new_task = not isinstance(future, futures.Future) + new_task = not futures.isfuture(future) future = tasks.ensure_future(future, loop=self) if new_task: # An exception is raised if the future didn't complete, so there @@ -366,7 +459,8 @@ def run_until_complete(self, future): # local task. future.exception() raise - future.remove_done_callback(_run_until_complete_cb) + finally: + future.remove_done_callback(_run_until_complete_cb) if not future.done(): raise RuntimeError('Event loop stopped before Future completed.') @@ -455,12 +549,10 @@ def call_at(self, when, callback, *args): Absolute time corresponds to the event loop's time() method. """ - if (coroutines.iscoroutine(callback) - or coroutines.iscoroutinefunction(callback)): - raise TypeError("coroutines cannot be used with call_at()") self._check_closed() if self._debug: self._check_thread() + self._check_callback(callback, 'call_at') timer = events.TimerHandle(when, callback, args, self) if timer._source_traceback: del timer._source_traceback[-1] @@ -478,18 +570,27 @@ def call_soon(self, callback, *args): Any positional arguments after the callback will be passed to the callback when it is called. """ + self._check_closed() if self._debug: self._check_thread() + self._check_callback(callback, 'call_soon') handle = self._call_soon(callback, args) if handle._source_traceback: del handle._source_traceback[-1] return handle + def _check_callback(self, callback, method): + if (coroutines.iscoroutine(callback) or + coroutines.iscoroutinefunction(callback)): + raise TypeError( + "coroutines cannot be used with {}()".format(method)) + if not callable(callback): + raise TypeError( + 'a callable object was expected by {}(), got {!r}'.format( + method, callback)) + + def _call_soon(self, callback, args): - if (coroutines.iscoroutine(callback) - or coroutines.iscoroutinefunction(callback)): - raise TypeError("coroutines cannot be used with call_soon()") - self._check_closed() handle = events.Handle(callback, args, self) if handle._source_traceback: del handle._source_traceback[-1] @@ -515,6 +616,9 @@ def _check_thread(self): def call_soon_threadsafe(self, callback, *args): """Like call_soon(), but thread-safe.""" + self._check_closed() + if self._debug: + self._check_callback(callback, 'call_soon_threadsafe') handle = self._call_soon(callback, args) if handle._source_traceback: del handle._source_traceback[-1] @@ -522,22 +626,13 @@ def call_soon_threadsafe(self, callback, *args): return handle def run_in_executor(self, executor, func, *args): - if (coroutines.iscoroutine(func) - or coroutines.iscoroutinefunction(func)): - raise TypeError("coroutines cannot be used with run_in_executor()") self._check_closed() - if isinstance(func, events.Handle): - assert not args - assert not isinstance(func, events.TimerHandle) - if func._cancelled: - f = futures.Future(loop=self) - f.set_result(None) - return f - func, args = func._callback, func._args + if self._debug: + self._check_callback(func, 'run_in_executor') if executor is None: executor = self._default_executor if executor is None: - executor = concurrent.futures.ThreadPoolExecutor(_MAX_WORKERS) + executor = concurrent.futures.ThreadPoolExecutor() self._default_executor = executor return futures.wrap_future(executor.submit(func, *args), loop=self) @@ -571,12 +666,7 @@ def _getaddrinfo_debug(self, host, port, family, type, proto, flags): def getaddrinfo(self, host, port, *, family=0, type=0, proto=0, flags=0): - info = _ipaddr_info(host, port, family, type, proto) - if info is not None: - fut = futures.Future(loop=self) - fut.set_result([info]) - return fut - elif self._debug: + if self._debug: return self.run_in_executor(None, self._getaddrinfo_debug, host, port, family, type, proto, flags) else: @@ -625,14 +715,14 @@ def create_connection(self, protocol_factory, host=None, port=None, *, raise ValueError( 'host/port and sock can not be specified at the same time') - f1 = self.getaddrinfo( - host, port, family=family, - type=socket.SOCK_STREAM, proto=proto, flags=flags) + f1 = _ensure_resolved((host, port), family=family, + type=socket.SOCK_STREAM, proto=proto, + flags=flags, loop=self) fs = [f1] if local_addr is not None: - f2 = self.getaddrinfo( - *local_addr, family=family, - type=socket.SOCK_STREAM, proto=proto, flags=flags) + f2 = _ensure_resolved(local_addr, family=family, + type=socket.SOCK_STREAM, proto=proto, + flags=flags, loop=self) fs.append(f2) else: f2 = None @@ -694,11 +784,19 @@ def create_connection(self, protocol_factory, host=None, port=None, *, raise OSError('Multiple exceptions: {}'.format( ', '.join(str(exc) for exc in exceptions))) - elif sock is None: - raise ValueError( - 'host and port was not specified and no sock specified') - - sock.setblocking(False) + else: + if sock is None: + raise ValueError( + 'host and port was not specified and no sock specified') + if not _is_stream_socket(sock): + # We allow AF_INET, AF_INET6, AF_UNIX as long as they + # are SOCK_STREAM. + # We support passing AF_UNIX sockets even though we have + # a dedicated API for that: create_unix_connection. + # Disallowing AF_UNIX in this method, breaks backwards + # compatibility. + raise ValueError( + 'A Stream Socket was expected, got {!r}'.format(sock)) transport, protocol = yield from self._create_connection_transport( sock, protocol_factory, ssl, server_hostname) @@ -712,14 +810,17 @@ def create_connection(self, protocol_factory, host=None, port=None, *, @coroutine def _create_connection_transport(self, sock, protocol_factory, ssl, - server_hostname): + server_hostname, server_side=False): + + sock.setblocking(False) + protocol = protocol_factory() - waiter = futures.Future(loop=self) + waiter = self.create_future() if ssl: sslcontext = None if isinstance(ssl, bool) else ssl transport = self._make_ssl_transport( sock, protocol, sslcontext, waiter, - server_side=False, server_hostname=server_hostname) + server_side=server_side, server_hostname=server_hostname) else: transport = self._make_socket_transport(sock, protocol, waiter) @@ -739,6 +840,9 @@ def create_datagram_endpoint(self, protocol_factory, allow_broadcast=None, sock=None): """Create datagram connection.""" if sock is not None: + if not _is_dgram_socket(sock): + raise ValueError( + 'A UDP Socket was expected, got {!r}'.format(sock)) if (local_addr or remote_addr or family or proto or flags or reuse_address or reuse_port or allow_broadcast): @@ -767,9 +871,9 @@ def create_datagram_endpoint(self, protocol_factory, assert isinstance(addr, tuple) and len(addr) == 2, ( '2-tuple is expected') - infos = yield from self.getaddrinfo( - *addr, family=family, type=socket.SOCK_DGRAM, - proto=proto, flags=flags) + infos = yield from _ensure_resolved( + addr, family=family, type=socket.SOCK_DGRAM, + proto=proto, flags=flags, loop=self) if not infos: raise OSError('getaddrinfo() returned empty list') @@ -804,12 +908,7 @@ def create_datagram_endpoint(self, protocol_factory, sock.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) if reuse_port: - if not hasattr(socket, 'SO_REUSEPORT'): - raise ValueError( - 'reuse_port not supported by socket module') - else: - sock.setsockopt( - socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + _set_reuseport(sock) if allow_broadcast: sock.setsockopt( socket.SOL_SOCKET, socket.SO_BROADCAST, 1) @@ -834,7 +933,7 @@ def create_datagram_endpoint(self, protocol_factory, raise exceptions[0] protocol = protocol_factory() - waiter = futures.Future(loop=self) + waiter = self.create_future() transport = self._make_datagram_transport( sock, protocol, r_addr, waiter) if self._debug: @@ -857,9 +956,9 @@ def create_datagram_endpoint(self, protocol_factory, @coroutine def _create_server_getaddrinfo(self, host, port, family, flags): - infos = yield from self.getaddrinfo(host, port, family=family, + infos = yield from _ensure_resolved((host, port), family=family, type=socket.SOCK_STREAM, - flags=flags) + flags=flags, loop=self) if not infos: raise OSError('getaddrinfo({!r}) returned empty list'.format(host)) return infos @@ -880,7 +979,10 @@ def create_server(self, protocol_factory, host=None, port=None, to host and port. The host parameter can also be a sequence of strings and in that case - the TCP server is bound to all hosts of the sequence. + the TCP server is bound to all hosts of the sequence. If a host + appears multiple times (possibly indirectly e.g. when hostnames + resolve to the same IP address), the server is only bound once to that + host. Return a Server object which can be used to stop the service. @@ -909,7 +1011,7 @@ def create_server(self, protocol_factory, host=None, port=None, flags=flags) for host in hosts] infos = yield from tasks.gather(*fs, loop=self) - infos = itertools.chain.from_iterable(infos) + infos = set(itertools.chain.from_iterable(infos)) completed = False try: @@ -929,12 +1031,7 @@ def create_server(self, protocol_factory, host=None, port=None, sock.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, True) if reuse_port: - if not hasattr(socket, 'SO_REUSEPORT'): - raise ValueError( - 'reuse_port not supported by socket module') - else: - sock.setsockopt( - socket.SOL_SOCKET, socket.SO_REUSEPORT, True) + _set_reuseport(sock) # Disable IPv4/IPv6 dual stack support (enabled by # default on Linux) which makes a single socket # listen on both address families. @@ -956,21 +1053,47 @@ def create_server(self, protocol_factory, host=None, port=None, else: if sock is None: raise ValueError('Neither host/port nor sock were specified') + if not _is_stream_socket(sock): + raise ValueError( + 'A Stream Socket was expected, got {!r}'.format(sock)) sockets = [sock] server = Server(self, sockets) for sock in sockets: sock.listen(backlog) sock.setblocking(False) - self._start_serving(protocol_factory, sock, ssl, server) + self._start_serving(protocol_factory, sock, ssl, server, backlog) if self._debug: logger.info("%r is serving", server) return server + @coroutine + def connect_accepted_socket(self, protocol_factory, sock, *, ssl=None): + """Handle an accepted connection. + + This is used by servers that accept connections outside of + asyncio but that use asyncio to handle connections. + + This method is a coroutine. When completed, the coroutine + returns a (transport, protocol) pair. + """ + if not _is_stream_socket(sock): + raise ValueError( + 'A Stream Socket was expected, got {!r}'.format(sock)) + + transport, protocol = yield from self._create_connection_transport( + sock, protocol_factory, ssl, '', server_side=True) + if self._debug: + # Get the socket from the transport because SSL transport closes + # the old socket and creates a new SSL socket + sock = transport.get_extra_info('socket') + logger.debug("%r handled: (%r, %r)", sock, transport, protocol) + return transport, protocol + @coroutine def connect_read_pipe(self, protocol_factory, pipe): protocol = protocol_factory() - waiter = futures.Future(loop=self) + waiter = self.create_future() transport = self._make_read_pipe_transport(pipe, protocol, waiter) try: @@ -987,7 +1110,7 @@ def connect_read_pipe(self, protocol_factory, pipe): @coroutine def connect_write_pipe(self, protocol_factory, pipe): protocol = protocol_factory() - waiter = futures.Future(loop=self) + waiter = self.create_future() transport = self._make_write_pipe_transport(pipe, protocol, waiter) try: @@ -1036,7 +1159,7 @@ def subprocess_shell(self, protocol_factory, cmd, *, stdin=subprocess.PIPE, transport = yield from self._make_subprocess_transport( protocol, cmd, True, stdin, stdout, stderr, bufsize, **kwargs) if self._debug: - logger.info('%s: %r' % (debug_log, transport)) + logger.info('%s: %r', debug_log, transport) return transport, protocol @coroutine @@ -1066,9 +1189,14 @@ def subprocess_exec(self, protocol_factory, program, *args, protocol, popen_args, False, stdin, stdout, stderr, bufsize, **kwargs) if self._debug: - logger.info('%s: %r' % (debug_log, transport)) + logger.info('%s: %r', debug_log, transport) return transport, protocol + def get_exception_handler(self): + """Return an exception handler, or None if the default one is in use. + """ + return self._exception_handler + def set_exception_handler(self, handler): """Set handler as the new event loop exception handler. @@ -1141,7 +1269,9 @@ def call_exception_handler(self, context): - 'handle' (optional): Handle instance; - 'protocol' (optional): Protocol instance; - 'transport' (optional): Transport instance; - - 'socket' (optional): Socket instance. + - 'socket' (optional): Socket instance; + - 'asyncgen' (optional): Asynchronous generator that caused + the exception. New keys maybe introduced in the future. diff --git a/lib/asyncio/base_subprocess.py b/lib/asyncio/base_subprocess.py index 73425d9b..23742a16 100644 --- a/lib/asyncio/base_subprocess.py +++ b/lib/asyncio/base_subprocess.py @@ -3,7 +3,6 @@ import warnings from . import compat -from . import futures from . import protocols from . import transports from .coroutines import coroutine @@ -87,6 +86,12 @@ def __repr__(self): def _start(self, args, shell, stdin, stdout, stderr, bufsize, **kwargs): raise NotImplementedError + def set_protocol(self, protocol): + self._protocol = protocol + + def get_protocol(self): + return self._protocol + def is_closing(self): return self._closed @@ -210,6 +215,10 @@ def _process_exited(self, returncode): logger.info('%r exited with return code %r', self, returncode) self._returncode = returncode + if self._proc.returncode is None: + # asyncio uses a child watcher: copy the status into the Popen + # object. On Python 3.6, it is required to avoid a ResourceWarning. + self._proc.returncode = returncode self._call(self._protocol.process_exited) self._try_finish() @@ -227,7 +236,7 @@ def _wait(self): if self._returncode is not None: return self._returncode - waiter = futures.Future(loop=self._loop) + waiter = self._loop.create_future() self._exit_waiters.append(waiter) return (yield from waiter) diff --git a/lib/asyncio/compat.py b/lib/asyncio/compat.py index 660b7e7e..4790bb4a 100644 --- a/lib/asyncio/compat.py +++ b/lib/asyncio/compat.py @@ -4,6 +4,7 @@ PY34 = sys.version_info >= (3, 4) PY35 = sys.version_info >= (3, 5) +PY352 = sys.version_info >= (3, 5, 2) def flatten_list_bytes(list_of_data): diff --git a/lib/asyncio/coroutines.py b/lib/asyncio/coroutines.py index 27ab42a5..488e4236 100644 --- a/lib/asyncio/coroutines.py +++ b/lib/asyncio/coroutines.py @@ -33,12 +33,16 @@ try: _types_coroutine = types.coroutine + _types_CoroutineType = types.CoroutineType except AttributeError: + # Python 3.4 _types_coroutine = None + _types_CoroutineType = None try: _inspect_iscoroutinefunction = inspect.iscoroutinefunction except AttributeError: + # Python 3.4 _inspect_iscoroutinefunction = lambda func: False try: @@ -120,8 +124,8 @@ def send(self, *value): def send(self, value): return self.gen.send(value) - def throw(self, exc): - return self.gen.throw(exc) + def throw(self, type, value=None, traceback=None): + return self.gen.throw(type, value, traceback) def close(self): return self.gen.close() @@ -193,7 +197,7 @@ def coroutine(func): """ if _inspect_iscoroutinefunction(func): # In Python 3.5 that's all we need to do for coroutines - # defiend with "async def". + # defined with "async def". # Wrapping in CoroWrapper will happen via # 'sys.set_coroutine_wrapper' function. return func @@ -204,7 +208,8 @@ def coroutine(func): @functools.wraps(func) def coro(*args, **kw): res = func(*args, **kw) - if isinstance(res, futures.Future) or inspect.isgenerator(res): + if (futures.isfuture(res) or inspect.isgenerator(res) or + isinstance(res, CoroWrapper)): res = yield from res elif _AwaitableABC is not None: # If 'func' returns an Awaitable (new in 3.5) we @@ -237,19 +242,27 @@ def wrapper(*args, **kwds): w.__qualname__ = getattr(func, '__qualname__', None) return w - wrapper._is_coroutine = True # For iscoroutinefunction(). + wrapper._is_coroutine = _is_coroutine # For iscoroutinefunction(). return wrapper +# A marker for iscoroutinefunction. +_is_coroutine = object() + + def iscoroutinefunction(func): """Return True if func is a decorated coroutine function.""" - return (getattr(func, '_is_coroutine', False) or + return (getattr(func, '_is_coroutine', None) is _is_coroutine or _inspect_iscoroutinefunction(func)) _COROUTINE_TYPES = (types.GeneratorType, CoroWrapper) if _CoroutineABC is not None: _COROUTINE_TYPES += (_CoroutineABC,) +if _types_CoroutineType is not None: + # Prioritize native coroutine check to speed-up + # asyncio.iscoroutine. + _COROUTINE_TYPES = (_types_CoroutineType,) + _COROUTINE_TYPES def iscoroutine(obj): @@ -260,6 +273,29 @@ def iscoroutine(obj): def _format_coroutine(coro): assert iscoroutine(coro) + if not hasattr(coro, 'cr_code') and not hasattr(coro, 'gi_code'): + # Most likely a built-in type or a Cython coroutine. + + # Built-in types might not have __qualname__ or __name__. + coro_name = getattr( + coro, '__qualname__', + getattr(coro, '__name__', type(coro).__name__)) + coro_name = '{}()'.format(coro_name) + + running = False + try: + running = coro.cr_running + except AttributeError: + try: + running = coro.gi_running + except AttributeError: + pass + + if running: + return '{} running'.format(coro_name) + else: + return coro_name + coro_name = None if isinstance(coro, CoroWrapper): func = coro.func @@ -270,7 +306,7 @@ def _format_coroutine(coro): func = coro if coro_name is None: - coro_name = events._format_callback(func, ()) + coro_name = events._format_callback(func, (), {}) try: coro_code = coro.gi_code diff --git a/lib/asyncio/events.py b/lib/asyncio/events.py index 176a8466..e85634e5 100644 --- a/lib/asyncio/events.py +++ b/lib/asyncio/events.py @@ -6,10 +6,12 @@ 'get_event_loop_policy', 'set_event_loop_policy', 'get_event_loop', 'set_event_loop', 'new_event_loop', 'get_child_watcher', 'set_child_watcher', + '_set_running_loop', '_get_running_loop', ] import functools import inspect +import os import reprlib import socket import subprocess @@ -35,23 +37,25 @@ def _get_function_source(func): return None -def _format_args(args): - """Format function arguments. +def _format_args_and_kwargs(args, kwargs): + """Format function arguments and keyword arguments. Special case for a single parameter: ('hello',) is formatted as ('hello'). """ # use reprlib to limit the length of the output - args_repr = reprlib.repr(args) - if len(args) == 1 and args_repr.endswith(',)'): - args_repr = args_repr[:-2] + ')' - return args_repr + items = [] + if args: + items.extend(reprlib.repr(arg) for arg in args) + if kwargs: + items.extend('{}={}'.format(k, reprlib.repr(v)) + for k, v in kwargs.items()) + return '(' + ', '.join(items) + ')' -def _format_callback(func, args, suffix=''): +def _format_callback(func, args, kwargs, suffix=''): if isinstance(func, functools.partial): - if args is not None: - suffix = _format_args(args) + suffix - return _format_callback(func.func, func.args, suffix) + suffix = _format_args_and_kwargs(args, kwargs) + suffix + return _format_callback(func.func, func.args, func.keywords, suffix) if hasattr(func, '__qualname__'): func_repr = getattr(func, '__qualname__') @@ -60,14 +64,13 @@ def _format_callback(func, args, suffix=''): else: func_repr = repr(func) - if args is not None: - func_repr += _format_args(args) + func_repr += _format_args_and_kwargs(args, kwargs) if suffix: func_repr += suffix return func_repr def _format_callback_source(func, args): - func_repr = _format_callback(func, args) + func_repr = _format_callback(func, args, None) source = _get_function_source(func) if source: func_repr += ' at %s:%s' % source @@ -81,7 +84,6 @@ class Handle: '_source_traceback', '_repr', '__weakref__') def __init__(self, callback, args, loop): - assert not isinstance(callback, Handle), 'A Handle is not a callback' self._loop = loop self._callback = callback self._args = args @@ -248,6 +250,10 @@ def close(self): """ raise NotImplementedError + def shutdown_asyncgens(self): + """Shutdown all active asynchronous generators.""" + raise NotImplementedError + # Methods scheduling callbacks. All these return Handles. def _timer_handle_cancelled(self, handle): @@ -266,6 +272,9 @@ def call_at(self, when, callback, *args): def time(self): raise NotImplementedError + def create_future(self): + raise NotImplementedError + # Method scheduling a coroutine object: create a task. def create_task(self, coro): @@ -484,6 +493,9 @@ def get_task_factory(self): # Error handlers. + def get_exception_handler(self): + raise NotImplementedError + def set_exception_handler(self, handler): raise NotImplementedError @@ -597,6 +609,36 @@ def new_event_loop(self): _lock = threading.Lock() +# A TLS for the running event loop, used by _get_running_loop. +class _RunningLoop(threading.local): + _loop = None + _pid = None + + +_running_loop = _RunningLoop() + + +def _get_running_loop(): + """Return the running event loop or None. + + This is a low-level function intended to be used by event loops. + This function is thread-specific. + """ + running_loop = _running_loop._loop + if running_loop is not None and _running_loop._pid == os.getpid(): + return running_loop + + +def _set_running_loop(loop): + """Set the running event loop. + + This is a low-level function intended to be used by event loops. + This function is thread-specific. + """ + _running_loop._pid = os.getpid() + _running_loop._loop = loop + + def _init_event_loop_policy(): global _event_loop_policy with _lock: @@ -622,7 +664,17 @@ def set_event_loop_policy(policy): def get_event_loop(): - """Equivalent to calling get_event_loop_policy().get_event_loop().""" + """Return an asyncio event loop. + + When called from a coroutine or a callback (e.g. scheduled with call_soon + or similar API), this function will always return the running event loop. + + If there is no running event loop set, the function will return + the result of `get_event_loop_policy().get_event_loop()` call. + """ + current_loop = _get_running_loop() + if current_loop is not None: + return current_loop return get_event_loop_policy().get_event_loop() diff --git a/lib/asyncio/futures.py b/lib/asyncio/futures.py index 4dcb6546..60b0d313 100644 --- a/lib/asyncio/futures.py +++ b/lib/asyncio/futures.py @@ -2,7 +2,7 @@ __all__ = ['CancelledError', 'TimeoutError', 'InvalidStateError', - 'Future', 'wrap_future', + 'Future', 'wrap_future', 'isfuture', ] import concurrent.futures._base @@ -110,6 +110,17 @@ def __del__(self): self.loop.call_exception_handler({'message': msg}) +def isfuture(obj): + """Check for a Future. + + This returns True when obj is a Future instance or is advertising + itself as duck-type compatible by setting _asyncio_future_blocking. + See comment in Future for more details. + """ + return (hasattr(obj.__class__, '_asyncio_future_blocking') and + obj._asyncio_future_blocking is not None) + + class Future: """This class is *almost* compatible with concurrent.futures.Future. @@ -134,7 +145,15 @@ class Future: _loop = None _source_traceback = None - _blocking = False # proper use of future (yield vs yield from) + # This field is used for a dual purpose: + # - Its presence is a marker to declare that a class implements + # the Future protocol (i.e. is intended to be duck-type compatible). + # The value must also be not-None, to enable a subclass to declare + # that it is not compatible by setting this to None. + # - It is set by __iter__() below so that Task._step() can tell + # the difference between `yield from Future()` (correct) vs. + # `yield Future()` (incorrect). + _asyncio_future_blocking = False _log_traceback = False # Used for Python 3.4 and later _tb_logger = None # Used for Python 3.3 only @@ -142,7 +161,7 @@ class Future: def __init__(self, *, loop=None): """Initialize the future. - The optional event_loop argument allows to explicitly set the event + The optional event_loop argument allows explicitly setting the event loop object used by the future. If it's not provided, the future uses the default event loop. """ @@ -221,6 +240,7 @@ def cancel(self): change the future's state to cancelled, schedule the callbacks and return True. """ + self._log_traceback = False if self._state != _PENDING: return False self._state = _CANCELLED @@ -341,6 +361,9 @@ def set_exception(self, exception): raise InvalidStateError('{}: {!r}'.format(self._state, self)) if isinstance(exception, type): exception = exception() + if type(exception) is StopIteration: + raise TypeError("StopIteration interacts badly with generators " + "and cannot be raised into a Future") self._exception = exception self._state = _FINISHED self._schedule_callbacks() @@ -354,7 +377,7 @@ def set_exception(self, exception): def __iter__(self): if not self.done(): - self._blocking = True + self._asyncio_future_blocking = True yield self # This tells Task to wait for completion. assert self.done(), "yield from wasn't used with future" return self.result() # May raise too. @@ -412,15 +435,17 @@ def _chain_future(source, destination): If destination is cancelled, source gets cancelled too. Compatible with both asyncio.Future and concurrent.futures.Future. """ - if not isinstance(source, (Future, concurrent.futures.Future)): + if not isfuture(source) and not isinstance(source, + concurrent.futures.Future): raise TypeError('A future is required for source argument') - if not isinstance(destination, (Future, concurrent.futures.Future)): + if not isfuture(destination) and not isinstance(destination, + concurrent.futures.Future): raise TypeError('A future is required for destination argument') - source_loop = source._loop if isinstance(source, Future) else None - dest_loop = destination._loop if isinstance(destination, Future) else None + source_loop = source._loop if isfuture(source) else None + dest_loop = destination._loop if isfuture(destination) else None def _set_state(future, other): - if isinstance(future, Future): + if isfuture(future): _copy_future_state(other, future) else: _set_concurrent_future_state(future, other) @@ -444,10 +469,12 @@ def _call_set_state(source): def wrap_future(future, *, loop=None): """Wrap concurrent.futures.Future object.""" - if isinstance(future, Future): + if isfuture(future): return future assert isinstance(future, concurrent.futures.Future), \ 'concurrent.futures.Future is expected, got {!r}'.format(future) - new_future = Future(loop=loop) + if loop is None: + loop = events.get_event_loop() + new_future = loop.create_future() _chain_future(future, new_future) return new_future diff --git a/lib/asyncio/locks.py b/lib/asyncio/locks.py index 34f6bc16..92661830 100644 --- a/lib/asyncio/locks.py +++ b/lib/asyncio/locks.py @@ -111,7 +111,7 @@ class Lock(_ContextManagerMixin): acquire() is a coroutine and should be called with 'yield from'. Locks also support the context management protocol. '(yield from lock)' - should be used as context manager expression. + should be used as the context manager expression. Usage: @@ -166,16 +166,20 @@ def acquire(self): This method blocks until the lock is unlocked, then sets it to locked and returns True. """ - if not self._waiters and not self._locked: + if not self._locked and all(w.cancelled() for w in self._waiters): self._locked = True return True - fut = futures.Future(loop=self._loop) + fut = self._loop.create_future() self._waiters.append(fut) try: yield from fut self._locked = True return True + except futures.CancelledError: + if not self._locked: + self._wake_up_first() + raise finally: self._waiters.remove(fut) @@ -192,14 +196,17 @@ def release(self): """ if self._locked: self._locked = False - # Wake up the first waiter who isn't cancelled. - for fut in self._waiters: - if not fut.done(): - fut.set_result(True) - break + self._wake_up_first() else: raise RuntimeError('Lock is not acquired.') + def _wake_up_first(self): + """Wake up the first waiter who isn't cancelled.""" + for fut in self._waiters: + if not fut.done(): + fut.set_result(True) + break + class Event: """Asynchronous equivalent to threading.Event. @@ -258,7 +265,7 @@ def wait(self): if self._value: return True - fut = futures.Future(loop=self._loop) + fut = self._loop.create_future() self._waiters.append(fut) try: yield from fut @@ -320,7 +327,7 @@ def wait(self): self.release() try: - fut = futures.Future(loop=self._loop) + fut = self._loop.create_future() self._waiters.append(fut) try: yield from fut @@ -329,7 +336,13 @@ def wait(self): self._waiters.remove(fut) finally: - yield from self.acquire() + # Must reacquire lock even if wait is cancelled + while True: + try: + yield from self.acquire() + break + except futures.CancelledError: + pass @coroutine def wait_for(self, predicate): @@ -433,7 +446,7 @@ def acquire(self): True. """ while self._value <= 0: - fut = futures.Future(loop=self._loop) + fut = self._loop.create_future() self._waiters.append(fut) try: yield from fut diff --git a/lib/asyncio/proactor_events.py b/lib/asyncio/proactor_events.py index 14c0659d..13a885cd 100644 --- a/lib/asyncio/proactor_events.py +++ b/lib/asyncio/proactor_events.py @@ -66,6 +66,12 @@ def __repr__(self): def _set_extra(self, sock): self._extra['pipe'] = sock + def set_protocol(self, protocol): + self._protocol = protocol + + def get_protocol(self): + return self._protocol + def is_closing(self): return self._closing @@ -90,7 +96,7 @@ def __del__(self): self.close() def _fatal_error(self, exc, message='Fatal error on pipe transport'): - if isinstance(exc, (BrokenPipeError, ConnectionResetError)): + if isinstance(exc, base_events._FATAL_ERROR_IGNORE): if self._loop.get_debug(): logger.debug("%r: %s", self, message, exc_info=True) else: @@ -225,8 +231,9 @@ class _ProactorBaseWritePipeTransport(_ProactorBasePipeTransport, def write(self, data): if not isinstance(data, (bytes, bytearray, memoryview)): - raise TypeError('data argument must be byte-ish (%r)', - type(data)) + msg = ("data argument must be a bytes-like object, not '%s'" % + type(data).__name__) + raise TypeError(msg) if self._eof_written: raise RuntimeError('write_eof() already called') @@ -440,14 +447,7 @@ def sock_sendall(self, sock, data): return self._proactor.send(sock, data) def sock_connect(self, sock, address): - try: - base_events._check_resolved_address(sock, address) - except ValueError as err: - fut = futures.Future(loop=self) - fut.set_exception(err) - return fut - else: - return self._proactor.connect(sock, address) + return self._proactor.connect(sock, address) def sock_accept(self, sock): return self._proactor.accept(sock) @@ -495,7 +495,7 @@ def _write_to_self(self): self._csock.send(b'\0') def _start_serving(self, protocol_factory, sock, - sslcontext=None, server=None): + sslcontext=None, server=None, backlog=100): def loop(f=None): try: diff --git a/lib/asyncio/queues.py b/lib/asyncio/queues.py index e3a1d5ed..2d38972c 100644 --- a/lib/asyncio/queues.py +++ b/lib/asyncio/queues.py @@ -7,7 +7,6 @@ from . import compat from . import events -from . import futures from . import locks from .coroutines import coroutine @@ -128,7 +127,7 @@ def put(self, item): This method is a coroutine. """ while self.full(): - putter = futures.Future(loop=self._loop) + putter = self._loop.create_future() self._putters.append(putter) try: yield from putter @@ -162,7 +161,7 @@ def get(self): This method is a coroutine. """ while self.empty(): - getter = futures.Future(loop=self._loop) + getter = self._loop.create_future() self._getters.append(getter) try: yield from getter diff --git a/lib/asyncio/selector_events.py b/lib/asyncio/selector_events.py index 5b26631d..12d357b5 100644 --- a/lib/asyncio/selector_events.py +++ b/lib/asyncio/selector_events.py @@ -11,6 +11,7 @@ import functools import socket import warnings +import weakref try: import ssl except ImportError: # pragma: no cover @@ -39,6 +40,17 @@ def _test_selector_event(selector, fd, event): return bool(key.events & event) +if hasattr(socket, 'TCP_NODELAY'): + def _set_nodelay(sock): + if (sock.family in {socket.AF_INET, socket.AF_INET6} and + sock.type == socket.SOCK_STREAM and + sock.proto == socket.IPPROTO_TCP): + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) +else: + def _set_nodelay(sock): + pass + + class BaseSelectorEventLoop(base_events.BaseEventLoop): """Selector event loop. @@ -53,6 +65,7 @@ def __init__(self, selector=None): logger.debug('Using selector: %s', selector.__class__.__name__) self._selector = selector self._make_self_pipe() + self._transports = weakref.WeakValueDictionary() def _make_socket_transport(self, sock, protocol, waiter=None, *, extra=None, server=None): @@ -104,7 +117,7 @@ def _socketpair(self): raise NotImplementedError def _close_self_pipe(self): - self.remove_reader(self._ssock.fileno()) + self._remove_reader(self._ssock.fileno()) self._ssock.close() self._ssock = None self._csock.close() @@ -117,7 +130,7 @@ def _make_self_pipe(self): self._ssock.setblocking(False) self._csock.setblocking(False) self._internal_fds += 1 - self.add_reader(self._ssock.fileno(), self._read_from_self) + self._add_reader(self._ssock.fileno(), self._read_from_self) def _process_self_data(self, data): pass @@ -151,43 +164,50 @@ def _write_to_self(self): exc_info=True) def _start_serving(self, protocol_factory, sock, - sslcontext=None, server=None): - self.add_reader(sock.fileno(), self._accept_connection, - protocol_factory, sock, sslcontext, server) + sslcontext=None, server=None, backlog=100): + self._add_reader(sock.fileno(), self._accept_connection, + protocol_factory, sock, sslcontext, server, backlog) def _accept_connection(self, protocol_factory, sock, - sslcontext=None, server=None): - try: - conn, addr = sock.accept() - if self._debug: - logger.debug("%r got a new connection from %r: %r", - server, addr, conn) - conn.setblocking(False) - except (BlockingIOError, InterruptedError, ConnectionAbortedError): - pass # False alarm. - except OSError as exc: - # There's nowhere to send the error, so just log it. - if exc.errno in (errno.EMFILE, errno.ENFILE, - errno.ENOBUFS, errno.ENOMEM): - # Some platforms (e.g. Linux keep reporting the FD as - # ready, so we remove the read handler temporarily. - # We'll try again in a while. - self.call_exception_handler({ - 'message': 'socket.accept() out of system resource', - 'exception': exc, - 'socket': sock, - }) - self.remove_reader(sock.fileno()) - self.call_later(constants.ACCEPT_RETRY_DELAY, - self._start_serving, - protocol_factory, sock, sslcontext, server) + sslcontext=None, server=None, backlog=100): + # This method is only called once for each event loop tick where the + # listening socket has triggered an EVENT_READ. There may be multiple + # connections waiting for an .accept() so it is called in a loop. + # See https://bugs.python.org/issue27906 for more details. + for _ in range(backlog): + try: + conn, addr = sock.accept() + if self._debug: + logger.debug("%r got a new connection from %r: %r", + server, addr, conn) + conn.setblocking(False) + except (BlockingIOError, InterruptedError, ConnectionAbortedError): + # Early exit because the socket accept buffer is empty. + return None + except OSError as exc: + # There's nowhere to send the error, so just log it. + if exc.errno in (errno.EMFILE, errno.ENFILE, + errno.ENOBUFS, errno.ENOMEM): + # Some platforms (e.g. Linux keep reporting the FD as + # ready, so we remove the read handler temporarily. + # We'll try again in a while. + self.call_exception_handler({ + 'message': 'socket.accept() out of system resource', + 'exception': exc, + 'socket': sock, + }) + self._remove_reader(sock.fileno()) + self.call_later(constants.ACCEPT_RETRY_DELAY, + self._start_serving, + protocol_factory, sock, sslcontext, server, + backlog) + else: + raise # The event loop will catch, log and ignore it. else: - raise # The event loop will catch, log and ignore it. - else: - extra = {'peername': addr} - accept = self._accept_connection2(protocol_factory, conn, extra, - sslcontext, server) - self.create_task(accept) + extra = {'peername': addr} + accept = self._accept_connection2(protocol_factory, conn, extra, + sslcontext, server) + self.create_task(accept) @coroutine def _accept_connection2(self, protocol_factory, conn, extra, @@ -196,7 +216,7 @@ def _accept_connection2(self, protocol_factory, conn, extra, transport = None try: protocol = protocol_factory() - waiter = futures.Future(loop=self) + waiter = self.create_future() if sslcontext: transport = self._make_ssl_transport( conn, protocol, sslcontext, waiter=waiter, @@ -226,8 +246,18 @@ def _accept_connection2(self, protocol_factory, conn, extra, context['transport'] = transport self.call_exception_handler(context) - def add_reader(self, fd, callback, *args): - """Add a reader callback.""" + def _ensure_fd_no_transport(self, fd): + try: + transport = self._transports[fd] + except KeyError: + pass + else: + if not transport.is_closing(): + raise RuntimeError( + 'File descriptor {!r} is used by transport {!r}'.format( + fd, transport)) + + def _add_reader(self, fd, callback, *args): self._check_closed() handle = events.Handle(callback, args, self) try: @@ -242,8 +272,7 @@ def add_reader(self, fd, callback, *args): if reader is not None: reader.cancel() - def remove_reader(self, fd): - """Remove a reader callback.""" + def _remove_reader(self, fd): if self.is_closed(): return False try: @@ -264,8 +293,7 @@ def remove_reader(self, fd): else: return False - def add_writer(self, fd, callback, *args): - """Add a writer callback..""" + def _add_writer(self, fd, callback, *args): self._check_closed() handle = events.Handle(callback, args, self) try: @@ -280,7 +308,7 @@ def add_writer(self, fd, callback, *args): if writer is not None: writer.cancel() - def remove_writer(self, fd): + def _remove_writer(self, fd): """Remove a writer callback.""" if self.is_closed(): return False @@ -303,6 +331,26 @@ def remove_writer(self, fd): else: return False + def add_reader(self, fd, callback, *args): + """Add a reader callback.""" + self._ensure_fd_no_transport(fd) + return self._add_reader(fd, callback, *args) + + def remove_reader(self, fd): + """Remove a reader callback.""" + self._ensure_fd_no_transport(fd) + return self._remove_reader(fd) + + def add_writer(self, fd, callback, *args): + """Add a writer callback..""" + self._ensure_fd_no_transport(fd) + return self._add_writer(fd, callback, *args) + + def remove_writer(self, fd): + """Remove a writer callback.""" + self._ensure_fd_no_transport(fd) + return self._remove_writer(fd) + def sock_recv(self, sock, n): """Receive data from the socket. @@ -314,7 +362,7 @@ def sock_recv(self, sock, n): """ if self._debug and sock.gettimeout() != 0: raise ValueError("the socket must be non-blocking") - fut = futures.Future(loop=self) + fut = self.create_future() self._sock_recv(fut, False, sock, n) return fut @@ -352,7 +400,7 @@ def sock_sendall(self, sock, data): """ if self._debug and sock.gettimeout() != 0: raise ValueError("the socket must be non-blocking") - fut = futures.Future(loop=self) + fut = self.create_future() if data: self._sock_sendall(fut, False, sock, data) else: @@ -382,27 +430,25 @@ def _sock_sendall(self, fut, registered, sock, data): data = data[n:] self.add_writer(fd, self._sock_sendall, fut, True, sock, data) + @coroutine def sock_connect(self, sock, address): """Connect to a remote socket at address. - The address must be already resolved to avoid the trap of hanging the - entire event loop when the address requires doing a DNS lookup. For - example, it must be an IP address, not an hostname, for AF_INET and - AF_INET6 address families. Use getaddrinfo() to resolve the hostname - asynchronously. - This method is a coroutine. """ if self._debug and sock.gettimeout() != 0: raise ValueError("the socket must be non-blocking") - fut = futures.Future(loop=self) - try: - base_events._check_resolved_address(sock, address) - except ValueError as err: - fut.set_exception(err) - else: - self._sock_connect(fut, sock, address) - return fut + + if not hasattr(socket, 'AF_UNIX') or sock.family != socket.AF_UNIX: + resolved = base_events._ensure_resolved( + address, family=sock.family, proto=sock.proto, loop=self) + if not resolved.done(): + yield from resolved + _, _, _, _, address = resolved.result()[0] + + fut = self.create_future() + self._sock_connect(fut, sock, address) + return (yield from fut) def _sock_connect(self, fut, sock, address): fd = sock.fileno() @@ -413,8 +459,8 @@ def _sock_connect(self, fut, sock, address): # connection runs in background. We have to wait until the socket # becomes writable to be notified when the connection succeed or # fails. - fut.add_done_callback(functools.partial(self._sock_connect_done, - fd)) + fut.add_done_callback( + functools.partial(self._sock_connect_done, fd)) self.add_writer(fd, self._sock_connect_cb, fut, sock, address) except Exception as exc: fut.set_exception(exc) @@ -453,7 +499,7 @@ def sock_accept(self, sock): """ if self._debug and sock.gettimeout() != 0: raise ValueError("the socket must be non-blocking") - fut = futures.Future(loop=self) + fut = self.create_future() self._sock_accept(fut, False, sock) return fut @@ -478,17 +524,17 @@ def _process_events(self, event_list): fileobj, (reader, writer) = key.fileobj, key.data if mask & selectors.EVENT_READ and reader is not None: if reader._cancelled: - self.remove_reader(fileobj) + self._remove_reader(fileobj) else: self._add_callback(reader) if mask & selectors.EVENT_WRITE and writer is not None: if writer._cancelled: - self.remove_writer(fileobj) + self._remove_writer(fileobj) else: self._add_callback(writer) def _stop_serving(self, sock): - self.remove_reader(sock.fileno()) + self._remove_reader(sock.fileno()) sock.close() @@ -523,6 +569,7 @@ def __init__(self, loop, sock, protocol, extra=None, server=None): self._closing = False # Set when close() called. if self._server is not None: self._server._attach() + loop._transports[self._sock_fd] = self def __repr__(self): info = [self.__class__.__name__] @@ -555,6 +602,12 @@ def __repr__(self): def abort(self): self._force_close(None) + def set_protocol(self, protocol): + self._protocol = protocol + + def get_protocol(self): + return self._protocol + def is_closing(self): return self._closing @@ -562,9 +615,10 @@ def close(self): if self._closing: return self._closing = True - self._loop.remove_reader(self._sock_fd) + self._loop._remove_reader(self._sock_fd) if not self._buffer: self._conn_lost += 1 + self._loop._remove_writer(self._sock_fd) self._loop.call_soon(self._call_connection_lost, None) # On Python 3.3 and older, objects with a destructor part of a reference @@ -578,8 +632,7 @@ def __del__(self): def _fatal_error(self, exc, message='Fatal error on transport'): # Should be called from exception handler only. - if isinstance(exc, (BrokenPipeError, - ConnectionResetError, ConnectionAbortedError)): + if isinstance(exc, base_events._FATAL_ERROR_IGNORE): if self._loop.get_debug(): logger.debug("%r: %s", self, message, exc_info=True) else: @@ -596,10 +649,10 @@ def _force_close(self, exc): return if self._buffer: self._buffer.clear() - self._loop.remove_writer(self._sock_fd) + self._loop._remove_writer(self._sock_fd) if not self._closing: self._closing = True - self._loop.remove_reader(self._sock_fd) + self._loop._remove_reader(self._sock_fd) self._conn_lost += 1 self._loop.call_soon(self._call_connection_lost, exc) @@ -629,9 +682,14 @@ def __init__(self, loop, sock, protocol, waiter=None, self._eof = False self._paused = False + # Disable the Nagle algorithm -- small writes will be + # sent without waiting for the TCP ACK. This generally + # decreases the latency (in some cases significantly.) + _set_nodelay(self._sock) + self._loop.call_soon(self._protocol.connection_made, self) # only start reading when connection_made() has been called - self._loop.call_soon(self._loop.add_reader, + self._loop.call_soon(self._loop._add_reader, self._sock_fd, self._read_ready) if waiter is not None: # only wake up the waiter when connection_made() has been called @@ -644,7 +702,7 @@ def pause_reading(self): if self._paused: raise RuntimeError('Already paused') self._paused = True - self._loop.remove_reader(self._sock_fd) + self._loop._remove_reader(self._sock_fd) if self._loop.get_debug(): logger.debug("%r pauses reading", self) @@ -654,11 +712,13 @@ def resume_reading(self): self._paused = False if self._closing: return - self._loop.add_reader(self._sock_fd, self._read_ready) + self._loop._add_reader(self._sock_fd, self._read_ready) if self._loop.get_debug(): logger.debug("%r resumes reading", self) def _read_ready(self): + if self._conn_lost: + return try: data = self._sock.recv(self.max_size) except (BlockingIOError, InterruptedError): @@ -676,14 +736,14 @@ def _read_ready(self): # We're keeping the connection open so the # protocol can write more, but we still can't # receive more, so remove the reader callback. - self._loop.remove_reader(self._sock_fd) + self._loop._remove_reader(self._sock_fd) else: self.close() def write(self, data): if not isinstance(data, (bytes, bytearray, memoryview)): - raise TypeError('data argument must be byte-ish (%r)', - type(data)) + raise TypeError('data argument must be a bytes-like object, ' + 'not %r' % type(data).__name__) if self._eof: raise RuntimeError('Cannot call write() after write_eof()') if not data: @@ -709,7 +769,7 @@ def write(self, data): if not data: return # Not all was written; register write handler. - self._loop.add_writer(self._sock_fd, self._write_ready) + self._loop._add_writer(self._sock_fd, self._write_ready) # Add it to the buffer. self._buffer.extend(data) @@ -718,12 +778,14 @@ def write(self, data): def _write_ready(self): assert self._buffer, 'Data should not be empty' + if self._conn_lost: + return try: n = self._sock.send(self._buffer) except (BlockingIOError, InterruptedError): pass except Exception as exc: - self._loop.remove_writer(self._sock_fd) + self._loop._remove_writer(self._sock_fd) self._buffer.clear() self._fatal_error(exc, 'Fatal write error on socket transport') else: @@ -731,7 +793,7 @@ def _write_ready(self): del self._buffer[:n] self._maybe_resume_protocol() # May append to buffer. if not self._buffer: - self._loop.remove_writer(self._sock_fd) + self._loop._remove_writer(self._sock_fd) if self._closing: self._call_connection_lost(None) elif self._eof: @@ -802,19 +864,19 @@ def _on_handshake(self, start_time): try: self._sock.do_handshake() except ssl.SSLWantReadError: - self._loop.add_reader(self._sock_fd, - self._on_handshake, start_time) + self._loop._add_reader(self._sock_fd, + self._on_handshake, start_time) return except ssl.SSLWantWriteError: - self._loop.add_writer(self._sock_fd, - self._on_handshake, start_time) + self._loop._add_writer(self._sock_fd, + self._on_handshake, start_time) return except BaseException as exc: if self._loop.get_debug(): logger.warning("%r: SSL handshake failed", self, exc_info=True) - self._loop.remove_reader(self._sock_fd) - self._loop.remove_writer(self._sock_fd) + self._loop._remove_reader(self._sock_fd) + self._loop._remove_writer(self._sock_fd) self._sock.close() self._wakeup_waiter(exc) if isinstance(exc, Exception): @@ -822,8 +884,8 @@ def _on_handshake(self, start_time): else: raise - self._loop.remove_reader(self._sock_fd) - self._loop.remove_writer(self._sock_fd) + self._loop._remove_reader(self._sock_fd) + self._loop._remove_writer(self._sock_fd) peercert = self._sock.getpeercert() if not hasattr(self._sslcontext, 'check_hostname'): @@ -851,7 +913,7 @@ def _on_handshake(self, start_time): self._read_wants_write = False self._write_wants_read = False - self._loop.add_reader(self._sock_fd, self._read_ready) + self._loop._add_reader(self._sock_fd, self._read_ready) self._protocol_connected = True self._loop.call_soon(self._protocol.connection_made, self) # only wake up the waiter when connection_made() has been called @@ -873,7 +935,7 @@ def pause_reading(self): if self._paused: raise RuntimeError('Already paused') self._paused = True - self._loop.remove_reader(self._sock_fd) + self._loop._remove_reader(self._sock_fd) if self._loop.get_debug(): logger.debug("%r pauses reading", self) @@ -883,17 +945,19 @@ def resume_reading(self): self._paused = False if self._closing: return - self._loop.add_reader(self._sock_fd, self._read_ready) + self._loop._add_reader(self._sock_fd, self._read_ready) if self._loop.get_debug(): logger.debug("%r resumes reading", self) def _read_ready(self): + if self._conn_lost: + return if self._write_wants_read: self._write_wants_read = False self._write_ready() if self._buffer: - self._loop.add_writer(self._sock_fd, self._write_ready) + self._loop._add_writer(self._sock_fd, self._write_ready) try: data = self._sock.recv(self.max_size) @@ -901,8 +965,8 @@ def _read_ready(self): pass except ssl.SSLWantWriteError: self._read_wants_write = True - self._loop.remove_reader(self._sock_fd) - self._loop.add_writer(self._sock_fd, self._write_ready) + self._loop._remove_reader(self._sock_fd) + self._loop._add_writer(self._sock_fd, self._write_ready) except Exception as exc: self._fatal_error(exc, 'Fatal read error on SSL transport') else: @@ -920,12 +984,14 @@ def _read_ready(self): self.close() def _write_ready(self): + if self._conn_lost: + return if self._read_wants_write: self._read_wants_write = False self._read_ready() if not (self._paused or self._closing): - self._loop.add_reader(self._sock_fd, self._read_ready) + self._loop._add_reader(self._sock_fd, self._read_ready) if self._buffer: try: @@ -934,10 +1000,10 @@ def _write_ready(self): n = 0 except ssl.SSLWantReadError: n = 0 - self._loop.remove_writer(self._sock_fd) + self._loop._remove_writer(self._sock_fd) self._write_wants_read = True except Exception as exc: - self._loop.remove_writer(self._sock_fd) + self._loop._remove_writer(self._sock_fd) self._buffer.clear() self._fatal_error(exc, 'Fatal write error on SSL transport') return @@ -948,14 +1014,14 @@ def _write_ready(self): self._maybe_resume_protocol() # May append to buffer. if not self._buffer: - self._loop.remove_writer(self._sock_fd) + self._loop._remove_writer(self._sock_fd) if self._closing: self._call_connection_lost(None) def write(self, data): if not isinstance(data, (bytes, bytearray, memoryview)): - raise TypeError('data argument must be byte-ish (%r)', - type(data)) + raise TypeError('data argument must be a bytes-like object, ' + 'not %r' % type(data).__name__) if not data: return @@ -966,7 +1032,7 @@ def write(self, data): return if not self._buffer: - self._loop.add_writer(self._sock_fd, self._write_ready) + self._loop._add_writer(self._sock_fd, self._write_ready) # Add it to the buffer. self._buffer.extend(data) @@ -986,7 +1052,7 @@ def __init__(self, loop, sock, protocol, address=None, self._address = address self._loop.call_soon(self._protocol.connection_made, self) # only start reading when connection_made() has been called - self._loop.call_soon(self._loop.add_reader, + self._loop.call_soon(self._loop._add_reader, self._sock_fd, self._read_ready) if waiter is not None: # only wake up the waiter when connection_made() has been called @@ -997,6 +1063,8 @@ def get_write_buffer_size(self): return sum(len(data) for data, _ in self._buffer) def _read_ready(self): + if self._conn_lost: + return try: data, addr = self._sock.recvfrom(self.max_size) except (BlockingIOError, InterruptedError): @@ -1010,8 +1078,8 @@ def _read_ready(self): def sendto(self, data, addr=None): if not isinstance(data, (bytes, bytearray, memoryview)): - raise TypeError('data argument must be byte-ish (%r)', - type(data)) + raise TypeError('data argument must be a bytes-like object, ' + 'not %r' % type(data).__name__) if not data: return @@ -1034,7 +1102,7 @@ def sendto(self, data, addr=None): self._sock.sendto(data, addr) return except (BlockingIOError, InterruptedError): - self._loop.add_writer(self._sock_fd, self._sendto_ready) + self._loop._add_writer(self._sock_fd, self._sendto_ready) except OSError as exc: self._protocol.error_received(exc) return @@ -1068,6 +1136,6 @@ def _sendto_ready(self): self._maybe_resume_protocol() # May append to buffer. if not self._buffer: - self._loop.remove_writer(self._sock_fd) + self._loop._remove_writer(self._sock_fd) if self._closing: self._call_connection_lost(None) diff --git a/lib/asyncio/sslproto.py b/lib/asyncio/sslproto.py index dde980b6..61d478eb 100644 --- a/lib/asyncio/sslproto.py +++ b/lib/asyncio/sslproto.py @@ -5,6 +5,7 @@ except ImportError: # pragma: no cover ssl = None +from . import base_events from . import compat from . import protocols from . import transports @@ -304,6 +305,12 @@ def get_extra_info(self, name, default=None): """Get optional transport information.""" return self._ssl_protocol._get_extra_info(name, default) + def set_protocol(self, protocol): + self._app_protocol = protocol + + def get_protocol(self): + return self._app_protocol + def is_closing(self): return self._closed @@ -403,7 +410,8 @@ class SSLProtocol(protocols.Protocol): """ def __init__(self, loop, app_protocol, sslcontext, waiter, - server_side=False, server_hostname=None): + server_side=False, server_hostname=None, + call_connection_made=True): if ssl is None: raise RuntimeError('stdlib ssl module not available') @@ -436,6 +444,7 @@ def __init__(self, loop, app_protocol, sslcontext, waiter, self._in_shutdown = False # transport, ex: SelectorSocketTransport self._transport = None + self._call_connection_made = call_connection_made def _wakeup_waiter(self, exc=None): if self._waiter is None: @@ -470,6 +479,7 @@ def connection_lost(self, exc): self._loop.call_soon(self._app_protocol.connection_lost, exc) self._transport = None self._app_transport = None + self._wakeup_waiter(exc) def pause_writing(self): """Called when the low-level transport's buffer goes over @@ -532,14 +542,19 @@ def eof_received(self): def _get_extra_info(self, name, default=None): if name in self._extra: return self._extra[name] - else: + elif self._transport is not None: return self._transport.get_extra_info(name, default) + else: + return default def _start_shutdown(self): if self._in_shutdown: return - self._in_shutdown = True - self._write_appdata(b'') + if self._in_handshake: + self._abort() + else: + self._in_shutdown = True + self._write_appdata(b'') def _write_appdata(self, data): self._write_backlog.append((data, 0)) @@ -599,11 +614,12 @@ def _on_handshake_complete(self, handshake_exc): compression=sslobj.compression(), ssl_object=sslobj, ) - self._app_protocol.connection_made(self._app_transport) + if self._call_connection_made: + self._app_protocol.connection_made(self._app_transport) self._wakeup_waiter() self._session_established = True # In case transport.write() was already called. Don't call - # immediatly _process_write_backlog(), but schedule it: + # immediately _process_write_backlog(), but schedule it: # _on_handshake_complete() can be called indirectly from # _process_write_backlog(), and _process_write_backlog() is not # reentrant. @@ -655,7 +671,7 @@ def _process_write_backlog(self): def _fatal_error(self, exc, message='Fatal error on transport'): # Should be called from exception handler only. - if isinstance(exc, (BrokenPipeError, ConnectionResetError)): + if isinstance(exc, base_events._FATAL_ERROR_IGNORE): if self._loop.get_debug(): logger.debug("%r: %s", self, message, exc_info=True) else: @@ -669,12 +685,14 @@ def _fatal_error(self, exc, message='Fatal error on transport'): self._transport._force_close(exc) def _finalize(self): + self._sslpipe = None + if self._transport is not None: self._transport.close() def _abort(self): - if self._transport is not None: - try: + try: + if self._transport is not None: self._transport.abort() - finally: - self._finalize() + finally: + self._finalize() diff --git a/lib/asyncio/streams.py b/lib/asyncio/streams.py index 0008d514..a82cc79a 100644 --- a/lib/asyncio/streams.py +++ b/lib/asyncio/streams.py @@ -14,13 +14,12 @@ from . import coroutines from . import compat from . import events -from . import futures from . import protocols from .coroutines import coroutine from .log import logger -_DEFAULT_LIMIT = 2**16 +_DEFAULT_LIMIT = 2 ** 16 class IncompleteReadError(EOFError): @@ -38,15 +37,13 @@ def __init__(self, partial, expected): class LimitOverrunError(Exception): - """Reached buffer limit while looking for the separator. + """Reached the buffer limit while looking for a separator. Attributes: - - message: error message - - consumed: total number of bytes that should be consumed + - consumed: total number of to be consumed bytes. """ def __init__(self, message, consumed): super().__init__(message) - self.message = message self.consumed = consumed @@ -132,7 +129,6 @@ def open_unix_connection(path=None, *, writer = StreamWriter(transport, protocol, reader, loop) return reader, writer - @coroutine def start_unix_server(client_connected_cb, path=None, *, loop=None, limit=_DEFAULT_LIMIT, **kwds): @@ -210,7 +206,7 @@ def _drain_helper(self): return waiter = self._drain_waiter assert waiter is None or waiter.cancelled() - waiter = futures.Future(loop=self._loop) + waiter = self._loop.create_future() self._drain_waiter = waiter yield from waiter @@ -229,9 +225,11 @@ def __init__(self, stream_reader, client_connected_cb=None, loop=None): self._stream_reader = stream_reader self._stream_writer = None self._client_connected_cb = client_connected_cb + self._over_ssl = False def connection_made(self, transport): self._stream_reader.set_transport(transport) + self._over_ssl = transport.get_extra_info('sslcontext') is not None if self._client_connected_cb is not None: self._stream_writer = StreamWriter(transport, self, self._stream_reader, @@ -242,17 +240,25 @@ def connection_made(self, transport): self._loop.create_task(res) def connection_lost(self, exc): - if exc is None: - self._stream_reader.feed_eof() - else: - self._stream_reader.set_exception(exc) + if self._stream_reader is not None: + if exc is None: + self._stream_reader.feed_eof() + else: + self._stream_reader.set_exception(exc) super().connection_lost(exc) + self._stream_reader = None + self._stream_writer = None def data_received(self, data): self._stream_reader.feed_data(data) def eof_received(self): self._stream_reader.feed_eof() + if self._over_ssl: + # Prevent a warning in SSLProtocol.eof_received: + # "returning true from eof_received() + # has no effect when using ssl" + return False return True @@ -413,8 +419,8 @@ def feed_data(self, data): self._wakeup_waiter() if (self._transport is not None and - not self._paused and - len(self._buffer) > 2*self._limit): + not self._paused and + len(self._buffer) > 2 * self._limit): try: self._transport.pause_reading() except NotImplementedError: @@ -442,11 +448,12 @@ def _wait_for_data(self, func_name): assert not self._eof, '_wait_for_data after EOF' # Waiting for data while paused will make deadlock, so prevent it. + # This is essential for readexactly(n) for case when n > self._limit. if self._paused: self._paused = False self._transport.resume_reading() - self._waiter = futures.Future(loop=self._loop) + self._waiter = self._loop.create_future() try: yield from self._waiter finally: @@ -486,24 +493,24 @@ def readline(self): @coroutine def readuntil(self, separator=b'\n'): - """Read chunk of data from the stream until `separator` is found. + """Read data from the stream until ``separator`` is found. - On success, chunk and its separator will be removed from internal buffer - (i.e. consumed). Returned chunk will include separator at the end. + On success, the data and separator will be removed from the + internal buffer (consumed). Returned data will include the + separator at the end. - Configured stream limit is used to check result. Limit means maximal - length of chunk that can be returned, not counting the separator. + Configured stream limit is used to check result. Limit sets the + maximal length of data that can be returned, not counting the + separator. - If EOF occurs and complete separator still not found, - IncompleteReadError(, None) will be raised and internal - buffer becomes empty. This partial data may contain a partial separator. + If an EOF occurs and the complete separator is still not found, + an IncompleteReadError exception will be raised, and the internal + buffer will be reset. The IncompleteReadError.partial attribute + may contain the separator partially. - If chunk cannot be read due to overlimit, LimitOverrunError will be raised - and data will be left in internal buffer, so it can be read again, in - some different way. - - If stream was paused, this function will automatically resume it if - needed. + If the data cannot be read because of over limit, a + LimitOverrunError exception will be raised, and the data + will be left in the internal buffer, so it can be read again. """ seplen = len(separator) if seplen == 0: @@ -529,8 +536,8 @@ def readuntil(self, separator=b'\n'): # performance problems. Even when reading MIME-encoded # messages :) - # `offset` is the number of bytes from the beginning of the buffer where - # is no occurrence of `separator`. + # `offset` is the number of bytes from the beginning of the buffer + # where there is no occurrence of `separator`. offset = 0 # Loop until we find `separator` in the buffer, exceed the buffer size, @@ -544,14 +551,16 @@ def readuntil(self, separator=b'\n'): isep = self._buffer.find(separator, offset) if isep != -1: - # `separator` is in the buffer. `isep` will be used later to - # retrieve the data. + # `separator` is in the buffer. `isep` will be used later + # to retrieve the data. break # see upper comment for explanation. offset = buflen + 1 - seplen if offset > self._limit: - raise LimitOverrunError('Separator is not found, and chunk exceed the limit', offset) + raise LimitOverrunError( + 'Separator is not found, and chunk exceed the limit', + offset) # Complete message (with full separator) may be present in buffer # even when EOF flag is set. This may happen when the last chunk @@ -566,7 +575,8 @@ def readuntil(self, separator=b'\n'): yield from self._wait_for_data('readuntil') if isep > self._limit: - raise LimitOverrunError('Separator is found, but chunk is longer than limit', isep) + raise LimitOverrunError( + 'Separator is found, but chunk is longer than limit', isep) chunk = self._buffer[:isep + seplen] del self._buffer[:isep + seplen] @@ -581,14 +591,15 @@ def read(self, n=-1): bytes. If the EOF was received and the internal buffer is empty, return an empty bytes object. - If n is zero, return empty bytes object immediatelly. + If n is zero, return empty bytes object immediately. If n is positive, this function try to read `n` bytes, and may return less or equal bytes than requested, but at least one byte. If EOF was received before any byte is read, this function returns empty byte object. - Returned value is not limited with limit, configured at stream creation. + Returned value is not limited with limit, configured at stream + creation. If stream was paused, this function will automatically resume it if needed. @@ -627,13 +638,14 @@ def read(self, n=-1): def readexactly(self, n): """Read exactly `n` bytes. - Raise an `IncompleteReadError` if EOF is reached before `n` bytes can be - read. The `IncompleteReadError.partial` attribute of the exception will + Raise an IncompleteReadError if EOF is reached before `n` bytes can be + read. The IncompleteReadError.partial attribute of the exception will contain the partial read bytes. if n is zero, return empty bytes object. - Returned value is not limited with limit, configured at stream creation. + Returned value is not limited with limit, configured at stream + creation. If stream was paused, this function will automatically resume it if needed. @@ -647,25 +659,22 @@ def readexactly(self, n): if n == 0: return b'' - # There used to be "optimized" code here. It created its own - # Future and waited until self._buffer had at least the n - # bytes, then called read(n). Unfortunately, this could pause - # the transport if the argument was larger than the pause - # limit (which is twice self._limit). So now we just read() - # into a local buffer. - - blocks = [] - while n > 0: - block = yield from self.read(n) - if not block: - partial = b''.join(blocks) - raise IncompleteReadError(partial, len(partial) + n) - blocks.append(block) - n -= len(block) + while len(self._buffer) < n: + if self._eof: + incomplete = bytes(self._buffer) + self._buffer.clear() + raise IncompleteReadError(incomplete, n) - assert n == 0 + yield from self._wait_for_data('readexactly') - return b''.join(blocks) + if len(self._buffer) == n: + data = bytes(self._buffer) + self._buffer.clear() + else: + data = bytes(self._buffer[:n]) + del self._buffer[:n] + self._maybe_resume_transport() + return data if compat.PY35: @coroutine @@ -678,3 +687,9 @@ def __anext__(self): if val == b'': raise StopAsyncIteration return val + + if compat.PY352: + # In Python 3.5.2 and greater, __aiter__ should return + # the asynchronous iterator directly. + def __aiter__(self): + return self diff --git a/lib/asyncio/subprocess.py b/lib/asyncio/subprocess.py index ead4039b..4c854668 100644 --- a/lib/asyncio/subprocess.py +++ b/lib/asyncio/subprocess.py @@ -24,6 +24,8 @@ def __init__(self, limit, loop): self._limit = limit self.stdin = self.stdout = self.stderr = None self._transport = None + self._process_exited = False + self._pipe_fds = [] def __repr__(self): info = [self.__class__.__name__] @@ -43,12 +45,14 @@ def connection_made(self, transport): self.stdout = streams.StreamReader(limit=self._limit, loop=self._loop) self.stdout.set_transport(stdout_transport) + self._pipe_fds.append(1) stderr_transport = transport.get_pipe_transport(2) if stderr_transport is not None: self.stderr = streams.StreamReader(limit=self._limit, loop=self._loop) self.stderr.set_transport(stderr_transport) + self._pipe_fds.append(2) stdin_transport = transport.get_pipe_transport(0) if stdin_transport is not None: @@ -86,9 +90,18 @@ def pipe_connection_lost(self, fd, exc): else: reader.set_exception(exc) + if fd in self._pipe_fds: + self._pipe_fds.remove(fd) + self._maybe_close_transport() + def process_exited(self): - self._transport.close() - self._transport = None + self._process_exited = True + self._maybe_close_transport() + + def _maybe_close_transport(self): + if len(self._pipe_fds) == 0 and self._process_exited: + self._transport.close() + self._transport = None class Process: @@ -166,7 +179,7 @@ def _read_stream(self, fd): @coroutine def communicate(self, input=None): - if input: + if input is not None: stdin = self._feed_stdin(input) else: stdin = self._noop() diff --git a/lib/asyncio/tasks.py b/lib/asyncio/tasks.py index c37aa419..6c43e683 100644 --- a/lib/asyncio/tasks.py +++ b/lib/asyncio/tasks.py @@ -4,7 +4,6 @@ 'FIRST_COMPLETED', 'FIRST_EXCEPTION', 'ALL_COMPLETED', 'wait', 'wait_for', 'as_completed', 'sleep', 'async', 'gather', 'shield', 'ensure_future', 'run_coroutine_threadsafe', - 'timeout', ] import concurrent.futures @@ -209,6 +208,7 @@ def cancel(self): terminates with a CancelledError exception (even if cancel() was not called). """ + self._log_traceback = False if self.done(): return False if self._fut_waiter is not None: @@ -241,8 +241,13 @@ def _step(self, exc=None): else: result = coro.throw(exc) except StopIteration as exc: - self.set_result(exc.value) - except futures.CancelledError as exc: + if self._must_cancel: + # Task is cancelled right before coro stops. + self._must_cancel = False + self.set_exception(futures.CancelledError()) + else: + self.set_result(exc.value) + except futures.CancelledError: super().cancel() # I.e., Future.cancel(self). except Exception as exc: self.set_exception(exc) @@ -250,7 +255,8 @@ def _step(self, exc=None): self.set_exception(exc) raise else: - if isinstance(result, futures.Future): + blocking = getattr(result, '_asyncio_future_blocking', None) + if blocking is not None: # Yielded Future must come from Future.__iter__(). if result._loop is not self._loop: self._loop.call_soon( @@ -258,13 +264,20 @@ def _step(self, exc=None): RuntimeError( 'Task {!r} got Future {!r} attached to a ' 'different loop'.format(self, result))) - elif result._blocking: - result._blocking = False - result.add_done_callback(self._wakeup) - self._fut_waiter = result - if self._must_cancel: - if self._fut_waiter.cancel(): - self._must_cancel = False + elif blocking: + if result is self: + self._loop.call_soon( + self._step, + RuntimeError( + 'Task cannot await on itself: {!r}'.format( + self))) + else: + result._asyncio_future_blocking = False + result.add_done_callback(self._wakeup) + self._fut_waiter = result + if self._must_cancel: + if self._fut_waiter.cancel(): + self._must_cancel = False else: self._loop.call_soon( self._step, @@ -333,7 +346,7 @@ def wait(fs, *, loop=None, timeout=None, return_when=ALL_COMPLETED): Note: This does not raise TimeoutError! Futures that aren't done when the timeout occurs are returned in the second set. """ - if isinstance(fs, futures.Future) or coroutines.iscoroutine(fs): + if futures.isfuture(fs) or coroutines.iscoroutine(fs): raise TypeError("expect a list of futures, not %s" % type(fs).__name__) if not fs: raise ValueError('Set of coroutines/Futures is empty.') @@ -373,7 +386,7 @@ def wait_for(fut, timeout, *, loop=None): if timeout is None: return (yield from fut) - waiter = futures.Future(loop=loop) + waiter = loop.create_future() timeout_handle = loop.call_later(timeout, _release_waiter, waiter) cb = functools.partial(_release_waiter, waiter) @@ -401,12 +414,12 @@ def wait_for(fut, timeout, *, loop=None): @coroutine def _wait(fs, timeout, return_when, loop): - """Internal helper for wait() and _wait_for(). + """Internal helper for wait() and wait_for(). The fs argument must be a collection of Futures. """ assert fs, 'Set of Futures is empty.' - waiter = futures.Future(loop=loop) + waiter = loop.create_future() timeout_handle = None if timeout is not None: timeout_handle = loop.call_later(timeout, _release_waiter, waiter) @@ -462,7 +475,7 @@ def as_completed(fs, *, loop=None, timeout=None): Note: The futures 'f' are not necessarily members of fs. """ - if isinstance(fs, futures.Future) or coroutines.iscoroutine(fs): + if futures.isfuture(fs) or coroutines.iscoroutine(fs): raise TypeError("expect a list of futures, not %s" % type(fs).__name__) loop = loop if loop is not None else events.get_event_loop() todo = {ensure_future(f, loop=loop) for f in set(fs)} @@ -507,7 +520,9 @@ def sleep(delay, result=None, *, loop=None): yield return result - future = futures.Future(loop=loop) + if loop is None: + loop = events.get_event_loop() + future = loop.create_future() h = future._loop.call_later(delay, futures._set_result_unless_cancelled, future, result) @@ -517,7 +532,7 @@ def sleep(delay, result=None, *, loop=None): h.cancel() -def async(coro_or_future, *, loop=None): +def async_(coro_or_future, *, loop=None): """Wrap a coroutine in a future. If the argument is a Future, it is returned directly. @@ -526,17 +541,23 @@ def async(coro_or_future, *, loop=None): """ warnings.warn("asyncio.async() function is deprecated, use ensure_future()", - DeprecationWarning) + DeprecationWarning, + stacklevel=2) return ensure_future(coro_or_future, loop=loop) +# Silence DeprecationWarning: +globals()['async'] = async_ +async_.__name__ = 'async' +del async_ + def ensure_future(coro_or_future, *, loop=None): """Wrap a coroutine or an awaitable in a future. If the argument is a Future, it is returned directly. """ - if isinstance(coro_or_future, futures.Future): + if futures.isfuture(coro_or_future): if loop is not None and loop is not coro_or_future._loop: raise ValueError('loop argument must agree with Future') return coro_or_future @@ -550,7 +571,8 @@ def ensure_future(coro_or_future, *, loop=None): elif compat.PY35 and inspect.isawaitable(coro_or_future): return ensure_future(_wrap_awaitable(coro_or_future), loop=loop) else: - raise TypeError('A Future, a coroutine or an awaitable is required') + raise TypeError('An asyncio.Future, a coroutine or an awaitable is ' + 'required') @coroutine @@ -578,15 +600,21 @@ def __init__(self, children, *, loop=None): def cancel(self): if self.done(): return False + ret = False for child in self._children: - child.cancel() - return True + if child.cancel(): + ret = True + return ret def gather(*coros_or_futures, loop=None, return_exceptions=False): """Return a future aggregating results from the given coroutines or futures. + Coroutines will be wrapped in a future and scheduled in the event + loop. They will not necessarily be scheduled in the same order as + passed in. + All futures must share the same event loop. If all the tasks are done successfully, the returned future's result is the list of results (in the order of the original sequence, not necessarily @@ -604,13 +632,15 @@ def gather(*coros_or_futures, loop=None, return_exceptions=False): be cancelled.) """ if not coros_or_futures: - outer = futures.Future(loop=loop) + if loop is None: + loop = events.get_event_loop() + outer = loop.create_future() outer.set_result([]) return outer arg_to_fut = {} for arg in set(coros_or_futures): - if not isinstance(arg, futures.Future): + if not futures.isfuture(arg): fut = ensure_future(arg, loop=loop) if loop is None: loop = fut._loop @@ -692,7 +722,7 @@ def shield(arg, *, loop=None): # Shortcut. return inner loop = inner._loop - outer = futures.Future(loop=loop) + outer = loop.create_future() def _done_callback(inner): if outer.cancelled(): @@ -733,53 +763,3 @@ def callback(): loop.call_soon_threadsafe(callback) return future - - -def timeout(timeout, *, loop=None): - """A factory which produce a context manager with timeout. - - Useful in cases when you want to apply timeout logic around block - of code or in cases when asyncio.wait_for is not suitable. - - For example: - - >>> with asyncio.timeout(0.001): - ... yield from coro() - - - timeout: timeout value in seconds - loop: asyncio compatible event loop - """ - if loop is None: - loop = events.get_event_loop() - return _Timeout(timeout, loop=loop) - - -class _Timeout: - def __init__(self, timeout, *, loop): - self._timeout = timeout - self._loop = loop - self._task = None - self._cancelled = False - self._cancel_handler = None - - def __enter__(self): - self._task = Task.current_task(loop=self._loop) - if self._task is None: - raise RuntimeError('Timeout context manager should be used ' - 'inside a task') - self._cancel_handler = self._loop.call_later( - self._timeout, self._cancel_task) - return self - - def __exit__(self, exc_type, exc_val, exc_tb): - if exc_type is futures.CancelledError and self._cancelled: - self._cancel_handler = None - self._task = None - raise futures.TimeoutError - self._cancel_handler.cancel() - self._cancel_handler = None - self._task = None - - def _cancel_task(self): - self._cancelled = self._task.cancel() diff --git a/lib/asyncio/test_utils.py b/lib/asyncio/test_utils.py index 396e6aed..a1686c58 100644 --- a/lib/asyncio/test_utils.py +++ b/lib/asyncio/test_utils.py @@ -13,6 +13,8 @@ import threading import time import unittest +import weakref + from unittest import mock from http.server import HTTPServer @@ -31,6 +33,7 @@ from . import tasks from .coroutines import coroutine from .log import logger +from test import support if sys.platform == 'win32': # pragma: no cover @@ -300,6 +303,8 @@ def gen(): self.writers = {} self.reset_counters() + self._transports = weakref.WeakValueDictionary() + def time(self): return self._time @@ -318,10 +323,10 @@ def close(self): else: # pragma: no cover raise AssertionError("Time generator is not finished") - def add_reader(self, fd, callback, *args): + def _add_reader(self, fd, callback, *args): self.readers[fd] = events.Handle(callback, args, self) - def remove_reader(self, fd): + def _remove_reader(self, fd): self.remove_reader_count[fd] += 1 if fd in self.readers: del self.readers[fd] @@ -337,10 +342,10 @@ def assert_reader(self, fd, callback, *args): assert handle._args == args, '{!r} != {!r}'.format( handle._args, args) - def add_writer(self, fd, callback, *args): + def _add_writer(self, fd, callback, *args): self.writers[fd] = events.Handle(callback, args, self) - def remove_writer(self, fd): + def _remove_writer(self, fd): self.remove_writer_count[fd] += 1 if fd in self.writers: del self.writers[fd] @@ -356,6 +361,36 @@ def assert_writer(self, fd, callback, *args): assert handle._args == args, '{!r} != {!r}'.format( handle._args, args) + def _ensure_fd_no_transport(self, fd): + try: + transport = self._transports[fd] + except KeyError: + pass + else: + raise RuntimeError( + 'File descriptor {!r} is used by transport {!r}'.format( + fd, transport)) + + def add_reader(self, fd, callback, *args): + """Add a reader callback.""" + self._ensure_fd_no_transport(fd) + return self._add_reader(fd, callback, *args) + + def remove_reader(self, fd): + """Remove a reader callback.""" + self._ensure_fd_no_transport(fd) + return self._remove_reader(fd) + + def add_writer(self, fd, callback, *args): + """Add a writer callback..""" + self._ensure_fd_no_transport(fd) + return self._add_writer(fd, callback, *args) + + def remove_writer(self, fd): + """Remove a writer callback.""" + self._ensure_fd_no_transport(fd) + return self._remove_writer(fd) + def reset_counters(self): self.remove_reader_count = collections.defaultdict(int) self.remove_writer_count = collections.defaultdict(int) @@ -415,13 +450,27 @@ def new_test_loop(self, gen=None): self.set_event_loop(loop) return loop + def unpatch_get_running_loop(self): + events._get_running_loop = self._get_running_loop + + def setUp(self): + self._get_running_loop = events._get_running_loop + events._get_running_loop = lambda: None + self._thread_cleanup = support.threading_setup() + def tearDown(self): + self.unpatch_get_running_loop() + events.set_event_loop(None) # Detect CPython bug #23353: ensure that yield/yield-from is not used # in an except block of a generator self.assertEqual(sys.exc_info(), (None, None, None)) + self.doCleanups() + support.threading_cleanup(*self._thread_cleanup) + support.reap_children() + if not compat.PY34: # Python 3.3 compatibility def subTest(self, *args, **kwargs): diff --git a/lib/asyncio/transports.py b/lib/asyncio/transports.py index 9a6d9197..0db08757 100644 --- a/lib/asyncio/transports.py +++ b/lib/asyncio/transports.py @@ -33,6 +33,14 @@ def close(self): """ raise NotImplementedError + def set_protocol(self, protocol): + """Set a new protocol.""" + raise NotImplementedError + + def get_protocol(self): + """Return the current protocol.""" + raise NotImplementedError + class ReadTransport(BaseTransport): """Interface for read-only transports.""" diff --git a/lib/asyncio/unix_events.py b/lib/asyncio/unix_events.py index 7747ff41..77905344 100644 --- a/lib/asyncio/unix_events.py +++ b/lib/asyncio/unix_events.py @@ -39,6 +39,13 @@ def _sighandler_noop(signum, frame): pass +try: + _fspath = os.fspath +except AttributeError: + # Python 3.5 or earlier + _fspath = lambda path: path + + class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop): """Unix event loop. @@ -177,7 +184,7 @@ def _make_subprocess_transport(self, protocol, args, shell, stdin, stdout, stderr, bufsize, extra=None, **kwargs): with events.get_child_watcher() as watcher: - waiter = futures.Future(loop=self) + waiter = self.create_future() transp = _UnixSubprocessTransport(self, protocol, args, shell, stdin, stdout, stderr, bufsize, waiter=waiter, extra=extra, @@ -234,6 +241,11 @@ def create_unix_connection(self, protocol_factory, path, *, else: if sock is None: raise ValueError('no path and sock were specified') + if (sock.family != socket.AF_UNIX or + not base_events._is_stream_socket(sock)): + raise ValueError( + 'A UNIX Domain Stream Socket was expected, got {!r}' + .format(sock)) sock.setblocking(False) transport, protocol = yield from self._create_connection_transport( @@ -251,8 +263,20 @@ def create_unix_server(self, protocol_factory, path=None, *, raise ValueError( 'path and sock can not be specified at the same time') + path = _fspath(path) sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + # Check for abstract socket. `str` and `bytes` paths are supported. + if path[0] not in (0, '\x00'): + try: + if stat.S_ISSOCK(os.stat(path).st_mode): + os.remove(path) + except FileNotFoundError: + pass + except OSError as err: + # Directory may have permissions only to create socket. + logger.error('Unable to check or remove stale UNIX socket %r: %r', path, err) + try: sock.bind(path) except OSError as exc: @@ -272,9 +296,11 @@ def create_unix_server(self, protocol_factory, path=None, *, raise ValueError( 'path was not specified, and no sock specified') - if sock.family != socket.AF_UNIX: + if (sock.family != socket.AF_UNIX or + not base_events._is_stream_socket(sock)): raise ValueError( - 'A UNIX Domain Socket was expected, got {!r}'.format(sock)) + 'A UNIX Domain Stream Socket was expected, got {!r}' + .format(sock)) server = base_events.Server(self, [sock]) sock.listen(backlog) @@ -305,17 +331,23 @@ def __init__(self, loop, pipe, protocol, waiter=None, extra=None): self._loop = loop self._pipe = pipe self._fileno = pipe.fileno() + self._protocol = protocol + self._closing = False + mode = os.fstat(self._fileno).st_mode if not (stat.S_ISFIFO(mode) or stat.S_ISSOCK(mode) or stat.S_ISCHR(mode)): + self._pipe = None + self._fileno = None + self._protocol = None raise ValueError("Pipe transport is for pipes/sockets only.") + _set_nonblocking(self._fileno) - self._protocol = protocol - self._closing = False + self._loop.call_soon(self._protocol.connection_made, self) # only start reading when connection_made() has been called - self._loop.call_soon(self._loop.add_reader, + self._loop.call_soon(self._loop._add_reader, self._fileno, self._read_ready) if waiter is not None: # only wake up the waiter when connection_made() has been called @@ -329,14 +361,17 @@ def __repr__(self): elif self._closing: info.append('closing') info.append('fd=%s' % self._fileno) - if self._pipe is not None: + selector = getattr(self._loop, '_selector', None) + if self._pipe is not None and selector is not None: polling = selector_events._test_selector_event( - self._loop._selector, + selector, self._fileno, selectors.EVENT_READ) if polling: info.append('polling') else: info.append('idle') + elif self._pipe is not None: + info.append('open') else: info.append('closed') return '<%s>' % ' '.join(info) @@ -355,15 +390,21 @@ def _read_ready(self): if self._loop.get_debug(): logger.info("%r was closed by peer", self) self._closing = True - self._loop.remove_reader(self._fileno) + self._loop._remove_reader(self._fileno) self._loop.call_soon(self._protocol.eof_received) self._loop.call_soon(self._call_connection_lost, None) def pause_reading(self): - self._loop.remove_reader(self._fileno) + self._loop._remove_reader(self._fileno) def resume_reading(self): - self._loop.add_reader(self._fileno, self._read_ready) + self._loop._add_reader(self._fileno, self._read_ready) + + def set_protocol(self, protocol): + self._protocol = protocol + + def get_protocol(self): + return self._protocol def is_closing(self): return self._closing @@ -397,7 +438,7 @@ def _fatal_error(self, exc, message='Fatal error on pipe transport'): def _close(self, exc): self._closing = True - self._loop.remove_reader(self._fileno) + self._loop._remove_reader(self._fileno) self._loop.call_soon(self._call_connection_lost, exc) def _call_connection_lost(self, exc): @@ -418,27 +459,31 @@ def __init__(self, loop, pipe, protocol, waiter=None, extra=None): self._extra['pipe'] = pipe self._pipe = pipe self._fileno = pipe.fileno() + self._protocol = protocol + self._buffer = bytearray() + self._conn_lost = 0 + self._closing = False # Set when close() or write_eof() called. + mode = os.fstat(self._fileno).st_mode + is_char = stat.S_ISCHR(mode) + is_fifo = stat.S_ISFIFO(mode) is_socket = stat.S_ISSOCK(mode) - if not (is_socket or - stat.S_ISFIFO(mode) or - stat.S_ISCHR(mode)): + if not (is_char or is_fifo or is_socket): + self._pipe = None + self._fileno = None + self._protocol = None raise ValueError("Pipe transport is only for " "pipes, sockets and character devices") - _set_nonblocking(self._fileno) - self._protocol = protocol - self._buffer = [] - self._conn_lost = 0 - self._closing = False # Set when close() or write_eof() called. + _set_nonblocking(self._fileno) self._loop.call_soon(self._protocol.connection_made, self) # On AIX, the reader trick (to be notified when the read end of the # socket is closed) only works for sockets. On other platforms it # works for pipes and sockets. (Exception: OS X 10.4? Issue #19294.) - if is_socket or not sys.platform.startswith("aix"): + if is_socket or (is_fifo and not sys.platform.startswith("aix")): # only start reading when connection_made() has been called - self._loop.call_soon(self._loop.add_reader, + self._loop.call_soon(self._loop._add_reader, self._fileno, self._read_ready) if waiter is not None: @@ -453,9 +498,10 @@ def __repr__(self): elif self._closing: info.append('closing') info.append('fd=%s' % self._fileno) - if self._pipe is not None: + selector = getattr(self._loop, '_selector', None) + if self._pipe is not None and selector is not None: polling = selector_events._test_selector_event( - self._loop._selector, + selector, self._fileno, selectors.EVENT_WRITE) if polling: info.append('polling') @@ -464,12 +510,14 @@ def __repr__(self): bufsize = self.get_write_buffer_size() info.append('bufsize=%s' % bufsize) + elif self._pipe is not None: + info.append('open') else: info.append('closed') return '<%s>' % ' '.join(info) def get_write_buffer_size(self): - return sum(len(data) for data in self._buffer) + return len(self._buffer) def _read_ready(self): # Pipe was closed by peer. @@ -507,39 +555,37 @@ def write(self, data): if n == len(data): return elif n > 0: - data = data[n:] - self._loop.add_writer(self._fileno, self._write_ready) + data = memoryview(data)[n:] + self._loop._add_writer(self._fileno, self._write_ready) - self._buffer.append(data) + self._buffer += data self._maybe_pause_protocol() def _write_ready(self): - data = b''.join(self._buffer) - assert data, 'Data should not be empty' + assert self._buffer, 'Data should not be empty' - self._buffer.clear() try: - n = os.write(self._fileno, data) + n = os.write(self._fileno, self._buffer) except (BlockingIOError, InterruptedError): - self._buffer.append(data) + pass except Exception as exc: + self._buffer.clear() self._conn_lost += 1 # Remove writer here, _fatal_error() doesn't it # because _buffer is empty. - self._loop.remove_writer(self._fileno) + self._loop._remove_writer(self._fileno) self._fatal_error(exc, 'Fatal write error on pipe transport') else: - if n == len(data): - self._loop.remove_writer(self._fileno) + if n == len(self._buffer): + self._buffer.clear() + self._loop._remove_writer(self._fileno) self._maybe_resume_protocol() # May append to buffer. - if not self._buffer and self._closing: - self._loop.remove_reader(self._fileno) + if self._closing: + self._loop._remove_reader(self._fileno) self._call_connection_lost(None) return elif n > 0: - data = data[n:] - - self._buffer.append(data) # Try again later. + del self._buffer[:n] def can_write_eof(self): return True @@ -550,9 +596,15 @@ def write_eof(self): assert self._pipe self._closing = True if not self._buffer: - self._loop.remove_reader(self._fileno) + self._loop._remove_reader(self._fileno) self._loop.call_soon(self._call_connection_lost, None) + def set_protocol(self, protocol): + self._protocol = protocol + + def get_protocol(self): + return self._protocol + def is_closing(self): return self._closing @@ -575,7 +627,7 @@ def abort(self): def _fatal_error(self, exc, message='Fatal error on pipe transport'): # should be called by exception handler only - if isinstance(exc, (BrokenPipeError, ConnectionResetError)): + if isinstance(exc, base_events._FATAL_ERROR_IGNORE): if self._loop.get_debug(): logger.debug("%r: %s", self, message, exc_info=True) else: @@ -590,9 +642,9 @@ def _fatal_error(self, exc, message='Fatal error on pipe transport'): def _close(self, exc=None): self._closing = True if self._buffer: - self._loop.remove_writer(self._fileno) + self._loop._remove_writer(self._fileno) self._buffer.clear() - self._loop.remove_reader(self._fileno) + self._loop._remove_reader(self._fileno) self._loop.call_soon(self._call_connection_lost, exc) def _call_connection_lost(self, exc): @@ -720,6 +772,7 @@ class BaseChildWatcher(AbstractChildWatcher): def __init__(self): self._loop = None + self._callbacks = {} def close(self): self.attach_loop(None) @@ -733,6 +786,12 @@ def _do_waitpid_all(self): def attach_loop(self, loop): assert loop is None or isinstance(loop, events.AbstractEventLoop) + if self._loop is not None and loop is None and self._callbacks: + warnings.warn( + 'A loop is being detached ' + 'from a child watcher with pending handlers', + RuntimeWarning) + if self._loop is not None: self._loop.remove_signal_handler(signal.SIGCHLD) @@ -781,10 +840,6 @@ class SafeChildWatcher(BaseChildWatcher): big number of children (O(n) each time SIGCHLD is raised) """ - def __init__(self): - super().__init__() - self._callbacks = {} - def close(self): self._callbacks.clear() super().close() @@ -796,6 +851,11 @@ def __exit__(self, a, b, c): pass def add_child_handler(self, pid, callback, *args): + if self._loop is None: + raise RuntimeError( + "Cannot add child handler, " + "the child watcher does not have a loop attached") + self._callbacks[pid] = (callback, args) # Prevent a race condition in case the child is already terminated. @@ -860,7 +920,6 @@ class FastChildWatcher(BaseChildWatcher): """ def __init__(self): super().__init__() - self._callbacks = {} self._lock = threading.Lock() self._zombies = {} self._forks = 0 @@ -892,6 +951,12 @@ def __exit__(self, a, b, c): def add_child_handler(self, pid, callback, *args): assert self._forks, "Must use the context manager" + + if self._loop is None: + raise RuntimeError( + "Cannot add child handler, " + "the child watcher does not have a loop attached") + with self._lock: try: returncode = self._zombies.pop(pid) diff --git a/lib/asyncio/windows_events.py b/lib/asyncio/windows_events.py index 922594f1..668fe145 100644 --- a/lib/asyncio/windows_events.py +++ b/lib/asyncio/windows_events.py @@ -197,7 +197,7 @@ def _unregister_wait_cb(self, fut): # # If the IocpProactor already received the event, it's safe to call # _unregister() because we kept a reference to the Overlapped object - # which is used as an unique key. + # which is used as a unique key. self._proactor._unregister(self._ov) self._proactor = None @@ -366,7 +366,7 @@ def loop_accept_pipe(f=None): def _make_subprocess_transport(self, protocol, args, shell, stdin, stdout, stderr, bufsize, extra=None, **kwargs): - waiter = futures.Future(loop=self) + waiter = self.create_future() transp = _WindowsSubprocessTransport(self, protocol, args, shell, stdin, stdout, stderr, bufsize, waiter=waiter, extra=extra, @@ -417,7 +417,7 @@ def select(self, timeout=None): return tmp def _result(self, value): - fut = futures.Future(loop=self._loop) + fut = self._loop.create_future() fut.set_result(value) return fut diff --git a/lib/asyncore.py b/lib/asyncore.py index 00a6396d..3b51f0f3 100644 --- a/lib/asyncore.py +++ b/lib/asyncore.py @@ -57,8 +57,8 @@ ENOTCONN, ESHUTDOWN, EISCONN, EBADF, ECONNABORTED, EPIPE, EAGAIN, \ errorcode -_DISCONNECTED = frozenset((ECONNRESET, ENOTCONN, ESHUTDOWN, ECONNABORTED, EPIPE, - EBADF)) +_DISCONNECTED = frozenset({ECONNRESET, ENOTCONN, ESHUTDOWN, ECONNABORTED, EPIPE, + EBADF}) try: socket_map @@ -141,10 +141,7 @@ def poll(timeout=0.0, map=None): time.sleep(timeout) return - try: - r, w, e = select.select(r, w, e, timeout) - except InterruptedError: - return + r, w, e = select.select(r, w, e, timeout) for fd in r: obj = map.get(fd) @@ -182,10 +179,8 @@ def poll2(timeout=0.0, map=None): flags |= select.POLLOUT if flags: pollster.register(fd, flags) - try: - r = pollster.poll(timeout) - except InterruptedError: - r = [] + + r = pollster.poll(timeout) for fd, flags in r: obj = map.get(fd) if obj is None: @@ -220,7 +215,7 @@ class dispatcher: connecting = False closing = False addr = None - ignore_log_types = frozenset(['warning']) + ignore_log_types = frozenset({'warning'}) def __init__(self, sock=None, map=None): if map is None: @@ -255,7 +250,7 @@ def __init__(self, sock=None, map=None): self.socket = None def __repr__(self): - status = [self.__class__.__module__+"."+self.__class__.__name__] + status = [self.__class__.__module__+"."+self.__class__.__qualname__] if self.accepting and self.addr: status.append('listening') elif self.connected: @@ -404,20 +399,6 @@ def close(self): if why.args[0] not in (ENOTCONN, EBADF): raise - # cheap inheritance, used to pass all other attribute - # references to the underlying socket object. - def __getattr__(self, attr): - try: - 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 # and 'log_info' is for informational, warning and error logging. @@ -604,8 +585,6 @@ def close_all(map=None, ignore_all=False): # Regardless, this is useful for pipes, and stdin/stdout... if os.name == 'posix': - import fcntl - class file_wrapper: # Here we override just enough to make a file # look like a socket for the purposes of asyncore. @@ -656,9 +635,7 @@ def __init__(self, fd, map=None): pass self.set_file(fd) # set it to non-blocking mode - flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0) - flags = flags | os.O_NONBLOCK - fcntl.fcntl(fd, fcntl.F_SETFL, flags) + os.set_blocking(fd, False) def set_file(self, fd): self.socket = file_wrapper(fd) diff --git a/lib/base64.py b/lib/base64.py index 640f787c..0dc64cff 100644 --- a/lib/base64.py +++ b/lib/base64.py @@ -12,7 +12,7 @@ __all__ = [ - # Legacy interface exports traditional RFC 1521 Base64 encodings + # Legacy interface exports traditional RFC 2045 Base64 encodings 'encode', 'decode', 'encodebytes', 'decodebytes', # Generalized interface for other encodings 'b64encode', 'b64decode', 'b32encode', 'b32decode', @@ -49,14 +49,11 @@ def _bytes_from_decode_data(s): # Base64 encoding/decoding uses binascii def b64encode(s, altchars=None): - """Encode a byte string using Base64. + """Encode the bytes-like object s using Base64 and return a bytes object. - s is the byte string to encode. Optional altchars must be a byte - string of length 2 which specifies an alternative alphabet for the - '+' and '/' characters. This allows an application to - e.g. generate url or filesystem safe Base64 strings. - - The encoded byte string is returned. + Optional altchars should be a byte string of length 2 which specifies an + alternative alphabet for the '+' and '/' characters. This allows an + application to e.g. generate url or filesystem safe Base64 strings. """ # Strip off the trailing newline encoded = binascii.b2a_base64(s)[:-1] @@ -67,18 +64,19 @@ def b64encode(s, altchars=None): def b64decode(s, altchars=None, validate=False): - """Decode a Base64 encoded byte string. + """Decode the Base64 encoded bytes-like object or ASCII string s. - 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. + Optional altchars must be a bytes-like object or ASCII string of length 2 + which specifies the alternative alphabet used instead of the '+' and '/' + characters. - The decoded string is returned. A binascii.Error is raised if s is - incorrectly padded. + The result is returned as a bytes object. 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 validate is False (the default), characters that are neither in the + normal base-64 alphabet nor the alternative alphabet are discarded prior + to the padding check. If validate is True, these non-alphabet characters + in the input result in a binascii.Error. """ s = _bytes_from_decode_data(s) if altchars is not None: @@ -91,19 +89,19 @@ def b64decode(s, altchars=None, validate=False): def standard_b64encode(s): - """Encode a byte string using the standard Base64 alphabet. + """Encode bytes-like object s using the standard Base64 alphabet. - s is the byte string to encode. The encoded byte string is returned. + The result is returned as a bytes object. """ return b64encode(s) def standard_b64decode(s): - """Decode a byte string encoded with the standard Base64 alphabet. + """Decode bytes encoded with the standard Base64 alphabet. - s is the byte string to decode. The decoded byte string is - returned. binascii.Error is raised if the input is incorrectly - padded or if there are non-alphabet characters present in the - input. + Argument s is a bytes-like object or ASCII string to decode. The result + is returned as a bytes object. A binascii.Error is raised if the input + is incorrectly padded. Characters that are not in the standard alphabet + are discarded prior to the padding check. """ return b64decode(s) @@ -112,21 +110,22 @@ def standard_b64decode(s): _urlsafe_decode_translation = bytes.maketrans(b'-_', b'+/') def urlsafe_b64encode(s): - """Encode a byte string using a url-safe Base64 alphabet. + """Encode bytes using the URL- and filesystem-safe Base64 alphabet. - s is the byte string to encode. The encoded byte string is - returned. The alphabet uses '-' instead of '+' and '_' instead of + Argument s is a bytes-like object to encode. The result is returned as a + bytes object. The alphabet uses '-' instead of '+' and '_' instead of '/'. """ return b64encode(s).translate(_urlsafe_encode_translation) def urlsafe_b64decode(s): - """Decode a byte string encoded with the standard Base64 alphabet. + """Decode bytes using the URL- and filesystem-safe Base64 alphabet. - s is the byte string to decode. The decoded byte string is - returned. binascii.Error is raised if the input is incorrectly - padded or if there are non-alphabet characters present in the - input. + Argument s is a bytes-like object or ASCII string to decode. The result + is returned as a bytes object. A binascii.Error is raised if the input + is incorrectly padded. Characters that are not in the URL-safe base-64 + alphabet, and are not a plus '+' or slash '/', are discarded prior to the + padding check. The alphabet uses '-' instead of '+' and '_' instead of '/'. """ @@ -142,9 +141,7 @@ def urlsafe_b64decode(s): _b32rev = None def b32encode(s): - """Encode a byte string using Base32. - - s is the byte string to encode. The encoded byte string is returned. + """Encode the bytes-like object s using Base32 and return a bytes object. """ global _b32tab2 # Delay the initialization of the table to not waste memory @@ -182,11 +179,10 @@ def b32encode(s): return bytes(encoded) def b32decode(s, casefold=False, map01=None): - """Decode a Base32 encoded byte string. + """Decode the Base32 encoded bytes-like object or ASCII string s. - s is the byte string to decode. Optional casefold is a flag - specifying whether a lowercase alphabet is acceptable as input. - For security purposes, the default is False. + Optional casefold is a flag specifying whether a lowercase alphabet is + acceptable as input. For security purposes, the default is False. RFC 3548 allows for optional mapping of the digit 0 (zero) to the letter O (oh), and for optional mapping of the digit 1 (one) to @@ -196,7 +192,7 @@ def b32decode(s, casefold=False, map01=None): the letter O). For security purposes the default is None, so that 0 and 1 are not allowed in the input. - The decoded byte string is returned. binascii.Error is raised if + The result is returned as a bytes object. A binascii.Error is raised if the input is incorrectly padded or if there are non-alphabet characters present in the input. """ @@ -257,23 +253,20 @@ def b32decode(s, casefold=False, map01=None): # lowercase. The RFC also recommends against accepting input case # insensitively. def b16encode(s): - """Encode a byte string using Base16. - - s is the byte string to encode. The encoded byte string is returned. + """Encode the bytes-like object s using Base16 and return a bytes object. """ return binascii.hexlify(s).upper() def b16decode(s, casefold=False): - """Decode a Base16 encoded byte string. + """Decode the Base16 encoded bytes-like object or ASCII string s. - s is the byte string to decode. Optional casefold is a flag - specifying whether a lowercase alphabet is acceptable as input. - For security purposes, the default is False. + Optional casefold is a flag specifying whether a lowercase alphabet is + acceptable as input. For security purposes, the default is False. - 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 result is returned as a bytes object. A binascii.Error is raised if + s is incorrectly padded or if there are non-alphabet characters present + in the input. """ s = _bytes_from_decode_data(s) if casefold: @@ -316,19 +309,17 @@ def _85encode(b, chars, chars2, pad=False, foldnuls=False, foldspaces=False): return b''.join(chunks) def a85encode(b, *, foldspaces=False, wrapcol=0, pad=False, adobe=False): - """Encode a byte string using Ascii85. - - b is the byte string to encode. The encoded byte string is returned. + """Encode bytes-like object b using Ascii85 and return a bytes object. foldspaces is an optional flag that uses the special short sequence 'y' instead of 4 consecutive spaces (ASCII 0x20) as supported by 'btoa'. This feature is not supported by the "standard" Adobe encoding. - wrapcol controls whether the output should have newline ('\\n') characters + wrapcol controls whether the output should have newline (b'\\n') characters added to it. If this is non-zero, each output line will be at most this many characters long. - pad controls whether the input string is padded to a multiple of 4 before + pad controls whether the input is padded to a multiple of 4 before encoding. Note that the btoa implementation always pads. adobe controls whether the encoded byte sequence is framed with <~ and ~>, @@ -359,9 +350,7 @@ def a85encode(b, *, foldspaces=False, wrapcol=0, pad=False, adobe=False): return result def a85decode(b, *, foldspaces=False, adobe=False, ignorechars=b' \t\n\r\v'): - """Decode an Ascii85 encoded byte string. - - s is the byte string to decode. + """Decode the Ascii85 encoded bytes-like object or ASCII string b. foldspaces is a flag that specifies whether the 'y' short sequence should be accepted as shorthand for 4 consecutive spaces (ASCII 0x20). This feature is @@ -373,13 +362,20 @@ def a85decode(b, *, foldspaces=False, adobe=False, ignorechars=b' \t\n\r\v'): ignorechars should be a byte string containing characters to ignore from the input. This should only contain whitespace characters, and by default contains all whitespace characters in ASCII. + + The result is returned as a bytes object. """ b = _bytes_from_decode_data(b) if adobe: - if not (b.startswith(_A85START) and b.endswith(_A85END)): - raise ValueError("Ascii85 encoded byte sequences must be bracketed " - "by {!r} and {!r}".format(_A85START, _A85END)) - b = b[2:-2] # Strip off start/end markers + if not b.endswith(_A85END): + raise ValueError( + "Ascii85 encoded byte sequences must end " + "with {!r}".format(_A85END) + ) + if b.startswith(_A85START): + b = b[2:-2] # Strip off start/end markers + else: + b = b[:-2] # # We have to go through this stepwise, so as to ignore spaces and handle # special short sequences @@ -432,10 +428,10 @@ def a85decode(b, *, foldspaces=False, adobe=False, ignorechars=b' \t\n\r\v'): _b85dec = None def b85encode(b, pad=False): - """Encode an ASCII-encoded byte array in base85 format. + """Encode bytes-like object b in base85 format and return a bytes object. - If pad is true, the input is padded with "\\0" so its length is a multiple of - 4 characters before encoding. + If pad is true, the input is padded with b'\\0' so its length is a multiple of + 4 bytes before encoding. """ global _b85chars, _b85chars2 # Delay the initialization of tables to not waste memory @@ -446,7 +442,10 @@ def b85encode(b, pad=False): return _85encode(b, _b85chars, _b85chars2, pad) def b85decode(b): - """Decode base85-encoded byte array""" + """Decode the base85-encoded bytes-like object or ASCII string b + + The result is returned as a bytes object. + """ global _b85dec # Delay the initialization of tables to not waste memory # if the function is never called @@ -531,7 +530,7 @@ def _input_type_check(s): def encodebytes(s): - """Encode a bytestring into a bytestring containing multiple lines + """Encode a bytestring into a bytes object containing multiple lines of base-64 data.""" _input_type_check(s) pieces = [] @@ -543,20 +542,22 @@ def encodebytes(s): def encodestring(s): """Legacy alias of encodebytes().""" import warnings - warnings.warn("encodestring() is a deprecated alias, use encodebytes()", + warnings.warn("encodestring() is a deprecated alias since 3.1, " + "use encodebytes()", DeprecationWarning, 2) return encodebytes(s) def decodebytes(s): - """Decode a bytestring of base-64 data into a bytestring.""" + """Decode a bytestring of base-64 data into a bytes object.""" _input_type_check(s) return binascii.a2b_base64(s) def decodestring(s): """Legacy alias of decodebytes().""" import warnings - warnings.warn("decodestring() is a deprecated alias, use decodebytes()", + warnings.warn("decodestring() is a deprecated alias since Python 3.1, " + "use decodebytes()", DeprecationWarning, 2) return decodebytes(s) diff --git a/lib/binhex.py b/lib/binhex.py index 8272d5ca..56b5f852 100644 --- a/lib/binhex.py +++ b/lib/binhex.py @@ -235,14 +235,13 @@ def binhex(inp, out): finfo = getfileinfo(inp) ofp = BinHex(finfo, out) - ifp = io.open(inp, 'rb') - # XXXX Do textfile translation on non-mac systems - while True: - d = ifp.read(128000) - if not d: break - ofp.write(d) - ofp.close_data() - ifp.close() + with io.open(inp, 'rb') as ifp: + # XXXX Do textfile translation on non-mac systems + while True: + d = ifp.read(128000) + if not d: break + ofp.write(d) + ofp.close_data() ifp = openrsrc(inp, 'rb') while True: @@ -459,13 +458,12 @@ def hexbin(inp, out): if not out: out = ifp.FName - ofp = io.open(out, 'wb') - # XXXX Do translation on non-mac systems - while True: - d = ifp.read(128000) - if not d: break - ofp.write(d) - ofp.close() + with io.open(out, 'wb') as ofp: + # XXXX Do translation on non-mac systems + while True: + d = ifp.read(128000) + if not d: break + ofp.write(d) ifp.close_data() d = ifp.read_rsrc(128000) diff --git a/lib/bz2.py b/lib/bz2.py index 6c5a60d6..bc78c544 100644 --- a/lib/bz2.py +++ b/lib/bz2.py @@ -12,6 +12,7 @@ from builtins import open as _builtin_open import io import warnings +import _compression try: from threading import RLock @@ -23,13 +24,11 @@ _MODE_CLOSED = 0 _MODE_READ = 1 -_MODE_READ_EOF = 2 +# Value 2 no longer used _MODE_WRITE = 3 -_BUFFER_SIZE = 8192 - -class BZ2File(io.BufferedIOBase): +class BZ2File(_compression.BaseStream): """A file object providing transparent bzip2 (de)compression. @@ -61,13 +60,11 @@ def __init__(self, filename, mode="r", buffering=None, compresslevel=9): multiple compressed streams. """ # This lock must be recursive, so that BufferedIOBase's - # readline(), readlines() and writelines() don't deadlock. + # writelines() does not deadlock. self._lock = RLock() self._fp = None self._closefp = False self._mode = _MODE_CLOSED - self._pos = 0 - self._size = -1 if buffering is not None: warnings.warn("Use of 'buffering' argument is deprecated", @@ -79,9 +76,6 @@ def __init__(self, filename, mode="r", buffering=None, compresslevel=9): if mode in ("", "r", "rb"): mode = "rb" mode_code = _MODE_READ - self._decompressor = BZ2Decompressor() - self._buffer = b"" - self._buffer_offset = 0 elif mode in ("w", "wb"): mode = "wb" mode_code = _MODE_WRITE @@ -107,6 +101,13 @@ def __init__(self, filename, mode="r", buffering=None, compresslevel=9): else: raise TypeError("filename must be a str or bytes object, or a file") + if self._mode == _MODE_READ: + raw = _compression.DecompressReader(self._fp, + BZ2Decompressor, trailing_error=OSError) + self._buffer = io.BufferedReader(raw) + else: + self._pos = 0 + def close(self): """Flush and close the file. @@ -117,8 +118,8 @@ def close(self): if self._mode == _MODE_CLOSED: return try: - if self._mode in (_MODE_READ, _MODE_READ_EOF): - self._decompressor = None + if self._mode == _MODE_READ: + self._buffer.close() elif self._mode == _MODE_WRITE: self._fp.write(self._compressor.flush()) self._compressor = None @@ -130,8 +131,7 @@ def close(self): self._fp = None self._closefp = False self._mode = _MODE_CLOSED - self._buffer = b"" - self._buffer_offset = 0 + self._buffer = None @property def closed(self): @@ -145,125 +145,18 @@ def fileno(self): def seekable(self): """Return whether the file supports seeking.""" - return self.readable() and self._fp.seekable() + return self.readable() and self._buffer.seekable() def readable(self): """Return whether the file was opened for reading.""" self._check_not_closed() - return self._mode in (_MODE_READ, _MODE_READ_EOF) + return self._mode == _MODE_READ def writable(self): """Return whether the file was opened for writing.""" self._check_not_closed() return self._mode == _MODE_WRITE - # Mode-checking helper functions. - - def _check_not_closed(self): - if self.closed: - raise ValueError("I/O operation on closed file") - - def _check_can_read(self): - if self._mode not in (_MODE_READ, _MODE_READ_EOF): - self._check_not_closed() - raise io.UnsupportedOperation("File not open for reading") - - def _check_can_write(self): - if self._mode != _MODE_WRITE: - self._check_not_closed() - raise io.UnsupportedOperation("File not open for writing") - - def _check_can_seek(self): - if self._mode not in (_MODE_READ, _MODE_READ_EOF): - self._check_not_closed() - raise io.UnsupportedOperation("Seeking is only supported " - "on files open for reading") - if not self._fp.seekable(): - raise io.UnsupportedOperation("The underlying file object " - "does not support seeking") - - # Fill the readahead buffer if it is empty. Returns False on EOF. - def _fill_buffer(self): - if self._mode == _MODE_READ_EOF: - return False - # Depending on the input data, our call to the decompressor may not - # return any data. In this case, try again after reading another block. - while self._buffer_offset == len(self._buffer): - rawblock = (self._decompressor.unused_data or - self._fp.read(_BUFFER_SIZE)) - - if not rawblock: - if self._decompressor.eof: - # End-of-stream marker and end of file. We're good. - self._mode = _MODE_READ_EOF - self._size = self._pos - return False - else: - # Problem - we were expecting more compressed data. - raise EOFError("Compressed file ended before the " - "end-of-stream marker was reached") - - if self._decompressor.eof: - # Continue to next stream. - self._decompressor = BZ2Decompressor() - try: - self._buffer = self._decompressor.decompress(rawblock) - except OSError: - # Trailing data isn't a valid bzip2 stream. We're done here. - self._mode = _MODE_READ_EOF - self._size = self._pos - return False - else: - self._buffer = self._decompressor.decompress(rawblock) - self._buffer_offset = 0 - return True - - # Read data until EOF. - # If return_data is false, consume the data without returning it. - def _read_all(self, return_data=True): - # The loop assumes that _buffer_offset is 0. Ensure that this is true. - self._buffer = self._buffer[self._buffer_offset:] - self._buffer_offset = 0 - - blocks = [] - while self._fill_buffer(): - if return_data: - blocks.append(self._buffer) - self._pos += len(self._buffer) - self._buffer = b"" - if return_data: - return b"".join(blocks) - - # Read a block of up to n bytes. - # If return_data is false, consume the data without returning it. - def _read_block(self, n, return_data=True): - # If we have enough data buffered, return immediately. - end = self._buffer_offset + n - if end <= len(self._buffer): - data = self._buffer[self._buffer_offset : end] - self._buffer_offset = end - self._pos += len(data) - return data if return_data else None - - # The loop assumes that _buffer_offset is 0. Ensure that this is true. - self._buffer = self._buffer[self._buffer_offset:] - self._buffer_offset = 0 - - blocks = [] - while n > 0 and self._fill_buffer(): - if n < len(self._buffer): - data = self._buffer[:n] - self._buffer_offset = n - else: - data = self._buffer - self._buffer = b"" - if return_data: - blocks.append(data) - self._pos += len(data) - n -= len(data) - if return_data: - return b"".join(blocks) - def peek(self, n=0): """Return buffered data without advancing the file position. @@ -272,9 +165,10 @@ def peek(self, n=0): """ with self._lock: self._check_can_read() - if not self._fill_buffer(): - return b"" - return self._buffer[self._buffer_offset:] + # Relies on the undocumented fact that BufferedReader.peek() + # always returns at least one byte (except at EOF), independent + # of the value of n + return self._buffer.peek(n) def read(self, size=-1): """Read up to size uncompressed bytes from the file. @@ -284,47 +178,29 @@ def read(self, size=-1): """ with self._lock: self._check_can_read() - if size == 0: - return b"" - elif size < 0: - return self._read_all() - else: - return self._read_block(size) + return self._buffer.read(size) def read1(self, size=-1): """Read up to size uncompressed bytes, while trying to avoid - making multiple reads from the underlying stream. + making multiple reads from the underlying stream. Reads up to a + buffer's worth of data if size is negative. Returns b'' if the file is at EOF. """ - # Usually, read1() calls _fp.read() at most once. However, sometimes - # this does not give enough data for the decompressor to make progress. - # In this case we make multiple reads, to avoid returning b"". with self._lock: self._check_can_read() - if (size == 0 or - # Only call _fill_buffer() if the buffer is actually empty. - # This gives a significant speedup if *size* is small. - (self._buffer_offset == len(self._buffer) and not self._fill_buffer())): - return b"" - if size > 0: - data = self._buffer[self._buffer_offset : - self._buffer_offset + size] - self._buffer_offset += len(data) - else: - data = self._buffer[self._buffer_offset:] - self._buffer = b"" - self._buffer_offset = 0 - self._pos += len(data) - return data + if size < 0: + size = io.DEFAULT_BUFFER_SIZE + return self._buffer.read1(size) def readinto(self, b): - """Read up to len(b) bytes into b. + """Read bytes into b. Returns the number of bytes read (0 for EOF). """ with self._lock: - return io.BufferedIOBase.readinto(self, b) + self._check_can_read() + return self._buffer.readinto(b) def readline(self, size=-1): """Read a line of uncompressed bytes from the file. @@ -339,15 +215,7 @@ def readline(self, size=-1): size = size.__index__() with self._lock: self._check_can_read() - # Shortcut for the common case - the whole line is in the buffer. - if size < 0: - end = self._buffer.find(b"\n", self._buffer_offset) + 1 - if end > 0: - line = self._buffer[self._buffer_offset : end] - self._buffer_offset = end - self._pos += len(line) - return line - return io.BufferedIOBase.readline(self, size) + return self._buffer.readline(size) def readlines(self, size=-1): """Read a list of lines of uncompressed bytes from the file. @@ -361,7 +229,8 @@ def readlines(self, size=-1): raise TypeError("Integer argument expected") size = size.__index__() with self._lock: - return io.BufferedIOBase.readlines(self, size) + self._check_can_read() + return self._buffer.readlines(size) def write(self, data): """Write a byte string to the file. @@ -386,18 +255,9 @@ def writelines(self, seq): Line separators are not added between the written byte strings. """ with self._lock: - return io.BufferedIOBase.writelines(self, seq) - - # Rewind the file to the beginning of the data stream. - def _rewind(self): - self._fp.seek(0, 0) - self._mode = _MODE_READ - self._pos = 0 - self._decompressor = BZ2Decompressor() - self._buffer = b"" - self._buffer_offset = 0 - - def seek(self, offset, whence=0): + return _compression.BaseStream.writelines(self, seq) + + def seek(self, offset, whence=io.SEEK_SET): """Change the file position. The new position is specified by offset, relative to the @@ -414,35 +274,14 @@ def seek(self, offset, whence=0): """ with self._lock: self._check_can_seek() - - # Recalculate offset as an absolute file position. - if whence == 0: - pass - elif whence == 1: - offset = self._pos + offset - elif whence == 2: - # Seeking relative to EOF - we need to know the file's size. - if self._size < 0: - self._read_all(return_data=False) - offset = self._size + offset - else: - raise ValueError("Invalid value for whence: %s" % (whence,)) - - # Make it so that offset is the number of bytes to skip forward. - if offset < self._pos: - self._rewind() - else: - offset -= self._pos - - # Read and discard data until we reach the desired position. - self._read_block(offset, return_data=False) - - return self._pos + return self._buffer.seek(offset, whence) def tell(self): """Return the current file position.""" with self._lock: self._check_not_closed() + if self._mode == _MODE_READ: + return self._buffer.tell() return self._pos diff --git a/lib/calendar.py b/lib/calendar.py index 5244b8d1..76cf8deb 100644 --- a/lib/calendar.py +++ b/lib/calendar.py @@ -8,6 +8,7 @@ import sys import datetime import locale as _locale +from itertools import repeat __all__ = ["IllegalMonthError", "IllegalWeekdayError", "setfirstweekday", "firstweekday", "isleap", "leapdays", "weekday", "monthrange", @@ -174,22 +175,20 @@ def itermonthdays2(self, year, month): Like itermonthdates(), but will yield (day number, weekday number) tuples. For days outside the specified month the day number is 0. """ - for date in self.itermonthdates(year, month): - if date.month != month: - yield (0, date.weekday()) - else: - yield (date.day, date.weekday()) + for i, d in enumerate(self.itermonthdays(year, month), self.firstweekday): + yield d, i % 7 def itermonthdays(self, year, month): """ Like itermonthdates(), but will yield day numbers. For days outside the specified month the day number is 0. """ - for date in self.itermonthdates(year, month): - if date.month != month: - yield 0 - else: - yield date.day + day1, ndays = monthrange(year, month) + days_before = (day1 - self.firstweekday) % 7 + yield from repeat(0, days_before) + yield from range(1, ndays + 1) + days_after = (self.firstweekday - day1 - ndays) % 7 + yield from repeat(0, days_after) def monthdatescalendar(self, year, month): """ @@ -313,7 +312,7 @@ def prmonth(self, theyear, themonth, w=0, l=0): """ Print a month's calendar. """ - print(self.formatmonth(theyear, themonth, w, l), end=' ') + print(self.formatmonth(theyear, themonth, w, l), end='') def formatmonth(self, theyear, themonth, w=0, l=0): """ diff --git a/lib/cgi.py b/lib/cgi.py index 45badf6c..189c6d5b 100644 --- a/lib/cgi.py +++ b/lib/cgi.py @@ -184,7 +184,7 @@ def parse(fp=None, environ=os.environ, keep_blank_values=0, strict_parsing=0): # parse query string function called from urlparse, -# this is done in order to maintain backward compatiblity. +# this is done in order to maintain backward compatibility. def parse_qs(qs, keep_blank_values=0, strict_parsing=0): """Parse a query given as a string argument.""" @@ -566,6 +566,12 @@ def __del__(self): except AttributeError: pass + def __enter__(self): + return self + + def __exit__(self, *args): + self.file.close() + def __repr__(self): """Return a printable representation.""" return "FieldStorage(%r, %r, %r)" % ( diff --git a/lib/cgitb.py b/lib/cgitb.py index 6eb52e76..b2911001 100644 --- a/lib/cgitb.py +++ b/lib/cgitb.py @@ -294,9 +294,8 @@ def handle(self, info=None): (fd, path) = tempfile.mkstemp(suffix=suffix, dir=self.logdir) try: - file = os.fdopen(fd, 'w') - file.write(doc) - file.close() + with os.fdopen(fd, 'w') as file: + file.write(doc) msg = '%s contains the description of this error.' % path except: msg = 'Tried to save traceback to %s, but failed.' % path diff --git a/lib/code.py b/lib/code.py index f8184b6c..53244e32 100644 --- a/lib/code.py +++ b/lib/code.py @@ -7,6 +7,7 @@ import sys import traceback +import argparse from codeop import CommandCompiler, compile_command __all__ = ["InteractiveInterpreter", "InteractiveConsole", "interact", @@ -136,25 +137,18 @@ def showtraceback(self): The output is written by self.write(), below. """ + sys.last_type, sys.last_value, last_tb = ei = sys.exc_info() + sys.last_traceback = last_tb try: - type, value, tb = sys.exc_info() - sys.last_type = type - sys.last_value = value - sys.last_traceback = tb - tblist = traceback.extract_tb(tb) - del tblist[:1] - lines = traceback.format_list(tblist) - if lines: - lines.insert(0, "Traceback (most recent call last):\n") - lines.extend(traceback.format_exception_only(type, value)) + lines = traceback.format_exception(ei[0], ei[1], last_tb.tb_next) + if sys.excepthook is sys.__excepthook__: + self.write(''.join(lines)) + else: + # If someone has set sys.excepthook, we let that take precedence + # over self.write + sys.excepthook(ei[0], ei[1], last_tb) finally: - tblist = tb = None - if sys.excepthook is sys.__excepthook__: - self.write(''.join(lines)) - else: - # If someone has set sys.excepthook, we let that take precedence - # over self.write - sys.excepthook(type, value, tb) + last_tb = ei = None def write(self, data): """Write a string. @@ -299,4 +293,12 @@ def interact(banner=None, readfunc=None, local=None): if __name__ == "__main__": - interact() + parser = argparse.ArgumentParser() + parser.add_argument('-q', action='store_true', + help="don't print version and copyright messages") + args = parser.parse_args() + if args.q or sys.flags.quiet: + banner = '' + else: + banner = None + interact(banner) diff --git a/lib/codecs.py b/lib/codecs.py index 6cdf3f4f..39ec8454 100644 --- a/lib/codecs.py +++ b/lib/codecs.py @@ -27,7 +27,8 @@ "getincrementaldecoder", "getreader", "getwriter", "encode", "decode", "iterencode", "iterdecode", "strict_errors", "ignore_errors", "replace_errors", - "xmlcharrefreplace_errors", "backslashreplace_errors", + "xmlcharrefreplace_errors", + "backslashreplace_errors", "namereplace_errors", "register_error", "lookup_error"] ### Constants @@ -105,8 +106,8 @@ def __new__(cls, encode, decode, streamreader=None, streamwriter=None, return self def __repr__(self): - return "<%s.%s object for encoding %s at 0x%x>" % \ - (self.__class__.__module__, self.__class__.__name__, + return "<%s.%s object for encoding %s at %#x>" % \ + (self.__class__.__module__, self.__class__.__qualname__, self.name, id(self)) class Codec: @@ -126,7 +127,8 @@ class Codec: 'surrogateescape' - replace with private code points U+DCnn. 'xmlcharrefreplace' - Replace with the appropriate XML character reference (only for encoding). - 'backslashreplace' - Replace with backslashed escape sequences + 'backslashreplace' - Replace with backslashed escape sequences. + 'namereplace' - Replace with \\N{...} escape sequences (only for encoding). The set of allowed values can be extended via register_error. @@ -358,7 +360,8 @@ def __init__(self, stream, errors='strict'): 'xmlcharrefreplace' - Replace with the appropriate XML character reference. 'backslashreplace' - Replace with backslashed escape - sequences (only for encoding). + sequences. + 'namereplace' - Replace with \\N{...} escape sequences. The set of allowed parameter values can be extended via register_error. @@ -428,7 +431,8 @@ def __init__(self, stream, errors='strict'): 'strict' - raise a ValueError (or a subclass) 'ignore' - ignore the character and continue with the next - 'replace'- replace with a suitable replacement character; + 'replace'- replace with a suitable replacement character + 'backslashreplace' - Replace with backslashed escape sequences; The set of allowed parameter values can be extended via register_error. @@ -1080,6 +1084,7 @@ def make_encoding_map(decoding_map): replace_errors = lookup_error("replace") xmlcharrefreplace_errors = lookup_error("xmlcharrefreplace") backslashreplace_errors = lookup_error("backslashreplace") + namereplace_errors = lookup_error("namereplace") except LookupError: # In --disable-unicode builds, these error handler are missing strict_errors = None @@ -1087,6 +1092,7 @@ def make_encoding_map(decoding_map): replace_errors = None xmlcharrefreplace_errors = None backslashreplace_errors = None + namereplace_errors = None # Tell modulefinder that using codecs probably needs the encodings # package diff --git a/lib/collections/__init__.py b/lib/collections/__init__.py index f94210ef..bea811db 100644 --- a/lib/collections/__init__.py +++ b/lib/collections/__init__.py @@ -1,3 +1,19 @@ +'''This module implements specialized container datatypes providing +alternatives to Python's general purpose built-in containers, dict, +list, set, and tuple. + +* namedtuple factory function for creating tuple subclasses with named fields +* deque list-like container with fast appends and pops on either end +* ChainMap dict-like class for creating a single view of multiple mappings +* Counter dict subclass for counting hashable objects +* OrderedDict dict subclass that remembers the order entries were added +* defaultdict dict subclass that calls a factory function to supply missing values +* UserDict wrapper around dictionary objects for easier dict subclassing +* UserList wrapper around list objects for easier list subclassing +* UserString wrapper around string objects for easier string subclassing + +''' + __all__ = ['deque', 'defaultdict', 'namedtuple', 'UserDict', 'UserList', 'UserString', 'Counter', 'OrderedDict', 'ChainMap'] @@ -7,7 +23,6 @@ import _collections_abc __all__ += _collections_abc.__all__ -from _collections import deque, defaultdict from operator import itemgetter as _itemgetter, eq as _eq from keyword import iskeyword as _iskeyword import sys as _sys @@ -16,10 +31,40 @@ from itertools import repeat as _repeat, chain as _chain, starmap as _starmap from reprlib import recursive_repr as _recursive_repr +try: + from _collections import deque +except ImportError: + pass +else: + MutableSequence.register(deque) + +try: + from _collections import defaultdict +except ImportError: + pass + + ################################################################################ ### OrderedDict ################################################################################ +class _OrderedDictKeysView(KeysView): + + def __reversed__(self): + yield from reversed(self._mapping) + +class _OrderedDictItemsView(ItemsView): + + def __reversed__(self): + for key in reversed(self._mapping): + yield (key, self._mapping[key]) + +class _OrderedDictValuesView(ValuesView): + + def __reversed__(self): + for key in reversed(self._mapping): + yield self._mapping[key] + class _Link(object): __slots__ = 'prev', 'next', 'key', '__weakref__' @@ -83,6 +128,8 @@ def __delitem__(self, key, dict_delitem=dict.__delitem__): link_next = link.next link_prev.next = link_next link_next.prev = link_prev + link.prev = None + link.next = None def __iter__(self): 'od.__iter__() <==> iter(od)' @@ -142,6 +189,7 @@ def move_to_end(self, key, last=True): link = self.__map[key] link_prev = link.prev link_next = link.next + soft_link = link_next.prev link_prev.next = link_next link_next.prev = link_prev root = self.__root @@ -149,12 +197,14 @@ def move_to_end(self, key, last=True): last = root.prev link.prev = last link.next = root - last.next = root.prev = link + root.prev = soft_link + last.next = link else: first = root.next link.prev = root link.next = first - root.next = first.prev = link + first.prev = soft_link + root.next = link def __sizeof__(self): sizeof = _sys.getsizeof @@ -166,9 +216,19 @@ def __sizeof__(self): return size update = __update = MutableMapping.update - keys = MutableMapping.keys - values = MutableMapping.values - items = MutableMapping.items + + def keys(self): + "D.keys() -> a set-like object providing a view on D's keys" + return _OrderedDictKeysView(self) + + def items(self): + "D.items() -> a set-like object providing a view on D's items" + return _OrderedDictItemsView(self) + + def values(self): + "D.values() -> an object providing a view on D's values" + return _OrderedDictValuesView(self) + __ne__ = MutableMapping.__ne__ __marker = object() @@ -233,6 +293,13 @@ def __eq__(self, other): return dict.__eq__(self, other) +try: + from _collections import OrderedDict +except ImportError: + # Leave the pure Python version in place. + pass + + ################################################################################ ### namedtuple ################################################################################ @@ -301,7 +368,7 @@ def namedtuple(typename, field_names, verbose=False, rename=False): >>> x, y = p # unpack like a regular tuple >>> x, y (11, 22) - >>> p.x + p.y # fields also accessable by name + >>> p.x + p.y # fields also accessible by name 33 >>> d = p._asdict() # convert to a dictionary >>> d['x'] @@ -696,14 +763,22 @@ def __and__(self, other): def __pos__(self): 'Adds an empty counter, effectively stripping negative and zero counts' - return self + Counter() + result = Counter() + for elem, count in self.items(): + if count > 0: + result[elem] = count + return result def __neg__(self): '''Subtracts from an empty counter. Strips positive and zero counts, and flips the sign on negative counts. ''' - return Counter() - self + result = Counter() + for elem, count in self.items(): + if count < 0: + result[elem] = 0 - count + return result def _keep_positive(self): '''Internal method to strip elements with a negative or zero count''' @@ -778,7 +853,8 @@ class ChainMap(MutableMapping): 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. + be 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 @@ -963,7 +1039,6 @@ def __repr__(self): return repr(self.data) def __lt__(self, other): return self.data < self.__cast(other) def __le__(self, other): return self.data <= self.__cast(other) def __eq__(self, other): return self.data == self.__cast(other) - def __ne__(self, other): return self.data != self.__cast(other) def __gt__(self, other): return self.data > self.__cast(other) def __ge__(self, other): return self.data >= self.__cast(other) def __cast(self, other): @@ -1035,15 +1110,13 @@ def __int__(self): return int(self.data) def __float__(self): return float(self.data) def __complex__(self): return complex(self.data) def __hash__(self): return hash(self.data) + def __getnewargs__(self): + return (self.data[:],) def __eq__(self, string): if isinstance(string, UserString): return self.data == string.data return self.data == string - def __ne__(self, string): - if isinstance(string, UserString): - return self.data != string.data - return self.data != string def __lt__(self, string): if isinstance(string, UserString): return self.data < string.data @@ -1083,9 +1156,13 @@ def __mul__(self, n): __rmul__ = __mul__ def __mod__(self, args): return self.__class__(self.data % args) + def __rmod__(self, format): + return self.__class__(format % args) # the following methods are defined in alphabetical order: def capitalize(self): return self.__class__(self.data.capitalize()) + def casefold(self): + return self.__class__(self.data.casefold()) def center(self, width, *args): return self.__class__(self.data.center(width, *args)) def count(self, sub, start=0, end=_sys.maxsize): @@ -1108,6 +1185,8 @@ def find(self, sub, start=0, end=_sys.maxsize): return self.data.find(sub, start, end) def format(self, *args, **kwds): return self.data.format(*args, **kwds) + def format_map(self, mapping): + return self.data.format_map(mapping) def index(self, sub, start=0, end=_sys.maxsize): return self.data.index(sub, start, end) def isalpha(self): return self.data.isalpha() @@ -1117,6 +1196,7 @@ def isdigit(self): return self.data.isdigit() def isidentifier(self): return self.data.isidentifier() def islower(self): return self.data.islower() def isnumeric(self): return self.data.isnumeric() + def isprintable(self): return self.data.isprintable() def isspace(self): return self.data.isspace() def istitle(self): return self.data.istitle() def isupper(self): return self.data.isupper() @@ -1125,6 +1205,7 @@ def ljust(self, width, *args): return self.__class__(self.data.ljust(width, *args)) def lower(self): return self.__class__(self.data.lower()) def lstrip(self, chars=None): return self.__class__(self.data.lstrip(chars)) + maketrans = str.maketrans def partition(self, sep): return self.data.partition(sep) def replace(self, old, new, maxsplit=-1): diff --git a/lib/compileall.py b/lib/compileall.py index 8e1569c5..2d4c523b 100644 --- a/lib/compileall.py +++ b/lib/compileall.py @@ -1,4 +1,4 @@ -"""Module/script to byte-compile all .py files to .pyc (or .pyo) files. +"""Module/script to byte-compile all .py files to .pyc files. When called as a script with arguments, this compiles the directories given as arguments recursively; the -l option prevents it from @@ -16,32 +16,24 @@ import py_compile import struct -__all__ = ["compile_dir","compile_file","compile_path"] - -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. +try: + from concurrent.futures import ProcessPoolExecutor +except ImportError: + ProcessPoolExecutor = None +from functools import partial - Arguments (only dir is required): +__all__ = ["compile_dir","compile_file","compile_path"] - dir: the directory to byte-compile - maxlevels: maximum recursion level (default 10) - 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 - """ +def _walk_dir(dir, ddir=None, maxlevels=10, quiet=0): if not quiet: print('Listing {!r}...'.format(dir)) try: names = os.listdir(dir) except OSError: - print("Can't list {!r}".format(dir)) + if quiet < 2: + print("Can't list {!r}".format(dir)) names = [] names.sort() - success = 1 for name in names: if name == '__pycache__': continue @@ -51,17 +43,53 @@ def compile_dir(dir, maxlevels=10, ddir=None, force=False, rx=None, else: dfile = None if not os.path.isdir(fullname): - if not compile_file(fullname, ddir, force, rx, quiet, - legacy, optimize): - success = 0 + yield fullname 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): + yield from _walk_dir(fullname, ddir=dfile, + maxlevels=maxlevels - 1, quiet=quiet) + +def compile_dir(dir, maxlevels=10, ddir=None, force=False, rx=None, + quiet=0, legacy=False, optimize=-1, workers=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: 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: full output with False or 0, errors only with 1, + no output with 2 + legacy: if True, produce legacy pyc paths instead of PEP 3147 paths + optimize: optimization level or -1 for level of the interpreter + workers: maximum number of parallel workers + """ + if workers is not None and workers < 0: + raise ValueError('workers must be greater or equal to 0') + + files = _walk_dir(dir, quiet=quiet, maxlevels=maxlevels, + ddir=ddir) + success = 1 + if workers is not None and workers != 1 and ProcessPoolExecutor is not None: + workers = workers or None + with ProcessPoolExecutor(max_workers=workers) as executor: + results = executor.map(partial(compile_file, + ddir=ddir, force=force, + rx=rx, quiet=quiet, + legacy=legacy, + optimize=optimize), + files) + success = min(results, default=1) + else: + for file in files: + if not compile_file(file, ddir, force, rx, quiet, + legacy, optimize): success = 0 return success -def compile_file(fullname, ddir=None, force=False, rx=None, quiet=False, +def compile_file(fullname, ddir=None, force=False, rx=None, quiet=0, legacy=False, optimize=-1): """Byte-compile one file. @@ -71,7 +99,8 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=False, 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 + quiet: full output with False or 0, errors only with 1, + no output with 2 legacy: if True, produce legacy pyc paths instead of PEP 3147 paths optimize: optimization level or -1 for level of the interpreter """ @@ -87,11 +116,12 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=False, return success if os.path.isfile(fullname): if legacy: - cfile = fullname + ('c' if __debug__ else 'o') + cfile = fullname + 'c' else: if optimize >= 0: + opt = optimize if optimize >= 1 else '' cfile = importlib.util.cache_from_source( - fullname, debug_override=not optimize) + fullname, optimization=opt) else: cfile = importlib.util.cache_from_source(fullname) cache_dir = os.path.dirname(cfile) @@ -114,7 +144,10 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=False, ok = py_compile.compile(fullname, cfile, dfile, True, optimize=optimize) except py_compile.PyCompileError as err: - if quiet: + success = 0 + if quiet >= 2: + return success + elif quiet: print('*** Error compiling {!r}...'.format(fullname)) else: print('*** ', end='') @@ -123,20 +156,21 @@ def compile_file(fullname, ddir=None, force=False, rx=None, quiet=False, errors='backslashreplace') msg = msg.decode(sys.stdout.encoding) print(msg) - success = 0 except (SyntaxError, UnicodeError, OSError) as e: - if quiet: + success = 0 + if quiet >= 2: + return success + elif quiet: print('*** Error compiling {!r}...'.format(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=False, quiet=False, +def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=0, legacy=False, optimize=-1): """Byte-compile all module on sys.path. @@ -145,14 +179,15 @@ def compile_path(skip_curdir=1, maxlevels=0, force=False, quiet=False, skip_curdir: if true, skip current directory (default True) maxlevels: max recursion level (default 0) force: as for compile_dir() (default False) - quiet: as for compile_dir() (default False) + quiet: as for compile_dir() (default 0) legacy: as for compile_dir() (default False) optimize: as for compile_dir() (default -1) """ success = 1 for dir in sys.path: if (not dir or dir == os.curdir) and skip_curdir: - print('Skipping current directory') + if quiet < 2: + print('Skipping current directory') else: success = success and compile_dir(dir, maxlevels, None, force, quiet=quiet, @@ -169,10 +204,15 @@ def main(): parser.add_argument('-l', action='store_const', const=0, default=10, dest='maxlevels', help="don't recurse into subdirectories") + parser.add_argument('-r', type=int, dest='recursion', + help=('control the maximum recursion level. ' + 'if `-l` and `-r` options are specified, ' + 'then `-r` takes precedence.')) 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('-q', action='count', dest='quiet', default=0, + help='output only error messages; -qq will suppress ' + 'the error messages as well.') 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, @@ -192,14 +232,22 @@ def main(): 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() + parser.add_argument('-j', '--workers', default=1, + type=int, help='Run compileall concurrently') + args = parser.parse_args() compile_dests = args.compile_dest if args.rx: import re args.rx = re.compile(args.rx) + + if args.recursion is not None: + maxlevels = args.recursion + else: + maxlevels = args.maxlevels + # if flist is provided then load it if args.flist: try: @@ -207,9 +255,13 @@ def main(): for line in f: compile_dests.append(line.strip()) except OSError: - print("Error reading file list {}".format(args.flist)) + if args.quiet < 2: + print("Error reading file list {}".format(args.flist)) return False + if args.workers is not None: + args.workers = args.workers or None + success = True try: if compile_dests: @@ -219,16 +271,17 @@ def main(): args.quiet, args.legacy): success = False else: - if not compile_dir(dest, args.maxlevels, args.ddir, + if not compile_dir(dest, maxlevels, args.ddir, args.force, args.rx, args.quiet, - args.legacy): + args.legacy, workers=args.workers): success = False return success else: return compile_path(legacy=args.legacy, force=args.force, quiet=args.quiet) except KeyboardInterrupt: - print("\n[interrupted]") + if args.quiet < 2: + print("\n[interrupted]") return False return True diff --git a/lib/concurrent/futures/_base.py b/lib/concurrent/futures/_base.py index b2598339..295489c9 100644 --- a/lib/concurrent/futures/_base.py +++ b/lib/concurrent/futures/_base.py @@ -302,17 +302,20 @@ def __repr__(self): with self._condition: if self._state == FINISHED: if self._exception: - return '' % ( - hex(id(self)), + return '<%s at %#x state=%s raised %s>' % ( + self.__class__.__name__, + id(self), _STATE_TO_DESCRIPTION_MAP[self._state], self._exception.__class__.__name__) else: - return '' % ( - hex(id(self)), + return '<%s at %#x state=%s returned %s>' % ( + self.__class__.__name__, + id(self), _STATE_TO_DESCRIPTION_MAP[self._state], self._result.__class__.__name__) - return '' % ( - hex(id(self)), + return '<%s at %#x state=%s>' % ( + self.__class__.__name__, + id(self), _STATE_TO_DESCRIPTION_MAP[self._state]) def cancel(self): @@ -517,7 +520,7 @@ def submit(self, fn, *args, **kwargs): """ raise NotImplementedError() - def map(self, fn, *iterables, timeout=None): + def map(self, fn, *iterables, timeout=None, chunksize=1): """Returns an iterator equivalent to map(fn, iter). Args: @@ -525,6 +528,10 @@ def map(self, fn, *iterables, timeout=None): passed iterables. timeout: The maximum number of seconds to wait. If None, then there is no limit on the wait time. + chunksize: The size of the chunks the iterable will be broken into + before being passed to a child process. This argument is only + used by ProcessPoolExecutor; it is ignored by + ThreadPoolExecutor. Returns: An iterator equivalent to: map(func, *iterables) but the calls may diff --git a/lib/concurrent/futures/process.py b/lib/concurrent/futures/process.py index 07b5225d..8f1d7141 100644 --- a/lib/concurrent/futures/process.py +++ b/lib/concurrent/futures/process.py @@ -55,12 +55,15 @@ from multiprocessing.connection import wait import threading import weakref +from functools import partial +import itertools +import traceback # 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, +# - The workers would still be running during interpreter 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. @@ -88,6 +91,27 @@ def _python_exit(): # (Futures in the call queue cannot be cancelled). EXTRA_QUEUED_CALLS = 1 +# Hack to embed stringification of remote traceback in local traceback + +class _RemoteTraceback(Exception): + def __init__(self, tb): + self.tb = tb + def __str__(self): + return self.tb + +class _ExceptionWithTraceback: + def __init__(self, exc, tb): + tb = traceback.format_exception(type(exc), exc, tb) + tb = ''.join(tb) + self.exc = exc + self.tb = '\n"""\n%s"""' % tb + def __reduce__(self): + return _rebuild_exc, (self.exc, self.tb) + +def _rebuild_exc(exc, tb): + exc.__cause__ = _RemoteTraceback(tb) + return exc + class _WorkItem(object): def __init__(self, future, fn, args, kwargs): self.future = future @@ -108,6 +132,26 @@ def __init__(self, work_id, fn, args, kwargs): self.args = args self.kwargs = kwargs +def _get_chunks(*iterables, chunksize): + """ Iterates over zip()ed iterables in chunks. """ + it = zip(*iterables) + while True: + chunk = tuple(itertools.islice(it, chunksize)) + if not chunk: + return + yield chunk + +def _process_chunk(fn, chunk): + """ Processes a chunk of an iterable passed to map. + + Runs the function passed to map() on a chunk of the + iterable passed to map. + + This function is run in a separate process. + + """ + return [fn(*args) for args in chunk] + def _process_worker(call_queue, result_queue): """Evaluates calls from call_queue and places the results in result_queue. @@ -130,8 +174,8 @@ def _process_worker(call_queue, result_queue): 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)) + exc = _ExceptionWithTraceback(e, e.__traceback__) + result_queue.put(_ResultItem(call_item.work_id, exception=exc)) else: result_queue.put(_ResultItem(call_item.work_id, result=r)) @@ -334,6 +378,9 @@ def __init__(self, max_workers=None): if max_workers is None: self._max_workers = os.cpu_count() or 1 else: + if max_workers <= 0: + raise ValueError("max_workers must be greater than 0") + self._max_workers = max_workers # Make the call queue slightly larger than the number of processes to @@ -408,6 +455,35 @@ def submit(self, fn, *args, **kwargs): return f submit.__doc__ = _base.Executor.submit.__doc__ + def map(self, fn, *iterables, timeout=None, chunksize=1): + """Returns an iterator equivalent to map(fn, iter). + + Args: + fn: A callable that will 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. + chunksize: If greater than one, the iterables will be chopped into + chunks of size chunksize and submitted to the process pool. + If set to one, the items in the list will be sent one at a 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 chunksize < 1: + raise ValueError("chunksize must be >= 1.") + + results = super().map(partial(_process_chunk, fn), + _get_chunks(*iterables, chunksize=chunksize), + timeout=timeout) + return itertools.chain.from_iterable(results) + def shutdown(self, wait=True): with self._shutdown_lock: self._shutdown_thread = True diff --git a/lib/concurrent/futures/thread.py b/lib/concurrent/futures/thread.py index f9beb0f7..9c3aec9a 100644 --- a/lib/concurrent/futures/thread.py +++ b/lib/concurrent/futures/thread.py @@ -10,12 +10,13 @@ import queue import threading import weakref +import os # 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, +# - The workers would still be running during interpreter 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. @@ -80,13 +81,20 @@ def _worker(executor_reference, work_queue): _base.LOGGER.critical('Exception in worker', exc_info=True) class ThreadPoolExecutor(_base.Executor): - def __init__(self, max_workers): + def __init__(self, max_workers=None): """Initializes a new ThreadPoolExecutor instance. Args: max_workers: The maximum number of threads that can be used to execute the given calls. """ + if max_workers is None: + # Use this number because ThreadPoolExecutor is often + # used to overlap I/O instead of CPU work. + max_workers = (os.cpu_count() or 1) * 5 + if max_workers <= 0: + raise ValueError("max_workers must be greater than 0") + self._max_workers = max_workers self._work_queue = queue.Queue() self._threads = set() diff --git a/lib/configparser.py b/lib/configparser.py index dcb7ec4d..af5aca1f 100644 --- a/lib/configparser.py +++ b/lib/configparser.py @@ -17,7 +17,8 @@ __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): + empty_lines_in_values=True, default_section='DEFAULT', + interpolation=, converters=): 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. @@ -47,6 +48,25 @@ When `allow_no_value' is True (default: False), options without values are accepted; the value presented for these is None. + When `default_section' is given, the name of the special section is + named accordingly. By default it is called ``"DEFAULT"`` but this can + be customized to point to any other valid section name. Its current + value can be retrieved using the ``parser_instance.default_section`` + attribute and may be modified at runtime. + + When `interpolation` is given, it should be an Interpolation subclass + instance. It will be used as the handler for option value + pre-processing when using getters. RawConfigParser object s don't do + any sort of interpolation, whereas ConfigParser uses an instance of + BasicInterpolation. The library also provides a ``zc.buildbot`` + inspired ExtendedInterpolation implementation. + + When `converters` is given, it should be a dictionary where each key + represents the name of a type converter and each value is a callable + implementing the conversion from string to the desired datatype. Every + converter gets its corresponding get*() method on the parser object and + section proxies. + sections() Return all the configuration section names, sans DEFAULT. @@ -129,9 +149,11 @@ __all__ = ["NoSectionError", "DuplicateOptionError", "DuplicateSectionError", "NoOptionError", "InterpolationError", "InterpolationDepthError", - "InterpolationSyntaxError", "ParsingError", - "MissingSectionHeaderError", + "InterpolationMissingOptionError", "InterpolationSyntaxError", + "ParsingError", "MissingSectionHeaderError", "ConfigParser", "SafeConfigParser", "RawConfigParser", + "Interpolation", "BasicInterpolation", "ExtendedInterpolation", + "LegacyInterpolation", "SectionProxy", "ConverterMapping", "DEFAULTSECT", "MAX_INTERPOLATION_DEPTH"] DEFAULTSECT = "DEFAULT" @@ -408,7 +430,7 @@ def _interpolate_some(self, parser, option, accum, rest, section, map, v = map[var] except KeyError: raise InterpolationMissingOptionError( - option, section, rawval, var) + option, section, rawval, var) from None if "%" in v: self._interpolate_some(parser, option, accum, v, section, map, depth + 1) @@ -481,7 +503,7 @@ def _interpolate_some(self, parser, option, accum, rest, section, map, "More than one ':' found: %r" % (rest,)) except (KeyError, NoSectionError, NoOptionError): raise InterpolationMissingOptionError( - option, section, rawval, ":".join(path)) + option, section, rawval, ":".join(path)) from None if "$" in v: self._interpolate_some(parser, opt, accum, v, sect, dict(parser.items(sect, raw=True)), @@ -514,7 +536,7 @@ def before_get(self, parser, section, option, value, vars): value = value % vars except KeyError as e: raise InterpolationMissingOptionError( - option, section, rawval, e.args[0]) + option, section, rawval, e.args[0]) from None else: break if value and "%(" in value: @@ -579,11 +601,12 @@ def __init__(self, defaults=None, dict_type=_default_dict, comment_prefixes=('#', ';'), inline_comment_prefixes=None, strict=True, empty_lines_in_values=True, default_section=DEFAULTSECT, - interpolation=_UNSET): + interpolation=_UNSET, converters=_UNSET): self._dict = dict_type self._sections = self._dict() self._defaults = self._dict() + self._converters = ConverterMapping(self) self._proxies = self._dict() self._proxies[default_section] = SectionProxy(self, default_section) if defaults: @@ -611,6 +634,8 @@ def __init__(self, defaults=None, dict_type=_default_dict, self._interpolation = self._DEFAULT_INTERPOLATION if self._interpolation is None: self._interpolation = Interpolation() + if converters is not _UNSET: + self._converters.update(converters) def defaults(self): return self._defaults @@ -646,7 +671,7 @@ def options(self, section): try: opts = self._sections[section].copy() except KeyError: - raise NoSectionError(section) + raise NoSectionError(section) from None opts.update(self._defaults) return list(opts.keys()) @@ -774,36 +799,31 @@ def get(self, section, option, *, raw=False, vars=None, fallback=_UNSET): 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): + def _get_conv(self, section, option, conv, *, raw=False, vars=None, + fallback=_UNSET, **kwargs): try: - return self._get(section, int, option, raw=raw, vars=vars) + return self._get(section, conv, option, raw=raw, vars=vars, + **kwargs) except (NoSectionError, NoOptionError): if fallback is _UNSET: raise - else: - return fallback + return fallback + + # getint, getfloat and getboolean provided directly for backwards compat + def getint(self, section, option, *, raw=False, vars=None, + fallback=_UNSET, **kwargs): + return self._get_conv(section, option, int, raw=raw, vars=vars, + fallback=fallback, **kwargs) 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 + fallback=_UNSET, **kwargs): + return self._get_conv(section, option, float, raw=raw, vars=vars, + fallback=fallback, **kwargs) 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 + fallback=_UNSET, **kwargs): + return self._get_conv(section, option, self._convert_to_boolean, + raw=raw, vars=vars, fallback=fallback, **kwargs) def items(self, section=_UNSET, raw=False, vars=None): """Return a list of (name, value) tuples for each option in a section. @@ -875,7 +895,7 @@ def set(self, section, option, value=None): try: sectdict = self._sections[section] except KeyError: - raise NoSectionError(section) + raise NoSectionError(section) from None sectdict[self.optionxform(option)] = value def write(self, fp, space_around_delimiters=True): @@ -916,7 +936,7 @@ def remove_option(self, section, option): try: sectdict = self._sections[section] except KeyError: - raise NoSectionError(section) + raise NoSectionError(section) from None option = self.optionxform(option) existed = option in sectdict if existed: @@ -1082,10 +1102,10 @@ def _read(self, fp, fpname): # raised at the end of the file and will contain a # list of all bogus lines e = self._handle_error(e, fpname, lineno, line) + self._join_multiline_values() # 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 @@ -1153,6 +1173,10 @@ def _validate_value_types(self, *, section="", option="", value=""): if not isinstance(value, str): raise TypeError("option values must be strings") + @property + def converters(self): + return self._converters + class ConfigParser(RawConfigParser): """ConfigParser implementing interpolation.""" @@ -1193,6 +1217,10 @@ def __init__(self, parser, name): """Creates a view on a section of the specified `name` in `parser`.""" self._parser = parser self._name = name + for conv in parser.converters: + key = 'get' + conv + getter = functools.partial(self.get, _impl=getattr(parser, key)) + setattr(self, key, getter) def __repr__(self): return ''.format(self._name) @@ -1226,22 +1254,6 @@ def _options(self): else: 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) - - def getint(self, option, fallback=None, *, raw=False, vars=None): - return self._parser.getint(self._name, option, raw=raw, vars=vars, - fallback=fallback) - - def getfloat(self, option, fallback=None, *, raw=False, vars=None): - return self._parser.getfloat(self._name, option, raw=raw, vars=vars, - fallback=fallback) - - def getboolean(self, option, fallback=None, *, raw=False, vars=None): - return self._parser.getboolean(self._name, option, raw=raw, vars=vars, - fallback=fallback) - @property def parser(self): # The parser object of the proxy is read-only. @@ -1251,3 +1263,77 @@ def parser(self): def name(self): # The name of the section on a proxy is read-only. return self._name + + def get(self, option, fallback=None, *, raw=False, vars=None, + _impl=None, **kwargs): + """Get an option value. + + Unless `fallback` is provided, `None` will be returned if the option + is not found. + + """ + # If `_impl` is provided, it should be a getter method on the parser + # object that provides the desired type conversion. + if not _impl: + _impl = self._parser.get + return _impl(self._name, option, raw=raw, vars=vars, + fallback=fallback, **kwargs) + + +class ConverterMapping(MutableMapping): + """Enables reuse of get*() methods between the parser and section proxies. + + If a parser class implements a getter directly, the value for the given + key will be ``None``. The presence of the converter name here enables + section proxies to find and use the implementation on the parser class. + """ + + GETTERCRE = re.compile(r"^get(?P.+)$") + + def __init__(self, parser): + self._parser = parser + self._data = {} + for getter in dir(self._parser): + m = self.GETTERCRE.match(getter) + if not m or not callable(getattr(self._parser, getter)): + continue + self._data[m.group('name')] = None # See class docstring. + + def __getitem__(self, key): + return self._data[key] + + def __setitem__(self, key, value): + try: + k = 'get' + key + except TypeError: + raise ValueError('Incompatible key: {} (type: {})' + ''.format(key, type(key))) + if k == 'get': + raise ValueError('Incompatible key: cannot use "" as a name') + self._data[key] = value + func = functools.partial(self._parser._get_conv, conv=value) + func.converter = value + setattr(self._parser, k, func) + for proxy in self._parser.values(): + getter = functools.partial(proxy.get, _impl=func) + setattr(proxy, k, getter) + + def __delitem__(self, key): + try: + k = 'get' + (key or None) + except TypeError: + raise KeyError(key) + del self._data[key] + for inst in itertools.chain((self._parser,), self._parser.values()): + try: + delattr(inst, k) + except AttributeError: + # don't raise since the entry was present in _data, silently + # clean up + continue + + def __iter__(self): + return iter(self._data) + + def __len__(self): + return len(self._data) diff --git a/lib/contextlib.py b/lib/contextlib.py index 07b22611..5371a9f3 100644 --- a/lib/contextlib.py +++ b/lib/contextlib.py @@ -5,7 +5,7 @@ from functools import wraps __all__ = ["contextmanager", "closing", "ContextDecorator", "ExitStack", - "redirect_stdout", "suppress"] + "redirect_stdout", "redirect_stderr", "suppress"] class ContextDecorator(object): @@ -65,7 +65,7 @@ def __exit__(self, type, value, traceback): try: next(self.gen) except StopIteration: - return + return False else: raise RuntimeError("generator didn't stop") else: @@ -75,12 +75,21 @@ def __exit__(self, type, value, traceback): value = type() try: self.gen.throw(type, value, traceback) - raise RuntimeError("generator didn't stop after throw()") except StopIteration as exc: - # Suppress the exception *unless* it's the same exception that + # Suppress StopIteration *unless* it's the same exception that # was passed to throw(). This prevents a StopIteration - # raised inside the "with" statement from being suppressed + # raised inside the "with" statement from being suppressed. return exc is not value + except RuntimeError as exc: + # Don't re-raise the passed in exception. (issue27122) + if exc is value: + return False + # Likewise, avoid suppressing if a StopIteration exception + # was passed to throw() and later wrapped into a RuntimeError + # (see PEP 479). + if type is StopIteration and exc.__cause__ is value: + return False + raise except: # only re-raise if it's *not* the exception that was # passed to throw(), because __exit__() must not raise @@ -89,8 +98,10 @@ def __exit__(self, type, value, traceback): # fixes the impedance mismatch between the throw() protocol # and the __exit__() protocol. # - if sys.exc_info()[1] is not value: - raise + if sys.exc_info()[1] is value: + return False + raise + raise RuntimeError("generator didn't stop after throw()") def contextmanager(func): @@ -151,8 +162,27 @@ def __enter__(self): def __exit__(self, *exc_info): self.thing.close() -class redirect_stdout: - """Context manager for temporarily redirecting stdout to another file + +class _RedirectStream: + + _stream = None + + def __init__(self, new_target): + self._new_target = new_target + # We use a list of old targets to make this CM re-entrant + self._old_targets = [] + + def __enter__(self): + self._old_targets.append(getattr(sys, self._stream)) + setattr(sys, self._stream, self._new_target) + return self._new_target + + def __exit__(self, exctype, excinst, exctb): + setattr(sys, self._stream, self._old_targets.pop()) + + +class redirect_stdout(_RedirectStream): + """Context manager for temporarily redirecting stdout to another file. # How to send help() to stderr with redirect_stdout(sys.stderr): @@ -164,18 +194,13 @@ class redirect_stdout: help(pow) """ - def __init__(self, new_target): - self._new_target = new_target - # We use a list of old targets to make this CM re-entrant - self._old_targets = [] + _stream = "stdout" - def __enter__(self): - self._old_targets.append(sys.stdout) - sys.stdout = self._new_target - return self._new_target - def __exit__(self, exctype, excinst, exctb): - sys.stdout = self._old_targets.pop() +class redirect_stderr(_RedirectStream): + """Context manager for temporarily redirecting stderr to another file.""" + + _stream = "stderr" class suppress: diff --git a/lib/copy.py b/lib/copy.py index f0fb4437..972b94ab 100644 --- a/lib/copy.py +++ b/lib/copy.py @@ -94,7 +94,7 @@ def copy(x): else: reductor = getattr(x, "__reduce_ex__", None) if reductor: - rv = reductor(2) + rv = reductor(4) else: reductor = getattr(x, "__reduce__", None) if reductor: @@ -171,7 +171,7 @@ def deepcopy(x, memo=None, _nil=[]): else: reductor = getattr(x, "__reduce_ex__", None) if reductor: - rv = reductor(2) + rv = reductor(4) else: reductor = getattr(x, "__reduce__", None) if reductor: @@ -207,7 +207,6 @@ def _deepcopy_atomic(x, memo): except AttributeError: pass d[type] = _deepcopy_atomic -d[range] = _deepcopy_atomic d[types.BuiltinFunctionType] = _deepcopy_atomic d[types.FunctionType] = _deepcopy_atomic d[weakref.ref] = _deepcopy_atomic @@ -221,17 +220,15 @@ def _deepcopy_list(x, memo): d[list] = _deepcopy_list def _deepcopy_tuple(x, memo): - y = [] - for a in x: - y.append(deepcopy(a, memo)) + y = [deepcopy(a, memo) for a in x] # We're not going to put the tuple in the memo, but it's still important we # check for it, in case the tuple contains recursive mutable structures. try: return memo[id(x)] except KeyError: pass - for i in range(len(x)): - if x[i] is not y[i]: + for k, j in zip(x, y): + if k is not j: y = tuple(y) break else: diff --git a/lib/csv.py b/lib/csv.py index a56eed88..ca40e5e0 100644 --- a/lib/csv.py +++ b/lib/csv.py @@ -147,16 +147,13 @@ def _dict_to_list(self, rowdict): if wrong_fields: raise ValueError("dict contains fields not in fieldnames: " + ", ".join([repr(x) for x in wrong_fields])) - return [rowdict.get(key, self.restval) for key in self.fieldnames] + return (rowdict.get(key, self.restval) for key in self.fieldnames) def writerow(self, rowdict): return self.writer.writerow(self._dict_to_list(rowdict)) def writerows(self, rowdicts): - rows = [] - for rowdict in rowdicts: - rows.append(self._dict_to_list(rowdict)) - return self.writer.writerows(rows) + return self.writer.writerows(map(self._dict_to_list, rowdicts)) # Guard Sniffer's type checking against builds that exclude complex() try: @@ -231,20 +228,21 @@ def _guess_quote_and_delimiter(self, data, delimiters): quotes = {} delims = {} spaces = 0 + groupindex = regexp.groupindex for m in matches: - n = regexp.groupindex['quote'] - 1 + n = groupindex['quote'] - 1 key = m[n] if key: quotes[key] = quotes.get(key, 0) + 1 try: - n = regexp.groupindex['delim'] - 1 + n = groupindex['delim'] - 1 key = m[n] except KeyError: continue if key and (delimiters is None or key in delimiters): delims[key] = delims.get(key, 0) + 1 try: - n = regexp.groupindex['space'] - 1 + n = groupindex['space'] - 1 except KeyError: continue if m[n]: diff --git a/lib/ctypes/__init__.py b/lib/ctypes/__init__.py index 5c803ffc..1469b8ba 100644 --- a/lib/ctypes/__init__.py +++ b/lib/ctypes/__init__.py @@ -47,7 +47,7 @@ def create_string_buffer(init, size=None): """create_string_buffer(aBytes) -> character array create_string_buffer(anInteger) -> character array - create_string_buffer(aString, anInteger) -> character array + create_string_buffer(aBytes, anInteger) -> character array """ if isinstance(init, bytes): if size is None: @@ -237,14 +237,8 @@ class c_char(_SimpleCData): class c_char_p(_SimpleCData): _type_ = "z" - if _os.name == "nt": - def __repr__(self): - if not windll.kernel32.IsBadStringPtrA(self, -1): - return "%s(%r)" % (self.__class__.__name__, self.value) - return "%s(%s)" % (self.__class__.__name__, cast(self, c_void_p).value) - else: - def __repr__(self): - return "%s(%s)" % (self.__class__.__name__, cast(self, c_void_p).value) + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, c_void_p.from_buffer(self).value) _check_size(c_char_p, "P") class c_void_p(_SimpleCData): @@ -259,6 +253,8 @@ class c_bool(_SimpleCData): class c_wchar_p(_SimpleCData): _type_ = "Z" + def __repr__(self): + return "%s(%s)" % (self.__class__.__name__, c_void_p.from_buffer(self).value) class c_wchar(_SimpleCData): _type_ = "u" @@ -331,6 +327,10 @@ class CDLL(object): """ _func_flags_ = _FUNCFLAG_CDECL _func_restype_ = c_int + # default values for repr + _name = '' + _handle = 0 + _FuncPtr = None def __init__(self, name, mode=DEFAULT_MODE, handle=None, use_errno=False, @@ -353,7 +353,7 @@ class _FuncPtr(_CFuncPtr): self._handle = handle def __repr__(self): - return "<%s '%s', handle %x at %x>" % \ + return "<%s '%s', handle %x at %#x>" % \ (self.__class__.__name__, self._name, (self._handle & (_sys.maxsize*2 + 1)), id(self) & (_sys.maxsize*2 + 1)) @@ -372,8 +372,8 @@ def __getitem__(self, name_or_ordinal): return func class PyDLL(CDLL): - """This class represents the Python library itself. It allows to - access Python API functions. The GIL is not released, and + """This class represents the Python library itself. It allows + accessing Python API functions. The GIL is not released, and Python exceptions are handled correctly. """ _func_flags_ = _FUNCFLAG_CDECL | _FUNCFLAG_PYTHONAPI diff --git a/lib/ctypes/_endian.py b/lib/ctypes/_endian.py index dae65fc2..37444bd6 100644 --- a/lib/ctypes/_endian.py +++ b/lib/ctypes/_endian.py @@ -45,6 +45,7 @@ def __setattr__(self, attrname, value): class BigEndianStructure(Structure, metaclass=_swapped_meta): """Structure with big endian byte order""" + __slots__ = () _swappedbytes_ = None elif sys.byteorder == "big": @@ -53,6 +54,7 @@ class BigEndianStructure(Structure, metaclass=_swapped_meta): BigEndianStructure = Structure class LittleEndianStructure(Structure, metaclass=_swapped_meta): """Structure with little endian byte order""" + __slots__ = () _swappedbytes_ = None else: diff --git a/lib/ctypes/macholib/README.ctypes b/lib/ctypes/macholib/README.ctypes index 4e10cbe4..2866e9f3 100644 --- a/lib/ctypes/macholib/README.ctypes +++ b/lib/ctypes/macholib/README.ctypes @@ -1,4 +1,4 @@ -Files in this directory from from Bob Ippolito's py2app. +Files in this directory come from Bob Ippolito's py2app. License: Any components of the py2app suite may be distributed under the MIT or PSF open source licenses. diff --git a/lib/ctypes/macholib/dyld.py b/lib/ctypes/macholib/dyld.py index dc7052e7..c158e672 100644 --- a/lib/ctypes/macholib/dyld.py +++ b/lib/ctypes/macholib/dyld.py @@ -135,10 +135,11 @@ def framework_find(fn, executable_path=None, env=None): Python.framework Python.framework/Versions/Current """ + error = None try: return dyld_find(fn, executable_path=executable_path, env=env) except ValueError as e: - pass + error = e fmwk_index = fn.rfind('.framework') if fmwk_index == -1: fmwk_index = len(fn) @@ -147,7 +148,7 @@ def framework_find(fn, executable_path=None, env=None): try: return dyld_find(fn, executable_path=executable_path, env=env) except ValueError: - raise e + raise error def test_dyld_find(): env = {} diff --git a/lib/ctypes/test/test_arrays.py b/lib/ctypes/test/test_arrays.py index 8ca77e0f..4ed566b4 100644 --- a/lib/ctypes/test/test_arrays.py +++ b/lib/ctypes/test/test_arrays.py @@ -24,20 +24,24 @@ def test_simple(self): self.assertEqual(len(ia), alen) # slot values ok? - values = [ia[i] for i in range(len(init))] + values = [ia[i] for i in range(alen)] self.assertEqual(values, init) + # out-of-bounds accesses should be caught + with self.assertRaises(IndexError): ia[alen] + with self.assertRaises(IndexError): ia[-alen-1] + # change the items from operator import setitem new_values = list(range(42, 42+alen)) [setitem(ia, n, new_values[n]) for n in range(alen)] - values = [ia[i] for i in range(len(init))] + values = [ia[i] for i in range(alen)] self.assertEqual(values, new_values) # are the items initialized to 0? ia = int_array() - values = [ia[i] for i in range(len(init))] - self.assertEqual(values, [0] * len(init)) + values = [ia[i] for i in range(alen)] + self.assertEqual(values, [0] * alen) # Too many initializers should be caught self.assertRaises(IndexError, int_array, *range(alen*2)) diff --git a/lib/ctypes/test/test_as_parameter.py b/lib/ctypes/test/test_as_parameter.py index 948b4632..2a3484be 100644 --- a/lib/ctypes/test/test_as_parameter.py +++ b/lib/ctypes/test/test_as_parameter.py @@ -194,7 +194,7 @@ class A(object): a = A() a._as_parameter_ = a - with self.assertRaises(RuntimeError): + with self.assertRaises(RecursionError): c_int.from_param(a) diff --git a/lib/ctypes/test/test_byteswap.py b/lib/ctypes/test/test_byteswap.py index 427bb8bc..01c97e83 100644 --- a/lib/ctypes/test/test_byteswap.py +++ b/lib/ctypes/test/test_byteswap.py @@ -22,6 +22,26 @@ def test_X(self): setattr(bits, "i%s" % i, 1) dump(bits) + def test_slots(self): + class BigPoint(BigEndianStructure): + __slots__ = () + _fields_ = [("x", c_int), ("y", c_int)] + + class LowPoint(LittleEndianStructure): + __slots__ = () + _fields_ = [("x", c_int), ("y", c_int)] + + big = BigPoint() + little = LowPoint() + big.x = 4 + big.y = 2 + little.x = 2 + little.y = 4 + with self.assertRaises(AttributeError): + big.z = 42 + with self.assertRaises(AttributeError): + little.z = 24 + def test_endian_short(self): if sys.byteorder == "little": self.assertIs(c_short.__ctype_le__, c_short) diff --git a/lib/ctypes/test/test_callbacks.py b/lib/ctypes/test/test_callbacks.py index 8eac58f0..f622093d 100644 --- a/lib/ctypes/test/test_callbacks.py +++ b/lib/ctypes/test/test_callbacks.py @@ -244,6 +244,7 @@ def callback(a, b, c, d, e): def test_callback_large_struct(self): class Check: pass + # This should mirror the structure in Modules/_ctypes/_ctypes_test.c class X(Structure): _fields_ = [ ('first', c_ulong), @@ -255,6 +256,11 @@ def callback(check, s): check.first = s.first check.second = s.second check.third = s.third + # See issue #29565. + # The structure should be passed by value, so + # any changes to it should not be reflected in + # the value passed + s.first = s.second = s.third = 0x0badf00d check = Check() s = X() @@ -275,6 +281,11 @@ def callback(check, s): self.assertEqual(check.first, 0xdeadbeef) self.assertEqual(check.second, 0xcafebabe) self.assertEqual(check.third, 0x0bad1dea) + # See issue #29565. + # Ensure that the original struct is unchanged. + self.assertEqual(s.first, check.first) + self.assertEqual(s.second, check.second) + self.assertEqual(s.third, check.third) ################################################################ diff --git a/lib/ctypes/test/test_find.py b/lib/ctypes/test/test_find.py index e6bc19d7..94b0b890 100644 --- a/lib/ctypes/test/test_find.py +++ b/lib/ctypes/test/test_find.py @@ -1,5 +1,5 @@ import unittest -import os +import os.path import sys import test.support from ctypes import * @@ -64,28 +64,10 @@ def test_gle(self): self.skipTest('lib_gle not available') self.gle.gleGetJoinStyle -# On platforms where the default shared library suffix is '.so', -# at least some libraries can be loaded as attributes of the cdll -# object, since ctypes now tries loading the lib again -# with '.so' appended of the first try fails. -# -# Won't work for libc, unfortunately. OTOH, it isn't -# needed for libc since this is already mapped into the current -# process (?) -# -# On MAC OSX, it won't work either, because dlopen() needs a full path, -# and the default suffix is either none or '.dylib'. -@unittest.skip('test disabled') -@unittest.skipUnless(os.name=="posix" and sys.platform != "darwin", - 'test not suitable for this platform') -class LoadLibs(unittest.TestCase): - def test_libm(self): - import math - libm = cdll.libm - sqrt = libm.sqrt - sqrt.argtypes = (c_double,) - sqrt.restype = c_double - self.assertEqual(sqrt(2), math.sqrt(2)) + def test_shell_injection(self): + result = find_library('; echo Hello shell > ' + test.support.TESTFN) + self.assertFalse(os.path.lexists(test.support.TESTFN)) + self.assertIsNone(result) if __name__ == "__main__": unittest.main() diff --git a/lib/ctypes/test/test_frombuffer.py b/lib/ctypes/test/test_frombuffer.py index 86954fe6..7ab38f1b 100644 --- a/lib/ctypes/test/test_frombuffer.py +++ b/lib/ctypes/test/test_frombuffer.py @@ -44,7 +44,7 @@ def test_from_buffer(self): (c_char * 16).from_buffer(memoryview(b"a" * 16)) with self.assertRaisesRegex(TypeError, "not C contiguous"): (c_char * 16).from_buffer(memoryview(bytearray(b"a" * 16))[::-1]) - msg = "does not have the buffer interface" + msg = "bytes-like object is required" with self.assertRaisesRegex(TypeError, msg): (c_char * 16).from_buffer("a" * 16) @@ -120,5 +120,13 @@ def test_from_buffer_copy_with_offset(self): with self.assertRaises(ValueError): (c_int * 1).from_buffer_copy(a, 16 * sizeof(c_int)) + def test_abstract(self): + self.assertRaises(TypeError, Array.from_buffer, bytearray(10)) + self.assertRaises(TypeError, Structure.from_buffer, bytearray(10)) + self.assertRaises(TypeError, Union.from_buffer, bytearray(10)) + self.assertRaises(TypeError, Array.from_buffer_copy, b"123") + self.assertRaises(TypeError, Structure.from_buffer_copy, b"123") + self.assertRaises(TypeError, Union.from_buffer_copy, b"123") + if __name__ == '__main__': unittest.main() diff --git a/lib/ctypes/test/test_loading.py b/lib/ctypes/test/test_loading.py index 4fb89642..3de457f4 100644 --- a/lib/ctypes/test/test_loading.py +++ b/lib/ctypes/test/test_loading.py @@ -52,7 +52,9 @@ def test_find(self): @unittest.skipUnless(os.name in ("nt", "ce"), 'test specific to Windows (NT/CE)') def test_load_library(self): - self.assertIsNotNone(libc_name) + # CRT is no longer directly loadable. See issue23606 for the + # discussion about alternative approaches. + #self.assertIsNotNone(libc_name) if test.support.verbose: print(find_library("kernel32")) print(find_library("user32")) @@ -62,6 +64,8 @@ def test_load_library(self): windll["kernel32"].GetModuleHandleW windll.LoadLibrary("kernel32").GetModuleHandleW WinDLL("kernel32").GetModuleHandleW + # embedded null character + self.assertRaises(ValueError, windll.LoadLibrary, "kernel32\0") elif os.name == "ce": windll.coredll.GetModuleHandleW windll["coredll"].GetModuleHandleW diff --git a/lib/ctypes/test/test_numbers.py b/lib/ctypes/test/test_numbers.py index 2afca266..ba4f5633 100644 --- a/lib/ctypes/test/test_numbers.py +++ b/lib/ctypes/test/test_numbers.py @@ -76,7 +76,7 @@ def test_bool_values(self): self.assertEqual(t(v).value, truth(v)) def test_typeerror(self): - # Only numbers are allowed in the contructor, + # Only numbers are allowed in the constructor, # otherwise TypeError is raised for t in signed_types + unsigned_types + float_types: self.assertRaises(TypeError, t, "") diff --git a/lib/ctypes/test/test_pointers.py b/lib/ctypes/test/test_pointers.py index 6eea58fb..751f85fd 100644 --- a/lib/ctypes/test/test_pointers.py +++ b/lib/ctypes/test/test_pointers.py @@ -22,7 +22,10 @@ class A(POINTER(c_ulong)): def test_pass_pointers(self): dll = CDLL(_ctypes_test.__file__) func = dll._testfunc_p_p - func.restype = c_long + if sizeof(c_longlong) == sizeof(c_void_p): + func.restype = c_longlong + else: + func.restype = c_long i = c_int(12345678) ## func.argtypes = (POINTER(c_int),) @@ -53,9 +56,13 @@ def test_change_pointers(self): # C code: # int x = 12321; # res = &x - res.contents = c_int(12321) + x = c_int(12321) + res.contents = x self.assertEqual(i.value, 54345) + x.value = -99 + self.assertEqual(res.contents.value, -99) + def test_callbacks_with_pointers(self): # a function type receiving a pointer PROTOTYPE = CFUNCTYPE(c_int, POINTER(c_int)) @@ -128,9 +135,10 @@ class Table(Structure): def test_basic(self): p = pointer(c_int(42)) - # Although a pointer can be indexed, it ha no length + # Although a pointer can be indexed, it has no length self.assertRaises(TypeError, len, p) self.assertEqual(p[0], 42) + self.assertEqual(p[0:1], [42]) self.assertEqual(p.contents.value, 42) def test_charpp(self): diff --git a/lib/ctypes/test/test_prototypes.py b/lib/ctypes/test/test_prototypes.py index 818c1110..cd0c649d 100644 --- a/lib/ctypes/test/test_prototypes.py +++ b/lib/ctypes/test/test_prototypes.py @@ -69,7 +69,10 @@ def test_paramflags(self): def test_int_pointer_arg(self): func = testdll._testfunc_p_p - func.restype = c_long + if sizeof(c_longlong) == sizeof(c_void_p): + func.restype = c_longlong + else: + func.restype = c_long self.assertEqual(0, func(0)) ci = c_int(0) diff --git a/lib/ctypes/test/test_slicing.py b/lib/ctypes/test/test_slicing.py index 240dc0cd..a3932f17 100644 --- a/lib/ctypes/test/test_slicing.py +++ b/lib/ctypes/test/test_slicing.py @@ -134,7 +134,7 @@ def test_wchar_ptr(self): dll.my_wcsdup.restype = POINTER(c_wchar) dll.my_wcsdup.argtypes = POINTER(c_wchar), dll.my_free.restype = None - res = dll.my_wcsdup(s) + res = dll.my_wcsdup(s[:-1]) self.assertEqual(res[:len(s)], s) self.assertEqual(res[:len(s):], s) self.assertEqual(res[len(s)-1:-1:-1], s[::-1]) @@ -153,7 +153,7 @@ def test_wchar_ptr(self): dll.my_wcsdup.restype = POINTER(c_long) else: self.skipTest('Pointers to c_wchar are not supported') - res = dll.my_wcsdup(s) + res = dll.my_wcsdup(s[:-1]) tmpl = list(range(ord("a"), ord("z")+1)) self.assertEqual(res[:len(s)-1], tmpl) self.assertEqual(res[:len(s)-1:], tmpl) diff --git a/lib/ctypes/test/test_structures.py b/lib/ctypes/test/test_structures.py index 84d456c3..2e9fc7c4 100644 --- a/lib/ctypes/test/test_structures.py +++ b/lib/ctypes/test/test_structures.py @@ -2,7 +2,8 @@ from ctypes import * from ctypes.test import need_symbol from struct import calcsize -import _testcapi +import _ctypes_test +import test.support class SubclassesTest(unittest.TestCase): def test_subclass(self): @@ -106,7 +107,7 @@ class XX(Structure): self.assertEqual(alignment(XX), alignment(X)) self.assertEqual(sizeof(XX), calcsize("3s 3s 0s")) - def test_emtpy(self): + def test_empty(self): # I had problems with these # # Although these are pathological cases: Empty Structures! @@ -201,7 +202,10 @@ class X(Structure): "_pack_": -1} self.assertRaises(ValueError, type(Structure), "X", (Structure,), d) + @test.support.cpython_only + def test_packed_c_limits(self): # Issue 15989 + import _testcapi d = {"_fields_": [("a", c_byte)], "_pack_": _testcapi.INT_MAX + 1} self.assertRaises(ValueError, type(Structure), "X", (Structure,), d) @@ -227,10 +231,10 @@ class Person(Structure): def test_conflicting_initializers(self): class POINT(Structure): - _fields_ = [("x", c_int), ("y", c_int)] + _fields_ = [("phi", c_float), ("rho", c_float)] # conflicting positional and keyword args - self.assertRaises(TypeError, POINT, 2, 3, x=4) - self.assertRaises(TypeError, POINT, 2, 3, y=4) + self.assertRaisesRegex(TypeError, "phi", POINT, 2, 3, phi=4) + self.assertRaisesRegex(TypeError, "rho", POINT, 2, 3, rho=4) # too many initializers self.assertRaises(TypeError, POINT, 2, 3, 4) @@ -394,6 +398,28 @@ class Z(Y): (1, 0, 0, 0, 0, 0)) self.assertRaises(TypeError, lambda: Z(1, 2, 3, 4, 5, 6, 7)) + def test_pass_by_value(self): + # This should mirror the structure in Modules/_ctypes/_ctypes_test.c + class X(Structure): + _fields_ = [ + ('first', c_ulong), + ('second', c_ulong), + ('third', c_ulong), + ] + + s = X() + s.first = 0xdeadbeef + s.second = 0xcafebabe + s.third = 0x0bad1dea + dll = CDLL(_ctypes_test.__file__) + func = dll._testfunc_large_struct_update_value + func.argtypes = (X,) + func.restype = None + func(s) + self.assertEqual(s.first, 0xdeadbeef) + self.assertEqual(s.second, 0xcafebabe) + self.assertEqual(s.third, 0x0bad1dea) + class PointerMemberTestCase(unittest.TestCase): def test(self): diff --git a/lib/ctypes/test/test_values.py b/lib/ctypes/test/test_values.py index 6850cf00..5a3a47f9 100644 --- a/lib/ctypes/test/test_values.py +++ b/lib/ctypes/test/test_values.py @@ -58,8 +58,13 @@ class struct_frozen(Structure): items = [] # _frozen_importlib changes size whenever importlib._bootstrap # changes, so it gets a special case. We should make sure it's - # found, but don't worry about its size too much. - _fzn_implib_seen = False + # found, but don't worry about its size too much. The same + # applies to _frozen_importlib_external. + bootstrap_seen = [] + bootstrap_expected = [ + b'_frozen_importlib', + b'_frozen_importlib_external', + ] for entry in ft: # This is dangerous. We *can* iterate over a pointer, but # the loop will not terminate (maybe with an access @@ -67,21 +72,22 @@ class struct_frozen(Structure): if entry.name is None: break - if entry.name == b'_frozen_importlib': - _fzn_implib_seen = True + if entry.name in bootstrap_expected: + bootstrap_seen.append(entry.name) self.assertTrue(entry.size, - "_frozen_importlib was reported as having no size") + "{!r} was reported as having no size".format(entry.name)) continue - items.append((entry.name, entry.size)) + items.append((entry.name.decode("ascii"), entry.size)) - expected = [(b"__hello__", 161), - (b"__phello__", -161), - (b"__phello__.spam", 161), + expected = [("__hello__", 161), + ("__phello__", -161), + ("__phello__.spam", 161), ] - self.assertEqual(items, expected) + self.assertEqual(items, expected, "PyImport_FrozenModules example " + "in Doc/library/ctypes.rst may be out of date") - self.assertTrue(_fzn_implib_seen, - "_frozen_importlib wasn't found in PyImport_FrozenModules") + self.assertEqual(sorted(bootstrap_seen), bootstrap_expected, + "frozen bootstrap modules did not match PyImport_FrozenModules") from ctypes import _pointer_type_cache del _pointer_type_cache[struct_frozen] diff --git a/lib/ctypes/util.py b/lib/ctypes/util.py index 595113bf..7684eab8 100644 --- a/lib/ctypes/util.py +++ b/lib/ctypes/util.py @@ -1,6 +1,7 @@ -import sys, os -import contextlib +import os +import shutil import subprocess +import sys # find_library(name) returns the pathname of a library, or None. if os.name == "nt": @@ -19,6 +20,8 @@ def _get_build_version(): i = i + len(prefix) s, rest = sys.version[i:].split(" ", 1) majorVersion = int(s[:-2]) - 6 + if majorVersion >= 13: + majorVersion += 1 minorVersion = int(s[2:3]) / 10.0 # I don't think paths are affected by minor version in version 6 if majorVersion == 6: @@ -36,8 +39,12 @@ def find_msvcrt(): return None if version <= 6: clibname = 'msvcrt' - else: + elif version <= 13: clibname = 'msvcr%d' % (version * 10) + else: + # CRT is no longer directly loadable. See issue23606 for the + # discussion about alternative approaches. + return None # If python was built with in debug mode import importlib.machinery @@ -88,28 +95,46 @@ def find_library(name): import re, tempfile def _findLib_gcc(name): - expr = r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name) - fdout, ccout = tempfile.mkstemp() - os.close(fdout) - cmd = 'if type gcc >/dev/null 2>&1; then CC=gcc; elif type cc >/dev/null 2>&1; then CC=cc;else exit 10; fi;' \ - 'LANG=C LC_ALL=C $CC -Wl,-t -o ' + ccout + ' 2>&1 -l' + name + # Run GCC's linker with the -t (aka --trace) option and examine the + # library name it prints out. The GCC command will fail because we + # haven't supplied a proper program with main(), but that does not + # matter. + expr = os.fsencode(r'[^\(\)\s]*lib%s\.[^\(\)\s]*' % re.escape(name)) + + c_compiler = shutil.which('gcc') + if not c_compiler: + c_compiler = shutil.which('cc') + if not c_compiler: + # No C compiler available, give up + return None + + temp = tempfile.NamedTemporaryFile() try: - f = os.popen(cmd) + args = [c_compiler, '-Wl,-t', '-o', temp.name, '-l' + name] + + env = dict(os.environ) + env['LC_ALL'] = 'C' + env['LANG'] = 'C' try: - trace = f.read() - finally: - rv = f.close() + proc = subprocess.Popen(args, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + env=env) + except OSError: # E.g. bad executable + return None + with proc: + trace = proc.stdout.read() finally: try: - os.unlink(ccout) + temp.close() except FileNotFoundError: + # Raised if the file was already removed, which is the normal + # behaviour of GCC if linking fails pass - if rv == 10: - raise OSError('gcc or cc command not found') res = re.search(expr, trace) if not res: return None - return res.group(0) + return os.fsdecode(res.group(0)) if sys.platform == "sunos5": @@ -117,55 +142,75 @@ def _findLib_gcc(name): def _get_soname(f): if not f: return None - cmd = "/usr/ccs/bin/dump -Lpv 2>/dev/null " + f - with contextlib.closing(os.popen(cmd)) as f: - data = f.read() - res = re.search(r'\[.*\]\sSONAME\s+([^\s]+)', data) + + try: + proc = subprocess.Popen(("/usr/ccs/bin/dump", "-Lpv", f), + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL) + except OSError: # E.g. command not found + return None + with proc: + data = proc.stdout.read() + res = re.search(br'\[.*\]\sSONAME\s+([^\s]+)', data) if not res: return None - return res.group(1) + return os.fsdecode(res.group(1)) else: def _get_soname(f): # assuming GNU binutils / ELF if not f: return None - cmd = 'if ! type objdump >/dev/null 2>&1; then exit 10; fi;' \ - "objdump -p -j .dynamic 2>/dev/null " + f - f = os.popen(cmd) + objdump = shutil.which('objdump') + if not objdump: + # objdump is not available, give up + return None + try: - dump = f.read() - finally: - rv = f.close() - if rv == 10: - raise OSError('objdump command not found') - res = re.search(r'\sSONAME\s+([^\s]+)', dump) + proc = subprocess.Popen((objdump, '-p', '-j', '.dynamic', f), + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL) + except OSError: # E.g. bad executable + return None + with proc: + dump = proc.stdout.read() + res = re.search(br'\sSONAME\s+([^\s]+)', dump) if not res: return None - return res.group(1) + return os.fsdecode(res.group(1)) if sys.platform.startswith(("freebsd", "openbsd", "dragonfly")): def _num_version(libname): # "libxyz.so.MAJOR.MINOR" => [ MAJOR, MINOR ] - parts = libname.split(".") + parts = libname.split(b".") nums = [] try: while parts: nums.insert(0, int(parts.pop())) except ValueError: pass - return nums or [ sys.maxsize ] + return nums or [sys.maxsize] def find_library(name): ename = re.escape(name) expr = r':-l%s\.\S+ => \S*/(lib%s\.\S+)' % (ename, ename) - with contextlib.closing(os.popen('/sbin/ldconfig -r 2>/dev/null')) as f: - data = f.read() + expr = os.fsencode(expr) + + try: + proc = subprocess.Popen(('/sbin/ldconfig', '-r'), + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL) + except OSError: # E.g. command not found + data = b'' + else: + with proc: + data = proc.stdout.read() + res = re.findall(expr, data) if not res: return _get_soname(_findLib_gcc(name)) res.sort(key=_num_version) - return res[-1] + return os.fsdecode(res[-1]) elif sys.platform == "sunos5": @@ -173,16 +218,27 @@ def _findLib_crle(name, is64): if not os.path.exists('/usr/bin/crle'): return None + env = dict(os.environ) + env['LC_ALL'] = 'C' + if is64: - cmd = 'env LC_ALL=C /usr/bin/crle -64 2>/dev/null' + args = ('/usr/bin/crle', '-64') else: - cmd = 'env LC_ALL=C /usr/bin/crle 2>/dev/null' + args = ('/usr/bin/crle',) - with contextlib.closing(os.popen(cmd)) as f: - for line in f.readlines(): + paths = None + try: + proc = subprocess.Popen(args, + stdout=subprocess.PIPE, + stderr=subprocess.DEVNULL, + env=env) + except OSError: # E.g. bad executable + return None + with proc: + for line in proc.stdout: line = line.strip() - if line.startswith('Default Library Path (ELF):'): - paths = line.split()[4] + if line.startswith(b'Default Library Path (ELF):'): + paths = os.fsdecode(line).split()[4] if not paths: return None diff --git a/lib/curses/ascii.py b/lib/curses/ascii.py index 800fd8b4..5b243be6 100644 --- a/lib/curses/ascii.py +++ b/lib/curses/ascii.py @@ -53,19 +53,19 @@ def _ctoi(c): def isalnum(c): return isalpha(c) or isdigit(c) def isalpha(c): return isupper(c) or islower(c) -def isascii(c): return _ctoi(c) <= 127 # ? -def isblank(c): return _ctoi(c) in (8,32) -def iscntrl(c): return _ctoi(c) <= 31 -def isdigit(c): return _ctoi(c) >= 48 and _ctoi(c) <= 57 -def isgraph(c): return _ctoi(c) >= 33 and _ctoi(c) <= 126 -def islower(c): return _ctoi(c) >= 97 and _ctoi(c) <= 122 -def isprint(c): return _ctoi(c) >= 32 and _ctoi(c) <= 126 -def ispunct(c): return _ctoi(c) != 32 and not isalnum(c) +def isascii(c): return 0 <= _ctoi(c) <= 127 # ? +def isblank(c): return _ctoi(c) in (9, 32) +def iscntrl(c): return 0 <= _ctoi(c) <= 31 or _ctoi(c) == 127 +def isdigit(c): return 48 <= _ctoi(c) <= 57 +def isgraph(c): return 33 <= _ctoi(c) <= 126 +def islower(c): return 97 <= _ctoi(c) <= 122 +def isprint(c): return 32 <= _ctoi(c) <= 126 +def ispunct(c): return isgraph(c) and not isalnum(c) def isspace(c): return _ctoi(c) in (9, 10, 11, 12, 13, 32) -def isupper(c): return _ctoi(c) >= 65 and _ctoi(c) <= 90 +def isupper(c): return 65 <= _ctoi(c) <= 90 def isxdigit(c): return isdigit(c) or \ - (_ctoi(c) >= 65 and _ctoi(c) <= 70) or (_ctoi(c) >= 97 and _ctoi(c) <= 102) -def isctrl(c): return _ctoi(c) < 32 + (65 <= _ctoi(c) <= 70) or (97 <= _ctoi(c) <= 102) +def isctrl(c): return 0 <= _ctoi(c) < 32 def ismeta(c): return _ctoi(c) > 127 def ascii(c): diff --git a/lib/curses/textpad.py b/lib/curses/textpad.py index 2b4b4cb6..2079953a 100644 --- a/lib/curses/textpad.py +++ b/lib/curses/textpad.py @@ -43,16 +43,20 @@ class Textbox: def __init__(self, win, insert_mode=False): self.win = win self.insert_mode = insert_mode - (self.maxy, self.maxx) = win.getmaxyx() - self.maxy = self.maxy - 1 - self.maxx = self.maxx - 1 + self._update_max_yx() self.stripspaces = 1 self.lastcmd = None win.keypad(1) + def _update_max_yx(self): + maxy, maxx = self.win.getmaxyx() + self.maxy = maxy - 1 + self.maxx = maxx - 1 + def _end_of_line(self, y): """Go to the location of the first blank on the given line, returning the index of the last non-blank character.""" + self._update_max_yx() last = self.maxx while True: if curses.ascii.ascii(self.win.inch(y, last)) != curses.ascii.SP: @@ -64,8 +68,10 @@ def _end_of_line(self, y): return last def _insert_printable_char(self, ch): + self._update_max_yx() (y, x) = self.win.getyx() - if y < self.maxy or x < self.maxx: + backyx = None + while y < self.maxy or x < self.maxx: if self.insert_mode: oldch = self.win.inch() # The try-catch ignores the error we trigger from some curses @@ -75,14 +81,20 @@ def _insert_printable_char(self, ch): self.win.addch(ch) except curses.error: pass - if self.insert_mode: - (backy, backx) = self.win.getyx() - if curses.ascii.isprint(oldch): - self._insert_printable_char(oldch) - self.win.move(backy, backx) + if not self.insert_mode or not curses.ascii.isprint(oldch): + break + ch = oldch + (y, x) = self.win.getyx() + # Remember where to put the cursor back since we are in insert_mode + if backyx is None: + backyx = y, x + + if backyx is not None: + self.win.move(*backyx) def do_command(self, ch): "Process a single editing command." + self._update_max_yx() (y, x) = self.win.getyx() self.lastcmd = ch if curses.ascii.isprint(ch): @@ -148,6 +160,7 @@ def do_command(self, ch): def gather(self): "Collect and return the contents of the window." result = "" + self._update_max_yx() for y in range(self.maxy+1): self.win.move(y, 0) stop = self._end_of_line(y) diff --git a/lib/datetime.py b/lib/datetime.py index 3af12e77..a2178c7c 100644 --- a/lib/datetime.py +++ b/lib/datetime.py @@ -12,7 +12,7 @@ def _cmp(x, y): MINYEAR = 1 MAXYEAR = 9999 -_MAXORDINAL = 3652059 # date.max.toordinal() +_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 @@ -26,7 +26,7 @@ def _cmp(x, y): # -1 is a placeholder for indexing purposes. _DAYS_IN_MONTH = [-1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] -_DAYS_BEFORE_MONTH = [-1] # -1 is a placeholder for indexing purposes. +_DAYS_BEFORE_MONTH = [-1] # -1 is a placeholder for indexing purposes. dbm = 0 for dim in _DAYS_IN_MONTH[1:]: _DAYS_BEFORE_MONTH.append(dbm) @@ -162,9 +162,9 @@ def _format_time(hh, mm, ss, us): # Correctly substitute for %z and %Z escapes in strftime formats. def _wrap_strftime(object, format, timetuple): # 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 + 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 = [] @@ -217,11 +217,6 @@ def _wrap_strftime(object, format, timetuple): 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): @@ -245,13 +240,31 @@ def _check_utc_offset(name, offset): 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)) + raise ValueError("%s()=%s, must be must be strictly between " + "-timedelta(hours=24) and timedelta(hours=24)" % + (name, offset)) + +def _check_int_field(value): + if isinstance(value, int): + return value + if not isinstance(value, float): + try: + value = value.__int__() + except AttributeError: + pass + else: + if isinstance(value, int): + return value + raise TypeError('__int__ returned non-int (type %s)' % + type(value).__name__) + raise TypeError('an integer is required (got type %s)' % + type(value).__name__) + raise TypeError('integer argument expected, got float') def _check_date_fields(year, month, day): - if not isinstance(year, int): - raise TypeError('int expected') + year = _check_int_field(year) + month = _check_int_field(month) + day = _check_int_field(day) if not MINYEAR <= year <= MAXYEAR: raise ValueError('year must be in %d..%d' % (MINYEAR, MAXYEAR), year) if not 1 <= month <= 12: @@ -259,10 +272,13 @@ def _check_date_fields(year, month, day): dim = _days_in_month(year, month) if not 1 <= day <= dim: raise ValueError('day must be in 1..%d' % dim, day) + return year, month, day def _check_time_fields(hour, minute, second, microsecond): - if not isinstance(hour, int): - raise TypeError('int expected') + hour = _check_int_field(hour) + minute = _check_int_field(minute) + second = _check_int_field(second) + microsecond = _check_int_field(microsecond) if not 0 <= hour <= 23: raise ValueError('hour must be in 0..23', hour) if not 0 <= minute <= 59: @@ -271,6 +287,7 @@ def _check_time_fields(hour, minute, second, microsecond): raise ValueError('second must be in 0..59', second) if not 0 <= microsecond <= 999999: raise ValueError('microsecond must be in 0..999999', microsecond) + return hour, minute, second, microsecond def _check_tzinfo_arg(tz): if tz is not None and not isinstance(tz, tzinfo): @@ -316,7 +333,7 @@ class timedelta: Representation: (days, seconds, microseconds). Why? Because I felt like it. """ - __slots__ = '_days', '_seconds', '_microseconds' + __slots__ = '_days', '_seconds', '_microseconds', '_hashcode' def __new__(cls, days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0): @@ -382,38 +399,26 @@ def __new__(cls, days=0, seconds=0, microseconds=0, # 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 + microseconds = round(microseconds + usdouble) + seconds, microseconds = divmod(microseconds, 1000000) + days, seconds = divmod(seconds, 24*3600) + d += days + s += seconds else: + microseconds = int(microseconds) 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) + s += seconds + microseconds = round(microseconds + usdouble) + assert isinstance(s, int) + assert isinstance(microseconds, int) 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) + seconds, us = divmod(microseconds, 1000000) + s += seconds days, s = divmod(s, 24*3600) d += days @@ -421,27 +426,31 @@ def __new__(cls, days=0, seconds=0, microseconds=0, assert isinstance(s, int) and 0 <= s < 24*3600 assert isinstance(us, int) and 0 <= us < 1000000 - self = object.__new__(cls) + if abs(d) > 999999999: + raise OverflowError("timedelta # of days is too large: %d" % d) + 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) - + self._hashcode = -1 return self def __repr__(self): if self._microseconds: - return "%s(%d, %d, %d)" % ('datetime.' + self.__class__.__name__, - self._days, - self._seconds, - self._microseconds) + return "%s.%s(%d, %d, %d)" % (self.__class__.__module__, + self.__class__.__qualname__, + 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) + return "%s.%s(%d, %d)" % (self.__class__.__module__, + self.__class__.__qualname__, + self._days, + self._seconds) + return "%s.%s(%d)" % (self.__class__.__module__, + self.__class__.__qualname__, + self._days) def __str__(self): mm, ss = divmod(self._seconds, 60) @@ -457,7 +466,7 @@ def plural(n): def total_seconds(self): """Total seconds in the duration.""" - return ((self.days * 86400 + self.seconds)*10**6 + + return ((self.days * 86400 + self.seconds) * 10**6 + self.microseconds) / 10**6 # Read-only field accessors @@ -578,12 +587,6 @@ def __eq__(self, other): 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 @@ -613,7 +616,9 @@ def _cmp(self, other): return _cmp(self._getstate(), other._getstate()) def __hash__(self): - return hash(self._getstate()) + if self._hashcode == -1: + self._hashcode = hash(self._getstate()) + return self._hashcode def __bool__(self): return (self._days != 0 or @@ -661,7 +666,7 @@ class date: Properties (readonly): year, month, day """ - __slots__ = '_year', '_month', '_day' + __slots__ = '_year', '_month', '_day', '_hashcode' def __new__(cls, year, month=None, day=None): """Constructor. @@ -670,17 +675,19 @@ def __new__(cls, year, month=None, day=None): 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 + if month is None and isinstance(year, bytes) and len(year) == 4 and \ + 1 <= year[2] <= 12: # Pickle support self = object.__new__(cls) self.__setstate(year) + self._hashcode = -1 return self - _check_date_fields(year, month, day) + year, month, day = _check_date_fields(year, month, day) self = object.__new__(cls) self._year = year self._month = month self._day = day + self._hashcode = -1 return self # Additional constructors @@ -699,7 +706,7 @@ def today(cls): @classmethod def fromordinal(cls, n): - """Contruct a date from a proleptic Gregorian ordinal. + """Construct 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. @@ -720,10 +727,11 @@ def __repr__(self): >>> 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) + return "%s.%s(%d, %d, %d)" % (self.__class__.__module__, + self.__class__.__qualname__, + 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 @@ -743,6 +751,8 @@ def strftime(self, fmt): return _wrap_strftime(self, fmt, self.timetuple()) def __format__(self, fmt): + if not isinstance(fmt, str): + raise TypeError("must be str, not %s" % type(fmt).__name__) if len(fmt) != 0: return self.strftime(fmt) return str(self) @@ -800,7 +810,6 @@ def replace(self, year=None, month=None, day=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. @@ -810,11 +819,6 @@ def __eq__(self, other): 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 @@ -843,7 +847,9 @@ def _cmp(self, other): def __hash__(self): "Hash." - return hash(self._getstate()) + if self._hashcode == -1: + self._hashcode = hash(self._getstate()) + return self._hashcode # Computations @@ -890,6 +896,7 @@ def isocalendar(self): ISO calendar algorithm taken from http://www.phys.uu.nl/~vgent/calendar/isocalendar.htm + (used with permission) """ year = self._year week1monday = _isoweek1monday(year) @@ -913,8 +920,6 @@ def _getstate(self): 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 @@ -933,6 +938,7 @@ class tzinfo: 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()") @@ -1019,6 +1025,7 @@ class time: Properties (readonly): hour, minute, second, microsecond, tzinfo """ + __slots__ = '_hour', '_minute', '_second', '_microsecond', '_tzinfo', '_hashcode' def __new__(cls, hour=0, minute=0, second=0, microsecond=0, tzinfo=None): """Constructor. @@ -1029,18 +1036,22 @@ def __new__(cls, hour=0, minute=0, second=0, microsecond=0, tzinfo=None): second, microsecond (default to zero) tzinfo (default to None) """ - self = object.__new__(cls) - if isinstance(hour, bytes) and len(hour) == 6: + if isinstance(hour, bytes) and len(hour) == 6 and hour[0] < 24: # Pickle support + self = object.__new__(cls) self.__setstate(hour, minute or None) + self._hashcode = -1 return self + hour, minute, second, microsecond = _check_time_fields( + hour, minute, second, microsecond) _check_tzinfo_arg(tzinfo) - _check_time_fields(hour, minute, second, microsecond) + self = object.__new__(cls) self._hour = hour self._minute = minute self._second = second self._microsecond = microsecond self._tzinfo = tzinfo + self._hashcode = -1 return self # Read-only field accessors @@ -1079,12 +1090,6 @@ def __eq__(self, other): else: return False - def __ne__(self, other): - if isinstance(other, time): - return self._cmp(other, allow_mixed=True) != 0 - else: - return True - def __le__(self, other): if isinstance(other, time): return self._cmp(other) <= 0 @@ -1125,8 +1130,8 @@ def _cmp(self, other, allow_mixed=False): if base_compare: return _cmp((self._hour, self._minute, self._second, self._microsecond), - (other._hour, other._minute, other._second, - other._microsecond)) + (other._hour, other._minute, other._second, + other._microsecond)) if myoff is None or otoff is None: if allow_mixed: return 2 # arbitrary non-zero value @@ -1139,16 +1144,20 @@ def _cmp(self, other, allow_mixed=False): 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)) + if self._hashcode == -1: + tzoff = self.utcoffset() + if not tzoff: # zero or None + self._hashcode = hash(self._getstate()[0]) + else: + 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: + self._hashcode = hash(time(h, m, self.second, self.microsecond)) + else: + self._hashcode = hash((h, m, self.second, self.microsecond)) + return self._hashcode # Conversion to string @@ -1176,8 +1185,9 @@ def __repr__(self): s = ", %d" % self._second else: s = "" - s= "%s(%d, %d%s)" % ('datetime.' + self.__class__.__name__, - self._hour, self._minute, s) + s= "%s.%s(%d, %d%s)" % (self.__class__.__module__, + self.__class__.__qualname__, + self._hour, self._minute, s) if self._tzinfo is not None: assert s[-1:] == ")" s = s[:-1] + ", tzinfo=%r" % self._tzinfo + ")" @@ -1210,6 +1220,8 @@ def strftime(self, fmt): return _wrap_strftime(self, fmt, timetuple) def __format__(self, fmt): + if not isinstance(fmt, str): + raise TypeError("must be str, not %s" % type(fmt).__name__) if len(fmt) != 0: return self.strftime(fmt) return str(self) @@ -1266,16 +1278,8 @@ def replace(self, hour=None, minute=None, second=None, microsecond=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): @@ -1289,15 +1293,11 @@ def _getstate(self): 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 + if tzinfo is not None and not isinstance(tzinfo, _tzinfo_class): + raise TypeError("bad tzinfo state arg") + 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) + self._tzinfo = tzinfo def __reduce__(self): return (time, self._getstate()) @@ -1314,25 +1314,30 @@ class datetime(date): 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. """ + __slots__ = date.__slots__ + time.__slots__ - __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: + if isinstance(year, bytes) and len(year) == 10 and 1 <= year[2] <= 12: # Pickle support - self = date.__new__(cls, year[:4]) + self = object.__new__(cls) self.__setstate(year, month) + self._hashcode = -1 return self + year, month, day = _check_date_fields(year, month, day) + hour, minute, second, microsecond = _check_time_fields( + hour, minute, second, microsecond) _check_tzinfo_arg(tzinfo) - _check_time_fields(hour, minute, second, microsecond) - self = date.__new__(cls, year, month, day) + self = object.__new__(cls) + self._year = year + self._month = month + self._day = day self._hour = hour self._minute = minute self._second = second self._microsecond = microsecond self._tzinfo = tzinfo + self._hashcode = -1 return self # Read-only field accessors @@ -1399,11 +1404,6 @@ def utcfromtimestamp(cls, t): """Construct a naive UTC datetime from a POSIX timestamp.""" return cls._fromtimestamp(t, True, None) - # 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." @@ -1490,11 +1490,8 @@ def replace(self, year=None, month=None, day=None, hour=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) + return datetime(year, month, day, hour, minute, second, microsecond, + tzinfo) def astimezone(self, tz=None): if tz is None: @@ -1564,10 +1561,9 @@ def isoformat(self, sep='T'): 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)) + 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: @@ -1583,14 +1579,15 @@ def isoformat(self, sep='T'): def __repr__(self): """Convert to formal string, for repr().""" - L = [self._year, self._month, self._day, # These are never zero + 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) + s = "%s.%s(%s)" % (self.__class__.__module__, + self.__class__.__qualname__, + ", ".join(map(str, L))) if self._tzinfo is not None: assert s[-1:] == ")" s = s[:-1] + ", tzinfo=%r" % self._tzinfo + ")" @@ -1622,7 +1619,9 @@ def tzname(self): 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) + if self._tzinfo is None: + return None + name = self._tzinfo.tzname(self) _check_tzname(name) return name @@ -1651,14 +1650,6 @@ def __eq__(self, other): else: return False - def __ne__(self, other): - if isinstance(other, datetime): - return self._cmp(other, allow_mixed=True) != 0 - elif not isinstance(other, date): - return NotImplemented - else: - return True - def __le__(self, other): if isinstance(other, datetime): return self._cmp(other) <= 0 @@ -1708,9 +1699,9 @@ def _cmp(self, other, allow_mixed=False): 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)) + (other._year, other._month, other._day, + other._hour, other._minute, other._second, + other._microsecond)) if myoff is None or otoff is None: if allow_mixed: return 2 # arbitrary non-zero value @@ -1768,12 +1759,15 @@ def __sub__(self, other): 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) + if self._hashcode == -1: + tzoff = self.utcoffset() + if tzoff is None: + self._hashcode = hash(self._getstate()[0]) + else: + days = _ymd2ord(self.year, self.month, self.day) + seconds = self.hour * 3600 + self.minute * 60 + self.second + self._hashcode = hash(timedelta(days, seconds, self.microsecond) - tzoff) + return self._hashcode # Pickle support. @@ -1790,14 +1784,13 @@ def _getstate(self): return (basestate, self._tzinfo) def __setstate(self, string, tzinfo): + if tzinfo is not None and not isinstance(tzinfo, _tzinfo_class): + raise TypeError("bad tzinfo state arg") (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) + self._tzinfo = tzinfo def __reduce__(self): return (self.__class__, self._getstate()) @@ -1813,7 +1806,7 @@ def _isoweek1monday(year): # XXX This could be done more efficiently THURSDAY = 3 firstday = _ymd2ord(year, 1, 1) - firstweekday = (firstday + 6) % 7 # See weekday() above + firstweekday = (firstday + 6) % 7 # See weekday() above week1monday = firstday - firstweekday if firstweekday > THURSDAY: week1monday += 7 @@ -1834,13 +1827,12 @@ def __new__(cls, offset, name=_Omitted): 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") + 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 @@ -1877,10 +1869,12 @@ def __repr__(self): 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) + return "%s.%s(%r)" % (self.__class__.__module__, + self.__class__.__qualname__, + self._offset) + return "%s.%s(%r, %r)" % (self.__class__.__module__, + self.__class__.__qualname__, + self._offset, self._name) def __str__(self): return self.tzname(None) @@ -2135,14 +2129,13 @@ def _name_from_offset(delta): 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) + del (_DAYNAMES, _DAYS_BEFORE_MONTH, _DAYS_IN_MONTH, _DI100Y, _DI400Y, + _DI4Y, _EPOCH, _MAXORDINAL, _MONTHNAMES, _build_struct_time, + _check_date_fields, _check_int_field, _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 diff --git a/lib/dbm/__init__.py b/lib/dbm/__init__.py index 5f4664a7..6831a844 100644 --- a/lib/dbm/__init__.py +++ b/lib/dbm/__init__.py @@ -153,9 +153,9 @@ def whichdb(filename): except OSError: return None - # Read the start of the file -- the magic number - s16 = f.read(16) - f.close() + with f: + # Read the start of the file -- the magic number + s16 = f.read(16) s = s16[0:4] # Return "" if not at least 4 bytes diff --git a/lib/dbm/dumb.py b/lib/dbm/dumb.py index 63bc3294..e6d65051 100644 --- a/lib/dbm/dumb.py +++ b/lib/dbm/dumb.py @@ -45,8 +45,9 @@ class _Database(collections.MutableMapping): _os = _os # for _commit() _io = _io # for _commit() - def __init__(self, filebasename, mode): + def __init__(self, filebasename, mode, flag='c'): self._mode = mode + self._readonly = (flag == 'r') # The directory file is a text file. Each line looks like # "%r, (%d, %d)\n" % (key, pos, siz) @@ -65,6 +66,17 @@ def __init__(self, filebasename, mode): # The index is an in-memory dict, mirroring the directory file. self._index = None # maps keys to (pos, siz) pairs + # Handle the creation + self._create(flag) + self._update() + + def _create(self, flag): + if flag == 'n': + for filename in (self._datfile, self._bakfile, self._dirfile): + try: + _os.remove(filename) + except OSError: + pass # Mod by Jack: create data file if needed try: f = _io.open(self._datfile, 'r', encoding="Latin-1") @@ -73,7 +85,6 @@ def __init__(self, filebasename, mode): self._chmod(self._datfile) else: f.close() - self._update() # Read directory file into the in-memory index dict. def _update(self): @@ -81,8 +92,9 @@ def _update(self): try: f = _io.open(self._dirfile, 'r', encoding="Latin-1") except OSError: - pass + self._modified = not self._readonly else: + self._modified = False with f: for line in f: line = line.rstrip() @@ -97,7 +109,7 @@ def _commit(self): # CAUTION: It's vital that _commit() succeed, and _commit() can # be called from __del__(). Therefore we must never reference a # global in this routine. - if self._index is None: + if self._index is None or not self._modified: return # nothing to do try: @@ -177,6 +189,7 @@ def __setitem__(self, key, val): elif not isinstance(val, (bytes, bytearray)): raise TypeError("values must be bytes or strings") self._verify_open() + self._modified = True if key not in self._index: self._addkey(key, self._addval(val)) else: @@ -205,6 +218,7 @@ def __delitem__(self, key): if isinstance(key, str): key = key.encode('utf-8') self._verify_open() + self._modified = True # 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 @@ -266,20 +280,20 @@ def __exit__(self, *args): self.close() -def open(file, flag=None, mode=0o666): +def open(file, flag='c', mode=0o666): """Open the database file, filename, and return corresponding object. The flag argument, used to control how the database is opened in the - other DBM implementations, is ignored in the dbm.dumb module; the - database is always opened for update, and will be created if it does - not exist. + other DBM implementations, supports only the semantics of 'c' and 'n' + values. Other values will default to the semantics of 'c' value: + the database will always opened for update and will be created if it + does not exist. The optional mode argument is the UNIX mode of the file, used only when the database has to be created. It defaults to octal code 0o666 (and will be modified by the prevailing umask). """ - # flag argument is currently ignored # Modify mode depending on the umask try: @@ -290,5 +304,4 @@ def open(file, flag=None, mode=0o666): else: # Turn off any bits that are set in the umask mode = mode & (~um) - - return _Database(file, mode) + return _Database(file, mode, flag=flag) diff --git a/lib/decimal.py b/lib/decimal.py index 324e4f91..7746ea26 100644 --- a/lib/decimal.py +++ b/lib/decimal.py @@ -1,6413 +1,11 @@ -# Copyright (c) 2004 Python Software Foundation. -# All rights reserved. - -# Written by Eric Price -# and Facundo Batista -# and Raymond Hettinger -# and Aahz -# and Tim Peters - -# This module should be kept in sync with the latest updates of the -# IBM specification as it evolves. Those updates will be treated -# as bug fixes (deviation from the spec is a compatibility, usability -# bug) and will be backported. At this point the spec is stabilizing -# and the updates are becoming fewer, smaller, and less significant. - -""" -This is an implementation of decimal floating point arithmetic based on -the General Decimal Arithmetic Specification: - - http://speleotrove.com/decimal/decarith.html - -and IEEE standard 854-1987: - - http://en.wikipedia.org/wiki/IEEE_854-1987 - -Decimal floating point has finite precision with arbitrarily large bounds. - -The purpose of this module is to support arithmetic using familiar -"schoolhouse" rules and to avoid some of the tricky representation -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 0.0; Decimal('1.00') % Decimal('0.1') returns the expected -Decimal('0.00')). - -Here are some examples of using the decimal module: - ->>> from decimal import * ->>> setcontext(ExtendedContext) ->>> Decimal(0) -Decimal('0') ->>> Decimal('1') -Decimal('1') ->>> Decimal('-.0123') -Decimal('-0.0123') ->>> Decimal(123456) -Decimal('123456') ->>> Decimal('123.45e12345678') -Decimal('1.2345E+12345680') ->>> Decimal('1.33') + Decimal('1.27') -Decimal('2.60') ->>> Decimal('12.34') + Decimal('3.87') - Decimal('18.41') -Decimal('-2.20') ->>> dig = Decimal(1) ->>> print(dig / Decimal(3)) -0.333333333 ->>> getcontext().prec = 18 ->>> print(dig / Decimal(3)) -0.333333333333333333 ->>> print(dig.sqrt()) -1 ->>> print(Decimal(3).sqrt()) -1.73205080756887729 ->>> print(Decimal(3) ** 123) -4.85192780976896427E+58 ->>> inf = Decimal(1) / Decimal(0) ->>> print(inf) -Infinity ->>> neginf = Decimal(-1) / Decimal(0) ->>> print(neginf) --Infinity ->>> print(neginf + inf) -NaN ->>> print(neginf * inf) --Infinity ->>> print(dig / 0) -Infinity ->>> getcontext().traps[DivisionByZero] = 1 ->>> print(dig / 0) -Traceback (most recent call last): - ... - ... - ... -decimal.DivisionByZero: x / 0 ->>> c = Context() ->>> c.traps[InvalidOperation] = 0 ->>> print(c.flags[InvalidOperation]) -0 ->>> c.divide(Decimal(0), Decimal(0)) -Decimal('NaN') ->>> c.traps[InvalidOperation] = 1 ->>> print(c.flags[InvalidOperation]) -1 ->>> c.flags[InvalidOperation] = 0 ->>> print(c.flags[InvalidOperation]) -0 ->>> print(c.divide(Decimal(0), Decimal(0))) -Traceback (most recent call last): - ... - ... - ... -decimal.InvalidOperation: 0 / 0 ->>> print(c.flags[InvalidOperation]) -1 ->>> c.flags[InvalidOperation] = 0 ->>> c.traps[InvalidOperation] = 0 ->>> print(c.divide(Decimal(0), Decimal(0))) -NaN ->>> print(c.flags[InvalidOperation]) -1 ->>> -""" - -__all__ = [ - # Two major classes - 'Decimal', 'Context', - - # Named tuple representation - 'DecimalTuple', - - # Contexts - 'DefaultContext', 'BasicContext', 'ExtendedContext', - - # Exceptions - 'DecimalException', 'Clamped', 'InvalidOperation', 'DivisionByZero', - 'Inexact', 'Rounded', 'Subnormal', 'Overflow', 'Underflow', - 'FloatOperation', - - # Exceptional conditions that trigger InvalidOperation - 'DivisionImpossible', 'InvalidContext', 'ConversionSyntax', 'DivisionUndefined', - - # Constants for use in setting up contexts - 'ROUND_DOWN', 'ROUND_HALF_UP', 'ROUND_HALF_EVEN', 'ROUND_CEILING', - 'ROUND_FLOOR', 'ROUND_UP', 'ROUND_HALF_DOWN', 'ROUND_05UP', - - # Functions for manipulating contexts - 'setcontext', 'getcontext', 'localcontext', - - # Limits for the C version for compatibility - 'MAX_PREC', 'MAX_EMAX', 'MIN_EMIN', 'MIN_ETINY', - - # C version: compile time choice that enables the thread local context - 'HAVE_THREADS' -] - -__version__ = '1.70' # Highest version of the spec this complies with - # See http://speleotrove.com/decimal/ -__libmpdec_version__ = "2.4.1" # compatible libmpdec version - -import math as _math -import numbers as _numbers -import sys - -try: - from collections import namedtuple as _namedtuple - DecimalTuple = _namedtuple('DecimalTuple', 'sign digits exponent') -except ImportError: - DecimalTuple = lambda *args: args - -# Rounding -ROUND_DOWN = 'ROUND_DOWN' -ROUND_HALF_UP = 'ROUND_HALF_UP' -ROUND_HALF_EVEN = 'ROUND_HALF_EVEN' -ROUND_CEILING = 'ROUND_CEILING' -ROUND_FLOOR = 'ROUND_FLOOR' -ROUND_UP = 'ROUND_UP' -ROUND_HALF_DOWN = 'ROUND_HALF_DOWN' -ROUND_05UP = 'ROUND_05UP' - -# Compatibility with the C version -HAVE_THREADS = True -if sys.maxsize == 2**63-1: - MAX_PREC = 999999999999999999 - MAX_EMAX = 999999999999999999 - MIN_EMIN = -999999999999999999 -else: - MAX_PREC = 425000000 - MAX_EMAX = 425000000 - MIN_EMIN = -425000000 - -MIN_ETINY = MIN_EMIN - (MAX_PREC-1) - -# Errors - -class DecimalException(ArithmeticError): - """Base exception class. - - Used exceptions derive from this. - If an exception derives from another exception besides this (such as - Underflow (Inexact, Rounded, Subnormal) that indicates that it is only - called if the others are present. This isn't actually used for - anything, though. - - handle -- Called when context._raise_error is called and the - trap_enabler is not set. First argument is self, second is the - context. More arguments can be given, those being after - the explanation in _raise_error (For example, - context._raise_error(NewError, '(-x)!', self._sign) would - call NewError().handle(context, self._sign).) - - To define a new exception, it should be sufficient to have it derive - from DecimalException. - """ - def handle(self, context, *args): - pass - - -class Clamped(DecimalException): - """Exponent of a 0 changed to fit bounds. - - This occurs and signals clamped if the exponent of a result has been - altered in order to fit the constraints of a specific concrete - representation. This may occur when the exponent of a zero result would - be outside the bounds of a representation, or when a large normal - number would have an encoded exponent that cannot be represented. In - this latter case, the exponent is reduced to fit and the corresponding - number of zero digits are appended to the coefficient ("fold-down"). - """ - -class InvalidOperation(DecimalException): - """An invalid operation was performed. - - Various bad things cause this: - - Something creates a signaling NaN - -INF + INF - 0 * (+-)INF - (+-)INF / (+-)INF - x % 0 - (+-)INF % x - x._rescale( non-integer ) - sqrt(-x) , x > 0 - 0 ** 0 - x ** (non-integer) - x ** (+-)INF - An operand is invalid - - The result of the operation after these is a quiet positive NaN, - except when the cause is a signaling NaN, in which case the result is - also a quiet NaN, but with the original sign, and an optional - diagnostic information. - """ - def handle(self, context, *args): - if args: - ans = _dec_from_triple(args[0]._sign, args[0]._int, 'n', True) - return ans._fix_nan(context) - return _NaN - -class ConversionSyntax(InvalidOperation): - """Trying to convert badly formed string. - - This occurs and signals invalid-operation if an string is being - converted to a number and it does not conform to the numeric string - syntax. The result is [0,qNaN]. - """ - def handle(self, context, *args): - return _NaN - -class DivisionByZero(DecimalException, ZeroDivisionError): - """Division by 0. - - This occurs and signals division-by-zero if division of a finite number - by zero was attempted (during a divide-integer or divide operation, or a - power operation with negative right-hand operand), and the dividend was - not zero. - - The result of the operation is [sign,inf], where sign is the exclusive - or of the signs of the operands for divide, or is 1 for an odd power of - -0, for power. - """ - - def handle(self, context, sign, *args): - return _SignedInfinity[sign] - -class DivisionImpossible(InvalidOperation): - """Cannot perform the division adequately. - - This occurs and signals invalid-operation if the integer result of a - divide-integer or remainder operation had too many digits (would be - longer than precision). The result is [0,qNaN]. - """ - - def handle(self, context, *args): - return _NaN - -class DivisionUndefined(InvalidOperation, ZeroDivisionError): - """Undefined result of division. - - This occurs and signals invalid-operation if division by zero was - attempted (during a divide-integer, divide, or remainder operation), and - the dividend is also zero. The result is [0,qNaN]. - """ - - def handle(self, context, *args): - return _NaN - -class Inexact(DecimalException): - """Had to round, losing information. - - This occurs and signals inexact whenever the result of an operation is - not exact (that is, it needed to be rounded and any discarded digits - were non-zero), or if an overflow or underflow condition occurs. The - result in all cases is unchanged. - - The inexact signal may be tested (or trapped) to determine if a given - operation (or sequence of operations) was inexact. - """ - -class InvalidContext(InvalidOperation): - """Invalid context. Unknown rounding, for example. - - This occurs and signals invalid-operation if an invalid context was - detected during an operation. This can occur if contexts are not checked - on creation and either the precision exceeds the capability of the - underlying concrete representation or an unknown or unsupported rounding - was specified. These aspects of the context need only be checked when - the values are required to be used. The result is [0,qNaN]. - """ - - def handle(self, context, *args): - return _NaN - -class Rounded(DecimalException): - """Number got rounded (not necessarily changed during rounding). - - This occurs and signals rounded whenever the result of an operation is - rounded (that is, some zero or non-zero digits were discarded from the - coefficient), or if an overflow or underflow condition occurs. The - result in all cases is unchanged. - - The rounded signal may be tested (or trapped) to determine if a given - operation (or sequence of operations) caused a loss of precision. - """ - -class Subnormal(DecimalException): - """Exponent < Emin before rounding. - - This occurs and signals subnormal whenever the result of a conversion or - operation is subnormal (that is, its adjusted exponent is less than - Emin, before any rounding). The result in all cases is unchanged. - - The subnormal signal may be tested (or trapped) to determine if a given - or operation (or sequence of operations) yielded a subnormal result. - """ - -class Overflow(Inexact, Rounded): - """Numerical overflow. - - This occurs and signals overflow if the adjusted exponent of a result - (from a conversion or from an operation that is not an attempt to divide - by zero), after rounding, would be greater than the largest value that - can be handled by the implementation (the value Emax). - - The result depends on the rounding mode: - - For round-half-up and round-half-even (and for round-half-down and - round-up, if implemented), the result of the operation is [sign,inf], - where sign is the sign of the intermediate result. For round-down, the - result is the largest finite number that can be represented in the - current precision, with the sign of the intermediate result. For - round-ceiling, the result is the same as for round-down if the sign of - the intermediate result is 1, or is [0,inf] otherwise. For round-floor, - the result is the same as for round-down if the sign of the intermediate - result is 0, or is [1,inf] otherwise. In all cases, Inexact and Rounded - will also be raised. - """ - - def handle(self, context, sign, *args): - if context.rounding in (ROUND_HALF_UP, ROUND_HALF_EVEN, - ROUND_HALF_DOWN, ROUND_UP): - return _SignedInfinity[sign] - if sign == 0: - if context.rounding == ROUND_CEILING: - return _SignedInfinity[sign] - return _dec_from_triple(sign, '9'*context.prec, - context.Emax-context.prec+1) - if sign == 1: - if context.rounding == ROUND_FLOOR: - return _SignedInfinity[sign] - return _dec_from_triple(sign, '9'*context.prec, - context.Emax-context.prec+1) - - -class Underflow(Inexact, Rounded, Subnormal): - """Numerical underflow with result rounded to 0. - - This occurs and signals underflow if a result is inexact and the - adjusted exponent of the result would be smaller (more negative) than - the smallest value that can be handled by the implementation (the value - Emin). That is, the result is both inexact and subnormal. - - The result after an underflow will be a subnormal number rounded, if - necessary, so that its exponent is not less than Etiny. This may result - in 0 with the sign of the intermediate result and an exponent of Etiny. - - In all cases, Inexact, Rounded, and Subnormal will also be raised. - """ - -class FloatOperation(DecimalException, TypeError): - """Enable stricter semantics for mixing floats and Decimals. - - If the signal is not trapped (default), mixing floats and Decimals is - permitted in the Decimal() constructor, context.create_decimal() and - all comparison operators. Both conversion and comparisons are exact. - Any occurrence of a mixed operation is silently recorded by setting - FloatOperation in the context flags. Explicit conversions with - Decimal.from_float() or context.create_decimal_from_float() do not - set the flag. - - Otherwise (the signal is trapped), only equality comparisons and explicit - conversions are silent. All other mixed operations raise FloatOperation. - """ - -# List of public traps and flags -_signals = [Clamped, DivisionByZero, Inexact, Overflow, Rounded, - Underflow, InvalidOperation, Subnormal, FloatOperation] - -# Map conditions (per the spec) to signals -_condition_map = {ConversionSyntax:InvalidOperation, - DivisionImpossible:InvalidOperation, - DivisionUndefined:InvalidOperation, - InvalidContext:InvalidOperation} - -# Valid rounding modes -_rounding_modes = (ROUND_DOWN, ROUND_HALF_UP, ROUND_HALF_EVEN, ROUND_CEILING, - ROUND_FLOOR, ROUND_UP, ROUND_HALF_DOWN, ROUND_05UP) - -##### Context Functions ################################################## - -# The getcontext() and setcontext() function manage access to a thread-local -# current context. Py2.4 offers direct support for thread locals. If that -# is not available, use threading.current_thread() which is slower but will -# work for older Pythons. If threads are not part of the build, create a -# mock threading object with threading.local() returning the module namespace. - -try: - import threading -except ImportError: - # Python was compiled without threads; create a mock object instead - class MockThreading(object): - def local(self, sys=sys): - return sys.modules[__name__] - threading = MockThreading() - del MockThreading - -try: - threading.local - -except AttributeError: - - # To fix reloading, force it to create a new context - # Old contexts have different exceptions in their dicts, making problems. - if hasattr(threading.current_thread(), '__decimal_context__'): - del threading.current_thread().__decimal_context__ - - def setcontext(context): - """Set this thread's context to context.""" - if context in (DefaultContext, BasicContext, ExtendedContext): - context = context.copy() - context.clear_flags() - threading.current_thread().__decimal_context__ = context - - def getcontext(): - """Returns this thread's context. - - If this thread does not yet have a context, returns - a new context and sets this thread's context. - New contexts are copies of DefaultContext. - """ - try: - return threading.current_thread().__decimal_context__ - except AttributeError: - context = Context() - threading.current_thread().__decimal_context__ = context - return context - -else: - - local = threading.local() - if hasattr(local, '__decimal_context__'): - del local.__decimal_context__ - - def getcontext(_local=local): - """Returns this thread's context. - - If this thread does not yet have a context, returns - a new context and sets this thread's context. - New contexts are copies of DefaultContext. - """ - try: - return _local.__decimal_context__ - except AttributeError: - context = Context() - _local.__decimal_context__ = context - return context - - def setcontext(context, _local=local): - """Set this thread's context to context.""" - if context in (DefaultContext, BasicContext, ExtendedContext): - context = context.copy() - context.clear_flags() - _local.__decimal_context__ = context - - del threading, local # Don't contaminate the namespace - -def localcontext(ctx=None): - """Return a context manager for a copy of the supplied context - - Uses a copy of the current context if no context is specified - The returned context manager creates a local decimal context - in a with statement: - def sin(x): - with localcontext() as ctx: - ctx.prec += 2 - # Rest of sin calculation algorithm - # uses a precision 2 greater than normal - return +s # Convert result to normal precision - - def sin(x): - with localcontext(ExtendedContext): - # Rest of sin calculation algorithm - # uses the Extended Context from the - # General Decimal Arithmetic Specification - return +s # Convert result to normal context - - >>> setcontext(DefaultContext) - >>> print(getcontext().prec) - 28 - >>> with localcontext(): - ... ctx = getcontext() - ... ctx.prec += 2 - ... print(ctx.prec) - ... - 30 - >>> with localcontext(ExtendedContext): - ... print(getcontext().prec) - ... - 9 - >>> print(getcontext().prec) - 28 - """ - if ctx is None: ctx = getcontext() - return _ContextManager(ctx) - - -##### Decimal class ####################################################### - -# Do not subclass Decimal from numbers.Real and do not register it as such -# (because Decimals are not interoperable with floats). See the notes in -# numbers.py for more detail. - -class Decimal(object): - """Floating point class for decimal arithmetic.""" - - __slots__ = ('_exp','_int','_sign', '_is_special') - # Generally, the value of the Decimal instance is given by - # (-1)**_sign * _int * 10**_exp - # Special values are signified by _is_special == True - - # We're immutable, so use __new__ not __init__ - def __new__(cls, value="0", context=None): - """Create a decimal point instance. - - >>> Decimal('3.14') # string input - Decimal('3.14') - >>> Decimal((0, (3, 1, 4), -2)) # tuple (sign, digit_tuple, exponent) - Decimal('3.14') - >>> Decimal(314) # int - Decimal('314') - >>> Decimal(Decimal(314)) # another decimal instance - Decimal('314') - >>> Decimal(' 3.14 \\n') # leading and trailing whitespace okay - Decimal('3.14') - """ - - # Note that the coefficient, self._int, is actually stored as - # a string rather than as a tuple of digits. This speeds up - # the "digits to integer" and "integer to digits" conversions - # that are used in almost every arithmetic operation on - # Decimals. This is an internal detail: the as_tuple function - # and the Decimal constructor still deal with tuples of - # digits. - - self = object.__new__(cls) - - # From a string - # REs insist on real strings, so we can too. - if isinstance(value, str): - m = _parser(value.strip()) - if m is None: - if context is None: - context = getcontext() - return context._raise_error(ConversionSyntax, - "Invalid literal for Decimal: %r" % value) - - if m.group('sign') == "-": - self._sign = 1 - else: - self._sign = 0 - intpart = m.group('int') - if intpart is not None: - # finite number - fracpart = m.group('frac') or '' - exp = int(m.group('exp') or '0') - self._int = str(int(intpart+fracpart)) - self._exp = exp - len(fracpart) - self._is_special = False - else: - diag = m.group('diag') - if diag is not None: - # NaN - self._int = str(int(diag or '0')).lstrip('0') - if m.group('signal'): - self._exp = 'N' - else: - self._exp = 'n' - else: - # infinity - self._int = '0' - self._exp = 'F' - self._is_special = True - return self - - # From an integer - if isinstance(value, int): - if value >= 0: - self._sign = 0 - else: - self._sign = 1 - self._exp = 0 - self._int = str(abs(value)) - self._is_special = False - return self - - # From another decimal - if isinstance(value, Decimal): - self._exp = value._exp - self._sign = value._sign - self._int = value._int - self._is_special = value._is_special - return self - - # From an internal working value - if isinstance(value, _WorkRep): - self._sign = value.sign - self._int = str(value.int) - self._exp = int(value.exp) - self._is_special = False - return self - - # tuple/list conversion (possibly from as_tuple()) - if isinstance(value, (list,tuple)): - if len(value) != 3: - raise ValueError('Invalid tuple size in creation of Decimal ' - 'from list or tuple. The list or tuple ' - 'should have exactly three elements.') - # process sign. The isinstance test rejects floats - if not (isinstance(value[0], int) and value[0] in (0,1)): - raise ValueError("Invalid sign. The first value in the tuple " - "should be an integer; either 0 for a " - "positive number or 1 for a negative number.") - self._sign = value[0] - if value[2] == 'F': - # infinity: value[1] is ignored - self._int = '0' - self._exp = value[2] - self._is_special = True - else: - # process and validate the digits in value[1] - digits = [] - for digit in value[1]: - if isinstance(digit, int) and 0 <= digit <= 9: - # skip leading zeros - if digits or digit != 0: - digits.append(digit) - else: - raise ValueError("The second value in the tuple must " - "be composed of integers in the range " - "0 through 9.") - if value[2] in ('n', 'N'): - # NaN: digits form the diagnostic - self._int = ''.join(map(str, digits)) - self._exp = value[2] - self._is_special = True - elif isinstance(value[2], int): - # finite number: digits give the coefficient - self._int = ''.join(map(str, digits or [0])) - self._exp = value[2] - self._is_special = False - else: - raise ValueError("The third value in the tuple must " - "be an integer, or one of the " - "strings 'F', 'n', 'N'.") - return self - - if isinstance(value, float): - if context is None: - context = getcontext() - context._raise_error(FloatOperation, - "strict semantics for mixing floats and Decimals are " - "enabled") - 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) - - @classmethod - def from_float(cls, f): - """Converts a float to a decimal number, exactly. - - Note that Decimal.from_float(0.1) is not the same as Decimal('0.1'). - Since 0.1 is not exactly representable in binary floating point, the - value is stored as the nearest representable value which is - 0x1.999999999999ap-4. The exact equivalent of the value in decimal - is 0.1000000000000000055511151231257827021181583404541015625. - - >>> Decimal.from_float(0.1) - Decimal('0.1000000000000000055511151231257827021181583404541015625') - >>> Decimal.from_float(float('nan')) - Decimal('NaN') - >>> Decimal.from_float(float('inf')) - Decimal('Infinity') - >>> Decimal.from_float(-float('inf')) - Decimal('-Infinity') - >>> Decimal.from_float(-0.0) - Decimal('-0') - - """ - if isinstance(f, int): # handle integer inputs - return cls(f) - if not isinstance(f, float): - raise TypeError("argument must be int or float.") - if _math.isinf(f) or _math.isnan(f): - return cls(repr(f)) - if _math.copysign(1.0, f) == 1.0: - sign = 0 - else: - sign = 1 - n, d = abs(f).as_integer_ratio() - k = d.bit_length() - 1 - result = _dec_from_triple(sign, str(n*5**k), -k) - if cls is Decimal: - return result - else: - return cls(result) - - def _isnan(self): - """Returns whether the number is not actually one. - - 0 if a number - 1 if NaN - 2 if sNaN - """ - if self._is_special: - exp = self._exp - if exp == 'n': - return 1 - elif exp == 'N': - return 2 - return 0 - - def _isinfinity(self): - """Returns whether the number is infinite - - 0 if finite or not a number - 1 if +INF - -1 if -INF - """ - if self._exp == 'F': - if self._sign: - return -1 - return 1 - return 0 - - def _check_nans(self, other=None, context=None): - """Returns whether the number is not actually one. - - if self, other are sNaN, signal - if self, other are NaN return nan - return 0 - - Done before operations. - """ - - self_is_nan = self._isnan() - if other is None: - other_is_nan = False - else: - other_is_nan = other._isnan() - - if self_is_nan or other_is_nan: - if context is None: - context = getcontext() - - if self_is_nan == 2: - return context._raise_error(InvalidOperation, 'sNaN', - self) - if other_is_nan == 2: - return context._raise_error(InvalidOperation, 'sNaN', - other) - if self_is_nan: - return self._fix_nan(context) - - return other._fix_nan(context) - return 0 - - def _compare_check_nans(self, other, context): - """Version of _check_nans used for the signaling comparisons - compare_signal, __le__, __lt__, __ge__, __gt__. - - Signal InvalidOperation if either self or other is a (quiet - or signaling) NaN. Signaling NaNs take precedence over quiet - NaNs. - - Return 0 if neither operand is a NaN. - - """ - if context is None: - context = getcontext() - - if self._is_special or other._is_special: - if self.is_snan(): - return context._raise_error(InvalidOperation, - 'comparison involving sNaN', - self) - elif other.is_snan(): - return context._raise_error(InvalidOperation, - 'comparison involving sNaN', - other) - elif self.is_qnan(): - return context._raise_error(InvalidOperation, - 'comparison involving NaN', - self) - elif other.is_qnan(): - return context._raise_error(InvalidOperation, - 'comparison involving NaN', - other) - return 0 - - def __bool__(self): - """Return True if self is nonzero; otherwise return False. - - NaNs and infinities are considered nonzero. - """ - return self._is_special or self._int != '0' - - def _cmp(self, other): - """Compare the two non-NaN decimal instances self and other. - - Returns -1 if self < other, 0 if self == other and 1 - if self > other. This routine is for internal use only.""" - - if self._is_special or other._is_special: - self_inf = self._isinfinity() - other_inf = other._isinfinity() - if self_inf == other_inf: - return 0 - elif self_inf < other_inf: - return -1 - else: - return 1 - - # check for zeros; Decimal('0') == Decimal('-0') - if not self: - if not other: - return 0 - else: - return -((-1)**other._sign) - if not other: - return (-1)**self._sign - - # If different signs, neg one is less - if other._sign < self._sign: - return -1 - if self._sign < other._sign: - return 1 - - self_adjusted = self.adjusted() - other_adjusted = other.adjusted() - if self_adjusted == other_adjusted: - self_padded = self._int + '0'*(self._exp - other._exp) - other_padded = other._int + '0'*(other._exp - self._exp) - if self_padded == other_padded: - return 0 - elif self_padded < other_padded: - return -(-1)**self._sign - else: - return (-1)**self._sign - elif self_adjusted > other_adjusted: - return (-1)**self._sign - else: # self_adjusted < other_adjusted - return -((-1)**self._sign) - - # Note: The Decimal standard doesn't cover rich comparisons for - # Decimals. In particular, the specification is silent on the - # subject of what should happen for a comparison involving a NaN. - # We take the following approach: - # - # == 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. - # - # This behavior is designed to conform as closely as possible to - # that specified by IEEE 754. - - def __eq__(self, other, context=None): - self, other = _convert_for_comparison(self, other, equality_op=True) - if other is NotImplemented: - return other - if self._check_nans(other, context): - return False - return self._cmp(other) == 0 - - def __ne__(self, other, context=None): - self, other = _convert_for_comparison(self, other, equality_op=True) - if other is NotImplemented: - return other - if self._check_nans(other, context): - return True - return self._cmp(other) != 0 - - - def __lt__(self, other, context=None): - self, other = _convert_for_comparison(self, other) - if other is NotImplemented: - return other - ans = self._compare_check_nans(other, context) - if ans: - return False - return self._cmp(other) < 0 - - def __le__(self, other, context=None): - self, other = _convert_for_comparison(self, other) - if other is NotImplemented: - return other - ans = self._compare_check_nans(other, context) - if ans: - return False - return self._cmp(other) <= 0 - - def __gt__(self, other, context=None): - self, other = _convert_for_comparison(self, other) - if other is NotImplemented: - return other - ans = self._compare_check_nans(other, context) - if ans: - return False - return self._cmp(other) > 0 - - def __ge__(self, other, context=None): - self, other = _convert_for_comparison(self, other) - if other is NotImplemented: - return other - ans = self._compare_check_nans(other, context) - if ans: - return False - return self._cmp(other) >= 0 - - def compare(self, other, context=None): - """Compare self to other. Return a decimal value: - - a or b is a NaN ==> Decimal('NaN') - a < b ==> Decimal('-1') - a == b ==> Decimal('0') - a > b ==> Decimal('1') - """ - other = _convert_other(other, raiseit=True) - - # Compare(NaN, NaN) = NaN - if (self._is_special or other and other._is_special): - ans = self._check_nans(other, context) - if ans: - return ans - - return Decimal(self._cmp(other)) - - def __hash__(self): - """x.__hash__() <==> hash(x)""" - - # 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.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. - - To show the internals exactly as they are. - """ - return DecimalTuple(self._sign, tuple(map(int, self._int)), self._exp) - - def __repr__(self): - """Represents the number as an instance of Decimal.""" - # Invariant: eval(repr(d)) == d - return "Decimal('%s')" % str(self) - - def __str__(self, eng=False, context=None): - """Return string representation of the number in scientific notation. - - Captures all of the information in the underlying representation. - """ - - sign = ['', '-'][self._sign] - if self._is_special: - if self._exp == 'F': - return sign + 'Infinity' - elif self._exp == 'n': - return sign + 'NaN' + self._int - else: # self._exp == 'N' - return sign + 'sNaN' + self._int - - # number of digits of self._int to left of decimal point - leftdigits = self._exp + len(self._int) - - # dotplace is number of digits of self._int to the left of the - # decimal point in the mantissa of the output string (that is, - # after adjusting the exponent) - if self._exp <= 0 and leftdigits > -6: - # no exponent required - dotplace = leftdigits - elif not eng: - # usual scientific notation: 1 digit on left of the point - dotplace = 1 - elif self._int == '0': - # engineering notation, zero - dotplace = (leftdigits + 1) % 3 - 1 - else: - # engineering notation, nonzero - dotplace = (leftdigits - 1) % 3 + 1 - - if dotplace <= 0: - intpart = '0' - fracpart = '.' + '0'*(-dotplace) + self._int - elif dotplace >= len(self._int): - intpart = self._int+'0'*(dotplace-len(self._int)) - fracpart = '' - else: - intpart = self._int[:dotplace] - fracpart = '.' + self._int[dotplace:] - if leftdigits == dotplace: - exp = '' - else: - if context is None: - context = getcontext() - exp = ['e', 'E'][context.capitals] + "%+d" % (leftdigits-dotplace) - - return sign + intpart + fracpart + exp - - def to_eng_string(self, context=None): - """Convert to engineering-type string. - - Engineering notation has an exponent which is a multiple of 3, so there - are up to 3 digits left of the decimal place. - - Same rules for when in exponential and when as a value as in __str__. - """ - return self.__str__(eng=True, context=context) - - def __neg__(self, context=None): - """Returns a copy with the sign switched. - - Rounds, if it has reason. - """ - if self._is_special: - ans = self._check_nans(context=context) - if ans: - return ans - - if context is None: - context = getcontext() - - if not self and context.rounding != ROUND_FLOOR: - # -Decimal('0') is Decimal('0'), not Decimal('-0'), except - # in ROUND_FLOOR rounding mode. - ans = self.copy_abs() - else: - ans = self.copy_negate() - - return ans._fix(context) - - def __pos__(self, context=None): - """Returns a copy, unless it is a sNaN. - - Rounds the number (if more then precision digits) - """ - if self._is_special: - ans = self._check_nans(context=context) - if ans: - return ans - - if context is None: - context = getcontext() - - if not self and context.rounding != ROUND_FLOOR: - # + (-0) = 0, except in ROUND_FLOOR rounding mode. - ans = self.copy_abs() - else: - ans = Decimal(self) - - return ans._fix(context) - - def __abs__(self, round=True, context=None): - """Returns the absolute value of self. - - If the keyword argument 'round' is false, do not round. The - expression self.__abs__(round=False) is equivalent to - self.copy_abs(). - """ - if not round: - return self.copy_abs() - - if self._is_special: - ans = self._check_nans(context=context) - if ans: - return ans - - if self._sign: - ans = self.__neg__(context=context) - else: - ans = self.__pos__(context=context) - - return ans - - def __add__(self, other, context=None): - """Returns self + other. - - -INF + INF (or the reverse) cause InvalidOperation errors. - """ - other = _convert_other(other) - if other is NotImplemented: - return other - - if context is None: - context = getcontext() - - if self._is_special or other._is_special: - ans = self._check_nans(other, context) - if ans: - return ans - - if self._isinfinity(): - # If both INF, same sign => same as both, opposite => error. - if self._sign != other._sign and other._isinfinity(): - return context._raise_error(InvalidOperation, '-INF + INF') - return Decimal(self) - if other._isinfinity(): - return Decimal(other) # Can't both be infinity here - - exp = min(self._exp, other._exp) - negativezero = 0 - if context.rounding == ROUND_FLOOR and self._sign != other._sign: - # If the answer is 0, the sign should be negative, in this case. - negativezero = 1 - - if not self and not other: - sign = min(self._sign, other._sign) - if negativezero: - sign = 1 - ans = _dec_from_triple(sign, '0', exp) - ans = ans._fix(context) - return ans - if not self: - exp = max(exp, other._exp - context.prec-1) - ans = other._rescale(exp, context.rounding) - ans = ans._fix(context) - return ans - if not other: - exp = max(exp, self._exp - context.prec-1) - ans = self._rescale(exp, context.rounding) - ans = ans._fix(context) - return ans - - op1 = _WorkRep(self) - op2 = _WorkRep(other) - op1, op2 = _normalize(op1, op2, context.prec) - - result = _WorkRep() - if op1.sign != op2.sign: - # Equal and opposite - if op1.int == op2.int: - ans = _dec_from_triple(negativezero, '0', exp) - ans = ans._fix(context) - return ans - if op1.int < op2.int: - op1, op2 = op2, op1 - # OK, now abs(op1) > abs(op2) - if op1.sign == 1: - result.sign = 1 - op1.sign, op2.sign = op2.sign, op1.sign - else: - result.sign = 0 - # So we know the sign, and op1 > 0. - elif op1.sign == 1: - result.sign = 1 - op1.sign, op2.sign = (0, 0) - else: - result.sign = 0 - # Now, op1 > abs(op2) > 0 - - if op2.sign == 0: - result.int = op1.int + op2.int - else: - result.int = op1.int - op2.int - - result.exp = op1.exp - ans = Decimal(result) - ans = ans._fix(context) - return ans - - __radd__ = __add__ - - def __sub__(self, other, context=None): - """Return self - other""" - other = _convert_other(other) - if other is NotImplemented: - return other - - if self._is_special or other._is_special: - ans = self._check_nans(other, context=context) - if ans: - return ans - - # self - other is computed as self + other.copy_negate() - return self.__add__(other.copy_negate(), context=context) - - def __rsub__(self, other, context=None): - """Return other - self""" - other = _convert_other(other) - if other is NotImplemented: - return other - - return other.__sub__(self, context=context) - - def __mul__(self, other, context=None): - """Return self * other. - - (+-) INF * 0 (or its reverse) raise InvalidOperation. - """ - other = _convert_other(other) - if other is NotImplemented: - return other - - if context is None: - context = getcontext() - - resultsign = self._sign ^ other._sign - - if self._is_special or other._is_special: - ans = self._check_nans(other, context) - if ans: - return ans - - if self._isinfinity(): - if not other: - return context._raise_error(InvalidOperation, '(+-)INF * 0') - return _SignedInfinity[resultsign] - - if other._isinfinity(): - if not self: - return context._raise_error(InvalidOperation, '0 * (+-)INF') - return _SignedInfinity[resultsign] - - resultexp = self._exp + other._exp - - # Special case for multiplying by zero - if not self or not other: - ans = _dec_from_triple(resultsign, '0', resultexp) - # Fixing in case the exponent is out of bounds - ans = ans._fix(context) - return ans - - # Special case for multiplying by power of 10 - if self._int == '1': - ans = _dec_from_triple(resultsign, other._int, resultexp) - ans = ans._fix(context) - return ans - if other._int == '1': - ans = _dec_from_triple(resultsign, self._int, resultexp) - ans = ans._fix(context) - return ans - - op1 = _WorkRep(self) - op2 = _WorkRep(other) - - ans = _dec_from_triple(resultsign, str(op1.int * op2.int), resultexp) - ans = ans._fix(context) - - return ans - __rmul__ = __mul__ - - def __truediv__(self, other, context=None): - """Return self / other.""" - other = _convert_other(other) - if other is NotImplemented: - return NotImplemented - - if context is None: - context = getcontext() - - sign = self._sign ^ other._sign - - if self._is_special or other._is_special: - ans = self._check_nans(other, context) - if ans: - return ans - - if self._isinfinity() and other._isinfinity(): - return context._raise_error(InvalidOperation, '(+-)INF/(+-)INF') - - if self._isinfinity(): - return _SignedInfinity[sign] - - if other._isinfinity(): - context._raise_error(Clamped, 'Division by infinity') - return _dec_from_triple(sign, '0', context.Etiny()) - - # Special cases for zeroes - if not other: - if not self: - return context._raise_error(DivisionUndefined, '0 / 0') - return context._raise_error(DivisionByZero, 'x / 0', sign) - - if not self: - exp = self._exp - other._exp - coeff = 0 - else: - # OK, so neither = 0, INF or NaN - shift = len(other._int) - len(self._int) + context.prec + 1 - exp = self._exp - other._exp - shift - op1 = _WorkRep(self) - op2 = _WorkRep(other) - if shift >= 0: - coeff, remainder = divmod(op1.int * 10**shift, op2.int) - else: - coeff, remainder = divmod(op1.int, op2.int * 10**-shift) - if remainder: - # result is not exact; adjust to ensure correct rounding - if coeff % 5 == 0: - coeff += 1 - else: - # result is exact; get as close to ideal exponent as possible - ideal_exp = self._exp - other._exp - while exp < ideal_exp and coeff % 10 == 0: - coeff //= 10 - exp += 1 - - ans = _dec_from_triple(sign, str(coeff), exp) - return ans._fix(context) - - def _divide(self, other, context): - """Return (self // other, self % other), to context.prec precision. - - Assumes that neither self nor other is a NaN, that self is not - infinite and that other is nonzero. - """ - sign = self._sign ^ other._sign - if other._isinfinity(): - ideal_exp = self._exp - else: - ideal_exp = min(self._exp, other._exp) - - expdiff = self.adjusted() - other.adjusted() - if not self or other._isinfinity() or expdiff <= -2: - return (_dec_from_triple(sign, '0', 0), - self._rescale(ideal_exp, context.rounding)) - if expdiff <= context.prec: - op1 = _WorkRep(self) - op2 = _WorkRep(other) - if op1.exp >= op2.exp: - op1.int *= 10**(op1.exp - op2.exp) - else: - op2.int *= 10**(op2.exp - op1.exp) - q, r = divmod(op1.int, op2.int) - if q < 10**context.prec: - return (_dec_from_triple(sign, str(q), 0), - _dec_from_triple(self._sign, str(r), ideal_exp)) - - # Here the quotient is too large to be representable - ans = context._raise_error(DivisionImpossible, - 'quotient too large in //, % or divmod') - return ans, ans - - def __rtruediv__(self, other, context=None): - """Swaps self/other and returns __truediv__.""" - other = _convert_other(other) - if other is NotImplemented: - return other - return other.__truediv__(self, context=context) - - def __divmod__(self, other, context=None): - """ - Return (self // other, self % other) - """ - other = _convert_other(other) - if other is NotImplemented: - return other - - if context is None: - context = getcontext() - - ans = self._check_nans(other, context) - if ans: - return (ans, ans) - - sign = self._sign ^ other._sign - if self._isinfinity(): - if other._isinfinity(): - ans = context._raise_error(InvalidOperation, 'divmod(INF, INF)') - return ans, ans - else: - return (_SignedInfinity[sign], - context._raise_error(InvalidOperation, 'INF % x')) - - if not other: - if not self: - ans = context._raise_error(DivisionUndefined, 'divmod(0, 0)') - return ans, ans - else: - return (context._raise_error(DivisionByZero, 'x // 0', sign), - context._raise_error(InvalidOperation, 'x % 0')) - - quotient, remainder = self._divide(other, context) - remainder = remainder._fix(context) - return quotient, remainder - - def __rdivmod__(self, other, context=None): - """Swaps self/other and returns __divmod__.""" - other = _convert_other(other) - if other is NotImplemented: - return other - return other.__divmod__(self, context=context) - - def __mod__(self, other, context=None): - """ - self % other - """ - other = _convert_other(other) - if other is NotImplemented: - return other - - if context is None: - context = getcontext() - - ans = self._check_nans(other, context) - if ans: - return ans - - if self._isinfinity(): - return context._raise_error(InvalidOperation, 'INF % x') - elif not other: - if self: - return context._raise_error(InvalidOperation, 'x % 0') - else: - return context._raise_error(DivisionUndefined, '0 % 0') - - remainder = self._divide(other, context)[1] - remainder = remainder._fix(context) - return remainder - - def __rmod__(self, other, context=None): - """Swaps self/other and returns __mod__.""" - other = _convert_other(other) - if other is NotImplemented: - return other - return other.__mod__(self, context=context) - - def remainder_near(self, other, context=None): - """ - Remainder nearest to 0- abs(remainder-near) <= other/2 - """ - if context is None: - context = getcontext() - - other = _convert_other(other, raiseit=True) - - ans = self._check_nans(other, context) - if ans: - return ans - - # self == +/-infinity -> InvalidOperation - if self._isinfinity(): - return context._raise_error(InvalidOperation, - 'remainder_near(infinity, x)') - - # other == 0 -> either InvalidOperation or DivisionUndefined - if not other: - if self: - return context._raise_error(InvalidOperation, - 'remainder_near(x, 0)') - else: - return context._raise_error(DivisionUndefined, - 'remainder_near(0, 0)') - - # other = +/-infinity -> remainder = self - if other._isinfinity(): - ans = Decimal(self) - return ans._fix(context) - - # self = 0 -> remainder = self, with ideal exponent - ideal_exponent = min(self._exp, other._exp) - if not self: - ans = _dec_from_triple(self._sign, '0', ideal_exponent) - return ans._fix(context) - - # catch most cases of large or small quotient - expdiff = self.adjusted() - other.adjusted() - if expdiff >= context.prec + 1: - # expdiff >= prec+1 => abs(self/other) > 10**prec - return context._raise_error(DivisionImpossible) - if expdiff <= -2: - # expdiff <= -2 => abs(self/other) < 0.1 - ans = self._rescale(ideal_exponent, context.rounding) - return ans._fix(context) - - # adjust both arguments to have the same exponent, then divide - op1 = _WorkRep(self) - op2 = _WorkRep(other) - if op1.exp >= op2.exp: - op1.int *= 10**(op1.exp - op2.exp) - else: - op2.int *= 10**(op2.exp - op1.exp) - q, r = divmod(op1.int, op2.int) - # remainder is r*10**ideal_exponent; other is +/-op2.int * - # 10**ideal_exponent. Apply correction to ensure that - # abs(remainder) <= abs(other)/2 - if 2*r + (q&1) > op2.int: - r -= op2.int - q += 1 - - if q >= 10**context.prec: - return context._raise_error(DivisionImpossible) - - # result has same sign as self unless r is negative - sign = self._sign - if r < 0: - sign = 1-sign - r = -r - - ans = _dec_from_triple(sign, str(r), ideal_exponent) - return ans._fix(context) - - def __floordiv__(self, other, context=None): - """self // other""" - other = _convert_other(other) - if other is NotImplemented: - return other - - if context is None: - context = getcontext() - - ans = self._check_nans(other, context) - if ans: - return ans - - if self._isinfinity(): - if other._isinfinity(): - return context._raise_error(InvalidOperation, 'INF // INF') - else: - return _SignedInfinity[self._sign ^ other._sign] - - if not other: - if self: - return context._raise_error(DivisionByZero, 'x // 0', - self._sign ^ other._sign) - else: - return context._raise_error(DivisionUndefined, '0 // 0') - - return self._divide(other, context)[0] - - def __rfloordiv__(self, other, context=None): - """Swaps self/other and returns __floordiv__.""" - other = _convert_other(other) - if other is NotImplemented: - return other - return other.__floordiv__(self, context=context) - - def __float__(self): - """Float representation.""" - if self._isnan(): - if self.is_snan(): - raise ValueError("Cannot convert signaling NaN to float") - s = "-nan" if self._sign else "nan" - else: - s = str(self) - return float(s) - - def __int__(self): - """Converts self to an int, truncating if necessary.""" - if self._is_special: - if self._isnan(): - raise ValueError("Cannot convert NaN to integer") - elif self._isinfinity(): - raise OverflowError("Cannot convert infinity to integer") - s = (-1)**self._sign - if self._exp >= 0: - return s*int(self._int)*10**self._exp - else: - return s*int(self._int[:self._exp] or '0') - - __trunc__ = __int__ - - def real(self): - return self - real = property(real) - - def imag(self): - return Decimal(0) - imag = property(imag) - - def conjugate(self): - return self - - def __complex__(self): - return complex(float(self)) - - def _fix_nan(self, context): - """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 - 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) - return Decimal(self) - - def _fix(self, context): - """Round if it is necessary to keep self within prec precision. - - Rounds and fixes the exponent. Does not raise on a sNaN. - - Arguments: - self - Decimal instance - context - context used. - """ - - if self._is_special: - if self._isnan(): - # decapitate payload if necessary - return self._fix_nan(context) - else: - # self is +/-Infinity; return unaltered - 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. - Etiny = context.Etiny() - Etop = context.Etop() - if not self: - 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) - return _dec_from_triple(self._sign, '0', new_exp) - else: - return Decimal(self) - - # exp_min is the smallest allowable exponent of the result, - # equal to max(self.adjusted()-context.prec+1, Etiny) - exp_min = len(self._int) + self._exp - context.prec - if exp_min > Etop: - # overflow: exp_min > Etop iff self.adjusted() > Emax - ans = context._raise_error(Overflow, 'above Emax', self._sign) - context._raise_error(Inexact) - context._raise_error(Rounded) - return ans - - self_is_subnormal = exp_min < Etiny - if self_is_subnormal: - exp_min = Etiny - - # round if self has too many digits - if self._exp < exp_min: - digits = len(self._int) + self._exp - exp_min - if digits < 0: - self = _dec_from_triple(self._sign, '1', exp_min-1) - digits = 0 - rounding_method = self._pick_rounding_function[context.rounding] - changed = rounding_method(self, digits) - coeff = self._int[:digits] or '0' - if changed > 0: - coeff = str(int(coeff)+1) - if len(coeff) > context.prec: - coeff = coeff[:-1] - exp_min += 1 - - # check whether the rounding pushed the exponent out of range - if exp_min > Etop: - ans = context._raise_error(Overflow, 'above Emax', self._sign) - else: - ans = _dec_from_triple(self._sign, coeff, exp_min) - - # raise the appropriate signals, taking care to respect - # the precedence described in the specification - if changed and self_is_subnormal: - context._raise_error(Underflow) - if self_is_subnormal: - context._raise_error(Subnormal) - if changed: - context._raise_error(Inexact) - context._raise_error(Rounded) - if not ans: - # raise Clamped on underflow to 0 - context._raise_error(Clamped) - return ans - - 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: - context._raise_error(Clamped) - self_padded = self._int + '0'*(self._exp - Etop) - return _dec_from_triple(self._sign, self_padded, Etop) - - # here self was representable to begin with; return unchanged - return Decimal(self) - - # for each of the rounding functions below: - # self is a finite, nonzero Decimal - # prec is an integer satisfying 0 <= prec < len(self._int) - # - # each function returns either -1, 0, or 1, as follows: - # 1 indicates that self should be rounded up (away from zero) - # 0 indicates that self should be truncated, and that all the - # digits to be truncated are zeros (so the value is unchanged) - # -1 indicates that there are nonzero digits to be truncated - - def _round_down(self, prec): - """Also known as round-towards-0, truncate.""" - if _all_zeros(self._int, prec): - return 0 - else: - return -1 - - def _round_up(self, prec): - """Rounds away from 0.""" - return -self._round_down(prec) - - def _round_half_up(self, prec): - """Rounds 5 up (away from 0)""" - if self._int[prec] in '56789': - return 1 - elif _all_zeros(self._int, prec): - return 0 - else: - return -1 - - def _round_half_down(self, prec): - """Round 5 down""" - if _exact_half(self._int, prec): - return -1 - else: - return self._round_half_up(prec) - - def _round_half_even(self, prec): - """Round 5 to even, rest to nearest.""" - if _exact_half(self._int, prec) and \ - (prec == 0 or self._int[prec-1] in '02468'): - return -1 - else: - return self._round_half_up(prec) - - def _round_ceiling(self, prec): - """Rounds up (not away from 0 if negative.)""" - if self._sign: - return self._round_down(prec) - else: - return -self._round_down(prec) - - def _round_floor(self, prec): - """Rounds down (not towards 0 if negative)""" - if not self._sign: - return self._round_down(prec) - else: - return -self._round_down(prec) - - def _round_05up(self, prec): - """Round down unless digit prec-1 is 0 or 5.""" - if prec and self._int[prec-1] not in '05': - return self._round_down(prec) - else: - return -self._round_down(prec) - - _pick_rounding_function = dict( - ROUND_DOWN = _round_down, - ROUND_UP = _round_up, - ROUND_HALF_UP = _round_half_up, - ROUND_HALF_DOWN = _round_half_down, - ROUND_HALF_EVEN = _round_half_even, - ROUND_CEILING = _round_ceiling, - ROUND_FLOOR = _round_floor, - ROUND_05UP = _round_05up, - ) - - def __round__(self, n=None): - """Round self to the nearest integer, or to a given precision. - - If only one argument is supplied, round a finite Decimal - instance self to the nearest integer. If self is infinite or - a NaN then a Python exception is raised. If self is finite - and lies exactly halfway between two integers then it is - rounded to the integer with even last digit. - - >>> round(Decimal('123.456')) - 123 - >>> round(Decimal('-456.789')) - -457 - >>> round(Decimal('-3.0')) - -3 - >>> round(Decimal('2.5')) - 2 - >>> round(Decimal('3.5')) - 4 - >>> round(Decimal('Inf')) - Traceback (most recent call last): - ... - OverflowError: cannot round an infinity - >>> round(Decimal('NaN')) - Traceback (most recent call last): - ... - ValueError: cannot round a NaN - - If a second argument n is supplied, self is rounded to n - decimal places using the rounding mode for the current - context. - - For an integer n, round(self, -n) is exactly equivalent to - self.quantize(Decimal('1En')). - - >>> round(Decimal('123.456'), 0) - Decimal('123') - >>> round(Decimal('123.456'), 2) - Decimal('123.46') - >>> round(Decimal('123.456'), -2) - Decimal('1E+2') - >>> round(Decimal('-Infinity'), 37) - Decimal('NaN') - >>> round(Decimal('sNaN123'), 0) - Decimal('NaN123') - - """ - if n is not None: - # two-argument form: use the equivalent quantize call - if not isinstance(n, int): - raise TypeError('Second argument to round should be integral') - exp = _dec_from_triple(0, '1', -n) - return self.quantize(exp) - - # one-argument form - if self._is_special: - if self.is_nan(): - raise ValueError("cannot round a NaN") - else: - raise OverflowError("cannot round an infinity") - return int(self._rescale(0, ROUND_HALF_EVEN)) - - def __floor__(self): - """Return the floor of self, as an integer. - - For a finite Decimal instance self, return the greatest - integer n such that n <= self. If self is infinite or a NaN - then a Python exception is raised. - - """ - if self._is_special: - if self.is_nan(): - raise ValueError("cannot round a NaN") - else: - raise OverflowError("cannot round an infinity") - return int(self._rescale(0, ROUND_FLOOR)) - - def __ceil__(self): - """Return the ceiling of self, as an integer. - - For a finite Decimal instance self, return the least integer n - such that n >= self. If self is infinite or a NaN then a - Python exception is raised. - - """ - if self._is_special: - if self.is_nan(): - raise ValueError("cannot round a NaN") - else: - raise OverflowError("cannot round an infinity") - return int(self._rescale(0, ROUND_CEILING)) - - def fma(self, other, third, context=None): - """Fused multiply-add. - - Returns self*other+third with no rounding of the intermediate - product self*other. - - self and other are multiplied together, with no rounding of - the result. The third operand is then added to the result, - and a single final rounding is performed. - """ - - other = _convert_other(other, raiseit=True) - third = _convert_other(third, raiseit=True) - - # compute product; raise InvalidOperation if either operand is - # a signaling NaN or if the product is zero times infinity. - if self._is_special or other._is_special: - if context is None: - context = getcontext() - if self._exp == 'N': - return context._raise_error(InvalidOperation, 'sNaN', self) - if other._exp == 'N': - return context._raise_error(InvalidOperation, 'sNaN', other) - if self._exp == 'n': - product = self - elif other._exp == 'n': - product = other - elif self._exp == 'F': - if not other: - return context._raise_error(InvalidOperation, - 'INF * 0 in fma') - product = _SignedInfinity[self._sign ^ other._sign] - elif other._exp == 'F': - if not self: - return context._raise_error(InvalidOperation, - '0 * INF in fma') - product = _SignedInfinity[self._sign ^ other._sign] - else: - product = _dec_from_triple(self._sign ^ other._sign, - str(int(self._int) * int(other._int)), - self._exp + other._exp) - - return product.__add__(third, context) - - def _power_modulo(self, other, modulo, context=None): - """Three argument version of __pow__""" - - other = _convert_other(other) - if other is NotImplemented: - return other - modulo = _convert_other(modulo) - if modulo is NotImplemented: - return modulo - - if context is None: - context = getcontext() - - # deal with NaNs: if there are any sNaNs then first one wins, - # (i.e. behaviour for NaNs is identical to that of fma) - self_is_nan = self._isnan() - other_is_nan = other._isnan() - modulo_is_nan = modulo._isnan() - if self_is_nan or other_is_nan or modulo_is_nan: - if self_is_nan == 2: - return context._raise_error(InvalidOperation, 'sNaN', - self) - if other_is_nan == 2: - return context._raise_error(InvalidOperation, 'sNaN', - other) - if modulo_is_nan == 2: - return context._raise_error(InvalidOperation, 'sNaN', - modulo) - if self_is_nan: - return self._fix_nan(context) - if other_is_nan: - return other._fix_nan(context) - return modulo._fix_nan(context) - - # check inputs: we apply same restrictions as Python's pow() - if not (self._isinteger() and - other._isinteger() and - modulo._isinteger()): - return context._raise_error(InvalidOperation, - 'pow() 3rd argument not allowed ' - 'unless all arguments are integers') - if other < 0: - return context._raise_error(InvalidOperation, - 'pow() 2nd argument cannot be ' - 'negative when 3rd argument specified') - if not modulo: - return context._raise_error(InvalidOperation, - 'pow() 3rd argument cannot be 0') - - # additional restriction for decimal: the modulus must be less - # than 10**prec in absolute value - if modulo.adjusted() >= context.prec: - return context._raise_error(InvalidOperation, - 'insufficient precision: pow() 3rd ' - 'argument must not have more than ' - 'precision digits') - - # define 0**0 == NaN, for consistency with two-argument pow - # (even though it hurts!) - if not other and not self: - return context._raise_error(InvalidOperation, - 'at least one of pow() 1st argument ' - 'and 2nd argument must be nonzero ;' - '0**0 is not defined') - - # compute sign of result - if other._iseven(): - sign = 0 - else: - sign = self._sign - - # convert modulo to a Python integer, and self and other to - # Decimal integers (i.e. force their exponents to be >= 0) - modulo = abs(int(modulo)) - base = _WorkRep(self.to_integral_value()) - exponent = _WorkRep(other.to_integral_value()) - - # compute result using integer pow() - base = (base.int % modulo * pow(10, base.exp, modulo)) % modulo - for i in range(exponent.exp): - base = pow(base, 10, modulo) - base = pow(base, exponent.int, modulo) - - return _dec_from_triple(sign, str(base), 0) - - def _power_exact(self, other, p): - """Attempt to compute self**other exactly. - - Given Decimals self and other and an integer p, attempt to - compute an exact result for the power self**other, with p - digits of precision. Return None if self**other is not - exactly representable in p digits. - - Assumes that elimination of special cases has already been - performed: self and other must both be nonspecial; self must - be positive and not numerically equal to 1; other must be - nonzero. For efficiency, other._exp should not be too large, - so that 10**abs(other._exp) is a feasible calculation.""" - - # In the comments below, we write x for the value of self and y for the - # value of other. Write x = xc*10**xe and abs(y) = yc*10**ye, with xc - # and yc positive integers not divisible by 10. - - # The main purpose of this method is to identify the *failure* - # of x**y to be exactly representable with as little effort as - # possible. So we look for cheap and easy tests that - # eliminate the possibility of x**y being exact. Only if all - # these tests are passed do we go on to actually compute x**y. - - # Here's the main idea. Express y as a rational number m/n, with m and - # n relatively prime and n>0. Then for x**y to be exactly - # representable (at *any* precision), xc must be the nth power of a - # positive integer and xe must be divisible by n. If y is negative - # then additionally xc must be a power of either 2 or 5, hence a power - # of 2**n or 5**n. - # - # There's a limit to how small |y| can be: if y=m/n as above - # then: - # - # (1) if xc != 1 then for the result to be representable we - # need xc**(1/n) >= 2, and hence also xc**|y| >= 2. So - # if |y| <= 1/nbits(xc) then xc < 2**nbits(xc) <= - # 2**(1/|y|), hence xc**|y| < 2 and the result is not - # representable. - # - # (2) if xe != 0, |xe|*(1/n) >= 1, so |xe|*|y| >= 1. Hence if - # |y| < 1/|xe| then the result is not representable. - # - # Note that since x is not equal to 1, at least one of (1) and - # (2) must apply. Now |y| < 1/nbits(xc) iff |yc|*nbits(xc) < - # 10**-ye iff len(str(|yc|*nbits(xc)) <= -ye. - # - # There's also a limit to how large y can be, at least if it's - # positive: the normalized result will have coefficient xc**y, - # so if it's representable then xc**y < 10**p, and y < - # p/log10(xc). Hence if y*log10(xc) >= p then the result is - # not exactly representable. - - # if len(str(abs(yc*xe)) <= -ye then abs(yc*xe) < 10**-ye, - # so |y| < 1/xe and the result is not representable. - # Similarly, len(str(abs(yc)*xc_bits)) <= -ye implies |y| - # < 1/nbits(xc). - - x = _WorkRep(self) - xc, xe = x.int, x.exp - while xc % 10 == 0: - xc //= 10 - xe += 1 - - y = _WorkRep(other) - yc, ye = y.int, y.exp - while yc % 10 == 0: - yc //= 10 - ye += 1 - - # case where xc == 1: result is 10**(xe*y), with xe*y - # required to be an integer - if xc == 1: - xe *= yc - # result is now 10**(xe * 10**ye); xe * 10**ye must be integral - while xe % 10 == 0: - xe //= 10 - ye += 1 - if ye < 0: - return None - exponent = xe * 10**ye - if y.sign == 1: - exponent = -exponent - # if other is a nonnegative integer, use ideal exponent - if other._isinteger() and other._sign == 0: - ideal_exponent = self._exp*int(other) - zeros = min(exponent-ideal_exponent, p-1) - else: - zeros = 0 - return _dec_from_triple(0, '1' + '0'*zeros, exponent-zeros) - - # case where y is negative: xc must be either a power - # of 2 or a power of 5. - if y.sign == 1: - last_digit = xc % 10 - if last_digit in (2,4,6,8): - # quick test for power of 2 - if xc & -xc != xc: - return None - # now xc is a power of 2; e is its exponent - e = _nbits(xc)-1 - - # We now have: - # - # x = 2**e * 10**xe, e > 0, and y < 0. - # - # The exact result is: - # - # x**y = 5**(-e*y) * 10**(e*y + xe*y) - # - # provided that both e*y and xe*y are integers. Note that if - # 5**(-e*y) >= 10**p, then the result can't be expressed - # exactly with p digits of precision. - # - # Using the above, we can guard against large values of ye. - # 93/65 is an upper bound for log(10)/log(5), so if - # - # ye >= len(str(93*p//65)) - # - # then - # - # -e*y >= -y >= 10**ye > 93*p/65 > p*log(10)/log(5), - # - # so 5**(-e*y) >= 10**p, and the coefficient of the result - # can't be expressed in p digits. - - # emax >= largest e such that 5**e < 10**p. - emax = p*93//65 - if ye >= len(str(emax)): - return None - - # Find -e*y and -xe*y; both must be integers - e = _decimal_lshift_exact(e * yc, ye) - xe = _decimal_lshift_exact(xe * yc, ye) - if e is None or xe is None: - return None - - if e > emax: - return None - xc = 5**e - - elif last_digit == 5: - # e >= log_5(xc) if xc is a power of 5; we have - # equality all the way up to xc=5**2658 - e = _nbits(xc)*28//65 - xc, remainder = divmod(5**e, xc) - if remainder: - return None - while xc % 5 == 0: - xc //= 5 - e -= 1 - - # Guard against large values of ye, using the same logic as in - # the 'xc is a power of 2' branch. 10/3 is an upper bound for - # log(10)/log(2). - emax = p*10//3 - if ye >= len(str(emax)): - return None - - e = _decimal_lshift_exact(e * yc, ye) - xe = _decimal_lshift_exact(xe * yc, ye) - if e is None or xe is None: - return None - - if e > emax: - return None - xc = 2**e - else: - return None - - if xc >= 10**p: - return None - xe = -e-xe - return _dec_from_triple(0, str(xc), xe) - - # now y is positive; find m and n such that y = m/n - if ye >= 0: - m, n = yc*10**ye, 1 - else: - if xe != 0 and len(str(abs(yc*xe))) <= -ye: - return None - xc_bits = _nbits(xc) - if xc != 1 and len(str(abs(yc)*xc_bits)) <= -ye: - return None - m, n = yc, 10**(-ye) - while m % 2 == n % 2 == 0: - m //= 2 - n //= 2 - while m % 5 == n % 5 == 0: - m //= 5 - n //= 5 - - # compute nth root of xc*10**xe - if n > 1: - # if 1 < xc < 2**n then xc isn't an nth power - if xc != 1 and xc_bits <= n: - return None - - xe, rem = divmod(xe, n) - if rem != 0: - return None - - # compute nth root of xc using Newton's method - a = 1 << -(-_nbits(xc)//n) # initial estimate - while True: - q, r = divmod(xc, a**(n-1)) - if a <= q: - break - else: - a = (a*(n-1) + q)//n - if not (a == q and r == 0): - return None - xc = a - - # now xc*10**xe is the nth root of the original xc*10**xe - # compute mth power of xc*10**xe - - # if m > p*100//_log10_lb(xc) then m > p/log10(xc), hence xc**m > - # 10**p and the result is not representable. - if xc > 1 and m > p*100//_log10_lb(xc): - return None - xc = xc**m - xe *= m - if xc > 10**p: - return None - - # by this point the result *is* exactly representable - # adjust the exponent to get as close as possible to the ideal - # exponent, if necessary - str_xc = str(xc) - if other._isinteger() and other._sign == 0: - ideal_exponent = self._exp*int(other) - zeros = min(xe-ideal_exponent, p-len(str_xc)) - else: - zeros = 0 - return _dec_from_triple(0, str_xc+'0'*zeros, xe-zeros) - - def __pow__(self, other, modulo=None, context=None): - """Return self ** other [ % modulo]. - - With two arguments, compute self**other. - - With three arguments, compute (self**other) % modulo. For the - three argument form, the following restrictions on the - arguments hold: - - - all three arguments must be integral - - other must be nonnegative - - either self or other (or both) must be nonzero - - modulo must be nonzero and must have at most p digits, - where p is the context precision. - - If any of these restrictions is violated the InvalidOperation - flag is raised. - - The result of pow(self, other, modulo) is identical to the - result that would be obtained by computing (self**other) % - modulo with unbounded precision, but is computed more - efficiently. It is always exact. - """ - - if modulo is not None: - return self._power_modulo(other, modulo, context) - - other = _convert_other(other) - if other is NotImplemented: - return other - - if context is None: - context = getcontext() - - # either argument is a NaN => result is NaN - ans = self._check_nans(other, context) - if ans: - return ans - - # 0**0 = NaN (!), x**0 = 1 for nonzero x (including +/-Infinity) - if not other: - if not self: - return context._raise_error(InvalidOperation, '0 ** 0') - else: - return _One - - # result has sign 1 iff self._sign is 1 and other is an odd integer - result_sign = 0 - if self._sign == 1: - if other._isinteger(): - if not other._iseven(): - result_sign = 1 - else: - # -ve**noninteger = NaN - # (-0)**noninteger = 0**noninteger - if self: - return context._raise_error(InvalidOperation, - 'x ** y with x negative and y not an integer') - # negate self, without doing any unwanted rounding - self = self.copy_negate() - - # 0**(+ve or Inf)= 0; 0**(-ve or -Inf) = Infinity - if not self: - if other._sign == 0: - return _dec_from_triple(result_sign, '0', 0) - else: - return _SignedInfinity[result_sign] - - # Inf**(+ve or Inf) = Inf; Inf**(-ve or -Inf) = 0 - if self._isinfinity(): - if other._sign == 0: - return _SignedInfinity[result_sign] - else: - return _dec_from_triple(result_sign, '0', 0) - - # 1**other = 1, but the choice of exponent and the flags - # depend on the exponent of self, and on whether other is a - # positive integer, a negative integer, or neither - if self == _One: - if other._isinteger(): - # exp = max(self._exp*max(int(other), 0), - # 1-context.prec) but evaluating int(other) directly - # is dangerous until we know other is small (other - # could be 1e999999999) - if other._sign == 1: - multiplier = 0 - elif other > context.prec: - multiplier = context.prec - else: - multiplier = int(other) - - exp = self._exp * multiplier - if exp < 1-context.prec: - exp = 1-context.prec - context._raise_error(Rounded) - else: - context._raise_error(Inexact) - context._raise_error(Rounded) - exp = 1-context.prec - - return _dec_from_triple(result_sign, '1'+'0'*-exp, exp) - - # compute adjusted exponent of self - self_adj = self.adjusted() - - # self ** infinity is infinity if self > 1, 0 if self < 1 - # self ** -infinity is infinity if self < 1, 0 if self > 1 - if other._isinfinity(): - if (other._sign == 0) == (self_adj < 0): - return _dec_from_triple(result_sign, '0', 0) - else: - return _SignedInfinity[result_sign] - - # from here on, the result always goes through the call - # to _fix at the end of this function. - ans = None - exact = False - - # crude test to catch cases of extreme overflow/underflow. If - # log10(self)*other >= 10**bound and bound >= len(str(Emax)) - # then 10**bound >= 10**len(str(Emax)) >= Emax+1 and hence - # self**other >= 10**(Emax+1), so overflow occurs. The test - # for underflow is similar. - bound = self._log10_exp_bound() + other.adjusted() - if (self_adj >= 0) == (other._sign == 0): - # self > 1 and other +ve, or self < 1 and other -ve - # possibility of overflow - if bound >= len(str(context.Emax)): - ans = _dec_from_triple(result_sign, '1', context.Emax+1) - else: - # self > 1 and other -ve, or self < 1 and other +ve - # possibility of underflow to 0 - Etiny = context.Etiny() - if bound >= len(str(-Etiny)): - ans = _dec_from_triple(result_sign, '1', Etiny-1) - - # try for an exact result with precision +1 - if ans is None: - ans = self._power_exact(other, context.prec + 1) - if ans is not None: - if result_sign == 1: - ans = _dec_from_triple(1, ans._int, ans._exp) - exact = True - - # usual case: inexact result, x**y computed directly as exp(y*log(x)) - if ans is None: - p = context.prec - x = _WorkRep(self) - xc, xe = x.int, x.exp - y = _WorkRep(other) - yc, ye = y.int, y.exp - if y.sign == 1: - yc = -yc - - # compute correctly rounded result: start with precision +3, - # then increase precision until result is unambiguously roundable - extra = 3 - while True: - coeff, exp = _dpower(xc, xe, yc, ye, p+extra) - if coeff % (5*10**(len(str(coeff))-p-1)): - break - extra += 3 - - ans = _dec_from_triple(result_sign, str(coeff), exp) - - # unlike exp, ln and log10, the power function respects the - # rounding mode; no need to switch to ROUND_HALF_EVEN here - - # There's a difficulty here when 'other' is not an integer and - # the result is exact. In this case, the specification - # requires that the Inexact flag be raised (in spite of - # exactness), but since the result is exact _fix won't do this - # for us. (Correspondingly, the Underflow signal should also - # be raised for subnormal results.) We can't directly raise - # these signals either before or after calling _fix, since - # that would violate the precedence for signals. So we wrap - # the ._fix call in a temporary context, and reraise - # afterwards. - if exact and not other._isinteger(): - # pad with zeros up to length context.prec+1 if necessary; this - # ensures that the Rounded signal will be raised. - if len(ans._int) <= context.prec: - expdiff = context.prec + 1 - len(ans._int) - ans = _dec_from_triple(ans._sign, ans._int+'0'*expdiff, - ans._exp-expdiff) - - # create a copy of the current context, with cleared flags/traps - newcontext = context.copy() - newcontext.clear_flags() - for exception in _signals: - newcontext.traps[exception] = 0 - - # round in the new context - ans = ans._fix(newcontext) - - # raise Inexact, and if necessary, Underflow - newcontext._raise_error(Inexact) - if newcontext.flags[Subnormal]: - newcontext._raise_error(Underflow) - - # propagate signals to the original context; _fix could - # have raised any of Overflow, Underflow, Subnormal, - # Inexact, Rounded, Clamped. Overflow needs the correct - # arguments. Note that the order of the exceptions is - # important here. - if newcontext.flags[Overflow]: - context._raise_error(Overflow, 'above Emax', ans._sign) - for exception in Underflow, Subnormal, Inexact, Rounded, Clamped: - if newcontext.flags[exception]: - context._raise_error(exception) - - else: - ans = ans._fix(context) - - return ans - - def __rpow__(self, other, context=None): - """Swaps self/other and returns __pow__.""" - other = _convert_other(other) - if other is NotImplemented: - return other - return other.__pow__(self, context=context) - - def normalize(self, context=None): - """Normalize- strip trailing 0s, change anything equal to 0 to 0e0""" - - if context is None: - context = getcontext() - - if self._is_special: - ans = self._check_nans(context=context) - if ans: - return ans - - dup = self._fix(context) - if dup._isinfinity(): - return dup - - if not dup: - return _dec_from_triple(dup._sign, '0', 0) - 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: - exp += 1 - end -= 1 - return _dec_from_triple(dup._sign, dup._int[:end], exp) - - def quantize(self, exp, rounding=None, context=None, watchexp=True): - """Quantize self so its exponent is the same as that of exp. - - Similar to self._rescale(exp._exp) but with error checking. - """ - exp = _convert_other(exp, raiseit=True) - - if context is None: - context = getcontext() - if rounding is None: - rounding = context.rounding - - if self._is_special or exp._is_special: - ans = self._check_nans(exp, context) - if ans: - return ans - - if exp._isinfinity() or self._isinfinity(): - if exp._isinfinity() and self._isinfinity(): - return Decimal(self) # if both are inf, it is OK - return context._raise_error(InvalidOperation, - 'quantize with one INF') - - # if we're not watching exponents, do a simple rescale - if not watchexp: - ans = self._rescale(exp._exp, rounding) - # raise Inexact and Rounded where appropriate - if ans._exp > self._exp: - context._raise_error(Rounded) - if ans != self: - context._raise_error(Inexact) - return ans - - # exp._exp should be between Etiny and Emax - if not (context.Etiny() <= exp._exp <= context.Emax): - return context._raise_error(InvalidOperation, - 'target exponent out of bounds in quantize') - - if not self: - ans = _dec_from_triple(self._sign, '0', exp._exp) - return ans._fix(context) - - self_adjusted = self.adjusted() - if self_adjusted > context.Emax: - return context._raise_error(InvalidOperation, - 'exponent of quantize result too large for current context') - if self_adjusted - exp._exp + 1 > context.prec: - return context._raise_error(InvalidOperation, - 'quantize result has too many digits for current context') - - ans = self._rescale(exp._exp, rounding) - if ans.adjusted() > context.Emax: - return context._raise_error(InvalidOperation, - 'exponent of quantize result too large for current context') - if len(ans._int) > context.prec: - return context._raise_error(InvalidOperation, - 'quantize result has too many digits for current context') - - # raise appropriate flags - if ans and ans.adjusted() < context.Emin: - context._raise_error(Subnormal) - if ans._exp > self._exp: - if ans != self: - context._raise_error(Inexact) - context._raise_error(Rounded) - - # call to fix takes care of any necessary folddown, and - # signals Clamped if necessary - ans = ans._fix(context) - return ans - - def same_quantum(self, other, context=None): - """Return True if self and other have the same exponent; otherwise - return False. - - If either operand is a special value, the following rules are used: - * return True if both operands are infinities - * return True if both operands are NaNs - * otherwise, return False. - """ - other = _convert_other(other, raiseit=True) - if self._is_special or other._is_special: - return (self.is_nan() and other.is_nan() or - self.is_infinite() and other.is_infinite()) - return self._exp == other._exp - - def _rescale(self, exp, rounding): - """Rescale self so that the exponent is exp, either by padding with zeros - or by truncating digits, using the given rounding mode. - - Specials are returned without change. This operation is - quiet: it raises no flags, and uses no information from the - context. - - exp = exp to scale to (an integer) - rounding = rounding mode - """ - if self._is_special: - return Decimal(self) - if not self: - return _dec_from_triple(self._sign, '0', exp) - - if self._exp >= exp: - # pad answer with zeros if necessary - return _dec_from_triple(self._sign, - self._int + '0'*(self._exp - exp), exp) - - # too many digits; round and lose data. If self.adjusted() < - # exp-1, replace self by 10**(exp-1) before rounding - digits = len(self._int) + self._exp - exp - if digits < 0: - self = _dec_from_triple(self._sign, '1', exp-1) - digits = 0 - this_function = self._pick_rounding_function[rounding] - changed = this_function(self, digits) - coeff = self._int[:digits] or '0' - if changed == 1: - coeff = str(int(coeff)+1) - return _dec_from_triple(self._sign, coeff, exp) - - def _round(self, places, rounding): - """Round a nonzero, nonspecial Decimal to a fixed number of - significant figures, using the given rounding mode. - - Infinities, NaNs and zeros are returned unaltered. - - This operation is quiet: it raises no flags, and uses no - information from the context. - - """ - if places <= 0: - raise ValueError("argument should be at least 1 in _round") - if self._is_special or not self: - return Decimal(self) - ans = self._rescale(self.adjusted()+1-places, rounding) - # it can happen that the rescale alters the adjusted exponent; - # for example when rounding 99.97 to 3 significant figures. - # When this happens we end up with an extra 0 at the end of - # the number; a second rescale fixes this. - if ans.adjusted() != self.adjusted(): - ans = ans._rescale(ans.adjusted()+1-places, rounding) - return ans - - def to_integral_exact(self, rounding=None, context=None): - """Rounds to a nearby integer. - - If no rounding mode is specified, take the rounding mode from - the context. This method raises the Rounded and Inexact flags - when appropriate. - - See also: to_integral_value, which does exactly the same as - this method except that it doesn't raise Inexact or Rounded. - """ - if self._is_special: - ans = self._check_nans(context=context) - if ans: - return ans - return Decimal(self) - if self._exp >= 0: - return Decimal(self) - if not self: - return _dec_from_triple(self._sign, '0', 0) - if context is None: - context = getcontext() - if rounding is None: - rounding = context.rounding - ans = self._rescale(0, rounding) - if ans != self: - context._raise_error(Inexact) - context._raise_error(Rounded) - return ans - - def to_integral_value(self, rounding=None, context=None): - """Rounds to the nearest integer, without raising inexact, rounded.""" - if context is None: - context = getcontext() - if rounding is None: - rounding = context.rounding - if self._is_special: - ans = self._check_nans(context=context) - if ans: - return ans - return Decimal(self) - if self._exp >= 0: - return Decimal(self) - else: - return self._rescale(0, rounding) - - # the method name changed, but we provide also the old one, for compatibility - to_integral = to_integral_value - - def sqrt(self, context=None): - """Return the square root of self.""" - if context is None: - context = getcontext() - - if self._is_special: - ans = self._check_nans(context=context) - if ans: - return ans - - if self._isinfinity() and self._sign == 0: - return Decimal(self) - - if not self: - # exponent = self._exp // 2. sqrt(-0) = -0 - ans = _dec_from_triple(self._sign, '0', self._exp // 2) - return ans._fix(context) - - if self._sign == 1: - return context._raise_error(InvalidOperation, 'sqrt(-x), x > 0') - - # At this point self represents a positive number. Let p be - # the desired precision and express self in the form c*100**e - # with c a positive real number and e an integer, c and e - # being chosen so that 100**(p-1) <= c < 100**p. Then the - # (exact) square root of self is sqrt(c)*10**e, and 10**(p-1) - # <= sqrt(c) < 10**p, so the closest representable Decimal at - # precision p is n*10**e where n = round_half_even(sqrt(c)), - # the closest integer to sqrt(c) with the even integer chosen - # in the case of a tie. - # - # To ensure correct rounding in all cases, we use the - # following trick: we compute the square root to an extra - # place (precision p+1 instead of precision p), rounding down. - # Then, if the result is inexact and its last digit is 0 or 5, - # we increase the last digit to 1 or 6 respectively; if it's - # exact we leave the last digit alone. Now the final round to - # p places (or fewer in the case of underflow) will round - # correctly and raise the appropriate flags. - - # use an extra digit of precision - prec = context.prec+1 - - # write argument in the form c*100**e where e = self._exp//2 - # is the 'ideal' exponent, to be used if the square root is - # exactly representable. l is the number of 'digits' of c in - # base 100, so that 100**(l-1) <= c < 100**l. - op = _WorkRep(self) - e = op.exp >> 1 - if op.exp & 1: - c = op.int * 10 - l = (len(self._int) >> 1) + 1 - else: - c = op.int - l = len(self._int)+1 >> 1 - - # rescale so that c has exactly prec base 100 'digits' - shift = prec-l - if shift >= 0: - c *= 100**shift - exact = True - else: - c, remainder = divmod(c, 100**-shift) - exact = not remainder - e -= shift - - # find n = floor(sqrt(c)) using Newton's method - n = 10**prec - while True: - q = c//n - if n <= q: - break - else: - n = n + q >> 1 - exact = exact and n*n == c - - if exact: - # result is exact; rescale to use ideal exponent e - if shift >= 0: - # assert n % 10**shift == 0 - n //= 10**shift - else: - n *= 10**-shift - e += shift - else: - # result is not exact; fix last digit as described above - if n % 5 == 0: - n += 1 - - ans = _dec_from_triple(0, str(n), e) - - # round, and fit to current context - context = context._shallow_copy() - rounding = context._set_rounding(ROUND_HALF_EVEN) - ans = ans._fix(context) - context.rounding = rounding - - return ans - - def max(self, other, context=None): - """Returns the larger value. - - Like max(self, other) except if one is not a number, returns - NaN (and signals if one is sNaN). Also rounds. - """ - other = _convert_other(other, raiseit=True) - - if context is None: - context = getcontext() - - if self._is_special or other._is_special: - # If one operand is a quiet NaN and the other is number, then the - # number is always returned - sn = self._isnan() - on = other._isnan() - if sn or on: - if on == 1 and sn == 0: - return self._fix(context) - if sn == 1 and on == 0: - return other._fix(context) - return self._check_nans(other, context) - - c = self._cmp(other) - if c == 0: - # If both operands are finite and equal in numerical value - # then an ordering is applied: - # - # If the signs differ then max returns the operand with the - # positive sign and min returns the operand with the negative sign - # - # If the signs are the same then the exponent is used to select - # the result. This is exactly the ordering used in compare_total. - c = self.compare_total(other) - - if c == -1: - ans = other - else: - ans = self - - return ans._fix(context) - - def min(self, other, context=None): - """Returns the smaller value. - - Like min(self, other) except if one is not a number, returns - NaN (and signals if one is sNaN). Also rounds. - """ - other = _convert_other(other, raiseit=True) - - if context is None: - context = getcontext() - - if self._is_special or other._is_special: - # If one operand is a quiet NaN and the other is number, then the - # number is always returned - sn = self._isnan() - on = other._isnan() - if sn or on: - if on == 1 and sn == 0: - return self._fix(context) - if sn == 1 and on == 0: - return other._fix(context) - return self._check_nans(other, context) - - c = self._cmp(other) - if c == 0: - c = self.compare_total(other) - - if c == -1: - ans = self - else: - ans = other - - return ans._fix(context) - - def _isinteger(self): - """Returns whether self is an integer""" - if self._is_special: - return False - if self._exp >= 0: - return True - rest = self._int[self._exp:] - return rest == '0'*len(rest) - - def _iseven(self): - """Returns True if self is even. Assumes self is an integer.""" - if not self or self._exp > 0: - return True - return self._int[-1+self._exp] in '02468' - - def adjusted(self): - """Return the adjusted exponent of self""" - try: - return self._exp + len(self._int) - 1 - # If NaN or Infinity, self._exp is string - except TypeError: - return 0 - - def canonical(self): - """Returns the same Decimal object. - - As we do not have different encodings for the same number, the - received object already is in its canonical form. - """ - return self - - def compare_signal(self, other, context=None): - """Compares self to the other operand numerically. - - It's pretty much like compare(), but all NaNs signal, with signaling - NaNs taking precedence over quiet NaNs. - """ - other = _convert_other(other, raiseit = True) - ans = self._compare_check_nans(other, context) - if ans: - return ans - return self.compare(other, context=context) - - def compare_total(self, other, context=None): - """Compares self to other using the abstract representations. - - This is not like the standard compare, which use their numerical - value. Note that a total ordering is defined for all possible abstract - representations. - """ - other = _convert_other(other, raiseit=True) - - # if one is negative and the other is positive, it's easy - if self._sign and not other._sign: - return _NegativeOne - if not self._sign and other._sign: - return _One - sign = self._sign - - # let's handle both NaN types - self_nan = self._isnan() - other_nan = other._isnan() - if self_nan or other_nan: - if self_nan == other_nan: - # compare payloads as though they're integers - self_key = len(self._int), self._int - other_key = len(other._int), other._int - if self_key < other_key: - if sign: - return _One - else: - return _NegativeOne - if self_key > other_key: - if sign: - return _NegativeOne - else: - return _One - return _Zero - - if sign: - if self_nan == 1: - return _NegativeOne - if other_nan == 1: - return _One - if self_nan == 2: - return _NegativeOne - if other_nan == 2: - return _One - else: - if self_nan == 1: - return _One - if other_nan == 1: - return _NegativeOne - if self_nan == 2: - return _One - if other_nan == 2: - return _NegativeOne - - if self < other: - return _NegativeOne - if self > other: - return _One - - if self._exp < other._exp: - if sign: - return _One - else: - return _NegativeOne - if self._exp > other._exp: - if sign: - return _NegativeOne - else: - return _One - return _Zero - - - def compare_total_mag(self, other, context=None): - """Compares self to other using abstract repr., ignoring sign. - - Like compare_total, but with operand's sign ignored and assumed to be 0. - """ - other = _convert_other(other, raiseit=True) - - s = self.copy_abs() - o = other.copy_abs() - return s.compare_total(o) - - def copy_abs(self): - """Returns a copy with the sign set to 0. """ - return _dec_from_triple(0, self._int, self._exp, self._is_special) - - def copy_negate(self): - """Returns a copy with the sign inverted.""" - if self._sign: - return _dec_from_triple(0, self._int, self._exp, self._is_special) - else: - return _dec_from_triple(1, self._int, self._exp, self._is_special) - - def copy_sign(self, other, context=None): - """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) - - def exp(self, context=None): - """Returns e ** self.""" - - if context is None: - context = getcontext() - - # exp(NaN) = NaN - ans = self._check_nans(context=context) - if ans: - return ans - - # exp(-Infinity) = 0 - if self._isinfinity() == -1: - return _Zero - - # exp(0) = 1 - if not self: - return _One - - # exp(Infinity) = Infinity - if self._isinfinity() == 1: - return Decimal(self) - - # the result is now guaranteed to be inexact (the true - # mathematical result is transcendental). There's no need to - # raise Rounded and Inexact here---they'll always be raised as - # a result of the call to _fix. - p = context.prec - adj = self.adjusted() - - # we only need to do any computation for quite a small range - # of adjusted exponents---for example, -29 <= adj <= 10 for - # the default context. For smaller exponent the result is - # indistinguishable from 1 at the given precision, while for - # larger exponent the result either overflows or underflows. - if self._sign == 0 and adj > len(str((context.Emax+1)*3)): - # overflow - ans = _dec_from_triple(0, '1', context.Emax+1) - elif self._sign == 1 and adj > len(str((-context.Etiny()+1)*3)): - # underflow to 0 - ans = _dec_from_triple(0, '1', context.Etiny()-1) - elif self._sign == 0 and adj < -p: - # p+1 digits; final round will raise correct flags - ans = _dec_from_triple(0, '1' + '0'*(p-1) + '1', -p) - elif self._sign == 1 and adj < -p-1: - # p+1 digits; final round will raise correct flags - ans = _dec_from_triple(0, '9'*(p+1), -p-1) - # general case - else: - op = _WorkRep(self) - c, e = op.int, op.exp - if op.sign == 1: - c = -c - - # compute correctly rounded result: increase precision by - # 3 digits at a time until we get an unambiguously - # roundable result - extra = 3 - while True: - coeff, exp = _dexp(c, e, p+extra) - if coeff % (5*10**(len(str(coeff))-p-1)): - break - extra += 3 - - ans = _dec_from_triple(0, str(coeff), exp) - - # at this stage, ans should round correctly with *any* - # rounding mode, not just with ROUND_HALF_EVEN - context = context._shallow_copy() - rounding = context._set_rounding(ROUND_HALF_EVEN) - ans = ans._fix(context) - context.rounding = rounding - - return ans - - def is_canonical(self): - """Return True if self is canonical; otherwise return False. - - Currently, the encoding of a Decimal instance is always - canonical, so this method returns True for any Decimal. - """ - return True - - def is_finite(self): - """Return True if self is finite; otherwise return False. - - A Decimal instance is considered finite if it is neither - infinite nor a NaN. - """ - return not self._is_special - - def is_infinite(self): - """Return True if self is infinite; otherwise return False.""" - return self._exp == 'F' - - def is_nan(self): - """Return True if self is a qNaN or sNaN; otherwise return False.""" - return self._exp in ('n', 'N') - - def is_normal(self, context=None): - """Return True if self is a normal number; otherwise return False.""" - if self._is_special or not self: - return False - if context is None: - context = getcontext() - return context.Emin <= self.adjusted() - - def is_qnan(self): - """Return True if self is a quiet NaN; otherwise return False.""" - return self._exp == 'n' - - def is_signed(self): - """Return True if self is negative; otherwise return False.""" - return self._sign == 1 - - def is_snan(self): - """Return True if self is a signaling NaN; otherwise return False.""" - return self._exp == 'N' - - def is_subnormal(self, context=None): - """Return True if self is subnormal; otherwise return False.""" - if self._is_special or not self: - return False - if context is None: - context = getcontext() - return self.adjusted() < context.Emin - - def is_zero(self): - """Return True if self is a zero; otherwise return False.""" - return not self._is_special and self._int == '0' - - def _ln_exp_bound(self): - """Compute a lower bound for the adjusted exponent of self.ln(). - In other words, compute r such that self.ln() >= 10**r. Assumes - that self is finite and positive and that self != 1. - """ - - # for 0.1 <= x <= 10 we use the inequalities 1-1/x <= ln(x) <= x-1 - adj = self._exp + len(self._int) - 1 - if adj >= 1: - # argument >= 10; we use 23/10 = 2.3 as a lower bound for ln(10) - return len(str(adj*23//10)) - 1 - if adj <= -2: - # argument <= 0.1 - return len(str((-1-adj)*23//10)) - 1 - op = _WorkRep(self) - c, e = op.int, op.exp - if adj == 0: - # 1 < self < 10 - num = str(c-10**-e) - den = str(c) - return len(num) - len(den) - (num < den) - # adj == -1, 0.1 <= self < 1 - return e + len(str(10**-e - c)) - 1 - - - def ln(self, context=None): - """Returns the natural (base e) logarithm of self.""" - - if context is None: - context = getcontext() - - # ln(NaN) = NaN - ans = self._check_nans(context=context) - if ans: - return ans - - # ln(0.0) == -Infinity - if not self: - return _NegativeInfinity - - # ln(Infinity) = Infinity - if self._isinfinity() == 1: - return _Infinity - - # ln(1.0) == 0.0 - if self == _One: - return _Zero - - # ln(negative) raises InvalidOperation - if self._sign == 1: - return context._raise_error(InvalidOperation, - 'ln of a negative value') - - # result is irrational, so necessarily inexact - op = _WorkRep(self) - c, e = op.int, op.exp - p = context.prec - - # correctly rounded result: repeatedly increase precision by 3 - # until we get an unambiguously roundable result - places = p - self._ln_exp_bound() + 2 # at least p+3 places - while True: - coeff = _dlog(c, e, places) - # assert len(str(abs(coeff)))-p >= 1 - if coeff % (5*10**(len(str(abs(coeff)))-p-1)): - break - places += 3 - ans = _dec_from_triple(int(coeff<0), str(abs(coeff)), -places) - - context = context._shallow_copy() - rounding = context._set_rounding(ROUND_HALF_EVEN) - ans = ans._fix(context) - context.rounding = rounding - return ans - - def _log10_exp_bound(self): - """Compute a lower bound for the adjusted exponent of self.log10(). - In other words, find r such that self.log10() >= 10**r. - Assumes that self is finite and positive and that self != 1. - """ - - # For x >= 10 or x < 0.1 we only need a bound on the integer - # part of log10(self), and this comes directly from the - # exponent of x. For 0.1 <= x <= 10 we use the inequalities - # 1-1/x <= log(x) <= x-1. If x > 1 we have |log10(x)| > - # (1-1/x)/2.31 > 0. If x < 1 then |log10(x)| > (1-x)/2.31 > 0 - - adj = self._exp + len(self._int) - 1 - if adj >= 1: - # self >= 10 - return len(str(adj))-1 - if adj <= -2: - # self < 0.1 - return len(str(-1-adj))-1 - op = _WorkRep(self) - c, e = op.int, op.exp - if adj == 0: - # 1 < self < 10 - num = str(c-10**-e) - den = str(231*c) - return len(num) - len(den) - (num < den) + 2 - # adj == -1, 0.1 <= self < 1 - num = str(10**-e-c) - return len(num) + e - (num < "231") - 1 - - def log10(self, context=None): - """Returns the base 10 logarithm of self.""" - - if context is None: - context = getcontext() - - # log10(NaN) = NaN - ans = self._check_nans(context=context) - if ans: - return ans - - # log10(0.0) == -Infinity - if not self: - return _NegativeInfinity - - # log10(Infinity) = Infinity - if self._isinfinity() == 1: - return _Infinity - - # log10(negative or -Infinity) raises InvalidOperation - if self._sign == 1: - return context._raise_error(InvalidOperation, - 'log10 of a negative value') - - # log10(10**n) = n - if self._int[0] == '1' and self._int[1:] == '0'*(len(self._int) - 1): - # answer may need rounding - ans = Decimal(self._exp + len(self._int) - 1) - else: - # result is irrational, so necessarily inexact - op = _WorkRep(self) - c, e = op.int, op.exp - p = context.prec - - # correctly rounded result: repeatedly increase precision - # until result is unambiguously roundable - places = p-self._log10_exp_bound()+2 - while True: - coeff = _dlog10(c, e, places) - # assert len(str(abs(coeff)))-p >= 1 - if coeff % (5*10**(len(str(abs(coeff)))-p-1)): - break - places += 3 - ans = _dec_from_triple(int(coeff<0), str(abs(coeff)), -places) - - context = context._shallow_copy() - rounding = context._set_rounding(ROUND_HALF_EVEN) - ans = ans._fix(context) - context.rounding = rounding - return ans - - def logb(self, context=None): - """ Returns the exponent of the magnitude of self's MSD. - - The result is the integer which is the exponent of the magnitude - of the most significant digit of self (as though it were truncated - to a single digit while maintaining the value of that digit and - without limiting the resulting exponent). - """ - # logb(NaN) = NaN - ans = self._check_nans(context=context) - if ans: - return ans - - if context is None: - context = getcontext() - - # logb(+/-Inf) = +Inf - if self._isinfinity(): - return _Infinity - - # logb(0) = -Inf, DivisionByZero - if not self: - return context._raise_error(DivisionByZero, 'logb(0)', 1) - - # otherwise, simply return the adjusted exponent of self, as a - # Decimal. Note that no attempt is made to fit the result - # into the current context. - ans = Decimal(self.adjusted()) - return ans._fix(context) - - def _islogical(self): - """Return True if self is a logical operand. - - For being logical, it must be a finite number with a sign of 0, - an exponent of 0, and a coefficient whose digits must all be - either 0 or 1. - """ - if self._sign != 0 or self._exp != 0: - return False - for dig in self._int: - if dig not in '01': - return False - return True - - def _fill_logical(self, context, opa, opb): - dif = context.prec - len(opa) - if dif > 0: - opa = '0'*dif + opa - elif dif < 0: - opa = opa[-context.prec:] - dif = context.prec - len(opb) - if dif > 0: - opb = '0'*dif + opb - elif dif < 0: - opb = opb[-context.prec:] - return opa, opb - - def logical_and(self, other, context=None): - """Applies an 'and' operation between self and other's digits.""" - if context is None: - context = getcontext() - - other = _convert_other(other, raiseit=True) - - if not self._islogical() or not other._islogical(): - return context._raise_error(InvalidOperation) - - # fill to context.prec - (opa, opb) = self._fill_logical(context, self._int, other._int) - - # make the operation, and clean starting zeroes - result = "".join([str(int(a)&int(b)) for a,b in zip(opa,opb)]) - return _dec_from_triple(0, result.lstrip('0') or '0', 0) - - def logical_invert(self, context=None): - """Invert all its digits.""" - if context is None: - context = getcontext() - return self.logical_xor(_dec_from_triple(0,'1'*context.prec,0), - context) - - def logical_or(self, other, context=None): - """Applies an 'or' operation between self and other's digits.""" - if context is None: - context = getcontext() - - other = _convert_other(other, raiseit=True) - - if not self._islogical() or not other._islogical(): - return context._raise_error(InvalidOperation) - - # fill to context.prec - (opa, opb) = self._fill_logical(context, self._int, other._int) - - # make the operation, and clean starting zeroes - result = "".join([str(int(a)|int(b)) for a,b in zip(opa,opb)]) - return _dec_from_triple(0, result.lstrip('0') or '0', 0) - - def logical_xor(self, other, context=None): - """Applies an 'xor' operation between self and other's digits.""" - if context is None: - context = getcontext() - - other = _convert_other(other, raiseit=True) - - if not self._islogical() or not other._islogical(): - return context._raise_error(InvalidOperation) - - # fill to context.prec - (opa, opb) = self._fill_logical(context, self._int, other._int) - - # make the operation, and clean starting zeroes - result = "".join([str(int(a)^int(b)) for a,b in zip(opa,opb)]) - return _dec_from_triple(0, result.lstrip('0') or '0', 0) - - def max_mag(self, other, context=None): - """Compares the values numerically with their sign ignored.""" - other = _convert_other(other, raiseit=True) - - if context is None: - context = getcontext() - - if self._is_special or other._is_special: - # If one operand is a quiet NaN and the other is number, then the - # number is always returned - sn = self._isnan() - on = other._isnan() - if sn or on: - if on == 1 and sn == 0: - return self._fix(context) - if sn == 1 and on == 0: - return other._fix(context) - return self._check_nans(other, context) - - c = self.copy_abs()._cmp(other.copy_abs()) - if c == 0: - c = self.compare_total(other) - - if c == -1: - ans = other - else: - ans = self - - return ans._fix(context) - - def min_mag(self, other, context=None): - """Compares the values numerically with their sign ignored.""" - other = _convert_other(other, raiseit=True) - - if context is None: - context = getcontext() - - if self._is_special or other._is_special: - # If one operand is a quiet NaN and the other is number, then the - # number is always returned - sn = self._isnan() - on = other._isnan() - if sn or on: - if on == 1 and sn == 0: - return self._fix(context) - if sn == 1 and on == 0: - return other._fix(context) - return self._check_nans(other, context) - - c = self.copy_abs()._cmp(other.copy_abs()) - if c == 0: - c = self.compare_total(other) - - if c == -1: - ans = self - else: - ans = other - - return ans._fix(context) - - def next_minus(self, context=None): - """Returns the largest representable number smaller than itself.""" - if context is None: - context = getcontext() - - ans = self._check_nans(context=context) - if ans: - return ans - - if self._isinfinity() == -1: - return _NegativeInfinity - if self._isinfinity() == 1: - return _dec_from_triple(0, '9'*context.prec, context.Etop()) - - context = context.copy() - context._set_rounding(ROUND_FLOOR) - context._ignore_all_flags() - new_self = self._fix(context) - if new_self != self: - return new_self - return self.__sub__(_dec_from_triple(0, '1', context.Etiny()-1), - context) - - def next_plus(self, context=None): - """Returns the smallest representable number larger than itself.""" - if context is None: - context = getcontext() - - ans = self._check_nans(context=context) - if ans: - return ans - - if self._isinfinity() == 1: - return _Infinity - if self._isinfinity() == -1: - return _dec_from_triple(1, '9'*context.prec, context.Etop()) - - context = context.copy() - context._set_rounding(ROUND_CEILING) - context._ignore_all_flags() - new_self = self._fix(context) - if new_self != self: - return new_self - return self.__add__(_dec_from_triple(0, '1', context.Etiny()-1), - context) - - def next_toward(self, other, context=None): - """Returns the number closest to self, in the direction towards other. - - The result is the closest representable number to self - (excluding self) that is in the direction towards other, - unless both have the same value. If the two operands are - numerically equal, then the result is a copy of self with the - sign set to be the same as the sign of other. - """ - other = _convert_other(other, raiseit=True) - - if context is None: - context = getcontext() - - ans = self._check_nans(other, context) - if ans: - return ans - - comparison = self._cmp(other) - if comparison == 0: - return self.copy_sign(other) - - if comparison == -1: - ans = self.next_plus(context) - else: # comparison == 1 - ans = self.next_minus(context) - - # decide which flags to raise using value of ans - if ans._isinfinity(): - context._raise_error(Overflow, - 'Infinite result from next_toward', - ans._sign) - context._raise_error(Inexact) - context._raise_error(Rounded) - elif ans.adjusted() < context.Emin: - context._raise_error(Underflow) - context._raise_error(Subnormal) - context._raise_error(Inexact) - context._raise_error(Rounded) - # if precision == 1 then we don't raise Clamped for a - # result 0E-Etiny. - if not ans: - context._raise_error(Clamped) - - return ans - - def number_class(self, context=None): - """Returns an indication of the class of self. - - The class is one of the following strings: - sNaN - NaN - -Infinity - -Normal - -Subnormal - -Zero - +Zero - +Subnormal - +Normal - +Infinity - """ - if self.is_snan(): - return "sNaN" - if self.is_qnan(): - return "NaN" - inf = self._isinfinity() - if inf == 1: - return "+Infinity" - if inf == -1: - return "-Infinity" - if self.is_zero(): - if self._sign: - return "-Zero" - else: - return "+Zero" - if context is None: - context = getcontext() - if self.is_subnormal(context=context): - if self._sign: - return "-Subnormal" - else: - return "+Subnormal" - # just a normal, regular, boring number, :) - if self._sign: - return "-Normal" - else: - return "+Normal" - - def radix(self): - """Just returns 10, as this is Decimal, :)""" - return Decimal(10) - - def rotate(self, other, context=None): - """Returns a rotated copy of self, value-of-other times.""" - if context is None: - context = getcontext() - - other = _convert_other(other, raiseit=True) - - ans = self._check_nans(other, context) - if ans: - return ans - - if other._exp != 0: - return context._raise_error(InvalidOperation) - if not (-context.prec <= int(other) <= context.prec): - return context._raise_error(InvalidOperation) - - if self._isinfinity(): - return Decimal(self) - - # get values, pad if necessary - torot = int(other) - rotdig = self._int - topad = context.prec - len(rotdig) - if topad > 0: - rotdig = '0'*topad + rotdig - elif topad < 0: - rotdig = rotdig[-topad:] - - # let's rotate! - rotated = rotdig[torot:] + rotdig[:torot] - return _dec_from_triple(self._sign, - rotated.lstrip('0') or '0', self._exp) - - def scaleb(self, other, context=None): - """Returns self operand after adding the second value to its exp.""" - if context is None: - context = getcontext() - - other = _convert_other(other, raiseit=True) - - ans = self._check_nans(other, context) - if ans: - return ans - - if other._exp != 0: - return context._raise_error(InvalidOperation) - liminf = -2 * (context.Emax + context.prec) - limsup = 2 * (context.Emax + context.prec) - if not (liminf <= int(other) <= limsup): - return context._raise_error(InvalidOperation) - - if self._isinfinity(): - return Decimal(self) - - d = _dec_from_triple(self._sign, self._int, self._exp + int(other)) - d = d._fix(context) - return d - - def shift(self, other, context=None): - """Returns a shifted copy of self, value-of-other times.""" - if context is None: - context = getcontext() - - other = _convert_other(other, raiseit=True) - - ans = self._check_nans(other, context) - if ans: - return ans - - if other._exp != 0: - return context._raise_error(InvalidOperation) - if not (-context.prec <= int(other) <= context.prec): - return context._raise_error(InvalidOperation) - - if self._isinfinity(): - return Decimal(self) - - # get values, pad if necessary - torot = int(other) - rotdig = self._int - topad = context.prec - len(rotdig) - if topad > 0: - rotdig = '0'*topad + rotdig - elif topad < 0: - rotdig = rotdig[-topad:] - - # let's shift! - if torot < 0: - shifted = rotdig[:torot] - else: - shifted = rotdig + '0'*torot - shifted = shifted[-context.prec:] - - return _dec_from_triple(self._sign, - shifted.lstrip('0') or '0', self._exp) - - # Support for pickling, copy, and deepcopy - def __reduce__(self): - return (self.__class__, (str(self),)) - - def __copy__(self): - 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) is Decimal: - return self # My components are also immutable - return self.__class__(str(self)) - - # PEP 3101 support. the _localeconv keyword argument should be - # considered private: it's provided for ease of testing only. - def __format__(self, specifier, context=None, _localeconv=None): - """Format a Decimal instance according to the given specifier. - - The specifier should be a standard format specifier, with the - form described in PEP 3101. Formatting types 'e', 'E', 'f', - 'F', 'g', 'G', 'n' and '%' are supported. If the formatting - type is omitted it defaults to 'g' or 'G', depending on the - value of context.capitals. - """ - - # Note: PEP 3101 says that if the type is not present then - # there should be at least one digit after the decimal point. - # We take the liberty of ignoring this requirement for - # Decimal---it's presumably there to make sure that - # format(float, '') behaves similarly to str(float). - if context is None: - context = getcontext() - - spec = _parse_format_specifier(specifier, _localeconv=_localeconv) - - # special values don't care about the type or precision - if self._is_special: - sign = _format_sign(self._sign, spec) - body = str(self.copy_abs()) - if spec['type'] == '%': - body += '%' - return _format_align(sign, body, spec) - - # a type of None defaults to 'g' or 'G', depending on context - if spec['type'] is None: - spec['type'] = ['g', 'G'][context.capitals] - - # if type is '%', adjust exponent of self accordingly - if spec['type'] == '%': - self = _dec_from_triple(self._sign, self._int, self._exp+2) - - # round if necessary, taking rounding mode from the context - rounding = context.rounding - precision = spec['precision'] - if precision is not None: - if spec['type'] in 'eE': - self = self._round(precision+1, rounding) - elif spec['type'] in 'fF%': - self = self._rescale(-precision, rounding) - elif spec['type'] in 'gG' and len(self._int) > precision: - self = self._round(precision, rounding) - # special case: zeros with a positive exponent can't be - # represented in fixed point; rescale them to 0e0. - if not self and self._exp > 0 and spec['type'] in 'fF%': - self = self._rescale(0, rounding) - - # figure out placement of the decimal point - leftdigits = self._exp + len(self._int) - if spec['type'] in 'eE': - if not self and precision is not None: - dotplace = 1 - precision - else: - dotplace = 1 - elif spec['type'] in 'fF%': - dotplace = leftdigits - elif spec['type'] in 'gG': - if self._exp <= 0 and leftdigits > -6: - dotplace = leftdigits - else: - dotplace = 1 - - # find digits before and after decimal point, and get exponent - if dotplace < 0: - intpart = '0' - fracpart = '0'*(-dotplace) + self._int - elif dotplace > len(self._int): - intpart = self._int + '0'*(dotplace-len(self._int)) - fracpart = '' - else: - intpart = self._int[:dotplace] or '0' - fracpart = self._int[dotplace:] - exp = leftdigits-dotplace - - # done with the decimal-specific stuff; hand over the rest - # of the formatting to the _format_number function - return _format_number(self._sign, intpart, fracpart, exp, spec) - -def _dec_from_triple(sign, coefficient, exponent, special=False): - """Create a decimal instance directly, without any validation, - normalization (e.g. removal of leading zeros) or argument - conversion. - - This function is for *internal use only*. - """ - - self = object.__new__(Decimal) - self._sign = sign - self._int = coefficient - self._exp = exponent - self._is_special = special - - return self - -# Register Decimal as a kind of Number (an abstract base class). -# However, do not register it as Real (because Decimals are not -# interoperable with floats). -_numbers.Number.register(Decimal) - - -##### Context class ####################################################### - -class _ContextManager(object): - """Context manager class to support localcontext(). - - Sets a copy of the supplied context in __enter__() and restores - the previous decimal context in __exit__() - """ - def __init__(self, new_context): - self.new_context = new_context.copy() - def __enter__(self): - self.saved_context = getcontext() - setcontext(self.new_context) - return self.new_context - def __exit__(self, t, v, tb): - setcontext(self.saved_context) - -class Context(object): - """Contains the context for a Decimal instance. - - Contains: - prec - precision (for use in rounding, division, square roots..) - rounding - rounding type (how you round) - traps - If traps[exception] = 1, then the exception is - raised when it is caused. Otherwise, a value is - substituted in. - flags - When an exception is caused, flags[exception] is set. - (Whether or not the trap_enabler is set) - Should be reset by user of Decimal instance. - Emin - Minimum exponent - 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) - """ - - def __init__(self, prec=None, rounding=None, Emin=None, Emax=None, - capitals=None, clamp=None, flags=None, traps=None, - _ignored_flags=None): - # Set defaults; for everything except flags and _ignored_flags, - # inherit from DefaultContext. - try: - dc = DefaultContext - except NameError: - pass - - self.prec = prec if prec is not None else dc.prec - self.rounding = rounding if rounding is not None else dc.rounding - 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 - - if _ignored_flags is None: - self._ignored_flags = [] - else: - self._ignored_flags = _ignored_flags - - if traps is None: - self.traps = dc.traps.copy() - elif not isinstance(traps, dict): - self.traps = dict((s, int(s in traps)) for s in _signals + traps) - else: - self.traps = traps - - if flags is None: - self.flags = dict.fromkeys(_signals, 0) - elif not isinstance(flags, dict): - self.flags = dict((s, int(s in flags)) for s in _signals + flags) - else: - self.flags = flags - - def _set_integer_check(self, name, value, vmin, vmax): - if not isinstance(value, int): - raise TypeError("%s must be an integer" % name) - if vmin == '-inf': - if value > vmax: - raise ValueError("%s must be in [%s, %d]. got: %s" % (name, vmin, vmax, value)) - elif vmax == 'inf': - if value < vmin: - raise ValueError("%s must be in [%d, %s]. got: %s" % (name, vmin, vmax, value)) - else: - if value < vmin or value > vmax: - raise ValueError("%s must be in [%d, %d]. got %s" % (name, vmin, vmax, value)) - return object.__setattr__(self, name, value) - - def _set_signal_dict(self, name, d): - if not isinstance(d, dict): - raise TypeError("%s must be a signal dict" % d) - for key in d: - if not key in _signals: - raise KeyError("%s is not a valid signal dict" % d) - for key in _signals: - if not key in d: - raise KeyError("%s is not a valid signal dict" % d) - return object.__setattr__(self, name, d) - - def __setattr__(self, name, value): - if name == 'prec': - return self._set_integer_check(name, value, 1, 'inf') - elif name == 'Emin': - return self._set_integer_check(name, value, '-inf', 0) - elif name == 'Emax': - return self._set_integer_check(name, value, 0, 'inf') - elif name == 'capitals': - return self._set_integer_check(name, value, 0, 1) - elif name == 'clamp': - return self._set_integer_check(name, value, 0, 1) - elif name == 'rounding': - if not value in _rounding_modes: - # raise TypeError even for strings to have consistency - # among various implementations. - raise TypeError("%s: invalid rounding mode" % value) - return object.__setattr__(self, name, value) - elif name == 'flags' or name == 'traps': - return self._set_signal_dict(name, value) - elif name == '_ignored_flags': - return object.__setattr__(self, name, value) - else: - raise AttributeError( - "'decimal.Context' object has no attribute '%s'" % name) - - def __delattr__(self, name): - raise AttributeError("%s cannot be deleted" % name) - - # Support for pickling, copy, and deepcopy - def __reduce__(self): - flags = [sig for sig, v in self.flags.items() if v] - traps = [sig for sig, v in self.traps.items() if v] - return (self.__class__, - (self.prec, self.rounding, self.Emin, self.Emax, - self.capitals, self.clamp, flags, traps)) - - def __repr__(self): - """Show the current context.""" - s = [] - s.append('Context(prec=%(prec)d, rounding=%(rounding)s, ' - '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) + ']') - names = [t.__name__ for t, v in self.traps.items() if v] - s.append('traps=[' + ', '.join(names) + ']') - return ', '.join(s) + ')' - - def clear_flags(self): - """Reset all flags to zero""" - for flag in self.flags: - self.flags[flag] = 0 - - def clear_traps(self): - """Reset all traps to zero""" - for flag in self.traps: - self.traps[flag] = 0 - - def _shallow_copy(self): - """Returns a shallow copy from self.""" - nc = Context(self.prec, self.rounding, self.Emin, self.Emax, - self.capitals, self.clamp, self.flags, self.traps, - self._ignored_flags) - return nc - - def copy(self): - """Returns a deep copy from self.""" - nc = Context(self.prec, self.rounding, self.Emin, self.Emax, - self.capitals, self.clamp, - self.flags.copy(), self.traps.copy(), - self._ignored_flags) - return nc - __copy__ = copy - - def _raise_error(self, condition, explanation = None, *args): - """Handles an error - - If the flag is in _ignored_flags, returns the default response. - Otherwise, it sets the flag, then, if the corresponding - trap_enabler is set, it reraises the exception. Otherwise, it returns - the default value after setting the flag. - """ - error = _condition_map.get(condition, condition) - if error in self._ignored_flags: - # Don't touch the flag - return error().handle(self, *args) - - self.flags[error] = 1 - if not self.traps[error]: - # The errors define how to handle themselves. - return condition().handle(self, *args) - - # Errors should only be risked on copies of the context - # self._ignored_flags = [] - raise error(explanation) - - def _ignore_all_flags(self): - """Ignore all flags, if they are raised""" - return self._ignore_flags(*_signals) - - def _ignore_flags(self, *flags): - """Ignore the flags, if they are raised""" - # Do not mutate-- This way, copies of a context leave the original - # alone. - self._ignored_flags = (self._ignored_flags + list(flags)) - return list(flags) - - def _regard_flags(self, *flags): - """Stop ignoring the flags, if they are raised""" - if flags and isinstance(flags[0], (tuple,list)): - flags = flags[0] - for flag in flags: - self._ignored_flags.remove(flag) - - # We inherit object.__hash__, so we must deny this explicitly - __hash__ = None - - def Etiny(self): - """Returns Etiny (= Emin - prec + 1)""" - return int(self.Emin - self.prec + 1) - - def Etop(self): - """Returns maximum exponent (= Emax - prec + 1)""" - return int(self.Emax - self.prec + 1) - - def _set_rounding(self, type): - """Sets the rounding type. - - Sets the rounding type, and returns the current (previous) - rounding type. Often used like: - - context = context.copy() - # so you don't change the calling context - # if an error occurs in the middle. - rounding = context._set_rounding(ROUND_UP) - val = self.__sub__(other, context=context) - context._set_rounding(rounding) - - This will make it round up for that operation. - """ - rounding = self.rounding - self.rounding= type - return rounding - - def create_decimal(self, num='0'): - """Creates a new Decimal instance but using self as context. - - This method implements the to-number operation of the - IBM Decimal specification.""" - - if isinstance(num, str) and num != num.strip(): - return self._raise_error(ConversionSyntax, - "no trailing or leading whitespace is " - "permitted.") - - d = Decimal(num, context=self) - 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) - - def create_decimal_from_float(self, f): - """Creates a new Decimal instance from a float but rounding using self - as the context. - - >>> context = Context(prec=5, rounding=ROUND_DOWN) - >>> context.create_decimal_from_float(3.1415926535897932) - Decimal('3.1415') - >>> context = Context(prec=5, traps=[Inexact]) - >>> context.create_decimal_from_float(3.1415926535897932) - Traceback (most recent call last): - ... - decimal.Inexact: None - - """ - d = Decimal.from_float(f) # An exact conversion - return d._fix(self) # Apply the context rounding - - # Methods - def abs(self, a): - """Returns the absolute value of the operand. - - If the operand is negative, the result is the same as using the minus - operation on the operand. Otherwise, the result is the same as using - the plus operation on the operand. - - >>> ExtendedContext.abs(Decimal('2.1')) - Decimal('2.1') - >>> ExtendedContext.abs(Decimal('-100')) - Decimal('100') - >>> ExtendedContext.abs(Decimal('101.5')) - 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): - """Return the sum of the two operands. - - >>> ExtendedContext.add(Decimal('12'), Decimal('7.00')) - 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') - """ - 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)) - - def canonical(self, a): - """Returns the same Decimal object. - - As we do not have different encodings for the same number, the - received object already is in its canonical form. - - >>> ExtendedContext.canonical(Decimal('2.50')) - Decimal('2.50') - """ - if not isinstance(a, Decimal): - raise TypeError("canonical requires a Decimal as an argument.") - return a.canonical() - - def compare(self, a, b): - """Compares values numerically. - - If the signs of the operands differ, a value representing each operand - ('-1' if the operand is less than zero, '0' if the operand is zero or - negative zero, or '1' if the operand is greater than zero) is used in - place of that operand for the comparison instead of the actual - operand. - - The comparison is then effected by subtracting the second operand from - the first and then returning a value according to the result of the - subtraction: '-1' if the result is less than zero, '0' if the result is - zero or negative zero, or '1' if the result is greater than zero. - - >>> ExtendedContext.compare(Decimal('2.1'), Decimal('3')) - Decimal('-1') - >>> ExtendedContext.compare(Decimal('2.1'), Decimal('2.1')) - Decimal('0') - >>> ExtendedContext.compare(Decimal('2.1'), Decimal('2.10')) - Decimal('0') - >>> ExtendedContext.compare(Decimal('3'), Decimal('2.1')) - Decimal('1') - >>> ExtendedContext.compare(Decimal('2.1'), Decimal('-3')) - 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): - """Compares the values of the two operands numerically. - - It's pretty much like compare(), but all NaNs signal, with signaling - NaNs taking precedence over quiet NaNs. - - >>> c = ExtendedContext - >>> c.compare_signal(Decimal('2.1'), Decimal('3')) - Decimal('-1') - >>> c.compare_signal(Decimal('2.1'), Decimal('2.1')) - Decimal('0') - >>> c.flags[InvalidOperation] = 0 - >>> print(c.flags[InvalidOperation]) - 0 - >>> c.compare_signal(Decimal('NaN'), Decimal('2.1')) - Decimal('NaN') - >>> print(c.flags[InvalidOperation]) - 1 - >>> c.flags[InvalidOperation] = 0 - >>> print(c.flags[InvalidOperation]) - 0 - >>> c.compare_signal(Decimal('sNaN'), Decimal('2.1')) - 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): - """Compares two operands using their abstract representation. - - This is not like the standard compare, which use their numerical - value. Note that a total ordering is defined for all possible abstract - representations. - - >>> ExtendedContext.compare_total(Decimal('12.73'), Decimal('127.9')) - Decimal('-1') - >>> ExtendedContext.compare_total(Decimal('-127'), Decimal('12')) - Decimal('-1') - >>> ExtendedContext.compare_total(Decimal('12.30'), Decimal('12.3')) - Decimal('-1') - >>> ExtendedContext.compare_total(Decimal('12.30'), Decimal('12.30')) - Decimal('0') - >>> ExtendedContext.compare_total(Decimal('12.3'), Decimal('12.300')) - 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): - """Compares two operands using their abstract representation ignoring sign. - - 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): - """Returns a copy of the operand with the sign set to 0. - - >>> ExtendedContext.copy_abs(Decimal('2.1')) - 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 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): - """Returns a copy of the operand with the sign inverted. - - >>> ExtendedContext.copy_negate(Decimal('101.5')) - 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): - """Copies the second operand's sign to the first one. - - In detail, it returns a copy of the first operand with the sign - equal to the sign of the second operand. - - >>> ExtendedContext.copy_sign(Decimal( '1.50'), Decimal('7.33')) - Decimal('1.50') - >>> ExtendedContext.copy_sign(Decimal('-1.50'), Decimal('7.33')) - Decimal('1.50') - >>> ExtendedContext.copy_sign(Decimal( '1.50'), Decimal('-7.33')) - 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): - """Decimal division in a specified context. - - >>> ExtendedContext.divide(Decimal('1'), Decimal('3')) - Decimal('0.333333333') - >>> ExtendedContext.divide(Decimal('2'), Decimal('3')) - Decimal('0.666666667') - >>> ExtendedContext.divide(Decimal('5'), Decimal('2')) - Decimal('2.5') - >>> ExtendedContext.divide(Decimal('1'), Decimal('10')) - Decimal('0.1') - >>> ExtendedContext.divide(Decimal('12'), Decimal('12')) - Decimal('1') - >>> ExtendedContext.divide(Decimal('8.00'), Decimal('2')) - Decimal('4.00') - >>> ExtendedContext.divide(Decimal('2.400'), Decimal('2.0')) - Decimal('1.20') - >>> ExtendedContext.divide(Decimal('1000'), Decimal('100')) - Decimal('10') - >>> ExtendedContext.divide(Decimal('1000'), Decimal('1')) - 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') - """ - 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. - - >>> ExtendedContext.divide_int(Decimal('2'), Decimal('3')) - Decimal('0') - >>> ExtendedContext.divide_int(Decimal('10'), Decimal('3')) - 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') - """ - 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). - - >>> 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')) - """ - 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. - - >>> c = ExtendedContext.copy() - >>> c.Emin = -999 - >>> c.Emax = 999 - >>> c.exp(Decimal('-Infinity')) - Decimal('0') - >>> c.exp(Decimal('-1')) - Decimal('0.367879441') - >>> c.exp(Decimal('0')) - Decimal('1') - >>> c.exp(Decimal('1')) - Decimal('2.71828183') - >>> c.exp(Decimal('0.693147181')) - 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): - """Returns a multiplied by b, plus c. - - The first two operands are multiplied together, using multiply, - the third operand is then added to the result of that - multiplication, using add, all with only one final rounding. - - >>> ExtendedContext.fma(Decimal('3'), Decimal('5'), Decimal('7')) - Decimal('22') - >>> ExtendedContext.fma(Decimal('3'), Decimal('-5'), Decimal('7')) - 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): - """Return True if the operand is canonical; otherwise return False. - - Currently, the encoding of a Decimal instance is always - canonical, so this method returns True for any Decimal. - - >>> ExtendedContext.is_canonical(Decimal('2.50')) - True - """ - if not isinstance(a, Decimal): - raise TypeError("is_canonical requires a Decimal as an argument.") - return a.is_canonical() - - def is_finite(self, a): - """Return True if the operand is finite; otherwise return False. - - A Decimal instance is considered finite if it is neither - infinite nor a NaN. - - >>> ExtendedContext.is_finite(Decimal('2.50')) - True - >>> ExtendedContext.is_finite(Decimal('-0.3')) - True - >>> ExtendedContext.is_finite(Decimal('0')) - True - >>> ExtendedContext.is_finite(Decimal('Inf')) - 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): - """Return True if the operand is infinite; otherwise return False. - - >>> ExtendedContext.is_infinite(Decimal('2.50')) - False - >>> ExtendedContext.is_infinite(Decimal('-Inf')) - 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): - """Return True if the operand is a qNaN or sNaN; - otherwise return False. - - >>> ExtendedContext.is_nan(Decimal('2.50')) - False - >>> ExtendedContext.is_nan(Decimal('NaN')) - 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): - """Return True if the operand is a normal number; - otherwise return False. - - >>> c = ExtendedContext.copy() - >>> c.Emin = -999 - >>> c.Emax = 999 - >>> c.is_normal(Decimal('2.50')) - True - >>> c.is_normal(Decimal('0.1E-999')) - False - >>> c.is_normal(Decimal('0.00')) - False - >>> c.is_normal(Decimal('-Inf')) - 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): - """Return True if the operand is a quiet NaN; otherwise return False. - - >>> ExtendedContext.is_qnan(Decimal('2.50')) - False - >>> ExtendedContext.is_qnan(Decimal('NaN')) - 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): - """Return True if the operand is negative; otherwise return False. - - >>> ExtendedContext.is_signed(Decimal('2.50')) - False - >>> ExtendedContext.is_signed(Decimal('-12')) - 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): - """Return True if the operand is a signaling NaN; - otherwise return False. - - >>> ExtendedContext.is_snan(Decimal('2.50')) - False - >>> ExtendedContext.is_snan(Decimal('NaN')) - 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): - """Return True if the operand is subnormal; otherwise return False. - - >>> c = ExtendedContext.copy() - >>> c.Emin = -999 - >>> c.Emax = 999 - >>> c.is_subnormal(Decimal('2.50')) - False - >>> c.is_subnormal(Decimal('0.1E-999')) - True - >>> c.is_subnormal(Decimal('0.00')) - False - >>> c.is_subnormal(Decimal('-Inf')) - 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): - """Return True if the operand is a zero; otherwise return False. - - >>> ExtendedContext.is_zero(Decimal('0')) - True - >>> ExtendedContext.is_zero(Decimal('2.50')) - 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): - """Returns the natural (base e) logarithm of the operand. - - >>> c = ExtendedContext.copy() - >>> c.Emin = -999 - >>> c.Emax = 999 - >>> c.ln(Decimal('0')) - Decimal('-Infinity') - >>> c.ln(Decimal('1.000')) - Decimal('0') - >>> c.ln(Decimal('2.71828183')) - Decimal('1.00000000') - >>> c.ln(Decimal('10')) - 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): - """Returns the base 10 logarithm of the operand. - - >>> c = ExtendedContext.copy() - >>> c.Emin = -999 - >>> c.Emax = 999 - >>> c.log10(Decimal('0')) - Decimal('-Infinity') - >>> c.log10(Decimal('0.001')) - Decimal('-3') - >>> c.log10(Decimal('1.000')) - Decimal('0') - >>> c.log10(Decimal('2')) - Decimal('0.301029996') - >>> c.log10(Decimal('10')) - Decimal('1') - >>> c.log10(Decimal('70')) - 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): - """ Returns the exponent of the magnitude of the operand's MSD. - - The result is the integer which is the exponent of the magnitude - of the most significant digit of the operand (as though the - operand were truncated to a single digit while maintaining the - value of that digit and without limiting the resulting exponent). - - >>> ExtendedContext.logb(Decimal('250')) - Decimal('2') - >>> ExtendedContext.logb(Decimal('2.50')) - Decimal('0') - >>> ExtendedContext.logb(Decimal('0.03')) - 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): - """Applies the logical operation 'and' between each operand's digits. - - The operands must be both logical numbers. - - >>> ExtendedContext.logical_and(Decimal('0'), Decimal('0')) - Decimal('0') - >>> ExtendedContext.logical_and(Decimal('0'), Decimal('1')) - Decimal('0') - >>> ExtendedContext.logical_and(Decimal('1'), Decimal('0')) - Decimal('0') - >>> ExtendedContext.logical_and(Decimal('1'), Decimal('1')) - Decimal('1') - >>> ExtendedContext.logical_and(Decimal('1100'), Decimal('1010')) - 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): - """Invert all the digits in the operand. - - The operand must be a logical number. - - >>> ExtendedContext.logical_invert(Decimal('0')) - Decimal('111111111') - >>> ExtendedContext.logical_invert(Decimal('1')) - Decimal('111111110') - >>> ExtendedContext.logical_invert(Decimal('111111111')) - 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): - """Applies the logical operation 'or' between each operand's digits. - - The operands must be both logical numbers. - - >>> ExtendedContext.logical_or(Decimal('0'), Decimal('0')) - Decimal('0') - >>> ExtendedContext.logical_or(Decimal('0'), Decimal('1')) - Decimal('1') - >>> ExtendedContext.logical_or(Decimal('1'), Decimal('0')) - Decimal('1') - >>> ExtendedContext.logical_or(Decimal('1'), Decimal('1')) - Decimal('1') - >>> ExtendedContext.logical_or(Decimal('1100'), Decimal('1010')) - 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): - """Applies the logical operation 'xor' between each operand's digits. - - The operands must be both logical numbers. - - >>> ExtendedContext.logical_xor(Decimal('0'), Decimal('0')) - Decimal('0') - >>> ExtendedContext.logical_xor(Decimal('0'), Decimal('1')) - Decimal('1') - >>> ExtendedContext.logical_xor(Decimal('1'), Decimal('0')) - Decimal('1') - >>> ExtendedContext.logical_xor(Decimal('1'), Decimal('1')) - Decimal('0') - >>> ExtendedContext.logical_xor(Decimal('1100'), Decimal('1010')) - 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): - """max compares two values numerically and returns the maximum. - - If either operand is a NaN then the general rules apply. - Otherwise, the operands are compared as though by the compare - operation. If they are numerically equal then the left-hand operand - is chosen as the result. Otherwise the maximum (closer to positive - infinity) of the two operands is chosen as the result. - - >>> ExtendedContext.max(Decimal('3'), Decimal('2')) - Decimal('3') - >>> ExtendedContext.max(Decimal('-10'), Decimal('3')) - Decimal('3') - >>> ExtendedContext.max(Decimal('1.0'), Decimal('1')) - 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. - - >>> 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): - """min compares two values numerically and returns the minimum. - - If either operand is a NaN then the general rules apply. - Otherwise, the operands are compared as though by the compare - operation. If they are numerically equal then the left-hand operand - is chosen as the result. Otherwise the minimum (closer to negative - infinity) of the two operands is chosen as the result. - - >>> ExtendedContext.min(Decimal('3'), Decimal('2')) - Decimal('2') - >>> ExtendedContext.min(Decimal('-10'), Decimal('3')) - Decimal('-10') - >>> ExtendedContext.min(Decimal('1.0'), Decimal('1')) - 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. - - >>> 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): - """Minus corresponds to unary prefix minus in Python. - - The operation is evaluated using the same rules as subtract; the - operation minus(a) is calculated as subtract('0', a) where the '0' - has the same exponent as the operand. - - >>> ExtendedContext.minus(Decimal('1.3')) - 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. - - >>> ExtendedContext.multiply(Decimal('1.20'), Decimal('3')) - Decimal('3.60') - >>> ExtendedContext.multiply(Decimal('7'), Decimal('3')) - Decimal('21') - >>> ExtendedContext.multiply(Decimal('0.9'), Decimal('0.8')) - Decimal('0.72') - >>> ExtendedContext.multiply(Decimal('0.9'), Decimal('-0')) - 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') - """ - 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. - - >>> c = ExtendedContext.copy() - >>> c.Emin = -999 - >>> c.Emax = 999 - >>> ExtendedContext.next_minus(Decimal('1')) - Decimal('0.999999999') - >>> c.next_minus(Decimal('1E-1007')) - Decimal('0E-1007') - >>> ExtendedContext.next_minus(Decimal('-1.00000003')) - 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): - """Returns the smallest representable number larger than a. - - >>> c = ExtendedContext.copy() - >>> c.Emin = -999 - >>> c.Emax = 999 - >>> ExtendedContext.next_plus(Decimal('1')) - Decimal('1.00000001') - >>> c.next_plus(Decimal('-1E-1007')) - Decimal('-0E-1007') - >>> ExtendedContext.next_plus(Decimal('-1.00000003')) - 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): - """Returns the number closest to a, in direction towards b. - - The result is the closest representable number from the first - operand (but not the first operand) that is in the direction - towards the second operand, unless the operands have the same - value. - - >>> c = ExtendedContext.copy() - >>> c.Emin = -999 - >>> c.Emax = 999 - >>> c.next_toward(Decimal('1'), Decimal('2')) - Decimal('1.00000001') - >>> c.next_toward(Decimal('-1E-1007'), Decimal('1')) - Decimal('-0E-1007') - >>> c.next_toward(Decimal('-1.00000003'), Decimal('0')) - Decimal('-1.00000002') - >>> c.next_toward(Decimal('1'), Decimal('0')) - Decimal('0.999999999') - >>> c.next_toward(Decimal('1E-1007'), Decimal('-100')) - Decimal('0E-1007') - >>> c.next_toward(Decimal('-1.00000003'), Decimal('-10')) - 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): - """normalize reduces an operand to its simplest form. - - Essentially a plus operation with all trailing zeros removed from the - result. - - >>> ExtendedContext.normalize(Decimal('2.1')) - Decimal('2.1') - >>> ExtendedContext.normalize(Decimal('-2.0')) - Decimal('-2') - >>> ExtendedContext.normalize(Decimal('1.200')) - Decimal('1.2') - >>> ExtendedContext.normalize(Decimal('-120')) - Decimal('-1.2E+2') - >>> ExtendedContext.normalize(Decimal('120.00')) - 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): - """Returns an indication of the class of the operand. - - The class is one of the following strings: - -sNaN - -NaN - -Infinity - -Normal - -Subnormal - -Zero - +Zero - +Subnormal - +Normal - +Infinity - - >>> c = ExtendedContext.copy() - >>> c.Emin = -999 - >>> c.Emax = 999 - >>> c.number_class(Decimal('Infinity')) - '+Infinity' - >>> c.number_class(Decimal('1E-10')) - '+Normal' - >>> c.number_class(Decimal('2.50')) - '+Normal' - >>> c.number_class(Decimal('0.1E-999')) - '+Subnormal' - >>> c.number_class(Decimal('0')) - '+Zero' - >>> c.number_class(Decimal('-0')) - '-Zero' - >>> c.number_class(Decimal('-0.1E-999')) - '-Subnormal' - >>> c.number_class(Decimal('-1E-10')) - '-Normal' - >>> c.number_class(Decimal('-2.50')) - '-Normal' - >>> c.number_class(Decimal('-Infinity')) - '-Infinity' - >>> c.number_class(Decimal('NaN')) - 'NaN' - >>> c.number_class(Decimal('-NaN')) - '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): - """Plus corresponds to unary prefix plus in Python. - - The operation is evaluated using the same rules as add; the - operation plus(a) is calculated as add('0', a) where the '0' - has the same exponent as the operand. - - >>> ExtendedContext.plus(Decimal('1.3')) - 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): - """Raises a to the power of b, to modulo if given. - - With two arguments, compute a**b. If a is negative then b - must be integral. The result will be inexact unless b is - integral and the result is finite and can be expressed exactly - in 'precision' digits. - - With three arguments, compute (a**b) % modulo. For the - three argument form, the following restrictions on the - arguments hold: - - - all three arguments must be integral - - b must be nonnegative - - at least one of a or b must be nonzero - - modulo must be nonzero and have at most 'precision' digits - - The result of pow(a, b, modulo) is identical to the result - that would be obtained by computing (a**b) % modulo with - unbounded precision, but is computed more efficiently. It is - always exact. - - >>> c = ExtendedContext.copy() - >>> c.Emin = -999 - >>> c.Emax = 999 - >>> c.power(Decimal('2'), Decimal('3')) - Decimal('8') - >>> c.power(Decimal('-2'), Decimal('3')) - Decimal('-8') - >>> c.power(Decimal('2'), Decimal('-3')) - Decimal('0.125') - >>> c.power(Decimal('1.7'), Decimal('8')) - Decimal('69.7575744') - >>> c.power(Decimal('10'), Decimal('0.301029996')) - Decimal('2.00000000') - >>> c.power(Decimal('Infinity'), Decimal('-1')) - Decimal('0') - >>> c.power(Decimal('Infinity'), Decimal('0')) - Decimal('1') - >>> c.power(Decimal('Infinity'), Decimal('1')) - Decimal('Infinity') - >>> c.power(Decimal('-Infinity'), Decimal('-1')) - Decimal('-0') - >>> c.power(Decimal('-Infinity'), Decimal('0')) - Decimal('1') - >>> c.power(Decimal('-Infinity'), Decimal('1')) - Decimal('-Infinity') - >>> c.power(Decimal('-Infinity'), Decimal('2')) - Decimal('Infinity') - >>> c.power(Decimal('0'), Decimal('0')) - Decimal('NaN') - - >>> c.power(Decimal('3'), Decimal('7'), Decimal('16')) - Decimal('11') - >>> c.power(Decimal('-3'), Decimal('7'), Decimal('16')) - Decimal('-11') - >>> c.power(Decimal('-3'), Decimal('8'), Decimal('16')) - Decimal('1') - >>> c.power(Decimal('3'), Decimal('7'), Decimal('-16')) - Decimal('11') - >>> c.power(Decimal('23E12345'), Decimal('67E189'), Decimal('123456789')) - Decimal('11729830') - >>> c.power(Decimal('-0'), Decimal('17'), Decimal('1729')) - 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') - """ - 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'. - - The coefficient of the result is derived from that of the left-hand - operand. It may be rounded using the current rounding setting (if the - exponent is being increased), multiplied by a positive power of ten (if - the exponent is being decreased), or is unchanged (if the exponent is - already equal to that of the right-hand operand). - - Unlike other operations, if the length of the coefficient after the - quantize operation would be greater than precision then an Invalid - operation condition is raised. This guarantees that, unless there is - an error condition, the exponent of the result of a quantize is always - equal to that of the right-hand operand. - - Also unlike other operations, quantize will never raise Underflow, even - if the result is subnormal and inexact. - - >>> ExtendedContext.quantize(Decimal('2.17'), Decimal('0.001')) - Decimal('2.170') - >>> ExtendedContext.quantize(Decimal('2.17'), Decimal('0.01')) - Decimal('2.17') - >>> ExtendedContext.quantize(Decimal('2.17'), Decimal('0.1')) - Decimal('2.2') - >>> ExtendedContext.quantize(Decimal('2.17'), Decimal('1e+0')) - Decimal('2') - >>> ExtendedContext.quantize(Decimal('2.17'), Decimal('1e+1')) - Decimal('0E+1') - >>> ExtendedContext.quantize(Decimal('-Inf'), Decimal('Infinity')) - Decimal('-Infinity') - >>> ExtendedContext.quantize(Decimal('2'), Decimal('Infinity')) - Decimal('NaN') - >>> ExtendedContext.quantize(Decimal('-0.1'), Decimal('1')) - Decimal('-0') - >>> ExtendedContext.quantize(Decimal('-0'), Decimal('1e+5')) - Decimal('-0E+5') - >>> ExtendedContext.quantize(Decimal('+35236450.6'), Decimal('1e-2')) - Decimal('NaN') - >>> ExtendedContext.quantize(Decimal('-35236450.6'), Decimal('1e-2')) - Decimal('NaN') - >>> ExtendedContext.quantize(Decimal('217'), Decimal('1e-1')) - Decimal('217.0') - >>> ExtendedContext.quantize(Decimal('217'), Decimal('1e-0')) - Decimal('217') - >>> ExtendedContext.quantize(Decimal('217'), Decimal('1e+1')) - 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): - """Just returns 10, as this is Decimal, :) - - >>> ExtendedContext.radix() - Decimal('10') - """ - return Decimal(10) - - def remainder(self, a, b): - """Returns the remainder from integer division. - - The result is the residue of the dividend after the operation of - calculating integer division as described for divide-integer, rounded - to precision digits if necessary. The sign of the result, if - non-zero, is the same as that of the original dividend. - - This operation will fail under the same conditions as integer division - (that is, if integer division on the same two operands would fail, the - remainder cannot be calculated). - - >>> ExtendedContext.remainder(Decimal('2.1'), Decimal('3')) - Decimal('2.1') - >>> ExtendedContext.remainder(Decimal('10'), Decimal('3')) - Decimal('1') - >>> ExtendedContext.remainder(Decimal('-10'), Decimal('3')) - Decimal('-1') - >>> ExtendedContext.remainder(Decimal('10.2'), Decimal('1')) - Decimal('0.2') - >>> ExtendedContext.remainder(Decimal('10'), Decimal('0.3')) - 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') - """ - 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 - value of "x / b" (if two integers are equally near then the even one - is chosen). If the result is equal to 0 then its sign will be the - sign of a. - - This operation will fail under the same conditions as integer division - (that is, if integer division on the same two operands would fail, the - remainder cannot be calculated). - - >>> ExtendedContext.remainder_near(Decimal('2.1'), Decimal('3')) - Decimal('-0.9') - >>> ExtendedContext.remainder_near(Decimal('10'), Decimal('6')) - Decimal('-2') - >>> ExtendedContext.remainder_near(Decimal('10'), Decimal('3')) - Decimal('1') - >>> ExtendedContext.remainder_near(Decimal('-10'), Decimal('3')) - Decimal('-1') - >>> ExtendedContext.remainder_near(Decimal('10.2'), Decimal('1')) - Decimal('0.2') - >>> ExtendedContext.remainder_near(Decimal('10'), Decimal('0.3')) - 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): - """Returns a rotated copy of a, b times. - - The coefficient of the result is a rotated copy of the digits in - the coefficient of the first operand. The number of places of - rotation is taken from the absolute value of the second operand, - with the rotation being to the left if the second operand is - positive or to the right otherwise. - - >>> ExtendedContext.rotate(Decimal('34'), Decimal('8')) - Decimal('400000003') - >>> ExtendedContext.rotate(Decimal('12'), Decimal('9')) - Decimal('12') - >>> ExtendedContext.rotate(Decimal('123456789'), Decimal('-2')) - Decimal('891234567') - >>> ExtendedContext.rotate(Decimal('123456789'), Decimal('0')) - 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): - """Returns True if the two operands have the same exponent. - - The result is never affected by either the sign or the coefficient of - either operand. - - >>> ExtendedContext.same_quantum(Decimal('2.17'), Decimal('0.001')) - False - >>> ExtendedContext.same_quantum(Decimal('2.17'), Decimal('0.01')) - True - >>> ExtendedContext.same_quantum(Decimal('2.17'), Decimal('1')) - 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): - """Returns the first operand after adding the second value its exp. - - >>> ExtendedContext.scaleb(Decimal('7.50'), Decimal('-2')) - Decimal('0.0750') - >>> ExtendedContext.scaleb(Decimal('7.50'), Decimal('0')) - 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') - """ - 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. - - The coefficient of the result is a shifted copy of the digits - in the coefficient of the first operand. The number of places - to shift is taken from the absolute value of the second operand, - with the shift being to the left if the second operand is - positive or to the right otherwise. Digits shifted into the - coefficient are zeros. - - >>> ExtendedContext.shift(Decimal('34'), Decimal('8')) - Decimal('400000000') - >>> ExtendedContext.shift(Decimal('12'), Decimal('9')) - Decimal('0') - >>> ExtendedContext.shift(Decimal('123456789'), Decimal('-2')) - Decimal('1234567') - >>> ExtendedContext.shift(Decimal('123456789'), Decimal('0')) - 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): - """Square root of a non-negative number to context precision. - - If the result must be inexact, it is rounded using the round-half-even - algorithm. - - >>> ExtendedContext.sqrt(Decimal('0')) - Decimal('0') - >>> ExtendedContext.sqrt(Decimal('-0')) - Decimal('-0') - >>> ExtendedContext.sqrt(Decimal('0.39')) - Decimal('0.624499800') - >>> ExtendedContext.sqrt(Decimal('100')) - Decimal('10') - >>> ExtendedContext.sqrt(Decimal('1')) - Decimal('1') - >>> ExtendedContext.sqrt(Decimal('1.0')) - Decimal('1.0') - >>> ExtendedContext.sqrt(Decimal('1.00')) - Decimal('1.0') - >>> ExtendedContext.sqrt(Decimal('7')) - 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): - """Return the difference between the two operands. - - >>> ExtendedContext.subtract(Decimal('1.3'), Decimal('1.07')) - Decimal('0.23') - >>> ExtendedContext.subtract(Decimal('1.3'), Decimal('1.30')) - 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') - """ - 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): - """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.__str__(context=self) - - def to_integral_exact(self, a): - """Rounds to an integer. - - When the operand has a negative exponent, the result is the same - as using the quantize() operation using the given operand as the - left-hand-operand, 1E+0 as the right-hand-operand, and the precision - of the operand as the precision setting; Inexact and Rounded flags - are allowed in this operation. The rounding mode is taken from the - context. - - >>> ExtendedContext.to_integral_exact(Decimal('2.1')) - Decimal('2') - >>> ExtendedContext.to_integral_exact(Decimal('100')) - Decimal('100') - >>> ExtendedContext.to_integral_exact(Decimal('100.0')) - Decimal('100') - >>> ExtendedContext.to_integral_exact(Decimal('101.5')) - Decimal('102') - >>> ExtendedContext.to_integral_exact(Decimal('-101.5')) - Decimal('-102') - >>> ExtendedContext.to_integral_exact(Decimal('10E+5')) - Decimal('1.0E+6') - >>> ExtendedContext.to_integral_exact(Decimal('7.89E+77')) - Decimal('7.89E+77') - >>> 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): - """Rounds to an integer. - - When the operand has a negative exponent, the result is the same - as using the quantize() operation using the given operand as the - left-hand-operand, 1E+0 as the right-hand-operand, and the precision - of the operand as the precision setting, except that no flags will - be set. The rounding mode is taken from the context. - - >>> ExtendedContext.to_integral_value(Decimal('2.1')) - Decimal('2') - >>> ExtendedContext.to_integral_value(Decimal('100')) - Decimal('100') - >>> ExtendedContext.to_integral_value(Decimal('100.0')) - Decimal('100') - >>> ExtendedContext.to_integral_value(Decimal('101.5')) - Decimal('102') - >>> ExtendedContext.to_integral_value(Decimal('-101.5')) - Decimal('-102') - >>> ExtendedContext.to_integral_value(Decimal('10E+5')) - Decimal('1.0E+6') - >>> ExtendedContext.to_integral_value(Decimal('7.89E+77')) - Decimal('7.89E+77') - >>> 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 - to_integral = to_integral_value - -class _WorkRep(object): - __slots__ = ('sign','int','exp') - # sign: 0 or 1 - # int: int - # exp: None, int, or string - - def __init__(self, value=None): - if value is None: - self.sign = None - self.int = 0 - self.exp = None - elif isinstance(value, Decimal): - self.sign = value._sign - self.int = int(value._int) - self.exp = value._exp - else: - # assert isinstance(value, tuple) - self.sign = value[0] - self.int = value[1] - self.exp = value[2] - - def __repr__(self): - return "(%r, %r, %r)" % (self.sign, self.int, self.exp) - - __str__ = __repr__ - - - -def _normalize(op1, op2, prec = 0): - """Normalizes op1, op2 to have the same exp and length of coefficient. - - Done during addition. - """ - if op1.exp < op2.exp: - tmp = op2 - other = op1 - else: - tmp = op1 - other = op2 - - # Let exp = min(tmp.exp - 1, tmp.adjusted() - precision - 1). - # Then adding 10**exp to tmp has the same effect (after rounding) - # as adding any positive quantity smaller than 10**exp; similarly - # for subtraction. So if other is smaller than 10**exp we replace - # it with 10**exp. This avoids tmp.exp - other.exp getting too large. - tmp_len = len(str(tmp.int)) - other_len = len(str(other.int)) - exp = tmp.exp + min(-1, tmp_len - prec - 2) - if other_len + other.exp - 1 < exp: - other.int = 1 - other.exp = exp - - tmp.int *= 10 ** (tmp.exp - other.exp) - tmp.exp = other.exp - return op1, op2 - -##### Integer arithmetic functions used by ln, log10, exp and __pow__ ##### - -_nbits = int.bit_length - -def _decimal_lshift_exact(n, e): - """ Given integers n and e, return n * 10**e if it's an integer, else None. - - The computation is designed to avoid computing large powers of 10 - unnecessarily. - - >>> _decimal_lshift_exact(3, 4) - 30000 - >>> _decimal_lshift_exact(300, -999999999) # returns None - - """ - if n == 0: - return 0 - elif e >= 0: - return n * 10**e - else: - # val_n = largest power of 10 dividing n. - str_n = str(abs(n)) - val_n = len(str_n) - len(str_n.rstrip('0')) - return None if val_n < -e else n // 10**-e - -def _sqrt_nearest(n, a): - """Closest integer to the square root of the positive integer n. a is - an initial approximation to the square root. Any positive integer - will do for a, but the closer a is to the square root of n the - faster convergence will be. - - """ - if n <= 0 or a <= 0: - raise ValueError("Both arguments to _sqrt_nearest should be positive.") - - b=0 - while a != b: - b, a = a, a--n//a>>1 - return a - -def _rshift_nearest(x, shift): - """Given an integer x and a nonnegative integer shift, return closest - integer to x / 2**shift; use round-to-even in case of a tie. - - """ - b, q = 1 << shift, x >> shift - return q + (2*(x & (b-1)) + (q&1) > b) - -def _div_nearest(a, b): - """Closest integer to a/b, a and b positive integers; rounds to even - in the case of a tie. - - """ - q, r = divmod(a, b) - return q + (2*r + (q&1) > b) - -def _ilog(x, M, L = 8): - """Integer approximation to M*log(x/M), with absolute error boundable - in terms only of x/M. - - Given positive integers x and M, return an integer approximation to - M * log(x/M). For L = 8 and 0.1 <= x/M <= 10 the difference - between the approximation and the exact result is at most 22. For - L = 8 and 1.0 <= x/M <= 10.0 the difference is at most 15. In - both cases these are upper bounds on the error; it will usually be - much smaller.""" - - # The basic algorithm is the following: let log1p be the function - # log1p(x) = log(1+x). Then log(x/M) = log1p((x-M)/M). We use - # the reduction - # - # log1p(y) = 2*log1p(y/(1+sqrt(1+y))) - # - # repeatedly until the argument to log1p is small (< 2**-L in - # absolute value). For small y we can use the Taylor series - # expansion - # - # log1p(y) ~ y - y**2/2 + y**3/3 - ... - (-y)**T/T - # - # truncating at T such that y**T is small enough. The whole - # computation is carried out in a form of fixed-point arithmetic, - # with a real number z being represented by an integer - # approximation to z*M. To avoid loss of precision, the y below - # is actually an integer approximation to 2**R*y*M, where R is the - # number of reductions performed so far. - - y = x-M - # argument reduction; R = number of reductions performed - R = 0 - while (R <= L and abs(y) << L-R >= M or - R > L and abs(y) >> R-L >= M): - y = _div_nearest((M*y) << 1, - M + _sqrt_nearest(M*(M+_rshift_nearest(y, R)), M)) - R += 1 - - # Taylor series with T terms - T = -int(-10*len(str(M))//(3*L)) - yshift = _rshift_nearest(y, R) - w = _div_nearest(M, T) - for k in range(T-1, 0, -1): - w = _div_nearest(M, k) - _div_nearest(yshift*w, M) - - return _div_nearest(w*y, M) - -def _dlog10(c, e, p): - """Given integers c, e and p with c > 0, p >= 0, compute an integer - approximation to 10**p * log10(c*10**e), with an absolute error of - at most 1. Assumes that c*10**e is not exactly 1.""" - - # increase precision by 2; compensate for this by dividing - # final result by 100 - p += 2 - - # write c*10**e as d*10**f with either: - # f >= 0 and 1 <= d <= 10, or - # f <= 0 and 0.1 <= d <= 1. - # Thus for c*10**e close to 1, f = 0 - l = len(str(c)) - f = e+l - (e+l >= 1) - - if p > 0: - M = 10**p - k = e+p-f - if k >= 0: - c *= 10**k - else: - c = _div_nearest(c, 10**-k) - - log_d = _ilog(c, M) # error < 5 + 22 = 27 - log_10 = _log10_digits(p) # error < 1 - log_d = _div_nearest(log_d*M, log_10) - log_tenpower = f*M # exact - else: - log_d = 0 # error < 2.31 - log_tenpower = _div_nearest(f, 10**-p) # error < 0.5 - - return _div_nearest(log_tenpower+log_d, 100) - -def _dlog(c, e, p): - """Given integers c, e and p with c > 0, compute an integer - approximation to 10**p * log(c*10**e), with an absolute error of - at most 1. Assumes that c*10**e is not exactly 1.""" - - # Increase precision by 2. The precision increase is compensated - # for at the end with a division by 100. - p += 2 - - # rewrite c*10**e as d*10**f with either f >= 0 and 1 <= d <= 10, - # or f <= 0 and 0.1 <= d <= 1. Then we can compute 10**p * log(c*10**e) - # as 10**p * log(d) + 10**p*f * log(10). - l = len(str(c)) - f = e+l - (e+l >= 1) - - # compute approximation to 10**p*log(d), with error < 27 - if p > 0: - k = e+p-f - if k >= 0: - c *= 10**k - else: - c = _div_nearest(c, 10**-k) # error of <= 0.5 in c - - # _ilog magnifies existing error in c by a factor of at most 10 - log_d = _ilog(c, 10**p) # error < 5 + 22 = 27 - else: - # p <= 0: just approximate the whole thing by 0; error < 2.31 - log_d = 0 - - # compute approximation to f*10**p*log(10), with error < 11. - if f: - extra = len(str(abs(f)))-1 - if p + extra >= 0: - # error in f * _log10_digits(p+extra) < |f| * 1 = |f| - # after division, error < |f|/10**extra + 0.5 < 10 + 0.5 < 11 - f_log_ten = _div_nearest(f*_log10_digits(p+extra), 10**extra) - else: - f_log_ten = 0 - else: - f_log_ten = 0 - - # error in sum < 11+27 = 38; error after division < 0.38 + 0.5 < 1 - return _div_nearest(f_log_ten + log_d, 100) - -class _Log10Memoize(object): - """Class to compute, store, and allow retrieval of, digits of the - constant log(10) = 2.302585.... This constant is needed by - Decimal.ln, Decimal.log10, Decimal.exp and Decimal.__pow__.""" - def __init__(self): - self.digits = "23025850929940456840179914546843642076011014886" - - def getdigits(self, p): - """Given an integer p >= 0, return floor(10**p)*log(10). - - For example, self.getdigits(3) returns 2302. - """ - # digits are stored as a string, for quick conversion to - # integer in the case that we've already computed enough - # digits; the stored digits should always be correct - # (truncated, not rounded to nearest). - if p < 0: - raise ValueError("p should be nonnegative") - - if p >= len(self.digits): - # compute p+3, p+6, p+9, ... digits; continue until at - # least one of the extra digits is nonzero - extra = 3 - while True: - # compute p+extra digits, correct to within 1ulp - M = 10**(p+extra+2) - digits = str(_div_nearest(_ilog(10*M, M), 100)) - if digits[-extra:] != '0'*extra: - break - extra += 3 - # keep all reliable digits so far; remove trailing zeros - # and next nonzero digit - self.digits = digits.rstrip('0')[:-1] - return int(self.digits[:p+1]) - -_log10_digits = _Log10Memoize().getdigits - -def _iexp(x, M, L=8): - """Given integers x and M, M > 0, such that x/M is small in absolute - value, compute an integer approximation to M*exp(x/M). For 0 <= - x/M <= 2.4, the absolute error in the result is bounded by 60 (and - is usually much smaller).""" - - # Algorithm: to compute exp(z) for a real number z, first divide z - # by a suitable power R of 2 so that |z/2**R| < 2**-L. Then - # compute expm1(z/2**R) = exp(z/2**R) - 1 using the usual Taylor - # series - # - # expm1(x) = x + x**2/2! + x**3/3! + ... - # - # Now use the identity - # - # expm1(2x) = expm1(x)*(expm1(x)+2) - # - # R times to compute the sequence expm1(z/2**R), - # expm1(z/2**(R-1)), ... , exp(z/2), exp(z). - - # Find R such that x/2**R/M <= 2**-L - R = _nbits((x< M - T = -int(-10*len(str(M))//(3*L)) - y = _div_nearest(x, T) - Mshift = M<= 0: - cshift = c*10**shift - else: - cshift = c//10**-shift - quot, rem = divmod(cshift, _log10_digits(q)) - - # reduce remainder back to original precision - rem = _div_nearest(rem, 10**extra) - - # error in result of _iexp < 120; error after division < 0.62 - return _div_nearest(_iexp(rem, 10**p), 1000), quot - p + 3 - -def _dpower(xc, xe, yc, ye, p): - """Given integers xc, xe, yc and ye representing Decimals x = xc*10**xe and - y = yc*10**ye, compute x**y. Returns a pair of integers (c, e) such that: - - 10**(p-1) <= c <= 10**p, and - (c-1)*10**e < x**y < (c+1)*10**e - - in other words, c*10**e is an approximation to x**y with p digits - of precision, and with an error in c of at most 1. (This is - almost, but not quite, the same as the error being < 1ulp: when c - == 10**(p-1) we can only guarantee error < 10ulp.) - - We assume that: x is positive and not equal to 1, and y is nonzero. - """ - - # Find b such that 10**(b-1) <= |y| <= 10**b - b = len(str(abs(yc))) + ye - - # log(x) = lxc*10**(-p-b-1), to p+b+1 places after the decimal point - lxc = _dlog(xc, xe, p+b+1) - - # compute product y*log(x) = yc*lxc*10**(-p-b-1+ye) = pc*10**(-p-1) - shift = ye-b - if shift >= 0: - pc = lxc*yc*10**shift - else: - pc = _div_nearest(lxc*yc, 10**-shift) - - if pc == 0: - # we prefer a result that isn't exactly 1; this makes it - # easier to compute a correctly rounded result in __pow__ - if ((len(str(xc)) + xe >= 1) == (yc > 0)): # if x**y > 1: - coeff, exp = 10**(p-1)+1, 1-p - else: - coeff, exp = 10**p-1, -p - else: - coeff, exp = _dexp(pc, -(p+1), p+1) - coeff = _div_nearest(coeff, 10) - exp += 1 - - return coeff, exp - -def _log10_lb(c, correction = { - '1': 100, '2': 70, '3': 53, '4': 40, '5': 31, - '6': 23, '7': 16, '8': 10, '9': 5}): - """Compute a lower bound for 100*log10(c) for a positive integer c.""" - if c <= 0: - raise ValueError("The argument to _log10_lb should be nonnegative.") - str_c = str(c) - return 100*len(str_c) - correction[str_c[0]] - -##### Helper Functions #################################################### - -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): - context = getcontext() - if equality_op: - context.flags[FloatOperation] = 1 - else: - context._raise_error(FloatOperation, - "strict semantics for mixing floats and Decimals are enabled") - return self, Decimal.from_float(other) - return NotImplemented, NotImplemented - - -##### Setup Specific Contexts ############################################ - -# The default context prototype used by Context() -# Is mutable, so that new contexts can have different default values - -DefaultContext = Context( - prec=28, rounding=ROUND_HALF_EVEN, - traps=[DivisionByZero, Overflow, InvalidOperation], - flags=[], - Emax=999999, - Emin=-999999, - capitals=1, - clamp=0 -) - -# Pre-made alternate contexts offered by the specification -# Don't change these; the user should be able to select these -# contexts and be able to reproduce results from other implementations -# of the spec. - -BasicContext = Context( - prec=9, rounding=ROUND_HALF_UP, - traps=[DivisionByZero, Overflow, InvalidOperation, Clamped, Underflow], - flags=[], -) - -ExtendedContext = Context( - prec=9, rounding=ROUND_HALF_EVEN, - traps=[], - flags=[], -) - - -##### crud for parsing strings ############################################# -# -# Regular expression used for parsing numeric strings. Additional -# comments: -# -# 1. Uncomment the two '\s*' lines to allow leading and/or trailing -# whitespace. But note that the specification disallows whitespace in -# a numeric string. -# -# 2. For finite numbers (not infinities and NaNs) the body of the -# number between the optional sign and the optional exponent must have -# at least one decimal digit, possibly after the decimal point. The -# lookahead expression '(?=\d|\.\d)' checks this. - -import re -_parser = re.compile(r""" # A numeric string consists of: -# \s* - (?P[-+])? # an optional sign, followed by either... - ( - (?=\d|\.\d) # ...a number (with at least one digit) - (?P\d*) # having a (possibly empty) integer part - (\.(?P\d*))? # followed by an optional fractional part - (E(?P[-+]?\d+))? # followed by an optional exponent, or... - | - Inf(inity)? # ...an infinity, or... - | - (?Ps)? # ...an (optionally signaling) - NaN # NaN - (?P\d*) # with (possibly empty) diagnostic info. - ) -# \s* - \Z -""", re.VERBOSE | re.IGNORECASE).match - -_all_zeros = re.compile('0*$').match -_exact_half = re.compile('50*$').match - -##### PEP3101 support functions ############################################## -# The functions in this section have little to do with the Decimal -# class, and could potentially be reused or adapted for other pure -# Python numeric classes that want to implement __format__ -# -# A format specifier for Decimal looks like: -# -# [[fill]align][sign][#][0][minimumwidth][,][.precision][type] - -_parse_format_specifier_regex = re.compile(r"""\A -(?: - (?P.)? - (?P[<>=^]) -)? -(?P[-+ ])? -(?P\#)? -(?P0)? -(?P(?!0)\d+)? -(?P,)? -(?:\.(?P0|(?!0)\d+))? -(?P[eEfFgGn%])? -\Z -""", re.VERBOSE|re.DOTALL) - -del re - -# The locale module is only needed for the 'n' format specifier. The -# rest of the PEP 3101 code functions quite happily without it, so we -# don't care too much if locale isn't present. -try: - import locale as _locale -except ImportError: - pass - -def _parse_format_specifier(format_spec, _localeconv=None): - """Parse and validate a format specifier. - - Turns a standard numeric format specifier into a dict, with the - following entries: - - fill: fill character to pad field to minimum width - align: alignment type, either '<', '>', '=' or '^' - sign: either '+', '-' or ' ' - minimumwidth: nonnegative integer giving minimum width - zeropad: boolean, indicating whether to pad with zeros - thousands_sep: string to use as thousands separator, or '' - grouping: grouping for thousands separators, in format - used by localeconv - decimal_point: string to use for decimal point - precision: nonnegative integer giving precision, or None - type: one of the characters 'eEfFgG%', or None - - """ - m = _parse_format_specifier_regex.match(format_spec) - if m is None: - raise ValueError("Invalid format specifier: " + format_spec) - - # get the dictionary - format_dict = m.groupdict() - - # zeropad; defaults for fill and alignment. If zero padding - # is requested, the fill and align fields should be absent. - fill = format_dict['fill'] - align = format_dict['align'] - format_dict['zeropad'] = (format_dict['zeropad'] is not None) - if format_dict['zeropad']: - if fill is not None: - raise ValueError("Fill character conflicts with '0'" - " in format specifier: " + format_spec) - if align is not None: - raise ValueError("Alignment conflicts with '0' in " - "format specifier: " + format_spec) - format_dict['fill'] = fill 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: - format_dict['sign'] = '-' - - # minimumwidth defaults to 0; precision remains None if not given - format_dict['minimumwidth'] = int(format_dict['minimumwidth'] or '0') - if format_dict['precision'] is not None: - format_dict['precision'] = int(format_dict['precision']) - - # if format type is 'g' or 'G' then a precision of 0 makes little - # sense; convert it to 1. Same if format type is unspecified. - if format_dict['precision'] == 0: - if format_dict['type'] is None or format_dict['type'] in 'gGn': - format_dict['precision'] = 1 - - # determine thousands separator, grouping, and decimal separator, and - # add appropriate entries to format_dict - if format_dict['type'] == 'n': - # apart from separators, 'n' behaves just like 'g' - format_dict['type'] = 'g' - if _localeconv is None: - _localeconv = _locale.localeconv() - if format_dict['thousands_sep'] is not None: - raise ValueError("Explicit thousands separator conflicts with " - "'n' type in format specifier: " + format_spec) - format_dict['thousands_sep'] = _localeconv['thousands_sep'] - format_dict['grouping'] = _localeconv['grouping'] - format_dict['decimal_point'] = _localeconv['decimal_point'] - else: - if format_dict['thousands_sep'] is None: - format_dict['thousands_sep'] = '' - format_dict['grouping'] = [3, 0] - format_dict['decimal_point'] = '.' - - return format_dict - -def _format_align(sign, body, spec): - """Given an unpadded, non-aligned numeric string 'body' and sign - string 'sign', add padding and alignment conforming to the given - format specifier dictionary 'spec' (as produced by - parse_format_specifier). - - """ - # how much extra space do we have to play with? - minimumwidth = spec['minimumwidth'] - fill = spec['fill'] - padding = fill*(minimumwidth - len(sign) - len(body)) - - align = spec['align'] - if align == '<': - result = sign + body + padding - elif align == '>': - result = padding + sign + body - elif align == '=': - result = sign + padding + body - elif align == '^': - half = len(padding)//2 - result = padding[:half] + sign + body + padding[half:] - else: - raise ValueError('Unrecognised alignment field') - - return result - -def _group_lengths(grouping): - """Convert a localeconv-style grouping into a (possibly infinite) - iterable of integers representing group lengths. - - """ - # The result from localeconv()['grouping'], and the input to this - # function, should be a list of integers in one of the - # following three forms: - # - # (1) an empty list, or - # (2) nonempty list of positive integers + [0] - # (3) list of positive integers + [locale.CHAR_MAX], or - - from itertools import chain, repeat - if not grouping: - return [] - elif grouping[-1] == 0 and len(grouping) >= 2: - return chain(grouping[:-1], repeat(grouping[-2])) - elif grouping[-1] == _locale.CHAR_MAX: - return grouping[:-1] - else: - raise ValueError('unrecognised format for grouping') - -def _insert_thousands_sep(digits, spec, min_width=1): - """Insert thousands separators into a digit string. - - spec is a dictionary whose keys should include 'thousands_sep' and - 'grouping'; typically it's the result of parsing the format - specifier using _parse_format_specifier. - - The min_width keyword argument gives the minimum length of the - result, which will be padded on the left with zeros if necessary. - - If necessary, the zero padding adds an extra '0' on the left to - avoid a leading thousands separator. For example, inserting - commas every three digits in '123456', with min_width=8, gives - '0,123,456', even though that has length 9. - - """ - - sep = spec['thousands_sep'] - grouping = spec['grouping'] - - groups = [] - for l in _group_lengths(grouping): - if l <= 0: - raise ValueError("group length should be positive") - # max(..., 1) forces at least 1 digit to the left of a separator - l = min(max(len(digits), min_width, 1), l) - groups.append('0'*(l - len(digits)) + digits[-l:]) - digits = digits[:-l] - min_width -= l - if not digits and min_width <= 0: - break - min_width -= len(sep) - else: - l = max(len(digits), min_width, 1) - groups.append('0'*(l - len(digits)) + digits[-l:]) - return sep.join(reversed(groups)) - -def _format_sign(is_negative, spec): - """Determine sign character.""" - - if is_negative: - return '-' - elif spec['sign'] in ' +': - return spec['sign'] - else: - return '' - -def _format_number(is_negative, intpart, fracpart, exp, spec): - """Format a number, given the following data: - - is_negative: true if the number is negative, else false - intpart: string of digits that must appear before the decimal point - fracpart: string of digits that must come after the point - exp: exponent, as an integer - spec: dictionary resulting from parsing the format specifier - - This function uses the information in spec to: - insert separators (decimal separator and thousands separators) - format the sign - format the exponent - add trailing '%' for the '%' type - zero-pad if necessary - fill and align if necessary - """ - - sign = _format_sign(is_negative, spec) - - if fracpart or spec['alt']: - fracpart = spec['decimal_point'] + fracpart - - if exp != 0 or spec['type'] in 'eE': - echar = {'E': 'E', 'e': 'e', 'G': 'E', 'g': 'e'}[spec['type']] - fracpart += "{0}{1:+}".format(echar, exp) - if spec['type'] == '%': - fracpart += '%' - - if spec['zeropad']: - min_width = spec['minimumwidth'] - len(fracpart) - len(sign) - else: - min_width = 0 - intpart = _insert_thousands_sep(intpart, spec, min_width) - - return _format_align(sign, intpart+fracpart, spec) - - -##### Useful Constants (internal use only) ################################ - -# Reusable defaults -_Infinity = Decimal('Inf') -_NegativeInfinity = Decimal('-Inf') -_NaN = Decimal('NaN') -_Zero = Decimal(0) -_One = Decimal(1) -_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 -_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 - -# _PyHASH_10INV is the inverse of 10 modulo the prime _PyHASH_MODULUS -_PyHASH_10INV = pow(10, _PyHASH_MODULUS - 2, _PyHASH_MODULUS) -del sys try: - import _decimal -except ImportError: - pass -else: - s1 = set(dir()) - s2 = set(dir(_decimal)) - for name in s1 - s2: - del globals()[name] - del s1, s2, name from _decimal import * - -if __name__ == '__main__': - import doctest, decimal - doctest.testmod(decimal) + from _decimal import __doc__ + from _decimal import __version__ + from _decimal import __libmpdec_version__ +except ImportError: + from _pydecimal import * + from _pydecimal import __doc__ + from _pydecimal import __version__ + from _pydecimal import __libmpdec_version__ diff --git a/lib/difflib.py b/lib/difflib.py index 605b9178..b4ec3350 100644 --- a/lib/difflib.py +++ b/lib/difflib.py @@ -28,9 +28,9 @@ __all__ = ['get_close_matches', 'ndiff', 'restore', 'SequenceMatcher', 'Differ','IS_CHARACTER_JUNK', 'IS_LINE_JUNK', 'context_diff', - 'unified_diff', 'HtmlDiff', 'Match'] + 'unified_diff', 'diff_bytes', 'HtmlDiff', 'Match'] -import heapq +from heapq import nlargest as _nlargest from collections import namedtuple as _namedtuple Match = _namedtuple('Match', 'a b size') @@ -729,7 +729,7 @@ def get_close_matches(word, possibilities, n=3, cutoff=0.6): result.append((s.ratio(), x)) # Move the best scorers to head of list - result = heapq.nlargest(n, result) + result = _nlargest(n, result) # Strip scores for the best n matches return [x for score, x in result] @@ -852,10 +852,9 @@ def __init__(self, linejunk=None, charjunk=None): and return true iff the string is junk. The module-level function `IS_LINE_JUNK` may be used to filter out lines without visible characters, except for at most one splat ('#'). It is recommended - to leave linejunk None; as of Python 2.3, the underlying - SequenceMatcher class has grown an adaptive notion of "noise" lines - that's better than any static definition the author has ever been - able to craft. + to leave linejunk None; the underlying SequenceMatcher class has + an adaptive notion of "noise" lines that's better than any static + definition the author has ever been able to craft. - `charjunk`: A function that should accept a string of length 1. The module-level function `IS_CHARACTER_JUNK` may be used to filter out @@ -1175,6 +1174,7 @@ def unified_diff(a, b, fromfile='', tofile='', fromfiledate='', four """ + _check_types(a, b, fromfile, tofile, fromfiledate, tofiledate, lineterm) started = False for group in SequenceMatcher(None,a,b).get_grouped_opcodes(n): if not started: @@ -1262,6 +1262,7 @@ def context_diff(a, b, fromfile='', tofile='', four """ + _check_types(a, b, fromfile, tofile, fromfiledate, tofiledate, lineterm) prefix = dict(insert='+ ', delete='- ', replace='! ', equal=' ') started = False for group in SequenceMatcher(None,a,b).get_grouped_opcodes(n): @@ -1293,22 +1294,70 @@ def context_diff(a, b, fromfile='', tofile='', for line in b[j1:j2]: yield prefix[tag] + line +def _check_types(a, b, *args): + # Checking types is weird, but the alternative is garbled output when + # someone passes mixed bytes and str to {unified,context}_diff(). E.g. + # without this check, passing filenames as bytes results in output like + # --- b'oldfile.txt' + # +++ b'newfile.txt' + # because of how str.format() incorporates bytes objects. + if a and not isinstance(a[0], str): + raise TypeError('lines to compare must be str, not %s (%r)' % + (type(a[0]).__name__, a[0])) + if b and not isinstance(b[0], str): + raise TypeError('lines to compare must be str, not %s (%r)' % + (type(b[0]).__name__, b[0])) + for arg in args: + if not isinstance(arg, str): + raise TypeError('all arguments must be str, not: %r' % (arg,)) + +def diff_bytes(dfunc, a, b, fromfile=b'', tofile=b'', + fromfiledate=b'', tofiledate=b'', n=3, lineterm=b'\n'): + r""" + Compare `a` and `b`, two sequences of lines represented as bytes rather + than str. This is a wrapper for `dfunc`, which is typically either + unified_diff() or context_diff(). Inputs are losslessly converted to + strings so that `dfunc` only has to worry about strings, and encoded + back to bytes on return. This is necessary to compare files with + unknown or inconsistent encoding. All other inputs (except `n`) must be + bytes rather than str. + """ + def decode(s): + try: + return s.decode('ascii', 'surrogateescape') + except AttributeError as err: + msg = ('all arguments must be bytes, not %s (%r)' % + (type(s).__name__, s)) + raise TypeError(msg) from err + a = list(map(decode, a)) + b = list(map(decode, b)) + fromfile = decode(fromfile) + tofile = decode(tofile) + fromfiledate = decode(fromfiledate) + tofiledate = decode(tofiledate) + lineterm = decode(lineterm) + + lines = dfunc(a, b, fromfile, tofile, fromfiledate, tofiledate, n, lineterm) + for line in lines: + yield line.encode('ascii', 'surrogateescape') + def ndiff(a, b, linejunk=None, charjunk=IS_CHARACTER_JUNK): r""" Compare `a` and `b` (lists of strings); return a `Differ`-style delta. Optional keyword parameters `linejunk` and `charjunk` are for filter - functions (or None): + functions, or can be None: - - linejunk: A function that should accept a single string argument, and + - linejunk: A function that should accept a single string argument and return true iff the string is junk. The default is None, and is - recommended; as of Python 2.3, an adaptive notion of "noise" lines is - used that does a good job on its own. + recommended; the underlying SequenceMatcher class has an adaptive + notion of "noise" lines. - - charjunk: A function that should accept a string of length 1. The - default is module-level function IS_CHARACTER_JUNK, which filters out - whitespace characters (a blank or tab; note: bad idea to include newline - in this!). + - charjunk: A function that accepts a character (string of length + 1), and returns true iff the character is junk. The default is + the module-level function IS_CHARACTER_JUNK, which filters out + whitespace characters (a blank or tab; note: it's a bad idea to + include newline in this!). Tools/scripts/ndiff.py is a command-line front-end to this function. @@ -1410,7 +1459,7 @@ def record_sub_info(match_object,sub_info=sub_info): change_re.sub(record_sub_info,markers) # process each tuple inserting our special marks that won't be # noticed by an xml/html escaper. - for key,(begin,end) in sub_info[::-1]: + for key,(begin,end) in reversed(sub_info): text = text[0:begin]+'\0'+key+text[begin:end]+'\1'+text[end:] text = text[2:] # Handle case of add/delete entire line @@ -1448,10 +1497,7 @@ def _line_iterator(): # are a concatenation of the first character of each of the 4 lines # so we can do some very readable comparisons. while len(lines) < 4: - try: - lines.append(next(diff_lines_iterator)) - except StopIteration: - lines.append('X') + lines.append(next(diff_lines_iterator, 'X')) s = ''.join([line[0] for line in lines]) if s.startswith('X'): # When no more lines, pump out any remaining blank lines so the @@ -1514,7 +1560,7 @@ def _line_iterator(): num_blanks_to_yield -= 1 yield ('','\n'),None,True if s.startswith('X'): - raise StopIteration + return else: yield from_line,to_line,True @@ -1536,7 +1582,10 @@ def _line_pair_iterator(): while True: # Collecting lines of text until we have a from/to pair while (len(fromlines)==0 or len(tolines)==0): - from_line, to_line, found_diff = next(line_iterator) + try: + from_line, to_line, found_diff = next(line_iterator) + except StopIteration: + return if from_line is not None: fromlines.append((from_line,found_diff)) if to_line is not None: @@ -1550,8 +1599,7 @@ def _line_pair_iterator(): # them up without doing anything else with them. line_pair_iterator = _line_pair_iterator() if context is None: - while True: - yield next(line_pair_iterator) + yield from line_pair_iterator # Handle case where user wants context differencing. We must do some # storage of lines until we know for sure that they are to be yielded. else: @@ -1564,7 +1612,10 @@ def _line_pair_iterator(): index, contextLines = 0, [None]*(context) found_diff = False while(found_diff is False): - from_line, to_line, found_diff = next(line_pair_iterator) + try: + from_line, to_line, found_diff = next(line_pair_iterator) + except StopIteration: + return i = index % context contextLines[i] = (from_line, to_line, found_diff) index += 1 @@ -1601,7 +1652,7 @@ def _line_pair_iterator(): + content="text/html; charset=%(charset)s" /> @@ -1679,7 +1730,7 @@ def __init__(self,tabsize=8,wrapcolumn=None,linejunk=None, tabsize -- tab stop spacing, defaults to 8. wrapcolumn -- column number where lines are broken and wrapped, defaults to None where lines are not wrapped. - linejunk,charjunk -- keyword arguments passed into ndiff() (used to by + linejunk,charjunk -- keyword arguments passed into ndiff() (used by HtmlDiff() to generate the side by side HTML differences). See ndiff() documentation for argument default values and descriptions. """ @@ -1688,8 +1739,8 @@ def __init__(self,tabsize=8,wrapcolumn=None,linejunk=None, self._linejunk = linejunk self._charjunk = charjunk - def make_file(self,fromlines,tolines,fromdesc='',todesc='',context=False, - numlines=5): + def make_file(self, fromlines, tolines, fromdesc='', todesc='', + context=False, numlines=5, *, charset='utf-8'): """Returns HTML file of side by side comparison with change highlights Arguments: @@ -1704,13 +1755,16 @@ def make_file(self,fromlines,tolines,fromdesc='',todesc='',context=False, When context is False, controls the number of lines to place the "next" link anchors before the next change (so click of "next" link jumps to just before the change). + charset -- charset of the HTML document """ - return self._file_template % dict( - styles = self._styles, - legend = self._legend, - table = self.make_table(fromlines,tolines,fromdesc,todesc, - context=context,numlines=numlines)) + return (self._file_template % dict( + styles=self._styles, + legend=self._legend, + table=self.make_table(fromlines, tolines, fromdesc, todesc, + context=context, numlines=numlines), + charset=charset + )).encode(charset, 'xmlcharrefreplace').decode(charset) def _tab_newline_replace(self,fromlines,tolines): """Returns from/to line lists with tabs expanded and newlines removed. diff --git a/lib/dis.py b/lib/dis.py index 81cbe7f4..f7e3c7f8 100644 --- a/lib/dis.py +++ b/lib/dis.py @@ -13,7 +13,8 @@ "get_instructions", "Instruction", "Bytecode"] + _opcodes_all del _opcodes_all -_have_code = (types.MethodType, types.FunctionType, types.CodeType, type) +_have_code = (types.MethodType, types.FunctionType, types.CodeType, + classmethod, staticmethod, type) def _try_compile(source, name): """Attempts to compile the given source, first as an expression and @@ -29,7 +30,7 @@ def _try_compile(source, name): return c def dis(x=None, *, file=None): - """Disassemble classes, methods, functions, or code. + """Disassemble classes, methods, functions, generators, or code. With no argument, disassemble the last traceback. @@ -41,6 +42,8 @@ def dis(x=None, *, file=None): x = x.__func__ if hasattr(x, '__code__'): # Function x = x.__code__ + if hasattr(x, 'gi_code'): # Generator + x = x.gi_code if hasattr(x, '__dict__'): # Class or module items = sorted(x.__dict__.items()) for name, x1 in items: @@ -82,6 +85,8 @@ def distb(tb=None, *, file=None): 16: "NESTED", 32: "GENERATOR", 64: "NOFREE", + 128: "COROUTINE", + 256: "ITERABLE_COROUTINE", } def pretty_flags(flags): @@ -99,11 +104,13 @@ def pretty_flags(flags): return ", ".join(names) def _get_code_object(x): - """Helper to handle methods, functions, strings and raw code objects""" + """Helper to handle methods, functions, generators, strings and raw code objects""" if hasattr(x, '__func__'): # Method x = x.__func__ if hasattr(x, '__code__'): # Function x = x.__code__ + if hasattr(x, 'gi_code'): # Generator + x = x.gi_code if isinstance(x, str): # Source code x = _try_compile(x, "") if hasattr(x, 'co_code'): # Code object @@ -268,33 +275,19 @@ def _get_instructions_bytes(code, varnames=None, names=None, constants=None, """ labels = findlabels(code) - extended_arg = 0 starts_line = None free = None - # enumerate() is not an option, since we sometimes process - # multiple elements on a single pass through the loop - n = len(code) - i = 0 - while i < n: - op = code[i] - offset = i + for offset, op, arg in _unpack_opargs(code): if linestarts is not None: - starts_line = linestarts.get(i, None) + starts_line = linestarts.get(offset, None) if starts_line is not None: starts_line += line_offset - is_jump_target = i in labels - i = i+1 - arg = None + is_jump_target = offset in labels argval = None argrepr = '' - if op >= HAVE_ARGUMENT: - arg = code[i] + code[i+1]*256 + extended_arg - extended_arg = 0 - i = i+2 - if op == EXTENDED_ARG: - extended_arg = arg*65536 + if arg is not None: # Set argval to the dereferenced value of the argument when - # availabe, and argrepr to the string representation of argval. + # available, and argrepr to the string representation of argval. # _disassemble_bytes needs the string repr of the # raw name index for LOAD_GLOBAL, LOAD_CONST, etc. argval = arg @@ -303,7 +296,7 @@ def _get_instructions_bytes(code, varnames=None, names=None, constants=None, elif op in hasname: argval, argrepr = _get_name_info(arg, names) elif op in hasjrel: - argval = i + arg + argval = offset + 3 + arg argrepr = "to " + repr(argval) elif op in haslocal: argval, argrepr = _get_name_info(arg, varnames) @@ -313,7 +306,7 @@ def _get_instructions_bytes(code, varnames=None, names=None, constants=None, elif op in hasfree: argval, argrepr = _get_name_info(arg, cells) elif op in hasnargs: - argrepr = "%d positional, %d keyword pair" % (code[i-2], code[i-1]) + argrepr = "%d positional, %d keyword pair" % (arg%256, arg//256) yield Instruction(opname[op], op, arg, argval, argrepr, offset, starts_line, is_jump_target) @@ -349,26 +342,37 @@ def _disassemble_str(source, *, file=None): disco = disassemble # XXX For backwards compatibility -def findlabels(code): - """Detect all offsets in a byte code which are jump targets. - - Return the list of offsets. - - """ - labels = [] +def _unpack_opargs(code): # enumerate() is not an option, since we sometimes process # multiple elements on a single pass through the loop + extended_arg = 0 n = len(code) i = 0 while i < n: op = code[i] + offset = i i = i+1 + arg = None if op >= HAVE_ARGUMENT: - arg = code[i] + code[i+1]*256 + arg = code[i] + code[i+1]*256 + extended_arg + extended_arg = 0 i = i+2 + if op == EXTENDED_ARG: + extended_arg = arg*65536 + yield (offset, op, arg) + +def findlabels(code): + """Detect all offsets in a byte code which are jump targets. + + Return the list of offsets. + + """ + labels = [] + for offset, op, arg in _unpack_opargs(code): + if arg is not None: label = -1 if op in hasjrel: - label = i+arg + label = offset + 3 + arg elif op in hasjabs: label = arg if label >= 0: diff --git a/lib/distutils/_msvccompiler.py b/lib/distutils/_msvccompiler.py new file mode 100644 index 00000000..b120273f --- /dev/null +++ b/lib/distutils/_msvccompiler.py @@ -0,0 +1,533 @@ +"""distutils._msvccompiler + +Contains MSVCCompiler, an implementation of the abstract CCompiler class +for Microsoft Visual Studio 2015. + +The module is compatible with VS 2015 and later. You can find legacy support +for older versions in distutils.msvc9compiler and distutils.msvccompiler. +""" + +# Written by Perry Stoll +# hacked by Robin Becker and Thomas Heller to do a better job of +# finding DevStudio (through the registry) +# ported to VS 2005 and VS 2008 by Christian Heimes +# ported to VS 2015 by Steve Dower + +import os +import shutil +import stat +import subprocess + +from distutils.errors import DistutilsExecError, DistutilsPlatformError, \ + CompileError, LibError, LinkError +from distutils.ccompiler import CCompiler, gen_lib_options +from distutils import log +from distutils.util import get_platform + +import winreg +from itertools import count + +def _find_vcvarsall(plat_spec): + try: + key = winreg.OpenKeyEx( + winreg.HKEY_LOCAL_MACHINE, + r"Software\Microsoft\VisualStudio\SxS\VC7", + access=winreg.KEY_READ | winreg.KEY_WOW64_32KEY + ) + except OSError: + log.debug("Visual C++ is not registered") + return None, None + + with key: + best_version = 0 + best_dir = None + for i in count(): + try: + v, vc_dir, vt = winreg.EnumValue(key, i) + except OSError: + break + if v and vt == winreg.REG_SZ and os.path.isdir(vc_dir): + try: + version = int(float(v)) + except (ValueError, TypeError): + continue + if version >= 14 and version > best_version: + best_version, best_dir = version, vc_dir + if not best_version: + log.debug("No suitable Visual C++ version found") + return None, None + + vcvarsall = os.path.join(best_dir, "vcvarsall.bat") + if not os.path.isfile(vcvarsall): + log.debug("%s cannot be found", vcvarsall) + return None, None + + vcruntime = None + vcruntime_spec = _VCVARS_PLAT_TO_VCRUNTIME_REDIST.get(plat_spec) + if vcruntime_spec: + vcruntime = os.path.join(best_dir, + vcruntime_spec.format(best_version)) + if not os.path.isfile(vcruntime): + log.debug("%s cannot be found", vcruntime) + vcruntime = None + + return vcvarsall, vcruntime + +def _get_vc_env(plat_spec): + if os.getenv("DISTUTILS_USE_SDK"): + return { + key.lower(): value + for key, value in os.environ.items() + } + + vcvarsall, vcruntime = _find_vcvarsall(plat_spec) + if not vcvarsall: + raise DistutilsPlatformError("Unable to find vcvarsall.bat") + + try: + out = subprocess.check_output( + 'cmd /u /c "{}" {} && set'.format(vcvarsall, plat_spec), + stderr=subprocess.STDOUT, + ).decode('utf-16le', errors='replace') + except subprocess.CalledProcessError as exc: + log.error(exc.output) + raise DistutilsPlatformError("Error executing {}" + .format(exc.cmd)) + + env = { + key.lower(): value + for key, _, value in + (line.partition('=') for line in out.splitlines()) + if key and value + } + + if vcruntime: + env['py_vcruntime_redist'] = vcruntime + return env + +def _find_exe(exe, paths=None): + """Return path to an MSVC executable program. + + Tries to find the program in several places: first, one of the + MSVC program search paths from the registry; next, the directories + in the PATH environment variable. If any of those work, return an + absolute path that is known to exist. If none of them work, just + return the original program name, 'exe'. + """ + if not paths: + paths = os.getenv('path').split(os.pathsep) + for p in paths: + fn = os.path.join(os.path.abspath(p), exe) + if os.path.isfile(fn): + return fn + return exe + +# A map keyed by get_platform() return values to values accepted by +# 'vcvarsall.bat'. Always cross-compile from x86 to work with the +# lighter-weight MSVC installs that do not include native 64-bit tools. +PLAT_TO_VCVARS = { + 'win32' : 'x86', + 'win-amd64' : 'x86_amd64', +} + +# A map keyed by get_platform() return values to the file under +# the VC install directory containing the vcruntime redistributable. +_VCVARS_PLAT_TO_VCRUNTIME_REDIST = { + 'x86' : 'redist\\x86\\Microsoft.VC{0}0.CRT\\vcruntime{0}0.dll', + 'amd64' : 'redist\\x64\\Microsoft.VC{0}0.CRT\\vcruntime{0}0.dll', + 'x86_amd64' : 'redist\\x64\\Microsoft.VC{0}0.CRT\\vcruntime{0}0.dll', +} + +# A set containing the DLLs that are guaranteed to be available for +# all micro versions of this Python version. Known extension +# dependencies that are not in this set will be copied to the output +# path. +_BUNDLED_DLLS = frozenset(['vcruntime140.dll']) + +class MSVCCompiler(CCompiler) : + """Concrete class that implements an interface to Microsoft Visual C++, + as defined by the CCompiler abstract class.""" + + compiler_type = 'msvc' + + # Just set this so CCompiler's constructor doesn't barf. We currently + # don't use the 'set_executables()' bureaucracy provided by CCompiler, + # as it really isn't necessary for this sort of single-compiler class. + # Would be nice to have a consistent interface with UnixCCompiler, + # though, so it's worth thinking about. + executables = {} + + # Private class data (need to distinguish C from C++ source for compiler) + _c_extensions = ['.c'] + _cpp_extensions = ['.cc', '.cpp', '.cxx'] + _rc_extensions = ['.rc'] + _mc_extensions = ['.mc'] + + # Needed for the filename generation methods provided by the + # base class, CCompiler. + src_extensions = (_c_extensions + _cpp_extensions + + _rc_extensions + _mc_extensions) + res_extension = '.res' + obj_extension = '.obj' + static_lib_extension = '.lib' + shared_lib_extension = '.dll' + static_lib_format = shared_lib_format = '%s%s' + exe_extension = '.exe' + + + def __init__(self, verbose=0, dry_run=0, force=0): + CCompiler.__init__ (self, verbose, dry_run, force) + # target platform (.plat_name is consistent with 'bdist') + self.plat_name = None + self.initialized = False + + def initialize(self, plat_name=None): + # multi-init means we would need to check platform same each time... + assert not self.initialized, "don't init multiple times" + if plat_name is None: + plat_name = get_platform() + # sanity check for platforms to prevent obscure errors later. + if plat_name not in PLAT_TO_VCVARS: + raise DistutilsPlatformError("--plat-name must be one of {}" + .format(tuple(PLAT_TO_VCVARS))) + + # Get the vcvarsall.bat spec for the requested platform. + plat_spec = PLAT_TO_VCVARS[plat_name] + + vc_env = _get_vc_env(plat_spec) + if not vc_env: + raise DistutilsPlatformError("Unable to find a compatible " + "Visual Studio installation.") + + self._paths = vc_env.get('path', '') + paths = self._paths.split(os.pathsep) + self.cc = _find_exe("cl.exe", paths) + self.linker = _find_exe("link.exe", paths) + self.lib = _find_exe("lib.exe", paths) + self.rc = _find_exe("rc.exe", paths) # resource compiler + self.mc = _find_exe("mc.exe", paths) # message compiler + self.mt = _find_exe("mt.exe", paths) # message compiler + self._vcruntime_redist = vc_env.get('py_vcruntime_redist', '') + + for dir in vc_env.get('include', '').split(os.pathsep): + if dir: + self.add_include_dir(dir) + + for dir in vc_env.get('lib', '').split(os.pathsep): + if dir: + self.add_library_dir(dir) + + self.preprocess_options = None + # If vcruntime_redist is available, link against it dynamically. Otherwise, + # use /MT[d] to build statically, then switch from libucrt[d].lib to ucrt[d].lib + # later to dynamically link to ucrtbase but not vcruntime. + self.compile_options = [ + '/nologo', '/Ox', '/W3', '/GL', '/DNDEBUG' + ] + self.compile_options.append('/MD' if self._vcruntime_redist else '/MT') + + self.compile_options_debug = [ + '/nologo', '/Od', '/MDd', '/Zi', '/W3', '/D_DEBUG' + ] + + ldflags = [ + '/nologo', '/INCREMENTAL:NO', '/LTCG' + ] + if not self._vcruntime_redist: + ldflags.extend(('/nodefaultlib:libucrt.lib', 'ucrt.lib')) + + ldflags_debug = [ + '/nologo', '/INCREMENTAL:NO', '/LTCG', '/DEBUG:FULL' + ] + + self.ldflags_exe = [*ldflags, '/MANIFEST:EMBED,ID=1'] + self.ldflags_exe_debug = [*ldflags_debug, '/MANIFEST:EMBED,ID=1'] + self.ldflags_shared = [*ldflags, '/DLL', '/MANIFEST:EMBED,ID=2', '/MANIFESTUAC:NO'] + self.ldflags_shared_debug = [*ldflags_debug, '/DLL', '/MANIFEST:EMBED,ID=2', '/MANIFESTUAC:NO'] + self.ldflags_static = [*ldflags] + self.ldflags_static_debug = [*ldflags_debug] + + self._ldflags = { + (CCompiler.EXECUTABLE, None): self.ldflags_exe, + (CCompiler.EXECUTABLE, False): self.ldflags_exe, + (CCompiler.EXECUTABLE, True): self.ldflags_exe_debug, + (CCompiler.SHARED_OBJECT, None): self.ldflags_shared, + (CCompiler.SHARED_OBJECT, False): self.ldflags_shared, + (CCompiler.SHARED_OBJECT, True): self.ldflags_shared_debug, + (CCompiler.SHARED_LIBRARY, None): self.ldflags_static, + (CCompiler.SHARED_LIBRARY, False): self.ldflags_static, + (CCompiler.SHARED_LIBRARY, True): self.ldflags_static_debug, + } + + self.initialized = True + + # -- Worker methods ------------------------------------------------ + + def object_filenames(self, + source_filenames, + strip_dir=0, + output_dir=''): + ext_map = { + **{ext: self.obj_extension for ext in self.src_extensions}, + **{ext: self.res_extension for ext in self._rc_extensions + self._mc_extensions}, + } + + output_dir = output_dir or '' + + def make_out_path(p): + base, ext = os.path.splitext(p) + if strip_dir: + base = os.path.basename(base) + else: + _, base = os.path.splitdrive(base) + if base.startswith((os.path.sep, os.path.altsep)): + base = base[1:] + try: + # XXX: This may produce absurdly long paths. We should check + # the length of the result and trim base until we fit within + # 260 characters. + return os.path.join(output_dir, base + ext_map[ext]) + except LookupError: + # Better to raise an exception instead of silently continuing + # and later complain about sources and targets having + # different lengths + raise CompileError("Don't know how to compile {}".format(p)) + + return list(map(make_out_path, source_filenames)) + + + def compile(self, sources, + output_dir=None, macros=None, include_dirs=None, debug=0, + extra_preargs=None, extra_postargs=None, depends=None): + + if not self.initialized: + self.initialize() + compile_info = self._setup_compile(output_dir, macros, include_dirs, + sources, depends, extra_postargs) + macros, objects, extra_postargs, pp_opts, build = compile_info + + compile_opts = extra_preargs or [] + compile_opts.append('/c') + if debug: + compile_opts.extend(self.compile_options_debug) + else: + compile_opts.extend(self.compile_options) + + + add_cpp_opts = False + + for obj in objects: + try: + src, ext = build[obj] + except KeyError: + continue + if debug: + # pass the full pathname to MSVC in debug mode, + # this allows the debugger to find the source file + # without asking the user to browse for it + src = os.path.abspath(src) + + if ext in self._c_extensions: + input_opt = "/Tc" + src + elif ext in self._cpp_extensions: + input_opt = "/Tp" + src + add_cpp_opts = True + elif ext in self._rc_extensions: + # compile .RC to .RES file + input_opt = src + output_opt = "/fo" + obj + try: + self.spawn([self.rc] + pp_opts + [output_opt, input_opt]) + except DistutilsExecError as msg: + raise CompileError(msg) + continue + elif ext in self._mc_extensions: + # Compile .MC to .RC file to .RES file. + # * '-h dir' specifies the directory for the + # generated include file + # * '-r dir' specifies the target directory of the + # generated RC file and the binary message resource + # it includes + # + # For now (since there are no options to change this), + # we use the source-directory for the include file and + # the build directory for the RC file and message + # resources. This works at least for win32all. + h_dir = os.path.dirname(src) + rc_dir = os.path.dirname(obj) + try: + # first compile .MC to .RC and .H file + self.spawn([self.mc, '-h', h_dir, '-r', rc_dir, src]) + base, _ = os.path.splitext(os.path.basename (src)) + rc_file = os.path.join(rc_dir, base + '.rc') + # then compile .RC to .RES file + self.spawn([self.rc, "/fo" + obj, rc_file]) + + except DistutilsExecError as msg: + raise CompileError(msg) + continue + else: + # how to handle this file? + raise CompileError("Don't know how to compile {} to {}" + .format(src, obj)) + + args = [self.cc] + compile_opts + pp_opts + if add_cpp_opts: + args.append('/EHsc') + args.append(input_opt) + args.append("/Fo" + obj) + args.extend(extra_postargs) + + try: + self.spawn(args) + except DistutilsExecError as msg: + raise CompileError(msg) + + return objects + + + def create_static_lib(self, + objects, + output_libname, + output_dir=None, + debug=0, + target_lang=None): + + if not self.initialized: + self.initialize() + objects, output_dir = self._fix_object_args(objects, output_dir) + output_filename = self.library_filename(output_libname, + output_dir=output_dir) + + if self._need_link(objects, output_filename): + lib_args = objects + ['/OUT:' + output_filename] + if debug: + pass # XXX what goes here? + try: + log.debug('Executing "%s" %s', self.lib, ' '.join(lib_args)) + self.spawn([self.lib] + lib_args) + except DistutilsExecError as msg: + raise LibError(msg) + else: + log.debug("skipping %s (up-to-date)", output_filename) + + + def link(self, + target_desc, + objects, + output_filename, + output_dir=None, + libraries=None, + library_dirs=None, + runtime_library_dirs=None, + export_symbols=None, + debug=0, + extra_preargs=None, + extra_postargs=None, + build_temp=None, + target_lang=None): + + if not self.initialized: + self.initialize() + objects, output_dir = self._fix_object_args(objects, output_dir) + fixed_args = self._fix_lib_args(libraries, library_dirs, + runtime_library_dirs) + libraries, library_dirs, runtime_library_dirs = fixed_args + + if runtime_library_dirs: + self.warn("I don't know what to do with 'runtime_library_dirs': " + + str(runtime_library_dirs)) + + lib_opts = gen_lib_options(self, + library_dirs, runtime_library_dirs, + libraries) + if output_dir is not None: + output_filename = os.path.join(output_dir, output_filename) + + if self._need_link(objects, output_filename): + ldflags = self._ldflags[target_desc, debug] + + export_opts = ["/EXPORT:" + sym for sym in (export_symbols or [])] + + ld_args = (ldflags + lib_opts + export_opts + + objects + ['/OUT:' + output_filename]) + + # The MSVC linker generates .lib and .exp files, which cannot be + # suppressed by any linker switches. The .lib files may even be + # needed! Make sure they are generated in the temporary build + # directory. Since they have different names for debug and release + # builds, they can go into the same directory. + build_temp = os.path.dirname(objects[0]) + if export_symbols is not None: + (dll_name, dll_ext) = os.path.splitext( + os.path.basename(output_filename)) + implib_file = os.path.join( + build_temp, + self.library_filename(dll_name)) + ld_args.append ('/IMPLIB:' + implib_file) + + if extra_preargs: + ld_args[:0] = extra_preargs + if extra_postargs: + ld_args.extend(extra_postargs) + + output_dir = os.path.dirname(os.path.abspath(output_filename)) + self.mkpath(output_dir) + try: + log.debug('Executing "%s" %s', self.linker, ' '.join(ld_args)) + self.spawn([self.linker] + ld_args) + self._copy_vcruntime(output_dir) + except DistutilsExecError as msg: + raise LinkError(msg) + else: + log.debug("skipping %s (up-to-date)", output_filename) + + def _copy_vcruntime(self, output_dir): + vcruntime = self._vcruntime_redist + if not vcruntime or not os.path.isfile(vcruntime): + return + + if os.path.basename(vcruntime).lower() in _BUNDLED_DLLS: + return + + log.debug('Copying "%s"', vcruntime) + vcruntime = shutil.copy(vcruntime, output_dir) + os.chmod(vcruntime, stat.S_IWRITE) + + def spawn(self, cmd): + old_path = os.getenv('path') + try: + os.environ['path'] = self._paths + return super().spawn(cmd) + finally: + os.environ['path'] = old_path + + # -- Miscellaneous methods ----------------------------------------- + # These are all used by the 'gen_lib_options() function, in + # ccompiler.py. + + def library_dir_option(self, dir): + return "/LIBPATH:" + dir + + def runtime_library_dir_option(self, dir): + raise DistutilsPlatformError( + "don't know how to set runtime library search path for MSVC") + + def library_option(self, lib): + return self.library_filename(lib) + + def find_library_file(self, dirs, lib, debug=0): + # Prefer a debugging library if found (and requested), but deal + # with it if we don't have one. + if debug: + try_names = [lib + "_d", lib] + else: + try_names = [lib] + for dir in dirs: + for name in try_names: + libfile = os.path.join(dir, self.library_filename(name)) + if os.path.isfile(libfile): + return libfile + else: + # Oops, didn't find it in *any* of 'dirs' + return None diff --git a/lib/distutils/archive_util.py b/lib/distutils/archive_util.py index 4470bb02..bed13849 100644 --- a/lib/distutils/archive_util.py +++ b/lib/distutils/archive_util.py @@ -57,26 +57,28 @@ def make_tarball(base_name, base_dir, compress="gzip", verbose=0, dry_run=0, """Create a (possibly compressed) tar file from all the files under 'base_dir'. - 'compress' must be "gzip" (the default), "compress", "bzip2", or None. - (compress will be deprecated in Python 3.2) + 'compress' must be "gzip" (the default), "bzip2", "xz", "compress", or + None. ("compress" will be deprecated in Python 3.2) 'owner' and 'group' can be used to define an owner and a group for the archive that is being built. If not provided, the current owner and group will be used. The output tar file will be named 'base_dir' + ".tar", possibly plus - the appropriate compression extension (".gz", ".bz2" or ".Z"). + the appropriate compression extension (".gz", ".bz2", ".xz" or ".Z"). Returns the output filename. """ - tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', None: '', 'compress': ''} - compress_ext = {'gzip': '.gz', 'bzip2': '.bz2', 'compress': '.Z'} + tar_compression = {'gzip': 'gz', 'bzip2': 'bz2', 'xz': 'xz', None: '', + 'compress': ''} + compress_ext = {'gzip': '.gz', 'bzip2': '.bz2', 'xz': '.xz', + 'compress': '.Z'} # flags for compression program, each element of list will be an argument if compress is not None and compress not in compress_ext.keys(): raise ValueError( - "bad value for 'compress': must be None, 'gzip', 'bzip2' " - "or 'compress'") + "bad value for 'compress': must be None, 'gzip', 'bzip2', " + "'xz' or 'compress'") archive_name = base_name + '.tar' if compress != 'compress': @@ -177,6 +179,7 @@ def make_zipfile(base_name, base_dir, verbose=0, dry_run=0): ARCHIVE_FORMATS = { 'gztar': (make_tarball, [('compress', 'gzip')], "gzip'ed tar-file"), 'bztar': (make_tarball, [('compress', 'bzip2')], "bzip2'ed tar-file"), + 'xztar': (make_tarball, [('compress', 'xz')], "xz'ed tar-file"), 'ztar': (make_tarball, [('compress', 'compress')], "compressed tar file"), 'tar': (make_tarball, [('compress', None)], "uncompressed tar file"), 'zip': (make_zipfile, [],"ZIP file") @@ -197,8 +200,8 @@ def make_archive(base_name, format, root_dir=None, base_dir=None, verbose=0, """Create an archive file (eg. zip or tar). 'base_name' is the name of the file to create, minus any format-specific - extension; 'format' is the archive format: one of "zip", "tar", "ztar", - or "gztar". + extension; 'format' is the archive format: one of "zip", "tar", "gztar", + "bztar", "xztar", or "ztar". 'root_dir' is a directory that will be the root directory of the archive; ie. we typically chdir into 'root_dir' before creating the diff --git a/lib/distutils/ccompiler.py b/lib/distutils/ccompiler.py index e93a2736..b71d1d39 100644 --- a/lib/distutils/ccompiler.py +++ b/lib/distutils/ccompiler.py @@ -875,9 +875,9 @@ def executable_filename(self, basename, strip_dir=0, output_dir=''): def library_filename(self, libname, lib_type='static', # or 'shared' strip_dir=0, output_dir=''): assert output_dir is not None - if lib_type not in ("static", "shared", "dylib"): + if lib_type not in ("static", "shared", "dylib", "xcode_stub"): raise ValueError( - "'lib_type' must be \"static\", \"shared\" or \"dylib\"") + "'lib_type' must be \"static\", \"shared\", \"dylib\", or \"xcode_stub\"") fmt = getattr(self, lib_type + "_lib_format") ext = getattr(self, lib_type + "_lib_extension") @@ -959,7 +959,7 @@ def get_default_compiler(osname=None, platform=None): # is assumed to be in the 'distutils' package.) compiler_class = { 'unix': ('unixccompiler', 'UnixCCompiler', "standard UNIX-style compiler"), - 'msvc': ('msvccompiler', 'MSVCCompiler', + 'msvc': ('_msvccompiler', 'MSVCCompiler', "Microsoft Visual C++"), 'cygwin': ('cygwinccompiler', 'CygwinCCompiler', "Cygwin port of GNU C Compiler for Win32"), diff --git a/lib/distutils/command/bdist.py b/lib/distutils/command/bdist.py index 6814a1c3..014871d2 100644 --- a/lib/distutils/command/bdist.py +++ b/lib/distutils/command/bdist.py @@ -61,13 +61,14 @@ class bdist(Command): 'nt': 'zip'} # Establish the preferred order (for the --help-formats option). - format_commands = ['rpm', 'gztar', 'bztar', 'ztar', 'tar', + format_commands = ['rpm', 'gztar', 'bztar', 'xztar', 'ztar', 'tar', 'wininst', 'zip', 'msi'] # And the real information. format_command = {'rpm': ('bdist_rpm', "RPM distribution"), 'gztar': ('bdist_dumb', "gzip'ed tar file"), 'bztar': ('bdist_dumb', "bzip2'ed tar file"), + 'xztar': ('bdist_dumb', "xz'ed tar file"), 'ztar': ('bdist_dumb', "compressed tar file"), 'tar': ('bdist_dumb', "tar file"), 'wininst': ('bdist_wininst', diff --git a/lib/distutils/command/bdist_dumb.py b/lib/distutils/command/bdist_dumb.py index 4405d12c..f1bfb249 100644 --- a/lib/distutils/command/bdist_dumb.py +++ b/lib/distutils/command/bdist_dumb.py @@ -22,7 +22,8 @@ class bdist_dumb(Command): "platform name to embed in generated filenames " "(default: %s)" % get_platform()), ('format=', 'f', - "archive format to create (tar, ztar, gztar, zip)"), + "archive format to create (tar, gztar, bztar, xztar, " + "ztar, zip)"), ('keep-temp', 'k', "keep the pseudo-installation tree around after " + "creating the distribution archive"), diff --git a/lib/distutils/command/bdist_wininst.py b/lib/distutils/command/bdist_wininst.py index 959a8bf6..0c0e2c1a 100644 --- a/lib/distutils/command/bdist_wininst.py +++ b/lib/distutils/command/bdist_wininst.py @@ -303,7 +303,6 @@ def get_installer_filename(self, fullname): return installer_name def get_exe_bytes(self): - from distutils.msvccompiler import get_build_version # If a target-version other than the current version has been # specified, then using the MSVC version from *this* build is no good. # Without actually finding and executing the target version and parsing @@ -313,20 +312,33 @@ def get_exe_bytes(self): # We can then execute this program to obtain any info we need, such # as the real sys.version string for the build. cur_version = get_python_version() - if self.target_version and self.target_version != cur_version: - # If the target version is *later* than us, then we assume they - # use what we use - # string compares seem wrong, but are what sysconfig.py itself uses - if self.target_version > cur_version: - bv = get_build_version() + + # If the target version is *later* than us, then we assume they + # use what we use + # string compares seem wrong, but are what sysconfig.py itself uses + if self.target_version and self.target_version < cur_version: + if self.target_version < "2.4": + bv = 6.0 + elif self.target_version == "2.4": + bv = 7.1 + elif self.target_version == "2.5": + bv = 8.0 + elif self.target_version <= "3.2": + bv = 9.0 + elif self.target_version <= "3.4": + bv = 10.0 else: - if self.target_version < "2.4": - bv = 6.0 - else: - bv = 7.1 + bv = 14.0 else: # for current version - use authoritative check. - bv = get_build_version() + try: + from msvcrt import CRT_ASSEMBLY_VERSION + except ImportError: + # cross-building, so assume the latest version + bv = 14.0 + else: + bv = float('.'.join(CRT_ASSEMBLY_VERSION.split('.', 2)[:2])) + # wininst-x.y.exe is in the same directory as this file directory = os.path.dirname(__file__) diff --git a/lib/distutils/command/build.py b/lib/distutils/command/build.py index cfc15cf0..337dd0bf 100644 --- a/lib/distutils/command/build.py +++ b/lib/distutils/command/build.py @@ -36,6 +36,8 @@ class build(Command): "(default: %s)" % get_platform()), ('compiler=', 'c', "specify the compiler type"), + ('parallel=', 'j', + "number of parallel build jobs"), ('debug', 'g', "compile extensions and libraries with debugging information"), ('force', 'f', @@ -65,6 +67,7 @@ def initialize_options(self): self.debug = None self.force = 0 self.executable = None + self.parallel = None def finalize_options(self): if self.plat_name is None: @@ -116,6 +119,12 @@ def finalize_options(self): if self.executable is None: self.executable = os.path.normpath(sys.executable) + if isinstance(self.parallel, str): + try: + self.parallel = int(self.parallel) + except ValueError: + raise DistutilsOptionError("parallel should be an integer") + def run(self): # Run all relevant sub-commands. This will be some subset of: # - build_py - pure Python modules diff --git a/lib/distutils/command/build_ext.py b/lib/distutils/command/build_ext.py index acbe6480..7c278ef0 100644 --- a/lib/distutils/command/build_ext.py +++ b/lib/distutils/command/build_ext.py @@ -4,7 +4,10 @@ modules (currently limited to C extensions, should accommodate C++ extensions ASAP).""" -import sys, os, re +import contextlib +import os +import re +import sys from distutils.core import Command from distutils.errors import * from distutils.sysconfig import customize_compiler, get_python_version @@ -16,10 +19,6 @@ from site import USER_BASE -if os.name == 'nt': - from distutils.msvccompiler import get_build_version - MSVC_VERSION = int(get_build_version()) - # An extension name is just a dot-separated list of Python NAMEs (ie. # the same as a fully-qualified module name). extension_name_re = re.compile \ @@ -85,6 +84,8 @@ class build_ext(Command): "forcibly build everything (ignore file timestamps)"), ('compiler=', 'c', "specify the compiler type"), + ('parallel=', 'j', + "number of parallel build jobs"), ('swig-cpp', None, "make SWIG create C++ files (default is C)"), ('swig-opts=', None, @@ -124,6 +125,7 @@ def initialize_options(self): self.swig_cpp = None self.swig_opts = None self.user = None + self.parallel = None def finalize_options(self): from distutils import sysconfig @@ -134,6 +136,7 @@ def finalize_options(self): ('compiler', 'compiler'), ('debug', 'debug'), ('force', 'force'), + ('parallel', 'parallel'), ('plat_name', 'plat_name'), ) @@ -163,6 +166,7 @@ def finalize_options(self): self.include_dirs.append(plat_py_include) self.ensure_string_list('libraries') + self.ensure_string_list('link_objects') # Life is easier if we're not forever checking for None, so # simplify these options to empty lists if unset @@ -199,27 +203,17 @@ def finalize_options(self): _sys_home = getattr(sys, '_home', None) if _sys_home: self.library_dirs.append(_sys_home) - if MSVC_VERSION >= 9: - # Use the .lib files for the correct architecture - if self.plat_name == 'win32': - suffix = '' - else: - # win-amd64 or win-ia64 - suffix = self.plat_name[4:] - new_lib = os.path.join(sys.exec_prefix, 'PCbuild') - if suffix: - new_lib = os.path.join(new_lib, suffix) - self.library_dirs.append(new_lib) - - elif MSVC_VERSION == 8: - self.library_dirs.append(os.path.join(sys.exec_prefix, - 'PC', 'VS8.0')) - elif MSVC_VERSION == 7: - self.library_dirs.append(os.path.join(sys.exec_prefix, - 'PC', 'VS7.1')) + + # Use the .lib files for the correct architecture + if self.plat_name == 'win32': + suffix = 'win32' else: - self.library_dirs.append(os.path.join(sys.exec_prefix, - 'PC', 'VC6')) + # win-amd64 or win-ia64 + suffix = self.plat_name[4:] + new_lib = os.path.join(sys.exec_prefix, 'PCbuild') + if suffix: + new_lib = os.path.join(new_lib, suffix) + self.library_dirs.append(new_lib) # for extensions under Cygwin and AtheOS Python's library directory must be # appended to library_dirs @@ -274,6 +268,12 @@ def finalize_options(self): self.library_dirs.append(user_lib) self.rpath.append(user_lib) + if isinstance(self.parallel, str): + try: + self.parallel = int(self.parallel) + except ValueError: + raise DistutilsOptionError("parallel should be an integer") + def run(self): from distutils.ccompiler import new_compiler @@ -442,15 +442,45 @@ def get_outputs(self): def build_extensions(self): # First, sanity-check the 'extensions' list self.check_extensions_list(self.extensions) + if self.parallel: + self._build_extensions_parallel() + else: + self._build_extensions_serial() + + def _build_extensions_parallel(self): + workers = self.parallel + if self.parallel is True: + workers = os.cpu_count() # may return None + try: + from concurrent.futures import ThreadPoolExecutor + except ImportError: + workers = None + + if workers is None: + self._build_extensions_serial() + return + + with ThreadPoolExecutor(max_workers=workers) as executor: + futures = [executor.submit(self.build_extension, ext) + for ext in self.extensions] + for ext, fut in zip(self.extensions, futures): + with self._filter_build_errors(ext): + fut.result() + def _build_extensions_serial(self): for ext in self.extensions: - try: + with self._filter_build_errors(ext): self.build_extension(ext) - except (CCompilerError, DistutilsError, CompileError) as e: - if not ext.optional: - raise - self.warn('building extension "%s" failed: %s' % - (ext.name, e)) + + @contextlib.contextmanager + def _filter_build_errors(self, ext): + try: + yield + except (CCompilerError, DistutilsError, CompileError) as e: + if not ext.optional: + raise + self.warn('building extension "%s" failed: %s' % + (ext.name, e)) def build_extension(self, ext): sources = ext.sources @@ -502,15 +532,8 @@ def build_extension(self, ext): extra_postargs=extra_args, depends=ext.depends) - # XXX -- this is a Vile HACK! - # - # The setup.py script for Python on Unix needs to be able to - # get this list so it can perform all the clean up needed to - # avoid keeping object files around when cleaning out a failed - # build of an extension module. Since Distutils does not - # track dependencies, we have to get rid of intermediates to - # ensure all the intermediates will be properly re-built. - # + # XXX outdated variable, kept here in case third-part code + # needs it. self._built_objects = objects[:] # Now link the object files together into a "shared object" -- @@ -655,10 +678,7 @@ def get_ext_filename(self, ext_name): """ from distutils.sysconfig import get_config_var ext_path = ext_name.split('.') - # extensions in debug_mode are named 'module_d.pyd' under windows ext_suffix = get_config_var('EXT_SUFFIX') - if os.name == 'nt' and self.debug: - return os.path.join(*ext_path) + '_d' + ext_suffix return os.path.join(*ext_path) + ext_suffix def get_export_symbols(self, ext): @@ -683,7 +703,7 @@ def get_libraries(self, ext): # to need it mentioned explicitly, though, so that's what we do. # Append '_d' to the python import library on debug builds. if sys.platform == "win32": - from distutils.msvccompiler import MSVCCompiler + from distutils._msvccompiler import MSVCCompiler if not isinstance(self.compiler, MSVCCompiler): template = "python%d%d" if self.debug: @@ -729,7 +749,7 @@ def get_libraries(self, ext): if sysconfig.get_config_var('Py_ENABLE_SHARED'): pythonlib = 'python{}.{}{}'.format( sys.hexversion >> 24, (sys.hexversion >> 16) & 0xff, - sys.abiflags) + sysconfig.get_config_var('ABIFLAGS')) return ext.libraries + [pythonlib] else: return ext.libraries diff --git a/lib/distutils/command/build_py.py b/lib/distutils/command/build_py.py index 9100b96a..cf0ca57c 100644 --- a/lib/distutils/command/build_py.py +++ b/lib/distutils/command/build_py.py @@ -314,10 +314,10 @@ def get_outputs(self, include_bytecode=1): if include_bytecode: if self.compile: outputs.append(importlib.util.cache_from_source( - filename, debug_override=True)) + filename, optimization='')) if self.optimize > 0: outputs.append(importlib.util.cache_from_source( - filename, debug_override=False)) + filename, optimization=self.optimize)) outputs += [ os.path.join(build_dir, filename) diff --git a/lib/distutils/command/install.py b/lib/distutils/command/install.py index d768dc54..67db007a 100644 --- a/lib/distutils/command/install.py +++ b/lib/distutils/command/install.py @@ -51,7 +51,7 @@ 'purelib': '$usersite', 'platlib': '$usersite', 'headers': '$userbase/Python$py_version_nodot/Include/$dist_name', - 'scripts': '$userbase/Scripts', + 'scripts': '$userbase/Python$py_version_nodot/Scripts', 'data' : '$userbase', } diff --git a/lib/distutils/command/install_lib.py b/lib/distutils/command/install_lib.py index 215813ba..6154cf09 100644 --- a/lib/distutils/command/install_lib.py +++ b/lib/distutils/command/install_lib.py @@ -22,15 +22,15 @@ class install_lib(Command): # possible scenarios: # 1) no compilation at all (--no-compile --no-optimize) # 2) compile .pyc only (--compile --no-optimize; default) - # 3) compile .pyc and "level 1" .pyo (--compile --optimize) - # 4) compile "level 1" .pyo only (--no-compile --optimize) - # 5) compile .pyc and "level 2" .pyo (--compile --optimize-more) - # 6) compile "level 2" .pyo only (--no-compile --optimize-more) + # 3) compile .pyc and "opt-1" .pyc (--compile --optimize) + # 4) compile "opt-1" .pyc only (--no-compile --optimize) + # 5) compile .pyc and "opt-2" .pyc (--compile --optimize-more) + # 6) compile "opt-2" .pyc only (--no-compile --optimize-more) # - # The UI for this is two option, 'compile' and 'optimize'. + # The UI for this is two options, 'compile' and 'optimize'. # 'compile' is strictly boolean, and only decides whether to # generate .pyc files. 'optimize' is three-way (0, 1, or 2), and - # decides both whether to generate .pyo files and what level of + # decides both whether to generate .pyc files and what level of # optimization to use. user_options = [ @@ -166,10 +166,10 @@ def _bytecode_filenames(self, py_filenames): continue if self.compile: bytecode_files.append(importlib.util.cache_from_source( - py_file, debug_override=True)) + py_file, optimization='')) if self.optimize > 0: bytecode_files.append(importlib.util.cache_from_source( - py_file, debug_override=False)) + py_file, optimization=self.optimize)) return bytecode_files diff --git a/lib/distutils/command/register.py b/lib/distutils/command/register.py index b49f86fe..86343c80 100644 --- a/lib/distutils/command/register.py +++ b/lib/distutils/command/register.py @@ -296,9 +296,9 @@ def post_to_server(self, data, auth=None): result = 500, str(e) else: if self.show_response: - data = result.read() + data = self._read_pypi_response(result) result = 200, 'OK' if self.show_response: - dashes = '-' * 75 - self.announce('%s%r%s' % (dashes, data, dashes)) + msg = '\n'.join(('-' * 75, data, '-' * 75)) + self.announce(msg, log.INFO) return result diff --git a/lib/distutils/command/upload.py b/lib/distutils/command/upload.py index 1a96e222..1fd574a9 100644 --- a/lib/distutils/command/upload.py +++ b/lib/distutils/command/upload.py @@ -1,11 +1,14 @@ -"""distutils.command.upload +""" +distutils.command.upload -Implements the Distutils 'upload' subcommand (upload package to PyPI).""" +Implements the Distutils 'upload' subcommand (upload package to a package +index). +""" -import sys -import os, io -import socket +import os +import io import platform +import hashlib from base64 import standard_b64encode from urllib.request import urlopen, Request, HTTPError from urllib.parse import urlparse @@ -14,12 +17,6 @@ from distutils.spawn import spawn from distutils import log -# this keeps compatibility for 2.3 and 2.4 -if sys.version < "2.5": - from md5 import md5 -else: - from hashlib import md5 - class upload(PyPIRCCommand): description = "upload binary package to PyPI" @@ -60,7 +57,8 @@ def finalize_options(self): def run(self): if not self.distribution.dist_files: - raise DistutilsOptionError("No dist file created in earlier command") + msg = "No dist file created in earlier command" + raise DistutilsOptionError(msg) for command, pyversion, filename in self.distribution.dist_files: self.upload_file(command, pyversion, filename) @@ -93,7 +91,7 @@ def upload_file(self, command, pyversion, filename): data = { # action ':action': 'file_upload', - 'protcol_version': '1', + 'protocol_version': '1', # identify release 'name': meta.get_name(), @@ -103,10 +101,10 @@ def upload_file(self, command, pyversion, filename): 'content': (os.path.basename(filename),content), 'filetype': command, 'pyversion': pyversion, - 'md5_digest': md5(content).hexdigest(), + 'md5_digest': hashlib.md5(content).hexdigest(), # additional meta-data - 'metadata_version' : '1.0', + 'metadata_version': '1.0', 'summary': meta.get_description(), 'home_page': meta.get_url(), 'author': meta.get_contact(), @@ -149,7 +147,7 @@ def upload_file(self, command, pyversion, filename): for key, value in data.items(): title = '\r\nContent-Disposition: form-data; name="%s"' % key # handle multiple entries for the same name - if type(value) != type([]): + if not isinstance(value, list): value = [value] for value in value: if type(value) is tuple: @@ -166,13 +164,15 @@ def upload_file(self, command, pyversion, filename): body.write(end_boundary) body = body.getvalue() - self.announce("Submitting %s to %s" % (filename, self.repository), log.INFO) + msg = "Submitting %s to %s" % (filename, self.repository) + self.announce(msg, log.INFO) # build the Request - headers = {'Content-type': - 'multipart/form-data; boundary=%s' % boundary, - 'Content-length': str(len(body)), - 'Authorization': auth} + headers = { + 'Content-type': 'multipart/form-data; boundary=%s' % boundary, + 'Content-length': str(len(body)), + 'Authorization': auth, + } request = Request(self.repository, data=body, headers=headers) @@ -181,21 +181,21 @@ def upload_file(self, command, pyversion, filename): result = urlopen(request) status = result.getcode() reason = result.msg - except OSError as e: - self.announce(str(e), log.ERROR) - raise except HTTPError as e: status = e.code reason = e.msg + except OSError as e: + self.announce(str(e), log.ERROR) + raise if status == 200: self.announce('Server response (%s): %s' % (status, reason), log.INFO) + if self.show_response: + text = self._read_pypi_response(result) + msg = '\n'.join(('-' * 75, text, '-' * 75)) + self.announce(msg, log.INFO) else: msg = 'Upload failed (%s): %s' % (status, reason) self.announce(msg, log.ERROR) raise DistutilsError(msg) - if self.show_response: - text = self._read_pypi_response(result) - msg = '\n'.join(('-' * 75, text, '-' * 75)) - self.announce(msg, log.INFO) diff --git a/lib/distutils/command/wininst-14.0-amd64.exe b/lib/distutils/command/wininst-14.0-amd64.exe new file mode 100644 index 0000000000000000000000000000000000000000..253c2e2eccefa79393827f44f85680536906574a GIT binary patch literal 587776 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 z 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

64S?Ez{~ zM4=-EeuF)OJ9R%3DakXs(cSdPg(Z}!g#BJjyt#>bySZfq>HQouA{I+)4NY%*hb=Z@ zac&^yPFL}pJ?X} z3pZiFDG<^>76129-w(DCE%-b29`b?I{GBETM#}SczYCR zB6c()Uf@*bEWGSNw6~>CI_s7`AWT8aDqPk8~Sh$dLm)3z0_p%+Rd1= z@L~wlOf-9KsOYtF=Y&bu`smE@LUhT=p_%7%uieDG#(f6x6QbXOt}~Rt(g)COB@dwK z=xf(9{367O?+IzR>skD+MBkOXimrPHO>e{BUUbD4bjiDT-ht)4b z(LIVK-|Ej;~BXp;!9 zn=`RV=)pU(Xn<1XR+|>S4qTzLnH9*`W`?k9x4D(V#%HRed>Cf}3=a z!pKKOJ;=3DB&fajVYVSs)Lg;`5uxxyQP*f6wc=SfuSY!pvc|x(d~HARyeJmWZ~De_ zYX9)u^NSwwOf&IZ)6!2ouYm=?(x2l?`s~kVm-MSY3wy@%hPRFWJgL8U{tf3aEO`ER z-*_Ipw_kWZe0z`m`9E(Nc-~2zSAXd9ZN#Rx;Q7h)KKt{E{^1##+asPsO*~th`ibX> zv3UN>(X0#*-t7DTwuyUUnsJ^@@oOGD}g%7a&;b9>fOWutW!N9CgCIS-w+3 z879un@7R(W$Uq;yomfz{C{xBZh^K(76X83QKv5<#r6a4oD1G6Hh$9r5UPe+8on{>1 zvE7*-L8OlzU;|6*!22+Vh@8Y725#6Rt@x${R_AkZ-H3C--1Pg0BkVYDKsbhY{apAR z;$}&hzc!P1He9kJQmEoK_B~vHxB4>N%;>$)W1EKnDaAlz38PV*z7XDu1%ye{Oc?X5 zc(m1b*11X}byY{kxq}bpq|+)MDCv?OZLG5ei{5+bsBe5P#Z@FOE?BhkrH`bV32?y< z7X6(bPs5|5Zt3i*Bl%J-xqx`o%1n1~3jEk(@Vr^Wa})|z+X+(-^_agA0Z4q4bWEIe z^Jgb$+@}bylb~^6b2=kM=$ps@{6pceqSNF1XLM08^;~)4x_eH+iBV}y2hMdPGEG#0 z&wFO*VHg;Yvm|&&PP)1UA&;E(pQ9nZ%j7p~N)-6V_BE6V+}C9qIw)sKI(PG=d|!HG z69yjxuWqS#&Gx8EyAE`@PCbB_^f-9anh`7_m-uvPO*35LBWDJS$R}RBUX7DsMdTDO zUN6Dowcd&X zsl*+YqZJg_Ve3VQ{g|$CqE3%}epgK2#MQ1hU>OG+yH56MC z7jMA+cHoBDr#AZceD7}@7@keDraMvnBv@pdgzIzp`lt~^QbUyJG?3H@p-W-EU_A6i zBW4!h1O`*k2%scpfiX~9>PkSuZB>U|-jP*@OTB}t4&O@uEbt8hJw*mO3YX5tTTp+FO6^Vq;VLr`gU1*bnVzYn*6%j=ldqfD|?$9g<57Y z)|5}59MnQ_G6aOa)&B)e13nreUTGTe*%0wc(_nqne`HYQ<;B6&6{yr1ys$`KeykL? z04%$(7xY(L5#M-5KjN3y%s^fP1v;E!K>@E;6!0o$B4Q<0_Ez209;$l+*LUYl=p3S<%L{=btEdwZvO#R-Km-sQD2%%ZH_^lP%5!YKNY2_-4h=by!nscGT2ITMjG6*jX`4}>1ov+kY z|F3eV9qwu00H9z)SF{bCTPktQ8R)Rh;YTsWNEouz4#8L+qcLcX`K!NRR>yIjAv%{80i9~|gUsKT10FQ`!G1 zxQewI9Ls=^k*eWG$)q!}atKDIkwRa=43vbAEt3CgdUD#^r0UznlWTV6U`m$P7RKI8 z-x?9V3QyvcHeM?ci*aS+I0}{!zJNaPSvpR4HobBj0W%$a4G*Lbd{~>)9Yy0CIG}); zj=qNBSSx~fiF)6oSB{KjrlYVAVN7EYyg{U+UW4$r>mm zge4t?iwNJw=@9loq$5YN1_}^iO^4U;WconCB1F22=#`>HSklqg@X1^r^z0(tP%ck< z)$pUR5*!CX1Ulk=`3yACKyf83<ar8BEuZWF=N-Mk#6qv%2fDQ*nt`T99D8ZEt6sf|JfX-({3L7YH1wkvu zH&EybPB4Z^gT6#g7KgNuppsSu(485+YaCV=aaK^mlNP`WylrHp{z^IY^MbN{?Lr@> zl-s_^n^9+Tryv+%Njpt2c$d-~!}RO6Sm0hto@}2?A+`&1M|ioEHPQ?dQ4o0I$@Vr9 z(XJ3H2Tv(pKEkWvlGTkjme!;bX7(nj_AIQ(F_RqIg|W`-OVur?J;7AhDy02GEt^Fc zp{914_eyYDy2pOlcReovS7{50^W2igxWxq5-EjMoQ3Ed{w1k{4>f!>?Bq~r2x@mR6 zTCxJ|^1Ve13Ln$Qcr{7YWGte61!3{1VF>c@8X&9qU@ax^(+uKZdm{Z4c%=|+H13;M z(HbD&%PjB}96Ck7X=3T=e@8LvxK@gbr$g%usNIB$zzW8fGp%1{!t`W(8EE}75nn|7 zJFX=H>ep(Ks()h5MOWh)UsA1KD7J|41()P#QSi9D-&((I#y10t<<>8BhB2s2p_W$6 zGs>bfj3OQ$!LKynza%)HcqBN7#6#br{ang9T*}#6YQz>H|9fL%N!1EoF>fHm_>O;0 z-NaMEQNO>$e$+cat{aG;!-{p=(kMD+^L?baKbvp(zhGg4ouC~t9(`wnG3pAXEfOs5 zQk;Z$*3U1M4}54yQ|+Wy(WEKMhB=Pc4JcoTM)TU3^#Ls}JQaI0KDF!n^TklqBNiwp zq-jFz95Zs-_Im=)Oxpc6%h2p$anc#1wMCep3_@|3a%*WVSjMa!^es(t{r2)e$y@$I zE^n6q5JWHkplFTPv#vYH$_fAWRRBZHdVT zSeFQ$6_kNOY|X7WJUve11eqrx*{s|j%5&D;+E8BG?3>&}p_?OTYOBJRIEi3oxy)8k zdt{xTn=J{{t-%8hk9^T6ja0q*}h5VC29lel7Je zS_z79db%B__%#@xucAVnZp#LDKI6d8tQ~Vlq4d=({>2{prXBbZLQy|XOBDy95 z3H%msEK^fxL+up*Fo@uMia!lsNCJNr!$1IR%U@*sn>$DXzY795Q4R!fki8~?|15@% ztU|#)vO9}?YuaR{P(ezDXaIJgbgLkya0?QHWe=~wFU#ksVGWJHkksV2h;n4HV>5lx z)oMC@HOUAO^f&Id+o1te;xNLWbN7ADdww{Hd$OkYKutacG;qLqVa zn&bN&rFj5jPkGhgw7c}Cxi z7l9y}T8T|YZY_wW2DhHuzY(-##)^2zl7s1zbC~D$MI;J7qi>wsKaEPzV@1r+PvcV% zP2-HdU59cI%~C$2ubtbE4Wijj!LZQTYgvg&8&)Dp<6u~V?vPq%07=m@BZjEVg>*Cd zZ!;%!Xp)7ZFzoBRIEABAQv}kQxgLda9~U;ETN_m0T4(*z* zQ}$~U7s0l_xo;;{W;yUvY-w^}Mk<9Q2b&tNwShSLZfM>)*hzJ*4MT1{9h-%J?c5lx z4f|J>#amt+9(52|UP5en*-u7w-SYC#bB4Y~K}2R@dAU!sykKvi)!pVY%B{+oZo8cB zm83Peji=EakTd;9qCU;;qL85lS#g;-`-JSPD^u9+a)9hE14LAu617#gyL<{oTjVFP zc9#=EM<547+J_-Vm|n=Xeo+E-9bDhw8i6b3EeI(ZLcfG+JF*9D_0lr-mD@hnN+aR9~i$B}PeXi8=jP zQ`KSHJ5;Xf)8zU#GQ%{={yk8bH2w%POyfc~Fe$O`BHlU09c|%GimpBW zPKs2624f8{v^^wfBzoLzBuJeGOKT*NIuz+>r^mkAS3-?dw4RdbkW|VtS+>J)77FGZ>T<8GvnrZ7@6JHDrdUfh8uLIOpy*w7w<{ zmIln}`t@M?o2tIZJJ5X8*TcHd7bQ5!Xhib`cY$@s10-NBQkXBaYHGdj9l{vUWaIfl zWMvpfj>A5qz&`T`YIg=l7P;)(mnAYSJ#VGxM`J0v4wjkKKh`pXi#0I7v1H$mL5}2NCtd5r-~LO|5}H+z4vL8vF!5JE&i1!jV=Dx-|1WYZ#9#Q5Xf1z zE><^ou7Fts)|fPD<(3FSGikd$BHke|LSr&*_r&S@l5u7?2sK)u3?>=Z9{C+IRRIF! z8suICBLEK&s({YS6MGSo;b%V4K@ zhRrnoBje--!%UNcy`8QW@Ld{H8H?BVq^dheW;Z7y>dK@B(M&{L|L~Yi+wyV26?Bx~#J7a~{N3F8?=-s7 z!L9UOgUz4H_e~?jUG)gIMur(*X>4chpw8;00m;-;WpB~FQ4xi@DS~KeWp5$40QXUX znQ<1F;mv4#`$Aa7jU{{tx&>D<$eE(6Z%r4XqbG1{CwXWzA}}`?1Z3;<&4Ctqs|^1g zVt}BV=(Y|_Fv`WwGZyMnkhRw5FqwEyVf_d+7@dSURB(;HVZw3pb!{|&eSMo@h>`i@ z8dDy1ow`jEQvIKRyJ*gE1!rF%bQc&?srq_s4Txp}rK_~E88T5AR0IKZyW~HY9%TCt zsk)FsWiZEZnzry+4CsU^6Azk@+DS2$n}sbVnZRUiDYqlhVMrCi6l2#C(Wmf-1Qx<7 z5>bd>VTaKZ(Wfvqhp$rMl3wMK$he{>ViU}7xFi8ah+8hnnuzT$cXCOCHJonclEj-p zoQ*GTPDEA|LfpZL#G96gEGjt!X3GmDI+fno@S@|yW*6d`8pyE1B^{tQirQ@=PBxca z1SYErCn8m_FuCX@vBBkk2+Rf-s_qqfV|$C9h^;LT6PT?nl;{C^V`B>vL1)!0Of7e6 zdYaCP+l8e?|H^ijQcYjeSutIhS@f@LWEn4vEcVWdaSd!^!Pn7LGFws)Bz>IThmQW7u!9MA#2J=wx(Dn zs%0_Gu`b3ZYReQY#=5jkt3Iv9P*CyAEwO*+yA%2@-|ece5v}7U-#e=NVsHW?O>d37jJ1HjQG3_0EtYTJ(`NoVLa_!gcrIYV#Dev_`X;=Q9YFuy6jtcFnDF?{s!#q7#S>i) z1Q@G61#F`ZW7VgCHRv!_eG1qMI*e7H0`{^Z^&8D;^PR^w$K*B*;_ZEN`kC>`T1OVoU7==`1v*51W@54c<{< zJK-ISxn=TbAhrv0ktBViDO)1_nupM6L1v`cNLG`PhSdb7lLbD=N;@=JsX4H8tyA40 zzscefj3t{u{4`M+iI|#qP|fD5gB8GFk+c<{lU5!~F<4E?unMf_CX)bF?*9T-lM+}> zI(>5D()-yiY7q|;sk>=yxs|)D&IPN91!=La!1!$9J1zmf-YjF~0J<0$-v}-)@^`Ri zMpdXLHt;c-L3pP%j3)E22AD>ZN?IdW#b2?_G?kDyg9d%eG?lO-TcrJ?ZlowMQ$C`@ z&M;tF;A73uG0+14In7lKZZ(-q13aN!QciSj95)eu4ucGVd6pA zka7&v5pPKO3^0@eBMjpC>CCB~w=LW%K6gmfILe^2rTXWl@QdOzMxX46+I@NY=f-fI z_zdLzoSRuu{d04e3_GYBCZ1b3H@8&(+!~%nz;b3fQGv`!#2c}oP2=cO_-q0T78Hq2 zqc_2Vwu?l}f+lfE%yc5rLE;Go3#uhzx1aY3Y*6zydSkC2yOxMPg;x-m_)J~~{z7kp z1=SMKr|_={ELc!fS4VGx1r_Ei!Ghjp(ETqo7F7GnEa(h_{yiECs(ob^v_NA)+bhm* z5G-hWWe#--v!Ewxo2hQyS!dZyyW0_aw(d-gp?}MsgZK~T1c(#FHxS<^qH$w!4i{A0sMt^q1<$o?>Y0DN*jT+V^@y$TKi(^hJ+-@jlH}WV zu`5Hyg`=S%B-?Q6*|_dC)6z1trR5LkFzlf@dKcQp-=EAKl) z1LRAf0cuBIyhR$IDUzlEG9XVPJXANs4$ek;)DPT&>(bqU%FL2LaTcAfxeDhz9Z+6n zxdT_Wy933zDf;ZCum1qM?=E*>dZ$O-Ul5h+2NX@L8PlFKFv&ZNo;Ko1s(l{h%Y@)x zk`9GaaVDLpfpezr9Dm@K1|gv03CkN^0~U zaF!vmoyU>J?@G(qbz$saE<3&HZ$CsaaC6fy2Jo0d^2+qAe+)L3FPWUSGU3%MZy9)EQiAQ;$NyHK4r6UNa^{1S|35K4T*4K1Rxfc@`;P6KVSj z=v-Og^lmOj_oqja)lG`OExB9{(4-vT`z=jM`EAmM3dSCViAjALHW4g@ZP=C9EyR8H zhQ)iXq92s0$*JP8(4-M$J%ja^V1(A;3^Px}bFS1U;u=aTtnpugIWe3fh3`|cx(-Oe z%4RnEklE~X9{HzGLSM~(1VzVrR&{IkAkpmpHhAGwZN^$Ib)Hw){r*dC@5MihnY-3C z*g^~4=kcY7|6-lTZ?nHR=0NZ|8|VN7X0Iu9(oB%}(Ze0`EOn{vT60pu??O8gu_2Oe zo!QXJnu>V)Cd@K*gBv=WPJXNc*5M#f^QC4Z87jJe!d? z1!!-;Q48*sMP)h_G}9Q;9MfjMi5S${8e)VL&40b!v&?qS<#zMoh5wCq4-)N;UlV(4 zvaxoTD(W3M@c7UcaDDL(&Ws^|_sq3G--EFVSjJ!5gU22Iv#|#&>fwR|^#if-p1+6Y z0BynNf66oeIT~>?oW<>0K&q1@ts1ZPIa< zW~0)eA4!T-$}4b9?w;-S<)H_c3J@ z|3jAbJQ*5ZH&C9~>ia!vkZS)%qA`LlV2I)vi5n=~gHCt6_gLtyOYAlw5E-!z1=~Eq zp-W%>APqW(=-*ggEP%F6U`k-@x7F2U`(#TV69EAL2!J` zHN?-Mzyj#|3Qz>7bxXj=LZxxyABpAi06wJBZ}qAk;g_ z?e2l(`fRj3vEBC!>XB;68`U7ZQes}M^yUs&_BrD;e@{FQs1Q8Q(LZWD&%cFoURpVj zxSexGiiMvZPh)a?8lBb?2LO`fCGg>q%mEPJ;vG<8(|7;YEF+9*y>)0SET3?J_a+q5=c3qG!(TZeac7?EdzxyJYRP|^X|#8q z&5)0+G<%gyH$FKE4zPsGlJfLUv1(%R1icO|d4cHl9vja4y+e$eMz4ds2E7itkLmRp zrdM&;RH4JBhU#Q9@n_}|s;f@i0k-aMf$C&4T@}bQdt`v`8KPMWn~tIc$)1j$-wehn zv7RDk_hyp;G8#*&rpnNpw_8;yTy1k0&wudJ7 zqAk*k)=2IIY|bFL%h0vFy23e`>_W60 zGOm4)7c6cQR6Wx&nSjGpD2!>?S3Uu|dtWsC3CPcMCy5*Peslu*_gvp&f|MWENm<+H z?|{MpOy?rq(?^f(ga~nWhMw99nJF>dfeAZ>-I`h=r)zz3!7!`zDmsR4bzrfYx+dNCLhoH;?Hd~~()vbP znL@(dsPuEtJA?T#6OZudG4wX*3sc5xkDP7oB;EAxwwm4{=(W53O(f`1XQli8%i38y zMm_b_ZO>Zzis`0?dWz`gLF%a9=q56>C%Q>H4!Zg5cShd`y6NPhBD(1!4E`g!f$GlM z75d!*iRaLDz{HqC8r?j{$PnGUM#%I@HwOpzK{tbrhi*D>AwnG8ga{!)H-`wL|3$hP z6h}9R73fjd`wyo{5B(8Kqg12f--1S*5}5A~+q@<8&qVs?EdOB}j9~a$Sk3L0~4tzN63*i0s%==gi3s-RFhm#)`mhpPso2V%XgUjn*P? zz8j@Jx4MxAbfG<)F`5+)Z_s(mxaEpIq*jE__v>3FH5k87ON}Uc>A8GycJU`<6vM zib)WD1<;tTp_q-f(CkW0>z&|R74$HX`+zMUE#*S%saLKglyJd`&6`KP;-nN@E2@pD z6tQRBXguk!BNtjah%lxP*k}uYoFt>mi{iNR~gvpltA} zxKPNO0Vl&_{zGRi?BJWZal-Y@T@)vc#7kGF=0bstIwPeUF@Bl>4ay-)*de+82YC%N z1E|im$04oQ!lzM&d4h0XRkw$(fx?kuzq|HOJ$#Yr40&`bFfiPyxfYG{Luru}ni{kf zF6dNIge(`1`a_Xt;QcGB-#Kuo$59m>hx;EkCr1Y0G0&IJdIDf?K^bBIA8xu|D@VAr?u7klWD%B1Fu?DBo8G#Ikw5HA9*clleoJxVLwD*@IBZ5;Yl9l$}!FEEl zAxmCGO?TCPCDm@Ehv4nWJZTf4bDKxCyAeEPFQQ3^*lJ~NG%TpRbfD=Yp9f_$z!4Ua zI-H@H+?Jjx*IM)niV!tZ{}}Bmq25#E`hTD-4Kc|X7W`pM&*X?WPp6x4o=(sYBbOEE zMVHL^3_)%E?NRr^@8FP(iF+t`MQ7FFjD>@o=p~#w>5QBsx=9DT963?+lMd*Jq{=7( zC-kGo`pe1`8Hn~XfQG#kICK(dMeqA z$-9InZ=t?YBvnOj>{oJLHL;OIF5o&++y{5-`AWKs7{MY|s!`1Zx7tcVVJYI^B0_Mb z1CfFeVC|p8waSQOL@x6tFM##Qi>Rr_`n3GZ*Ju_L9FXh7ygnWAOc<|4GCDWpyWLoy z&cBP+r>Xk-w2+&akM-#g=2L?8>A{>T6dXEvJFQVuGfUKeDS?`t=KxznI}O$=A!;gt zsaOIQ@~Sm8ec?5VdI?#3ew#u{k1p_xZQh~0Hz~F$>FT}+ZgkttB}B#}t`OM#nCv*@ zeg-sDq3e;I^JXAa5&Z7DK-SXA-F(~*YW&cilh}>^ZAZ;lF7+4=9k!MGzaQs=@AMWd zPcaLUSl)47;|pIdgKzxrN>1@iLR5#PB`c zco|C$FEYDN;q49z4C4&Ws>1Vy|(B=YmX?;>*kz^{{BI`Df7 z`FY^iL*5(s^^!{mezVC@1HZZCy@B6+a@W9bAvtQ`*Fjzw_*Ezl3w}$?=1%4OMv2A1N9fPg1+`dz)#)apQAitkjd7-qj1#vvyqYe_izuT6a#JvNF z7VNzkf$&zyebjH^c082ZcS-(VQxUSgOR9bbPpgD;&rWhcZ=vJVUaF>xD*>OxaB{Js zauy*Fihw&A{yPnRi&XupfO{DJOAYRos-F<>Y``ljsEk~A9tl`J!EPj2p;S$NVyMqS zuxSKSr0VMgtdw9E6Ks}LJypPF6YMO4&BZDG@c`2E0`l-~$!oTF*A(j>-o-H<-r^03 zbq{asv8Tz*jUn94K;CD`MG5p0q|1N4HnlYjO9?>$o3XGMt!orD~~u2yE11NRTgY> z&-se>X64|&3pzaNH;TGXxMUIyK1DQ0b7=^C!dsl6{c1q8P!(O0|5kbvp(>F24!p5{ z<*tfO&Fd0S(o<>B)tc8OpbiuI0((pXHr9kati!SZE4PbTAUv&c6%dFY$&YG82;#qy zh@aLmf`6vLLHy)t4LFD&@J$*V#DBhkgZKfbBMwAdi=^t41RTT0e2KmMXHQJYg8tr47fNR-_c%|W@0}l(qHHEUl zVRbO)bU$%Ui(P|{cji|w4ehSBVQA>(v`SVp#d{? zR-EH#5fN=T;#||h6F{!m3w4UUc?^`>#2Y(n4*5zHRczKkhq6G(F_3D$JP4d8ECg5e37(K8ybHf>^&c`c_;cX4TZ4nD4cUH3D#>KSg%Se>je{fuW}(&@`yj^qN`eVDa3Xu6hQLa(6O5gwreG( zz9AMuVJunGdrh5<+YF%l2W>b{Pt&ihg1 zNkk`2Bp^Z?SDXpKnIznJfiva^V{D1Ow8)3ZCBHsOKG-It#bjFVVx`5iAqL44TXga8 z<;X~gK^A}^FOGE8Lf&jdcgXL6lS>NIDyBEoBDzA*GmQ+_#2hR3PQ&hn*wRx_lrAtH ziYdY(FoOT>sjqyOMn075H=-0-U5AYiW#}Ov4WYjmLv+cU9lCS~#d&)-$uL*z5?vB} zIdYQLCpyF`8zP!xTjkC2;D49c_a~4I*T>0*`WR}WA;jMB63jenV`nN}xAU zonL6_?b95`+Mq?~!5mG*>!{g>1F-gahA5crEWGs)i-LQMMT@m9Qq5mk6r6)a3uZ8g zg4yAx4N>sb8(0)v68gpU`g+BpAl9nQQBxL7Tr|monj*)tMnM+rM{+ct{)?3bMZS_f z#+qcvf})x;HCYe~(SINdeu^l7yeL5yG<8+CUuP^zXWbkl3*N@EAnB?$pz9OLf^`^@ z(BSr?2*C24N4+bPr2|jkm7FSk5GxePaksHG02?3$!To?-|FF|&aZTV@m1UfowSPz+ zUmqCqTqcxUSc0w)aq>O3yEKjv1qyC0rl=8?6Ef1uUFG)od?X&4#Wg_zMW?J0O-@$7 z#Okz;#ltlJCS5w5BBWSD0*%9btQGUGEY;};h`e2>IFiPt^iPH)_7BpSCPUKk3cUUQ zOor4Je4$i5njg9ehI9@I0+tXAF$ze`f*}Qi1Y8J)pKEaN z#%Bq*5Dd3yaPY>11Y8J)D>e8csrnn@O+AqEse_)?;NXpS3Ahjp$;F%M1#jFe;MpX| zRT2!m@m~du1;d*N2HyB70dr8g=>!9BOu-;g7Yl}$5DdI=g@DbbbfXD27hxc80Fa&+ zkTQjY^B*S}irq8>P#3g4&V;?8!$tw7i+~lsgLovwKjNNf=x0{c z113bjuQl=mc~=k_>!ROw4G!|A-5YQr`jJl}l>zcD5^y2L( zicpAhvPo3Cm(qf~H8GJzzjA_syeUE<(m5#I&j<$c-Y#G)`dvmakT*{kP4qjDU~?-+ zhyP-b_fV4PC9#DTjz}tI44)UE`#}ds)cau5Jn~L$q9y@#^t4bn%r2&s6g@iFaU`mO2isgLtQx zCqzk0Sr#3$##dNar#p`;8z_hVgLubv3U#_@237l@Xu|U^L8Ez8Q8n=n{lx5yV5RyH z@63Dn=E4fM0Lt}6*>og{9j>xV)Wh240BRWbrFpMX)cbIHB<~BTf1uhY*Ok;fZ|cMp z-wm*dz}t5jokD`VEr#hNO&hl0D_qtGbByUN9BqNiiaVJ46FQ~UAbdp}?7AX+R#43> z-El>}zGZx4rO$_jWh@Sj1TuIISwtX1KB7mPKgd+I)Z099gL4nS{uGi!DO; z?a^;Y@_(7_D)`FUYqI)pLh&Zbz5pEuCJi1g=(MzQdy4Ni?lm?5++}4P*F}EfQ7<8H zpR$OiHzAG50QPJF+lcFHps<5ip}!^BjnEA*3nWVo|M%HO=h00Ng!V^f|9QzL>AsvS zxYKFfxYMa_Cp711J-JKxy`+vHG?RTdaxYQ@p$T`xVU9YH!U}DUs9;-Yok|!2u_kUUoVP z?;h{N;wIDLCS!41fW_^A@8po*tZ))49N!ow3(5hT%{vr~JzP@dMe3qM5!{ToC5k(L zd}9vSQoS(zxYI-JpNV}sm2`Wp1Cm0Qa9J?gCPh|y)Z5{Y@h^`Wt=&fVoJOPGR8MeO z6#HH6SKbV_{VS>Fc_ImQdV1)7l;l(!Y16-#3WHtBReQi&ME*?Y`wtKF-6BgX$jdXS zYU#jeyTAQVw6VUO@1&s*N>eE1-A`&}W_cn*;WTv6e<<~y@vuu|d&gES8K2~n==#4C zTy?2A?&w-~bc+&Q7rqCK5@99PxbPId6#uX#1B*M(7O3rtdS!a(3pmdz@SLgn4E!=M z@(lyt-i!k&sZtG@W4QF;gld}HnwkSWDKE->Y_Ecz(6tsGiVl>48F*hk;!&HNh1X2< zxfK8MDs&LWi+?@ptV1QW?_6}X;M0e^CS$yWkzu@ws=dIC?x zAxiKr+*$t$L*afT3Z-Dn86Nu~siuQ+(!3mi8X+IE*CzJ)B!qE`Jdm;_* zQYARO6v6yL*TSQ-rZIvb^HUDON|<`joO0QJEZcj1)shR7e3uXj6)C|Qt{0c3c)^C_ zbSV&oyh~Tm;=U(!L+BMq!*p^$fh0%PH{JH;j<$ub#yE>C9QlfRPg|h^U$D64D+?+{-fF6nN>?BCHV?U zFjp^Dz-cci$9V>fu#h|B2Kk{N4|0(QvuXj2>@~*7Qi!5(CjL>wku=5}yc*$(Sxp%G zAZ3Jgy;Cu+3&#Lcm?0iux@tu%&@|?Z5tMMBk|9Kh@M4S?h?z}iF!FWBv72tZA<>-9 zhm7V_k6=z9pc7ej7e$R^FEqO^BNK_=5^I>%RY)}aenA)D2=p1 zUg1%17Bf2GAd8uNN6vy0k{mzvW*~$K);N6vpN*o$#tH{OWBP7^uFpG#hIKrquT)Db z-EnWQSMdg0>MWc$-d8{~`(h_~cJsi~T;@5gkG+`K5AgEf9Z$0=!A4WlqE<`^>uS=6K<`WT}Vb7S`6#N$loCbBqgzEEIbq(2qx5`1`_ zg*Q+1Ipa2Ab52i}jqy&M%H#crHr~){5Z}a`aBtIE--ChdIiVjM#)!Y+Q5R!cZ=tn& z5lP;&t#kSc%xO=~Y`6L)F*V?$_DVG$@o?+Yne6k}kg-%#SO;v#hcG`#udZ=bmVw83 z^mE{OXBM2*)jaSsoP~2H`tm8}aDg%Kv(16`0CzF!GRORm`$t~W{PzU{{i!q z{7d2{Iks-FgS2&1E0_x-moSYB+p7^k4M2>^iNu9v&?>$N9h!~NH}`KW#UMCis^hN9 zMDsjbO!9(l>NkZm5^vsDO*RHya`B+w8aK&fH}8C!oj zeg73nG1*(`0?tJq^>dECLX>SV(6_ zX3evmH|vvWv%Xc|tpBnc2OG9wyEc~bX}pX(3$MJ;cPcF=xzwGr%Q2UD1H!XAx-pW* zPz9s8h~}`*7|ro`Di53#K97E7Fv<{AuY~zz6Yb5RZ{VQ{dpu?10(eRj=Y{sszPw)> z(H8+C42Dp6k^ke;YGe9h%5x3E>CZKW6aPG<>BP#_l61b~V9p?UQO=CrnKNL5sF25+hLaxWogBjKJ6fQIwNXr3$*F*qw`)Nt;ynb ze`==xdq`ltwdiKl6W5}9LzWhK(;_eWH(*(Y?31WjIzCX$>Wy1NXQ|l@kC1@_UY1xn zre>BSw*HAm6Mu$!K>VqZch%k0RfXN!>QYe-){l(vFK&!RH^O&Nf1h{*z2l>_G9&y9 zTwLiH=`Mp0Hqp?RSgkZIhevflWN!403_UZ!nCMqcrimUIrbKy%e$A#1k^|`&t6Y$v zmY=_XiRWLll&j5BGR;!NK4U3G!$C-XX6{jD?roPb4uh4bjHS(zRmWdQ5N6&F3l01q zH}hh4CCDo@JZ9eK&Aiu&yf{OxwH+aW+(zRC-aWMaIHTuq*UobCy@GwBR*+`9@13bi%SI)h~} zm*gpWfbc0}UN#|vnHy>1^Y``szfookjuw35>$awg|MsKaTQ)i(Smnf%SN0L z1QxhRfQtyoh3M)!`84Y2fSg+=j}6FJAe&GURcG`&1i21UzSy*3!d&>j{Vv+er~7ZtO#2~xr7Li zm>F8cNL)xr2n76dN)03m35kjL^O%+UfB&$C1nOoaSUqXQgW$P9!)!mbo!cFlfk`r> zG%zE-ZpMXpDh$kU)Xi`PW+)iRS(K6H&TO7LGv*?}0)1|Y^5Wa1w+AVaamFl^ST|!5 zo*XE$ZiWYSiSk6jVA?S$XDqA)T1t zps@5L$O5!hi05g+R7^agUM&AqKh_e!isE6S9yQq$EJ|epaw52zzbVz-^l|d)jbI`} z6a5%|1H(3e=$+ReuCG-_)!I6PXPe$enty6WXv_t0IrNXlA5)B|kH5JKLxcDuNYS7t zaGa2l8c9%CoSEcHftCZ@!;gnq>njaCbv_|O)D}y>0-sr-d&T!z3BPBAuH(;%+L95P z9Q%#^$8~ms4D{%V(>Tc(^j?ZWp>f){Uzp7TI>ce=^QpJ-_|tOhcwmj1V3Qt0vL8EWjO%a5Ri?aVYk{2sjNzH((iu+V434a&5iC+Y$*DyXJcCkwgE)cjv+<2B zl<<&>>*JkOsqzG8AXR!?k7J>=g~;hJl<4Zo_30F-kKMe?Af9HRvlHT@sFojM-_!Iu zY*+9OBgZU+c9}qqS87pKT~;Ldb(;v_kx!$e$L;7=FU1c`STO?f@}VBt-blfJjB~c~ z0a0;zpx-sq-kK4v`ZdkXwMt-CI($Sj;UkiTJM`v0uPpy^A5Vg`%^##DDFsO3nyRRn zd^-^s_X$)T+W9n}@J`2Jj)3nWMa^^ur({Dhkd5QL4@jA=2NboC^#m%q0SCcV)KIb# zf4G#W1k_Ommv5}%u6F{yk3z}-6c%(n8}R)TUy;f+raa*L+)?KXqx8K2-$6WnAMimH zzVre>F2UdAQ2jL6%nN<5lE$#)3B&QYWU5lIF;w=|4^cV;LG6IcaVE|`Wa0Q}HjbYP z-C_EF-tQgX{iuGI(+jpa>oG}S7FKX+qyi!-g*ULruE2*yKGrB#9fJS3MqG55A5p+$qP{&H93nCF@{s?~q14}^*OCxyP8(0cj zSo(cnDN1%po`CCQK+nM6n9vl^11Mpj+7&9UTEE0SvghMK{hroSN zQPb+TYkNrT9(dOm94JxuIn`rMXx+%`7{?NiW=OSU^Khv@an-)>o#s?q;kPEM+hlm` zsm{k!mD}5s)<0#ZC=V$JZPg?PJdcBc3uPk$yl)2`>o`gFUshQ;R8|ML0?uEi%FzLl z6ghA)skmGyPM&bF_bgfMlySCSZIe}G=aLRk})`*lO z^?xX<|CmjBpqiG!S}L#L08}sO=&o=J1_-V>Ftp>rrJm%f#dS1)#WN4dW{=t>t6OD+ z15?qDGzQYE8@dh2dfzS#iBxkB^6{|1OT!qJ@N9wxE_T$nhOZV0fXknO3ovyDZ%kF~ z_zDlkraj5f2zjT%6~H$%^vf~2Y9W%}KwLY_&mOQr=#s~%2_e@BDM^u2JZfXXw$S0D zKSV<_ImcNjI5ZSK=#eMJHfFyQq<5M0BdRF+ z0yY^`jsN2oq;_7Ac3_eF6&561Ha-)-=s_4n`dW|_HAu@T@w&HvqVc*|RA%(qC;j0YZbxs)N&6Un@U|nkFWp`#3+=+%%Vc3Bf0+8Vbbazu2 z>tv?y0~ho^AP6iKm6a#B0v^axQS!i9zLtt^aRsv6K?P1^SnizIrr<8sAw=yh2$%); zTP+>qquSJMN($EbPW%f29TQC#_4qrM8mGF=BfZ)Q$q!uw-68P7j7rBK4siyCBQ^6d zq-q!XZ8~7SbHd$lo`Rj|IbcLQap3Ne<$=3#R{ZW$l)&BDfxC0@I5u!Mj*jR12EBt+ zNrA;9Bb%fZ>7hCtCWbmkm_*EeMLs-T2AfzGCZEE znx6jmvkQkywG>eUcLm!D;kfZ9Jfk4&4oO@9luYSWh3EO8S8jCFEdfTwC~iY&*(|ca zN;PmD0fV+7bk}u+{PgsC+oXlQK~x&0j0_lC{L)_BTT-`VETwuLanLE(Fv>NjI9~}4 zao`sg{*iS`FzwpP6v#;zCQG$+MUK)|oOu#{%LA3@j7n5pnH{J^XH@^eA@UHYEkRmg4l#;Vm#gteBp)I;jd})s9qf$Qr#>pN=cqMpGT< zp#&VUPFzg{x0*NBO&>)?!6VkN#K0Mu1Y?sYEkHNyoUm97<lO&tChsu+lH9>wsXnDMlw*tF`tml|;Zvchg`~G3 zv{W14!x)(LQHtuXbK8wnhUFN#)32$HVjQ6)Ofe#WG8uoX^-8Nf{-Q8(uf34JcJi; zA~P1iM{(cTF!bO*J+H5O-fKATA4J|s$b`I)QQkN7ylOI{nK}#U`uds=oAvJE9^5Kx)l`#JaCnMp_h_4CKim*g&U?m5qS&U2pgoM$_A zhEsG_hukGCawJZJ@EymiE#8z!11EQE@Hga!GaRp?<~E?cnrB-)YT3zFT z9d(Va!JldV|D3A%*XB6$Lt6g-4(xCZuGgw}Rf&A(uV1W{?7Qb84z#b?>OF;gPO*+S z9&SRe8|ThIgP#-b%Wu4C_;5(|2I)p1M`isBZG`dEHF{p*9gsUDWp!)y?kd(Vf5Q^o z34%^gs0C9BG<}~dSP>y7S8|J!r9YR&86NEj45o&IJ9(4mKUC`Qe?kwC1xyy?_#J|9 zbEMVrpd9+MZI`>Y+3|40()(3EU4RU8YH`+m$a`b3W-M}AvqeDbqlf-+CZ7( z!5Y=27|!sNI0nLsjj#Ws5O#`*u-+z`hC1uM3FiV2r!zK#C-CR(BS`9Pp^P+(bzf=v zCN>?-pd^L{Fk=&;ia>)nh9rO&_pla)mx$nH{u%Bqd+Qn_>ny5)B>fGGOZMNB8T!dL zk;pwgsM)>klcP~O>`={CkA9N3Ao%kLACYHcZT0R*PbPZ9t_*iZi(Abjp-1 zQSJ;ppn8Re1TR@Eg&TGcto}p>r+T*#GqW?JC1Z1-7jkgy;PeizeD?a?m0x^;o0(U| zNHATsR~V}VSKWSQFCk)e6VeXs(8w=0ZtzzB2dDZQbF}<_IZNJIFw$R~Fu#XW*HXgC z!bgfvpPz~Z5Kb!O1knSc{jHZ1F@Zgo%}eOtQmMW{YY3;hf_Fw-!LlQofAsoD!H6t0o2M**Qlnrt96{a+q^ zv$JlkIG$Dkiy8QYpTKM}=EoeZ#Kj}jc-XYpM|p1_lff#E+kxE*pH;#ce~=>ebo|zn z3+tOAt%X}%j#tZN(|0k?pK&mOCF`6H$BRC%U(^Q%2T;c)#!9U-Opz zk43clQ^|0LZsPN`wr*l(FQKu%<-t?mc5y_2a4-c+x7cp43N z)`$e|SFg=&>So_Iap7Ai#Cf6rRRMI~ywmksxp4++YNZ0FuHI{rW@kX%h5G}v)va(( z>u2%Doh{7+#(B2>WQ}e%rZ)b&t(L71JPia#5M8-VzWjmakIb8Q8Z6Tp_)P@jVl#1nKR8KJ#i>X^F(pzMwEJ}y%u&j~|dV^zPQ=yE_ z7}2W0LGSmAT7{dJ{4u>sRI9QEqz7v2`D?s_;Q)E$&9d&aDpEF1tFkc>t;(zNsi+q2 z`y+#<*Gd_5Dvns^LfOKAMYYnK8}LcEvfQYlEk`qD>s2h|vP)lYYF0W8?ORH3P|wqg=3ItXkO}g!{7k#a{})ZLJ@d`iqgfYM@)i{?RcrV`3?qE0ZZQ^oipA{ zh;6|GpRxu2C|kDdL#fVqf-~tYdXP8{$v^1-ik@?lgxPcagrhy*cH%t`C9=|KC2ZvX zxsR@AMgT+uv)(G;aX8y#D$jI@v-7k(HyWB|v@_I0%1RKU zFglc~*!$|Es)-&t-Bq&PQJXBa2k%I9241TDFAXwNq}@=MPLk_WG%^fhD^GD68G;-( z`vxnG%*D|!(X(WrlnKh`a*swvAFxE}Puz~bzr_xFyk4W);8qGKrOZQStGa4csb`uo z1))cAhaW;ibBU>3QBcxmHf`FFDkV*=v}UPkw&^uN=>pXU9#VGr{Um{$t# zy|(o0NIp3!8Lm=qub0mRkkEOi!luYp*qCYBL}{bxHGWfTgA;0ud(9IK&TYc8e*E3$ zm-ifdZvFWx#=ck7KNlN@6zpq;`cKZ5myd-Bca7ois``Ou|*>U2yDV|~Iw zw@9J33zkVXIF!*{G0pqX_`@`xrnatC(Vdv)BVUuZI>t2r#xQruGzZ#DvyaU*Q@=CQ z^p3L!^|%c4X~m!H)leAbGTvD98d}2r_hX*F{wg*z%0FRd{D~kMi+RpGDLONRdG58D zryMtJE*lCskqFB5Z^JxAdl1h&6=k~gPZaZfP6kMrr_EB!$@UGO{6zRBZlG`|vd*ag%4koPgBxlT&O)Y!9QOmk8^(-cj%%``=gZ8A+^nR<&- zV0UDjrY`#!Z1cew+pHDl{(rzWO^*3PYKt+;e>rMD`j*VH_E3x@ivI&iJcKsSB8iFt zQIZg)xzmXNUu5dIUl%;)|3|R^(Qi8)wP%{_a*VJ7#WM3-95o_KMp{t@elr>-QM1(I zn(+e4zbK*v6g4)ovNftfGAZrb2`Jus?74PavGZy%Q_Lyn&lS$1Ic&TWw7R8oe1-Rv zPO6`Eo`#Q6{p>Ff6odz;t;ssA;bU|^Z`_pffUE-69A`;6nUwwKQ-$bHYVx^}J<~!&c?l>*7XBFNBiCd? z?vnRei+7+` z=pP{&NYV1-T}bKb)@n>-d5r{g7+62@PGe7^WO)Gi{r3WC13GQ11tsyXb1Y9b1^#-% zg`Eieg4bG!6BGe$jFQgs?Db4LxXWJ9+)C=7EbCb%9nc$ApGog9Pfu)AqJL3vA*?Gk zqv-1hi`h!cth(~p;{U6=)k^l(4^MC<+w{K1`Ai9#B+U58#ZlOi+a5dMKDy|OA zERN&q9p;)Mk@(GkdZEmbn|$VGV2spe(v8E<(gFm>q@9om;+DW{5elQ5xM05igrWp} zbJRI9psFJeRM7_R5|%l`U1co|sV)iQK7)y{x77cHi zv~*0Vy$SiTqttFz5;d}}P&QX{36gARvINbv<#iDqoE!xfRNDv{(tx6i4h&)}7AI>& z%@yTKpEyA4ysGrbxS+VaFHzoXCbqj8ab|fbA z3(=c0zuZ|ZMP>fArgr52j-xKREZT@#$Ye#m!dYB!cd|1-P3cO+C{Ums+-+)5-OY+3 zpqB)_(HZ!I>;vN!DT<}`&=0bjnk;y zu}@g4e5y1QqL~EpN zA3=xKS`O2biZ%b&=xE*)3_Kyv1RI+=2-PWlyG&~14r;+k=IGT%+5?V|Y#pp-;`A^G z@ZIWRRL|oV9rYp(6^lo)}y98QxcjEm21!XqpmzBj-?u8_&rz@(tsh2|f+mXn7s|YTUgq_)-)&wyYGzZ_ z2@|vA303Uo=+4?`{baHLZtrUlRJFQa%brGdINkxq?{|@k@*RxV=9Y&2Oh02|^qp*K zoPh$u z5GmO*n6q`K{%u%0xeE#$_sW)7KPcNIbVbd6wpRE8H5jS+>w4p zl9$pGIbOYA?FN*goc$DLipKngl6@#Khii^U8gLPEF6HnU$0Lp0`g*>Q=)#c9scCQ2 zY&D(TYFZt&TY##h3DME%7~iHWKw>HxBSKtFtxzOMQ}G9NBHk2f`7+It2!fbu=sp9M24Rc75dQfn`|2RIP!+RE=B`-3%hEY3d~(KHZP$7SIOHD z!5T#bH+Cd~kD^4dhPH$V@N<9&9OHLjwHqUcE7er-Ml>EAR15`}BR__bZ=XBE8u^E! zBmY*HBkvrxf#f)hxpNo}#T&G3A4JD}J>&kWj6~;g{|#-8ks)!`0rN3N{i9%6qQkW# z@Pv$Qc-V2qds{g?A^5Q3X~Cd;Voa8SELQ3S(l0BzcRbWoj`5SQgjTVC+Ab(Ev;6p^ zaD0&Elp=4i;Z7@gWBzIRZ`8cw?WayF;WS)aImn`gR}mxoo`1Fo8Lo;`)9}5CY|t>#ikqOn zK1WO5;VIeb9i<)E8FQ1U7+haItlm>y5C2Q>5J{2kSf1o3YhJ@{(sVUqa^o9j3$v;( z^V-|DE{WGzC_cm1E6T7ng0Peo6?!XXZ6L)SMi_Z)KIQ>=t zT^+$G92i;!{NfSh9$fDp*5GCfv)HuB%N{xFvdb}XyOr2SmI5Vo%*Bcj%>NWD3 zyefJUOohZ3kLQ*0g5VNKGgicHdEobX1?u`zUaq>nl9$8PZ*aP-?5Lgk9Vv1wALQd3 zu3xGAJW0RPkmAI@Rg_X>R8XAG*yRCFO`%jclC)oJ7j_2Dr=a;swD3PDOiGs~?cl-M zt%1up_Tb@l)EH_8+1e|zUU~E*9{qC{2P}@jt-&#*3Sfxy!*>?n>W2U1x{^1#GuDci zhWp@tVg>X)c*;=R_l^ygxTHF%)MCV!q!?pq)va#|+y(`NatT?iZ4F$KCT(jG)x2Ap zFKL+HlS3rNaE}D&nYG24UoU4w3`3u3hw+3IspvD&IeNFCF328cEcx4 zuc9Somw7ptikZ^);bGxAqR-&!c(svXTu2FT`f5daZKB%T=FERP@KD|y-YYL$oPA5- z<)WY2ux$@XrqwU{)@{4=E!#eHX0%zApbJWxe-gESLqXknU&=NkV~6#djC=5k2v0Mq z7Ha@np{4IL(cu!~gJbeVk)p;*7({_MI236mhZgkEZ+#7C98Lc`I1wAwy(z(o1b|Gt zH$`O3;n)~bP8TD{&e>^Qu@~v=xveVRLVv8f zov6fxJr_hafC{072ztLy;P>_A$?et%GrWNk_7ZBt)9NhgGcVH=f4ujml=PYVr87Vp znO5)MSbz1CBI}*S-e0BodIU~lo3huVH`1vGSG34Z@$Re0JjcOznIB7G}7LczEV^D_i#U2jcT|lPD)Goa|W9+>s}D1p%2L ziHIbAxa4BI5FM`(?1|^}#aTok`+%cKzQH+7nk_HZ>}H3?e?{VLylxwC7``|Qu|*Cf zXi&$&h|5%5+;_V12~bLCGfNnb9UjgYv1i~Hc)2T1kU04wa}DXI5;5yg2&Km9}Q z;`4n&gO??m>)?y~qwC=2z4kh|mOEJ-gVnND#y7XF+vdyxl8iOh%BdpFzUH&6h|XW%>C}-Vrb-Yi$;E~$f1wO zO3!{~(#UDXZ@F_9lZjN89hIo)VKvuP(g9kF<`B@e0q_&~hXt*^wtnF$s1NSBlapG~ z{OvvNIm6%H)7!hG(feb16+ zPyYW5Tb-5z6W|YU)@g(A$s{sJv{DFdQ_>j1NIg+JDt(5GyYOs8a>Q|y4m$AgC{M@2 z;ICgIW1{|1b@ac|sY;lYWdncBiypW9HEZ(rA$33-dN1(pxNjxM)W@eFEe)4QyWk~= zsEDG|NLOoyA)RE3q!r^NS8!+_)rs2ZzfLpe^i%cGwS@Fx5?$}s+vC=6GkDGq_4-*G zQTlCXbtV5qps#7V-GF=vh5VUZWW+=DK{a_V2NXnlgmfC|5()|GSaXqtLWORoOgY}U zXxPP){A=E1%A(~(VT;U|tL!fy z37(oX{Z{->KJyJiWl+10SA;?`@-=x$>%iLXuMh?FI`El0XixYkVsi_|6LCFjbhNyE1L z!oRTXu3jUv>bh&MZh~Z3SeDDAv*H6)aG)>WCNSdc7-^OoHhG}PoJ)yj^HwyU|8Syr zzw9SvQ1wArB>G7(k@-wp?N9={U=tQKrmrY)WWEI#rRZNUN{%56aT6Zw`hfeGZ}*7| zYbx0#0+IO^!m#^}?;QyWkS>YrnPmkc4vh;$q~|7k=-9d|fXHQ{GS&?@=fD22M2I!q zO#j(_IQ}llf2{wy@*&S6fC4=2LllK4{pAtchkO?xDSI0@BHp{PJa|TM;Mb9ZA4sdP zD$5h^T&Ld7&L~%Q5ffMiR-OsbM)wQ;$eAvEFG?B1bjQn+ZJ#?hrdEQx$2*pnpOwU4 z?^((2;3+{qOdv2EpFB4-3+u@%H4fK*9T`u$!?V;YF}bmQ5&IgMaU?R_ll(8^q&Km# z;bW54^-_VVq5DM07H8#q)QzmBKX zIqHj{Ai?Ti?o7jntXD9y3 zX7DKV$ogTrM<0crZveh4^aw1~MG8F<&0g|GMew#1xGI{eOcf+up?{-TsZ&_#U3sul zkNze#iGHX+tW5gOKrl;c7gxS)Zp`%nzBFbDYs+cEO~n~Nant)%q5$&)%Zj!|ZxW6w z4A-f@LqNLuz3{HJ6^mUjfB6Ll zTcM+c+>;CBa9IVg?{Ub{JH^tcNPBmhoE(L&s#mzJDk=O{r3Y~8DRshiSwEsYSCufi zE+&+E-*Tzxe6gYxSgd!yGNLsIfoXw0LSxsHnca7W(ax#g7LAdt0xcjD{rCOxq{T5W zZ)CkI&?j8s;V5m3_M%A&p5D@d`lh@J_5D@VrKsFf65U$`qFwpl zC}!al)guwArfCj-f*Qm!&D9fj5iAwg=o-t(?^dz?mwH4b5@~`tMvW#1A9ta-fJ8&d zPve|fwHt>m!LxN6+Hh3;T7o1U=%U^K$$*3M{%r65SH?u zIche%t7gMZtb0ezhRM9_dTx4{54S5ALg!J)=HAvmpCB-z%*jZnx5(zn1wi)v6pJo- z3|htu2UByCJ#XXqzyQC5gt|)B``(pJg^DpkUR5!iG%%!t?loe@os#3qjzO1HS!9zo z&!uX?8`R2PT#Y+JINi8o#gRx&y)WI(jyoh=-0iEDE%P!4#L&5m<&26 zrPM#0m~bFD(K|@LdN|@rKO>(O^&^2C=xdfK*&NMCZuPc6^>!?fsNS|4-SyU@ znYghul}kH)gN!2|MEkjd3e@mDth%#^;p=O>$M12&HR>o~5amIV4sg`4IgAz0bQ;DRX<2n_NcSC!vq2m^gu`N8`!|8Y zGK7CLd!~jkg`ZfAI(ViHscVlHfu#5~5vWmmm6Neu4z0S1lSHeV-%BxBMw-p@a5sqoTj5Z%86)elsTe-u}kD z<#4E;pJVvc$+fBP^dC-jJa8YV@!nG?rzwpSEls}s87hA=5s`h1gO{g;Hc@6Z7O(_O zX^=wo+!k&P?^l`vqc#&6<3(Nx^%zt4B5Y!OA^9|aeM5ko4K5Ss@Z*@>GNX@AJ` z*l@Omqg>(ykHXRKZPZlTH}@GkkqYB*)F|s}w>bJgq@fDIQIv+BwSn!7qbHy2fTL|O z9Gwsijp{^0BW(QS7)5rI$EKmPJL2a@$HY&Rh8|?_P0tWD&&p)@luGnWx$Db7I%=0t zM&_FMe8tmJ8_%)1_FM*dYUGu0GsFLBH*@Wxr#j&2sYh&D3f*g0A6_8j^iu)gdij%Y zlhe(LoW_`_x+;#GU?T?WV?x-a8t-8@eo}PSJ!uhAXdxnpeSatS#x)4iadgG$VBOLc zEzXhU+TW07{OV~mZ5RqjEI!GW2>ym1M~N1pJ<|L36cqUW_B6#R`YHM2=X5?z?nbDi z2czcb*!-rAg?1Z9XhVbzYeDX3o&>}RV9^zs)^d$IyJ>B2w6*Iywq~u%R<6P+SJ>@M zpgr3>!Xin>>_u|;Z>^p^+f7^f9Or5F?1^KxHPdeEMcPueTb)w}Gx>V0Hmx)3V`m+y zE{^J^*=f;c8#_0v^xtG+jh>T%{=XDMc5o2`V2-1{S*zP;Y-HxjNRl4^(lkN$K;u7j zw_GnP67>~RW&1j^UNf>NmyGe`K4WQx4%(b`Z#ea}#z(hs=o06uCMcUijvSDu&fM&y ziPok~M*EsPlc>OS+@TPqW`3S-VN`Tr!uVfc6wu_%gPt5R!dU~p)EFU5%mP|={YOFj zuR#i0YOg<>kS!Crhc3jp(EIxm#Z)aSrg}MdZAy`xtZBhu=U){dkU0hZsIQM>InFmb zjSU6f^+dmn5#|btn$^TnLf80JG@xLeEU;!Dh~hU2 z>q20?EsEi@nTv`!+xx~!JX!}-mP{@3I@QkKnule~!!XI{rp{ErCV*CUoY8TxR%}(1 z)DZouJGf(d0Clt=Ql0N~wG@jyH331e^PIi*F-z)rbT>R!c5Z`AfeyG%L5Rbloeqx? z89;V#=Hu@Bgdf|+Yg6sg*%q$O2bT8pmAEDCKS=w>CK`~CYdE@rak~_ z3rMUn>;e~O0m~IiwtS7vgN0U6R>X1GrysE}GOQao6h``S40s$yCf1!09A7YH1t%kc zW2S&n-8Ex6Ft_MlsV&X4tkksQd>cr@dgJiEa$Y#MS(*&O**@#<`*(4>I^*1K{ zS}`59+{LYx$jsI^>-k2FBa%|GNJ?cLB&D(}=DzuLv_0mc+1>=cjpAJddsAxZZ=C(B zxK^XzF)hsRKIS_UPpVOw7+pvZaZSgs@#)5hjk0Nqo)v+dIvj1MHv_9xkb`Kve?*gP z`1>XaWte$dc4S6iJ z*r7En!f1E+vGZv*a^?+3g>?U!`Fq!&ZgiJS$>QJ+7JjTA)@uK~-q|U%d32otsivAHK8P@w+b+o6>@pL^Q{PYg7ulM!Z#%Mf? zHhPmwmlJA1{CjE50q+IIBBmhv+QA)C(4AIV_ilKQ%GB2+?#>*{Noh09ABR=oVeVMo zMCv)o`w7}hWgmd>;+5USU8Kf>B9(Pu2KWXmoWRokwVXU%_=Vw9#d?0D}IQ1jNN72 z#Kum|+Kxv}9dGCwzuV9yeaoRs>NIrAzvs}Ueb=EAK)~5|8ag!VYUpHMMpF&1(@s`H zYR%L5B(|C)v6W1E_Bx@=S9OW!7K}+>kcLmFzrHOzjOXje46>g89PZDv7JY6FCu20g zZEn778{rN&<`tlR5OecNAv)u1q7ubugiuU4veNSRLSW8y2PUP3H~Qt&GXJbxPV6#| zEE`$h@zAnnGjXxC4vPG1A7{98b%CG>ds}63U&j*mmI*46v$x1wFM-oCQBlKG*^PK>?%V{r0(^{z1X?m_$A zkmx(dk~BQq=qMw!(fZi8)s98fd=#q5bwj z_5GPra+vvU-r&&Pyj*#*LVTiQ$5hDK84LGw8uQXZ~X52=(0pA`3o z+XcyfBiCQwYLnI_cxww;NdQiPGdQuAu{V4S%GzY7MIKq-WnyGm+H5ajwSF;QU{`Cv zSXEKEqvo&bW}2hsA$2p``;)4opLj2;D!SEsZdH-jn^RSEhoe>okqsaWR(Q0UOXj2S zQuC3BsH)Mo`jp{|a>9SjA8?%HS=I*J!leq~?s4DDj(!6Lb@)a&w)IU$^e0t?T6|d?D~u+4FJ$-1p-w8fdaj*Z9rhTQnzM8&lJC^qz|`SOr=A;_S`g;APCK)DbkCsl zF#43Vx+F7={CLId?KJZ1v~xvI!|#q2Bs(|Ll#f>nNO-8EDPL`-DQ_fA`6AMk_o+)% zY08OWz$+oe{JisC6@~REm4{yrbT~rnt43ersCiTJcx&#O9op6?P5 z+DA{FJF)7}HD0Z5a%A+D`910y>$cRj9BBxj<8&-<#1AN$iwpO-*RJ99w2!-5AHaCHnka#yCOFpDql!jwst5k z=0UBIakd0)nl|0uUbus|7r6>QA`nsSC|*s-#dEayOy4}cP2XgkQ!j{AKK2ut(6T2x z%^1s_6gkV?JW&m&GjMZ`bLq{ejKrDu=3JhJ^K>rP0yUtjV)aQX^D*5dg^9V^0|{9LIezsDQzxn|}R#|ml0__wsIC4b5S>tcZFWp<3npvdP@3HgkJAjnv#B042Epl9V<|@ zbc|W;0XbOv_F=?soBQ@b+?DG+9V^}ac9Xgh?ImcTfNyx-~ENvM8nFrK_l+LH~k;26%yKt8&4**03kl zr5`fsLgVzp{3CLBd*NrEZ69lqH;5(21)a1F2meKMC1*w2c@)+XlTW;OKdoecspFBg zY|^a62I=ggy%LL`G_qAP8xW$ag4$o;>c-Pu_ExXY<)Nh6F}~f8>ik9&e|Ssh@`IMY zb$sv?Dt&wI1WivX*)p%EOP{e!(wFP+0t+#Kyx^D>d9legY$LD|(;ivx8up$_X5h-$ zAW-1}{p%D|Pret#hfme4uN7y>oS6Te-h#Vj;MS!Qedzl_piW|A86cma8?R{rGo0%R z439g&1$;&w2KyjSVz;s5_V5q{yS9hmwL*hcd&&+p?lt3nSp3RXnu|vp`&bqapLpfy zWal=7Qc?pVi&#pysULz$FY_>QJBy*ul^D6J#qI^J@qIFz+=wDFgFyHIDGF1|0P zmDWUPJ$#aMjqLwQopHdM`Z9nf*vD@&rHX|5N=1+1R(cEpeq}CYjI6t_-x71nJ@YsFTicU2s|lozd*Akn`i7oROij7E z0li9VFgnpMK#6vZSUoswACi#!4<&muN?IH)!qY{FqMs7WG}4^Lj7gk7$nhHSy?rhH z=*3DuiVvb61+l3tw7&;Yjh*W_T|8S?t20nb8W;4Z9(vuvVX5%We#Yo(yApR7?Vo(4 zvFb3#YI5$OsH)s0i6T>wQVz9MSLIKyjI1}$TP$f?fWa~Pm2K{mv&+$W>WyNV#0rnI zr`}g@F|J!;gQ0$>8>>wdOQU#Qs8RrJ++P(frgUcl5PDxt^uAOWgUtf=Wc6Eb&9u6R zL>&V};Y{%D8(_@fm>0{}G`n8;*k;>(SSmHUi!*)rh8jR8!b>M~=+)|tidSzVNq z;Qf!1E{Ag>40>O)?Cw?3rXM?A*?-2$-gdmQfmqq`-IryA$T2X`xH?vPfGVvBOa6pX z?icUOD7@##O73AT>wp3^c8XAN4Ig0ih!uU^ESe_cvEXA13#>a*uZ)!{|JKSVDjw*j zysqUk^-j(Iz{=v5SidEy7yp{Nb7tm(;EYz}iU=$clqajGW zk2KGI7^Cy{N{+ww_dt?S4jRHOBMiQkhvNId`^ru6@ScibY+B_yo25w{Bw%AD0qm6m zwgR=1Fc)A?ET2`HX7pBI_ppK9Tp3^<;`m}hu7DQNSb-ho%X5_GsK7rnhZ@}^TiFS_ zDVI|*V9xzLvV@+et(0w~;Y6B5XVu%^eiGSP+lTtbgv;Ht6h@pRNKedgpPd$F^{4viCvZ*Qqckv2}8^il~`ag1r}~CY&HIl z1O;7yU&jjiSs7qJoX~?mkxV3_jTM@S9=$0vaYFsLYs|OmM+L>dUq3#1`S|^avkPep z88_A~ZJ}GgqFqjX9AGT_XLJ^Ms11CUA_oX>tgaGb5ck+oYb^U+t}?QUgJTkQ5WkV) z+w2aMgnRRkQ*lc;RcmR9iT(nch~sSNepRChf?0VKie51&npmXcp;&4{k!$>|>AOM^ z4}&(-!okcK4(@*`iUXI~`dGn13i$(OD+FxrzG+UJJSGAhttXSKv2(j2rv|s4Kl8JF^aPOfT>~>M44HEeD{GoxjCSyH$gd)Zn*;Y8?QN{PyCghB>RJKT}w%mbC^TEX8-UpTT@EksRIPO!^NM_pI$=hE$m)`wYdEoA+xqpWHzjA$^ zXM=zHjKC(sUW@Qg^a3f6#g2DZX`|-<%slc^P{NUyxg764oFgw;6+V>R2xHm}9$x^D zyFf;ln9F@3in*Qmmn+P{jXPp4)5IKm`!QGf4uHA^pk<)Za;n1IbqaF|Z@hlQ#9h!O zCzRg@>MHJ|`{3KPm}>;QJ>ux=dW!1`@K^D7(DypsZ|2`#(6Je`Y~y}A_wRH6A=giM z?#Vxg@q;%+r&O_*JP5x-N3Fgox_bo7Pp=qg;fvf|!N>EY!(C%Z$t^_{`W9#-y=1+& zHwo>$Jqg)ThyAhcEcEE>CG(@o&<<-Lzp!*J*GL0FEK{BJek`RPWw?ggd}6e|Or)}G zW9{FML|zuP2O%4G{}U5xQ+SNbV?^FGTOXXQf95RSlQ);id3l}E% zPFK+utL~*XWAt<8mTTfdO*4L^9&(KFa@8+gxLUp} z*HcLUbaBL(jMFO0#tmnex7fOA?roD(fLe<)W!4%TE+!bRdO3G)W0GZ%FM>(iZKT}Q(r z=hc^UzO6(_wlUX2Y4j7k7Rec5xo91b2>VigtpONzIuh5t&xeIa5dr9p+3I^q33<#^~|XXFAa1 zP}Qt8d9p1SQWXrxpvOFB7xU=Y^r)j&I{|v^Z(QSu1 zkYiNJ_2>b8fLWxtS4k6LR1n}02$1l1C$XWolAaRorQBmg(W}V2^H{(Ae3UMg#4bFl z-;oaZChJ;9e0SzizXH92YdDP38u%rF>n|jDGo|NlRJnFA>;}|^Z0s&I- zD4A18SKd6e=j<@Y`x}S<9K-30e+Q?NqBtE956e&smVR72P3B?W7oT;s8=dFG+^X3k zn4Rp=53vC0y^O)6vt8!qi1t~88K84QN!QMK?U^MDQ%G&PYaC(SFzen8;}19!ixN3> zd?@{DP7teT_~LT?lC%mv{Sw`oIGh>yVSiBn4l2k|;@u`EEHgQcUn~|w#RTN$G_}q< z`X(yj$kEr6B`-=HeYITwhKTMk<^qgKB{OC3hvdFyGJ8MS|0tizF5tf!-|ep8zjHx1 z@ZW%Cz8m-t!iSHBf6F~54F8J&b3*XXjN<<{==8dR|K<7J!2kdL6or3!C;U$wV8K65 z;lIelf8P^C0PxjP}Ld zsV^%WR$oS{zK{(@XvuYV_hW$=RvNG=X?PU;cuHa}uyz>#VjF>O0991Lol}lxBj9EY zA>-EMT=X1i#z3{Wt3+VOEH@0B6nGhK=^DDN?@kgook+RT={d$@TucpSNUSI9LV^`-Y&i0a+`Bk;Aj{RJyY?YU?{f4_cJ!T; zB_2exrV}7?1UYjoANz9?jOnTpHJQD&TNhqYl3m8Uzk4!h;`HhtdQ%eoyT{o~p<#Cq zkDgM0z?oK*=siz%l7Z>wp48kh<_4y(WKSvkM)sN0R|3#*V*`B(4>mUb-fZwAuIB7B zo?um!ZI*w{?5o*{2%qZIXLKA^pM!ixpZ}=77HKJj1Y`EOo<0{C=YnJUTu+DFq{CS* z`c3dk-}qinw1BuS3PjlIbp_nQ?zJ}WgnIov+iBS*)9YvG_4D+a9#4Np^*Vs7BfWm> zcV@3Y=W6x(Pk*+1y-<36j6OfgNA&qc^{whNHsDyFi;SW>y6m&;pY}<|naz_BS?n?Q zSioE#1;+0ErSKZN_hoaxfA2F$xIyoC{6Dkz`?y-Y|1*oTIp(VO#~kxV`Aqt+zV2$w zixzd+|Bhom)dJ#{C=hn9uY@bvy{@?Z`}cbKA=T>-erxvnFjuSB|M*jfUfaHd&~~e@ zr!kgM4UdZR12pbLl0^VXg%gJzrWc^T&B&&ls4zqez4Hm#8!~l7LXGm|oavWi(G|yN zr8Al%={mo#6dpqi4*&&>(%)i_PIPHa(gj5JlVSiX#RpO((cZJ6L(q%qVY?5BqF zB58FMfB91OSK`d&3Tizg*|+>w~G&sR{ z`>7+5@NYc&TdQ#8OE3=Nx@USbR0bv>5KS*a8&jDEe1W@*jJ|{9Q|m+o21E|V(OqQa zT@z`cufsV~Ixrz?)hpN|13h?=tK_V}gi`_&azdA}yd0MX7MMclF=*h;-+X%-aM4L< z;94%0^=sfC6m6>bZ|(I0x*nr}?LHIFHiomH2Z+GY*QA0Mpn|WzFsUF%HY|z?l2qLa zrQ4s3Qo-XVMg=!9WN}n*V~h&!GpS&Bj0%b^=;BBq4!}4%c;<;?(}7bBrq7~-txrG) zk4EWWFVn7jI!K2~gbpehQuae9KnML1Oac?~m`H&M!xbq+eTlo}2e*LSWAlU7M~_Pj z)t^=|xZ-SP>p+JX7t+^2fpIApiyYE^XOhEy%4)9{ndCrjKVb=a zn@T3y+~Q`%Eqq1BhsfnZC0BMuC0>(TOtiQ~Pqve}#{b0Z9hitZ9^baW#Db8dbUH52 zC}RtGY&!YvBgdr^|EHbSjlOgho^cKri%33vx_h4SyW5Y;Gg>}3N#mnAHfg-^Sd=t= z7rhOiAkR3Tv5O;(IG%CVQ9L8ng6_CH!~5v5c}5m&fknyU8BadifoHtjH%1LduN`mp z`?fselY4|3CO`=jFy6p6ra}$TeLXq|+0vJau&f_4<||_J^)Xkq6d=PO#qMFdxvmWX zHuRP=Ve=@dUlk9P%0L-gd`!Z(q!~pDIPbI8_ zoiL0~-z_la0brnP_zA=KgAOph@`!@*mM9*#vp+osjQi-qw}CMQ7;)5P!k013Wd&V; zY0G(?aCxs9F&mcyI>R~Q*l@bPC7d(R-&-(3id~{p!I?Gvvde8r)LePodg`CbY?E9& zl&WkXEfr{?zKCSs^$7E=MNa4;LX|`=;k1+Ud_rCemNf zE8(bnD51NaN9Yo2d`;v_5nsqH$vTkpQ^_^>2Aea4MiEsV*!d)dB$oueVjp8^WZ|iY zA!t|OR+$CHyFC!t%E)!ixxPP_%JhmIdYgfl0wCbH8kvsmo*WfP1_x08679xumqy;7o2rZ!{WBtRz z18cYq4{Fjt3oSCY@}K~?NVelwNdj39Fc>Hspw*$9uV|O!W-gj4YL>_I z1X1KStl}c}!t2ChX!4yd$G2JQc6=XyxQp>UM7>>)?+fJilJWhV65Wn((Jh_Ecf@jY ze8m`Pjc)<|^z_Oe-*0f`gB7UpeUMW8B;)(BdacHCu|5?|HRHQgy&Ep$d&@D$chx`D z_+Cs@R!&0o-8foA7T=R;9R6i=JoDLvuVKvHoD<|u-a;foPIy6wG2WyKTVs4(Bs=q$ zYK$lF`uJmf?y<%=A8KKY)7-N}MNyU$H_BH`>3)=DPub-de>kq&F! z<4qan7)zLBw_`l*$DPJ_5;iuO!!5BfF7MT0jJL3<6xJmBNJq`%)+l2)GFF(s7E{sZp&!Pts;Io4@3!LA{5u&Q)Xmy!l!Y$Z>f0yDzA*J|6Gxvn)FH9eMZv z9*7%fc=rVlsBylH*T)~{WAg5)%evv+ODeh_W%3VM{zP+Fj&sJuhBi>q#$wZ3&fVdf zqBn*Yvex1jRM8g6J_yuM{ADa~J5c8EK9c>8`EEQOfzvj3#pK?V7fL2*oqfqrC?4Z7O1 zm5OWPLYHGjI{%0fA(S&mUd`bRU~pfPR|VZViLl@(D!GOIE)=BUK(?mz>d?FFqD?)) z_pj?wsY{wiU6tof1|Hlp#`{$j{$Cpn_@^howHosFYELx@Zn`tAKSG2Q7# z6CT|i@^%zQBxXTR4~1^F$6~Tur&BwzmS$cJNyb}HyD*(*8K3AjH-J{edO9i1GM!>X zHP({fiA~Y@V#&eQrA#@~Z;;7cW*JV!r3@=7Lxs&WQS7JKyTbn<-XA;Z)uvy=%qUEY zt~wEzrgnho@=h@Q@1TO|q8Lm?M#J@8z@&`23NDW*gmC;7tFE#qhX1GloT<)}0)N3S z@VDv86M^+lb@BKs?*!{j|5LCQ#$YWlvX2L=KpB3)#vrj(>7jJo#O3-Q>-w&B-H$uA zyx(L!@0MppGch_yJ)wTQA2?P&Zf`%j9~Co?gY(Y)i2C()ju)!193l6JuHZ5N(b4~L zQNCq_?QQnwCK#31!tby(!1Q|?=ksd-x@!ubVVuFezi6meT-F8}19@UM;nv^eWP$>t zfUi6OR}sr_;Q`0;EpnL>-mYSK8xNk!!Ms`Q75%|Ari2hNjPm^9j( zObSU?O)CjPQapN#+u>@dEI!{mh?EO6ygm3k!q=m+`1T}k&zBS6_~F!-OG^=*8b0W$ zJcud}Zmk^FwlYD)=Zv<>jJKu5n^ymTwSn4fZk$W zougiDHkO;O&g9iu)~nx{uln;U&w8~?z4E10`Ztt{y}!t@qN$Q_txEr{qRNaN<`Yow z>RFkg+<1_bCBy-4!$0^s|7`yGcH!1Q|M=7enQ@5<7L2a+FG?EiJGZiUN{X$uxb|M9 zT{%7OBQv2vJqZn-M$Po-bW}J5#o4TyI1AEB$vQ zjeb5+PM58(sdoo2PJsfpsMvf7?yK~VR#Y&0gs&$r$0vFBNSL4M7Yr;?Us*gr!{ylD zp6Xa~FN0pGt(w7~l_QX@i_;+>XK|u$4u3sR5Em`UcY|C~yjRQ16z?>`UVA{Gl>#Gy zyRF{d{B0o@Jz>D%k1LC_yywfU+zhwMo!WId=yWoPe*jy-2$j$9(!oO`yvanft%O7+ zJkHenh^{Y3bU%0ymnMmzp8EXxmb_M@0w+Q2Ydd5__8T8um57V+0r-rg<|#$l?-`9X z@TB3!%UraTDI%)NYKYe$hkYBHt`x>3&UfB)s{eH6ts7MBnH(jfxkJt-vtG)#bG z2c8qCZ!LHJfyR@5W%^#hrE3?Du~$Vcpr&yy&c!3wbn&=~kA;`dRh7tA(>8q1yeww; zo<=I^W4UjEXDcO;4TOJFFhhwg9M7)x`Jsw&}4R z-!SEbdMxf8(&J~$9^396W{>}}L-qJzwb^4wv_YfgKD)=JgG2XyUd_k!c^en2&#%V! zxyZQkiZ1(nlzWHO`=40AEKwE4_kPZ_SnqF~{QY}>?oQSFLse$)`$iiy*2Ae}8{}Uj zz3;y7ukk5;-_E7$@xJErF8gk|c33@LXaTTHRTJOid#1*EJonP?-{U*Bs~-2X8XOXB z(AZHM-{a_dBz{Y+zI5aI_efZ{gFKsPVXZ7r#uJwd4?~p4RM*RJ+(iZWd7u>DW{jk` z`iWqL19zhEuw6XtbveUgcLkqE>wWM2SiMgktKJ4OPj;^Nv&-V)y)M4q z2PILZbOm@P9IM`6e0X%d4|k}yXMDXkP~5Kf^E=}45OTb$j;#r0&?VNKkmT%?>j*xP zYlPym76q5o3(HF@?gfwcH{#>n=KGS>3Jrnq=asYO{d5HhJf7fZhCJ8Momy*)sEj zs@MCztS$;BB-HEkvK%#V*o5jLY!&t11=U3l$hES%h;Qn>)2oYqAy;R05xVYrZ(()O zFXcM4y6B}Ou6?VE{>jyMDJjFKFCjdE8?kzX$8rN<eNy*_1iP zoE?=pOetjNDz?{=NcPk_lM>2Fv=W>Qn>zf>f-5Dlda1|^*h5R}GTgzMyiwG#w3nQ7 zPyBdq$BJaPzbUDbHg{?HTbxU4@(L;7l<->lxS(7jeIx*g92BaSNcN_?M8LUU9oH-d zmCuUVzg4dh;I6j)nWMhr%1%a%2&x3LtC5+itBx8`o);dV;Y|P7K`7oiOYIAAp}jsp z)YZZBPU0QbCR{aVv`K@TjA?)8i&o^R+Kc$MJb1ww6sZhca3-c1ycJA|Dw2Koq9o2q zU;GJG58~^~_*&mCCb9v1FJBWu1N{+N5^1Q^ibq|7xD?b7zuU*mhbDVhyaFyY1+6=I z6E!$h`wgD_b?zjd8W+i{je6>XD8!pK1~Ti^74dekg)LrTqli-5T3|5cdg?0mo-_a2R+T2e zj6WEc&q?hq9yrcMK!f14v~o^)%WvgS^PZA$hQpyN91~k40@2}-L{5F(K~Gu*$F}WP zGNsyQWnWLd`g(U^b}8mm)!=#@gGJaBz8AU3hAS{m}*q z9+rJP$uw}5`v>zvChj&tv9-CQKKil$j1&fnlknpD;n~(&QP@%*$b4nIV1st?t9JTi z?ynDxk*n;X<7~i?cTmF`yPqk{L0IoDj0HzLe2*~rP#*MZth!4E|J2*4)e`>oZH29o z?0+s0=q@;b<`<{Nz^UJe3K&nL)Y77he93P8OC&Wn!Gj!=P*JKP?*zetyWQ-ipz0f# zcjfxsrDkptf)U*w#Qp@l^DLI$+n5AI!8>~gio&BW38p~5+fR;au8^@9ZR;VMYWMMAPaLgB6NV-Y|pQc)B>pO$$~VvjBv=pr6F`xQu5nZ_?IEkEb7HERzqt7F} z>bWbOTQpB`xuia*(06+~Yqh!PA_`NPyz4UV>GVJpOfQr6WJ9DjDh{CC_K2 z%ZHXa9$HKMarlRxNKcU=de$W`?MX^A$7_YWhO&#Qlun~xr{DQe5*nXEExzYVtB&M) zIf&p$y6^W=N&32pk%VxvJF=m4dwTWW+mx`TX{nt)@?DuS!9u+ z@gD1X&XAmJk)EtQpVlR_?{-T1p|ml%-jXrt-XDUCd?}b}bu`16wm8LNd(_R@0I?yPb?jtjFD;mKyBx55mxFIP##1m0Ru4af@OA8R)RiDJI z{Dv^$J(cplwI~l69R8;JR*8x)gOYQOgj3uk3o5NUqP;#t>MuUuOw^3pP?auDCxlko z_+0(;B4unM62RP%WKCsq($|GmIi>U{_WoA^`a`Xz$p>k0RtC@4Cs|dI5D5nT9>dkl zaP^C@4&gUY=?WAH;Piz7+aq1!^$5!PdMnuT1a|Lfa;S5b`GwV0z7P;YbJb8tRa&6H zsw#Aj8nUh4WOraB(8}m!$-v~uz~q+e_?u~x>rAW}fz&=1$|_^t_cHI5?I>p7>Bin8 z$4jOg&)x=l{KNRC3_TrmG7oh8jpCxv?&~g2l9MEVq)w~wk52G?DeWKYH^%jJ)?MI@ zzlJ`+cNbpKi0}|9<17j8?W?Ifjpus8s4>Lpdl{^WWi&(!Po#7^nSABu~~;V~jK(+q#;( zku=%H-&s0YqHbgb>L3X``!gj8d|GHNQD1WNEhK>}2+TQJ5;&klOHmkeIHd+WN)jk+ z4T(~+J8>YBs+2e|K#2oLB@=-qkUEyXVFocK228ruTAv1q7+{=otKesYEbB%V_xceI z6g-w>5FSZq38Fv@2xNc2gR%3LSQVSNH;;DgC>9(K?^pgThWEaRY@u^ZsUWd4$|wI+ zp}bn45aDYnH{U`k_-z#B@lt_bV%?OE9+bav+lit4vZGM`Z*45S- z|0IU5C%8W@$`?{&6y*zeFe>A!xJs%(9Xum$3u61mWnNEdWJ2pIb|^jdO(jU-FKiSF zs}7{Dkr$@937x+x^XF)p%gr*sjw@r&{nUB9EPO{OHQ!x)>Q8x6Li}k4AThgrUmA$( zkXPo#{8U@-cDOc(lEUPCG5u6(VTXE7jnyLx09DTmW<9;StVhZBYJUMq6pwuuK}s0~ z-NpgDx{X94n5@xF#n(D~mKdl0E8U`M^`;LQ1$IJ_hNCe;`V-D zjzQnAzZE`tjH-q_eyC0(^`%9l60J5{>Jsq?*oE~dakVH?j8zIp%CdE9x!$f7UFf~u z?cc;_Yqg?LKakJXYE>7F^TXhYZPF-XSRJUvgi`y-+-&6P3v3+w$`6Y6os)-7dRj*^jE&?W1%Z zcHhbT+}@%0mBpjP?iK8G`d^w6QRg`xD1PuQ`GR zu@aIh$R=VD5kxLIB(Z%hMjxp$xEK`&5%EOR;TAsV+egBZ0g3mA~wD#gOnD#aI11DmLwj?mN(|* zCPcCy{xM6$ECb1~HA;AyieS`8U43*Ne2j_i);z3BXwT5gQ3Icg_X`NfM4|=rU)(6o zuS4;9QG%m(9wLeeXYmmR*CKIkFxFEpRL;+F^d@~mrFOx1L@N5^n?rxX^AzaP7ioX1 z3BD*8{(Y0(fgP)DqA6EntvWD?;_{UMEa~Sgp#R8M0DYbb^h}%u0eWm4(7p#ufI+V8 zJWyAe01py?Ia)GOA$!VxNS2{qjMv7EvFEzqDB3|3HQ7Sdm?T({scW&{Rr<$_Pw*WO zIoX_N(R0<+L6~0%opHqXKxNq6B4dQA=Pk6E87Xu&tZMpdy*|fxFBr+-JDo(U?S@HMC#EU2_vax;rvGr_UZ?~SWm+N=Y zuYH$q;$e)x5AEeJ9_E;m`^VwYBlB*tc~$FuF!RqM$dQTwopI(L&S}d!xt&bF~m}M%WWxg`YjEO4~8ezfr zOAEdzt1`}}7l<$SaE^fJi_BV(DRz{OjMOEM$z7bz{{4K}N}f4}Y>wQ>_R;ZLi}BRY z)TW)al)SM`;jL1Z8`<#tzO+)o*z3EVdWLEsb2Xw9b4iIUEp?5u+g*^(VHw`DXl5{v zC>96{c&J|OeRs+Ja{&eyS!>lg@wvF6Yz)ogFdYl`0N-$y;LN^)57`SPDDXP&g<>q) zIG-B$=fDgdg~u zdusaU?uNano7a0B6OV}IWQbcY6p@~!Xt+T93ASdji3o|s_WE{PoFZzXk-W(wQ)=Su z(3$rr^_JMfa@~4~)XqAGMQDoDx*b9#?W8cE_rmWnV>+=Qx1L??NJ&r{b8-gn8kgTf zp>3jDmodDF-@4MZSE%>h=-ScPoi`H=ovkCVWogk3$hz7qk}bc-X$XW5RUZ-)p=9Xp zqWm}4bt*4&I+Fb{uVXvx=y=#Vr8(%7D444e^Yk{la2Lq9SXQ6UuctbwX6&kpt3=zA z<|}m1a*x3;t-5%!i|l53D>GP?DvPxo-yr|08dl{#^RtRyXy?Y8#sOqT8v>gd@pr@1 z2?@)p#xD6m3a3xiie||4(AV_l`%FE+yYLWphHa>;8NFZ5O2SpAUKT$Lcfrg;N`=XT_r4D`N#nUf<iL!k0r?6v~ce4v7C*j=IQdYU+_aO>lBj$Sq?P$Q*kvJW~lmg{@-N3>ENR#-4}7&@djrll{#A9`)2D`9$Fh zo6)NoYM!!9GHqYcd5rbTU7Y$}KHJuQ?rrNmg>9%bHuI=@Y_vi`Qg7kb&}#?`b{mPI z=XqjS%)K(M6$6dOuC<*2+V`A1KI{FWZC$^6>nZ8?&M2bZ>}E*}Or>rnM!a#%9m+1lMY*_6eKcuIm6J(me0b^VeDDJ zQ=zwMuSw554Tlkl%>-eW=)Jd>0bF9y#JU-Y#rn?b536X{LPfcSn$f`}_yRBxks}=a ztChUvb5^xhl`ZA7i}jT4HN4-npn5Er|Rk?7#*ss0ii^O8#XFXR6?!$vP z4@T}wxzE(9zDgv1F_Du2oNV}H8w%4P$r;^|^DRZ6yFRq+3fXC`4=uH>RdN;iCz@&u z%Rad}+ewv{hD5$hwrR-hY+A77m8R{BBNZodq>&(Q z=l@6ByTC_PU5o!oCPM-wOh5t%B0-`?gBp!!LO^F=A~P`2SVi#(7NuIXQkfBahfY!$ z4x_eeYkTe0>TR{Q-dbBL;41+kf$&BF#RqEDJ&q52K>%U?-?jIdM;@r{@BaCG$eeTb zd+oK?UTf{O)+WwHCqQ?Dojq5VlF`f+1L&LE-o{>LARMSVsbQYtj&Vy-P3-oLuyUAUs^n|JiE1NVd=y36V#%!nBT6ZgSN; zre2)(icr6F_Pll{6iIK>YxmjiYxnN_@3VHJrLwGKU9x`pJHTGPYA1`pURp26B_N&G z?u^~ew_pjgLl>sk?gbEh^(S4-Ihp75+WjB0nzfrlr}WzWI*(79rrC{5(~ao$TgMTu zNdw23xEJmR&-v~BD}Dv%c|IWDl5^`3$_gy`{tG*A#QuFXozJGEB7<}}cguK(mlJ+2 z8-DJ)ajO$f&-uZjar(k3K{nBJp(CP1{Cu29ot(O}#Xo7ZS{Ogs{JIKX z0%WmylvT07dHqo^YHlrvu%~ayW!9?4Uw1sbO8L@UX{H2*7}_aZ{!~7z$5~~l75qG# z4Cj$SW)oz$$Cc9WvI;SS-)@2(t5plph5H0-OUtm#LJ$Sj* za&yKuG18gZhgzdyc5Iz(IN5GVX7lI^b?l6zC8Ggb>Np?G+H_+CcAXvJ08N~Au^xBV zJ|^|=SfG2Btf4tXZ=4Zg$A5$GKmsj(jW+Qtl#YaJP7IwKh)&Gmmb9wivCCMbbn-6g zq>g7juSNjxY7!ex+@4=Nl7#qjkM$Yc_)~utKD=H1kDOA-cD#g4f#|6du@X3=>66fj zOt0G&eng6f6DPVFACPpbEjks^28>;=hU(gbSH-Z(%L*UJ4&f5q;hr;x?9+qS(~kyI zg6z?$XR0_n9-H`SF;)oKb3*v2cNER>&Jw!|%8U--7^M3yc4yWKJsS0lE)}|2GPoKY z?0MvOHSS|m^G7ewNvw>?ovNaQ1m=)fiR}Wu&Hw|_s{eolIZNW(VHW{+@UPRygK$>e zKE`_b_=QR;j#ltvbqOOj&t_d&S0IOGf6x#OPm2xzPtp*N=S=EV}Z{0lO9(XYG4u3XM&Q27@hni+Fgrf$ z{-?<2St=A0AqBhCg-c}85Z9^#(Z`odCDDZbW_g<6)COeoWTx7BLgdZZsyb^;97yMGjBoG{4mvJ705$?JSEg#n3dd z3d&J1m*c1T2L-xw@K0^^TPy3zxg;@PnC-0cHNtE+8)h36dqjE&Z%3zE1Le(!%62Q+ z)RbG~Z~+SFwe~hPYYqiNqgFKn#V`c+_S&X|a56K*&J0xm3=EdF!8~>B-Pq4M)eIsN zLmDp8d6r54{G1J*!Ff6)udeA|uEre#r)X1y{3KQ|mT}VZRDm=U{xK|uxk986pz;8NP`v2a*MJ`P$Q|sIToZF;=-)m9}>mSos*NrWK_7G zWl)#1rL}t^dB7vp$3ZkkABD&UG`#ezlTU+pt=c`tOyW?Th~sgzp18=8s&?2(AJH2I zbY}3FB{Fhf&8|7b@DXH)o-Lawp(38rS%dvzY;9&vpypLFE??yine{$0QB_vHCxX2! z_bk;}D+M|JHD3l4x`~O7O@xK#G#uc0s>fX_z@s+;x;pD+v#n1y9cmm*&e*-Q)YU@y zwexx)k}vo4@vrtrSwbno``ey!Hudyzc26HKN!6J_%+E@FBrsi)!k;W3HJN;XQD`|A zUUww)r4p3#g=@0Vfodk? zzf3KC)c^uv3PXwfIwX8^k?4)0&f(uAtN0Q-p?ge`=cNj$*7H88%J%7>dLI1j$HJDV zQ_&}y4czL8PX1h7H<=~1!xyPVC)OOiL)FugnqDu>)j~jU|FZ5Dxfh4GU6}JAfx%6a z*}5CH25RC%WZ>1@m{Ss+m>a1ri_})At3QVX+9^^^bfQzLAXRE12VQd}>Q|qM*ic9L z%c$}-*p*!Ax3-(Am=2MHr|o_}VoC>v5h;`a%I;dQpWbl4gzR3sc=SfE%Jx`mqf?92 zBH$}*Bp6zxu9+g6V*xp0LKC&1B&+!-SUax7l~oHyr~-xoWA22VdORK@VfMmy;#04)(TnT#$$~(+&U-9L72X)h3T73 zZJO!RTQRrh%+B?^a4!ABO!OGf5+1PP({~I{WPc}uZ{e_q45tt7JMVqiJeR0zgiDB? zaPYrz#);nQW5b<8n%{a=d{Ej>G!%UZ6y0y-)kV)K@pBAbzL8idL{BN;8rl|nBV8Se zbHg1IEE_tpVfT7mwcpUvane$FC6GYIF#R%>2dug*6&F2B)VY(^eKqQ~`P{vI9<-?n zOC=#Qhgq#cvK+0U-ttvl=M~I5uR=Je!>kRY^>J*_X%(uTv_NDgGS4c0N^s%gN|HHc zv6V7F#!{in9-o+!;uyjrT4y7^DtGs|KEJkT!?xiU?VAFfN{Alrf1 zS|;ar{>NyksmFQzSFBRQ748DCa*40VX?qz|iD*UoC}Fc=(`6f-$t@5&5hlI)WqN;q zfse4>Uz5+(Sj7wWHB|vu6E0cuI4@_St7)qJoIBmsJXktSCTaf5GFNF%_cBx# z@l63SItfG#2ktQ(jKt0swpTOi0^%UvZPJZLJ=U9n=-e_U)a|zhX?~xT18d3R$u07f zw}H5@pmOy15lDyk7t9yVF7%DAwYiM*jIvPwS=E=8{xH;Ma^yi>9A{wzAoT}`{NzZZ{t7zhxSBT66%i@C z){4j^HeN)Sb2xC$-BvxRAb2?UH*E7Hn*EW2ZQ|Yt>WB9^Z`&C^415Uo^UvHU8T=9a zJqosYh&>Uy&6-qD8};1+!m%I9%s4&2)f{`7CwlJB*llK48fz$RD?%-9@tmrgy(B^Q z#PV4qv+-fe5sDJ-xbsF#j*9p0V&ci+S#nUo_Vg)o2+Pp#Yw<+R$wgF;OwXlN!~l;q zDK|3di1-l)8%vIQkt|U&C^;dS|e!{w1`}HMpf)Q+Rzd}1`EG!k|yLV zkk#FiDTCp5C=e3Z`JRIG0Cq?nCL>J(i_{({0F zxwJVj##T_xYori!QuwLkP&OB9V%Lq=L+SBM?5jI9v%f)YV`*_p)>oa@zyoYu>S#Hs zBSiOU4%Je76Gn&JgHxvu*ZwROvD?d87l%l~~xH+Kl<+$!L+-ttvmobTI$Jw0IbMJI8@(s97GV4q;C^`T^9S$`?1Rh}2zeDzrCpo7qf zEtr+8@`dC1rfALEHx~G+9ig0f9P_w2D9>2uHT$*siu%zwnYuCm`WWD^q9fjNMLp1* zEx2NC)g7mr{>g`P^HWbn@f)r6;-^_z>Dxj`SckT;nK(ggZ0rGBV-{1dJw%ClU)hHt z;$(@sCtv!wrLC@JZOG}bSrsf1eF=o44A9AMh3u@bIbivA`7MbZp^4A)r6Q$N?%>qy z(L0zDrdh(t!Nkcyg*!zCjhu1Fv#m3Qkuf@^Z#~=EA?wBDlk@+$VC$jmaqRQ0F2kMWlqv(PBM$kK}r{$foCP?|0_R{t1v8ty+kOVtvxs;8Wc+LR&u z8FozvZ@j`+JvfN*S?AS3a8v=J|0Lx*o^l3?q zK0PmR@>%8Jv&3tYbzogmM91(S);-)VXuhRAP}A;eYL&d@Z!Qrw$k(zT3pF^@{XW;E z4iEe2PM?EkZwG7g#u2rAXwwG*>r@`6OJa_^OSM#K9bE`%7Z;^;c{-W|z2 zzcYDUjpwpX=w^SqSr;vM3-H0g9R!3(&*jv;Rn|5h>g6qBrV?(>^NZb_a`>&)^p;+& zVy_PlLh~osiKjM-rO@Zp_>$6-K{ZrX-sYFXMXShzIIq#94##i5tbyWRktT5hp~er-M^(RP|3CpA%!kj#TO(~z2!Se_AFW}h*n7)_v0Fs`p2^oyuD}O>H42 zHZ-IP+!OhReM;hbu~ea5Tp3TmE8IAXulTU@UIqRHD%aFS^6SvZ@E^p6rlDa*9>#FT z4tVOe1!ScaJ4Q&}g`BN)cM!IBim;6!9;w}_`k9Wnk(;B=zkwgVh;uR~hWc7@V}lee zJel)OrIo{O;EWs_kkp=1lT_-s^U~#*PGkwZjYH$dh#!p2NPGp?@2Jao;#T!7UXE!K-N} zy5HSnXd^PbRK8nE4CLsxPhnE6~Zyvo{SpChoStlfsf=j z-jq8_%1y=X(Uf}>o4aJ5@Xd0my|d^v#_Fo|U=n2VxI~EeFFR?yav&SDt+a9T>mUMLxkHhb<;l)zW?dcFF2R}h*x~Pec?~C zgZV%-w`O~AbNTiLPCc?Dni~vI_~FN4Mh|clu8*#|?s42oAw2V%*v>1>J%ADBCQ`4X zohP*>sEY&Ey9hf4LN+>uMa&eua7@KG*2T z0KoNfro3Jq_baWrZ~;~8pc-`w3YOOy^dc_OL)En|V6DPd^)yW99e&5wI>#!%W4&h) z_N%x7F+an7c-dN8`KX#JWj?h2^>|dpubnJ_KOj>3dL9L}E|(VD)wB2O79sWf<3ADs z&sycJIx#qKQBlLbqk_(b{i)6G;9FVfWADP_s4GeZQg(y7)s+1cALmJ}3}CkY-Gc*P zbo?%Y9h;0l&>4eye0-aal5r9l{b-1AM&mds*e_rm@F!X)RgE4O+7oxwN?(a&(Cn=m zofp~@ZZ42uG>%rQ&1>ycUuwFxT}#d{10IfKybI+HE&8v1d#_Q)h&C{@4xgjdSJM%! z3q-F@EW4Cao~o%?Aqg_19CR;GqdczrSH<%@4Yy~V6dc72))JlINanPJF8ihOb~RpC z9?Xx4o}VlM@MoSZ#>4CkN7$`5k&a&(-7m*G%@MD)HU!GwhxwzHv!l7A)Q>sL! zOk?*RReKWH{Q={1JqxE~(7P4yy&w@kJHT-0Sik`ev|#>_#rF`Z5`*Ic#zc#HL&Hea zgdxH4uTL}>1@u1RQE>c9gX4#pb}!)MHGaLu@m92LCk2PNo-M*D^C*lRV6PnOe2$fT zFAFI&c7o}TQjOHGUTtEGH1jAQ?~=MfnxE_6gM#T}`dJ3k*Q>{MM#1zk2NnsiHmJ+U zD6?y0I!el~{T%*IsvaE-?G{w$7(-Bm0){^a>A_|ZKSL5F~9{8 zpnsq{G_Xyz4>stY(1?;z4|=Avd^Mj3%ZQ(P@Y3K&`8g>#6ns9Pj_EK;?J8=QMu)~( z^}m9HIC`{`ZnTqrAr<2xN$;)|?bX0*YpwMXk;ZEGm9HRrQ=q(qyR;NAwh@|0W*VUx zSG)T2bV2A1>cSHQY?sQh04FyhRB}ep<#?bgR_|xC?S|DK^z?C4PamI`k6Pbh?@LL) ziWwyyV}GEHk3^v$#M}D3az}JZfjajFHd)KLuNRt+FS#YCiw%;(mn!KIn{qf)#wBD&~a)2r6NPj z1v6vsk;w}_uMV6SXK(oig@ZhSO>6U&4Dv#=k?N3eXW5`%XUTv2Wwy zP-CBqH7={?I5kMRZ;$)N^U;ib&xwZ^`xmD&-q%P?jy?O|8vE5n9B>%_&hU&O zqLaLeV9`3PPK@=0=Lfz|7pUNGCxQVS} z9u_&o&jITwsCkv_%+M_f#s%F<@603EnU@LsB6O>Cv@8Wpw}i@!Kc3b&{9mssdGa-V z5j^mP~>Vv2T_FpSk z`y;2RoqUIth!e4R{EVL`_v7UzkCQRP`@oS-CWT1c7$dM^pVUP(7lc~Q+{{&>3IqD^;>TXz;^;d;_E@8Fs1kb%zFg%OL!E}Ut&Q2j_zNmPC$Phq^~A8)b*^35XMIlJegyCX(-$(XEtYsKB!zR zwjU})6;gV=deoGD%uK;$QZ-W`EYbUo$tk$g$`XCuL~uco$nF-VhymX%fZ=@gG*?JAB8#cRoF^;}+1e z?X*5#p1DRucWMBmF1;PmDC0fAds27X^*5=f5F2@wcQjKS9+gxYbV`et9y)E<*`W?R_b^4$K7GS|(RoIas2UvVqh18y2pDlk!;gksa4r{hsex!_+GmRcCsZZ_GC zlAKH71)DQ3F*cU0>DZ2i_>V|X!Wi+6*6!9sY)etDtwGLtgEun)rp_2~ci$8SI zLA=K+l*-mVY|qd>(75qQK=^}&GlwqqbX$}T%jvhC$9iTN6XW}vfx!^9>FCt(7nSRS zy4V7S$aP9H5=_rLxn|NWe=2m$*)lo%cay;@p}MpS(F_^&Jmu}nG0%21scz1X=i!$) zA9l;tGClSC@N~a7vk$W<{=P0g>D>>5bHq5^2Vor_` zyB+k%9Ieg(rL?cn_Zr^~qtfF$7H;tWukr2q&hfpE(MJ#Cy9H_Y5aR;}<(?|D!6YxL zV#5&N9^$5|arrFI|DaqI-TzO@tYpRUVA3eYaKeWg@=3a z(MKcw1ZERhnGt}mmmrJZ`rLy)(BiZxw`Ia^Z&6i)tFZ{y#`98^J1?uYsWo&I2P^sT zO7&{haxoHjHHmLJfT{P4Y7L#~TJ~8#YlCOh2G2`;&cZR;joN-P(^PI?Ek&$xlPDvSzuf=>nZ6UzEyIc$C{u5hikPx>PPe zX%bEGOZ?9g$Bgy-+NOrWP@^QSmk0r03=~W3J9ipYE z>py$$rL2&b{>K*bMPF+!WuJf#le4^Av~Vt89*{3`;~V!Ml^_~t#r{bFt1h=CHX?Qt zw9)k}a2$US#Kj=CA6d*r2JwyK69@TrI5i>FNL6)qRl#QjLWiZA7?b?|J5c{3*NgqE z{W4kMSaxuL*q022(AP_49xD;=FE7&jmw-5H<&H8hlwc<3Qw-mW-o~*}+PY!|WJ|8S zJc}+mw%6>xbGuMHyevfWn<1N0XQ@NzN6M=E5_mAPNBcaMd!V>__jPkDy+)*p3Nh11 zOXbe5wcG_B>j$~K6S(Q=5y*q#mwo1}%i83%TEg!-g1+!a*};+Fec8C>e0F3KJnC^+ zId+Qpi9$P@kn6|m5ljNka+7X@7Lq8FKvta$GiHDGfpcZa$7*!@gHWKIDW1T>K5ZafflH1sWc zF^jjRgP{@73}_Y?BeP9TQ$qkHoY9@ta8N;qJHLFXjJs)XaIod9f)CiU(KBk5yK!Ym zE*y|X+`C)d!G5k~*A5)YpWLD_PVTN{9~R7U^qx}0L*X2!Bpyz_l3<=Z4vY8Yr6{Ps z;|rqKhOK)O)x6x|(@m5f$Cz0jnuiU?ih@(*&zbV4QvMtp-s=X(a*jw%`Ew%;vX>G~ zW*_}*fLDkQ)<_jEG6>h(pr0I635si(-?i)=*PJ|kt3lNY76_mT%B=_Z6bcuy_jwJT zLg<-W0LZF|ztXLeRI(Exg-!GN~DF5b2)z!k~l~m9C|P@zbed-f_19@Frkm}v(R}#j4G`5Su|JS zjh>aQE_Vq{4k1`$sWmhtJT`UZbDS;od8H4-DHs_{EaB^Rua%EE z7tp9sii-l_d0~$Uj;T*}-8v3KYy%0VJo7)9}^*LRX2aG z$LAD^HqmM(kyjuBUoAiE7JK%t<#HSi|HQ?1gU+c^}-hy6^lc3>k~ zbz^KVOrFSMobj>)lnJ(bgqWDika+sUYpO^Bn z*%zsONsNK%g{$djeDPSTG@EpmXZOq5L6BQ^)MN)-%XW|OMnZp~t@0y76w-6UPz8ch)PQr){$SFoYs%KlXcb>Zn0L27E^3fzPmO1-UF zg$ylr2k(n6do0|M6LL1RyW{!rTp>q8d%or3AW*&W1>n^E{osAw--h;VO?ZqPy702c zq}K~C&95R?_u;Ja=2vF{wVdDpY#Gb(cl26{F;oVu>tAve)jkFkAF^C>FBYbHQxmn; z%dX>U6RT<;$^u#HTk${0Qg^lI>;+J~?O=rZJm-u~63aVn-0 z1hg6!t)NPaDk{weCgTt0d;5>yy}}nNp>z3!%j&7~4T1cs5{(fy0=SxfFW3MQxEe*s z)PU_(EIReNR>22@86J%iR_$jZNP?q+cY+RrC4wRbEly)X#0Euhy^=Uj1NDp-70ZOC zAc>@}yDW*&Wq$2H(Ff(9zyAdXVzh{K-?u?%ZI%PM}$LipsLIlSzOux z5X2;ah(c%d3FS2es;YA1&W0@=1rxNEJD`I~nb(NzqzQGJdVwBAD zXa+%c*4bicm{^%8erkaB(w0pXEzfjDaM!yIEYEZI?em-Mv~+^ruV44q zyh4x#S?$pu=PVO~<$6{wTwscR!q-_f%Yy@FVU>-~j?9NlM(nrAd7|PvaXAgI%H>dq z$b~DpnPEjv{I*$^R10Jfu>v{XUHmY%oc}F}I&49JeQtFgSK?{7SedvFrVUH^1UXn4M8#{7I_n)kfC zPISH4sNdOO4pXE13Acl8SnIikZ%;JWET^sI)E}R^8|HBGl_oe|+IuGv^=2h;e^jbXYCIW&k*NAdQ`hhPToLfA)8nXY8Fw}P0A#p?6E$=;|{JR-2f zT`>A^#+1V#mNOe3<|-amaEBox&U#DW!vLVLpNIq>e*a9l z-Oxv8eJ$@ZL;HwJIef(Hu<^9%hHu~EZSFx;ZZKT+X$~!R)!~+*Y)E0Wz{dyE$SzVi-$`U7v5*D0wU6i z`zoGMFQWm%*r2%9`U?6RdzkFWkD(*f6{hW5Qf;fsA}~RmQYhfr^r^RIkJmL}kLXJ` z^#TIe=5ALD^pk4fC!j5aLc`SQx{3m-R|WbJ;*b>^Pep7fdUMQHH_Q>m2(SPyd9Pt$ zwaD(TUd_l)TX4!c4h}kZi*-Dj@I$Z(kHB8|xXnIvA|wxbgvC zI#BPkQl~3pD;V9WJI@v{MbitAs0i~AErc32=@vd zZf9}n65fF>TJKt+Q$!0%6o1lNvWG?xZ#G|(r(ixS05^NI6~#_bdBm_F5K1SdqvIyU z-R^}F3oWzKWOdZcJ%xFOnfiwO-s=C+wPm>v2D6QM_qKF8#*@U z0)Nz<^P0tiKH&50MmXyM0hEKGzf`fJwQ z_+q@bbhaVD!tj@O_%W>F*7olFRgmcivZO%{an z;43)wX#s`u6NrqvXOSjlrRCtbtfuc1?- zDPrG;Z?=_1%uGHrbAtA&hosl@f0_uM`e1FEUKHZPULv|@pZXdvPxh#Txu(HEsRq>s zmVuy<5%n|RL#Ski^*9q@gJ?(mLO`@*%Xfh&M3EGTu9osPh%S|90nr9W5=3wEq{AFH zf{$pK8SX7KAk&LFHiNfV9!eqK#dSGs14y$>z*? z=HEf1FcFO=!({Ed6lb2|(aaWBMbNswz6HaNBm<~WzD$labyxqAEpsUT8^O2rhl#9S z0;xk%78HK=w9wns1Y;W#JYC0B3Sh8+1!7?@x{xS-++f6D^$;BgZ}4-^(L{Ay4htFq z){bSDwiOER-8pw0)fvvx<=q}HR-0S2Htc92!4rJikP!Q}!bjP=&{bD%P89oSM>_gS zyilD=QzmGTKB3T#W_JDbvQR8C^AU7HM|c-zt2{C%?I>PiuVg{%K4CCK$l-~La`Qb4 z#cT+o%FQt!Wos{&P``!!)jCG-+)*r&_{+d;bu#v!Ky)=;J+zW1tqfauxOzmg3g3mX z#>L;ek=H1Bb-IzYmh)$S6OZfVo;@2!t?`a(_V#V@Mx594kxqYHrlwD3Uk3<%mP%Wy zjo01II+AmO$1NQ!pDt4xczH`XpUYZzoMGRW6>}WriTZNX9T4HA4$1h+Bl=ju_R9;f z{s;@pxcV`-(Fr?!d%pD7G~YPU@A~U@Gf_=Dd}gkW!O@RCK)txH>MY$HhgF4p*xHyY zeI-<)_%|Sd^mG%`)s)?Rx>$=kWU$Di4u7NwA}xGlPv2~sR^hR>i+8#76z8r0`=S_S zp=tE|Et|j+V*mdvz!aQyGsT47Owq$0%HGqp?A?%!?^)K}wd}F*-QTn9kgjD%xH^{= z8ftCg6v~WI?qU&UP>(X|>7$;5RkjJ5#`y{&hDz=vP#&d#$?jPqT!2i|0#l|UX*1Qe z%nd!1xt3WIFdnLmjxVjdm2T(s?E^un@ znzt31^V{HbHwC)F>0VRjkYm22hcXwMK1rFb{U6=6Pvz;hL8|MhM(!`#IeX&P3k)!{(awJh@4bt&c3IL zyGZmer9~VbaT>V`(hLhSLjJui7v7p3*zQ)I12WC|G^^^}& zmwSrXI*=-MVbg~1mKtgo_V}y^bYZMh$3S5d#rX%a*|~8S!SM_?L!8&N;NTWB4kX85 zI?e%x9KOU(pEdjh=_8R5hX1A2?qfSnK%u8Z#+zZ+CGwxCQ2hWn(odm#TEm~=wsod# zDj9UKYx)SCer0J1?xZPSFA)WFePxDqIBKWOIsn=<>o7Psi%VF1*-8S5vVJ;b!rPX~ zXMr5o7~Qc~jQ)u*aT)3sxGJI{FLhIe)}b{s1?yHhh%!q=1Z$0suhABW6fWgk`VJ#? zVnA?YV{%F&zqMz^#1Ig_=-b4MK;lyvKEeEkM^dUKU60u8pC=i^qhwyCw9OaF9aAH- zmcp9h-*&o)hoD@A`X%qgrSPHYhxa*L0vJ=FFMLHA<*7V;MSfP;KO|ua?xLW~tEm~- zu?+Gt@?cR)kR77iN9yzC7t0X&3Hyd{z%8#wm>)z`oe8v^n7^QYpvJrYmCXw#$fNM~ zI=+SGTQXwCFy|#)j&%KVLjOMZ%{<`}c_OYHCqJAOj{eM7L)1DGBzA=Ok&U+|4s20{ zk7r}e&Kf%ZqHhwe!YKp!YPGqR#R_-1{x)S`=u5Kf-bf}WV7saz6LsdtpI$Z%s6;3K zN$mubLIU&DK{2!^6bS34LPn(-4%f1WF{MFiP@yhALLf6@*Ze61vr6Q#UA@ij;fov@ zN560kA4HIlwIMb%ED3Pf2-M=0@(;46=ZQJITif-g<`AC|SK;J=)`WETC!6l?*@y{5 z2A^j+N53pU+AG`Q>kVT|WQs~Sh4;$e{&|se@*|USwccH>mhWh&xgt2Sq2|V5|Av~m zuBHmsL~EYdkH-6tbhS>(g;a8DOKVa`* zY}j@d%*j1vb3C6f1(Cd`x$uag;ilr)E<@Y`_g9OUB}Wh%`n-e&2>6N@T^0%PUeY*}Iak!d(K_Vw|j(DnuhMMuAJCfCTw4Eg7hS=pAUeHEq9A&S- zrtYKxalajXCt93sXSpn$gzgF4gtSd(QiwJ^k!iqfseL=aGa{OSPG zs20ekV=<(|$EHM(uKS8`C@h4%V5h7spAsV?WjRAF4lxYLi(XS=aCBDn@uA*w{5Xr) zjeBrM#ALApWDR$KtltE!15?P_4-Y3}@_LD>6s=FJmo*}rD#?|H|u092>Zj6_9#?}5dt~%X|wx*G_5v9f|!Pe*z z69rv2YjoWl)Mx8@7Gy0A5CAs4#1J&TuHqquug_007_)GQ?4+(HAp}feFQI|Mve+G7A$FC>d=L$=eOy#`DViT-~SMbT~}t+zOk#f zXP|@|4$qK%xVFp}p20?J9`fyn63q5t6zaaw05m)*>QRD)G#(n zC#}jMA>3Qv|J3DMiR8Dkmt}F{h0lgm)OUHZ9xGdKIr^(?B+2ic>ToT)Cbw^wwp`18 zYJWo^nd|~R%?W*F6BjyU`E$eXd=p+x{7zr)iY{&SOSaXS_fOv16hqsDAGzXUO!Zq9 zNac<+#&}CB;LNMWt_V)@0^ABMlzFZ7X$*f=z5a^Em}3&o=h4uS>(pJnCk^Cxc4Rsd z;Pf*7v#Lh;thXbRhRCwJ+7=pS2%9A6jG^uK=o@jt{@uQ9-k$7R$^T2=Qs)gwGt+;o z880o+gr83dG$I>){F&UA&mvJYG&-fH8hF!=jPf`Y9 z1v#bsb*r7;C9i&bnGziTWYdDMteSQ0bRRW`bcZ|xnnW8+oslV>e%k0smvOTGeh6Jg z@~kK+@1)+N&Ip^*3Rvd;IAx}Ex6bz-(5>Lks)r941Oh$_gM}iA7Z@!(mvZJ_%MX`n za3qRP`$W_M&c`U5ghJ)!MeQjlyhujj(l3$rzCJ#)y*ZRIlAdYrU1?7wJX-n?IT0}r zh$H6RLLG_XwZspmrSXSq>HJhnB9FEugkEsb(mQ^RFIIDE@(K?yWUulQdl^N5ou*GuvrNS3m%@nT=~;aq9bS4nzPX1XZCDQ~A=fX`6uDm%SJ%JVGgPnz;)OM0!H z{wGNnL7DXXP5K#8DA$(@wux(uL6^{VJ0_i=P;_$h!YOm2{C- zNx#gbUoPn@Gt015|ndJv`FW+RR z&zJTOGwIS7(Ca4xpQ|#{ao&^q?ex2blU{45Yxr`kBI%j%Jvy`e;7;Xr{Z2dGFYSxW zK>0DI{nMmmQW~LwBJ^c@v>6jWz{h8&P?DUJI{$VD)*3^HN z)PI$ouKR~jFXgLEy6#`CojzX54>##2nDi5+{Lyy0lv#SDNk7`8A0z3`%=8i6)8FS4 zdKFFg(=l^Rtd|n)Sh`o=eCQ0mf;_-HDslHd3;suZX|fZH(zC zeDDGOa>GT)|&!O>}pqoeTz*q z&2f%d1^@JG8vlB|E7jUu6{D%Lm2BJ+CEK?Lr$P9Y4Bs(5meid+;1{k>1WWeUY&5|$ zx#anm97^bQ^;J*n>Z@++>ZtV4SXcj|N9m#Ib(L)ot;@R7)h98(#GnPvk9eY;I6s=}Q7_1$k!cLW6&hQp8(UoZFiS*x`!$v} z@F?dIs6gGaqKuusyn-UeJ3wA4cwR_W9??q^3*O6#*OvGxCVJdX=%$1S+<-ZpcM-wFAdbFOh^0`ec9z!nh{#!28Q)7qNR!yk zW$IC_&hF}fF5ytj-lJ`(P;W2?Pn}6`U|OVS5AsZ>PQWorjZ1e5aheIp)1Bgahd3A6 ze_^9!%6uoIUh6g4-=$iczpOLWeu|@%dS8M(8UP^c)>?V zZi8-jacP508RDiOI5-Qo%(Gq<6c@zdSQ1na!n7qm+TeKyK$B?j8xYXvWgxkImI_r{ zZ}&cP%K*c#W*lz8@Fa}^PH|%ICb{ozJOi=q{HVNiZw-mqtl^#_5o*vIMjz8NZ79j0 z4Np;@fysZH1|H&Psc`h__gb5$*9&R|aOgT(G3SZ;Xp%t}E}citMDfLMYVu}8+@7|M zL(-|tzFe2;%f+TImq=ekI#9)3`?3=#cJ9kiGXZm14Vuu%Y`vN&zVodVvh>t3%|F?w zDGTZdY0?%b3QR8w`BC@nPAw?2PN(Z>loe&zw-4V)w)3s{$SHG*P`xoO3HvoXF}dyh zEEQ=*9j%+wus>eIeiy?4XEziKL1vODNd6R^m4HW(El}`0rt1(p z9jD6X94x(N+0~ev)RwcH(r9kFQ3-K}y`&zKw3=pOlGK4+ zQ#oFfCDBbLV5f~I>gB-0yi{Eq6sz`6@esL@}KAOe4xfwL)wN^@? z0Zp3BLvi(vBo?GOD1#3fIxg7{X4A-0TkyM78jKcS#*Z4U8`2YR@NAF9E7=bQYsHr$ z>NeB6jK3_rKc(8!tsI&kz@3Cp9@`CE%V|mT14B*A!}u|W_fP5_P40OLnl(R<=82)J z4db>kqp!vblh5kZN!4EA-pAeesq`n7+vAIs3k+jC&1S(ft^(txe!IM=j!Hb3gJbaM!`kf}17lgqmd0W^!& z$Q0CalGI{rLq?cdN~D&->001{sO8=5*<7+G|Ix^WTMjJ$yQ~+KQRQJOkt?KkB-4<^6Q!r&GDm$W9~9 z;BgYCQIV}-it|iGAM>L|QIR$}4;~9-KIao?{jSOI9zW43!&Kr^CKFQSd`_q#@uh*M zv7h8_xw6=ad{ITSpO09aOkl$k(n!u0xO}G3hSWs#Y zRVFpC@0nKqC9TYqQX0|p`DL93#_`+qyczt%-?s1~>7%V5t>u}~pT&~iLo zn$vnL-9Bufuow&yVv&w@$gph1tBi+^y)VOBtZ!ZwM>)OAY-^f zXO%Gok@Ao_M$>)415^5x=6IDTPvEWXW3WVV9~q$Scg{zT1`TEL!u~q3jf(JZF0Cph zJ_53+*zx`J7h={Y;-Ws~GV8L)tV?GAKI^6_Vo@I;r=+v!}%m(++);hBL2_)&lV zSmsOj7T&&%DaIr0mRI3xwW(x9Vq3W5mQZ2Tx8SMl1%;pg9_>)!5)m&KIRC{95sm1E zEsUgic#K_6;zpWb4+C3s%`>_fj4%=He!WqKI*R`*xOl_)fMluJ9@?(&aIx*xl6Y33 zL1K)@ro(#O!@&}Q_NAqzHN$^>zatAbwwe2x!+Ux5S-CO@E_$14`3APc3U{|;2gM2E zCY@NYS6y`KH~3B^g1HH=7zH{nGYK7Hft4t}VYA>^p)Z+x%6fr|vr68>r5#y`74e^& zo*YL{kaDOZZKeWF1q2s8oVPJc-qL(IgBN+&%BDM`*3_(FB6ts27~8M$?AhdT75WBB zL^h%hjat3S^TAHx_q4rPY&( zA9s?v-*-59hskzbq8nAWyS3fLTPb~yCPFd66@h~-lg%EIj6LqQ zJ`c}Wn-!kXW*ZjXhWv34hI%jG3(RLapW!DEJ+;*%zJ)CHGJnm%;7>7=ll6PG{HYKv zUyisQwX46L{7vGXq}?8NVh6Jc#liP-`Yka7k7w3P#Slu!;=j zDeVhCR251OrS1nfOk~CJ)QeAIGLl|Ff-i224||DWUTQs=>DMNS!DE?!(&;~;w*C3) zk@iGl*Zi3SU!qn(9}x>lM`R)kPBsP1GW(0ATCXKA*ohTO5Fhx9l*K7wYEC-Y?9TuA z9%hV_`-O?(qc(^Lvzyou!O|bP8Xu4Zpl~${&DFR_-ewXjes=ClStUF>-m0#01*&i) zdG}9X-uSi92iG~{SMozN#LGS0v=*)*gd=|s2G88xD?2!LcdvZWdiPBfpF>mN7c^)c zr0EyGGbiBI%A{YL&ryKqio+N2lr8GD&{IX!6bXSKR3!XacIXdc)hr#ysu|-^B5zuA zU=D9ZC5LshP4l&EC#W>U=%Onz#N6$h`8LO{B{ax}6Mm1#B;rW-UN4{Hh!(DDE;CN< z9oaYaZkSTl5R1^ps)<$etqqK6kbt%iVF8y4bmIj}1-x{7YE z)9^5})@Nsldm4is?)qmNw@a6W_vVCpQM)^SVtB73bR3WQ@lo=@*E+F`c@;|ZzxU8X z3K3>Mw^ys1z*qch2^aDr%g&+!SXgSwO0n_D4gg+Z+=A-2SRY${)o9#$QAM zZreQzv(?G#jo9$2e@08IH~l*;y(+~ww?S^@Mkh@Rv9H_iV2lwmC3rBM}$^t_hp{(cC`q{D$f*PUi@xPiXTm{pG9i zFb?@~=khyN*OhaW)lA_)d2_(gvhw2#H^+-seiH1x^1!whpobx3ONpOyajNRyy{eTR zb1~bF{V!b*j$yH^?C?9TC8Cn?5w2m+Yg~`msY_GnQh@NAg+2A^@2Ot7E?liU<}w|N z51i$?A$yi9*fPttjRl}PRwNz!u;-4&t`e+vaA8(UfQ!)z8@LZl->X3g9bkZ^`O77j zt>^~d^0@-v&45*dcpbwKZs0gyH1}L_^rGVf(9A&A`fvzI01IEu#?a@!@=d-?`%IMR zcSh(5F@vugwj(fVyMI&MU$cezz+H?{Z3G0<0yWJ+SC?x1CJ;ShvdmAYPkMfqGe28GaVo8k6Sb#P$4rfSl9tEzB_}C4s`B)xxW^rM z*&}l`%GQ@0-6yf(ymg+`|8QKcrXW?N$HpY<;kg)|9-7Q@&Ma3bW0sdQ%UePp9%^{8 zF!YDZkj@2L0>nABXD44~hY%yo_UEh%2^*p(oHZ2yk!cm&Nn-^bm!U72GTAptXR38N zSAu{xx+clNJWBu)GIhZl&_#@Lm?u5=G#Id6Pp?I(%q&O8njVqSPwbe<{_@R}qXlq+ z7*jFd8-0#VE5EvMHPiidFq`R4Vtt8RFCf!>_@S0#kGO5H+D7K@2A7U`748X{t>#Yn zJSAEHGPh`C?hbW-&5F=@V6s~#JrkK3nT^a}hjP-R`@YC*&t?knhT{Cc0v>z}?*thI z_L|~=he4=L3id?;d%?vXU=Pl$NCWxk?}L*odR$fiF97awMKOvHt13B)?^scgAaHQS zTByc~20Gt{O6nE<=?s~c0#d}Ml0 zPws`?IThBonR44U1OB}Dn)@6Kwi&YZU;JOAJLRpK$)*SJ;!T2=6bvaSKYU z-iQIEfIvs3MppFA#LBmz0jJPm8&HL--9ob_D0(qA(SQVAK zIj`cN3UOkf`VpS%#(hX^&0^x#wQRi1GBI49yBlv;>~oL>F88AYxX4{KJ3C;FaYP;5 zVQ}BdK+TvUd0faXhoJ$SXRB*C#^%m(U&Q(LV$7eO2VIF~wcB{CU>;ZRs~)qCl;9+_ z?;=YI{yByT(lAnLdONXVxX+f20Rf_!25lm713h z(OmO6vFg;*LwoeeqCa|ejw)lu?6Z?u9ode6HH9#P54agR@Yrd*MLB`q~5-6lKrerG$C<2x%K?B$|j4Z7jtaT(Uw= zR*Vi89@@^AV^@N{e%->(_1Cuk7(zEmj1tO?ZxGlE6@oH`?o_3OSH z>&s|RM)JL}zLJYS4Ay`0tiaM>{p||{>#4}qw6rIz|Kqn|{muhYrC|MUsj?f^kECv} zzUdQJ;}xB7e%8e5Q|E?uFB9!-z(Lg0_3C8i%|?5-z7Uo3(KfY>6B#C|#*Z0THl5^Z zwDW-dtjLWA4Zh#S!XZPUdUIK4eD6(%V)$fr!}m5K;AG%CI_|Vi4rR0^?0J>VvAgJ- zfUDJ4?ZQA5)koBgwy!D+DKF8Yu|VA>sAA5Q=J~>BW(9u$ECS_w#M>HjjEb;fTm=!Q z_3HLT&(2npDIu3W=AOa5<08j$DLD$D3j&mgUXZOWpxcZG{Z%WCaeL=KSfb;}X`d3h zMP2Tb!=!pm%r9S|9-;^ut}9DRgwsea>vvw_s|rh6hW@*gt`%oi%wN+Z7RFZ;pfco> zh>>Iund^?fN{x6C71|@dRdSu%b-=T#N6%)!GP8QX7g4|-@J-L82YgX-z+ZC$#DGuL z1Fk+P^bF&zBV&5JUClzpSVlboNaW&QAy_F=_Q{Jf=DQ`SJ3Z4{Fl&5Mli=hRa_fTi zww^QDjI?FXI(6Ui8k;cQb2Njhcaxxh;S@A8t^0&=qWIxCzVUEs|CcJWl zj#zVO+qe?W81m_aD1Vdv2vnagktmYgs4dgoK*iwTO2FCl1D(vNvkiF&uqkf-e*MTa z3t1a$Jy*je`D^Z5g#Gyv+g6Pu*hBQ@z2;f{?s`$)V_*M#e`2lu{N(RRG52Ok(i8uX z3e)e$$2-3->XP0^r+?o&B-8aC1RR;|9CNI^!v!u4OT3WuZ;So@H~aTDnZL1e8OhqU zu@4{ChJPAnZVaO=T_@Lwk`2t zYBL%s_9zYyh<~UdHlrq;>-lpPdBw^%2qwbimt z*Tm{+LiZ)pZ4MP^2eA>if0d9!oIHtoVMWBESBS@vu%5eIE!k*KLjwTE&wd1oqnhfa za;fVctFQWsa+wVss{VPV{z=phL&h=?6i|2J>Lj)3yHPT`YtFKJ=x0S{Kj%w7cO|l@ z;kxhHk6jocF;17*UiDV}?DkLWQrgfpRQo;0b9`2BIjQ(Chs^ic_sn+=Y#&QI8E@v0 z^?jJGFX{W2{O#rwsAHl8BA)yeshE@Bp7+rCj2xzI<468IJB#Rj$pea{{3I4pfb+Az z$~Z=eFd!%1dd<5i!P)>FQ+J)Rz+d=+mBH^b@~h8Kh;h;h^@eAlUVl(8?<#|OP8HFu z04>Ugba^?RrZMfn3}(enN7|uH(zES$_F}HY8tAR3JvKO%{*L)>+D@XA=z=JFxLx)I zok!#~eFi$!xVgLa&dVhjWs!5SHmOzkqZc9RJw&WTAGb7PM{%X>2en0!dme`!)ya{C zg0a!b6Xb1HWa0C&&dlR-L^Ay0OLV^Q!FZ=e$h-l_U);$^F3qiv^=#eJ^t-H z+T+Jckvi=0tKIuyeF+l4(CEZexqoMf`VAi$PrdQ!N3SWAYN(Du2_C9m0!ZlP51-W8 zME;kfYkS(@!?MvQ?3J*QT&7SUtYDSowCbQs{@JQL{W47*nSPm}`lny6R=MdHpW5@6 zR0Z|wqx8!>^=9fN(g0Z1y7YT9Gpt^acQcpFUtNg?zCGG9CP430pChj_Po7%+xjtV4;Sm{wyl2-&1@1IID2oWu`0hb*T;X?_X*RT_MXI(gC@Q)MxDx zi>yaz%wAlnpY_zJ}E=%if{h_At{hv9q zur-M~$4Lj_3i^g`F3JiyRl!!WMIJKc#Pt$TUH7UKO&y=FwF-A%s%InQsBB<6K+s zHvO^+-!Lh`zDBbs5m@juSinEFJs$l1!YK(sS2>GCiipf)mll>Dn0JI`IoyiuaYL1w4TzJzY4jY0AZ@uUL%}_Jq|U9T3(s z9@A=2VOR9tZ519b`9bXhHJb9_!ozt_q%y(zSN{nvfZqtcu;`^WIoYoqyNp)6m*bufnH*b{5n$F&(PYJ_9s zHOuc|XQ^p3_HIdjEA=aOAn+ip6N0G6iso0p`6dywoNC7(0iNOQywUkm?-J=k=eE~e z>9ubipWl+oB8@vuTpZmo>+8Tf38AkN#t0q0=Etof@|fHlIHR+=dU7wf0oX@ z=TrSd^dHC8+VVE3(VmAjdF7uo1JZ5B`aQ8Ot(CWV=I_0ZwA-9m7X^>t?OOZI5$wm? zkL3;1rQDD<=_`z$eb8zp>s&{45^P?|kVvo7B>xH|6Z8@=I-?U4ac*U~(-ED>E~2T5 z7&a~w7L?(ZBV2-PI@i#7;YwAc^n&4a5D1;mnMwJ1s56)5;KZjcn^79 z&n_p>h9}%{vFom>`QY8sA2LY8`yt?y>1J* zOeku5-HO=7>5NvDa5ywCptD@0_->b&)ug{AmdMV?Gg5LoIbH1a*n=hMSL6f^4YS|9 zhU-RGW!LVpo4R(^?kl#x*bZ#-K>+{7 z1eMTd=EAF_2?9^EpCYg3^USHw9Isz7TK5+si~AKc8z`qnw?$G{r$pOA_a>nGi^a60 za9*-<=Brh=sC8jcM>tDbpPLnOVCZ^+{q9Z3fXsgH>e^NNVSZ#>!o#QfK4eg>s-NL@ zJOkk_HH?N&8zK?V-MGNlKf@SH=U3Y6p-@Q;phxJk8^nScm#pp7t`PZ+4AZy(1R;1; zj`8Y$#iZ2XO1=f>Sb>H4pUD}am5uRDbV<4ymYFY)@noQLJ~i3DPkOLH zCRo0uJQ3Bmlsj%dZRHy~ff3>Gekl#Ztz8jo7(_{-VRDHmAz-UC%u%aP!#5tQ;bp2} z@JZ;o+4w;+RH$BmpT;jgR^#KW#>L`JH4ZFJru)C-3*XWBhO4cH1vU!yvQQK-01y*k zya>BfhlF0xJ$xr$V8-y!N|+}5gRSwIItM#(X1&+HoNgoHlOzay}+uRVM}LfRdaK&vXf5h4|HJvt#AQwno%C(qh3Df zd_{g3ST#C*$z)^Y?_&65oX2i2mgjMuTIKcE%}0MZcXaP}7W95c4d+-mfimWq-V5dk62>rojFhd)-f1yO0fgox~U2U%BB^rc z*br=JXdsBuuVnveE!kYOS@xn4DX7@Noi>RwG=iiT^DEcF=;@^DHg5tl5thVZyVD zeN9oO&=tFk?NAfl+o|ruHJq9CD{@0WOQ3xP3`P+twclG5mZU)~p+>2du)FUtiQFU_ z5o9WPrx&}quK=|b1qv@=iRp7ySkAlKxJFN$MFy8{zdvm~EhZ3YMHYqfsZ+g4{&up9 zSh#RJr(cB&_PV!eCx#F9I;=xkya1pdad|KN3{O0C4&wP4$43T60FefKI%I{)I<(gp zfdf>j*dR=al;z9P6&YyBJF*-%!e3e)xR)lr582@}8@xgs%nu24tW{nZClSVPyA^S$ ztD=4RzPoI+??iCnR6cUFSuJE%rSZ#XT-H0IkZ-89tf)$?`%bIzbgOaNeT5tb@(rOS zL*kTPA&o`p-2vL)x1q+IlYtF~C&@8gKy{UTU%nO2qbRT{bCmt=huDiLtguR z8(G`-x~J3%KgnK)XpLn&-d;CMUBF=cMXh-*E{=Y1jEf_BhtMn-A!7 zGXn?d{S>a7{`AK`o^~L-^N__QhEgwW*_#5ND5nRzvm<;hP&Ho&&rz4TL2seS9b25k z-+_0GbNJHuA@=8SwjnLr7KEAb7OG`wGR$fj%6i@amYd;{WO~)cqHu@faBOb@?h53_ z&4}qu)GVYE7HUS4zCUW-#c@bNuC(_`%s(!6}@m;;%{7i1t&2BZLG4dl09<868D_BSjy z#~|&8$I5K?Bb0|JqZ3`bk47jt85~U0TZA9%fag(!g_#C3p*QV@izyF}fXv9QY`^;{ znae&qvxIN9I&H5D^ElRHd)+(SxkX%L5Kj} zdoaF@=EY61%{UB%WR+I)hp}3BQ`MS%koWt~#7`+xGm&r}P@9#xgFK=r4D72w-sF&C zYbpF5h@sCN&w*Q?DH2Q00>bgX8T5AJeCO&k9z}}I5KJi`E1jkZ7 z%A*Y$1CV`35Ijz0diOuH2-g0m{NEK%fA6>7e{FvE{s*LFZC25KpZ$-I9Bcnmn_u`E z7LI;g;NaBo6A9xniNRpBri8DV0uwPD+9q6s6|8d`L{-?@SA zN>bM8xHL6u$r(hI4-aE$u*kYmJcFVb#3z$8Gt~<#7Nth`Q}ldFwNLhrm^s6UM-kDX z8$wnb^X2QcWw5XFk?!L&uTGremTEDW`*#|KVtDnj8d}jMw=~^UU0toDIWd_C=lN>3 zyHHk6wGCg4q84zmxwzUDw7Dg1aC8Wnoa%S2N_Pj!l9x8S^%36Cj2>@rY-A*aR3$5Q z1e4qW10EFnfL2oO?YRAKkt_!3DcVgvckon^V<1))iz=kN=uB1=V zEz9Z4uR-S3*^iTX3S7SMYuIl0N%37Ho=va{^Oqs2d}sz@$C6jjlfWy!Afj!rzT2b! zw-`whjn5NwG1f1|L^bHmyBI_IC)?auUVj0Qd;z_vxpf5beg-Oy}We;8jqT1_T z;V&~kBbQ{n>*aL7Hs?A4v$i%E(`p-u)!ceWypyYgt{kbDQ|+*|ax+41vbY#27nxj) zmJ4z?y2f&0<9EEPuFIDv+h(Cjlu$PrQ`RF#9xeC9{hkEY$`WbTJ2|PV5c^&S*vgHtw^{%u|=CE4sxUi%UOCM#*5mxP1=d9Sce@4m!(6sCP=;fvulC9 zoyPU6A$>)AwU9U&*S}-0dkzS;+qP~6I%4{0zHNN`bJYz?_-}7d5^*ZlGKAT8i&+#e z{!Fbz1(p)9Y`csz7r~KeV^N#3#EqFv3=>)+EFgvQ(0uUg{|D9EL0+=xX6QlW1aFyr z$piq#x+BM>o>fpCOm+uS3&22obaVZ#)$ePeIh}E#ZaTdq>4FV**`xb2&Dr$m^Mx0( z%aMQt>daP?@KxTj3=*izoHy=9f+{k2GPgTyo4w4Sv+Ot@KGN%U5@hebn-wm|G0O4W zDZJ?@e+^DtE{ooJfok@;dZY1RMR)+r&7&Np`Tuw-XsT}@PAPOu{(cjR}OI$wFWwRz*V;XnX}26 z3Tg?61VW%nk3sO8RWN+uY@~XL74$^F`F}BzTw{$UM4lgnp-s#zJ=StMwdc) z$Waj<$8XaO(y8HG$>?N4!l~Q@_TOx;+reF6f0_y8!2Wc5og{t{j_K6Ep)`B_2DwAZ z49rsP^-pmZntr)T)7&IXt;%Wc2;7-DY&9$WLGzf#g&flurvq=CQUMpI$WVj1m``1 z+6sPx=L*N`C&4i|8gXAnU{>;~@z;wbcmw1zbYmY|aO2^h$0S_nlXhZXb&0qt2`xDh zR~`&zH~G@BR|&MRo#ExyZy&xgA&YjUL~qcVj43_xDPl*sN(gv*z#Z5MoA7i#6YC1i z%5aDN-IS6|FU9&C3q#h~RLxHo?V*xLH`-k7Dx2!?cWE{pty{5I_-Tq}>txGK_KW4e zpZQBNei#x~LMa6^-Fh_qTM8jZZv6}Qnl?EBI9|AbBA{DDPy(+$vjg&kK{af|7OkjV zoceIpinbcJy)2xsz5Z!xTc4*pGx$6iC3sb}z^h&u8Q$hq8i>Drrh40dL*F-X66a-c zlGXx8GVDKF0)3^Oax`?e-UDk}N~j$%ZXn8C;J2b!JK}-hR!SUJ{hDI@$N|ja3h^Pw z!{0IAaqac&lAF}jgSjQUXy?#_@J0O#|0adsV}3&49%Fv8m>*MGI(@;vK0mGcG3G~( zeEsL=v7^k-tF8Z=`MFQMea!jE>N7u^8~<v6&A`kl3cn9E{YhC$j=;Cc{&Pjeeg%D&Jyrra+Ux#=7>TV@OlNLbDD9w~ zG$q!mjgnmvc+)!7s8|2oTlKA{2xH){H2sKMatu#$hq4Hnyu%&zOoJ784SQWNF?1I|kEo*_EjNAV#&4yN%s&>f|YmP+PpB>CP(`tKu zyzQZbwY@Q+ZE5pav(1`gwmBr;<|ZYz?bqMFZDv_*z99R}F`IoRC~y`q)c+IB7Bg4J zZ1$RXvySg>Hbr{-yqZ0Q0H>JK^_Dw$Zv`JxK&_W6BQ@Ua5#j&zka{lG>FCVja|biN zyMIICE@w6*SH^Pxl-nQg9Z#S2fy@U|_*NgxV6Dh0&!gAN zkFhBJu^*|Ag``xuaVYdmm^#>i-p{C2bCl-gG)eRFV`Rmf&o`TwQgaRdD@{LbHZ4EK zU!IGBX}4M;Au0XWo0cOiy_O&2ig?ran@vk;|E3*LQmSlB0TL-o#e}}mScu+>E0c@kI`mJiWcpVH|4d+4c_{X?REdAh6L6%zCvEM z6aDF8$ws2KK)K78&fkmusd7)-Vw6~U~0$Ct0ZC|{P% zy&7(v3*QJe#G;kOk@Mu%=0KO{44&bti~2`UfbZE2bpoErbSRQzT&`|La${t}f4KD{ z&eGdDeL%vy+v4(J@P*N@F&Bey_;)5Q25bq$PHljZ+Ih8fC?F09Jh$eU*)24Ezl;+( zG{)xG$}`LKE59&9XUQ17Ag?;Td<5o--0RHh6 z4gUP~d;quUm6O_BlanH&jSqizB&wplncl8E$!~K_f=bO(HuELT?L=3k7b2G{b?I#l zt_$?(9J_u|N+AF&1u06YQ_O~@)=Qx>Y?0J>tCc&Cz0L_PARD!>b(Q*t*w=c?TJ~lR zYl)4+hvl`2K!^QBZ@EiKY0Qg-)uM*&w^M3-r)4n-6{2pKP{~P=+ zB4CglM*dgui~D6&;hee4H<8p>3Ppw!)I#5`^jyZ;Gg*9S8ImGt#+zq=aqCi-1@=Nh z5S!2e6?GJoU$KSl_469MJ;4;CI-$M0;_aEHiT&CmQCHO_k{??Wg)0wdJFCn>eklt% z5dOz#Z@%T|P@`25rI>Y;@)>w=Sgj+_k#)4ixD1>F4=`$1>&R?={1`zxd)>Q&WxdNN zj9}JWL^%yEoVKh*RH%QQxQI?7R)r8W7Ez8`L=E$Yx4A|nMMfFFS=|Q*8s_J;xrWD< zP)?sElus58V8mh_u!`~rUqxlzCKfmtk$vs_d|63#=cpU+q?V5|hwT}#k`#T!)m^`4 zG^_#=uaCr8BIMY63>dzMO-4T$5*Hy6hURqquVA>3053;@;SW~*Q!s>z{Sz?!MiD@J z-TxYfvbX4I8)oBNPdGm;^=|@3dPihf$njF3d(2W>pe0|0b20ZB>fYO1cQt#AS@%(^ z?%sIafqFR==dcn-S2tlWM6{sR@9|zfe4MlNZ2!r!$EC8Fo`pt+mAXKbWjBkmtR^wU zx0|48A@(QZy|5(@@svF+wA-I2*8S=BwdvKtjOS$ChJVK&``Y7VZ!0y zT%LkbCowCXvvf(a?;QQ=%*ZtJJ#3@RS2tj4Cc85&b-GNSB+Iq47I9-zUwI=zTol|B;`nAN-jGQ_t1B)V2>-g(j<5Y~5 zw^U*tOLl=3sHL!Yj^-T%H9)B`0rJ)+nHj^R$&{e2&p}zSH!w#SAD(|H(#{Ur^osUmA@R2LR}}LnLD=gGo>pJm z=4@cvnE|F+&P~6kDSbC{o#|{iCHzC~Hr%KxZZ~e$ScU*wnf5f^Q_xI#*PgLhy6>%R zt!c<{HY~7(r$|?1WbzNtrTfy`GNx$Jc4y=B>WvqoFrALZcE^TdYIN;(bKbV#Z@t#J zdK@s_8`#hKOZSg;HjLtRhUY$Uqjq4oHtw@Ef9s)*2o0ab6AyfIw4EqOX|9HoBQW7k z$&l8-4h&3^8#5gZ$9d{cu;0~40Y>nxZfhp(L{3_T5G~!&*x^30MOuH&Q2OgYci9qK zwXL-wgNRGSd~FG*imk-A%eSFlYF_5-|4yJga|!XcFOD`QpFZ=+5Bz{`xE9?M>2#NG z^Bv!qJhjZ9QQLY!!|=$Ep5}(+h^@yF<=dkWR+HpjG^2m<0$ST)6DF?GlihY_kE82@ zvCRd0Q)?Qqu{=&|37@lOpF-iHSCu@uBONpQA2?gghN|%&;UFuO0=f! z2QK^QKOcTMUpo-i@E9_Y{~}`OI6|o-Xcn;E(mMKE)`+dcUEVFhUk)x@afa43?5!ti zrVV}T33<`BlXp=8$-?RT?xNO55?&m-qDWJQ6d0aVn_q+*9qcU>Tpf;-%?);E!%fK! z{at72!h)MkGF_RV7OGL)5Cm-z+|XjQLw6!bF*QSyB8}MI@4AE65cOzLjyt$`w2_2Z4l)t>|GTQ7d#AdR8&T8FInqzG;7;g($(F*&l5d z1sTCf36iPVoo~`v&9>d>`CRR)X>A!vk!kXo$G|62vp87^iq4bY!dzY3Wk;%L<9KDB zt*HADb=C43%|-u>-`a}$>#r2OP=hLIc315N^IVO%KJJG&+ArQ1Xda?Hgk8eYGmO`L zy+rk%JD`nFyQ8oc5~Sk+^T5XNi;b204o7RBSN*43e^mhUkxDIa;S~D=t?($ew=ijV zv>AFZ-i_+R9c`XoOI{q5gjF5f5G&R=_j(Bbg;Us#{TZ^Dy~4JETpsR=qDui4(#Y=v#V*OcJRW;)6xU6umEI&Ihmbq}S& z00XI{%WR8UyQttynvohXRShZr+}g5Y*rCuR$&MyDE*q}vl9S?$1(R%@>tPM*)o!z} zARn?G4@)z9xyY#ExVl(n4XA+QTP7?Z3i@vF(hw}=w^O;clEh1Yle^uDu@4~W*%oyKR;4DbE`W_R5y}Qa2{wi1un=D} zK0@!&W-F#LhDlX3Q_<8AZ6D*FMMRV9_X_Sd`m4rYcta2kRkO ziR0w%^7fUf`oRMqz-5h7(TdI>T9H<@-4mLbtl0u6Vw{RhIhjm(RI*7CEIJ7SIHKDW0?1++f0is3nbs@!D-_+Dzv)J#F21F>ph2 z>AJ|P@3gONMNF6KCGQx~=uU97VMvpuDta%HJRO@b^VinAEbq?WShve}oIBb=r77;{ zW=C6cQrG;|S}0H3&HRSaT>NOkc^}|>bB1~O314}M|Jx~=sESEqK@6nyF zykseqi3G@*po>x2f7y&OpspD!yJn}Yg-GBY8{x)x&6V0l;g1wKtQ!W)WOBy|q~-&k zkQ(LtOkij6^_$pBw554>)h0!>JH$tO<<*Q|v-X6dyi#JiG;7Jq*%{a${|)zY2?e5sSX|!(?XzM-c?Jd%(c3IP7XI zU!(>;Ne*mJ2}B(Bx>G3%L{jZ_pD_EFe)_JGKcfN>TvD=Q&$h_3!^ys>g2BRXd-(+D zD!efz&6L0jaPooG*e+iz7Sbu)bG}gsqom@2GZ?8T={hUj#O!G{ef}z~wyrX(--sko zX!qr^peluSzrpIQG-)^XG|+CAt}I*IHsi6s_bywB_ZRD)deozvxqP!R)sX9<`6$!|oa-Gdy0o42{qs&;cp ze6w_8i#*#)Hj2IJZd>a6Xgl>+8Y{|rI;JMu?^2o<-uW-pK-6sQSBb6N)3>#i-0DW) zZ?yL}+B^OIE>~%-?_y4{+eA=P;81Z+A5>L99&4Aeku_b?4Y z7N02;0g7aCVY8|+x2WqAa)8>`?NP^mW6~C>i1o9XQ8+9To~_|)dQ7?^=X6a14hdJ9 z536EdcaRMbP1b{Oh-l4b-x}AN*=q2&Qsd!hyV6R(44*wg(4O|LeZ~g{u%}KU5oPdIf>24q&L5h8?1f93ya#&rj-mJPV>?7Xs z;c?RQty|4^Kg{d@u0`vtu@q~vz!{L9IG)kQ9k;~CQ}Pq=gMhbhsl~b3_%oj~^WT7y z_WB1^@fM@*5f({Rvm>OX;F5Sjsl=a1UUCAWEH^f`EeF#BAA%Dff)lyo7wQJfBSX2U z5Omjf*4|N;@FAOBrhke7a47hcXHtex&=vW6%Ax`v7u;Uy0;jgZz6xTnuQ9WCsLH~ zJLa5+e`(FVo;@XxxFF5s_)gZ!vKyw^k|g4Ursw5Z<(nzbt1-(D>{0g8o3&u}b(Dzh zq?u=Dh?Qq2F7pBEdyKgcvs^li*LDgKwAcL>V4C>&^A0AJbX7yhg))JLA|sU)-&k&y z-$8jc<6F~fMT3W%A{ z2(CP5eEW86D3~|>V{~bDxWR43K>#wWE;U_G4Qw6)IpVQD2puhN6xh+y#P_; z*mLfrk+!6Yx>x**jT8SvXqZKUMyeZ&pbg> zqogf7F8NA>O|!kHv87!q0N-n0$L6L&ee1IrP8MG`qcRKa{Ef67>%`C1jn#=dZW21- z=T`3w{2k=|JIH&^2VFCvgLkSn7@D+Ef}(nXkqc_t*OF;=LsBi$(E41>M?%`|#xk>( zKiB>u4}}!ofsofEELZd94KK({d8)8Gy`1L|)T@lAuo0$Z)YKbplr z^jSGDA1m?#%{{?cIT+aBsH)WMLA#g-ZZ|d$RcsNd*zs{ETAayGVVsfHCIqH~1yX3y z#TmVH5&Hs9K`fZ!9wCaGVnp#=)d{5@|NPb%Q5}D~Zjy|k;rMD0D@_LY}c@VVoq?@g-UR69Tue{t4$yr;Q3T=%FYodU4_4 z#@>05$P&Fe)?!Z;5MY;YDs}zbABYs!N1499>geX6v4u z!jk5|DM{KSCt1FO$}g#sslF2$oo8QIyYFxV_YSd` zf@;m%+u+*kY`AS-#ATcV&9twz_pa_!-R0lf17A=vR^8hy%lnJJsl{ur`-ij_@Ev&g&Kwmmo})mi56xc!HY+9N{=-OGES z>Dj^yVxxRKmEQlom9E5EWZT!BFYMw*;t^JtxA{*E%t*zAsAs63?1=5uo=Rl0J#D`6ChN@p)m|kWo~K>QWPWB`0|3rO78^Z)NUjReM-c2R6yx zt|E8+fM9j(X%o(r#Q{o64mk3^0S$z2Y!B}m+n$l_*|gIMtS^=M7sD#RwZg^@_M8R; zh2YZ-&#=O1UMrA;alq!Gw@-8jKE~>cb+%}#WScR3Zs;JcUV7ROvaH2YS5Uzdz{Kih z={ks&ENSUN*~`u#jJvE`o57ZiR5?(UO1;z;*~Mis&((c7g`VJPSe!f~wY>dyJ5AsIu;9r#2q4X1r}{l+zrmI5tJyIYe4mDGH;c$wA!%Y?(Zt4pMd*ElxmgzW2EgbBU~`vr@wX%f=?Bys=m=@H0*wY5AGf~MS=1KIbMp#S zN84ECpx>*+nki~$9P~U~0{;=s`#r{6aby(W2;rJO)`Yfz(UKNa#8o#6AqD2}Owb6$ zH^ogdIaj(!F^;cC)tiSDOf>fX;7BxBY8>Rp%sgQ{vp{_$yp%d#{U7Y?@180+ z_jlx?pSoL)q`P6UJ;B>LXX{zU^u}w?DkEgv)a0c@9D4hK9h^q~G`-7tR%yQPxRPBC zrZ+S_#pv3|j)EszaOGa(ikHp97nwr9qO{dC4r5^OQgLamy$-Q*l5yUD_tNDuMVI{} zse4(hZt05RGJD-iRHby9D$O$39J^t1&Vo*l1zF^P_iJj@z2m#HXG`tjWc%IXS~_Xv zj=7z*P8d&71tihX6z zNz#{PDycc$UsWgo0F(l?TA$bc;ugAmw)ioQ;Y{ye-nWH^MIQHj-?Ar=b4Q-+Tej#R zWe+HLz%9JausJLU;|Izid4vUf9sUo|EGRJq{?F{TzH&F+bM5%bKHSB|#s4ivJ~eLU zlJz--yo>&E#+IvMOT>6gT?+dXZs&ZVV{kW(qVNW87~F49NtoRV-;ml15t81?GvjXc zqNJENuH}-I7rsjxaD7}v|4gF=(0f55BO-f#j2Eflq_8Ys3s&Y>27me3Kp_BHq}<}> zjn+GzoR1w{&FTK}##r?+S<{SZbZ0I@h$VL3+%G?93N`?S$F@H3U z(MEO?Tv+*T$-O*(v?+c#v`!WIhruzO(4rU{6wJhc-UO+|3U_ox<8}kmiB+q;ZW3e? zL5t$x`|(Q&JlxUXR?;X-t`FQ!Fh0B@Hqo)sVkROuY4EkC*`sO6w* zJXMLV;m8Q$rs(|lqc0Q!;S>`LtSgx81TeVh zNFOkWRE3xv0|9Z-iAM#*<-{jYfLPZHh=oWC6d<1H4+yq9b`uK_D!Nd2${>J9Ja?G# zX`wqFPXP$0etklS^&_dEyjI_ z_>l+*;V6LK3gNiEKjE-sLKeS@k-a)!#mAZ1cbsF5=PvCtPLo84c1aB(VWhuM71c=J z7;L1V-S-&j$ItW`=^N&)8tFlzX|UAQNdL22jZ_x;h1N*t_gm=JKrMNT@zU>P(;CEg zW3~h_mLn2o@N7zAzpJOckb=NDET$S3PMBa)qsO+en)Ju1q@$j#Y`VEyoWn~Eyo8Pl zBYG`TR1&R*E3Hsxk7{&*#p?7Cx;U9&JJGyvDiu%syh436C72SjD6KB*X_uZLcE%F_ zC+)c;jty0+Q348})eS?~h&K(}3-3!>bn(kE4ddhToAx8bc~kmsHp*VVlMZ9o3m5^; zN}dKAl(dvCyLOP6g8nWK|4e37d`EV~d`HfJms7#1;0?s~y_uNF$#{vhw7FV|yUkTI z!GqgeEeQ0HVH5`?*Mh4V5yq#|FxHs*H8#J1X%dzG(?wy6J3YbKC45t$td!h@sSF-IYPDgzxt=^%QxVAPEEEYwWoamk*=))3xjC}m)QEJ z?ci4?r#)>mSDmQjz@<>y3*4xDJJ#BX|pl!$lOJVd;56&b3h zt)f~mnW34u8LvONxBfeqC)B_6DD}UZ8*l$liS@@0sIQ{`?BWdJo9SipXUdfgS5o@v zD|5iNE-e8uzk)cst9E+y)E#~855%RNgzg+cPPTe2t!yxU+S`HCVjWO}B31E{p-nuqqmxYQ;BCyeCn2i z84|)tqNG{@RIL!)&Z5n~3Vdj@wG!kh(ENqNCNVi;vZk0$@}S{{>y=DXMB`CDS#OD# z*Ft(&->lhP?6XJ>SE8xob+Q z$FhP)ndT!5G8(r8FU$3W^74qx+mzut11ZNA`G~{~&*}Zb|MWzImyM@&mdRwQ)@}0G zT|3;N*{P8XmyLKO?$om3E530RZBdOlgL85)KXDZ0HU=-tI&h{6aAzAO8Zl{hbD%X< z;@uNMcR*zIrdOVQ{O9x_IA^r##ISfLWSt_PmLUcu7O&MyD>^;#@}>qaOO*j*r_LUh zpk!9^hXM1{I42HnWBMg(o;b1gnWv1&);uY#AksT+nl)G6-j^e7y>o@!X7xvE_cAR) zMaW|OnpQFYkv%5wzf)6^f-`d6&s}-yCEFhBa6uh6ni6a`am`u|p{|V?(Mr^`TRLh1 zJwR9QT{hlhYr!}TNl+a64oPEYLf;YHL~FoZz;D4`J8-7bL+YE+N7Fc*s@V0K(eeW= zse{ab)|9s5>svp(dZ$L0mCOo~u(7F_i6vvoKaj2+HaY*IoLwF+?y zWu>$nObN}{5NLtGb!b&PwW<#7mX?1*bD$M5`^~kNAeBtcSUyo(lc4~hZ*rhQ|NXC( z-`6N9rG_NeF#gRfv$dsTW92I-jmhjy(Kx&knosi4^nv$A92XPdZ9X%0qM)}ZDwd@c zH$z(er-rlw<8`W+pi-Z4vk{ax^%?3}HW(#amY5`{jYRTp4S?yF1|7uq#SCX8{uS{ z(sF{!ats@%AjDYpyHbb|`Mk1g^rLi1G>$^t+0Wfy|8cJ4B`_kXe9E)kSWst`PkD}C zdmXehschO$uyeH6`KeG{Hf`txTatgFeO-1o7f597>++Jhvad_)p&C|1d7b9(gpcL5 zbrU`?Z?j+H7jCT*)=ewf1Z!8}(BE?MTPkdnHGGEtz^`7vCque-B{glW z!6`crK-1E;^VhNHm8lNf%ZobryWIr9EgYZN_iSy!w7*b;@wSbV?__V`hi;_3q;At* zR6lvonx)xK@EaSOL*Figb8^q|bY`yqL;*^`tT|z&)KufW9J6N`aQIi|So40!qmo?$ zkz#5T@_&A~mS>Lja$s=j2V9{W@U6KTAGwzUM;5Id;euWhQnFqAZ8BXUUxAHxDwz|0 zG=R%bMG53}p$TGc3l+P3JToah!&^>XkPh>L)b3N)DJKA$F+P$G>e-KUrI3Q>zxXS0 zVV&ihRu;HO2XW7Dj=(o@ao>YnP_pVI?pE?yr&igLvm8YGr3La^Xe5OKD02c{KApy67uxxQ# zE8`QaGlQdH>tsdu+F%)P9i_azZ@DS2;PhjyF)=6zEXYj4ytHZ$M&$_Hh)`*-{{V0p zuRSCdhZFu!BOHq};FPzyv@MYemCCAhhUC|0_V&-|cy-hzL~qPtH1 zPOIdvFg%)QYb%9_Ud5bY*(JIoP%v+Xs_fveu@J*f=(*z-_z^)7@+MIXLy&as$!Wpt z3EV6nYU_w*+qsHlm3OQ-uBaoLmdsr+JNmVJ?o0kg?lToi2?oh?H6g5u?pwUp9<1Vi z^ej7=zSDH7mj09)KYdIcE@2ZkMm!QjS5D|Nlqksf>4ovV2af?3JWZUcnjj#9FjME4 zFK6`0fp&PByk!Yp%?(fWL-a87urgJl9g4;HYzpy$B{glxTj~t@5&M$k!CzslqPQL+ z9|cQzR$*jY1$(4G&t8j3mYk|i_P{~mgIToEzBXs3eQj2fqi83Rj;I)5`y&$O<4WEz zZj}z9d&0(u$bZh#bB6ixTWc|XLAmUpX*bfK;Z)tuzS?ZuUN>GohVxwMIptKxwFaLt z<3u1LUAsW_P9lAmXQa5IMlYO#Vfy-nv<=X_nI|rI|f#hPp2Jtn+Nm^hhmiWL01JQwXqtJ_pzX6 z3>zxIb>kKkJCqNJoG!jT&W2)reyOpHpsm5t`uqyxI&~#DzCbWr!UalF60lE6Rbtd@ ziwhL&U$}O#!ru;7c+X&kOQjInH}A4o&LqwFk zK_cl^SKsgmUJt`F;s91wkYc>35d&42_A{j-wJH`h^F@4L>6Z2F(Y(F%N?I)QSBM}V zyV9DABG__pfz-@Zp&F50nr8o@jJIZFN`theZ|`l*DNe=@m~GjmEshs6GF4+NYq?_f z3ynKIH)p@tSYTb18rP{SP(Zu>x@*+@OY8C7Ys(CC{`(ZJ9jx%TQkcl2uKBb=%}H9x zeoH1-hcN~UQ_jw64!#a+EAFcHa&YYAmDW`^|zWu{M?2xksN)#MtH(Pg%<16U48; zm#(C`gczTmkMtWT6juEeb9_0B?a`s8_9amkJ3De5US}-NDNo*?saZaEj zhx%NIY7o7Sy3%r!aZfPMD`=NMCiV5+h&C07&?XudPPn>pOsEc$#S~RTJ(k!kQXdYY z-a=!ky6eH>7UUHOR8yc;VeWHozF3rCtB(DXQ7P5pt_MA$EJ`Z|<#0bJc?T|j{@#Z9 zok8!H_-UOAo4d^!nvsUIw-b7@*RZz;J@E}yfKZ}faue;1q{2_?@E#7WHu=4IhARJ+zg#TE^WaAlkeS*&OX81r; z@2T8A@?R8x0p;ltt}fqZuM@8aCQ`pNSHC#-$z=bimi;MRtwd7U%Y&Wat`-=@H@MUS zt20T?C@8P-$TV1{{`FGw6&_PEC2Z{5jlZdz9#I%?H+FM_tunLmi%gjf(-EXqs>EmG zeKi}8Gud4gSOFj|!P(+fV;@T@vOv>k5q&~S(aP+Lj}NRS$>?mYYLj;NLgOf8$zN+{)Yz1Dr%NJBv

QbLC(Bnv{x7BHHU)bL9A2urh9Fvng{fl)!m5#tJM#gKnL%h1m>ol@e&IA z@u<*3<3u{Y9^FyWi7mbYrSS2MoR-9q-IrN}3+uS%$ni})g%?+rwvn_ZC^)M?VwcJL zAHA29bdtQUibRi*{A9AyV|@9P{Kn-Rz3Wezz40=!Yd&`C2C)e-V#YOJINTg`0Lnb6 zmd_9-Ws;aEjWOn*7oV7sQ-$>79fMgOTZt9CCLjA3A*rP*5Q&`VJY|oSQp10`hM#@i zI4-9XD6NE8HZw~LO6+xF@FOgwgfl3A@AePFs0t+(iAEF|2?Ney+KU%6r|A69WkaKR zx0bLyR&@wJwV9gHyfNphASC|lodosOyXrslo$82gcb1;)n-q_cGyD&VuY^NylLzPU z>#C1N@Sdfsj-oE}L)&5>QhGEM*XG-^t*`lq8w<{r!HZ`B068Uc83&2TTZ7OVzauJN?{UP3DwviogGD6t;s|l99uz$cDIcFe;sI&|` z2MLQ1o(OXFk&P)uBldNeH8Q^y%&%9>(>tNFWQI^vyvv^{sz_BqGu&Miy1|(Gi!dM);U>weq$lr)icWe$+QgRr;iazD?#!#6Edcc%_l} zwLDiz0go5X>LCWB&*l!M3-n6GVH+MRu8s5++-%A~A>FvFs~t(q^se2gr~L;DhS3j! z>$>J+PO~-+4dI=L4uuzD^XL~}@?qf2`x0N$l#!BB1VyF)IAA1yc8NqVa;89Rlqlgw z$ip;e!;pv}KpyXGNH)gFSVULISYCrUi!nr=B2JV$z53g3{07Kh?9X#Frsk+<3J5_8 z-ICYjvq*koVU`s7Ftz2;Cxqtlr!eOFD%$=Z8k%3CQB;V`SK!-=(mw__m z;gM!@L~b1e-h%^2bgJt1XOT&LMkIwYBF?RhDC)O48$gzK{CBGcj+@zV`4}WNmsN%r zGeo{(Y(2{wQ5fYJi=aAyt@sJmf#oR|_(w@Tx`1&5cF1G6B<7cqA82mgNB|&n{@7>J z=Dct5bewWjPS-I@@{KEKwLQtsEE$W`rPv!91?QVv(OKlAso)$lS;gE(?3%CEL^N+A zCk2LYkLJ~Lg;e{+;v><>R#8K-3WPhF@CDovCs)(QC_(?oxdU2Hf1Z1<_uN5=2%tR$ zlS_7qO+vn-DU&i|X$C}=BS-#vIqgUD9z8QTX_{n`SbDO39o->YgKv2?j>nTP@c%UA zc+>0mSh7!DjV97bI9xpm;+zTbhv;%6jt5Dd5JI8Z_;(cP$B zz$$y4I9OJcUSO}ghYLZrp~haCQQ^2^5+{NbMDy-FBL?^^`VQb-X9D=kbwd$ywiV4j zE23d9+Ok4`ELviJu5mw=N;a7TG=^Qr8LN&U-Pas+LvwX$2%N*jMZg*#<*r_i4twZn|6n>TVVu}A~{fr#t6f& zEym1iWsWQXU@s4CO~zpO*1w;ggnl-2&8H5Y9`C0K>3BaE-w}s&wlRIsek$8L_SwGu z{0(;G-_cJ+m(^N;;vN*LnrnPOPb`y@ht5Xy)EsfG2NSVW-LoXyXi-lm1DMF)MEIq* zi?dlrG_M9Bx!$3bZ&{4TxlpH1RDqGt3y}@+ekdp@7!CIAs@`vpANXz8(Z2odY5l&v z$o%&2iUxjLAA#~WJb8~8ahccGscYo;Na#ufTYFf@Odq;(9<7-m`>Ps<@!4r~d(l9;a?7$fCgd8Wj4i(Ap1qnk%6jRF z@M?YN$}gw@U5VuNB`V{1z^*U;r>sv|O#T%DreWA=H9cFnM_%N-h;{*r8(OH-=@t@(U%2w_8G1ugm5s-;MR z=CtbaoxbhLgs>I%%{+@?`y}Ty;jj9hxfCm|9PrhZG83M;b_Tc5;!qUxSdhP zk-~WI*0M1xl!&YlMzl~Xds3aF&e4hv*w>A}Ff=3HqmB7E6!qz$@-8A+V_9|i9^cN$ z+4glRSZJYf`I{0om^e0QN$u-)YoR46Mg?VBXf@F6sOB9;wcEE%jcQ|2OGPP&*Po~6 zI3fsnmB@G&Fm4q_Dkevb?{(JkqZl4Wj+SAWfKkTS-XXp=MTl7Vw`vsQe^_Rh*Q%|Wc2b>b($-4ESBS7?PaHcgU{eArr9Ix z1V)?vzvN{4|0EDFO6al0)6G)N#$GSxbp;Ov0|kv}-f-G70kIvB0Ln~hXb?a=GC2l_ z1v2|L%k0O`Q{s1tt=}^03~JMJy*%i(>47aMOGXc*4^*n7i`o?8oC+)ba)Z=aQ! zlr)qmi&;Z!Z_4hjXjsacSzV!pW)Xt(6+a~SYuLbF;@V*mWq1)Kp%cm+=l9@JXIq_% z8Eb2yzo;JIkPDkacu4j+5by?|%{r~n%6t5u3!+W!Z5qQJv&r73WFkka^L#OODeveT z*BngBXEFamh+Ceq5exF?#H)sL<(;R=xiYeFz1R&J!73$-KP*k6=qb4nZ&pFUNQ>ESJ?%yw5?E9S zAT&8%SJG?N1-L-4@KD|vd;qGf0-4}iYk5Ynea|aM#wNNieN?dzB@|G;gbUXXA&Fj8 zJ|O}bUtA^LexY+e_=@h}=BGzAeBV@f2|e|5EwG}vFzUa^c<2v3;xXYbgouqS+U*-g zOpEdBQ9wi^S?j-%pS1P(d@+4*Mf2uNO6EMm+dQ*6TUvm4~3z76{&Pw1p1T9P?hbGVjH*h3xs z)v6A=^>e;ui||i3GT2v{BJS`M_#;mPX(n=`W% zGxo0|cX&cCnwZ!lC$2(wdDM4(*R0ZH5MZ>z$rmOQFii@O`+}ltxm74Clqf=g%pOr* z@HyLItqPfrSQEyh+vurV+ngz-$v|~0>ev)$_<=}9bYr*>n481Bl;KK1X>f)OMC=oQ zr)(f*jy@uM_J>ytA3%oN%O|{#`N85_;UqwYU7aAqCm_Su8cv{E%O~l4xU0)qO7CZZ z6QzYw-$h2^YK4T2N0t&~nwDA7f#UtU%?aT{p`Eu5InWgnc7HA?p@WT7f2 zc;Pz&2YXnIBh9+T8S&R6a~Z+e<=Nw91gD9KUno0=5nv;kAOhEL9`17HFoF#B{G7tb zcwQYkA;!msHQW41vbld#AlWD*} zi9!pMkU*4-R#ROYO>JWWm(E~uq}kaZDm48~#D00m(`vV%L=qvOAdDxJmiGmdXGKNQ zQ#%^2O(FJLY6ownl*vWy!jx$}D)=A`JU|2Hp{_!Z2FxKKwnCcg$dijcKJH1Z3_O-0 zMe%P1w5CTz=vNPPONS4+^sob8{=+k|tU^|@=CF*rDk&#=$T-@&%p&RZRRE1 zRY%$H7X7hPUwN}55G}Xg&648o#v1YnahJ=T&9~uOn%sj-3sGN2Aeyo&CE8}(_95FC zavpC}a3%jW2Uk9CtlVx|5+Fk68-&Jq#45-#3!XQgvkHhUm!z+3HZ4+3BUW^XtiJDO zcYn*41WPGpeQ#^S_}RNV->ZvSu<&cgIAl#r?V5QjY~JS`wym~|KXYj@j|H#0~rX*hu1L-iH+b1LQzp8s* z)>|US8DCEjd>84XqQa(6$@}jp@!7IoN7R`1pM5E2{qZW>znLm2p|EMN!Y8EAvgwH9 z5FywO5?ZB!FM~*@Z;m0-7bYTktHR(?43S5Ym;Q&_7%n}|bsU!-i4+Wm8l|M0Y3F8UwZd_{OlHi$zOCi-{ zFD^~mYvIzLgDd%OU2x^I##yhLxOA!dhIgG&X%!&$5afE+_=#1JE(KL9*Zr%=WEtFH z=M1bcb>`@1+B$C%LU>Dupqf@iR>hr!sn)7o&yx7`QvQEc(9CUnRl}CiZF@^ww0SBv z>u%dt&32GHNiPLfu1=OiW_x57mtfgP!TIliWmv!7wjC@({c>Zwe^g-QpObv}5qaB< zc^@b&gZ1#f#6}sh8~V5yo+*;5d%qB=rGF2Iw&rC)f@9*^4X|tjaLqGV;rUXSs9RN7 zBoUrh6+sXmd0pfg&ZbmHQEF59?$xPygytD-KT#xMsHCb)3ml%XD%>{$8S(NCn|9G* zRFJBD_8@ked{PCk$_^Z~`A6z$9qcoaNqSl{zrpMca(BG(Z)0QQHaz)CuC2Bcljm0Y zfK4KC6|IJEh=`viplca!T+3@Qoj@+{#ncy^(uZS!A~~!u8Nlp-V|hBM(}zd4q6gpv zok~J*V&=FxI5&%gpGq7EclmRql949OD$&*N*>|6c=~pB&fa*rJbR$!`F}^xD#&~l~ zZ!hkhn9z%|F$tX@t(SDd5gZeF_1K+wNh%q&(yHpjlO`XcYI!Dc zWt^MP!Ou=m9aMrGbxbf7BMGXWF#?~QYRp(dmn!&7QR<#>-AQiDJZ79lSv4 ze28;p>U^@Qrw3PD(xa3K>&Bs-jRK=EhO6 zQQ|y;{_ZGe3yiWmm7*IPP!8P{jYc%@p;4x6+BbpkC`m1GQ${8eeD5=CY#{7O;s553 z@jHjCMt^QJubMYRCx2jfpxLHElSti#!hRJh4^me2X(^n=*nv>3!D~w#Z+QDsE-NT? zM)Nl0!CRw>5IdDo>|H9e1=89-QVu9nVRD>W;BeIH&^S5-p*zKRe7fpIrhUWbd8CWa zG&a#KVFMGQ8xdE>ia za~=f$Sg`pGY|&t_xtH=mU=yWS!R9V=hI(Q1-*l@FY({?vY+U*fug}W>4k_0^u&yO`03Pi9?64BnMIJy@>!KF0_MiLlw4&zzCABX8AnW2D zU}~<5xxB&LM-O}uXtybGy41a{&$<}F@;uhuKR%*&?!5`?;uorX3#gbG&C8}6kuzn9 zM7}cazo_4eSf?gAVMY8$IV8AStqD0ukkF=&082$7A0V%d%OetCbC6Fd*es+2{bAER zd?0LUm>3f_XY|9-G3 z-@G^#PFw_Z<(r9D4lZ&ru*{TqV52rz@FQ?r_T-U9-iRa7(DfscRc#+7yJPqi@Y3~v z;0byw&1fFV$Zfl^?NPipO(px%*`ZuwT_J#(w_6+}gY&MUeIL#VTePBWlKs=GXOODg z9erg+>C{3T0E%{St=ZneEJ9Yl-SJv*3M=C-{;1SFQX>nfrk z`TvToriK_s3yy}Eyqg7!pua00FM1!iFt~^{xDRCK%O$S4v5rbe`n;jlqyjta|5Ws` zPf(`BHjflMCLiq=3P?0^vZ7-LFwaOu`Vt{RquphkmUI!}&$5{YE;iCn|d<|SunQ*H;P&%3O$KHB?4U04r3frPO*gR41E8s`W z>{bJ#SoX)5vAKdVgYhMCd8wSv+=SkfAbXf9ekeH!qV2U)u_-;E><8RMaqaaR1scj8$O4Hj6FeA5TEA8xnQMY%Q7=gTU_MYF3HAZWO__iM%c7b1#E}o# zg32c2V#(~m4z1k-&dq42aj;$c3t3YsNq{=jwi_Fmv_x+W1`6QljQ6>~4z9 z6`4EM5g5}r6LtHPr9UwJtFe2JGFPug^FGfQbgo`CF6lp4uT#^U zD;cEB)#P?_u6E1)nwJF{y>qpMxjGL$26lC7vPM>y$poD&x7RUWa=W14e7#}3b6CyS zUA^-)jn6WJ>-*2wS7u}>g`-)W%vS|lgf(Bce9L^n^RwhZP4mE_k8tX&4>3E?hbYf! zkDlYfgSwEDL?`mV?_2kIRaZ_^y!d-=LPRT4!x=l}?kc4E*nOFtbb19E%B zO@!Eu@1_Bpz%bFxsPmfAXDK<7rjJqT->Wl4|2xc>YvYT;oR(joDf(Y%q?izjgeZJA z&w!KA|3Tj!ag2AnQq;SlG&h=0iDNWRerxxiS$k;8(vjNw*g}JJ?Obf-y%dXBv7Oz| zsylWq)hE$4Y>fYG66}WNq#BnpFTxCG7&XbVo>U@=47CAAuGfNi4h5R?`OS$m zYei>iV%{VFR?EuQ$~%{0+DO)m)6diNH>}0Ea2EKbFv0h;R3E0GcXjfOmqpbZ)JA(k+UTH$Q4WFsEAu%oW*Wa|K0$C%%<3tS z5s5)&(UsUeOSDEqv1d_T}U+IK0gaXCr$;mP(KEyq6Gonz0b$hQx7x=5B+sVwA0>_EEXrD zH9dPQMNr$wGyIfy_|lBqj>xetaVCOk-~5%QiDjKp?v?3yFJS~fPDO6HbxrtpNHu(? z1;VMm34w67F9#nxGk>w3R=!kub)^+qzvHamVe)Iq2`ltH_+7*6{?nU>FtyM^tFpA<%v9w8tOf59WQO&t z{`yZO`+4Xu0IjH}F?AyOYQ$6QGol)3p?hW8H^>+oQ_FBtm4h}~rLQb_#!YtAZX;_Q zHv>$lNH+LXLG3|siQ@xD6q ztEZr*AsL6IXbVB;zk#p5LSDE6dxe^O?Cb4or;~7{Ba)59^>KPjn=`8>pOl2<9ZRrP zYE5ZOK_K|HJ}Xs@0yQe4y9A5@zNQ^~sJ@2L3;A_2al=kx#ugS-;A+Wdd5c*1n*K`x z5C;lzvNZ%;!Mb05fQT= zl>T5gH>N8k**COyNRFe?nfXa1yDKHdms2|gpCspyPa4(;lp+~z>KC3`#2zRE@lE>w zXgd@5sEaG`=Y(*%;RuFHR*4oHk7zs+gPMh1+?51^h+@64V6@x=JOHcWQJrNyqEr&lvj6X!`Rx^g+W!B4Ka&0Z<~PTiH*em& zdGp?z_Sl(1hEs+S{`NucxpInSZ2|Q}#yeyExtVp?yhEraGJa>bO1MZC6mOl&<|Lv= z1~K~0r;R^}3?2R?I;^D6dS8MAYktn&(8T$uu3q{DqRG5EMmp?K>N&t;i~c^6IvO)2 z0p~S;bX8U7X-~(MOomls>8jejH=&R*J}9V2)N0XpL$zM3QMf8yx$%EvIKJF=;e5W_ z<`n14tchS-2njBywdwG|xa_K8V6wWzaqdXbEJDJHY*4*{RW=&3y%$4VwtmE- z3FNDZSUw5hEDZv18n+smmJG+L{A+?^J>9iGZZ3@19rY-ro3Jh|CH6Ev20O|Lg!kls zf<`Sn& z|KCix=s4C&S%+Cn8&CIbp__rhf6rdURy(eaM-{#{|0EK#pU5@~s-DAosa6k9mU_Fa z0keUmX^8+P9@Z-*GESD9nB6OVjy0@8*O#Rh!0ip|48t%oq3M>Cu{l<1F!M{yIb7_bw1HDuSs6?1#swTt4 zicjtUSeeM=Us7KDcD)JytggVNOZH#E`2|`2e5*PymQ&u`1CIq2UTD31OLPEu(lk%J zf@?~g+6>Be_ypdDN9l#|G+`0e4i$srmG2Q%Re+t!26f>+Hp-`iatsgWK>=u-trtwJ zuF48WNNh=5xfs?rmj(ZmKlkIv;$mb%S5=$i)&YDXkZ-)Gz00sgqIkm|a7Po9!Qa}6 z_zRIZsW`cuui3mW9=;*Z8~%szE;k!mr{mTKbgr6`qpBoM&vr$I^lq#fU7Y92jr&U92<#R+gX)SC0$9;c7`}?-E32`fz?X~4Yg6D$ zYf_%Qavm=$=i#z)4t7;-k{}vu#T!09{!>m!*p20XDi26j`ESWXZysLa!5a>05W_bV z#B#$8h}t)xLRhQUCRQY^Bd$F8_DuwGYx)~x3jIXC?B3n2oA$-67i(eKoKl>e4^2HQ z9Hb^Pvf9`3eqL?v2DHDfiWB_R)fIEI{im^NaK+(xiOV^!#1pB@b}iZ`3uVKhuBE*^ zxoxfj_wZs*?i+TV>vjrzt*$sR(2O@iK`pw>($4~a2ElH?Ui6tQIeeDq9~t~?fd3Fz zRh&pY*2#Hx*H7O46VvsR-;)Qre)20k)RZ#g^#Cz;{a3RZW=)EW8{=5vc68bH8!`Z& z2?hOuxy@?GOsNq4#`xvJm@x*1EA#JaFI-vM&Risye`wyE+Ba%hy8|O%aU7MnPv?nq zK#}@S=hsQogQt+6uwovEVF5d4mVp6;2@*Bc%J-Ps8xXn?AFkPmRt5iw7dfnG=&zoY z@<#x5>^U8f_W;={6OL+Yhl|yofk0!Z<)oRj?Rn@}zO_1ZW6rR^aB7D{SX_}jvD273 zX01$3fHE%lJx=8ht<~ruB(L$$YC$hi6gPwuu%#UM;xaVG#e( z4{gM=OY}Pz&Ce4G4jgL7=;OLwtVcJf)2Ya62mkAmXh%r0qf$#uEw1Q+Xv(yBkTLf1b>1U~sKGUvMfH zZgj_~e2^lSnr|6HcSzJHscEo~lOa1KieqwI!(EVsGj@|3^_VuDlQE#0k6aU|LM%6_;BHZ!(6jC^pGuZtJu{+lXiDTuH} z9ER0rvieJ;Cjare5{l?bC@n!FBwC^t)Z<0#2+~D1_T8K-?dN3G0?tjUo*K+c(zxiS zp9|QUM`$0CnWDCtA$AFJMkQCtC-`N?{wZE`1_cwn_g^U3#Vsh_QeSzJSFoGsWxWQw zWni}y@q)ld90*2^p|O|MVX2-BtPwY7aSm125HpqYa@)b%VH7A(+q;bFUn_9-iA(F9|R#(w0?CoDosh==_romj88*Gwl3?m`dg! zaj^W4B=URn$I9H+MNAtXO*jZla}#Ay&)98npJ=EJ#tkw^Y^|~W=?fFGD|~gaFWlfv znY}Oi_#r(J@)zE33w(SR1L$21C_|)0hM0m55lqQj7(7 zVC!82Cy?E^(;cbKR>xmyD!-QMB(paX>7gpKD?eC8YS$H}l4Gc(60e7aCs+KsfqY%LSLcF zF|arPrl3!Bp-c4P4mt2F9B~IuL8BhxR7(mvMb}Jun^}|F#WPC9c6se^6tWR? z%^^wZb5KZXIIEBZsc(u@EH#`}MvBxoMJm=+>9?7_@L6*#*L+v_toe==t{X8DKI=x8 zb?bck;Bpk+Dt+Pi_CozTu$QDc>ehEWBNBq8tM!I^|2X{e`9rZ?7{i6RYLRO#4gS z7xHP2{b?p|11HaMg(uF(NcPqnNd|z4H@cSIx=^w#nYhqV*d#DmO#+dlFd~3(Ss0H^ z0*7N@Eq#p$6r|GICV_!e`pX7Gr_$plX`j?Obi`WeL*|+2ifiYDJ@mUtw;cA|xU@;y zUE)CxtjS44hsm~Df#dTDBy(EK*=AITM*T#|q`yO8ay>6Y%jQg7U=oW-l-IM(t8_x0 zp`W?0Im~=oEJ?y~XUcI0zgLnbwfiEsN?p83#okDh{U$ZK!&elnjHp)|LtQM`dZj7Znw}ZfUk^K47We<3tP7P;^-iU_s-$P0x zRd+GG$h4kcdx0b_lGeFZF4DwQlyfCid=@N7f91d>Sj7Q=cdJioI0>{`@fNj(oNKLqWqNCn>)%DQzZYEGhSz zl&4Gz>W=}po0Q+0lnPQpCgo=)rJ9uMP0GzC#Y@T@lk#JeawRDXP0EEPWezERlj1We z^GUhNq@1c#EIq7M11>g+N0>yde5o2xXA%dPL{a%re1b{r`j?Eff^ z#HvNPg|(9PIZL89d~uhz^1f;ihcdtNKCe7D+}8W--7k0uRh6IB#UKreZ}giju#v%D<6<19iekz z@;Jx|T^?c==u*e%0^DgX1xEA24V^Irx96mcFEX(hYeg-4x>gkl5+b!7F6%XQ@-OA< zWEOAuIn)VrmyG7CwM`Y7?_*u7#Kynd_kP>dm-l65^&>JsZ3k_zU>r)WWJ}cl8sA;3 zib<`F{G@^}+IUCu_uJ-*OpU51b%r{rTqlx+nyL+`k*R-Ecj^pNyJheuGCZN?rh`!D zS~WZk1UCKC(mzjits0j3`K0vE=eSm#oB6p&e+Hsyu2s`BJ_)#L596Ec{<*GI=VfG3 zZ=`>o?pk$z=I52^pU-oxIw$kuI)jX9+Thqc)wB{*yYCS&x((p1diL3n{9SD#Prq%j zW0`-hReCVxqfiPh1MT0`pCnJK|NBC}v#?c>-}B$obgZW6-%aW)epkx8ie4d9ZB0j9 zt>9wH$4FY*H{#k%Sn5L?qh(9_n(61ueXU}6;9t?(`QEsKtWIUYEXCDB_a zDRrdGl$4n~%;CX3w8K%@U&{9%%A!<=DDECw80%%)^IErd(GXf9Ul`j5`^C%KWY&em zD`h4Zre0_8%FZwG6_I1|Oi%cj5`<{D>5+2``s?2|ZmI>k;eH4XjH>SBE*E38!qm;;!i9ADS3} zL-Yt>DCMr`8`#w*)$op4S$3+Ud8%#t zz$^hHd5Qj*{h%v6vV^5W7Ac30s404eGwecL>fdimAvQ@X7;0B|v?$ov_C6>1U8@NC zF6>+@JmBWN@6xM9foqk7yGyMG6tP}owfN{x&F2eUs|phZIc%_u2_36N8{bW(x|sye znvvi*|5m@(Idts;NFv9~M2_7mNDlbK2;mP;qpAwNtXah|^n)cYict-}!Wd!$yAh;j z2C>+*Zy-o3|K58g63|x!h(y7ECojG*2D(=MGV{o6%zxQDG86OHo5z{1#4L2_S*Qd% z-#!a>l1(+drDvg@kyr4{ER^EkFbl6{7B-~MLWwJb#VD%j|8X87SAP7@{c*h+k6uUX zFTJ#yBh#$tRpzEbDDncQ9iY3AWc3+*PEupv-LP^Itx;I zfAMF}dj*x9NT#Kqqlv=_;Ztm%+P#jmIz^FB;E8_fM5$ODFfN2$>=!45ZYC01Z>V+o z#tbdEO7=5M(!Bu;sPbQTai&qVx|xn?<+@s>M?~WO5t&m|$?fVC^0QbtMXme*;+I70 z*`QXwDMP2vQ;jWNYg_Olsy4NM1qp7~y17E>69;dcVomQRSJqg67ho_@C+;d1{2h>W zP5AOIRYrskUwH9Kpz`2leu9e|z1uuxGAb^vTwHmi@8>0*?^-R~>mU~WYhO{hYqUu~ zi^{pWxp!$h8k|U;Tw&dz=Z>1h+S{+g6R9QrtO`ni+%DUQAd%;LI**iLH$M}&s9my= zThoTADLbvE#rKjcQJIiNs=*z1NP{k14uU@}Bj$lv`MRF1AdfHd{APYUk;`8tV9rAn z2P~9INCZiM)UPNm%?s9a1GOgfU%w93sB6|`B6{=pw6rXlqzZJEbU5^_| zFyCq3bcP|5&a=s3SMzG3J@r{cy2FRZbXN#Mm5z{C)h529xOu{KRIeLSmhMvv1Nu1M zwV2Uwt=arGa0y8>vPSY;P|F(WJR?J&d;GQx-hy1=2#H?7KEOjUu0z8-E3*e?La%>@ zgvqI81&_D#7zyN~+KNn?qvq=mme41shgX@mGSCv4{*}5=UM)*TL_Ph6OnysHuO2tg zf@1Ysd5%~zd#Lq^S;hwJgE3sx#FKa+TXW z3o2EGc@`d_j?vGW^VuP$E-`o1^~Cc=y*Cb?cTO75dr>f`4@x*;T^Hl770vg~07Up6 zY;c2mgflVuUGusvgvX8|s9%!dsXvmA4#z1-_$B*VMl1ZFycvcZG5@R3Dq~yKNPjq!1~HU|DirkB6!iU`Y?gwh{9c27<=_jx%Ps!al{r(r^ceP zpIzc4L`^|_O}wb{b!b_~o9kU3k8%dXK4rf*m$99K@VeJKqS@Qj;t!;~azF6tSs)Bv zSUB%A+;F@j)_KPeHQ(jnZb+O^R&qy-{O9lncXp`P*O0Xo7-g_ICR)&z`_fAsVvkIg zRF}S%7)}2nZCP*0u}!eGkdx3irGOWSjv3BSUidif3l`BYi0?#)WV#p*EWRek(Gf5_ zyvB+&(hIlM%rOwUjCj$IZBT3%)rtLz1k0Gzv8Ym73FNEay}Cc%cvG>y=GT*zIBaE& ztaWd1Ueldt4!mJpc{ zw?Lw+!nLxOpJ8<;zEfEfzl2$v-v7ZjL+*hsY@g^6Q>c{!D1^cBpY48 zpB~TEXih{m5lLuq0g;*NZ>N!}7XOdER&b{c=Vif)tiVOgUjz6$|4>3r6BYi}tuio{ z7WNi?tY^6COESmov>gBZLg$c5RZUuYwSEpJx_y>3HlAt=1P``Gb)?B(NRn2ldeNX3 zn6zTa#_L{gXrYu;qdqau(s%WH=|tooV|jkoAo2$6wDPZ8EXX{oLoQ-+$kuI;^l5t}S<6XPn^PNP{te=@#k35|%V<40v2&p4NLn1n zA}e-Pg)0Fi3;WqcB3;FsTg4XsX3pWrS7!E)k$i__W>m<3sHHQC^^)ok<4s7tzlOoq z^-*p(_5br!Q~x1mcWi2JPtnkGYH8>iV) zZmKxzbXt9D3?oi(vay$4vYjui_^m;~lT8`Pg!LFB59zz~rCd{Cy% zHaP;{pnk3mE!)IaCT+Elc5oe8^s$~r*jqJHM9kDeB|8Cjk*`qVn9HEcUU9YP=Tv*A zj^VcOM4udYi~k&vZaMWHUpdZe9Bd=j3>Hl7T4#z3ds=%&9WMHKsaN#z5C62@-p2OPts#B0l2$p2tm2-`7dp>ytywBP?QdB-iWu*SjQI zy*7x==R)7u+uVjNZiADCi2X@qfY|z=Sj$!Ce9qa$q{2Ndy&ZS2ajbr~4^beG*vvx# z524M0-0%(1v@9m}5`AEQbZ2asaZ^S`3)BL$s~9Hu{}5(qPs98`$Nu=9359tF0>{FI z@mDSq02pE0*pkyDO7h}sVsCRzukH)`uWL$*z%%t;s2b&Ys+j+T{~rG2yEXe~LZ;S#e&%sSs8K z>bH-|?A*i+1V25Gv-74L#>1LW?2b%vb#kSUoCKRnb9ANIQt72s3PS&)ozS!gvfWaI`Q<&GvtB3gAHM6+d##lJ? zQ;MxRH6I_GRG9P4)$Us%)%rcrujm0)W(Q6aC)ZBkA^A8e;LgEsf@_toz|pi>W1!H! zxQ4q*LzfwxXl!(DGq^txE50uTD~`~eS^&k3Uohi-M0d97Fsg8@zPhlFHKDNb>SF&m z$7;n0=s6yqP*`7iw4-Ss7(hc%UwO>JKxa*43h-)#ISpW;9fGj4s0cJULTLOMM%J)R z+FItAvjd$~lZH5&7U}ejYCo6-a}#Q;et0GLtaX?r`p1a;D%5BPgT4;G(_LI9u@B>Q zzPRGev3EsPz`S==9^(q^!F1Dqxd5I37Dxl<_5|hi%QgNhqN@SmfZm~#=%Hs5bs0y~ zuPFjz&E z5`W%P;!QmzUfEOPw4M^rki^8C)z>^M`dBt!OxR7=Cnu~(DQIt0yHh6Yd^(UhVT)zL zZjuR`Hn(zNDdl?@xPov)Azs@tUvidp{Z=p>AduENd#yNWO6L4(Gb_bsPwI>(LGyXt z(5hhqtyfwGtpYV@#pR&E30m#N1G}*VPNyM^*=vW<59tL&47S8eQWn(n>MKw1PeqKq zW*XPAOl#Eh&Bgm{-euLS)=2?OM(&U$ZFRq_ENn_9=V)xGLD+CHLCeH+DK<{>AG48j z#1rk@#&A$)U5(+6Q@w``4OkEZ+e4%b&%NSBjV}wX#V%c2cj+a z7uTvj_Fy;}A2@lP61%T*t`hJMcde98#YlYl6Zyff}AU4t3jELCHD>1+8gZ<00SD0T0{nrsI6 z=sT9@8+~C%tnY&S$W7S#MFQDXoeQ6DY8JyeFX+%wON}o8=rzGhoyImAH7}Cb!C_>DiKbVZYRX-f~WBKY;e%zI_3Ii@NwvG%Ki6(V*HG`~HQxu$x7Oz&j zcyIL@Zsf84>H$-DG;dPa5h|3a61iOq$#}5rLKd{P3B&KCs>l-YCBT=>ajQtxgq@EU z-PkTu!4Z0c1W^O$gb&lkT+Q+xc}k|DOl3m79VN@O8%zFujxt3 zxFgWqdE_7bLYJ7z94GI; zFz^0h$+8q{g z2zzjLZZ}NUYPUKvr6BGnTZf`YZVKrg9vzvGEmtx}9?~7d#X~*#fF^tx)PR+fv$N`} zPVg6tPac6EiGv#K>O1%2pd&9luOK`x&+47NN@)mTr^9YF>{O4bW~W!fP8(RQV5uqo zAe~t~Tm-K5oW*J3yHSG?UsGQ>u1*ASB2h#Ja7cs$lvXk$zPiGV(#d$yR}j>m+`K?e zxGG%|Haf;dee7LEyl4aY!V?O@6Y|WVVmH};yE+9JWCKuZwIee}mWe3F?V_m-h=|U6 zwdg#)*lTfHX}67liS;9*_4twaXH;Ha94Pf#ecjd@h`W7!7^~S=H|PyOR5k#6hs|RH zfVc}YbceZwF)~0Vj_zc<$O*_+zk>?@-Flq4m)WlBhnfD=Ses=Jp!?H(AE37<8k@I> zYymA0%zM*!+ON36OaGhz6|h4rV%%fg*3)1>=LeOr=sw~VH@v_g_1dJ*TM}VkZ1TTj z?g8Y#q!@~cJRyUmZj@&$IR%K6o@GrQ*`5Bk9YFtU(FW?fiUzpDXX?oty-Uw7&hcTU zPW7u_2?{sLTG#Ym!~MlzQT&8_v-n|Z#)9jz!wxF3dZ&r(e=%J!h(Tl* z>FdBw{QO13p)u0~t9D0z_DkK7lN_s$?_>1_{9-?$=2StrcWkQe4Bohh_~L8pCsvNT zR@Ue1h6LWxWE>kUxdSKZTrgO>!fI0?Uf?WU$TwGN$=pAH1`6DjH_Ptv(u44omPHwS z1%A??rC$k%qxnigCQGuAMel<{fv|66#YCUXqk-9eRUOnn2-^5*|E{Vg~eHcvf#oYxOpeDjWgMXC2%ks;)8tp2w%OF z{~5iW_-adIM*mY{Dw7Nm3syof-Po!4;MPhQ-e6b$yxuFfW5XOf#O!eL=M}8n%X@w9 zW>?G3JjaUlu3++@Vq};u-%bmT1rdw&w`8@uEDPxw~Zh$Qt3z znUbwOM<*U@Um$?fX4ku_3jFk_-N!+MkNDHUeSPQd5+{^2J|q3I#C^De$YjdXgFSfmrz-A}ZljWknHUq5nf7d575oY?qt><#I}J?rT})g_}=oHj5>KtGzowM!~D(1-aosHoMRT2RH>@;tYm+5ut!Ma zzc`;7(@I2>-PhQ2%hQ3RU!yM`RlA7n#o<57EF6TzDSNvl=pc^BwGTW5uDszTBCy!v zE%r)!y$$DzYMv0(2(^b=MMF93VsUx!wCt=nH$1f}oIkjC;PRbV>FE4qUZ68KNVEms z2I@By4P2i+vJ0UVGXWmo<RuM%cb=~9=Th@EcAlxa$w}%sF;QMxwg}V0w-S_>UL*$>;(uhop~%i6Bith5 zi{_B=qEnk$Ha$D@0>h+lvB7OTLv-9`2qkCI^3eqgu!m*-9D&q&p#XqXZ4)f@Rm2a^y%)fKsRI=AsD z^*$#A*s9IeU+QK2o9OOn89!wcB#5nA??1{L`2ifNN~noC zaV4K&>U<+rCqeo&f5OKwEs&JgDr!Pb&}v?;Q`vcDX*(QdYrD664LmFVM|{JM%93}v ziD+v(2(7X5r|R?;m$g}!NufAeO&J!MicR( z3z(ULhpiBdElOmbrI@`8jysPM17exi8g=s(`0;O$K9-LfObgzv8~FN`j%LB$Ois!& z+T`Okrf88{iVE(;A-8zh)1To3#lm)-X&M%dv;Wi|Y#gMo z>9Lmf5@_i&f+PssZInvDeFF{9E|F-D?759^h`n@A;`B_ctTKoP=7 z6GcJuLVk3BrPyE+=bOZ9C9z1TVe1B>KjzFGWDi1usD0rq(?l*!$cdgv9mQnLmWhK0 z=jf+loe)k^bV!{81*18B9Ts6wFxp3=3iV2hDiq284+q~8pW+ky_e=ZtHTnJD=skaX ze->+z8*54SZ#D;0qDDMu#R6qvM`~C*`^N!Az|YT6^K+>A zDV9zp){7|;^EB98m?%R7s>-C=r^UY=dO_=g1d_VYV}dmVo6WR79Becg{@Zgn+n)S%;IJ6M457d-+7 zjtM#Z=iz>WxA(x?E8uNG9u^P57Vx$nysd*4IK{rZnPP)f+wugWRO2&<%qzG`zM%TV zJqe&^ZZ{uC^oXCW|vacaPU#7~MJLhxVN9HuJpUT&(>ygaUtG1NM#o#y;j5-Nh_0+8nz~T0TsD z-CGv?ZMdOPrL}@pb&EBv5VP_48?k{pH9sJ!n{|?Oxm9=hS+8Zf>kE4dq^duwzflzj zJ-V0P!q~5%QZGV}MDM*MJ>J%hL>m#qR3!1>$K3Y#g~YNN{~tF0C|e^)RKdMN9gANA zIV;_bGi9DRf~>MxC%%csLyyLVoL4U63MzkoFkTcmt?~3%mO_z%6SQE6oI3a`m~7Gg zs6AMg`mD|WJD;;-gVdNOzn0F%i9-L(1f_nFBl)_So}``+pveOC}$WP#3L>b%3PAOH0{&^X77qYo1!+EG zBscv5nT7q}Hl!DmUYpxYXzmsfR1T?}ujeAXqH2xsFzR=Nejsa4qz-9dknYf7lOz3G ze2`XA<@H|^sXlz7=6aE{yL?sa0x=&36^C_ac=5j{=CNjYEUwj}BgVK(ZaoXiEqTJP zqA}p+*R2190JpY@AKDJ$dsNhv)cFrtn{#YUvLUx?_<=D<`_2It2m&2)T zI&8hTYxn$^+_+nYqpJoF)l~w%Z5lp4RHZoGwy6Xx|-|9RX7DE6X@=$@Jit6Jy% z)xAW@aHe9d*EL16Sl&&$<<03WKO+^`E&F?HnQNA4{D78&-?e3Ks$%jb`&^Ug^Z2xu z_dWSv^tsPBwd_mlb4_NSlP$OZHPz?F9$P+0pYM=@GSPd&c6!TKrS(}R(GF~s`eH!a zv~EGp6WJcCeF;mi9FnNVwAGhA+(x#lC~xLOBb8n9D#R2K&nkqUCC2fa>Q&#dUK~db zT5qod!CkeF>G%+_a%N-@cKmyfi<-VUlM{-VOmK&bhIoF>caIt_dJX+Ktx!&+$^wQ4he_f7_OR<47?u6*Zu^+>{r($G9z;@Ni z#W-Y>`W~u7Uswl>E>wH|_;q}h=#;_**7n$NajYBmAgwktOOxmJnMETbB5)IB?TMcO29KWrEih&{XWYZ@|Ih?AB}? zOtEoLUpaC<$GCA0;pwU%;RsZzNhEy!hl3#DF`T*B_jE(T9uB66=WqZLo_qY;kf7IF zt&h@i0$id;M+b*J2pYe2fuq$GK$7U_XsTm;Gm)zCiGrNSMWQ1_MScXkoMu0!WbNu$ zdM6%lfGnE>^cNeTfY!!w9Q~N9OT4*g7dNO9RKRG}C1au54JS>IKuhcn%-igUO~Y~8 zAV*)(lRe=LkB`ksTc+I*+!Tlpuc-W{jHw3O(bi;~ISVZ5R( zSA=d1$%y~S(b7Yx{QJ^DOm*7TtL!V%I@n@&5Zk;NGQ6$!@NQ5OzD(=jpo|XQmg?X- zT<|hG2ubS@mkGL#{UuHQMSUqExV;%3qE2oE4B$y@}R8W)QHk0~@4lf~mM*{h4p62Y{*5p)My))W+3*~o@ZGBA`2i?|# z>1~~s(N>BcWfw=IpR3QS*{K-i;Q2E5&#|Xa*A}`(o{xfC2Vvdg*IUXUks_nWW zF334O;6V8c07ghiyHm~( za4^`{T+NtcdMn5JT`6ctl-O@TGqgT9Zb1qhH*y*@WM9!$YM_ba%@+@$gt-j!um@eZ zZ7R2 zxmv9NxlS_VEEz2^WiCR-z#>Hi>T(<7u8NU@@p!YIzkB>6xKQT0-bS`UtFBjp%YLKj z`Va%zSwPkkyK(>#1oA)e>>m0L9UjpC-=+2ckMw`eckO>}eOM#leU@w+0VmlAzyx+A zrtja3fzNPgV`keJ@W=sC4-9(%FnuEiGUo?poZGa4rDoP+)au`+x;OU^Y3xi^K-N8M z0c}^wHmb08>5%SnU0A=J`^YT;P<}g|VvhH>oZ>&We8^M>hR1yx8sr6#8c1 z1>KGMFd;YAsc{H$Y2A1_HB`r#q3WRb)-|)8DV^_I=lDncsU2k%PBX`Qrx3yU2Ok5zjVvqCb4>>7IhejM_H`5v8w zmNtUI*M21L0T6 zNb_uV^F2!j#x`-|t!rssfsR7Y$(gy|_!H_3IQXuPlIk>>JkEObWU~F_{l+kyE&krD zsDTknhN;enQmsGEa?kkd)`|C)Y<#y=p6^;REVlXJjj!dq`h(P_8-K#+*_BAOaz!h( za`b6%QHP@R%vw2>fVsMfqP0V0pHXTOrGw5aH7gCK5gMl0C7o+PYCQ0!Hi;?U5-(ba z+KfI7jTePj-w+L+eemnJ8cR`&D=GJ|jVKHpB|q8z(XYhzv}XH@)h@EMW#t2sitEP# zJjT|`HnWBqi@~5`*8-?@qUdDY$My)n(*WFC9S2A;`10(MGHe9IuVD5(ZFZB7+!Z4* zi$u2As!$DLlB(ZS>#tHcW^52MqQJm`L3-?hY71K?ZV1_|&7I5R>g4`JtYyv(QfFzn zMeCrleh)*vphj$1T$S?+0uJH*8`R-HmDAm>*qM&ygRS-B_qcP0<@t|9Hd;D3w8cND z1;?G`Yx=cUeJ&BmZ-}FTXB9&I_*JY|)fZle`zx}Bwgg&|QE#!hwl7_fwSvK8fj5FZ zM%MH}8u6a{!Gr`ctSC!`iuZRe=C}HNy0=a=CT1r0VoXE+bUSRPQVl*7?8T z*=vnNM=om-f5u8@b7I(PZBW0*wRxE}VMvP_@L0A)UcX_i_P^Yqwu;j;BP_Wrd2WmS z*0eO8mcR>NE=q7>15@U?AiR*H!DkleknOqNSsaBBFrtOqU3zgw_D{@8&>?d!(#vP% zHScj3R%AiG_rD@2!T z=7x!TQ4^Nifx4d!N(IDZ<2J2{Kvgq~wPQ1GVghLj#1;c0z9&-&57sVBQ$~c91pPEn zYV&%wc}UGN8DXd`^WjkZ-VX}&IC^JoQA?1q)26J^v!<;h_Pu~0cZZH-qc7J$Rx9Xb zwq0_20Cw8HmYEoRgvQIa5c@VVK1Y4X)Z=_0?#G$sD{m=Io^i+RFOtLcd=@qrUalfS z=9&M?gaFFkr4h%xv__>g4nlmd!D1VO?|;tCQ^N^_g+)` z&2JG{W~172hSa!0O}uwMX6?<~B}n20HZYD*9b;hhwbj~;-t0tz7$RwVKJ|}Z!IvuX z1O3!Gp4zg6YpCaVq)w|sb=(6iZL#MU{EoUcFiIkwN=F?b&THZ{Rs&h+llJHF4-#9> z0Bx%4Q`Jq;)p4e={Fid35f!DC%q{LgDY=2ZMn&#Hu7uWQHv2Xw1!GtO<;vHnNP;YLJrBa z-2=N|j>dZGEBA>e`}zaB?zzdj6XQ6qYShI{^oE~i^!04aM0)J25cijU;UwKUTb!&} zeR8kqY$2WX>CQe6a>d(sgSsC)!PjFWRh!AB-r?8o?!6B%d%mT=9GfQZmYTKQC6!uV zsPkA2Wg0M#??~21O|es%xw2ZdD{;c$sE$ed5uJeR)?*zahr7@Y%+03Q=Em6t760Mt zEBJYtpu9p*&gvs{)kE8;)YNuHP&YnM4N5jH{fJG&z+B`Auih7ggK? zIPL1%d%ljxwn{Ki~N^_XJ_yR|lWTlNB*EDZhI8)px!_|E~5iQSLHP$ni@ zLvkgYf#nv+@y_}Jx#uE`36#&8=47qMha@{*^vk<79E0LTPXG~cM1R4U3B;_TGqgL! z3}G>^(*dafK;lK0>q7nw-bJWR;3_vSJRGyjgmO-28BZ29fIgY)Tw;HwV^D5##&J~xis23oM|o$h!!Ec#)-kN^LR>awd9A!Pj^!}J;Ld8! zm1B1&Y5sSJX^9pDKJF6boW53$$6jHdg5bG8bW+!d;5uCWT^AH+)zuVSDWjmF6H)u! zn$j&C6ZRiuyI#!W*s;Ya4h{*|<9Ye`VKh6n^*omt`TNzgOX!91aJzpkC7a2+&RyE> zvKMZtIVuJzvOXYNboVtAonkeS>wV(Rg~Io9E=vYjM@Y`%4ZztcfLF8HYJj6+o38=p zwh%3Na*vU@C}C_(#Mf=H^eF&E411DSate%F$Z;sygb&iVw*aacW>mn985&Lk(*7)zEU#39{>J!`tDfK zTH4~~)|Lv1b8vt-s^i(G`8Y{Xd$+!iz_sdq`ZOVum+i9F-&YeE6&{|QWjyS-T=u=Z z@umHT`-gH=(~J2RkL5{3ZBJFp!k3wIV%OLMZp%L*nBQ+s%Er!=8x%ixJ#Dvu(6#@* zr>E1L)L%0TA)_7Ul{eYye}b=c?|8?GA&$t|U=4p8Id^t4uzfPnhE*S(XZY#-7U}#e zetO=5X?$tBzgU3uL}mb_2Wx&$)z*beg)#J#l|2`p;$B-I6B_XpUv=S-a22iqmiYeA zE64R5;R(gMYf5)afEov9v<)zWY;$n~oliVD2x%g~tP^0?3ovU^lqmDo@ZNNtU72qd z7kszCemh&@+9L?x#kleO8$D5kxulC++-0^bZh}XPG_g_}8Y!pGStLe>!L#tYE zx>vY1p~sQGcD|_9LB-t5YxBKpg#FvL4x*=-UE&mT;)LFJH$t7gw~;;j5$aPI9IXyR zv^@O-hPlB3-Z4@+g&rVMX(gh~&jc?S_J?MlaW3%`mx&g%+djjOxk^fixAz$(YS30g-hY1>3ui=_@mTCKed5>9s@PVPKNw2b=5K({8#7_;9t+X4L%2c ztmxXli2caX*U+X&Pq4BI@2ppX`-HtuN@kB;1ACuQRaA*BSVJ9wtpV#;z|w-lngn|{ zyEYMZkCjj!)yUFy{F@=y+tmsGF_9y+N9azq;GVdiMFTSqnB^ zBvBq^N}06)4-8ob{-lfPWnebNx-A2~MP0Hk%-d#W;E=%?6OI{31llB5ZQ+PJH3L)Z zKqg_S*}v&!077I<>w!-JXq_ngI*f{%sjn}dlU=I;cK_|tM-A+XyAn%)Snlg3;AsK1 z29;xxdM%alb}~jECXc;Bl&N2TCCUc3cyKHc*;(qIP*d7Tclw`^(I3Z(Lo+&TR*6s3 zIm~)G=UB15bX)9Lq)FTWH|h{^*llTZe)W&ZT09z{#wS6-n`FlR>Ix>aEDgvMiI7ox z3qDi0;gvGY=Lk1M+y6qHaKx8hvhw?0NQ4^}#jDPfJ>j~9-Xas1qQRgNv@ucLWiJ+X zUFy+&bQ+~e@A5aJ4{3~jf=Lc){6p1akt9|xBjAKaW9(-AgB|{LgVf?whH5gzW*K41 zT4u0^6eQG%8J3))2um9deCQdftZE$f_=4Pr8UM>DQOaB^wac;KN{rjoM?^+uoCl~Q zl8tEmpvjq`RVK^<8$S{*Z>Gh~?cDKUamPm@%z)8VuEu(eHF~=mRmIL&Jm#_U`RdX@ zzP8Lm4A)cD&MT2(B=6^^eOq4c2sLs={a!;gW4ZYX15~-`>PXqx_oQ;diC zB*v<=f1asZ7? z*8Tm^JnR1MFweTb8_l!s?;7*mD0ME^&&b=!n63xL-m4?_9=gGRm)Lu7{QUi=unOte zdj;NE7tiE8${U`J`xCf{0#$+t%r zdXYkIuF-*M5($)0{Rn`1veK=1(Qj@Q#^4A&NG_3ds=*W6rr9MGEMsfsp$voB*J`Yr z^m3CCh*TCP2;Ny;Quv1y=6)53z|*9-joC9tQt`{rs_Jsw;r`~T;aWJ!qU-fs zDICnH9+lI7skaG}{j6xd6gMt|85q4T1EaV>VPaf3?~DV`**(f($F(+AMPc3arX%z_ z209h#FoD%TNU*98mwSkG*zgUwg5l~8A?rES4i4lQ7|d=3rFd-}&RGC^bZ~^n6+-|A zttV})e88i$FDB~xdd!*L^x%h&2HAA7vh0zdh?7%65$_;f)`QvtiGk?x2peE7Sj+{B zxe&R$P&=jR7>I)CB$i`$rPcqaI*WQ@4<+PwTVKP<_yt!aqWKoZgh)oJZ8!HxhDw@2 znIz1h&gvQzI-!!4db+fcz=Hoe2~Uu~TbY=VKPLI5eV!jO=}uLSJ^;3UrCxui+&)Q= z^=FP*eHvJOh{ljOCzBwP>(B#Ua;vS5#4>p3ZK6wz-X#sNH+tXG)VyREh;>Y9=798G zr~!MLcP~QlqK{Yh25c+8_C#S=h-ys_^^$}pC+RM8Vrw>DMs+;3&%0-(D7_f+dLya@V0#BbdPzLk5^i4!dILe2yE%x;mUm-2xl^(mWcOXd2?G+foLKC zBHWb6Q)Es~_^B)&s$O?AakmXys^;;QJ2ZQIWk_dmG@U7_xoh3rxWr9nZG8i7(apsy~l#4S1)yOPler&hb=j zb2;v5b5-H@d`~k!fxezl+@J4Sx-ph>;yM=r-aX$$noRMlFyV|<6F z>P!Fm9)d`hwoHhOD{xiqo$Ga1t!d~z(KJ0_)%_$ zr$g%A)7FGCLAYvG@b?;*=3-pA6bqgEvmzGqBPf+Pt23il@{~Xu=CB(gvTW`?!3mbjnZ`2KaU-&P| zL|^THkSBzfz?mUY4zo&|Azv4kT|!<}>l^x5EpDN58PVp5E0b8P=9XQD zwd~9(-)yzGa$gQZRj#U+?e48{?LjDx$lURa*dxvpZ@Y8%K=%9faJPJxZ@uGfd7mJA zttxi@zIgD%(eB92+0{!oYLC0EJHw$-x-d$8+DMX$CLFExJC$xe{Stp@M$p8r&apUK zzuzq-)vH&FSVw{;E9l9lm&{(%XOG?Isx6LtAm%MH(@+k%mU2t&iOqtF(7M163HW?T zf`WHHZ^2W`uF;HqR*zVEfrc_h{CU7N6QcAh8-3g?xuxaPeCzE5x}G@? zbbSQ67J{x#ndpkX1g#iv=)1|LI&PMIo2(w{4@g>WOXF|l%WM@rO&>UxJjMidb$-Cs zhxihI-WzXGxv=Y#+}2KhN<o!dxgx>Hu9$$~s8kgLR51T}mASZ!BJyn|mZ?I_kPYP-jH8c#jnhcW)X-;ON z&c$K;l2p?tHb-&$158~gdNa>1aoAfdN@weSq03sfqfoGiOC!VgOO2wgtX`Qxxy|#y zm7&_he#A;Y9GfE?+P9p`5FWcQIfNe~=-5q(?6;4x7Q}=K65Wndz3%qb|EAmeW@Aqk zp7a_`9_yo;$X{d%v4;XI=|rfx#^H{79G>_2CmH3l*Wu>SKw{6yaqd_~W3!f`KX8PG zQD%bWzZ>y;4I7igJymZwn!3Qr_`{#P`&G%so#b|b=uXnh-^4G)BfV)zbWSE(FD30N!qeZdu86gg9@|bE$x2J(uFZ0a3aC`?=&w%%!6bI+q5e=2GFVParsezi^=m}>=IevyU?T0mq*)&Hgvy+Tzx0~JGOkqpo zY_?QWGP|ZKjVXm^r>OAsI;im81xYHL%IT$ULMHE_Na>m-b;&f6ZU1NTAbm3|>0A32 z9ZeOUDk87|gva_YofVy1GCPA2ktvN4?Xek=*ByT98yFD+d?q8B3nS76zKs!W{C~@c zbekEB$eYfHexMoA#{ZrX+4W{JBGE<&Bl2c4qP0f2f=NS=3Mn0M9&nWvtrje&Ork}5Mv9Z4uK>zf%nGWdHDYOs6I!i<4JlY9BP z)gl+DmpQ*+<(1_@=D|$W&Yk!83p$!)pAv6d+v@~x)nAQ2HwWC1#Fp!^BnpJIH$M`O9?tTK|I6|LNx9d4EZy+F`TXvY9 zrY$CpX`*9->mRjet^Z{e=uQZ5>0EJrV8{Or!Y?Z7hVb*R{|}(NfgI*8-RjcTms)Ce zb?$80mAiC(^Se1nAvw+N@3UL?q7-9!e`RhjgoY*ki`}^!-H^aWF)1x=^;NC)Ii?`i z;(z^)qiF>Az(w3kL{QO}V4OSYL>8JwFNi)Rq3}w?VKO>?3~AJ@m2mCc;Lg$GMhAMW zoluY!<6vlmdXr?{IQ>ny*=W0w#svj(SM9ZYmGd+?8cP1>#@>>XMSP8`a{Rd`HWRl^ zP&dXgI>ZnN9)>i@?|7@GZ{GOInn8Ji9q5#Z3;Zt$i}aG)%JGQRwW%W8UvOeGBFA0L zCQUDZhp1~y4^Fv;3=Dr3XUCYFgApx*8!9MHuu z29e2oY6WBDF1b=eQsG@CaPwayV^&L)SMQjX=bgOckM4U0;^03NUA7ji!@wt6yZiV}C>4@gyKZWy zvl42jrcgJn9SPr}wR<)sTDyE}dSmPfgH&jg+loYe=Mc5cdV1PKrgeeO(=>P>xiQw4 ztRCx2clgJRZB6HbAY^caN$_r~^1{#7k;*Pkkn0ZrMbIAg^M@6!g7DE7cGKgztvD!5 z)8OsU8kwe0rUtLHOB8qpkuRhn60M9@;H@|MPg{Zakd4WE55(lb6?g~1WJ>EfMl80s z?sE6r;trn+5{(uwp~2Jm{9_7}hgdD~mLwv-0M3L`r#m9UPYEKU%hNP^$B^(XMB9ff zJrL~|bU8t^njnQ}wRX$UBdV$W2PpGQ*Hg+on}!Zh<_S@}Y$&JI=<;Ny9H7e!G-jys zobGg09vfZFltswY8a-VF0iQA`M{Dy=r;7<~o}fr;^W=~hF-S_OUNgX3Aqgn*^z5{0 zC#}D(s(sSk;Jya*c>I;Aw+0QHOuZLvHQtq|@n$DxTvCm9M!FiWXAE^$7(w%4@lJs(v#e)`fJK^rggDIV!&`GzA@@)}G6d4?eH(jSE zb!wfSJh;Ol&Ct*;$ZMKTPhQzJ6V*&q5Aqgmhui#n(edn zOr4GWWJ0I6znUSu;Hu;h4w{o3!VH~WAL*^AGP4rhPU!T$Q@7J}dXnGh^aL2A)04D} zt+eR$H1Qctpqevb^cQcqGCDk+w+m#&E2nNsR46cnVCdb&ZQ z)RS^t5e2nqrJh|sO1+GFGxySE)Fz@&MPe_F>v@_|?;Ghss#T{=OVPupXFw0WE0gq) z^OGb!$XZ)1N=O3R8ieQ+Wy9-dBLb z%EFDZsP|l)m2;YBHCwmcEd-KMmWS;Gy|>*IcwNN&$w56o8X8WqnxxAIPAZ$tM}HxW zh%E6RgapYNZPd*sUjw`*rrVErPHFnsSE0k)b!UQ$RitsTdmz^yxS039=VDi#uDMu{ zX1)^_yM8M2zLt8lu75FTwVGu1z{C1$?OUKRx{-OSk^Q2c$*97fNz7z(C{Ys$b>m*7 zBw3fO<~uTYQ)5HJK+DsJl20V1K%?mR?zdZkSK&JY@@Vj49*l-h6e`xmA&l*`ddKPv z-_$4^>NH!uR~t%b_XKG5r@9n5UOEnPTzz?x92>3{atw%g9Xf)YiB|6IW(>7*ZI*WFh``kOX5x%tqs@j<=L}oUFkw}Mn zCzfDG=8VBs?F9}p@=~Gt+vL>9Uw#@R-#jZh@(X@sMn1`fyxsMB)0QQ$bsPYQvaU|D zpsy5(lf55l+`=;YNRR8}=rFS@7&Ap^Fc0)=p(;I0C zy^MPNVy%3+Ec$zTo%*~J<|Ut(na|^+zsmT$%Km(<`8+22vy9I_w?ChwKRcQ*yUTK8 zEPf^NSG2}y!I9EfL@TaPFzyjx=ov?6ybLZ=q;~dgGv8AWPKlhY) zPfv-rNMbkJJST>E#)Kb>HXR@2JTZrqQpOOUfgQAY`=Yise1}7?~s;gf4Mvc-J^b$ zpI4%!_&JH6I91*&1{{y3L6>PqSgMRwksJ#R$k4$%-61X~I^`gk!9_7nzZ7G_1qLzBR@ zr=?e5wr=NeDioj3)Aie6{kGqKe4@P^X>S&mk|#fN42n;21&#MoA1|X25zSk6_VVWD z?U!iA`7ia)$y!=nQF6<;Tcit~Lg^Y1dI}fKcgk)haI{89FX{ep4Xxm(K=8vV=_zch zcA}iqosN6y4uKm6NS@D;_RgbOKM7n z#d_Db@rF#ef}d8-2JqRMS{Ppw+?S0$wbg#kk!MlK7sTb}X@0%dz@<4@!Q$v8M+^EJ zRbv}F+_5|>k*mICXF+|-u7di0YrOI7Rs!7yU4W%42xjMShsB%Nl2Sg(d7J#|vxsz` zH~3Z;u{Gx3rvohYcC08+Prxi3E4r+H_|)%@J62%Pd=bKjW;nucaI@_>M#h7TEZO9A z<{SLfE~8TTRyIvVJv1e8E%p-d;iY=%zX{|{-1Z4LWKU4Uaxd0_6SNO@WSWx`iaH+w z-ua!O+v6|N&JT9%#6ECys zc+uZb%E6wu%V<@bniKZU@%%rJ6!P1?O6IzHf4Yw5#x-&3ULJ{d!U5GG9l=RMs@o=L zb`U$XW6`7`qGaasi;*P4NjP?yz|c-82+qPBm@v~I)%eo2KpR{3w2by2v$Ta69N8s> zY#KK2X({mM2G{59X~~mcD3ueg;QB&Ws9k=YmITU|0ny9dSM>JC(K4C!AV|7;^Ly;t zc8)eF_?e8(Ey#*)A!DLn9L4?V`;uo(`MN!=qy5J&-ReekZ+uSxXy4N+0IbW~(<%V0 zE7;R20IUOmZSw2vM^G!e5S>pInSQ3+NhlXV#a;TcN<8qInim|Z%4Q^4T$IJ0s5<`! zD=6;|SI>ee_M4;C$LC8P2k9zXXCJ_0Y?In~6f3|slpsZot<^HET&Eqk1mF1TB{!B# zFcYo%I1JDC_(wK3kmU}dNg=ehbZgfMeoUT zyBYxiv5TsdKD2S1^(Au6uYV_glHz~>fRi+U1p?qq9?RF6z$fa>Cdy&8v{u*8{XXhl z#$3waioYTD4wq!CB#XpU5jO#uR47CATCA60J5GXg`guAc4`+5QxsaULqGMjr9hLt& zFNw+r2tNlZyAr6JOJ6ml59>S{mCN;`jmo=W!Wxyu^}*I^P#^guE z3P^JvM|}x&E(4wQ;RHIJN(^z*(P?BZ?wLR}m_X;9^4zXonr@?0CV)X_Em!d-(fKvs z+p;t|2LfagowtS3&PktKo78XPLhUs4+fBSC@NDa5sEp#Wk+;S~6;#-PMAME<})7*hdO(0>F4>TVPu?8d&=hhg{@cvqotOTk-e z;GKPL5?+I8f%knbrL{3#tbR@o8`F>K$L{dn-y^)a-vM4d+r={l%rZhl)I{BY7Chq@ z2zka%M#Kf{)*JDn&(GoXvTg1F<@+8m61p^YA$RDDzN#G(+R%B%H?A1@h%y)uRbHVNb`4n2v-{5LUvRyr|@; zc+sywO|UD+ai@KmvZnX93!!&NoqK!|@$p2-$dRR!)#??lgAnvunar0F zM-R1a_wPN{^8*BmZ>s0CAf?sUB5hEvo>;TC7Y5 zJ623DOt>HaNWw3~W;s^W7Os|?zi@(VI9g9db!?Un-_}dA69$P&y1-e-x85gZ$L#oWRbJsGJ+ z*4P=0>WZdYho!jo$$R;_`zVxti%TXdIrVAs>zi^2NV_d@69IRm3{Uh*t0kn=u?W{XT$ zE&T1%L-cshXvHxx7FGQie&jkBSrojQHCX0HH4{e6_)h^lI zQsVS)=*b<}Ox!xKsDsY|(OC7pbMfe1C$=>$c8C;gR_rKcbG(sj=wSF_{6_=$```_qbaC>xd{TS*U><@t(pDR{%iG?0fy4M zJRbd8@X~|N;(`pHMepfGRQpbDi=h3^`W0@kQjg03OrHMF?Arr=t8V%kqtR1HB!wLgdy!4Aj8~^cgRjjq9v^vmC+^ zE{T&;E{R{C%{??X6>IADWF?&LS$b-n;-mO*e?cHPx4^jt&Mj~rxtu`YE?GH6Mno8K znOo5peY{y?GQadIdlrOBnsv&PC1uPz5m2JsG|8Rm-tZZ+!D-sTXOXgnr#TyWL5d)% zo62XyIc}H zi92eMwAdW|m`dYCN7o8xyxuejH6X({O;bMWC3TO)rZvv7tI3XktKlLmQp0m3H@3Mm zOI@Y|i3nR@4SEO2V!0C07+4?miJxc8|FkT#Ra*n1hZOpIO*CM){B*x*%s-We0C;Mu zmFGP&;&#Rh$QXM7rgyYs^(UifyA03Xh+7;|H%LX5d+LAZ$&_uzL>l_R%GKo7Z}0ptrwFS0uQdq zk#GFV8^|ZW5;;8&HBqbMLH4RLZ-s{pQ`glbdC2k9r~{43HUsX5^_n%{uSdwP>xHo_ zlCAhG%3d>B_Ow*l-BWugJ2zE!v0YZ{wUC0k#dCIIvZkdB3B0Lep{r+?vUOE&O!}I0 z9gfqWZ5>`S2m0VzQ9)sUf*GsjWC)kV4ws0P162W`;gBQ=%O07JdS$faxJ#-CSD0F# zrdAQ-w2&@w%+S76OQ3u$sq0I3jPC6_!8lCASl%7RKX!w0PzH>%C7zoNBQsRP*l~6m zjEDXgFs8+Ow>KApM;(1~m)a$JM$M-jp># zqkrNL^MHGi)C>{`Zu`;yq3u1Onp(cN(a@2WL`4)E3Mh&w3RXlDYC^X+RBYH4A=nET zkP=W-RIFe_K}A%MVj+Mi*eEt^2x1AMf~eT#{pOrQ%f0vi)_d#wvQ}o#%$}Juv#0Ob zvxhW+Te2;Qq;En8GWK9rP>$V$Y9$TQfRRd+^6@-h$ozc}DP31((HfHHQY=s?Xu2z# z=t!Eo|58j&ZnA|3>7tLOss||z5l9df%4cz#7u`zs8=*-%SCi>GASi_tagiFylqQvw zD7AV>I$7Y7O2p8y1Xb6Pk49cQpqEK>_oX_^F?Yh8pcYRpe76$cMxJleJ_5sHwBA5FzXGo|;35 znm@b&YvIpDprfdgYCS$)vpNHBAyHxl`XVV%yNagvV>D%bZFq?H9$-&Z~Z!6H{+o+0W@jb!Z>WB(uhZl)KBU_fu<2@($`hpHT?Hw=lH*ue{A0LL`Kwbl#Nzcix~v3)y=t zgh(f;Q#yOmjoG^nK_fbQE+_}q2?#F97n**@LAdPhzNT!({iDG?$7~87TU)T!;yaPS zn6hJNmfE4LWPeHRp`hSW5NU1Mm5kgYU$Ff3sd+Jjv{>0b)HA7Z7z84{E*Ei3`AOa#zyR*=|o@fj>SV-Boii zOevM2g|-*hKqnF875q8ZmHeyKuPce56+p=kT9E~&pP`=>yhTYqL%zr@q)cS_Fmo?O zHazZ!MN~s}z(`GuOx`z?HT&bH*>yW`kgHWNJpD_1k{=Y-U|wg=UoZ!>7+>kPLgxIy zLy4a{Qv(7u2Dl8j{DmTo zLs7wAZp4UGw!Hy95R_z#rDW6qNvc)J=+Q)ue-5HE{?VnqoajD6>w@{Mh-w5w6)i8{$U9-KJS({Hi?^ zzq^&+e%epcrpdR;170oNtKp_~s}cRqE@46r(Ot@gI?5Jq2_HP%G1zXlagivxn6g}y zFei~`OZo5{0~R*%1c#gIuPmV{Sw}VXIcgNcey;I#5?xsr+vY>U`vU5a5E@E)u;_(I z87@Uek(YjM$-v5mDr*eWYBHG(Rx=&x5S-0eNP<<;#H4RMJ~10Co}Hx5Me^ek`T+}R zeIwg+uxl`asz=&tR*ir^rhR{1;2<;6fbD4~>d3K*yx*Nkcb|OO0&oecE6IM*Ts9fY zU~)CdoaL{Pj!w#|`U*qlimXY@Wexyh10p)@!=hIINXA}rm!EDEOnTxYl7^3Li3!SE zie1fW@prC&JLWruG8VprnNG3#n(PlB2_a`{vd^%End6H-4Wbovc|lez%?f4j`ynr3 zwkG6G4vs2To3Q*j*vk6II*){QFN)1Inw&l{c=E6(9nyd$(LSpKjdTUdln*&`BoQeG zjyWiZ_lKbFgx(r=Z17i+WuTfUx9rjp=wP9kR9Ig{&dS=EW{O#*_6s>JQ3? zP*=mSpP;Horu>yO9Z1E{*=kzn>&eFQn)V2qSTN9fVL2o=bfA%GL6-joajDW^PGZu> za6>|5@<@PtXnPdR=}?(CYk~ePcs7TZ&rcB)SpIu{az;1hXB}2r|B|0*61Oowje8Ec zvD`yAhn((9-r>FuHccCKFoHsBs7wpX9I;^pt ziUy*7@k%;`AF=`xG1Zu8@VVZf;k7{fU&oD%TyxE1e|XLU|XZ0s>S}$NUyd#6s|Tyav6IAS-06ZyVLH%1t5G7aKLGa#zK}C5W8B zp|oPng<^bAFa|j@6>s1u`aZQAHVW+YR$l5Z&U3vqz}=s*DgQtc*uLQnVXp zP+f`qnp43KHwE8A1>epOcV!`b8-!CqF>Br1fe85$PA&-_)O3o?~+z%fHsiVT-MGA#_&ssDmv&Xb5)WGBRx1SRyjmF zTJTNL8eyaW&TOJktxwAUy#IXtwBv*<<}o$rl5@~zc3#(@y?oJ%Qg?AdsoYX z=8}R!@6=P#c*3l~HF;>B1Zxn-R?UT+4&EFhQA)mem~yD?98sWlgXwS+(9~VksRXE~ zvfel|kkle+=xD>~VhaXZ8`-6y=#Lbpfw0!xn^|jzfgv0MSFqvl$R_=OtZn4HVNE-o z8Pf;@jhgqH#%T22);hd+GNqH04lmLV$tXzp(@+~pba=(V3+xPdm{Yrx=I5@7;;txQ zfijS(Vr&EhmR1}IPFss7KrldvC}o;nL~R#j51?UH2M-jJ7h|v|2a~ss=J8=9HXwI0 z&sQ`wK;3ID0k%*Di;)q>)AXH9YOq2eNgLQKSyWhwv$E_W0@Le(6!Z%+sSa22*9Np4 zZmUemLaERv?5)!phDvyhltxm;9wu9b$fz~ufvukOcuuArG5+0gR4#++d%_?nlaYI+ zjd{bQL|xs(f^0kv%`y;%{5IhUdD1R3KqdTmwopCB$75g6=;y1~=v%u9&Xw3`+^kB# zG}Gig8s2`Uq=(HWvRZnLR~K=?5foRaE)1$yI{>OI{EVgdNPH{<3slJK%Pjlm)1|#v z4go^Jw<3WdL^zsaAw8&AMhzg9$ttFZP1*>~7QES;uqhT>KV@U&V+Rv0r`~LnQvA!k z*?#vdy#?%zQg8OJuA>nLzA|t2crzu+KEUi(9pVH9({YMEv)_F7W>5Oy%^r})-ZFsl zeX83VVe8nQ#%Jj`gNNeWogGw)?v0d;!{7o3RA8vW2HwR`yCZzqnB}6}aBHul;YWPD3Lobv zW8I}Y5o~`1YM%qcIE)VQ1~ISU{{Vk{bva0mCO$dWoBB207e)l#)l>vel>iD(EanApC2Qg-=MZ0QO?o$^Rw(@8D<5qqA)`nSxEY5+)qNaCD!2A~~J> z8req%IfF05jkHktPrK6bJ#&FChhHxKx0&=&uqk)(F9VJgDjR>`n+t^+%BGU`)XktE z=N*#N?MjkIb3&Q?9$0#_f0|jLkZ3hut3H9{?H%Rc4Y|{@#d5@nOZ$nt5X6#7fY(B@d?!2mM-CZR7 zr)up;Ks4eGU?}H(_Plv5+YhZ99TP{F3aQJj%~?PW#AZ6u(5xrUDX1@PL-f>X_Hc*hT>4d+!)egIU4ij)dFIVbN& zQ$0uF!@BvDzo$wOfz^|X@_x;@34h}v>WdsR8U5u?d7md2;G4zQUREU-AB88em}L@d z^_1egr&C@6mQeP4Q{Lr0o%}lQsplIIx$8wL1{M z`3M$js6(tuWI;9&v8t8X-FYp)M8<0D(#X0UTf1C@gWUw`R>|HATueJMC6hBI$dbLb zK+_IW`G`9u7-R(Wz77W^&xr?aR^y~tVWnz=?u0=IC&mWPq9i=qD1e^{=>c3ewxI;T z_*iQZ!7p}Zg@zw2%CKk>iy|yq#G(LQuW0G~;C!5AXTEEPTWA?9FL)Sciwvz<1}I-A zYZebcn{Sov&k4LqE+AH`LlGi0Y-$z-R`3E)j4Ga}b6}Np6T)eVfAIXZ4-Ga{olS~QrFQR5gX&@yU`vT3xe8lRR;Q>@Q6AX!%+GHZ0s5T9O4$c8Hu zPKL|{z(QJ|J6dEBehcf%fC>e+eGV;amJeAol3>1a&=Ud_>vKtZ`3gh?4e817!Wb<) zS4A{cVtvETtZP?gLEAF|x?|t?5+%jDhtQS7Pe(EFh5?~&lV}}?Kt$_E_B0yvsY%3q ziH{adSrEFNAE6XQS)@oP+XLz&L>-CtLyD?xK_@RzRRhsyKs85OUQTyS*AKmemlE1$AXM?{kI|(cuvZ6AU{$;VO z1G=Gd+K^JpT#+c%W=Ifqvh;!QUJj|490>2x6_a0|!^rIA&@z1dpdt{kK(RiD5VAm_ zK8GS?6`D+ow~(kU19RewWg%1qd9e3!0Ia&0k-kdbd# zC^7=U3{c)qfIL7gKCU~mwHw>+HPUvkXA%Q{wTYO|R3 zE9)e=^{v=2-|(~MT_T1;&Qij&uOd4v$qQajXo<9cij4%1h+L}iHJ{OTWf8hwwFd1_ zB#2Vl4ncUn<`ZJ4+6NqE#39KxCRr32L20Q-k8gENq-9M!{M+$0Uy$ZeO*M~dZ4x-J zf@F?qA)T2PqC-I?lK*OUwVL=AuUe7*5O0`u!W#vaXk>jZPc#&KII;4)G}HSM6v*YL(aN0l2GRSFwx<~OOCzk!vcYQ)uKRHh2A?24>Wz`8xJ2k_aQ}a~8K{ zo8MtmD>+~kKt3OMjpU2uQ*vz_p%IofbipK#VU3`QoGERW(D}nbvE6!I6v(JJg z(M$nqUqBKvp_&PJX3Gi72gLWHTcbJ{T(Zx}e+aYP5{FA*HQ=jw&V%|Uc$k3GmrEwh zC&A6BubaTc#!2=Sn}zBw;Bp#(y-tazQ6-TLZmdZGcq27LWX($Uqk(ObVpI+ypcxTb zfI&_%Nd5<%@*e)b(CIe5%g~7;)8{5K2|g3m+&oz(vIyUQkf{QALMDY4WCE!fG70`k zCN36(vfF1VGVwtsYwBBsPOyMJxF!WcHGnq~?p3Y}yl&D7rhH_gb>0zaH#Xy#KFL#WD9Cu12qv#J9b52?f`A4blfL&Slw zK2=!>ovQYD#SZEU*hj*Yp(>ukq8>mr%tu>OhFqBz)+qff3fKzd?JWa=W)UT9MoMUa z#qkitH&Pv`Y7c%ilIsm8fDr)2li^Ium8f)p3-?@9bO6QHN-$YiB7~{@h4>`34jzVU zS41w4pV1iwp_`&Tv59e+17i!pUy;KhRhsbNxf?tM}jR?G_!d#8MYvf;*p z2kP9|7o`-&Fkh(vOHAghoI`FmL@r3cpjeJzC~YYQ#nO_-&fXnzh?~g3cmu?qZ~~_Q zv9L^!Wt-H2Ln%Y0V9XoX1EUd8o5b^KaI@Y%mg&5%XW=-3Ay*|@!Jo|0M6gl+Or@{p=+158M3!DXz*no-7wh3ci`L}xO{ zOhrOJ?M}wFnUy~pTp|xpV;kN{FyXUJ{GbuPlDc_-wFPyi#FY75^F&a}?z5Uq2Ia+# zd_K=9GjxhWD17>>>g&ozkvZCWe&4{M2a10zlx!T zeF#khXVeEn14~RCpn<}v)-=GU$`~3vYC(gQstgURVQHelco2lpU=?VfMrbhYB~1e| zus6{FgjiSJLcqC8q9g6`h3G5^l_L0jtWKqxoD!8&&YA`DAj693+f{)>pe2JsId{%m4f2^)Ko%1i*XQB`r}-zst_&#%9x2+i3{ z&NbQM8dwotksp4I-xGS_KC&m(v+ZlJ_`SZi4VGFpRaX&$EC+mK;V2zB><-K(=j}+A zUbhDRxQo$igC>}Z*AYIPglCqtct8LJttGnP!0k5o7MHWV`I@*H6lAm&!2*9MVdU80 zz=X~uF4JNRj}buff^LXZfX z(y%cQDX`~+d8pGsi(fD0h_s0#Ya8%l0p7c;mnt$|J83UPcquSm+i5RlcqubppfZW5 z4li}a3sVpA;-EX?uY`J0%Pv>`R%F3f7ngGe5@D4_M4Rk#B?h713ZVhLTns|F6+%-< z%J?h7AE}eHvSnI~rvf}>FR1aQ2(P|VpArkTIG7e1?dq56DIJe77#k{(<{l8!Z+X>O2OO+<5Q7wbC`rfvuF_=_ug&1m|!+kIs!=t zL70b!jO^VlO7MC&M^Z>Gem3YvgU>^fuji|C@DdI?(?Shab=uknR%P0%0jnZy)r3`n zwrWWf#N|pNbF$#+P7EEg=rIulYO=gAmUJiSX~QtpscuK?I>fFGss$Q0#6rZJ5JXMF z_kg@RD9Oil3{|qbfs{>ajRDlT707_Df^`Lv|EJb*Cd7y7GL$R_)nzE&7H;>8bR+qH zk)Kr^Wxcu)*$lFbs@9Zsq8vTwE@pzxh_MKVx-iEZb)+_%m_V*!8YFravaysM&H;CY zJno~t5r<1;5e90aS(uSwhMu~p^*u%sPm)782y0CQXi3TdI1F$- z;7E*K0f+@F57r>sN_2|jt~wm_-u2DVroC%-v;vG(2wtr#gY+knuhOLb=#(&SU`xyJ zKCNrW6fCYBEpsI{EO(hZz+}F|h=L)D7S5zD@W^yXpjs+>;9mt6Yzk;-02k|*W+B%3 z<^k3R?}PLrJG<<)h`pS35Pva53Ug4@DuU3wa3` zG+-jPjMjn53iT~0A&`N#+3c+Rz^cC(a`|Bzxs6GHHWY!VB z2uZ@x)q)hE-2k;g6|1o}=mNA3kC1c}I%TEe|BOdx1q~>>%7CylYd868VEw8}9gXKw zJ`Icy**fI%)4!u$iO^K`J9BToVG-WcE<$h=fFMbwQ$B6+S}~d%9w?TIRMW}U3%Z_j z8;rv{f%|0~L77`AGm0{IP-Zk`?xxHb%G^hp2PpFpWyVqF5z0J9nemi)k}?x1^E729 zQD!n_o~O(d%DhOK>6DpCnU^W^DrHJ3Glw#-QRa2Z%%{wolzE#n?^0$FWtLFpead`D znWdEZgfgE|W*KFcQ|3#`d_|dWC{sq6?RB#L(9dg#8(3-!(!o_Tqj9;3{|$!@uMzK za#pYBE3rkm+R2A1_F5OEd>0FRq+nWA$o(G>xx5X2h1o}R%`k<=vpe{7CO#lV7>4&AoXpk}k#^ zB;{&#ug*`;n;-a+RqGnLpH&Mzd(bjZy!~rjB#4a}OdVh2P%=zaWT?P*swUYHQE)O} zb%dM&36_n^yl4*IkUgkl`{YBk!sjvebW&roG!$6Z!d%twGoQU&9T6ZEQbQy|AROTj z@fH7DkaGcil!M``YdXnTHP@Su)kkk{dsJUnN{-P*Nm`x6zMPL1M{ureyL_xts@1R> zQKFs?y#~!M^1o(!;CX5^RH67ghrPuIUx3YErIWz4*pDrg+@3>K*ZTYL)BqfmmIcxZ>DobcI=Bbitw@vyB1+okE&_R7DYoMtJ#MvBveJ)$n4htWFxT2Q$Q=V+D zri*E|E3W!vSE)e|lvH^ieyxj@b}^V0EA^^!;L}`xz?HN62&wp}XAO=ZFq>*7efRV3_k7|EHrF>GhT7kHVL&{K} z&4z3exq(h-4jewu#jTBOSJPaE=pt~B6{@vSL_3Q19Cn{JR2n3o<$}N}hQ2-^oEJfO z5Co5=uLnGN#M7L38i1DO@G&4h95zQw!#@dL$O%Q>MuO_Mq^APOD#;fiXDvle9I10O z(0et}i3uSwoFK<&_%|&I#Ma4I6j+KIOmUST^Bo2dsiHRqtj{Q|B4l<&s${V8|3pk7 z1}D<}af`mXE#raZ7RIuvBpP1OR5+>Q{|3vg0)Hb+Gi3R^EWcVANDdh!F?^1gldtPv zgWC+KVICfLO@pl8vixQw-%pJ~w&#gdNwn%$5Rm-G0N7k!jC2_C)O~x_XRC?Ydq;&U zj8N9_?}yu2!iIeK`G2H|q~zb7$YZi;ycUi3{{-=|+|E*QX+v%l`ir7yC~n>etdPPq zY+uy{R(2Zd{w}a$hlmRBhq^d^M`G>h-ybd3fROkONZg(Ih-iOf{LVnD6ZA)*)vCqB z+4Pra;gB(3z%IbPlm5Eg0N<*7RLR&EQ82E*J2H;bHhWUC_rgm7%ukrv@R$D0$3raC zcM}bGXEPD``@Ub|3)p8M|sI6!mCC7 zG}fN0mu%#JV46#%FP~g9s$uDW7WkDM%6>JtTBLzg=~uO+EI3_F7?vD{Qsdbety1=1 z(F)`+3TTi839T9=U0~`kiOxhSdL^zNwD(52r0fYVj>`3WawoAV0&)#1sH6SU z8Zm09Kvdgrq-}H5bN`6inCPBX80wUP$wzAhx)IYX=e0(V9V{Kgr4@qYukj;;_6q4fVt|!{z}l`I=>~oSbUAwI3)bt92fn|aL^)30~8Fj{7U0!$ws_p{|xZ$nAUe!E<$tbOZiXC zzW>Ao{3pg+4&zF$5MoSOxv*u%$jB?31FwG@^33TNcJeWJvVs2=qp7myylP344CXWk z-!5_Z4@Aa)A>vvgkXxn{$+#TbOx|Yym{$HR$Udo6LM;%ZS|I|+97?(;#;nG!Abar7 zu0Y7PzLM@&%uN2z(X0~pAe%Euo#)dOz1bSkJT#qag`j4CkZu3J`Ubc1Z8?lBZiS&F z1nBRMZG{N9LV7)9kf{)|!9f0_6|{(~+Z34^=9u}PjDOHPKo?UEQQ3qbeYwTL;O2iJ z&NU(Al8X5+#HJ<$`34#SPZsU{FU`3{mvxbg(%b_RA?wv#VNLCM=f8WeZ{^*x0%rdw zrU<$Sn!nIEiJ*?t{tI#FzYxJq2tq5A9J<7CVY-bhRFj;(xthx)^JqetH_jh4i4^F? zrbsJrSXV4MMMQ+L`QF(7$FQxJyUj(zeBWwSOEtjU+oZw$-~7cahtLRqi41J-L%Wq3 zSj+gAG4M76GZ`4lz{WiKE~4XmF|g6!iwWn(z)=hw#K2w*?7%=p2Kq3tF`bW#>GTzs z(6BuNjTktLfo=?(!9ZUIZf0N<1LGKYo`F{xc$8VrZ5h~$fuk6xzKX`1 zGx;oSgez(PYYa?b-~k2(G0>NRlNmUQfrA*>i-GMKsLa4G{&f1~47|-iDFc%k7{kEL z3|zv%$qck(U_S=7XW(_F92XgQgn?TbxSD}W80g8s_e}om7`HwHwHWx9X(uuUmNM`< z12Y+Tnt=xx_?t=hr-&|hM~3b)#$C$5n+!~5U?Nl0RyKp za1jFo7`U5(js4x6sgFkYW~QHs7`T9eFPG8fEn(nw2BtGGnSpT(j9_2@1AQ4dmw^)* zXve@o3^ZV%76a><@4sT;eFo+;Fq46a3_QfZtqg1|mnT!sWejwYD|hG-7A5l;a7~qh zjq#rCqWv4)jgZ6m-)3O?AO$L3lvss=%3?a5fW+46HO4RBpiG4ewWRUtaWs5yL8sFg zP97$|G%cMwsfe4fj3sq{q;jP4^Yst>pf>z7pOYS7`d^Ts&CJJ$#p>H@%&5`QwKncTDXLPMV=n?Y`XC zs$lu2q*Z@iuADgO{V_fyvi938W{2^@uF}39M`Bt6YWmh`vqrT zQR?|ge={$_++=txf0#@0P^C^c(|hpahc9(B>#(P)lS^esuV|r{mEyd7T6Mo6zUv)a2loY0JpOJQH%i60iKYW*X+qz|+7w#z#tqOV+ zlqOmo>#F6ZEZn`0mt3MX%6a;D#iI>n&JL$!v-0Og?%ZGWGBf)H$I|xd^R-7bd#*hg zQD3*SbJu&^qdF-6np4H=%W{Q%?iF!Q`N5q#Dj>n;;fiy0cWln=sNTuCaa-ea^iVDP z)I{5(eR4(Sd69jOxxSh)XlbRxmdk46wLcV|vf7Q^>uvnU%=8a#!=n#QHX8`C5X|Zyj!HcW}`CEmvM;4@H?x zmlE#Peyq5Hlh%jN8VNqUdUn!2@a69{^VEk}vTT-Uh2<4-_wGY}A5N&)c4X3o_~~Nz z&^9>*;oSJ2ujJATSh%KJ-hpE7$&x zZ@!jnlVeU1H)90Kr_{EB^=IgX2L}f`#NU5n@rPr&_MTk+72Q7>by-);6>np)zAMiv zjJzV+QCyhIzcAz6=m{q#f9dr|xS_{L75BY~e{u>O9VS}!dhH&#J=-Dq*uf9?pKf^9 zNu@s8!^B9KIJJ02(3XJVy^>s~sihZjV{}pe$DMqGQg2O4sY-G@Y;f&`#o)4+tW_J^ z`3)}QCM{HFnf7lxn{{;IAp84avn@(#xK6yfb(`Q;WG5Qb5mTf8Lh67a6=8v5<456!|eXKUm)1 zV{E|tB5v3zta&uPZsQ(`k=|!|gD|{_D+hzx7D` zxZn2Dn4uZVA12ra{B66GyQYx43kT-XX-S$h=cPAf1m1N|Kd^7{iKXq{aw01fB(5dg z;OQ(cb(Nj-qt6@)4(@+Oa6-S$!dnyL?|hZ3&%9~{_h7?1{*kTyJh%UvSpFq2@KKoI zE5`&cugf_fc4}sB)3!H0gZhC?{E*IEr7^CayH`pkxdo(6bnSmEbw){t%57tHG#n0e z6V&>(pm%J030WAD$g;x#IDxvdc3R-H#?L*&H^*Q`zg( zgx6NOdakj`$N%pAt^2;yB<`{P54#Ez*S~d^R3^_iE$`cP!jRhz7e|;(3Dvc_S?zRU zb4rq@LtOi!Js&NX&dC{I*|*oaB5p(i+M8LHj8z@=h1a&28xzX%+EaG=N1xqiw*_=+ za0oCwc=yzSQI!LBbQ9W6{Ni+?+HGyS$UqS*wL&<=;U0I-H2G%uDvxMt&g9MD$A7%} z(AZ%0xGle`FRCpZ$=~2~?X;p}Gra{XXF_y&X3@ zT)QRyXxux-arq&~6vsf*pBwsbtCGuqY3i$f+m4iQx8$LGTi@;dyLisVgcmbhQzkyT z5HqGUp;9iq(aXT$Q@-BgZXJQ}3x+-j?>)((Gav1ZjK64an{#U7%Dr?Pc8cDGjv4to z;I&-*MCp)zZ6}8~XBD{=y*Rgjcz}1gL#;E$vyZ4>?R?>Wm$}yI-Ob(i+t>RT&Rip1 zZk&3#e$7XhG~;KMuN40{tY_c-tRTGYv_eH;n=k&#l`q|Lo8IOY z^o*04jSG=5;>^Ps^8ALT%t?|9h{gPSN{959>Pa*=<#~JSgP^;QeXRE?KMSP zz1N=@(UD~oJnNV6NU78CzFwwB$7P+$+f>nmAI91;dwEWQ-#oeX*6{}?6CZw>Vv~7t z_1hXR@r?0;)e~=BH+2YH7+UFZI(_rh-@y{9Z(Pahko z_qFTLP?;RPj9mAWU4J~vHEa79`!NO?dKQYG174(+aF1Hi^xxn%tSC2gkK?xXw~q9= zup@QV^i3yB`{&xlA2}7TpyD*9!!8R>v`Gm!YB*`X)uWyrAJsV|b&F$4&6TdXgOZZ? zUOA#-pJHy>RFq$3?qBaKv!2U*o(pzQn3q?Pm{Yua+JH@>h9~^9rCnAZ|D1HhcHce| zyQ`CU$2w15yYkW79}5e+jf$AK+x6fS2Rpv#pZr5#dhru-Q~P%R!j<(OdUWD} zyCvLM9pv}=mimAry?avqxs2}53nq&m&%C+PY5Se8hx`0WUa48Sxis%Y!mtCM^3p?Q1G-WZU4PHuh7(rvxooxXElXtzCWointIUKCWkmi=^@ z?X~LIImhs=Bb++LbnIPTG>O%6{_c_GsWY>l$Uj|^Aw!3ebaZPcI<2A zFOtu;UF&Ris?0CbH2G~9&|qojQq$aD25J?DKkd8Q=H$0mK5YjT_!`UQ|8V!ak{)e3 zbIvueo-1hG7DOFia&7cTtl!>Xy`pk+(yK3{D(XrwpLHPPMJL}lzJlYHF(uqf7eT*u z4lbKV^$7TL+1({n?^18Cw(}E=4wV{p8enOEuxyd{ieKq}Ih$&o1|oij4eAr?rM`WZ z47u<)%k`4aoBp{)m$J$qWMm~=cj28spS1MiS>b~IL#Chj?UCkwy5GsGi39C+E;+kd z`IEY(OGH$~#sWzal#tTo6-gCsa!;kP9IsEPC52F`2`SXv8u1t0sqq#c3s_s{9WoHfq zX%bnZCVuzcanz0L8_T=cv)=kjhdc}aRbsO{^==L)>;(ggQbapH%cVD1Zv3i{!*?Do zdiZ#(nw!{de!&*b=Y#uBxy`g#Fv~sc;J`wMJqLw{9V#D;`!-0JqGT@$&@qa+IA=yw zR<@h){DFCCgYK2+%L0uCU$q!xlNs!#cJo8w!OvNXP3~uIvrSf8lh$j!wP3s4_XfW^ zzWL7MJjdOADy9!u;kInm&VZ%0i=6GkCLLWhTG2o!df%ccqZEp{k(TuM#nO}czOVFh z$TAvfwQg>xYLQ%e7v%OA^>X+Tx3g0EJ$123?z5?CU&wWS%<6zi?h&)?F3nk?Y#4B^ z&C}qEqkhb*cZnNOSu@W!)>`Gmr*BJRk2oeK^|_~az1Ev^YV0BH?=OU>({0VaE?7Mw zs_%Gd%lRwdP-W=q7VG6B>`#AZ3|4j98Mxo*(YM>7#dAi6<=gAIUhUM=>TT!Bc}2-# z7w@gS^3IkntKVFhmFkgSKUAU`Q z)aIevmSuHHrJBPy)pF(A7QAFePaQAEeShCs=v`dlcjCdkIoj;k_96A!#}_Yh-=HJ8 zvvElFey3=z+ZAuW+qv##m*=Uu_jVVb(Eokx#fImSr&TGLmcw0AZ+AF+r60eGL#FBR zimI8m7ap5Di5E!g#||6z{oe0lZaglykonZ|T8?>`dZbIt^n`C6&65)@EWD}v(L7st zx?=H+T;H>)Z8c&&5A7=A9zBNoQ|~q8M)r=fP2z>_M=x!8Gtj`vP~%kF9*gq-x*uJj zUA4?nq5rwK2v*93i7x3UU0T&4(^)zTOWi7i zq6$kETKrCP-=|fbT>Cp^F;yax^IshC}h1c(U@d@<+A6^o(?HymnS~ec-^5~ z_&C<1z2*ZWqt(2!9W5C)v>Ivu}(0gWnOcnv}O=ikGT89?99}d@Nd3VsPG2 zjVCuAyp#L>g6kgVH+-#diyzrX=WTIf_b+nzHrpOwGn!TIzSm~++z#qy2|SgkYikVs zU4oqFzWMom#o|DzM^Z_3xb2n+;cfa`#;n~u{nYI2Ke?_6&(*ZM3a9V+DVN^Si=zF1 zw7KqwR?l3`s(b6JHaB7Bs*NKBr*|&wsuHX%yCl~?SGi^Dzg3*>mS)%gT+*1Bw19D4 zLgH+&zk5f|pL;Bpe7kV=k^S-5slqB1+l>+2e1TC0>i+@gaWUixuXg6#K#$IxSar~A zz%I8sx%^0eoU%<1Gjq(i`)2>Tv*YKPVST-gs{f+cq^Ja|xB$24%X`~C^5826hHO7t_c}^& z;qi&rwu1&mn13=(t8vul9RH-~6>+Hg^xXK7qH1l>%b3LP)p)1k&xULm zyDYw=&ij}c13T9(cHVn->K+N4E4SW@lgt0>nU32RPtJ6U^e+}!rr)XmB!_<~=iYyv zedpzh*ABa*rrn91lz74I{&0oAi|T}1qO8x}eAAHLOK$z(@M_Sj_0hTtipJ`=Jk7tT=|OS+>st3-EzWeZNfLsoyzvO zIr*Zsi}q2a0S<9*)OWb>Q}4g`F)jPHf2Y$GYopPt1Mlz9t4X-}X|tF6jv)8QG*7Fl zxEr?%Hy>P50b9q~|-1n0Qj(zy){3OA#yC>gXf48CctZ%FI&MXLb4Y^{THszeyj};{kg0?pHc?@UzW3+aa)f>YN4npzi^+ zPpqFzxpw`UQ(`2b=&IN%fFMirOO<-{i^6pg>L;avLm|ZH$xlj|EC+hG(CjAH8^}*enJfofnxTGD8aNox zunBL#(xH5$w7)|SIXp}5dkS)H^AUbpUHabTo_US&*>06Jbc0YoDGeOF7Jq$UHASD3 zO8ARrS!;_1x=-n}sfc?n8uO`ke%8fU#p9MEi@68(qJ3n6QV-+^xz~{p`CX=e{lH zz9Z+pE9Wkhb7#waPb%lWF6X{0=boTF%-1yXsBLgN(`BFATUPPp%J=4LU8&-%IfB?; z!}d9>tSPfyG+KIWUdNc2jpgZ_lK46Za8N%f4IJp+(A%S_}arA(GXFF7~4WkY^a z%49ic$>H0`xgjJ?{iHN-kS>m`>)~;in~2+C3l;i|CKsm2PfD3A2Td9_yXDf;kax?4 zCu-NoPfD3A2cmn6{G^o0a%fA#CO2CS?;+Mq;zs5~|l%VGTe*AB^7igzMxh~ZiHouU&o2&=nUVHzu zx#L{4&yl2k4qKX3uX%ZODEN(4aOYgVZ<*gIa!m{O4!QWf=zKJN-(D`i>NgJVUg?Sb z=@xPxlRT*`YE87s?GkR>H&(#R;BDt1+D82v^H1(Flb@6_Sq|jh9r;NqljYE6YVDCD zy;E&2>zwW|>dV3XxktuMHlJ6_y?7Mq`>*@)Bx#?=@QH9ArrotsZU0^=EaVPyTdDhfkT_2R_YY)xl~DKrasPIA1AWcWX&Y_uV1qZv=eAeqtuQzN>wltM92- z%-wYr>yJI}++VoY-u3;9@%T96FBn?*`}7*ll|JCNYg6tAHoBdP#;;KqURFInMDwbB z=IN>H9`Cp@(ss}E!PnkBTYIoq)WJo|bX**gSvK#b8{YWoe5jeQqeFiVe}DFS9zUqZu&X889Q=}v-`**>-N}CY#zE74f2Q0W zU0%Ov?!zx`yL){r|1iXV#{6TS_1$~K*hFnrO&+B={Ktlvh@?Fs_uQ`f_H}%|Gcx)3 zkHwuFF4VducAI`DZT^CD3kr9>ng6&gKV;5cy}lo3Og(ITCO>-6M}FYE6?)D^T_#=X z{l3&{v<>f^hEw5Vqx<8&j)|8_QgHtA0rUVrb&E;PfvwLQDY;q>R{P$}I-Z`= zj=%l*-0PQbTAEu#&Gwm7Y2tKzzIIxG>YGQUMbQsVM6R>HkaBl==4s7PrQ|cLsw>y+ zxArND-8rs8y|R0$*~pU#u1C!sjSgB)JA8HL#D?;I0@u_$Uf>4(I~jwXpAj4%X~aKO znR>!_;CZ!q-kN`JmS+eLuQv5eiQIAg$)5$`&$=79L{#&=ZByfoPkc&Nj*7l)x4Xh- z_tCavUoJf{>SA#B<@SdU2y_qq43><(>bGgluaUy|8yhR!j3)ChJvh*%DrUP~JbPf` zZQt~fix%k*>i+Pv^PZ&kSNm+aG$eMzTK|39JM#BFdU{?x|F(ALX_(e{(Vu%oJnlT7!YkjS0)Z22~=Y^@{qrw36#IRDY!P%fUSHHV+2?{!|J z(v6pt^3p!{9QyMB)ED@*;yw>Nh0z#a$obKlGqt|l2H~@JOYL?9>&_h)Z`-BF?^pJS z_eY#l+eSRy9&)ibHsE&Q^gc41*xxqJ7S}@F_{nOX?^(FO?P{;#7cI_QZTm=}*Rw+^ zzir|U_Wjf2;)8E~a_M=hsQEdaUo40J2l<%YP;_YHG6~W%e)8CbZ3>sqa*i512d8cm$yLyB^ z#>WewpGMT_-^Q<2{kWxOo4M%Hq~4;5vtBJNUi0x(4_@TK?F-M(dN|4E^Ph=J{}gd| zg`@uz#6|5Ant6>lx8EjyS*O9T+->!?|I&YFa%hWlRMnx_@EJ8{V{TYqE8g|aIm-Of z-j^{W^q=f2b~$c5$mv3w<+_j4Di=?Q_p>f~ZR2`YI;Kaajc4RE_U_ODHtXC1+WGOv z1?J}Nj_)tMy``IQzoBTjld|1O@6n5OiesdLBRzkWiC+H7yA-oM@%?Qx$EzPY9jo^n zy;gDC@orNebmb=>IyCQe53gxAu5=#q@l89otCJ_cG|~!Wr(V4GLMyw$*TEG1R30#xDJI%%0A3Cv3Vni|pBIa;W%dq~S~xCqV{&%(()FooPPP}Y7$pHM032$A>Z*4^+JWk_S)sM8n$`4?Qokn z^kRp$&xh1WBHruI6vjkvf3SY!g5|w^Q%el3cRBBr3Qg~i&z*QjIdtNqTg~SwyShsq zgDd*+Q}f(=zt}zXUC&Nq6WXfVxgN^hbT7hpxph0;nI5AS?+_gEo$@_&pX(?;x%MT` zZ>+s_c&`44S$5ke4_&x$;c$kTKtFHaQ4^M0`?D4|H%;)c6uR%-Be$AskGJy)9VZ>OO-&W-zKmCMo7Q3H#5nsi=x zcxCtKAE$ZS^4m<2g*b8oGE2{xvWmFd*q}f7z4^8;I9DOhB(Ws>`DyoqTrQXRjvnh2 zue|Qo=a_z%_-&v11$PUy-|Bv{TUU0(q{FPEE|L3-xsslw{yG*7UD^HPunMdX*U;|r zE3bYpa1L=49o!4GyAXp}wUrC5c-cUDH+$x_wN`r(?L? zos=yHyzO=<^qpbQ?CKPM?|$^iUw^G1?5bO} z+^myZdeWc)qkQ#KJ)e9~IU1wq82&Wx?UTLT&h44<=lbHlEBO}epOr_W_!n0+>{OM&^4;5CnSmR<<56L*~7?|Gc!-G}q8|ESC9hant`F+cAmb2Z2r;%)6!iJTz+0L zz-IiX^`2@OTkntNo!a~G##&C$>GvF;a=pt^yFJMcHwva5dS+hL#&Y;n)b~&HSI8f1 zC@$k)csR>u?}pIpI?i1u7rlyzGyZwXE_MBlbGe4)8D6)ZT$!{o$uY>jWR!m4=A^=@ zYQfzN-|=>HN*1r1TtDo4uh5grJBA1o%id|#Y|E03(c3+Du)5TC+usK(w~W)7yUd(t zx9?GrAoR_#UORg4|CRKs7rRkq?|X539$;R%;UmUvNJ*|4YQ_!7qza`&Z;fZuo9@ z@wv{b3$9CFS@?ad{rzUYW32H>uN=#xTYoN#=k_0_Z@b5$oiYFeDML3QLY3 z80S3XbH=>8+=NW5|8Sn-)i08kAA3ZfFDS@`U!d3BB0=T3Yx=S$m9EeBhL2k}IJ(cx z)z0E1*vF5hnPZZjtGrTA8{M$m{8FsfD{)EMHg1a5pXcVT zM+LE|53}_Dj!l0wp}4-zCec5{S{Rle|3@BHiuCaybY zOnS9G>9OChnq{}^ZSx#9v!t#bwo7G}7m-H%_&Dq8e9wy+4mBjW9p3l z++xSI{o;x=>MLH~vQO2om=-JPV>#5w?#+TFW8I^l_xoEizb0e%b7TI9awYyYpYkai z0;~??`dNgAWfgO;4u-tjIen#yUj_S)ls|Mz`S`M}!l(qj@>$iszFU-RxBJ(Th)JOyf*q^4>C>KWZF691muKO!GS}mWy}tJ~Nc#5qWs0;%rk`VErp1f`hiZxW zjat`(FDjk3k4s#$sBx{J((DK_Ll+bT5fw{h7g&nIvhGriMl?oIqoT3JiXA&KnxIjG z9gPJwwrC7?G)5O}*nihG=NfiFbLV^i?&tNo|9GCG!)M>;I&IFJIWu$4nO(=bTTkCr z44Craev5_&wzta3w7WDW=xoE;)1mz-pZ{{)veED_(sp{U+Uxt+ze)M=Bzb)E-A~W@ z&Zyro`PY?a%4*b?HQxWT!7Kar-)qOUQBVEaF{;+kc%|>`n|aOK>}okcd0^6-RsrbL+M`Ha7UEU^n)i$|i8(n!~#vTz)=zVxzr&dGd$;Gk+TT zFehU{-`&YG7w){iBV_ZtN%`U1Y~3g9y4JA6`nsMgv)21J$;~O;@=crhn;-S`ncO{V z()k{T9&c578iqF&`fmzPAF+N?;aA(`u8Y50m_1-+u;#%x3&VcD^rC}D=K+0v=1rZ@ zeOu3BS>kCAw=Mj2pM5v{;#!t+MPCd~Y@7Dk%HWO4?${`_D5AgT9+>-njW6LfY6fQ`J)@9txas;@G|# zd%tmYeCXfy;YnZLwMUXl?e6$kM|J+b#g)!oe7-qrx^hlRJ6UsYfo$J5gw#Qzt z4EaHCw}LRA4r9C~UoE_mnNak@Cfl7hQ*RnVR*kNhQrvso4{N%`4={^d5mc~3H@?Za zn)jDJ_UPQ|_9g$XE%qH&WE1LJ!9dhzM8*jmmn8!3HuH(a|QcS!FJTj#;g@!A`L7EUcG&e+#( z(f;4!`JUtC!C5w|e!TirSpB3{`gd6s?tYVP`rKS|UOi%Zf6JcV&+6^H=-82FkN4+i zZ2a}%clweaeexC!*>Pv4Mfcn}7UB>fERYj57v?T;2(5s$g4DG(^H*^(TpFk1v|Iw$ zos&k>Ir4Gjk~lr*$fbi-0~*aGb2?59pJKr$3O?yXdm`5r{Mg0@=2(jV9AD36LWz5(M53S3Ni;^^+G=nsobO~u5 zX#wdG(qhspq@|=4q-(aYe9Wwv%1LcVTaqeB+mqUpI*_`Mx{~^m29O4khLVPpMv#so zRg%V##*-$JYDrT_Ge{?pW|2-M%_jYZG>3En=@QcAq`9Q)Nb^XylID}{A}t`@M_Nc) zOj<%(N?JiGugB_JLF!5xL7G9jgtUP43Mp5g;*$oD#*^ld=93nZ9w99zEg>~)K*yg{ zLFz&pN~$GYLRv^#N@^o#>9~?ANwY}{NJ~jA8nW0-8g-25Az87Y3XM9Qg@Vs9>O^&ln)3h; zp69r}eL1cTeD(m3Hn5HSoP|u*s&y(T50WEp-+LXI9d^xs?x=XHYHY?Ce9cx z4UZ?(3izku0U(Q#9S&Aq5TSYqWsEvjm718Mh)&i>Q5DgN$@)~CSXLz-YnASw<5T*k z(cxks);Sizn15yJrshgJEB-&Fg6SmaQc|HNViNS)L{+pp39es?B$Za$Pj$TFUy8!& z-5AbTR|;d1lt%l1StMn2vNrRdDh>BLH6}%&Q>UcrG~!`W`UP;xu<-Ceu@qou$F;Im zROL@6jO|})*X)3(CFGRJNty%&wjq52whE=OlGvGIdQh=^)$JocuM$F;q|&J3)G_~7 zsnx@MvQjZWW4(S`Q5>BfBYV~Xy8p6k9YE(&ZY9v(N5a<+e>!Og_g=ANys525$ zOq#!GC`hNW@%)$BRfhYI3WxjsQ9b@k`c=;Nlz5$56=QPRb5eh%Xxml+$#aTu=r0t$ zYJIdWL2EqiIfXZN2R?}^z23yCP^BnXmv3tK(&<#0R9_S&AOTKcU37e=Nn|mE!Y4UN zi#@L*Bx(%w^ok(ppD16Sp>ATC+}z;HN7N7u>fBMQ(y5XlybgO?j*C-k)X>{IVmF6A zO4u-ArAm{m$xKR4)hpn5s`Xkp#nIo6%B2J|i}Z2I5R6MpP7p5`%1@Y`oSe9ULxO@Z zTnBbpfqqshpjeV>rG)ZG1&ziktiBf}cG$Q0LHB8RgS;O*%Yr%gi9IK2E>!fFI^g9R4FP&k~%3$t*g>|{CDDgB0IY9 za8mb+`+!cXOGtym%MQ56$tlnr)G_3vO^r%Sh^{nqBU9p2HDf=Amcg7l1Db?J(bfl= zaVG`GMaRQM22A?+WL*k;NPylQkV{O4YZhp#2KqI)DZtNotWnXWB||GqNMsKUz;L(X z=B7zaOoZEuNJye8!*yMqn3$l|C+Okg?tl%0oP((W{1kBNC8yhS(b`l_m89hy6s(LG zTr49flu;}n`o$(DtMC>kCK+}dN)wfwEZ!kSMnWi8yuSkN+KXxTZcN4dAN21AA(-n6 zwWLW-*WkSeI;@wjy&~!Eh;jFBMoz~7fRT~uy?Q7(Zpgp^14D)l6p0tw~M74^r~@Sqx2g~=?enx3ResN!$Yl{t67VyRYPv(=jUgRuBQtZiUI5uuAvdNEx6t;F2h1= z!vRxRz+cWn8LtG#aT(eSa2B<}w>D>CVF4xxjsGz=S1T)PTGr$&$e`x5tZ7rS8k?I# z4qt!$wK>PRxMYQ9Wpfssy`5|5uq=oHnS_RBV=net*;x>agKXmC<1H*|XQXS>wc0A_ z+vE8}3;yh3v>_G75)wJ`fVk-Jc^%hK;q$rljE-ss8?OC?VsQ0fgL;79X63|ETY)u%Ue_Y?u!G3XND} zD~VTwV)@p=hWTxT4a5Be88`^E)U}61Kx;u5l(GE2c?Kt}QSgc=go)7;{ z@h@KPuu2T8dcJ4NFY87fF6$KCN;Jppe%HhN$oc89^ItV_PxKZ2IQi8a^#TX)sLL|H zwo99JvN z?77MXY>{ajtC>GHTQ_IN&`qoQ)#|l#z~?2A*R$$uesA!+Iw!9<%XVzLXy}JsgSss` zKJ2TD-u>^W+HSh)A@93i_w??(lqSOq>uqTDDDi4%`GtFsr~K>BMyf(I37x;NX?ZKu ztV@qSoFChTMt?W%#D=C1`p>awNA~;~%E&Nnbx+_5PzTrMMvgOt#yL)-dH+&Bdq_8vniyu8RWVf4iG^tKR zve_>_hd+0$2(W1QxaIKN*Y<{Ir-k)S&2z_H+Be-dVWLd)O1@&KZD#7axg2+M=bpZ) z*;Br4Dx4ncwrj?X=_@V#{et7IHe7o*@7wl&T0e?+vGd*a9jvzc@$ktS5oLqoudeBk zHgWd2fzX0t-rW0YM0ta4!xl8$oyEoO?xZ+-{^;e2Ny7S;!9Sh8po|;zb>u|PT zuGdO)ixYgUy}6}DXYUUwe0_7Luk1F#wa zBq^yuoAT~M?+&#-*kSSIzwD>}wdg{+d*El=9b5NPxNchn?|IL>cKXJ^X@&7aR)3zb zb4K4r?Z^2|gzm?yrCtAKnqk|c9&Mh{_Q@Y_*8F(q$<1cv`(wLjW$)_EI~4pf@5a{V z7BSC$8#v}e&-!DA#x>q^&GV#t=ZyGe-Ns%$y5sr2gqV)7KX;x*X1&>QZphzxivG(g zhArMw=lyos7aJV`n z6nSg6w+#dCZkrQ5Tz>!8#ia?CJMy|ayQXc<8QHc^_vNXz#}AnIpr2#aSVeS^hXAuHLF9n=b9%{i6ON zSN*(0fq$J^+$D0M;M(BD5Azg_duN>c)BD`P8*TX}!~WbcGxF~9^jRnGuN@iYT+<`u zzBl&iocxbxVU~WM-}K(K-Kob9Ym?WW7Bu7ScfCwMIH0)bd|XLI-u5{66We|mT9jCK z#fpX8!GMt-GY|BC*6V6maOt&iN87Y~WjWce|ClSAFHZ=%X|a6$vQ#d+@116tHs?A2 zH+h-a0bPuZ)xloV-)zZf+-b+4w|y4>)uD+?m!P`!Yp!i{waHZ;pzFT8kxDhA$8fZ<)LNU zt{->U=Hc2q_?W&pF6m*dyk&zb%4;53b2BX0t)2YxCjHmGd#^S+ThnH`cA4Atu+p%d zcL%;{R@-?)@PV6`x6}#wJL;P+9_=~P#xwRe?yDxN@1@>8Ro=;Y>SQeboo4AVZ=PQm z=aH8Cz`60n*q~?KoyrQH&Z*bHI$=SOKCeMXrKh#5Z=LLMr`kBK@BQS&?=yFG{n^_q z=+&h}^I1g&GV@y#{$3n*M0Gf7^OfJH-+0))KoxITuz%?(>oFG(&UO?VjNH1k*Z5;A zJ2ib)M+kg+cEkGgwe1e9n=*3EDQBI$=Oo`12MVkYbYffgmHYO--&1<|-`Sq>ulU3D-udaZ6Plf?Vf(&pH@|($p2NwHEm|I67g5j6 zhdd`=O|Nk9xO{Wm66gCDEu#nN{%D@J!+%8QOJ}av==}Z<|1-1OTib0r-|na8Kbsxt za;nx(C;$4s-}M2OD^Aw9ess>DdtI}J9-fmI;oQUfGqYKZ@BZ@M`t1kv2AR=u?|NN~ zO$)ubCkk&GRQYkXb!6uYZ<-go>loVW&p!?)l)o zb)ss}5!-8f7w%p$uxxec{a4q=O^UAFWtH=d6>YImaPk@ZNB14tJN>rn+Fw0FR-T{i zu=3@)l;f`sFTUS$jO(4qt;=UUt^N8@_QkCSNAPD(?f6XMK%ia3NYmPYqXOq4(yVL$VyqPn) z8gM9U!hMjLB_}hh$H~kaak5%AT#Z_vaW!gp;A&Vnay2b_ay2b^u4bJ8&a6%tXIA%f z&de%-Gq+0R%aYRM09wHlt~YBwz5YBwt9YFnGjEUay07EK&w z7EOI*mQ9DtESqU%mNwb4IySkoI?eaU>eyb8)oJlqR=0&^jk+y6)TrAE`;i#fO5w^S zx8u0nX)?~!y8)NAXbD_LpPO?hm%8IFB#ASFrn7aUU{+7ZrNF%4D7fQhpGS$IqQOU+ zL1g}sqJIj_^{L?QH4@^bi1CxT49*GW2dlbB2qDEa4e7?Y!JK3w%ulKyKF(IgK}any zeun2vah5X?N~DI6ZV*ZjelcKAg?Y?G@MR85^l6J^|38_(vBc6|wt-UpS0#Yoe34-n z0~@HxXZVPYeE)l&*<+CSEQIM&S;RD?Gy@0v!?-~#7dS959G^9C*fJAzpw~c7qtbAJ z+(6DB{h_vFL6HNwaPheWQ1nlL;~>U?r<;M`r-7rO!La|$v@yN^PWpqPkewio9tw&Y zIMBCKf+hw(a)Y5HSQ-&943$b^%*Pk3xL+}#|CH8$Hyz9y-OvO*&JOr=>7wun3wKit zOiod#HOZ-Q@i-`!tjkoSB*VBEja}k6IeNfohgzpg);V!Q#nu92JfQ}L4TKgBuhK*N)`)FA4$deAoV9A$a4tU+C{)Y7BS%RqQ#{7@lvd*ymT{Z>#aH8dbyZuEx@{ZXp$jJzlbB zYxD|({s#M_1o0RsIKRr;Yy;2Zbzn>d>oyMlsvra&LFW3xkukL>#CD3u5xyTBK93cT zZ#w*CBXCY|WbpW4nx-xoa>o8M8Cp90o(q&j@eggNdP##IW|UYiY&DXrA&!*5ul(7w z@gzK!=*&30X@IeiPLjW~$WnL(`7>XB#iNn_umX&`_5schYgzjC?8AkKK3;GZ`c?7A9x;^sjNyYJ{6O#xhm^v^K9i+0kQ>4UgMS$G zcRnWRgae~Lbk;R$nwi(CZDCobu2sGI4de|QSvPLdw3$tF+ZHWbeb!phrfs|S9Xi_C zck0~5!O_XtrE9nDJ$m-)?b^r9y{`u^czSvJ`1<+x3kd8#ASifXNa&!j!Qt?;+Y!S@ zj2!j(=t!k13g*{ij7?0^Bx}d%^eL%n=^2^hznC!b%SkYgE#3x*fAQ&@l@i8{ z;TSpqN5Y2fP;wUEvEwDNvKkk^XZ9G5)o%pZN0DA>$o%^@VybM)zBhFOsg&+63h!yh z!c8EpFlXGO7SrE;t>GzyNN-7dTxR}KI+6x0ty=DVl)f)%0BK7pUs60Dqz&rEGhxx;VUoVmw^YR=ouO79a0m*>yL88!wh60eo=nopF_1 zS2}hm|NqlVhTa^n0n+u}GzXW;9R4kRc2$aoJ=Vj&6u7&<9voK{;<`cOy1;If;=iTG z?lIW?yJ>u>ME@Q?6LMyEp~;mcU`OIVsV}zD5uaZ^mHWTfcZOICtPNobQXBZM%a8dP z+aTT_q(VA)rzNeu#3z;XPn7A?^?`AtAq^eW1m1HyLTXqq*s_!0s*mSNG!ScBRgaWZ zQg*=b?hfk$`zNU^jvuYgbcC}}1u2e&zj2TUt`x=Gu`S@a)LFTc<+k-elXuSI1?7FCgH+^M75sQd*Uz-+W$%Me$pB?SY+x2>qOJk z!{Es!IURgB@ta1#-zQb4gGEK5s_3z>@+Ay{29%y6!jgx;!Z2wKGn-Gua>HWhaB%_H4Pnoqimw1Bjb zw21UK((9yB`ge#Qkv=2k`mp+tliHBlllCBWCG{i?C5(g4y>(g;!|X*{WxG=nsYG@CSsbO~u5X(4Gbsg!;R zv4NCxr+Ou|Cv_zaC5<6XB+VerAuS*+CLNrr;ew&R3y1dyhQZq5@c2~DUzflQR;6%Y zi&4R&HrPeQ;{oCH2oP4oKA|u8$EWH<1AK<~mEj?sR2@-&m4@?IM{&U_9p|Ohfo5|3 zA^-lViJVtz9Gt^iE(8`d4@^$ueAUrnx)2A>2FCAh1PsB_!}nB-e&7xHVfy%Xj4>|yv;2&HEME+1^uuudp#)fNe@HnBG#D7` z&@=?&-T(7F)|D}+e_olk{!pN@iRKKOJmV$_<;r&=KcTv zQVl5J=v4h@&q@EP`hS`N;12J<`ZpC;I#vHyHT+~)BPIQV>6QQJpJ@tHIOD$>aG(F5 z{FCA<{;zKbS(B%HHTCOh)3ayHob}D@IdgO7&Hr}6!bOXhe7AJj@)avr<*r_{cHR07 z8}l}8-m-Pu_8s}(@BCrc?jL_D*t7TNeftmmQh4yt;UhKBX z|2%;D9}TenZ}0#AcK-k4`v129?tiw61Kj`X`(Lg1!(LA6|Li|u`?weUKhOipCRhHe zoD}|-4^j_?Iz?vuS3~xn{gdLG?$3X=zos$pv&AR+|3)%Q-=)&hm}?B5nXeo^OP?^* zzwBr=bM^55+1?sM$vNX^_DP)X$7ca}41l|TxbcU3eYnNPCj_{UQ)%GV9PY^BW*qM4 z;Z_{(!r=xS?z`bu8}72{Mm~H5yObFG*mDf$&(wz0l{A1fo-~Uzm$ZPin6#9X>&Mcw zC-o$aAdM%@Ak8LSLYhZfKw3n4h15VQ51@QVrF27ym82P@*`&Fo`J{!U#iUX?rPbIX zkmaW!btR1;)sjl_2PP}TnR)5L9U{KRiSHkHi73Kg+4VpUJ(l;SqgY< zASMCEw%~Od_S6CQk!07>#AhJBy`W2lhp8AvERCWla5(^F6sXarrZ^|3rhu51KuRY@ zot6+S=A?>=fpI?Rj@C;Tr$~gC>0wntvI1XPq4fMR)Y15+8N5nwnjGh?g8Z?}@Xigr zx+TVde4^tM5@QrpU}+>VOs!8&)j`hq-cK^To0X85seo5y(^LtGIQ%HplgYXV@f|A3 zKNM?Bug40EQEL+D?OIMOhv}XSVXvQ5mbEfpw&NU*7%JdRI(-V;^S~r1C>##hc@2)= zRjnuF6oty$MxhsnzaY>j z>|)eFjY^9p(j~y~og&aV1Qva0Qgq3Qm=L^`iesYa#QhMD732)Hhvmk1&J>C2*c63W zOL(9w%cobXard||cpuL(Sp$_X9=(stgY^Jazm3$Iwu_Ra=BKQYfA_3Ap?PYq>Hx z&R05L!~oE?#MMafOqQ6aj#DKnqB5Zx_3Ck{Y7Nu|8#}~u=F|CFNLoxD*M@O&Bo>}tY+z@EfDV8v(DP!l_b&!}e4HL+v-A`^Q!uz^@A zk9<(o{c|;8EPtp82LWqM7|Wk+!nl9=CM@P}!u`QsYQo|rEXeqyEeJib_e3o648LgIdKw6MIz#CZPV{+Cqp2yxwpp;~w@g6)qKUqOuLD;^&g;sEHgu)G14 z_7M;Q-k+<+@x)U8S=D$6aVV4)k4HW+*4F^wLgEpycUXTV!~>u;iN~Xw-DWr|zhrkI z4u|%O?N6$2Y5yX~E^!92RG-rEkhmG7fez=HaAXlzzp;_kPy4f#l5sG;iK}p>QO}R# zJBlNLp9;suI&u9TrXl5pqnL>>GAsG8Qs6oX$%mB(M_Msg)Pzc9S(2nBBt?52vN;rOMtTjhSBYRpwHUn;Z+bRYX;Eq>Zx1^!qfjPp-Nu&VvzR$SGN>+NxMFplEme(Ne* zXw`6f@f*fW$JuceiB7Cx9B)@aTxn$xJ62Wgc&1^Aab7_qhE$8S2H)FIC7pjRPt|ZA zmj~Mc>k&SE=HogW;~9_ZZE%%|agT8IwGRB$P$JVa{$qbUZ<68YVe7&(in&jP(?DBb z{(3sv_`WaZgX@RlV8dA{T#dul?T&|>P5UHFSr0L=Mx}kj*&nP6wniCO=rdP69F`H& zN)z|y<8)YmVSKNmdVE~%ge!WmWng_s?o;8|8Xbjr1hB=Ss~!$l^RRbOq#ho_;~W@! zzXN?g6_2fx!ZQo&?c;Q@*H%Docr5UH^8P6t);3%9#g3b_N7c*8`fKbZvHxV};-|`m zyh?591+RYN#q<)Pbj;!ThSxNy9pg1l1IG;8 zHeTsW&qt|mWLFdxA3tMSRj)_zeHc(4c5Y*O#_Oe&Z?t&j!Pd?kmJY9PQogv7AJ;?T zI{WHrVGL{!_>P4XUb>#+8asAnXX)ZTFoOPZ`UhRJC>O3E!MYmox|Q6dwbR%+1^yOo5uV2de;&5 z3tLUq>m9CO#N&d;9P0}AI!-)0lf*2l+ND)~*dH?I1y9rXj2}X3Wo2tsTi(me%EHmC zrkRyoZqdpDL&{iiI8f|9wFa>TaZO@5u^F)qu{p6lF?%Zl?#qd5liihA%oO}Qi7m+< zKrF4p3MH;f_6TAtVkL1s;&|fv#9HD8#2LhL;w)lZyM->BxDi7xhuE5U32|fMT;e9g zdBjbL^NI00HqaFiH)qHd65A3N5w{>NCT>Z5g_wO`1Uz>Te@6CF;?~3lVg+#paT{VT zfgSI*#L{y}J7Np6w)}cu_JLPu`_W5 zv9!)kN!*+4@x;>m0$SoeWX~XGZxg{d2C+c)Y+_I19AYoxCB!&pfG(HVmm!x&jL(SZ z@`>>o6<1L z!T@7fdyt-oEQlwe4V;`J;tJx9#1@Hc|Lurvh&vJ66L%(dCGJ8TK290$1uEgLwyH2eK=P-H9`ZdE#thAL1p%V~O*L zWpqPTNNh%2OkA6|gxHeUKwO8I)3W`uB9;@|5-W(^iCu}u5(f~&!UfSq5StOl6W1or zAhsmVCay!ggxHEWkJy&DfY_b5h*(B9cvpzch)aoU6IT#h5?hR8`(KCHhS-YOp4gVy zmDrs)lvqYLj7nllVl8nU;w)k-;v8aI;#^{P;sRnB-B1=0TM}O(u0vc(Y)f20>`pA# zvHg`P*zr;jTN1kv*CF;Kb|(&%;$@GdMor)T?XNh~L>L#!Z{^TFKs* z*|Q`&agJp7WcFOiPMj~XFS8d)9Kg6(;vmK)5{EK25SvY4%%!mXweDe@pER~zhp0M|- z(SIz3A4}=sco4c&N;ie_$MG(7IA(?pM|GL9^5gd`pi8Q(4`xpye;oTkhxI%b6yLkV zn!u4Mwx?*t{lRe=HV24|?ZXk~0Hx<2Y$uL1=ZJP}AGi)ddLF@c;s`T*((@0t7i2#S z4351O-A|lzL&o;v2s3ih^AxrlBX(*9$6 zbc8uaDSdpK9_>N4Ot}#EUe(?=*JbqIC*v=iHbkg%Cws&Ny{;=I+e$xJ7`*(!U((^2y2e^MYM~)89 z2jmg(HwY+&+MimiH#S#|;qW|hgjmvkVK_&K<%LFY8DhN{8OK#k?Rb8fGM;CS&`PEK z!t)K=k#yYfyu|yviS&OyI}7X2Upf3o~O9JMr!AHz9LKK1)jIaQvP`U z;&G{74|qPCwx1|*zm4U>^W3zYc)pu5p7&LmogdPEVLxDMPZE#6F@5YGOzk+jY|7YQ z;Bk@Cix&42XSJEbe#G?rXZ?vW1=g=%i(@U2^)HwrYhmMHf3Rk}>B!)$nCWrExe`1- zrFv%lQ6M*n@@M@Ct^!~V$F=deOXb7s5at#^cGjPGR~{GEKN#b){=sP1i0K*I0qYNN z)e3WZvHk|b8CkV`v-8^6kC~naK4M>+C?>$#neqH*?aaHfzD?VWX*{Xk{KVB(Qa|li z+5Xsd8&^{>$La~c=Y=^|PhOS%7ON-Y`N-TPl}yyjg$iZBou8xQ8(I=q!KV%J#$Z$M3N*t(PE^ z{t)vwwl|i)Pi23{@;8c=Q2dkGq1kNPW_S4^lu^xt2 z?ho!i`baH$MVC~ zI#T(uboebs%(3unEhG9!^(Kw8vh=01O*-EORIaCF;X_UOAxz%~+JtHPUMBmG_92z$ z1HL<*y9L})n_xY@buw9rsY1~-q zSCusG>_CB(Q`mC_yW0j&nx~M`(31TMd1Mj)PMkyhJ#j8^E^$8bT;f9Fv&6;37l})V z&l4MnHxYARvGVRAmJ=T#RuEq&b|IGL2|S5Ukv)|77h)xG5pe+3S0`dE*_RP#6OSdf zpzv*obIBep*{ObI#Q9{G?gI;nCz8FG_$OjH#jinJN_Oe|u_wD3*(=B{-QQSDW%W0m z>@F1EoY;o!Qa|lVc4=PGp6qKVyesiM;s9dlJ~e{)2eQW#Zy~mzar(;pMY7K#yC>ye zn>d^7(mYK7*)7PvgzRgH^N3Fq7Z7hHE+Rfbe1&*EaVha$;tJxU#1>z(`rJiqLwt$Y zp17FUl~}sI2M`yMJ%acGaVYJdC2>62&k<)3|3REhe3^I&@k!!5;@!jr#0QCsh<_u# zLcET+lz1C)1@WK67SmXLd`oOYTtaM5{4=pD@fqR(;+w<~#Mg-9iEj{R5ML$ECO$@N zL+4{V;w5BP6Ni#rnitI@dm`Cg$lj5-fb8+a5p;a&5Eqd>h*(SUortfHT~Dl}_;ra( z$-aQNf_Oc##dKD`+lg(6rR%#r@sDJ8CEiCY%|lA_t^s7vCc88*+M76n?9x1BJeAjq z?D1rmo-;CNf1SymLG~2lB8qQAoK5x}#7l^!`vWb7uSfPgvWF0RQhxTt1!NyX%#pn{ zaS_>7#2J)+ec~%*r(rlQi|h@^UP|_b#1+JYh_flYoY*3p9WUv69Z%(PB)bjSrK@%} z*}Ib6p6nUK#bkFQb|w21;+%MvUqfPPo>?Fcpzt$@BZvn}@hSao#PMX$B(^7e2jUE} z#}O;Y-kvy{>}(Vb-j5)jL-q&?--S4j>`BB0#9tAYQh59>NOVPHpT@9qUfx||vQH(> zqWCVvrDR`0TtS>pY%zn?XDYD`@etxA@yywajhf49u-h?=t?8Atc5HBIlBmRcCfOr*g5pkYmr}Uc=Um<%0 zaUt285tovkjVi!8ngq6geaT)y_OFR8W-|LmVjJQ#VteA{#Kn|fb7EJrk0h2;er<^Z z$R0x+L41HXp7=0v25~-dHgN&*65?Np^N5cVU!n5Z5*Ltt6tOg}f19|7?1zX;i1!m$ z5O0v;&tmoUofMw(Ye8&J_NBzG#Gex{q5A1g96ARt-DeX7m+=RxPsFE zjQ9%KmBhJ}eh=bOvaclOzG27X3u0FaZ%1rH_5@;k;$$g2aZTa?;#i7|f2Nou=`n(Qe6}X|lgK z>d8h0qfN#GnGgGmt7oMY*f@SvW%_I$Mw+d|aef>vVQYV}1Z-Ri*P`NgS{l<~^EEgc zz#N;e!PUVM<2W{cesw`axrSae-nss9H zLAV;3IX13>?^T#`RAqUYU7D?w#%-c2%g^k_{b7vn6`7_t21c7qSz4_urKhcImn^P-;$O**^=WEn z=bO=voK)HV*gPQqWsc1cDZ<1C!aB#h2GeT02A+dDEODvs{Y`$7rCBfE7ux}w}`@`n@jOAhTImYvc zv2lMG<6kL0wg+jo1zR_PEcF*Sj*r(MDFun86p(SA#MF*#YR4LtN`&FDMvaW~>BcwE z*n9@N?wMX!P5n);hs+<}-odj1*FdnI*VMmicx<0i?zq3mQfZNK-WPvoNU9%96GN~& z12$i4tZ#fX#I${~`G0AZ3a;zGy*IWWwjMxQy@GZ;pQY6=xK0F*2fpLQ99u7-tvp{? z>tTOQ_t(_lbbs0Wej1d5IW`Y(EDxJE!2X2YQ5cz>K@v;x8OK+ahcUh-#vEgPW&gl9 zrSd+G&HJC~;q`DaY`bCm4YsGSHH5G(u;H&RCQ&@_24|pQ62k|^(qF?Q z=@Zk6YIciC^9SihoqVb!4}Ms_4<8y-h8I5=ssEox4~zeUZG6|6%pahHsKs{EqYHq_j4X4_GyB-#&c@a-n^D zu{{_%P8Buh{B2QP9DKe67@i|R7t zj;Njw-S(ls)?%Kh@;moL&7IhHKl*1~oF^*x=$@##2i*^#fBw)MQFFK45w)<;^%pVx zqHjcXaV!xv_fYdfG5ru9QI-1>Ma{BWBr50ei>UIDQc(@w4G&`YTpKr0vx=ic%^5RG zRQdfMM0N4M&N$ob5QZ<=>L6qL8yboImCFWK zQA58OEox50R8c+aZWdL$?kwZ>&qXbsX?qOw3%w$UT5P2jHOqb`(=IziEot%_v)``} zRlB;?aj`zUy+!pr6)S44$1G7@7Vi*Me)WQ=p*1T+RWxl`gz<73dWu^3JX+L}L(@g& zGPj5d?N8L)+;UO1jhc%6bAD1EQL}cA5|z6-Nz~9+Yedz)IVx)Y{Zdg4`|F;>{Ib4u z5>?r0kf^x_^rC9{MWPz=3Pe@B|6NoU{vFeV*1uwW&q-dQYO|t6H6(s5YQFDA#pYDwSaOg;CBn$_*P zsKpK6GXEPb&SL!H@7zVr^&Tl|*46Q%YDX^>HRs$OQ4QU$ikds6Le$Wby2WDtX)Q!` zG3z3#e1@B-p62~UEy)-zs^NCLs9OIFQL}QViz+W)B&v)122l-P>=re2*AY>bcP=oy z)ooD=yOfJ6_mQ2${5`|uOoz7?)g{77R4&9r)MAeyQFGgm6jfVujHsc%XNc;#X}YLN z?IKZgo!5&B{e!55iw=pJ&z}?3aN|!=wds#VRa(3gHFsX!^H`oxYg}mz#a#eU5lETz^b?dd4e{RZguH$x-h-PVfJ{ zU{=Tjk0vhOPp)Kh{I<+48?4PN_#+n{7dBgL&esV1ZCiT(Z65VqdK-ka`uyP+wfbF- zy6Mq)UvTSFvReEvcV7%DdHT@fucx-3)g5oiAFolUow2siW6<$o@B?FVzP{%4V{T*< ze#h>b+D_B{^hjFU>xVtV&#BxlE1N_$%At)jrp#*bLH}}t$45Uy>*vQZ_0Psw_&u*;)6$G z+_BRhKX&3Bwj9~#P@@a)tNvqRakEz*D|^=*ru1yWD_e|TFzcKZ|K+9$cIs`-_)U?k zjs|BpNwPg3)XYV`~)(t+s z%()$JZIu~Ns(1flKx?@9yDpg7j*49+_RZ8-_tdw!>qF{_^iKYwaSZZ z&EL7&=~Zz5_WS~?wpSEO8u6C5&Att&+nIl_-mqn8aBDt#P_O1WR&DrRcEjIiG^p@6 z*|MlZc9@(Wlae%X@WK)gRzEg;-*5dM<>lAnH(h()qGE)cZ*p-+$Je)-@xR>J{>!7` z_4u_y{O``QTJgPmyS@9PpgnKaXuImmDc1a|1`h(i_{M>6HGFXs!^2K|uP?f4Hf(Or z52>ZI9IA8T!?%uq+r4E+K5UKZY?s(hyo=qDhDAO$e6L?e)crX|!LO6qO||M#hrg6L zclVGvw*1l+qnEdFeBd$W-K2W=q6NNj^q{NtmpSoPJ)@fEwCluA9-ZNuo!pJzv0+Ql zn33*$%d|Py$Fwzg{B7&j^>Y8de6!j538gVjc$?UnWsl}|=VQ~-pRb(n!58GtHn<1$ z;X6ibeq8?#cYbg08pHoM(uY5O@~@HyPi^_F_3Wk$KGT!myQlNul#o7r__XYWc0FqI zh5@(hawDwhINI?Q-~7F+1J{AiJzsui);ZX})H+s9_HFo+8;5s)s;$R&@XWLRa#9!m zvl(p;Yh1eXp{cX*^2x`vy8KPge4bBP@O9gwG&lbCv>Rc^>U;1zBBqs`TIbBKtW$V? z%~W^(+{i=a`Rf(@cP~b^RbKModmEO0-Qar*{^qYayW3^+{EJ1OJ#A>+i(lGc&8X?E zo%r>8`UL70I`Fq5`0)$PUHKk0S3T@l=FD3ToiqIT;68l7k)ajI2Yd0)ROJoY{@R{j zGuY#z;$}~NX!#7)q%b$W!SdfeydCPw|9;tiij$QKU-GB#f@143j}5~g){mBV;2U1< z)*!>FBY(C+ZhMvg+vDrg1HSC9ZpXLTmKB&W(wvXceE*xHa~t03d7rD>j`Zf=W{r!Q z+RUC;1>QU!HRz!SYkyt&?6lQgg8TO7Cwn|7Szp?j-_fS2;aGlWK6d0EDYHIv;TMGe z{3u!B$aAMcbhCCDJUZP{)ouNi!0*^s`^aM33Xjb{{n|3op69=<(>Abfv^_t{Va>rN zbw7CcTeiIK7v7WqI;mEL%RP6#PvTX>pWk)hC+E-o{_9h{_@cbexTJnP_|_MiuFMvC z^V2(L){_tG&cBeUwr8Gm=Wo3@JXF5cihpCVJW6X1zc3khKFf8l8^2*!9?5@>Y5t;nZ+`4o{8RZ)&b+leX0Il@4{!U;j^g;i zZ#>3zn%yGdo1RdPq_*k>?>!cCLai?&y7M-x8r8UO(~HidUVMM0#ncV81-?!Af;+$T z=*!o2@v3k5-j$DTacQczkHGgz=`-3g+Ks=_e9zEn-aYsYS9i+hpX|lA{I;{(6jwXm zWqz~z*$w;h@57R2U-#(BH+#@_#>L0bPX8D?b^h>9{46`C$lz=B_#-`QbuL?9=Fy|C zL$<-&pC8@t#l~fiJM(vx{lA(tpd;^;>2u4wUMF4_Vr`+Y3*twfz0}kxqdy-I`+Mf7 zP+z{y>EMUIZ3^P|U7Qf*F|iYGy?NiOQQs^0Khn-dD@JzZ1MD_Gz4uioe!+te&tifE zzQKsn340It^936QzDro{&Tm;4`PKR{UHEfIgyt1u*&nsW{=Fji(blKOe zJzxGL^NS~QJoyL1uJ=hk9?TDWX0`EKvmoAT!}>FqzUt5K9d@*y_L2)E%TB%%u1(&PrriEcm^taQcIETm2|XMN z!hcMEC+yUvW^EnyPH6R;7yN%G*p7U=*1_(bkW#3byW8rWaO@Y~QJV6%LfvIGXAHga zR=EGGY*EsIx5B)eqs@O<_g3(p`DR4NS#O1w$H&3{x5Bi*dgm5IycG-;hh=jG@XvMX zv%BM4p`>tf&D!8Y^tJHruj$UMcDxo2M2}HES@K#Ke$!>py(zDS zB-`Q*vZU8S|C#Z%y@tIOY}a|g|JOop%cIjfJG>TZ3>g3FLR0Y1mBIhl!qx8bg>xRg z5**{lHd}T1l~7~#oq^{Ly%K(x4|&*r`zwKsm%S1`6wayV{PinAS723fNBc_Ho_@RT z&!b)mB@ZsW%@24b+;dnq=~$0fLZ@5Bfo~MAgo`<{Mj`cH39oII&pr66LJ)eFjSst5 zAzZL=nJv3eA-H{j8>wF^gw0RKHrTVZLRdaS_M-5+3ZZ}g7Q@|X6~dnRh5PJMDg>Kp zD)?U^G_I9#hzqF@{y4C6{Nlb9LhiaLvi_aHF1iY#mW?t?R;xnroFsc^{_LgDDrCf< z4!2$k&&(%!MHaslCdi!T|8U@?aQIP)McZvJg;{-m9J_hhOQC38#^j)xFNJ!?mNvA= zd?|dtsmGn`aW93WRN0}ELthI1%|1^#=l4>W{od=Sp~p*M-wpps_U&H^mJ^mIWmvxy zw3F+ODyi{OXr?T0sw{sY9PjNj_T|kNf~sh8#|`IR2+a}?HcdSELh!I%TPN`Q7ebD1 z@VUOLUkHW8*>3&jfjxe5)A-3R1lu@SuT|q-2(Q`>8uLi?LRj11{p*mh7eY*%y2FdS zUkEn}XBG_Y{zCAKlBK_H_d-~(y=(jrjb8}0nnoxmo4*j=I(F(2^WwR1ze#by$otQQ z^|u}!j=S<)7$*O=OZJK9!o1i0j_up~TqqnN8*9Gxxv=?EyE4^^=fd9+lU>fwc`hi; zBVvBApFR`%m^*JQx%*5Aa=3oL=gKodv!lcG zf|JjLn{zk(!S8=2jQk}m_tK7MLVsD>wn=NA330_cZ}(aFOvnn5wKdOvCVbI&d%v6G zp9!(b;XTeIJrkCmeSZ8`ttm89b z(D2b6cPpL=jYS9X)%DL!sSV++^99+Pr$X%=>%Hebek#=1bZ4K#t*64V)Xnp*UVJLt z-}1$}g(sd0Lprs)s@(rnXzc#Iwfpx^g|t2ygE=Wcy^I(Guu@zm>+yP zzvZTKVP&H`oxE0-3+v4aM(VyT7p(KM8|7!03&CeryS|=SF7&jUTo9@+7wlx0Cj1az zE|ksB(zYF4F6>O7_vMPg<-+{-f$+av7^!Tw_$R(x_;cUVjG;Zsh0cv~+sZnZ3mI?P zO#Z2Lxo~etKvrgxa^Y%WoK?TNZKOVXQr14nR@RTwKQddtlQhr z*)e58J-gJ%&qkIBCoaBQyJt|D;C{MJ{m6hap?$v|v+E0GLc^k-@V`vhTKM;jSq^1F zcbCuaN3<&wItFZP?qUme(Ul2Va@m15wSf)q<%ahLVNHi0UjO~vAml8+H}?4>gV0bp zWk{_%24Ru^+&slKgW$HZ=TP7C2EqOJeIesc7=&?Nv!-n!5}EV+Ogr<3WE@CDeEIFGzhoUsXuJ_#vn|Udt0=b3jDC3)2c5F!m*eA z>U7i_gg1>cm+u~95Z-PoJ0Bcn5T>Q-avu*j2xdJlzg#@XARLWpdMYr`Ajr+U;6H@x zH|3{et_Hz&bnD&oTnxg4PU&xB>D42)AeLsf z;v8ai2K*8^{2s49K_f10iB8swnxbN?0s}@-fk9L-NZJiP=*Q{A30(|^LC_qn7JpWe zi%m>U#`OxZswgpTQgRGyTQQg`$3;dnLu7oWHW^Zhg#eg#td8uNE?i`0SJ*UMBs4@^ zdlsotXQZfN;m1TUS6$MMnw#(m}ON&6h%cu%o#Bt21G^0HGqn74Y&G);c%RB{lb0k zfA4$4uj$jNy1Kf$x;l4A>4*u|A*DkkPUoKy+U7xCaKn?k{^F!)ad^V8)(4Hj-#a!D zo+g=0?v(J)2uMo|4J2OBAfFq?&@eoBkv9}xS0-OP#_;`;Q)3e#op*eAH1Z1c?LQ>c z%WvpVzhR+)L7o9YLkA2S0rf}tw|bCnPk(=kC~ea99}qY=$j`sOZ@>VrAin^-Pk;>n zF`!L40{jSnj3?>e>d_{ie}Lb>0MDU;l6b8iWIS)bVNiFt@ub9v;^VtII|!*4b0lx* z;AeRlZ;u4};1{Qp;hpL5c<-cmp-Ai>o*Ltc5~=riq6j~VNdaye_3jLjU#mB~1KwI) zRQ-1*Ur`Y_PU--!#HXZ81w(J=f%s%gAovEJERmK5vO~kuMB)tMF`y`T^~J^)-p5IS zM{RH`>BNMOnFA2F=_CUXg!iXj@PVggWJbg9I>Qq|#1l6u5Q}&RhAaWb2G*=aslM=1 z4e{(W38-xp2p^Rsj)R6Rkp8q5IS@ac1Q`KL;YqV2MBJ)9Q&YusTb1|&TekK0qMyhK9VSZ0?YCbVyuSpm zj;E3EpxQ$H!^J70pfq?r2eKf3{uu+r;!yILbnM}uF$5lc3iTEzC5(jkdi+zVejx&Y zrN$-_{SJ;tz=fnlhXiiFK+-|V5t2*r-czrnw0I%$CJ*rdQgTuXhJr<*J^5agphJoZ z!~>}W?}$eWNV)3ZKnO?hx3#quw8Dr_3Kt4U$_gL^!b$x7#j(_rOJ3oL9a9C&c<^Wy zJVGV1r?M4L+1hjvVtGpEn;Iix#vKIMr2OO9bB}fn@z}sA)TtRw}k|F_3 z3MWTL!O(D_NC52JlPVid=M4Tt*X5OzoWTf?`1?&1iPOcQ|*X;cZ+=Z2?YW2 zDS`la)rn9;aQjV59tAJZCZ&7DhfAIXBmEsjlW4|itrM{*8ZrhfAZth^h$NB`aG?Ac zcjhlBCN@PdDt2Fw@Q4?LLiLeZlCWv^L4iq8sp;X+`hIASBvuqNjl^OT@&_zz zqLSd9ZTv_VSu!zD656#)I3esNO#p`W8lD&}Lj994KgJ~jVaTjHrqD$uc`P*}nWiaD zNZ|u`0G-6}UKPA+1uv{YiBmD?R0iY2i zV1|eZp9oKVrA0->M#jP$VS@0;ND$fracp!4BevzF8gh8cuNU^6|8y?l+vnL?$2>C;`HH!`@7K9GqprR5a&LFu}1f3XU3q+=f z15F32AY7OLO*aMD244MhAmpAeQ)h%$BF2yXbr{p7LqeAh@Zr<5!%!>*3Aq$VysFnK zSbFs60j(NpV6ccOZUArlqbet-fo&g|JCyF|x z5K<-`4^H3*By1C4A+S#qV9&PHr-yN91R?&Qd!af^+<&y8V-%)xj7&ndl{gU~+c3XK z>Wbug=l}psfo~L+SI~ff$$*W-@T8(B9Sf((7~J*+#It`y9I=3|Y!U#_1|(T@N@tLk zSi<##!2&Jqq)4`OLS7PW5x?6@JZ5Ji+`z)&5iJ{$uHAr%ICn1#ARKMS;=^az5>2)@O90FXi{vXMP|Pbe<* zGcY`ARVPPFniPSOf2+bmYYsFAIjE4#g4qMcSin_+h_uvHEMR`kRZqfG5S;{7(I!Q!e1eoBpd=;le()#77P1!;Qi+hq zt#P2%+O`x*QzUg#j8_^hhbT1{`{CaxF{szH-Uf06$WPG1o;v8{?0~&+=a0Kj(if}X zk!|&8=x}i3Wa*<2k7_?q1yQkLIIQF;77;=!C5`%Ml~!2VsxY+*K@@2HZB;O8J`?Ie z*rq$dMInCwjs90cD=9rN@FwFxS3r*=ad>MTM&K5V=}+iD7`7yhY`6pplKzbn zIU*qpLg3fRn}nVcE(YypBJc;b^hSJ@3Yz&kp1Z>F++>C4!uTaT8m zFO{?5&Q%O8u2nm2^-Oi|oT*7&w1>ou-f0$oypMg&Pj&10Ay|i)DIps+%pOEI_9lTtlJdFoO)W=V=?~}k?6)#-I35k5tGCVFW zB0S#omZNCaFck6T-D8J z(Zzdi^zN$Cu-hs23jH6O$#)GMqVngtsp|K7Pf+y#(^IaamX>PyY=PRj4ka<8_RUHX z@UBN4+RzYwS>sdPxV&?D1Xi6+;-)!SX07d-j4 z*2O{hqLc0)`t+%(e6d&gLBku1i^3W=-@KW*{OZP&mABji-rV$SMU~}OCv|Xr z7!$BEUG1X$K-D9M4CE}jSSyYw?{)Z;jq1_joZ$U-3gh>MZ2w+U>2#&4WWoB{&Sr&m zzZF#q*IK`ce6eox_%xN}333HWymjub8qa4%D~okUA;p=4j;++2T>7xkZtoN~lLKmN z7gy(~m*04Nu<7p9_V@3oUa!73yWhj>uRrd)7n7KE+x(J2`HbJJ550eoUX+wR@SxF? zVD8-H@tWVy_fm=qQRQfVUoTn`TNv^2+V{Bd0axM;COuTie^sqs6O^Seta6`xk8tbg z?H>%ns)i1X^}Umx*muzQtA%%iZ`MYs-uGzkb*p<|VOhbc^+)QaT`3;;?0bo$>6@cR z=T{y+@3?$l;I_^CtrT6W4lh@#xzuS?UC^=UT1$t?QTs{A%n39z60)uBRV&e(hP`Bb8U@ zH#U8~P}}lI&97wF(9FV;#r9e2ubAe|8Lm?~NXxP|@LF=qq#Vzt@DVkI4th^Yy4RHC zndW9^DUQiAnQ2sj*53JSU$o|eYr)vD=}%1K&;?)T1fbR?wNJteW6v zs`J*>`+l+V*wYh&l3zNC;#xkuwLNm6(rHuLGShi3`N~WGF56&ucu}@SMqNP%x2EFW z-!tB(l>0P}|J~%2XK&lO(TaW<&HXbYp3SlMt2}KQbcZuve}%^pjYV0FmfI?8-HxfB z%6A*ma-{u|?YSzKa~J9I(8UlRrzcHhAGz|Eb`kJ?s-K^GnTK3r<_f_;H z_{r1`ybGg*ZdJoJ7cS#G+%Vs${l)EF!@ic;?s;If`&E_ue)}w)4e`ai**mR#&i$ny zSk=ip^Y55+(Zlx{?p+T0Sh=Q}@XWf|>L|P^3mm*}kxxK!ohZYpDKqTb+b2(tRet?8 zby?MoKKU0ees-OobwqjDnn^)rdwYns?--ny)k~!y&*I$Y65iYkg<5`Nn?g0>Tf8s1 z*Cs5~s7&?J34SYQ>G{|3m`JKLnpc)sU^-;M;w`gLiBd% z-nQ*h(w&s|KS=g@R~0whHOqT)dhyuEVKtvGXg+yReX-=?r$yN{^@DVB z_o!MfI&d~Q|Brc|N3y-gTFNWOyBVK!*LRwwp<&Z6&o5fOASkoqbH<1n7b1q3pK_Zq zuEjF%kL?=!?&j+MX@Y_t2Rr$FSsf8kb1N|8vZ3Dt-;tT0{a4!8Ou24)F+z9#BKv?N zxh~5ZkC<20<}1B;n>lWGrD$N{GN1S^`GIMTt~xynlzEopgRHoAB6sbEjP1EweaeEidS9xxtGj2-x6UkH=6=YkD*2=Sh1g!+ zPx?(x|N2`=oak;#aOSD*yueoz-F!amE##FpZ_wG;^`iTH@mH(mxczq@U&%?lo-IG7 z+*|JM4xaM!$_q-xQx_@BGVoI@9j>dfq}Nr=o3mD_KWG}ECj4Z~3tWDSt75-irO&_+ z)#T0ArUTD4n6*paWZuavtlcivE+%WkUm925-)Z!&B$j{nO*ftIc^`DQS?|?eHY7z$ zFSxh9rQ@9Vm|p19qOQ()wheQcD|z8fnY|BAcje2=5ub?M_ctL;k)I(&0#>Ui!qd+Q-X zBP^$M%(t*u{Y0Su)~dbD{z zN&fJ3^n}7O?NU~Ey(>2Dusq$)Z}>##eJvcbwL;b5Odmq+rO?ecuulJL<+HX&tqe~wvs!wik8)V(2DQI{# za{ZH3Uf`x^>dBC5Jx&c8Z7 z`N_4h##Yr@-^QKlxMJPOOxxS1eLalMTpT&7^6%a&Pi&fb?fA+@?TQ_HhF;ikY6;ZT zxwEgeRnMg=_rK_sF#D46wTdhDhvY7M-uiX-5DfY_2w^& zm8YiHulRVTY{Qk^nd@_P-PUc)`22@eQc0e!V*2VqN1WFFe)a7d$8Pf$DTfYTIPQV^ z;@AaOmc005`usZ|`Yo90t1z#`^7P!f!ZlgPG)B*SQq*qx)!TPx1YKO79l34zte!@O zbJWE*X6IKuELe3|u;a2q)SsUcx9prc>3QK`olbjnb|vlZaC-MH*!#1+%k!?=CXPtk z;^}^H>&Z?Z^WS>)-1z6$shj^;Rlcbz`SC&<8di3_V;A1ac^2%}h7e$wqE;!5k^VOab zol&bImN+-pb-x=L*Wr*`%^yuR`%lgHK7fc5~3ZTjSq!O}}41QY&)w zhEaU~&yHIg^)_gIp73#3zq1un?(g1yCfbWDPP0zBd?|U_?v?sR+V+1R+~U5z%4kl< z#w*7CmZWUjB{*GlZs25Y-#yOxznxudR1)1wr3%d{j&<|zq89pM)x+j7v*xUN+~?|? z*`odRJ?{nkExF=u(=~mzR>NP9(nklV-+Jj0b8Ps>jUVlrPi|P{W9w)U>^Ext!}m$P z)0T7Jo%wyc#|@*3FA2k{HgvN)Xu0T=QSOHPX|^5t1HT@gD)5xcS<<5Qq0whYpV4}2 zN4xK^Jh0Tv>Dq^l^TuD9sHeH({H_Pe)5^2MPAF9;=Z`yRX!>MKX+cZx`}#v>J*~W2 zKRq*6qsxUUaX#rMHfgF~I1{rsrPCRO@FDr7weRwh`Y-){?MtSA)GL$jw?_``cu2#> zL-D}rrlKvI=c<_+7u}gLttPt1vPso;E#`^KWidwoRYp#2jWiLlB?-Uf)f4`IR zN45JqhPFF=<@L4~_jA6@D|PUWHe4Gdm)!Bi5ufG>bzO#>bDaL^&WNW2BG=`+$9+1J zb(4;t1Xukn4#+y#XOP2CMXddyV#qhsGPEAwaVJtvJ= zy{D#r!TF+)uDuS7c{%Rt;}J!f{sV9DUwn^m-z$RWJ?Hk*XU=D5E?%5`ef>+Dy`P3J z4ISAqv!~POo>{yl+n*$MHRm3mt)8&0XUUca9!HPQ{-W5Wlds{RVej_5ooL~ltG#~y zq;k7Fv)WaH!RJ~gITZR-RB*mlZ;kRCZ#pjbLq-o=H23} z5l^2Vs0$5P%&YAFtYmVnH;O51Usm;()BU+iMn*b@f4XU*uzviSquY)u-#X4Y=9;*F zcOZ|ezh;=&?0cunTeW9TIhgw3jOL|Ut(+a@yKKF+`EHgIw@y!sI(h96tMez5Zkmlz*}L&ULp!~Ei7MQL`&2-0s`CE~&Hcp=&FeXDIMiJ}KQh#aDfEJBPg{ zK7Fgb?8hYjed7I{XWN!V4>vC;x;Xi=TI!1MLs74l6zg#NPgi!#eWelJc{OkNVdaFA z{N(X1OP)n;?Y3UHa5&s(mS49~hY>x^!E-bi8Gj*71lpC->2oqM6j3Gs_m zim&~w*(Y)lSN&bDM8Vl1v0CG9gn2e>ik=|uE#Gi;u)_6$)#|gpZdECX>sGSkbHC!e z_&Z0=-P~37$;0_p{G$Q)L(=ZvRKK$GYM(LIwT`=n*P)(gYlcrhRuyj0et%2J$bBa* zuN+>vy7cH)O&ht=^`VM$xu?}?i}$KNv+9yGbr@>{6HhmXxe!7Biw{O_RECv ztDZ!LaN^>Ix0uF|)w9p7l{H(go=muQ zv*mBE*k|Ki5^KNgj-Km%JFK*-zk+>AS9!C?f2s@#y{A6)oWJ6P-W}yU|14M4(mJOm zm^~_{q=O)7*1khg*Lj!28#auq`=rsS_FP_3Ri4F}njQPc?CWFbuwQ+~!J{F)Dh|i* zep#9&SaWRO0r>+D)l&CXulei7z#e~8r|(g?W38WZ*C6Xrq^iyeVXtiUgz2|B~q~HBF0auSi zzus0hD%!n7>7Gxq>)>1JcDMJam<(~1AN1Z+VRG30u%;6`qVJDzN?hFJ9b2ApA$;G$ zvZyTM4oTJd0Wl9K-a^jc&?7*RY6VvY$UL8NE_Ga*1kNc`o-EZ}3E+{JutUI#))WG5^ z(;Q2_KRbH#jp_NrmGcAlEqAoqzj@o?Dp$ozHA>5a>PEp1@91Ouq9!|>3?EcFHpWEG zHpyz_)`kMr?(6k8N7YApJ(ow zv$gZNS1XVBer{@<|EQ(*!mbiOwUWZjq3g5k7thHvy)vj$XLw+(rPidDk~d~8DljUt|9q#w^}?Db>0`%!kB>LK?&*I2Qn1GQmDyIC z7H-ggw7u9{P+pK8{<`tOw#K(rPfyfkS=~NWEE+qu@X!9<8=fb{U9_2){57_RQ(U3I zHn>Sunb%pz)Ggt@x9i)}W0i|vCI?Mui4!>htlT+n^d8W(KmMZ7F7;Y&0TO)hX z;SL3L8NG{}+*00Vd>`NFQ|@`nWcIK0v!8P($n^9I{rQ|l8b>^~ zSvF=JbE~a%%Ri;w{z%IZmE7%1bQk4b4!MYU18SUn5QNPiG`{_v5pL7VgA_2({f zzqu?|V^v*Ep8uJP1==qje6D`?`NDE>L`_Is#uGD-ppv>Ve%TugG;~I5>Ra-y+>&qi zvGiOk`Wm-=Dog;3y1?r&tjbNuSys5&X#R$WUAJFsZ(H^?Y`4{eJ^S6OUTx6HvY*W> zjz8yPwX-Tv|F6F@y*oV=rN?w}&v@@@<#W)CXOgO*W7}X z8tdoX60ea^>wYP&c&JT2vp$0r}ZmF_uxm21tw#r98>wxW_#WhU9#6Flb| z#{?hw%`3ezFF3xoR^Q&QotkTAXh#$Kb$yZPy+e6OCqK(6a6w_j(wvgOtM^(?8Juit z8P!y|eZ~9Q+#7|;WzPRca8JEfct z!~e*kZ$%bz#cv;*6jW|;%w87VX+yqs!>Pj3uXP(f-F$no=at5GWhm8~ug{#v|& z*LP>O+qBN6Rz=;E^$WvN>A}gez1RdT-CeD-dV1ZV~f)-e6AT*{osk_Czt@J zugPAtCs$|Cfkl?8f8-~h%|7BePu_B@w=q1{>ZI>}(ndpLR+3erj%b>H7@h*i-Drb-SIxl64L_nyY$p?ZM5Vm7jUhD#%sHa>~gnf zG%P9e*}7$!Nz~E#wjJMGD64;7wP<6-*Sd(>Cry@mWe<+3Z?F1dJwMCceOYm4a+TGg z*bDj}`#tgQ_1o9<$#+F@C8sijTV4h7x_|I-n^?*#+`Ca{L-Tz1i(QvneI<5_aVp1m zm(RYQDCb>1Mwz$cuF{3d=L(Ca7AyK0%+k;uUaEPu*An$rvu>)5XnMdi{v_nyS{|sf z-d;sDWMCgt>&?k#4d(`$Z%S|1F3hZxNf*^!#xKLy8tuGa$&W30r_=4tS=|qL-?jHz zZ_`Q{vP{2su%6yGM@xg!#gh#uHw<+f>RsPsN#~kg=W-VJRQ-6!z5lD;eP?H@_O9p| z&_~W+W%sYJv^S0@$GQoa=X-nsQ6Hhq2JG-V! z_f5N#*DR`-F#73-ly+kZ#do`|PG8=^bmDM7yHvy1;J89bB1Af1&a}B0m ziD;iOJ$1{Ze(jb|R>(g;<@EWl@nsH037P%pB)NUAOZ>d_SzO8N`0?p4ev5Gmkc)lm z)-7VsG;U;qzI(Xc&Zw|w$A^oy)m#?}k3NdBkUtz=7PXif1}~xK=WaPmZfF* z)kS_8X3D!TB=}&?(7bzFy-%*m8>rO0XMo4B_x)#D9rwKC+}}fUbV@(pC$?S-7McaU zj`1JSRW5o|T3O$b2XFEMKUORZ==tXn|EYQphnHvG2!7b3@i#%>{V`E5XOG@8XYbhO z%UnV_{VhK(X+Uu3?k))rx|vL`|1ja;!@XBtJxbYH`=s~2kf+~j;~tkrJ2y;LS8TE# z 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 literal 0 HcmV?d00001 diff --git a/lib/distutils/command/wininst-14.0.exe b/lib/distutils/command/wininst-14.0.exe new file mode 100644 index 0000000000000000000000000000000000000000..46f5f356676c800f99742deb6bf4c0a96aa166c0 GIT binary patch literal 458240 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�VkkI>$}~d(uZJ57cs;^Uz@cHejzJwU6!4Wk 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%0A7`rF^n2GrI3(dH~ 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' % ( + self.__class__.__module__, + self.__class__.__qualname__, + self.name, + id(self)) + + def read_setup_file(filename): """Reads a Setup file and returns Extension instances.""" from distutils.sysconfig import (parse_makefile, expand_makefile_vars, diff --git a/lib/distutils/filelist.py b/lib/distutils/filelist.py index db3f7a96..6522e69f 100644 --- a/lib/distutils/filelist.py +++ b/lib/distutils/filelist.py @@ -6,6 +6,7 @@ import os, re import fnmatch +import functools from distutils.util import convert_path from distutils.errors import DistutilsTemplateError, DistutilsInternalError from distutils import log @@ -242,35 +243,28 @@ def exclude_pattern (self, pattern, # ---------------------------------------------------------------------- # Utility functions +def _find_all_simple(path): + """ + Find all files under 'path' + """ + results = ( + os.path.join(base, file) + for base, dirs, files in os.walk(path, followlinks=True) + for file in files + ) + return filter(os.path.isfile, results) + + def findall(dir=os.curdir): - """Find all files under 'dir' and return the list of full filenames - (relative to 'dir'). """ - from stat import ST_MODE, S_ISREG, S_ISDIR, S_ISLNK - - list = [] - stack = [dir] - pop = stack.pop - push = stack.append - - while stack: - dir = pop() - names = os.listdir(dir) - - for name in names: - if dir != os.curdir: # avoid the dreaded "./" syndrome - fullname = os.path.join(dir, name) - else: - fullname = name - - # Avoid excess stat calls -- just one will do, thank you! - stat = os.stat(fullname) - mode = stat[ST_MODE] - if S_ISREG(mode): - list.append(fullname) - elif S_ISDIR(mode) and not S_ISLNK(mode): - push(fullname) - return list + Find all files under 'dir' and return the list of full filenames. + Unless dir is '.', return full filenames with dir prepended. + """ + files = _find_all_simple(dir) + if dir == os.curdir: + make_rel = functools.partial(os.path.relpath, start=dir) + files = map(make_rel, files) + return list(files) def glob_to_re(pattern): diff --git a/lib/distutils/msvc9compiler.py b/lib/distutils/msvc9compiler.py index a5a50100..0b1fd19f 100644 --- a/lib/distutils/msvc9compiler.py +++ b/lib/distutils/msvc9compiler.py @@ -51,7 +51,7 @@ # A map keyed by get_platform() return values to values accepted by # 'vcvarsall.bat'. Note a cross-compile may combine these (eg, 'x86_amd64' is -# the param to cross-compile on x86 targetting amd64.) +# the param to cross-compile on x86 targeting amd64.) PLAT_TO_VCVARS = { 'win32' : 'x86', 'win-amd64' : 'amd64', @@ -179,6 +179,9 @@ def get_build_version(): i = i + len(prefix) s, rest = sys.version[i:].split(" ", 1) majorVersion = int(s[:-2]) - 6 + if majorVersion >= 13: + # v13 was skipped and should be v14 + majorVersion += 1 minorVersion = int(s[2:3]) / 10.0 # I don't think paths are affected by minor version in version 6 if majorVersion == 6: diff --git a/lib/distutils/msvccompiler.py b/lib/distutils/msvccompiler.py index 81166569..1048cd41 100644 --- a/lib/distutils/msvccompiler.py +++ b/lib/distutils/msvccompiler.py @@ -157,6 +157,9 @@ def get_build_version(): i = i + len(prefix) s, rest = sys.version[i:].split(" ", 1) majorVersion = int(s[:-2]) - 6 + if majorVersion >= 13: + # v13 was skipped and should be v14 + majorVersion += 1 minorVersion = int(s[2:3]) / 10.0 # I don't think paths are affected by minor version in version 6 if majorVersion == 6: diff --git a/lib/distutils/spawn.py b/lib/distutils/spawn.py index 22e87e8f..5dd415a2 100644 --- a/lib/distutils/spawn.py +++ b/lib/distutils/spawn.py @@ -137,9 +137,6 @@ def _spawn_posix(cmd, search_path=1, verbose=0, dry_run=0): try: pid, status = os.waitpid(pid, 0) except OSError as exc: - import errno - if exc.errno == errno.EINTR: - continue if not DEBUG: cmd = executable raise DistutilsExecError( diff --git a/lib/distutils/sysconfig.py b/lib/distutils/sysconfig.py index a1452fe1..bd19ebf5 100644 --- a/lib/distutils/sysconfig.py +++ b/lib/distutils/sysconfig.py @@ -9,6 +9,7 @@ Email: """ +import _imp import os import re import sys @@ -22,23 +23,15 @@ BASE_EXEC_PREFIX = os.path.normpath(sys.base_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. +# live in project/PCBuild/win32 or project/PCBuild/amd64. # set for cross builds if "_PYTHON_PROJECT_BASE" in os.environ: project_base = os.path.abspath(os.environ["_PYTHON_PROJECT_BASE"]) else: 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 -if os.name == "nt" and "\\pc\\v" in project_base[-10:].lower(): - project_base = os.path.abspath(os.path.join(project_base, os.path.pardir, - os.path.pardir)) -# PC/AMD64 -if os.name == "nt" and "\\pcbuild\\amd64" in project_base[-14:].lower(): - project_base = os.path.abspath(os.path.join(project_base, os.path.pardir, - os.path.pardir)) +if (os.name == 'nt' and + project_base.lower().endswith(('\\pcbuild\\win32', '\\pcbuild\\amd64'))): + project_base = os.path.dirname(os.path.dirname(project_base)) # python_build: (Boolean) if true, we're either building Python or # building an extension with an un-installed Python, so we use @@ -51,11 +44,9 @@ def _is_python_source_dir(d): return True return False _sys_home = getattr(sys, '_home', None) -if _sys_home and os.name == 'nt' and \ - _sys_home.lower().endswith(('pcbuild', 'pcbuild\\amd64')): - _sys_home = os.path.dirname(_sys_home) - if _sys_home.endswith('pcbuild'): # must be amd64 - _sys_home = os.path.dirname(_sys_home) +if (_sys_home and os.name == 'nt' and + _sys_home.lower().endswith(('\\pcbuild\\win32', '\\pcbuild\\amd64'))): + _sys_home = os.path.dirname(os.path.dirname(_sys_home)) def _python_build(): if _sys_home: return _is_python_source_dir(_sys_home) @@ -102,14 +93,11 @@ 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 = _sys_home or project_base if plat_specific: - return base - if _sys_home: - incdir = os.path.join(_sys_home, get_config_var('AST_H_DIR')) + return _sys_home or project_base else: incdir = os.path.join(get_config_var('srcdir'), 'Include') - return os.path.normpath(incdir) + return os.path.normpath(incdir) python_dir = 'python' + get_python_version() + build_flags return os.path.join(prefix, "include", python_dir) elif os.name == "nt": @@ -468,7 +456,7 @@ def _init_nt(): # XXX hmmm.. a normal install puts include files here g['INCLUDEPY'] = get_python_inc(plat_specific=0) - g['EXT_SUFFIX'] = '.pyd' + g['EXT_SUFFIX'] = _imp.extension_suffixes()[0] g['EXE'] = ".exe" g['VERSION'] = get_python_version().replace(".", "") g['BINDIR'] = os.path.dirname(os.path.abspath(sys.executable)) diff --git a/lib/distutils/tests/test_archive_util.py b/lib/distutils/tests/test_archive_util.py index 2d72af4a..02fa1e27 100644 --- a/lib/distutils/tests/test_archive_util.py +++ b/lib/distutils/tests/test_archive_util.py @@ -13,7 +13,7 @@ ARCHIVE_FORMATS) from distutils.spawn import find_executable, spawn from distutils.tests import support -from test.support import check_warnings, run_unittest, patch +from test.support import check_warnings, run_unittest, patch, change_cwd try: import grp @@ -34,6 +34,16 @@ except ImportError: ZLIB_SUPPORT = False +try: + import bz2 +except ImportError: + bz2 = None + +try: + import lzma +except ImportError: + lzma = None + def can_fs_encode(filename): """ Return True if the filename can be saved in the file system. @@ -52,19 +62,36 @@ class ArchiveUtilTestCase(support.TempdirManager, unittest.TestCase): @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run') - def test_make_tarball(self): - self._make_tarball('archive') + def test_make_tarball(self, name='archive'): + # creating something to tar + tmpdir = self._create_files() + self._make_tarball(tmpdir, name, '.tar.gz') + # trying an uncompressed one + self._make_tarball(tmpdir, name, '.tar', compress=None) @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run') + def test_make_tarball_gzip(self): + tmpdir = self._create_files() + self._make_tarball(tmpdir, 'archive', '.tar.gz', compress='gzip') + + @unittest.skipUnless(bz2, 'Need bz2 support to run') + def test_make_tarball_bzip2(self): + tmpdir = self._create_files() + self._make_tarball(tmpdir, 'archive', '.tar.bz2', compress='bzip2') + + @unittest.skipUnless(lzma, 'Need lzma support to run') + def test_make_tarball_xz(self): + tmpdir = self._create_files() + self._make_tarball(tmpdir, 'archive', '.tar.xz', compress='xz') + @unittest.skipUnless(can_fs_encode('Ã¥rchiv'), 'File system cannot handle this filename') def test_make_tarball_latin1(self): """ Mirror test_make_tarball, except filename contains latin characters. """ - self._make_tarball('Ã¥rchiv') # note this isn't a real word + self.test_make_tarball('Ã¥rchiv') # note this isn't a real word - @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run') @unittest.skipUnless(can_fs_encode('ã®ã‚¢ãƒ¼ã‚«ã‚¤ãƒ–'), 'File system cannot handle this filename') def test_make_tarball_extended(self): @@ -72,16 +99,9 @@ def test_make_tarball_extended(self): Mirror test_make_tarball, except filename contains extended characters outside the latin charset. """ - self._make_tarball('ã®ã‚¢ãƒ¼ã‚«ã‚¤ãƒ–') # japanese for archive - - def _make_tarball(self, target_name): - # creating something to tar - tmpdir = self.mkdtemp() - self.write_file([tmpdir, 'file1'], 'xxx') - self.write_file([tmpdir, 'file2'], 'xxx') - os.mkdir(os.path.join(tmpdir, 'sub')) - self.write_file([tmpdir, 'sub', 'file3'], 'xxx') + self.test_make_tarball('ã®ã‚¢ãƒ¼ã‚«ã‚¤ãƒ–') # japanese for archive + def _make_tarball(self, tmpdir, target_name, suffix, **kwargs): tmpdir2 = self.mkdtemp() unittest.skipUnless(splitdrive(tmpdir)[0] == splitdrive(tmpdir2)[0], "source and target should be on same drive") @@ -89,27 +109,13 @@ def _make_tarball(self, target_name): base_name = os.path.join(tmpdir2, target_name) # working with relative paths to avoid tar warnings - old_dir = os.getcwd() - os.chdir(tmpdir) - try: - make_tarball(splitdrive(base_name)[1], '.') - finally: - os.chdir(old_dir) + with change_cwd(tmpdir): + make_tarball(splitdrive(base_name)[1], 'dist', **kwargs) # check if the compressed tarball was created - tarball = base_name + '.tar.gz' - self.assertTrue(os.path.exists(tarball)) - - # trying an uncompressed one - base_name = os.path.join(tmpdir2, target_name) - old_dir = os.getcwd() - os.chdir(tmpdir) - try: - make_tarball(splitdrive(base_name)[1], '.', compress=None) - finally: - os.chdir(old_dir) - tarball = base_name + '.tar' + tarball = base_name + suffix self.assertTrue(os.path.exists(tarball)) + self.assertEqual(self._tarinfo(tarball), self._created_files) def _tarinfo(self, path): tar = tarfile.open(path) @@ -120,6 +126,9 @@ def _tarinfo(self, path): finally: tar.close() + _created_files = ('dist', 'dist/file1', 'dist/file2', + 'dist/sub', 'dist/sub/file3', 'dist/sub2') + def _create_files(self): # creating something to tar tmpdir = self.mkdtemp() @@ -130,15 +139,15 @@ def _create_files(self): os.mkdir(os.path.join(dist, 'sub')) self.write_file([dist, 'sub', 'file3'], 'xxx') os.mkdir(os.path.join(dist, 'sub2')) - tmpdir2 = self.mkdtemp() - base_name = os.path.join(tmpdir2, 'archive') - return tmpdir, tmpdir2, base_name + return tmpdir @unittest.skipUnless(find_executable('tar') and find_executable('gzip') and ZLIB_SUPPORT, 'Need the tar, gzip and zlib command to run') def test_tarfile_vs_tar(self): - tmpdir, tmpdir2, base_name = self._create_files() + tmpdir = self._create_files() + tmpdir2 = self.mkdtemp() + base_name = os.path.join(tmpdir2, 'archive') old_dir = os.getcwd() os.chdir(tmpdir) try: @@ -164,7 +173,8 @@ def test_tarfile_vs_tar(self): self.assertTrue(os.path.exists(tarball2)) # let's compare both tarballs - self.assertEqual(self._tarinfo(tarball), self._tarinfo(tarball2)) + self.assertEqual(self._tarinfo(tarball), self._created_files) + self.assertEqual(self._tarinfo(tarball2), self._created_files) # trying an uncompressed one base_name = os.path.join(tmpdir2, 'archive') @@ -191,7 +201,8 @@ def test_tarfile_vs_tar(self): @unittest.skipUnless(find_executable('compress'), 'The compress program is required') def test_compress_deprecated(self): - tmpdir, tmpdir2, base_name = self._create_files() + tmpdir = self._create_files() + base_name = os.path.join(self.mkdtemp(), 'archive') # using compress and testing the PendingDeprecationWarning old_dir = os.getcwd() @@ -224,17 +235,17 @@ def test_compress_deprecated(self): 'Need zip and zlib support to run') def test_make_zipfile(self): # creating something to tar - tmpdir = self.mkdtemp() - self.write_file([tmpdir, 'file1'], 'xxx') - self.write_file([tmpdir, 'file2'], 'xxx') - - tmpdir2 = self.mkdtemp() - base_name = os.path.join(tmpdir2, 'archive') - make_zipfile(base_name, tmpdir) + tmpdir = self._create_files() + base_name = os.path.join(self.mkdtemp(), 'archive') + with change_cwd(tmpdir): + make_zipfile(base_name, 'dist') # check if the compressed tarball was created tarball = base_name + '.zip' self.assertTrue(os.path.exists(tarball)) + with zipfile.ZipFile(tarball) as zf: + self.assertEqual(sorted(zf.namelist()), + ['dist/file1', 'dist/file2', 'dist/sub/file3']) @unittest.skipUnless(ZIP_SUPPORT, 'Need zip support to run') def test_make_zipfile_no_zlib(self): @@ -250,18 +261,24 @@ def fake_zipfile(*a, **kw): patch(self, archive_util.zipfile, 'ZipFile', fake_zipfile) # create something to tar and compress - tmpdir, tmpdir2, base_name = self._create_files() - make_zipfile(base_name, tmpdir) + tmpdir = self._create_files() + base_name = os.path.join(self.mkdtemp(), 'archive') + with change_cwd(tmpdir): + make_zipfile(base_name, 'dist') tarball = base_name + '.zip' self.assertEqual(called, [((tarball, "w"), {'compression': zipfile.ZIP_STORED})]) self.assertTrue(os.path.exists(tarball)) + with zipfile.ZipFile(tarball) as zf: + self.assertEqual(sorted(zf.namelist()), + ['dist/file1', 'dist/file2', 'dist/sub/file3']) def test_check_archive_formats(self): self.assertEqual(check_archive_formats(['gztar', 'xxx', 'zip']), 'xxx') - self.assertEqual(check_archive_formats(['gztar', 'zip']), None) + self.assertIsNone(check_archive_formats(['gztar', 'bztar', 'xztar', + 'ztar', 'tar', 'zip'])) def test_make_archive(self): tmpdir = self.mkdtemp() @@ -282,6 +299,41 @@ def _breaks(*args, **kw): finally: del ARCHIVE_FORMATS['xxx'] + def test_make_archive_tar(self): + base_dir = self._create_files() + base_name = os.path.join(self.mkdtemp() , 'archive') + res = make_archive(base_name, 'tar', base_dir, 'dist') + self.assertTrue(os.path.exists(res)) + self.assertEqual(os.path.basename(res), 'archive.tar') + self.assertEqual(self._tarinfo(res), self._created_files) + + @unittest.skipUnless(ZLIB_SUPPORT, 'Need zlib support to run') + def test_make_archive_gztar(self): + base_dir = self._create_files() + base_name = os.path.join(self.mkdtemp() , 'archive') + res = make_archive(base_name, 'gztar', base_dir, 'dist') + self.assertTrue(os.path.exists(res)) + self.assertEqual(os.path.basename(res), 'archive.tar.gz') + self.assertEqual(self._tarinfo(res), self._created_files) + + @unittest.skipUnless(bz2, 'Need bz2 support to run') + def test_make_archive_bztar(self): + base_dir = self._create_files() + base_name = os.path.join(self.mkdtemp() , 'archive') + res = make_archive(base_name, 'bztar', base_dir, 'dist') + self.assertTrue(os.path.exists(res)) + self.assertEqual(os.path.basename(res), 'archive.tar.bz2') + self.assertEqual(self._tarinfo(res), self._created_files) + + @unittest.skipUnless(lzma, 'Need xz support to run') + def test_make_archive_xztar(self): + base_dir = self._create_files() + base_name = os.path.join(self.mkdtemp() , 'archive') + res = make_archive(base_name, 'xztar', base_dir, 'dist') + self.assertTrue(os.path.exists(res)) + self.assertEqual(os.path.basename(res), 'archive.tar.xz') + self.assertEqual(self._tarinfo(res), self._created_files) + def test_make_archive_owner_group(self): # testing make_archive with owner and group, with various combinations # this works even if there's not gid/uid support @@ -291,7 +343,8 @@ def test_make_archive_owner_group(self): else: group = owner = 'root' - base_dir, root_dir, base_name = self._create_files() + base_dir = self._create_files() + root_dir = self.mkdtemp() base_name = os.path.join(self.mkdtemp() , 'archive') res = make_archive(base_name, 'zip', root_dir, base_dir, owner=owner, group=group) @@ -311,7 +364,8 @@ def test_make_archive_owner_group(self): @unittest.skipUnless(ZLIB_SUPPORT, "Requires zlib") @unittest.skipUnless(UID_GID_SUPPORT, "Requires grp and pwd support") def test_tarfile_root_owner(self): - tmpdir, tmpdir2, base_name = self._create_files() + tmpdir = self._create_files() + base_name = os.path.join(self.mkdtemp(), 'archive') old_dir = os.getcwd() os.chdir(tmpdir) group = grp.getgrgid(0)[0] diff --git a/lib/distutils/tests/test_bdist.py b/lib/distutils/tests/test_bdist.py index 503a6e85..f762f5d9 100644 --- a/lib/distutils/tests/test_bdist.py +++ b/lib/distutils/tests/test_bdist.py @@ -21,7 +21,7 @@ def test_formats(self): # what formats does bdist offer? formats = ['bztar', 'gztar', 'msi', 'rpm', 'tar', - 'wininst', 'zip', 'ztar'] + 'wininst', 'xztar', 'zip', 'ztar'] found = sorted(cmd.format_command) self.assertEqual(found, formats) diff --git a/lib/distutils/tests/test_bdist_rpm.py b/lib/distutils/tests/test_bdist_rpm.py index 25c14abd..c5962ddd 100644 --- a/lib/distutils/tests/test_bdist_rpm.py +++ b/lib/distutils/tests/test_bdist_rpm.py @@ -3,9 +3,7 @@ import unittest import sys import os -import tempfile -import shutil -from test.support import run_unittest +from test.support import run_unittest, requires_zlib from distutils.core import Distribution from distutils.command.bdist_rpm import bdist_rpm @@ -48,6 +46,7 @@ def tearDown(self): # spurious sdtout/stderr output under Mac OS X @unittest.skipUnless(sys.platform.startswith('linux'), 'spurious sdtout/stderr output under Mac OS X') + @requires_zlib @unittest.skipIf(find_executable('rpm') is None, 'the rpm command is not found') @unittest.skipIf(find_executable('rpmbuild') is None, @@ -90,13 +89,14 @@ def test_quiet(self): # spurious sdtout/stderr output under Mac OS X @unittest.skipUnless(sys.platform.startswith('linux'), 'spurious sdtout/stderr output under Mac OS X') + @requires_zlib # http://bugs.python.org/issue1533164 @unittest.skipIf(find_executable('rpm') is None, 'the rpm command is not found') @unittest.skipIf(find_executable('rpmbuild') is None, 'the rpmbuild command is not found') def test_no_optimize_flag(self): - # let's create a package that brakes bdist_rpm + # let's create a package that breaks bdist_rpm tmp_dir = self.mkdtemp() os.environ['HOME'] = tmp_dir # to confine dir '.rpmdb' creation pkg_dir = os.path.join(tmp_dir, 'foo') diff --git a/lib/distutils/tests/test_build_ext.py b/lib/distutils/tests/test_build_ext.py index b9f407f4..ce132271 100644 --- a/lib/distutils/tests/test_build_ext.py +++ b/lib/distutils/tests/test_build_ext.py @@ -37,6 +37,16 @@ def setUp(self): from distutils.command import build_ext build_ext.USER_BASE = site.USER_BASE + # bpo-30132: On Windows, a .pdb file may be created in the current + # working directory. Create a temporary working directory to cleanup + # everything at the end of the test. + self.temp_cwd = support.temp_cwd() + self.temp_cwd.__enter__() + self.addCleanup(self.temp_cwd.__exit__, None, None, None) + + def build_ext(self, *args, **kwargs): + return build_ext(*args, **kwargs) + def test_build_ext(self): global ALREADY_TESTED copy_xxmodule_c(self.tmp_dir) @@ -44,7 +54,7 @@ def test_build_ext(self): xx_ext = Extension('xx', [xx_c]) dist = Distribution({'name': 'xx', 'ext_modules': [xx_ext]}) dist.package_dir = self.tmp_dir - cmd = build_ext(dist) + cmd = self.build_ext(dist) fixup_build_ext(cmd) cmd.build_lib = self.tmp_dir cmd.build_temp = self.tmp_dir @@ -91,7 +101,7 @@ def tearDown(self): def test_solaris_enable_shared(self): dist = Distribution({'name': 'xx'}) - cmd = build_ext(dist) + cmd = self.build_ext(dist) old = sys.platform sys.platform = 'sunos' # fooling finalize_options @@ -113,7 +123,7 @@ def test_solaris_enable_shared(self): def test_user_site(self): import site dist = Distribution({'name': 'xx'}) - cmd = build_ext(dist) + cmd = self.build_ext(dist) # making sure the user option is there options = [name for name, short, lable in @@ -144,14 +154,14 @@ def test_optional_extension(self): # with the optional argument. modules = [Extension('foo', ['xxx'], optional=False)] dist = Distribution({'name': 'xx', 'ext_modules': modules}) - cmd = build_ext(dist) + cmd = self.build_ext(dist) cmd.ensure_finalized() self.assertRaises((UnknownFileError, CompileError), cmd.run) # should raise an error modules = [Extension('foo', ['xxx'], optional=True)] dist = Distribution({'name': 'xx', 'ext_modules': modules}) - cmd = build_ext(dist) + cmd = self.build_ext(dist) cmd.ensure_finalized() cmd.run() # should pass @@ -160,7 +170,7 @@ def test_finalize_options(self): # etc.) are in the include search path. modules = [Extension('foo', ['xxx'], optional=False)] dist = Distribution({'name': 'xx', 'ext_modules': modules}) - cmd = build_ext(dist) + cmd = self.build_ext(dist) cmd.finalize_options() from distutils import sysconfig @@ -172,14 +182,14 @@ def test_finalize_options(self): # make sure cmd.libraries is turned into a list # if it's a string - cmd = build_ext(dist) + cmd = self.build_ext(dist) cmd.libraries = 'my_lib, other_lib lastlib' cmd.finalize_options() self.assertEqual(cmd.libraries, ['my_lib', 'other_lib', 'lastlib']) # make sure cmd.library_dirs is turned into a list # if it's a string - cmd = build_ext(dist) + cmd = self.build_ext(dist) cmd.library_dirs = 'my_lib_dir%sother_lib_dir' % os.pathsep cmd.finalize_options() self.assertIn('my_lib_dir', cmd.library_dirs) @@ -187,41 +197,48 @@ def test_finalize_options(self): # make sure rpath is turned into a list # if it's a string - cmd = build_ext(dist) + cmd = self.build_ext(dist) cmd.rpath = 'one%stwo' % os.pathsep cmd.finalize_options() self.assertEqual(cmd.rpath, ['one', 'two']) + # make sure cmd.link_objects is turned into a list + # if it's a string + cmd = build_ext(dist) + cmd.link_objects = 'one two,three' + cmd.finalize_options() + self.assertEqual(cmd.link_objects, ['one', 'two', 'three']) + # XXX more tests to perform for win32 # make sure define is turned into 2-tuples # strings if they are ','-separated strings - cmd = build_ext(dist) + cmd = self.build_ext(dist) cmd.define = 'one,two' cmd.finalize_options() self.assertEqual(cmd.define, [('one', '1'), ('two', '1')]) # make sure undef is turned into a list of # strings if they are ','-separated strings - cmd = build_ext(dist) + cmd = self.build_ext(dist) cmd.undef = 'one,two' cmd.finalize_options() self.assertEqual(cmd.undef, ['one', 'two']) # make sure swig_opts is turned into a list - cmd = build_ext(dist) + cmd = self.build_ext(dist) cmd.swig_opts = None cmd.finalize_options() self.assertEqual(cmd.swig_opts, []) - cmd = build_ext(dist) + cmd = self.build_ext(dist) cmd.swig_opts = '1 2' cmd.finalize_options() self.assertEqual(cmd.swig_opts, ['1', '2']) def test_check_extensions_list(self): dist = Distribution() - cmd = build_ext(dist) + cmd = self.build_ext(dist) cmd.finalize_options() #'extensions' option must be a list of Extension instances @@ -240,7 +257,7 @@ def test_check_extensions_list(self): self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts) # second element of each tuple in 'ext_modules' - # must be a ary (build info) + # must be a dictionary (build info) exts = [('foo.bar', '')] self.assertRaises(DistutilsSetupError, cmd.check_extensions_list, exts) @@ -270,16 +287,16 @@ def test_check_extensions_list(self): def test_get_source_files(self): modules = [Extension('foo', ['xxx'], optional=False)] dist = Distribution({'name': 'xx', 'ext_modules': modules}) - cmd = build_ext(dist) + cmd = self.build_ext(dist) cmd.ensure_finalized() self.assertEqual(cmd.get_source_files(), ['xxx']) def test_compiler_option(self): # cmd.compiler is an option and - # should not be overriden by a compiler instance + # should not be overridden by a compiler instance # when the command is run dist = Distribution() - cmd = build_ext(dist) + cmd = self.build_ext(dist) cmd.compiler = 'unix' cmd.ensure_finalized() cmd.run() @@ -292,7 +309,7 @@ def test_get_outputs(self): ext = Extension('foo', [c_file], optional=False) dist = Distribution({'name': 'xx', 'ext_modules': [ext]}) - cmd = build_ext(dist) + cmd = self.build_ext(dist) fixup_build_ext(cmd) cmd.ensure_finalized() self.assertEqual(len(cmd.get_outputs()), 1) @@ -355,7 +372,7 @@ def test_ext_fullpath(self): #etree_ext = Extension('lxml.etree', [etree_c]) #dist = Distribution({'name': 'lxml', 'ext_modules': [etree_ext]}) dist = Distribution() - cmd = build_ext(dist) + cmd = self.build_ext(dist) cmd.inplace = 1 cmd.distribution.package_dir = {'': 'src'} cmd.distribution.packages = ['lxml', 'lxml.html'] @@ -462,7 +479,7 @@ def _try_compile_deployment_target(self, operator, target): 'ext_modules': [deptarget_ext] }) dist.package_dir = self.tmp_dir - cmd = build_ext(dist) + cmd = self.build_ext(dist) cmd.build_lib = self.tmp_dir cmd.build_temp = self.tmp_dir @@ -481,8 +498,19 @@ def _try_compile_deployment_target(self, operator, target): self.fail("Wrong deployment target during compilation") +class ParallelBuildExtTestCase(BuildExtTestCase): + + def build_ext(self, *args, **kwargs): + build_ext = super().build_ext(*args, **kwargs) + build_ext.parallel = True + return build_ext + + def test_suite(): - return unittest.makeSuite(BuildExtTestCase) + suite = unittest.TestSuite() + suite.addTest(unittest.makeSuite(BuildExtTestCase)) + suite.addTest(unittest.makeSuite(ParallelBuildExtTestCase)) + return suite if __name__ == '__main__': - support.run_unittest(test_suite()) + support.run_unittest(__name__) diff --git a/lib/distutils/tests/test_build_py.py b/lib/distutils/tests/test_build_py.py index c8f6b899..18283dc7 100644 --- a/lib/distutils/tests/test_build_py.py +++ b/lib/distutils/tests/test_build_py.py @@ -120,8 +120,8 @@ def test_byte_compile_optimized(self): found = os.listdir(cmd.build_lib) self.assertEqual(sorted(found), ['__pycache__', 'boiledeggs.py']) found = os.listdir(os.path.join(cmd.build_lib, '__pycache__')) - self.assertEqual(sorted(found), - ['boiledeggs.%s.pyo' % sys.implementation.cache_tag]) + expect = 'boiledeggs.{}.opt-1.pyc'.format(sys.implementation.cache_tag) + self.assertEqual(sorted(found), [expect]) def test_dir_in_package_data(self): """ diff --git a/lib/distutils/tests/test_check.py b/lib/distutils/tests/test_check.py index 959fa908..3d22868e 100644 --- a/lib/distutils/tests/test_check.py +++ b/lib/distutils/tests/test_check.py @@ -7,6 +7,12 @@ from distutils.tests import support from distutils.errors import DistutilsSetupError +try: + import pygments +except ImportError: + pygments = None + + class CheckTestCase(support.LoggingSilencer, support.TempdirManager, unittest.TestCase): @@ -119,9 +125,15 @@ def foo(): pkg_info, dist = self.create_dist(long_description=rest_with_code) cmd = check(dist) cmd.check_restructuredtext() - self.assertEqual(cmd._warnings, 0) msgs = cmd._check_rst_data(rest_with_code) - self.assertEqual(len(msgs), 0) + if pygments is not None: + self.assertEqual(len(msgs), 0) + else: + self.assertEqual(len(msgs), 1) + self.assertEqual( + str(msgs[0][1]), + 'Cannot analyze code. Pygments package not found.' + ) def test_check_all(self): diff --git a/lib/distutils/tests/test_config.py b/lib/distutils/tests/test_config.py index 8286e1d9..a3844974 100644 --- a/lib/distutils/tests/test_config.py +++ b/lib/distutils/tests/test_config.py @@ -18,6 +18,7 @@ index-servers = server1 server2 + server3 [server1] username:me @@ -28,6 +29,10 @@ password: secret realm:acme repository:http://another.pypi/ + +[server3] +username:cbiggles +password:yh^%#rest-of-my-password """ PYPIRC_OLD = """\ @@ -47,14 +52,14 @@ """ -class PyPIRCCommandTestCase(support.TempdirManager, +class BasePyPIRCCommandTestCase(support.TempdirManager, support.LoggingSilencer, support.EnvironGuard, unittest.TestCase): def setUp(self): """Patches the environment.""" - super(PyPIRCCommandTestCase, self).setUp() + super(BasePyPIRCCommandTestCase, self).setUp() self.tmp_dir = self.mkdtemp() os.environ['HOME'] = self.tmp_dir self.rc = os.path.join(self.tmp_dir, '.pypirc') @@ -73,7 +78,10 @@ def initialize_options(self): def tearDown(self): """Removes the patch.""" set_threshold(self.old_threshold) - super(PyPIRCCommandTestCase, self).tearDown() + super(BasePyPIRCCommandTestCase, self).tearDown() + + +class PyPIRCCommandTestCase(BasePyPIRCCommandTestCase): def test_server_registration(self): # This test makes sure PyPIRCCommand knows how to: @@ -113,6 +121,20 @@ def test_server_empty_registration(self): finally: f.close() + def test_config_interpolation(self): + # using the % character in .pypirc should not raise an error (#20120) + self.write_file(self.rc, PYPIRC) + cmd = self._cmd(self.dist) + cmd.repository = 'server3' + config = cmd._read_pypirc() + + config = list(sorted(config.items())) + waited = [('password', 'yh^%#rest-of-my-password'), ('realm', 'pypi'), + ('repository', 'https://upload.pypi.org/legacy/'), + ('server', 'server3'), ('username', 'cbiggles')] + self.assertEqual(config, waited) + + def test_suite(): return unittest.makeSuite(PyPIRCCommandTestCase) diff --git a/lib/distutils/tests/test_filelist.py b/lib/distutils/tests/test_filelist.py index e82bc3d2..391af3cb 100644 --- a/lib/distutils/tests/test_filelist.py +++ b/lib/distutils/tests/test_filelist.py @@ -6,7 +6,9 @@ from distutils.log import WARN from distutils.errors import DistutilsTemplateError from distutils.filelist import glob_to_re, translate_pattern, FileList +from distutils import filelist +import test.support from test.support import captured_stdout, run_unittest from distutils.tests import support @@ -292,8 +294,47 @@ def test_process_template(self): self.assertWarnings() +class FindAllTestCase(unittest.TestCase): + @test.support.skip_unless_symlink + def test_missing_symlink(self): + with test.support.temp_cwd(): + os.symlink('foo', 'bar') + self.assertEqual(filelist.findall(), []) + + def test_basic_discovery(self): + """ + When findall is called with no parameters or with + '.' as the parameter, the dot should be omitted from + the results. + """ + with test.support.temp_cwd(): + os.mkdir('foo') + file1 = os.path.join('foo', 'file1.txt') + test.support.create_empty_file(file1) + os.mkdir('bar') + file2 = os.path.join('bar', 'file2.txt') + test.support.create_empty_file(file2) + expected = [file2, file1] + self.assertEqual(sorted(filelist.findall()), expected) + + def test_non_local_discovery(self): + """ + When findall is called with another path, the full + path name should be returned. + """ + with test.support.temp_dir() as temp_dir: + file1 = os.path.join(temp_dir, 'file1.txt') + test.support.create_empty_file(file1) + expected = [file1] + self.assertEqual(filelist.findall(temp_dir), expected) + + def test_suite(): - return unittest.makeSuite(FileListTestCase) + return unittest.TestSuite([ + unittest.makeSuite(FileListTestCase), + unittest.makeSuite(FindAllTestCase), + ]) + if __name__ == "__main__": run_unittest(test_suite()) diff --git a/lib/distutils/tests/test_install.py b/lib/distutils/tests/test_install.py index 18e1e575..9313330e 100644 --- a/lib/distutils/tests/test_install.py +++ b/lib/distutils/tests/test_install.py @@ -20,8 +20,6 @@ def _make_ext_name(modname): - if os.name == 'nt' and sys.executable.endswith('_d.exe'): - modname += '_d' return modname + sysconfig.get_config_var('EXT_SUFFIX') diff --git a/lib/distutils/tests/test_install_lib.py b/lib/distutils/tests/test_install_lib.py index 40dd1a95..5378aa82 100644 --- a/lib/distutils/tests/test_install_lib.py +++ b/lib/distutils/tests/test_install_lib.py @@ -44,12 +44,11 @@ def test_byte_compile(self): f = os.path.join(project_dir, 'foo.py') self.write_file(f, '# python file') cmd.byte_compile([f]) - pyc_file = importlib.util.cache_from_source('foo.py', - debug_override=True) - pyo_file = importlib.util.cache_from_source('foo.py', - debug_override=False) + pyc_file = importlib.util.cache_from_source('foo.py', optimization='') + pyc_opt_file = importlib.util.cache_from_source('foo.py', + optimization=cmd.optimize) self.assertTrue(os.path.exists(pyc_file)) - self.assertTrue(os.path.exists(pyo_file)) + self.assertTrue(os.path.exists(pyc_opt_file)) def test_get_outputs(self): project_dir, dist = self.create_dist() @@ -66,8 +65,8 @@ def test_get_outputs(self): cmd.distribution.packages = ['spam'] cmd.distribution.script_name = 'setup.py' - # get_outputs should return 4 elements: spam/__init__.py, .pyc and - # .pyo, foo.import-tag-abiflags.so / foo.pyd + # get_outputs should return 4 elements: spam/__init__.py and .pyc, + # foo.import-tag-abiflags.so / foo.pyd outputs = cmd.get_outputs() self.assertEqual(len(outputs), 4, outputs) diff --git a/lib/distutils/tests/test_msvc9compiler.py b/lib/distutils/tests/test_msvc9compiler.py index 5e18c613..77a07ef3 100644 --- a/lib/distutils/tests/test_msvc9compiler.py +++ b/lib/distutils/tests/test_msvc9compiler.py @@ -125,7 +125,7 @@ def test_reg_class(self): self.assertRaises(KeyError, Reg.get_value, 'xxx', 'xxx') # looking for values that should exist on all - # windows registeries versions. + # windows registry versions. path = r'Control Panel\Desktop' v = Reg.get_value(path, 'dragfullwindows') self.assertIn(v, ('0', '1', '2')) diff --git a/lib/distutils/tests/test_msvccompiler.py b/lib/distutils/tests/test_msvccompiler.py new file mode 100644 index 00000000..4dc24886 --- /dev/null +++ b/lib/distutils/tests/test_msvccompiler.py @@ -0,0 +1,108 @@ +"""Tests for distutils._msvccompiler.""" +import sys +import unittest +import os + +from distutils.errors import DistutilsPlatformError +from distutils.tests import support +from test.support import run_unittest + + +SKIP_MESSAGE = (None if sys.platform == "win32" else + "These tests are only for win32") + +@unittest.skipUnless(SKIP_MESSAGE is None, SKIP_MESSAGE) +class msvccompilerTestCase(support.TempdirManager, + unittest.TestCase): + + def test_no_compiler(self): + import distutils._msvccompiler as _msvccompiler + # makes sure query_vcvarsall raises + # a DistutilsPlatformError if the compiler + # is not found + def _find_vcvarsall(plat_spec): + return None, None + + old_find_vcvarsall = _msvccompiler._find_vcvarsall + _msvccompiler._find_vcvarsall = _find_vcvarsall + try: + self.assertRaises(DistutilsPlatformError, + _msvccompiler._get_vc_env, + 'wont find this version') + finally: + _msvccompiler._find_vcvarsall = old_find_vcvarsall + + def test_compiler_options(self): + import distutils._msvccompiler as _msvccompiler + # suppress path to vcruntime from _find_vcvarsall to + # check that /MT is added to compile options + old_find_vcvarsall = _msvccompiler._find_vcvarsall + def _find_vcvarsall(plat_spec): + return old_find_vcvarsall(plat_spec)[0], None + _msvccompiler._find_vcvarsall = _find_vcvarsall + try: + compiler = _msvccompiler.MSVCCompiler() + compiler.initialize() + + self.assertIn('/MT', compiler.compile_options) + self.assertNotIn('/MD', compiler.compile_options) + finally: + _msvccompiler._find_vcvarsall = old_find_vcvarsall + + def test_vcruntime_copy(self): + import distutils._msvccompiler as _msvccompiler + # force path to a known file - it doesn't matter + # what we copy as long as its name is not in + # _msvccompiler._BUNDLED_DLLS + old_find_vcvarsall = _msvccompiler._find_vcvarsall + def _find_vcvarsall(plat_spec): + return old_find_vcvarsall(plat_spec)[0], __file__ + _msvccompiler._find_vcvarsall = _find_vcvarsall + try: + tempdir = self.mkdtemp() + compiler = _msvccompiler.MSVCCompiler() + compiler.initialize() + compiler._copy_vcruntime(tempdir) + + self.assertTrue(os.path.isfile(os.path.join( + tempdir, os.path.basename(__file__)))) + finally: + _msvccompiler._find_vcvarsall = old_find_vcvarsall + + def test_vcruntime_skip_copy(self): + import distutils._msvccompiler as _msvccompiler + + tempdir = self.mkdtemp() + compiler = _msvccompiler.MSVCCompiler() + compiler.initialize() + dll = compiler._vcruntime_redist + self.assertTrue(os.path.isfile(dll)) + + compiler._copy_vcruntime(tempdir) + + self.assertFalse(os.path.isfile(os.path.join( + tempdir, os.path.basename(dll)))) + + def test_get_vc_env_unicode(self): + import distutils._msvccompiler as _msvccompiler + + test_var = 'ṰḖṤṪ┅ṼẨṜ' + test_value = '₃â´â‚…' + + # Ensure we don't early exit from _get_vc_env + old_distutils_use_sdk = os.environ.pop('DISTUTILS_USE_SDK', None) + os.environ[test_var] = test_value + try: + env = _msvccompiler._get_vc_env('x86') + self.assertIn(test_var.lower(), env) + self.assertEqual(test_value, env[test_var.lower()]) + finally: + os.environ.pop(test_var) + if old_distutils_use_sdk: + os.environ['DISTUTILS_USE_SDK'] = old_distutils_use_sdk + +def test_suite(): + return unittest.makeSuite(msvccompilerTestCase) + +if __name__ == "__main__": + run_unittest(test_suite()) diff --git a/lib/distutils/tests/test_register.py b/lib/distutils/tests/test_register.py index 61801339..e68b0af3 100644 --- a/lib/distutils/tests/test_register.py +++ b/lib/distutils/tests/test_register.py @@ -12,7 +12,7 @@ from distutils.errors import DistutilsSetupError from distutils.log import INFO -from distutils.tests.test_config import PyPIRCCommandTestCase +from distutils.tests.test_config import BasePyPIRCCommandTestCase try: import docutils @@ -72,7 +72,7 @@ def getheader(self, name, default=None): }.get(name.lower(), default) -class RegisterTestCase(PyPIRCCommandTestCase): +class RegisterTestCase(BasePyPIRCCommandTestCase): def setUp(self): super(RegisterTestCase, self).setUp() @@ -301,6 +301,20 @@ def test_list_classifiers(self): results = self.get_logs(INFO) self.assertEqual(results, ['running check', 'xxx']) + def test_show_response(self): + # test that the --show-response option return a well formatted response + cmd = self._get_cmd() + inputs = Inputs('1', 'tarek', 'y') + register_module.input = inputs.__call__ + cmd.show_response = 1 + try: + cmd.run() + finally: + del register_module.input + + results = self.get_logs(INFO) + self.assertEqual(results[3], 75 * '-' + '\nxxx\n' + 75 * '-') + def test_suite(): return unittest.makeSuite(RegisterTestCase) diff --git a/lib/distutils/tests/test_sdist.py b/lib/distutils/tests/test_sdist.py index 5a04e0dd..5444b815 100644 --- a/lib/distutils/tests/test_sdist.py +++ b/lib/distutils/tests/test_sdist.py @@ -23,7 +23,7 @@ from distutils.command.sdist import sdist, show_formats from distutils.core import Distribution -from distutils.tests.test_config import PyPIRCCommandTestCase +from distutils.tests.test_config import BasePyPIRCCommandTestCase from distutils.errors import DistutilsOptionError from distutils.spawn import find_executable from distutils.log import WARN @@ -52,7 +52,7 @@ somecode%(sep)sdoc.txt """ -class SDistTestCase(PyPIRCCommandTestCase): +class SDistTestCase(BasePyPIRCCommandTestCase): def setUp(self): # PyPIRCCommandTestCase creates a temp dir already diff --git a/lib/distutils/tests/test_unixccompiler.py b/lib/distutils/tests/test_unixccompiler.py index 3d14e12a..e171ee9c 100644 --- a/lib/distutils/tests/test_unixccompiler.py +++ b/lib/distutils/tests/test_unixccompiler.py @@ -127,7 +127,7 @@ def gcv(v): self.assertEqual(self.cc.linker_so[0], 'my_cc') @unittest.skipUnless(sys.platform == 'darwin', 'test only relevant for OS X') - def test_osx_explict_ldshared(self): + def test_osx_explicit_ldshared(self): # Issue #18080: # ensure that setting CC env variable does not change # explicit LDSHARED setting for linker diff --git a/lib/distutils/tests/test_upload.py b/lib/distutils/tests/test_upload.py index cbbbe33b..2cb2f6ce 100644 --- a/lib/distutils/tests/test_upload.py +++ b/lib/distutils/tests/test_upload.py @@ -1,15 +1,18 @@ """Tests for distutils.command.upload.""" import os import unittest +import unittest.mock as mock +from urllib.request import HTTPError + from test.support import run_unittest from distutils.command import upload as upload_mod from distutils.command.upload import upload from distutils.core import Distribution from distutils.errors import DistutilsError -from distutils.log import INFO +from distutils.log import ERROR, INFO -from distutils.tests.test_config import PYPIRC, PyPIRCCommandTestCase +from distutils.tests.test_config import PYPIRC, BasePyPIRCCommandTestCase PYPIRC_LONG_PASSWORD = """\ [distutils] @@ -63,7 +66,7 @@ def getcode(self): return self.code -class uploadTestCase(PyPIRCCommandTestCase): +class uploadTestCase(BasePyPIRCCommandTestCase): def setUp(self): super(uploadTestCase, self).setUp() @@ -127,23 +130,50 @@ def test_upload(self): # what did we send ? headers = dict(self.last_open.req.headers) - self.assertEqual(headers['Content-length'], '2161') + self.assertEqual(headers['Content-length'], '2162') content_type = headers['Content-type'] self.assertTrue(content_type.startswith('multipart/form-data')) self.assertEqual(self.last_open.req.get_method(), 'POST') expected_url = 'https://upload.pypi.org/legacy/' self.assertEqual(self.last_open.req.get_full_url(), expected_url) self.assertTrue(b'xxx' in self.last_open.req.data) + self.assertIn(b'protocol_version', self.last_open.req.data) # The PyPI response body was echoed results = self.get_logs(INFO) - self.assertIn('xyzzy\n', results[-1]) + self.assertEqual(results[-1], 75 * '-' + '\nxyzzy\n' + 75 * '-') def test_upload_fails(self): self.next_msg = "Not Found" self.next_code = 404 self.assertRaises(DistutilsError, self.test_upload) + def test_wrong_exception_order(self): + tmp = self.mkdtemp() + path = os.path.join(tmp, 'xxx') + self.write_file(path) + dist_files = [('xxx', '2.6', path)] # command, pyversion, filename + self.write_file(self.rc, PYPIRC_LONG_PASSWORD) + + pkg_dir, dist = self.create_dist(dist_files=dist_files) + tests = [ + (OSError('oserror'), 'oserror', OSError), + (HTTPError('url', 400, 'httperror', {}, None), + 'Upload failed (400): httperror', DistutilsError), + ] + for exception, expected, raised_exception in tests: + with self.subTest(exception=type(exception).__name__): + with mock.patch('distutils.command.upload.urlopen', + new=mock.Mock(side_effect=exception)): + with self.assertRaises(raised_exception): + cmd = upload(dist) + cmd.ensure_finalized() + cmd.run() + results = self.get_logs(ERROR) + self.assertIn(expected, results[-1]) + self.clear_logs() + + def test_suite(): return unittest.makeSuite(uploadTestCase) diff --git a/lib/distutils/unixccompiler.py b/lib/distutils/unixccompiler.py index 094a2f0b..3f321c28 100644 --- a/lib/distutils/unixccompiler.py +++ b/lib/distutils/unixccompiler.py @@ -76,7 +76,9 @@ class UnixCCompiler(CCompiler): static_lib_extension = ".a" shared_lib_extension = ".so" dylib_lib_extension = ".dylib" + xcode_stub_lib_extension = ".tbd" static_lib_format = shared_lib_format = dylib_lib_format = "lib%s%s" + xcode_stub_lib_format = dylib_lib_format if sys.platform == "cygwin": exe_extension = ".exe" @@ -225,6 +227,8 @@ def runtime_library_dir_option(self, dir): if sys.platform[:6] == "darwin": # MacOSX's linker doesn't understand the -R flag at all return "-L" + dir + elif sys.platform[:7] == "freebsd": + return "-Wl,-rpath=" + dir elif sys.platform[:5] == "hp-ux": if self._is_gcc(compiler): return ["-Wl,+s", "-L" + dir] @@ -255,12 +259,28 @@ def library_option(self, lib): def find_library_file(self, dirs, lib, debug=0): shared_f = self.library_filename(lib, lib_type='shared') dylib_f = self.library_filename(lib, lib_type='dylib') + xcode_stub_f = self.library_filename(lib, lib_type='xcode_stub') static_f = self.library_filename(lib, lib_type='static') if sys.platform == 'darwin': # On OSX users can specify an alternate SDK using # '-isysroot', calculate the SDK root if it is specified # (and use it further on) + # + # Note that, as of Xcode 7, Apple SDKs may contain textual stub + # libraries with .tbd extensions rather than the normal .dylib + # shared libraries installed in /. The Apple compiler tool + # chain handles this transparently but it can cause problems + # for programs that are being built with an SDK and searching + # for specific libraries. Callers of find_library_file need to + # keep in mind that the base filename of the returned SDK library + # file might have a different extension from that of the library + # file installed on the running system, for example: + # /Applications/Xcode.app/Contents/Developer/Platforms/ + # MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/ + # usr/lib/libedit.tbd + # vs + # /usr/lib/libedit.dylib cflags = sysconfig.get_config_var('CFLAGS') m = re.search(r'-isysroot\s+(\S+)', cflags) if m is None: @@ -274,6 +294,7 @@ def find_library_file(self, dirs, lib, debug=0): shared = os.path.join(dir, shared_f) dylib = os.path.join(dir, dylib_f) static = os.path.join(dir, static_f) + xcode_stub = os.path.join(dir, xcode_stub_f) if sys.platform == 'darwin' and ( dir.startswith('/System/') or ( @@ -282,6 +303,7 @@ def find_library_file(self, dirs, lib, debug=0): shared = os.path.join(sysroot, dir[1:], shared_f) dylib = os.path.join(sysroot, dir[1:], dylib_f) static = os.path.join(sysroot, dir[1:], static_f) + xcode_stub = os.path.join(sysroot, dir[1:], xcode_stub_f) # We're second-guessing the linker here, with not much hard # data to go on: GCC seems to prefer the shared library, so I'm @@ -289,6 +311,8 @@ def find_library_file(self, dirs, lib, debug=0): # ignoring even GCC's "-static" option. So sue me. if os.path.exists(dylib): return dylib + elif os.path.exists(xcode_stub): + return xcode_stub elif os.path.exists(shared): return shared elif os.path.exists(static): diff --git a/lib/distutils/util.py b/lib/distutils/util.py index 5adcac5a..e423325d 100644 --- a/lib/distutils/util.py +++ b/lib/distutils/util.py @@ -322,11 +322,11 @@ def byte_compile (py_files, prefix=None, base_dir=None, verbose=1, dry_run=0, direct=None): - """Byte-compile a collection of Python source files to either .pyc - or .pyo files in a __pycache__ subdirectory. 'py_files' is a list + """Byte-compile a collection of Python source files to .pyc + files in a __pycache__ subdirectory. 'py_files' is a list of files to compile; any files that don't end in ".py" are silently skipped. 'optimize' must be one of the following: - 0 - don't optimize (generate .pyc) + 0 - don't optimize 1 - normal optimization (like "python -O") 2 - extra optimization (like "python -OO") If 'force' is true, all files are recompiled regardless of @@ -438,8 +438,9 @@ def byte_compile (py_files, # cfile - byte-compiled file # dfile - purported source filename (same as 'file' by default) if optimize >= 0: + opt = '' if optimize == 0 else optimize cfile = importlib.util.cache_from_source( - file, debug_override=not optimize) + file, optimization=opt) else: cfile = importlib.util.cache_from_source(file) dfile = file diff --git a/lib/distutils/version.py b/lib/distutils/version.py index ebcab84e..af14cc13 100644 --- a/lib/distutils/version.py +++ b/lib/distutils/version.py @@ -48,12 +48,6 @@ def __eq__(self, other): return c return c == 0 - def __ne__(self, other): - c = self._cmp(other) - if c is NotImplemented: - return c - return c != 0 - def __lt__(self, other): c = self._cmp(other) if c is NotImplemented: diff --git a/lib/doctest.py b/lib/doctest.py index 432ef05e..38fdd80b 100644 --- a/lib/doctest.py +++ b/lib/doctest.py @@ -399,8 +399,9 @@ def _module_relative_path(module, path): basedir = os.curdir else: # A module w/o __file__ (this includes builtins) - raise ValueError("Can't resolve paths relative to the module " + - module + " (it has no __file__)") + raise ValueError("Can't resolve paths relative to the module " + "%r (it has no __file__)" + % module.__name__) # Combine the base directory and the path. return os.path.join(basedir, *(path.split('/'))) @@ -530,8 +531,9 @@ def __repr__(self): examples = '1 example' else: examples = '%d examples' % len(self.examples) - return ('' % - (self.name, self.filename, self.lineno, examples)) + return ('<%s %s from %s:%s (%s)>' % + (self.__class__.__name__, + self.name, self.filename, self.lineno, examples)) def __eq__(self, other): if type(self) is not type(other): @@ -978,7 +980,8 @@ def _find(self, tests, obj, name, module, source_lines, globs, seen): for valname, val in obj.__dict__.items(): valname = '%s.%s' % (name, valname) # Recurse to functions & classes. - if ((inspect.isroutine(val) or inspect.isclass(val)) and + if ((inspect.isroutine(inspect.unwrap(val)) + or inspect.isclass(val)) and self._from_module(module, val)): self._find(tests, val, valname, module, source_lines, globs, seen) @@ -1049,7 +1052,7 @@ def _get_test(self, obj, name, module, globs, source_lines): filename = None else: filename = getattr(module, '__file__', module.__name__) - if filename[-4:] in (".pyc", ".pyo"): + if filename[-4:] == ".pyc": filename = filename[:-1] return self._parser.get_doctest(docstring, globs, name, filename, lineno) @@ -2367,15 +2370,6 @@ def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None, suite = _DocTestSuite() suite.addTest(SkipDocTestCase(module)) return suite - elif not tests: - # Why do we want to do this? Because it reveals a bug that might - # otherwise be hidden. - # It is probably a bug that this exception is not also raised if the - # number of doctest examples in tests is zero (i.e. if no doctest - # examples were found). However, we should probably not be raising - # an exception at all here, though it is too late to make this change - # for a maintenance release. See also issue #14649. - raise ValueError(module, "has no docstrings") tests.sort() suite = _DocTestSuite() @@ -2385,7 +2379,7 @@ def DocTestSuite(module=None, globs=None, extraglobs=None, test_finder=None, continue if not test.filename: filename = module.__file__ - if filename[-4:] in (".pyc", ".pyo"): + if filename[-4:] == ".pyc": filename = filename[:-1] test.filename = filename suite.addTest(DocTestCase(test, **options)) diff --git a/lib/email/__init__.py b/lib/email/__init__.py index ff16f6af..fae87243 100644 --- a/lib/email/__init__.py +++ b/lib/email/__init__.py @@ -4,8 +4,6 @@ """A package for parsing, handling, and generating email messages.""" -__version__ = '5.1.0' - __all__ = [ 'base64mime', 'charset', diff --git a/lib/email/_header_value_parser.py b/lib/email/_header_value_parser.py index a9bdf445..5c4eb75f 100644 --- a/lib/email/_header_value_parser.py +++ b/lib/email/_header_value_parser.py @@ -320,17 +320,18 @@ def cte_encode(self, charset, policy): return ''.join(res) def _fold(self, folded): + encoding = 'utf-8' if folded.policy.utf8 else 'ascii' for part in self.parts: tstr = str(part) tlen = len(tstr) try: - str(part).encode('us-ascii') + str(part).encode(encoding) except UnicodeEncodeError: if any(isinstance(x, errors.UndecodableBytesDefect) for x in part.all_defects): charset = 'unknown-8bit' else: - # XXX: this should be a policy setting + # XXX: this should be a policy setting when utf8 is False. charset = 'utf-8' tstr = part.cte_encode(charset, folded.policy) tlen = len(tstr) @@ -340,9 +341,7 @@ def _fold(self, folded): # avoid infinite recursion. ws = part.pop_leading_fws() if ws is not None: - # Peel off the leading whitespace and make it sticky, to - # avoid infinite recursion. - folded.stickyspace = str(part.pop(0)) + folded.stickyspace = str(ws) if folded.append_if_fits(part): continue if part.has_fws: @@ -394,11 +393,12 @@ class UnstructuredTokenList(TokenList): def _fold(self, folded): last_ew = None + encoding = 'utf-8' if folded.policy.utf8 else 'ascii' for part in self.parts: tstr = str(part) is_ew = False try: - str(part).encode('us-ascii') + str(part).encode(encoding) except UnicodeEncodeError: if any(isinstance(x, errors.UndecodableBytesDefect) for x in part.all_defects): @@ -437,7 +437,7 @@ def _fold(self, folded): if folded.append_if_fits(part): continue if part.has_fws: - part.fold(folded) + part._fold(folded) continue # It can't be split...we just have to put it on its own line. folded.append(tstr) @@ -458,7 +458,7 @@ def cte_encode(self, charset, policy): last_ew = len(res) else: tl = get_unstructured(''.join(res[last_ew:] + [spart])) - res.append(tl.as_encoded_word()) + res.append(tl.as_encoded_word(charset)) return ''.join(res) @@ -475,12 +475,13 @@ def _fold(self, folded): # comment that becomes a barrier across which we can't compose encoded # words. last_ew = None + encoding = 'utf-8' if folded.policy.utf8 else 'ascii' for part in self.parts: tstr = str(part) tlen = len(tstr) has_ew = False try: - str(part).encode('us-ascii') + str(part).encode(encoding) except UnicodeEncodeError: if any(isinstance(x, errors.UndecodableBytesDefect) for x in part.all_defects): @@ -1519,7 +1520,7 @@ def get_qp_ctext(value): This is not the RFC ctext, since we are handling nested comments in comment and unquoting quoted-pairs here. We allow anything except the '()' characters, but if we find any ASCII other than the RFC defined printable - ASCII an NonPrintableDefect is added to the token's defects list. Since + ASCII, a NonPrintableDefect is added to the token's defects list. Since quoted pairs are converted to their unquoted values, what is returned is a 'ptext' token. In this case it is a WhiteSpaceTerminal, so it's value is ' '. @@ -1534,7 +1535,7 @@ def get_qcontent(value): """qcontent = qtext / quoted-pair We allow anything except the DQUOTE character, but if we find any ASCII - other than the RFC defined printable ASCII an NonPrintableDefect is + other than the RFC defined printable ASCII, a NonPrintableDefect is added to the token's defects list. Any quoted pairs are converted to their unquoted values, so what is returned is a 'ptext' token. In this case it is a ValueTerminal. @@ -1879,7 +1880,7 @@ def get_dtext(value): obs-dtext = obs-NO-WS-CTL / quoted-pair We allow anything except the excluded characters, but if we find any - ASCII other than the RFC defined printable ASCII an NonPrintableDefect is + ASCII other than the RFC defined printable ASCII, a NonPrintableDefect is added to the token's defects list. Quoted pairs are converted to their unquoted values, so what is returned is a ptext token, in this case a ValueTerminal. If there were quoted-printables, an ObsoleteHeaderDefect is @@ -1963,6 +1964,8 @@ def get_domain(value): token, value = get_dot_atom(value) except errors.HeaderParseError: token, value = get_atom(value) + if value and value[0] == '@': + raise errors.HeaderParseError('Invalid Domain') if leader is not None: token[:0] = [leader] domain.append(token) @@ -2768,6 +2771,9 @@ def get_parameter(value): while value: if value[0] in WSP: token, value = get_fws(value) + elif value[0] == '"': + token = ValueTerminal('"', 'DQUOTE') + value = value[1:] else: token, value = get_qcontent(value) v.append(token) @@ -2869,7 +2875,7 @@ def parse_content_type_header(value): _find_mime_parameters(ctype, value) return ctype ctype.append(token) - # XXX: If we really want to follow the formal grammer we should make + # XXX: If we really want to follow the formal grammar we should make # mantype and subtype specialized TokenLists here. Probably not worth it. if not value or value[0] != '/': ctype.defects.append(errors.InvalidHeaderDefect( diff --git a/lib/email/_parseaddr.py b/lib/email/_parseaddr.py index cdfa3729..41ff6f8c 100644 --- a/lib/email/_parseaddr.py +++ b/lib/email/_parseaddr.py @@ -379,7 +379,12 @@ def getaddrspec(self): aslist.append('@') self.pos += 1 self.gotonext() - return EMPTYSTRING.join(aslist) + self.getdomain() + domain = self.getdomain() + if not domain: + # Invalid domain, return an empty address instead of returning a + # local part to denote failed parsing. + return EMPTYSTRING + return EMPTYSTRING.join(aslist) + domain def getdomain(self): """Get the complete domain name from an address.""" @@ -394,6 +399,10 @@ def getdomain(self): elif self.field[self.pos] == '.': self.pos += 1 sdlist.append('.') + elif self.field[self.pos] == '@': + # bpo-34155: Don't parse domains with two `@` like + # `a@malicious.org@important.com`. + return EMPTYSTRING elif self.field[self.pos] in self.atomends: break else: diff --git a/lib/email/_policybase.py b/lib/email/_policybase.py index 81061149..7326d3a2 100644 --- a/lib/email/_policybase.py +++ b/lib/email/_policybase.py @@ -149,12 +149,18 @@ class Policy(_PolicyBase, metaclass=abc.ABCMeta): during serialization. None or 0 means no line wrapping is done. Default is 78. + mangle_from_ -- a flag that, when True escapes From_ lines in the + body of the message by putting a `>' in front of + them. This is used when the message is being + serialized by a generator. Default: True. + """ raise_on_defect = False linesep = '\n' cte_type = '8bit' max_line_length = 78 + mangle_from_ = False def handle_defect(self, obj, defect): """Based on policy, either raise defect or call register_defect. @@ -266,6 +272,8 @@ class Compat32(Policy): replicates the behavior of the email package version 5.1. """ + mangle_from_ = True + def _sanitize_header(self, name, value): # If the header value contains surrogates, return a Header using # the unknown-8bit charset to encode the bytes as encoded words. @@ -349,8 +357,12 @@ def _fold(self, name, value, sanitize): # Assume it is a Header-like object. h = value if h is not None: - parts.append(h.encode(linesep=self.linesep, - maxlinelen=self.max_line_length)) + # The Header class interprets a value of None for maxlinelen as the + # default value of 78, as recommended by RFC 2822. + maxlinelen = 0 + if self.max_line_length is not None: + maxlinelen = self.max_line_length + parts.append(h.encode(linesep=self.linesep, maxlinelen=maxlinelen)) parts.append(self.linesep) return ''.join(parts) diff --git a/lib/email/architecture.rst b/lib/email/architecture.rst index 78572ae6..fcd10bde 100644 --- a/lib/email/architecture.rst +++ b/lib/email/architecture.rst @@ -66,7 +66,7 @@ data payloads. Message Lifecycle ----------------- -The general lifecyle of a message is: +The general lifecycle of a message is: Creation A `Message` object can be created by a Parser, or it can be diff --git a/lib/email/base64mime.py b/lib/email/base64mime.py index f3bbac1c..17f0818f 100644 --- a/lib/email/base64mime.py +++ b/lib/email/base64mime.py @@ -103,7 +103,7 @@ def decode(string): """Decode a raw base64 string, returning a bytes object. This function does not parse a full MIME header value encoded with - base64 (like =?iso-8895-1?b?bmloISBuaWgh?=) -- please use the high + base64 (like =?iso-8859-1?b?bmloISBuaWgh?=) -- please use the high level email.header class for that functionality. """ if not string: diff --git a/lib/email/charset.py b/lib/email/charset.py index e9994723..ee564040 100644 --- a/lib/email/charset.py +++ b/lib/email/charset.py @@ -249,9 +249,6 @@ def __str__(self): def __eq__(self, other): return str(self) == str(other).lower() - def __ne__(self, other): - return not self.__eq__(other) - def get_body_encoding(self): """Return the content-transfer-encoding used for body encoding. diff --git a/lib/email/contentmanager.py b/lib/email/contentmanager.py index d3636529..b98ce271 100644 --- a/lib/email/contentmanager.py +++ b/lib/email/contentmanager.py @@ -126,12 +126,13 @@ def _finalize_set(msg, disposition, filename, cid, params): msg.set_param(key, value) -# XXX: This is a cleaned-up version of base64mime.body_encode. It would -# be nice to drop both this and quoprimime.body_encode in favor of -# enhanced binascii routines that accepted a max_line_length parameter. +# XXX: This is a cleaned-up version of base64mime.body_encode (including a bug +# fix in the calculation of unencoded_bytes_per_line). It would be nice to +# drop both this and quoprimime.body_encode in favor of enhanced binascii +# routines that accepted a max_line_length parameter. def _encode_base64(data, max_line_length): encoded_lines = [] - unencoded_bytes_per_line = max_line_length * 3 // 4 + unencoded_bytes_per_line = max_line_length // 4 * 3 for i in range(0, len(data), unencoded_bytes_per_line): thisline = data[i:i+unencoded_bytes_per_line] encoded_lines.append(binascii.b2a_base64(thisline).decode('ascii')) diff --git a/lib/email/feedparser.py b/lib/email/feedparser.py index c95b27f1..0b312e56 100644 --- a/lib/email/feedparser.py +++ b/lib/email/feedparser.py @@ -26,6 +26,8 @@ from email import errors from email import message from email._policybase import compat32 +from collections import deque +from io import StringIO NLCRE = re.compile('\r\n|\r|\n') NLCRE_bol = re.compile('(\r\n|\r|\n)') @@ -50,10 +52,11 @@ class BufferedSubFile(object): simple abstraction -- it parses until EOF closes the current message. """ def __init__(self): - # Chunks of the last partial line pushed into this object. - self._partial = [] - # The list of full, pushed lines, in reverse order - self._lines = [] + # Text stream of the last partial line pushed into this object. + # See issue 22233 for why this is a text stream and not a list. + self._partial = StringIO(newline='') + # A deque of full, pushed lines + self._lines = deque() # The stack of false-EOF checking predicates. self._eofstack = [] # A flag indicating whether the file has been closed or not. @@ -67,8 +70,10 @@ def pop_eof_matcher(self): def close(self): # Don't forget any trailing partial line. - self.pushlines(''.join(self._partial).splitlines(True)) - self._partial = [] + self._partial.seek(0) + self.pushlines(self._partial.readlines()) + self._partial.seek(0) + self._partial.truncate() self._closed = True def readline(self): @@ -78,49 +83,45 @@ def readline(self): return NeedMoreData # Pop the line off the stack and see if it matches the current # false-EOF predicate. - line = self._lines.pop() + line = self._lines.popleft() # RFC 2046, section 5.1.2 requires us to recognize outer level # boundaries at any level of inner nesting. Do this, but be sure it's # in the order of most to least nested. - for ateof in self._eofstack[::-1]: + for ateof in reversed(self._eofstack): if ateof(line): # We're at the false EOF. But push the last line back first. - self._lines.append(line) + self._lines.appendleft(line) return '' return line def unreadline(self, line): # Let the consumer push a line back into the buffer. assert line is not NeedMoreData - self._lines.append(line) + self._lines.appendleft(line) def push(self, data): """Push some new data into this object.""" - # Crack into lines, but preserve the linesep characters on the end of each - parts = data.splitlines(True) - - if not parts or not parts[0].endswith(('\n', '\r')): - # No new complete lines, so just accumulate partials - self._partial += parts + self._partial.write(data) + if '\n' not in data and '\r' not in data: + # No new complete lines, wait for more. return - if self._partial: - # If there are previous leftovers, complete them now - self._partial.append(parts[0]) - parts[0:1] = ''.join(self._partial).splitlines(True) - del self._partial[:] + # Crack into lines, preserving the linesep characters. + self._partial.seek(0) + parts = self._partial.readlines() + self._partial.seek(0) + self._partial.truncate() # If the last element of the list does not end in a newline, then treat # it as a partial line. We only check for '\n' here because a line # ending with '\r' might be a line that was split in the middle of a # '\r\n' sequence (see bugs 1555570 and 1721862). if not parts[-1].endswith('\n'): - self._partial = [parts.pop()] + self._partial.write(parts.pop()) self.pushlines(parts) def pushlines(self, lines): - # Reverse and insert at the front of the lines. - self._lines[:0] = lines[::-1] + self._lines.extend(lines) def __iter__(self): return self @@ -145,7 +146,7 @@ def __init__(self, _factory=None, *, policy=compat32): """ self.policy = policy - self._factory_kwds = lambda: {'policy': self.policy} + self._old_style_factory = False if _factory is None: # What this should be: #self._factory = policy.default_message_factory @@ -160,7 +161,7 @@ def __init__(self, _factory=None, *, policy=compat32): _factory(policy=self.policy) except TypeError: # Assume this is an old-style factory - self._factory_kwds = lambda: {} + self._old_style_factory = True self._input = BufferedSubFile() self._msgstack = [] self._parse = self._parsegen().__next__ @@ -197,7 +198,10 @@ def close(self): return root def _new_message(self): - msg = self._factory(**self._factory_kwds()) + if self._old_style_factory: + msg = self._factory() + else: + msg = self._factory(policy=self.policy) if self._cur and self._cur.get_content_type() == 'multipart/digest': msg.set_default_type('message/rfc822') if self._msgstack: diff --git a/lib/email/generator.py b/lib/email/generator.py index 4735721b..256278d0 100644 --- a/lib/email/generator.py +++ b/lib/email/generator.py @@ -18,6 +18,7 @@ UNDERSCORE = '_' NL = '\n' # XXX: no longer used by the code below. +NLCRE = re.compile(r'\r\n|\r|\n') fcre = re.compile(r'^From ', re.MULTILINE) @@ -32,16 +33,16 @@ class Generator: # Public interface # - def __init__(self, outfp, mangle_from_=True, maxheaderlen=None, *, + def __init__(self, outfp, mangle_from_=None, maxheaderlen=None, *, policy=None): """Create the generator for message flattening. outfp is the output file-like object for writing the message to. It must have a write() method. - Optional mangle_from_ is a flag that, when True (the default), escapes - From_ lines in the body of the message by putting a `>' in front of - them. + Optional mangle_from_ is a flag that, when True (the default if policy + is not set), escapes From_ lines in the body of the message by putting + a `>' in front of them. Optional maxheaderlen specifies the longest length for a non-continued header. When a header line is longer (in characters, with tabs @@ -56,6 +57,9 @@ def __init__(self, outfp, mangle_from_=True, maxheaderlen=None, *, flatten method is used. """ + + if mangle_from_ is None: + mangle_from_ = True if policy is None else policy.mangle_from_ self._fp = outfp self._mangle_from_ = mangle_from_ self.maxheaderlen = maxheaderlen @@ -94,7 +98,7 @@ def flatten(self, msg, unixfrom=False, linesep=None): self._NL = policy.linesep self._encoded_NL = self._encode(self._NL) self._EMPTY = '' - self._encoded_EMTPY = self._encode('') + self._encoded_EMPTY = self._encode(self._EMPTY) # Because we use clone (below) when we recursively process message # subparts, and because clone uses the computed policy (not None), # submessages will automatically get set to the computed policy when @@ -134,10 +138,6 @@ def clone(self, fp): # 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() @@ -150,14 +150,17 @@ def _write_lines(self, lines): # We have to transform the line endings. if not lines: return - lines = lines.splitlines(True) + lines = NLCRE.split(lines) for line in lines[:-1]: - self.write(line.rstrip('\r\n')) - self.write(self._NL) - laststripped = lines[-1].rstrip('\r\n') - self.write(laststripped) - if len(lines[-1]) != len(laststripped): + self.write(line) self.write(self._NL) + if lines[-1]: + self.write(lines[-1]) + # XXX logic tells me this else should be needed, but the tests fail + # with it and pass without it. (NLCRE.split ends with a blank element + # if and only if there was a trailing newline.) + #else: + # self.write(self._NL) def _write(self, msg): # We can't write the headers yet because of the following scenario: @@ -399,10 +402,6 @@ class BytesGenerator(Generator): 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')) @@ -449,7 +448,7 @@ class DecodedGenerator(Generator): Like the Generator base class, except that non-text parts are substituted with a format string representing the part. """ - def __init__(self, outfp, mangle_from_=True, maxheaderlen=78, fmt=None): + def __init__(self, outfp, mangle_from_=None, maxheaderlen=78, fmt=None): """Like Generator.__init__() except that an additional optional argument is allowed. diff --git a/lib/email/header.py b/lib/email/header.py index 9c89589e..6820ea16 100644 --- a/lib/email/header.py +++ b/lib/email/header.py @@ -262,9 +262,6 @@ def __eq__(self, other): # args and do another comparison. return other == str(self) - def __ne__(self, other): - return not self == other - def append(self, s, charset=None, errors='strict'): """Append a string to the MIME header. diff --git a/lib/email/headerregistry.py b/lib/email/headerregistry.py index 911a2afe..fafd489b 100644 --- a/lib/email/headerregistry.py +++ b/lib/email/headerregistry.py @@ -16,7 +16,7 @@ class Address: def __init__(self, display_name='', username='', domain='', addr_spec=None): - """Create an object represeting a full email address. + """Create an object representing a full email address. An address can have a 'display_name', a 'username', and a 'domain'. In addition to specifying the username and domain separately, they may be @@ -31,6 +31,11 @@ def __init__(self, display_name='', username='', domain='', addr_spec=None): without any Content Transfer Encoding. """ + + inputs = ''.join(filter(None, (display_name, username, domain, addr_spec))) + if '\r' in inputs or '\n' in inputs: + raise ValueError("invalid arguments; address parts cannot contain CR or LF") + # This clause with its potential 'raise' may only happen when an # application program creates an Address object using an addr_spec # keyword. The email library code itself must always supply username @@ -81,7 +86,8 @@ def addr_spec(self): return lp def __repr__(self): - return "Address(display_name={!r}, username={!r}, domain={!r})".format( + return "{}(display_name={!r}, username={!r}, domain={!r})".format( + self.__class__.__name__, self.display_name, self.username, self.domain) def __str__(self): @@ -108,7 +114,7 @@ class Group: def __init__(self, display_name=None, addresses=None): """Create an object representing an address group. - An address group consists of a display_name followed by colon and an + An address group consists of a display_name followed by colon and a list of addresses (see Address) terminated by a semi-colon. The Group is created by specifying a display_name and a possibly empty list of Address objects. A Group can also be used to represent a single @@ -132,7 +138,8 @@ def addresses(self): return self._addresses def __repr__(self): - return "Group(display_name={!r}, addresses={!r}".format( + return "{}(display_name={!r}, addresses={!r}".format( + self.__class__.__name__, self.display_name, self.addresses) def __str__(self): diff --git a/lib/email/message.py b/lib/email/message.py index 2f37dbb8..4b042836 100644 --- a/lib/email/message.py +++ b/lib/email/message.py @@ -710,7 +710,7 @@ def set_param(self, param, value, header='Content-Type', requote=True, message, it will be set to "text/plain" and the new parameter and value will be appended as per RFC 2045. - An alternate header can specified in the header argument, and all + An alternate header can be specified in the header argument, and all parameters will be quoted as necessary unless requote is False. If charset is specified, the parameter will be encoded according to RFC @@ -927,20 +927,21 @@ def get_charsets(self, failobj=None): """ return [part.get_content_charset(failobj) for part in self.walk()] + def get_content_disposition(self): + """Return the message's content-disposition if it exists, or None. + + The return values can be either 'inline', 'attachment' or None + according to the rfc2183. + """ + value = self.get('content-disposition') + if value is None: + return None + c_d = _splitparam(value)[0].lower() + return c_d + # I.e. def walk(self): ... from email.iterators import walk -# XXX Support for temporary deprecation hack for is_attachment property. -class _IsAttachment: - def __init__(self, value): - self.value = value - def __call__(self): - return self.value - def __bool__(self): - warnings.warn("is_attachment will be a method, not a property, in 3.5", - DeprecationWarning, - stacklevel=3) - return self.value class MIMEPart(Message): @@ -950,12 +951,9 @@ def __init__(self, policy=None): policy = default Message.__init__(self, policy) - @property def is_attachment(self): c_d = self.get('content-disposition') - result = False if c_d is None else c_d.content_disposition == 'attachment' - # XXX transitional hack to raise deprecation if not called. - return _IsAttachment(result) + return False if c_d is None else c_d.content_disposition == 'attachment' def _find_body(self, part, preferencelist): if part.is_attachment(): @@ -1024,7 +1022,7 @@ def iter_attachments(self): maintype, subtype = self.get_content_type().split('/') if maintype != 'multipart' or subtype == 'alternative': return - parts = self.get_payload() + parts = self.get_payload().copy() if maintype == 'multipart' and subtype == 'related': # For related, we treat everything but the root as an attachment. # The root may be indicated by 'start'; if there's no start or we @@ -1045,7 +1043,7 @@ def iter_attachments(self): yield from parts return # Otherwise we more or less invert the remaining logic in get_body. - # This only really works in edge cases (ex: non-text relateds or + # This only really works in edge cases (ex: non-text related or # alternatives) if the sending agent sets content-disposition. seen = [] # Only skip the first example of each candidate type. for part in parts: diff --git a/lib/email/mime/text.py b/lib/email/mime/text.py index ec18b859..da03086e 100644 --- a/lib/email/mime/text.py +++ b/lib/email/mime/text.py @@ -6,6 +6,7 @@ __all__ = ['MIMEText'] +from email.charset import Charset from email.mime.nonmultipart import MIMENonMultipart @@ -36,6 +37,6 @@ def __init__(self, _text, _subtype='plain', _charset=None): _charset = 'utf-8' MIMENonMultipart.__init__(self, 'text', _subtype, - **{'charset': _charset}) + **{'charset': str(_charset)}) self.set_payload(_text, _charset) diff --git a/lib/email/parser.py b/lib/email/parser.py index 8c9bc9e4..555b1725 100644 --- a/lib/email/parser.py +++ b/lib/email/parser.py @@ -23,7 +23,7 @@ def __init__(self, _class=None, *, policy=compat32): textual representation of the message. The string must be formatted as a block of RFC 2822 headers and header - continuation lines, optionally preceeded by a `Unix-from' header. The + continuation lines, optionally preceded by a `Unix-from' header. The header block is terminated either by the end of the string or by a blank line. @@ -87,7 +87,7 @@ def __init__(self, *args, **kw): 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 + continuation lines, optionally preceded by a `Unix-from' header. The header block is terminated either by the end of the input or by a blank line. diff --git a/lib/email/policy.py b/lib/email/policy.py index f0b20f4b..35d0e699 100644 --- a/lib/email/policy.py +++ b/lib/email/policy.py @@ -2,6 +2,7 @@ code that adds all the email6 features. """ +import re from email._policybase import Policy, Compat32, compat32, _extend_docstrings from email.utils import _has_surrogates from email.headerregistry import HeaderRegistry as HeaderRegistry @@ -18,6 +19,8 @@ 'HTTP', ] +linesep_splitter = re.compile(r'\n|\r') + @_extend_docstrings class EmailPolicy(Policy): @@ -35,6 +38,13 @@ class EmailPolicy(Policy): In addition to the settable attributes listed above that apply to all Policies, this policy adds the following additional attributes: + utf8 -- if False (the default) message headers will be + serialized as ASCII, using encoded words to encode + any non-ASCII characters in the source strings. If + True, the message headers will be serialized using + utf8 and will not contain encoded words (see RFC + 6532 for more on this serialization format). + refold_source -- if the value for a header in the Message object came from the parsing of some source, this attribute indicates whether or not a generator should refold @@ -72,6 +82,7 @@ class EmailPolicy(Policy): """ + utf8 = False refold_source = 'long' header_factory = HeaderRegistry() content_manager = raw_data_manager @@ -127,6 +138,8 @@ def header_store_parse(self, name, value): if hasattr(value, 'name') and value.name.lower() == name.lower(): return (name, value) if isinstance(value, str) and len(value.splitlines())>1: + # XXX this error message isn't quite right when we use splitlines + # (see issue 22233), but I'm not sure what should happen here. raise ValueError("Header values may not contain linefeed " "or carriage return characters") return (name, self.header_factory(name, value)) @@ -142,7 +155,9 @@ def header_fetch_parse(self, name, value): """ if hasattr(value, 'name'): return value - return self.header_factory(name, ''.join(value.splitlines())) + # We can't use splitlines here because it splits on more than \r and \n. + value = ''.join(linesep_splitter.split(value)) + return self.header_factory(name, value) def fold(self, name, value): """+ @@ -175,9 +190,13 @@ def fold_binary(self, name, value): refold_header setting, since there is no way to know whether the binary data consists of single byte characters or multibyte characters. + If utf8 is true, headers are encoded to utf8, otherwise to ascii with + non-ASCII unicode rendered as encoded words. + """ folded = self._fold(name, value, refold_binary=self.cte_type=='7bit') - return folded.encode('ascii', 'surrogateescape') + charset = 'utf8' if self.utf8 else 'ascii' + return folded.encode(charset, 'surrogateescape') def _fold(self, name, value, refold_binary=False): if hasattr(value, 'name'): @@ -199,3 +218,4 @@ def _fold(self, name, value, refold_binary=False): strict = default.clone(raise_on_defect=True) SMTP = default.clone(linesep='\r\n') HTTP = default.clone(linesep='\r\n', max_line_length=None) +SMTPUTF8 = SMTP.clone(utf8=True) diff --git a/lib/email/quoprimime.py b/lib/email/quoprimime.py index c1fe2b46..c543eb59 100644 --- a/lib/email/quoprimime.py +++ b/lib/email/quoprimime.py @@ -292,7 +292,7 @@ def header_decode(s): """Decode a string encoded with RFC 2045 MIME header `Q' encoding. This function does not parse a full MIME header value encoded with - quoted-printable (like =?iso-8895-1?q?Hello_World?=) -- please use + quoted-printable (like =?iso-8859-1?q?Hello_World?=) -- please use the high level email.header class for that functionality. """ s = s.replace('_', ' ') diff --git a/lib/email/utils.py b/lib/email/utils.py index 5080d819..a759d233 100644 --- a/lib/email/utils.py +++ b/lib/email/utils.py @@ -87,7 +87,7 @@ def formataddr(pair, charset='utf-8'): 'utf-8'. """ name, address = pair - # The address MUST (per RFC) be ascii, so raise an UnicodeError if it isn't. + # The address MUST (per RFC) be ascii, so raise a UnicodeError if it isn't. address.encode('ascii') if name: try: diff --git a/lib/encodings/aliases.py b/lib/encodings/aliases.py index 4cbaadea..67c828d6 100644 --- a/lib/encodings/aliases.py +++ b/lib/encodings/aliases.py @@ -412,6 +412,11 @@ # koi8_r codec 'cskoi8r' : 'koi8_r', + # kz1048 codec + 'kz_1048' : 'kz1048', + 'rk1048' : 'kz1048', + 'strk1048_2002' : 'kz1048', + # latin_1 codec # # Note that the latin_1 codec is implemented internally in C and a diff --git a/lib/encodings/cp65001.py b/lib/encodings/cp65001.py index 287eb877..95cb2aec 100644 --- a/lib/encodings/cp65001.py +++ b/lib/encodings/cp65001.py @@ -11,20 +11,23 @@ ### Codec APIs encode = functools.partial(codecs.code_page_encode, 65001) -decode = functools.partial(codecs.code_page_decode, 65001) +_decode = functools.partial(codecs.code_page_decode, 65001) + +def decode(input, errors='strict'): + return codecs.code_page_decode(65001, input, errors, True) class IncrementalEncoder(codecs.IncrementalEncoder): def encode(self, input, final=False): return encode(input, self.errors)[0] class IncrementalDecoder(codecs.BufferedIncrementalDecoder): - _buffer_decode = decode + _buffer_decode = _decode class StreamWriter(codecs.StreamWriter): encode = encode class StreamReader(codecs.StreamReader): - decode = decode + decode = _decode ### encodings module API diff --git a/lib/encodings/hp_roman8.py b/lib/encodings/hp_roman8.py index 23342088..58de1033 100644 --- a/lib/encodings/hp_roman8.py +++ b/lib/encodings/hp_roman8.py @@ -5,6 +5,8 @@ Original source: LaserJet IIP Printer User's Manual HP part no 33471-90901, Hewlet-Packard, June 1989. + (Used with permission) + """#" import codecs diff --git a/lib/encodings/koi8_t.py b/lib/encodings/koi8_t.py new file mode 100644 index 00000000..b5415ba3 --- /dev/null +++ b/lib/encodings/koi8_t.py @@ -0,0 +1,308 @@ +""" Python Character Mapping Codec koi8_t +""" +# http://ru.wikipedia.org/wiki/КОИ-8 +# http://www.opensource.apple.com/source/libiconv/libiconv-4/libiconv/tests/KOI8-T.TXT + +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='koi8-t', + encode=Codec().encode, + decode=Codec().decode, + incrementalencoder=IncrementalEncoder, + incrementaldecoder=IncrementalDecoder, + streamreader=StreamReader, + streamwriter=StreamWriter, + ) + + +### Decoding Table + +decoding_table = ( + '\x00' # 0x00 -> NULL + '\x01' # 0x01 -> START OF HEADING + '\x02' # 0x02 -> START OF TEXT + '\x03' # 0x03 -> END OF TEXT + '\x04' # 0x04 -> END OF TRANSMISSION + '\x05' # 0x05 -> ENQUIRY + '\x06' # 0x06 -> ACKNOWLEDGE + '\x07' # 0x07 -> BELL + '\x08' # 0x08 -> BACKSPACE + '\t' # 0x09 -> HORIZONTAL TABULATION + '\n' # 0x0A -> LINE FEED + '\x0b' # 0x0B -> VERTICAL TABULATION + '\x0c' # 0x0C -> FORM FEED + '\r' # 0x0D -> CARRIAGE RETURN + '\x0e' # 0x0E -> SHIFT OUT + '\x0f' # 0x0F -> SHIFT IN + '\x10' # 0x10 -> DATA LINK ESCAPE + '\x11' # 0x11 -> DEVICE CONTROL ONE + '\x12' # 0x12 -> DEVICE CONTROL TWO + '\x13' # 0x13 -> DEVICE CONTROL THREE + '\x14' # 0x14 -> DEVICE CONTROL FOUR + '\x15' # 0x15 -> NEGATIVE ACKNOWLEDGE + '\x16' # 0x16 -> SYNCHRONOUS IDLE + '\x17' # 0x17 -> END OF TRANSMISSION BLOCK + '\x18' # 0x18 -> CANCEL + '\x19' # 0x19 -> END OF MEDIUM + '\x1a' # 0x1A -> SUBSTITUTE + '\x1b' # 0x1B -> ESCAPE + '\x1c' # 0x1C -> FILE SEPARATOR + '\x1d' # 0x1D -> GROUP SEPARATOR + '\x1e' # 0x1E -> RECORD SEPARATOR + '\x1f' # 0x1F -> UNIT SEPARATOR + ' ' # 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 -> DELETE + '\u049b' # 0x80 -> CYRILLIC SMALL LETTER KA WITH DESCENDER + '\u0493' # 0x81 -> CYRILLIC SMALL LETTER GHE WITH STROKE + '\u201a' # 0x82 -> SINGLE LOW-9 QUOTATION MARK + '\u0492' # 0x83 -> CYRILLIC CAPITAL LETTER GHE WITH STROKE + '\u201e' # 0x84 -> DOUBLE LOW-9 QUOTATION MARK + '\u2026' # 0x85 -> HORIZONTAL ELLIPSIS + '\u2020' # 0x86 -> DAGGER + '\u2021' # 0x87 -> DOUBLE DAGGER + '\ufffe' # 0x88 -> UNDEFINED + '\u2030' # 0x89 -> PER MILLE SIGN + '\u04b3' # 0x8A -> CYRILLIC SMALL LETTER HA WITH DESCENDER + '\u2039' # 0x8B -> SINGLE LEFT-POINTING ANGLE QUOTATION MARK + '\u04b2' # 0x8C -> CYRILLIC CAPITAL LETTER HA WITH DESCENDER + '\u04b7' # 0x8D -> CYRILLIC SMALL LETTER CHE WITH DESCENDER + '\u04b6' # 0x8E -> CYRILLIC CAPITAL LETTER CHE WITH DESCENDER + '\ufffe' # 0x8F -> UNDEFINED + '\u049a' # 0x90 -> CYRILLIC CAPITAL LETTER KA WITH DESCENDER + '\u2018' # 0x91 -> LEFT SINGLE QUOTATION MARK + '\u2019' # 0x92 -> RIGHT SINGLE QUOTATION MARK + '\u201c' # 0x93 -> LEFT DOUBLE QUOTATION MARK + '\u201d' # 0x94 -> RIGHT DOUBLE QUOTATION MARK + '\u2022' # 0x95 -> BULLET + '\u2013' # 0x96 -> EN DASH + '\u2014' # 0x97 -> EM DASH + '\ufffe' # 0x98 -> UNDEFINED + '\u2122' # 0x99 -> TRADE MARK SIGN + '\ufffe' # 0x9A -> UNDEFINED + '\u203a' # 0x9B -> SINGLE RIGHT-POINTING ANGLE QUOTATION MARK + '\ufffe' # 0x9C -> UNDEFINED + '\ufffe' # 0x9D -> UNDEFINED + '\ufffe' # 0x9E -> UNDEFINED + '\ufffe' # 0x9F -> UNDEFINED + '\ufffe' # 0xA0 -> UNDEFINED + '\u04ef' # 0xA1 -> CYRILLIC SMALL LETTER U WITH MACRON + '\u04ee' # 0xA2 -> CYRILLIC CAPITAL LETTER U WITH MACRON + '\u0451' # 0xA3 -> CYRILLIC SMALL LETTER IO + '\xa4' # 0xA4 -> CURRENCY SIGN + '\u04e3' # 0xA5 -> CYRILLIC SMALL LETTER I WITH MACRON + '\xa6' # 0xA6 -> BROKEN BAR + '\xa7' # 0xA7 -> SECTION SIGN + '\ufffe' # 0xA8 -> UNDEFINED + '\ufffe' # 0xA9 -> UNDEFINED + '\ufffe' # 0xAA -> UNDEFINED + '\xab' # 0xAB -> LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + '\xac' # 0xAC -> NOT SIGN + '\xad' # 0xAD -> SOFT HYPHEN + '\xae' # 0xAE -> REGISTERED SIGN + '\ufffe' # 0xAF -> UNDEFINED + '\xb0' # 0xB0 -> DEGREE SIGN + '\xb1' # 0xB1 -> PLUS-MINUS SIGN + '\xb2' # 0xB2 -> SUPERSCRIPT TWO + '\u0401' # 0xB3 -> CYRILLIC CAPITAL LETTER IO + '\ufffe' # 0xB4 -> UNDEFINED + '\u04e2' # 0xB5 -> CYRILLIC CAPITAL LETTER I WITH MACRON + '\xb6' # 0xB6 -> PILCROW SIGN + '\xb7' # 0xB7 -> MIDDLE DOT + '\ufffe' # 0xB8 -> UNDEFINED + '\u2116' # 0xB9 -> NUMERO SIGN + '\ufffe' # 0xBA -> UNDEFINED + '\xbb' # 0xBB -> RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + '\ufffe' # 0xBC -> UNDEFINED + '\ufffe' # 0xBD -> UNDEFINED + '\ufffe' # 0xBE -> UNDEFINED + '\xa9' # 0xBF -> COPYRIGHT SIGN + '\u044e' # 0xC0 -> CYRILLIC SMALL LETTER YU + '\u0430' # 0xC1 -> CYRILLIC SMALL LETTER A + '\u0431' # 0xC2 -> CYRILLIC SMALL LETTER BE + '\u0446' # 0xC3 -> CYRILLIC SMALL LETTER TSE + '\u0434' # 0xC4 -> CYRILLIC SMALL LETTER DE + '\u0435' # 0xC5 -> CYRILLIC SMALL LETTER IE + '\u0444' # 0xC6 -> CYRILLIC SMALL LETTER EF + '\u0433' # 0xC7 -> CYRILLIC SMALL LETTER GHE + '\u0445' # 0xC8 -> CYRILLIC SMALL LETTER HA + '\u0438' # 0xC9 -> CYRILLIC SMALL LETTER I + '\u0439' # 0xCA -> CYRILLIC SMALL LETTER SHORT I + '\u043a' # 0xCB -> CYRILLIC SMALL LETTER KA + '\u043b' # 0xCC -> CYRILLIC SMALL LETTER EL + '\u043c' # 0xCD -> CYRILLIC SMALL LETTER EM + '\u043d' # 0xCE -> CYRILLIC SMALL LETTER EN + '\u043e' # 0xCF -> CYRILLIC SMALL LETTER O + '\u043f' # 0xD0 -> CYRILLIC SMALL LETTER PE + '\u044f' # 0xD1 -> CYRILLIC SMALL LETTER YA + '\u0440' # 0xD2 -> CYRILLIC SMALL LETTER ER + '\u0441' # 0xD3 -> CYRILLIC SMALL LETTER ES + '\u0442' # 0xD4 -> CYRILLIC SMALL LETTER TE + '\u0443' # 0xD5 -> CYRILLIC SMALL LETTER U + '\u0436' # 0xD6 -> CYRILLIC SMALL LETTER ZHE + '\u0432' # 0xD7 -> CYRILLIC SMALL LETTER VE + '\u044c' # 0xD8 -> CYRILLIC SMALL LETTER SOFT SIGN + '\u044b' # 0xD9 -> CYRILLIC SMALL LETTER YERU + '\u0437' # 0xDA -> CYRILLIC SMALL LETTER ZE + '\u0448' # 0xDB -> CYRILLIC SMALL LETTER SHA + '\u044d' # 0xDC -> CYRILLIC SMALL LETTER E + '\u0449' # 0xDD -> CYRILLIC SMALL LETTER SHCHA + '\u0447' # 0xDE -> CYRILLIC SMALL LETTER CHE + '\u044a' # 0xDF -> CYRILLIC SMALL LETTER HARD SIGN + '\u042e' # 0xE0 -> CYRILLIC CAPITAL LETTER YU + '\u0410' # 0xE1 -> CYRILLIC CAPITAL LETTER A + '\u0411' # 0xE2 -> CYRILLIC CAPITAL LETTER BE + '\u0426' # 0xE3 -> CYRILLIC CAPITAL LETTER TSE + '\u0414' # 0xE4 -> CYRILLIC CAPITAL LETTER DE + '\u0415' # 0xE5 -> CYRILLIC CAPITAL LETTER IE + '\u0424' # 0xE6 -> CYRILLIC CAPITAL LETTER EF + '\u0413' # 0xE7 -> CYRILLIC CAPITAL LETTER GHE + '\u0425' # 0xE8 -> CYRILLIC CAPITAL LETTER HA + '\u0418' # 0xE9 -> CYRILLIC CAPITAL LETTER I + '\u0419' # 0xEA -> CYRILLIC CAPITAL LETTER SHORT I + '\u041a' # 0xEB -> CYRILLIC CAPITAL LETTER KA + '\u041b' # 0xEC -> CYRILLIC CAPITAL LETTER EL + '\u041c' # 0xED -> CYRILLIC CAPITAL LETTER EM + '\u041d' # 0xEE -> CYRILLIC CAPITAL LETTER EN + '\u041e' # 0xEF -> CYRILLIC CAPITAL LETTER O + '\u041f' # 0xF0 -> CYRILLIC CAPITAL LETTER PE + '\u042f' # 0xF1 -> CYRILLIC CAPITAL LETTER YA + '\u0420' # 0xF2 -> CYRILLIC CAPITAL LETTER ER + '\u0421' # 0xF3 -> CYRILLIC CAPITAL LETTER ES + '\u0422' # 0xF4 -> CYRILLIC CAPITAL LETTER TE + '\u0423' # 0xF5 -> CYRILLIC CAPITAL LETTER U + '\u0416' # 0xF6 -> CYRILLIC CAPITAL LETTER ZHE + '\u0412' # 0xF7 -> CYRILLIC CAPITAL LETTER VE + '\u042c' # 0xF8 -> CYRILLIC CAPITAL LETTER SOFT SIGN + '\u042b' # 0xF9 -> CYRILLIC CAPITAL LETTER YERU + '\u0417' # 0xFA -> CYRILLIC CAPITAL LETTER ZE + '\u0428' # 0xFB -> CYRILLIC CAPITAL LETTER SHA + '\u042d' # 0xFC -> CYRILLIC CAPITAL LETTER E + '\u0429' # 0xFD -> CYRILLIC CAPITAL LETTER SHCHA + '\u0427' # 0xFE -> CYRILLIC CAPITAL LETTER CHE + '\u042a' # 0xFF -> CYRILLIC CAPITAL LETTER HARD SIGN +) + +### Encoding table +encoding_table=codecs.charmap_build(decoding_table) diff --git a/lib/encodings/kz1048.py b/lib/encodings/kz1048.py new file mode 100644 index 00000000..712aee6e --- /dev/null +++ b/lib/encodings/kz1048.py @@ -0,0 +1,307 @@ +""" Python Character Mapping Codec kz1048 generated from 'MAPPINGS/VENDORS/MISC/KZ1048.TXT' with gencodec.py. + +"""#" + +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='kz1048', + encode=Codec().encode, + decode=Codec().decode, + incrementalencoder=IncrementalEncoder, + incrementaldecoder=IncrementalDecoder, + streamreader=StreamReader, + streamwriter=StreamWriter, + ) + + +### Decoding Table + +decoding_table = ( + '\x00' # 0x00 -> NULL + '\x01' # 0x01 -> START OF HEADING + '\x02' # 0x02 -> START OF TEXT + '\x03' # 0x03 -> END OF TEXT + '\x04' # 0x04 -> END OF TRANSMISSION + '\x05' # 0x05 -> ENQUIRY + '\x06' # 0x06 -> ACKNOWLEDGE + '\x07' # 0x07 -> BELL + '\x08' # 0x08 -> BACKSPACE + '\t' # 0x09 -> HORIZONTAL TABULATION + '\n' # 0x0A -> LINE FEED + '\x0b' # 0x0B -> VERTICAL TABULATION + '\x0c' # 0x0C -> FORM FEED + '\r' # 0x0D -> CARRIAGE RETURN + '\x0e' # 0x0E -> SHIFT OUT + '\x0f' # 0x0F -> SHIFT IN + '\x10' # 0x10 -> DATA LINK ESCAPE + '\x11' # 0x11 -> DEVICE CONTROL ONE + '\x12' # 0x12 -> DEVICE CONTROL TWO + '\x13' # 0x13 -> DEVICE CONTROL THREE + '\x14' # 0x14 -> DEVICE CONTROL FOUR + '\x15' # 0x15 -> NEGATIVE ACKNOWLEDGE + '\x16' # 0x16 -> SYNCHRONOUS IDLE + '\x17' # 0x17 -> END OF TRANSMISSION BLOCK + '\x18' # 0x18 -> CANCEL + '\x19' # 0x19 -> END OF MEDIUM + '\x1a' # 0x1A -> SUBSTITUTE + '\x1b' # 0x1B -> ESCAPE + '\x1c' # 0x1C -> FILE SEPARATOR + '\x1d' # 0x1D -> GROUP SEPARATOR + '\x1e' # 0x1E -> RECORD SEPARATOR + '\x1f' # 0x1F -> UNIT SEPARATOR + ' ' # 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 -> DELETE + '\u0402' # 0x80 -> CYRILLIC CAPITAL LETTER DJE + '\u0403' # 0x81 -> CYRILLIC CAPITAL LETTER GJE + '\u201a' # 0x82 -> SINGLE LOW-9 QUOTATION MARK + '\u0453' # 0x83 -> CYRILLIC SMALL LETTER GJE + '\u201e' # 0x84 -> DOUBLE LOW-9 QUOTATION MARK + '\u2026' # 0x85 -> HORIZONTAL ELLIPSIS + '\u2020' # 0x86 -> DAGGER + '\u2021' # 0x87 -> DOUBLE DAGGER + '\u20ac' # 0x88 -> EURO SIGN + '\u2030' # 0x89 -> PER MILLE SIGN + '\u0409' # 0x8A -> CYRILLIC CAPITAL LETTER LJE + '\u2039' # 0x8B -> SINGLE LEFT-POINTING ANGLE QUOTATION MARK + '\u040a' # 0x8C -> CYRILLIC CAPITAL LETTER NJE + '\u049a' # 0x8D -> CYRILLIC CAPITAL LETTER KA WITH DESCENDER + '\u04ba' # 0x8E -> CYRILLIC CAPITAL LETTER SHHA + '\u040f' # 0x8F -> CYRILLIC CAPITAL LETTER DZHE + '\u0452' # 0x90 -> CYRILLIC SMALL LETTER DJE + '\u2018' # 0x91 -> LEFT SINGLE QUOTATION MARK + '\u2019' # 0x92 -> RIGHT SINGLE QUOTATION MARK + '\u201c' # 0x93 -> LEFT DOUBLE QUOTATION MARK + '\u201d' # 0x94 -> RIGHT DOUBLE QUOTATION MARK + '\u2022' # 0x95 -> BULLET + '\u2013' # 0x96 -> EN DASH + '\u2014' # 0x97 -> EM DASH + '\ufffe' # 0x98 -> UNDEFINED + '\u2122' # 0x99 -> TRADE MARK SIGN + '\u0459' # 0x9A -> CYRILLIC SMALL LETTER LJE + '\u203a' # 0x9B -> SINGLE RIGHT-POINTING ANGLE QUOTATION MARK + '\u045a' # 0x9C -> CYRILLIC SMALL LETTER NJE + '\u049b' # 0x9D -> CYRILLIC SMALL LETTER KA WITH DESCENDER + '\u04bb' # 0x9E -> CYRILLIC SMALL LETTER SHHA + '\u045f' # 0x9F -> CYRILLIC SMALL LETTER DZHE + '\xa0' # 0xA0 -> NO-BREAK SPACE + '\u04b0' # 0xA1 -> CYRILLIC CAPITAL LETTER STRAIGHT U WITH STROKE + '\u04b1' # 0xA2 -> CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE + '\u04d8' # 0xA3 -> CYRILLIC CAPITAL LETTER SCHWA + '\xa4' # 0xA4 -> CURRENCY SIGN + '\u04e8' # 0xA5 -> CYRILLIC CAPITAL LETTER BARRED O + '\xa6' # 0xA6 -> BROKEN BAR + '\xa7' # 0xA7 -> SECTION SIGN + '\u0401' # 0xA8 -> CYRILLIC CAPITAL LETTER IO + '\xa9' # 0xA9 -> COPYRIGHT SIGN + '\u0492' # 0xAA -> CYRILLIC CAPITAL LETTER GHE WITH STROKE + '\xab' # 0xAB -> LEFT-POINTING DOUBLE ANGLE QUOTATION MARK + '\xac' # 0xAC -> NOT SIGN + '\xad' # 0xAD -> SOFT HYPHEN + '\xae' # 0xAE -> REGISTERED SIGN + '\u04ae' # 0xAF -> CYRILLIC CAPITAL LETTER STRAIGHT U + '\xb0' # 0xB0 -> DEGREE SIGN + '\xb1' # 0xB1 -> PLUS-MINUS SIGN + '\u0406' # 0xB2 -> CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I + '\u0456' # 0xB3 -> CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I + '\u04e9' # 0xB4 -> CYRILLIC SMALL LETTER BARRED O + '\xb5' # 0xB5 -> MICRO SIGN + '\xb6' # 0xB6 -> PILCROW SIGN + '\xb7' # 0xB7 -> MIDDLE DOT + '\u0451' # 0xB8 -> CYRILLIC SMALL LETTER IO + '\u2116' # 0xB9 -> NUMERO SIGN + '\u0493' # 0xBA -> CYRILLIC SMALL LETTER GHE WITH STROKE + '\xbb' # 0xBB -> RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK + '\u04d9' # 0xBC -> CYRILLIC SMALL LETTER SCHWA + '\u04a2' # 0xBD -> CYRILLIC CAPITAL LETTER EN WITH DESCENDER + '\u04a3' # 0xBE -> CYRILLIC SMALL LETTER EN WITH DESCENDER + '\u04af' # 0xBF -> CYRILLIC SMALL LETTER STRAIGHT U + '\u0410' # 0xC0 -> CYRILLIC CAPITAL LETTER A + '\u0411' # 0xC1 -> CYRILLIC CAPITAL LETTER BE + '\u0412' # 0xC2 -> CYRILLIC CAPITAL LETTER VE + '\u0413' # 0xC3 -> CYRILLIC CAPITAL LETTER GHE + '\u0414' # 0xC4 -> CYRILLIC CAPITAL LETTER DE + '\u0415' # 0xC5 -> CYRILLIC CAPITAL LETTER IE + '\u0416' # 0xC6 -> CYRILLIC CAPITAL LETTER ZHE + '\u0417' # 0xC7 -> CYRILLIC CAPITAL LETTER ZE + '\u0418' # 0xC8 -> CYRILLIC CAPITAL LETTER I + '\u0419' # 0xC9 -> CYRILLIC CAPITAL LETTER SHORT I + '\u041a' # 0xCA -> CYRILLIC CAPITAL LETTER KA + '\u041b' # 0xCB -> CYRILLIC CAPITAL LETTER EL + '\u041c' # 0xCC -> CYRILLIC CAPITAL LETTER EM + '\u041d' # 0xCD -> CYRILLIC CAPITAL LETTER EN + '\u041e' # 0xCE -> CYRILLIC CAPITAL LETTER O + '\u041f' # 0xCF -> CYRILLIC CAPITAL LETTER PE + '\u0420' # 0xD0 -> CYRILLIC CAPITAL LETTER ER + '\u0421' # 0xD1 -> CYRILLIC CAPITAL LETTER ES + '\u0422' # 0xD2 -> CYRILLIC CAPITAL LETTER TE + '\u0423' # 0xD3 -> CYRILLIC CAPITAL LETTER U + '\u0424' # 0xD4 -> CYRILLIC CAPITAL LETTER EF + '\u0425' # 0xD5 -> CYRILLIC CAPITAL LETTER HA + '\u0426' # 0xD6 -> CYRILLIC CAPITAL LETTER TSE + '\u0427' # 0xD7 -> CYRILLIC CAPITAL LETTER CHE + '\u0428' # 0xD8 -> CYRILLIC CAPITAL LETTER SHA + '\u0429' # 0xD9 -> CYRILLIC CAPITAL LETTER SHCHA + '\u042a' # 0xDA -> CYRILLIC CAPITAL LETTER HARD SIGN + '\u042b' # 0xDB -> CYRILLIC CAPITAL LETTER YERU + '\u042c' # 0xDC -> CYRILLIC CAPITAL LETTER SOFT SIGN + '\u042d' # 0xDD -> CYRILLIC CAPITAL LETTER E + '\u042e' # 0xDE -> CYRILLIC CAPITAL LETTER YU + '\u042f' # 0xDF -> CYRILLIC CAPITAL LETTER YA + '\u0430' # 0xE0 -> CYRILLIC SMALL LETTER A + '\u0431' # 0xE1 -> CYRILLIC SMALL LETTER BE + '\u0432' # 0xE2 -> CYRILLIC SMALL LETTER VE + '\u0433' # 0xE3 -> CYRILLIC SMALL LETTER GHE + '\u0434' # 0xE4 -> CYRILLIC SMALL LETTER DE + '\u0435' # 0xE5 -> CYRILLIC SMALL LETTER IE + '\u0436' # 0xE6 -> CYRILLIC SMALL LETTER ZHE + '\u0437' # 0xE7 -> CYRILLIC SMALL LETTER ZE + '\u0438' # 0xE8 -> CYRILLIC SMALL LETTER I + '\u0439' # 0xE9 -> CYRILLIC SMALL LETTER SHORT I + '\u043a' # 0xEA -> CYRILLIC SMALL LETTER KA + '\u043b' # 0xEB -> CYRILLIC SMALL LETTER EL + '\u043c' # 0xEC -> CYRILLIC SMALL LETTER EM + '\u043d' # 0xED -> CYRILLIC SMALL LETTER EN + '\u043e' # 0xEE -> CYRILLIC SMALL LETTER O + '\u043f' # 0xEF -> CYRILLIC SMALL LETTER PE + '\u0440' # 0xF0 -> CYRILLIC SMALL LETTER ER + '\u0441' # 0xF1 -> CYRILLIC SMALL LETTER ES + '\u0442' # 0xF2 -> CYRILLIC SMALL LETTER TE + '\u0443' # 0xF3 -> CYRILLIC SMALL LETTER U + '\u0444' # 0xF4 -> CYRILLIC SMALL LETTER EF + '\u0445' # 0xF5 -> CYRILLIC SMALL LETTER HA + '\u0446' # 0xF6 -> CYRILLIC SMALL LETTER TSE + '\u0447' # 0xF7 -> CYRILLIC SMALL LETTER CHE + '\u0448' # 0xF8 -> CYRILLIC SMALL LETTER SHA + '\u0449' # 0xF9 -> CYRILLIC SMALL LETTER SHCHA + '\u044a' # 0xFA -> CYRILLIC SMALL LETTER HARD SIGN + '\u044b' # 0xFB -> CYRILLIC SMALL LETTER YERU + '\u044c' # 0xFC -> CYRILLIC SMALL LETTER SOFT SIGN + '\u044d' # 0xFD -> CYRILLIC SMALL LETTER E + '\u044e' # 0xFE -> CYRILLIC SMALL LETTER YU + '\u044f' # 0xFF -> CYRILLIC SMALL LETTER YA +) + +### Encoding table +encoding_table = codecs.charmap_build(decoding_table) diff --git a/lib/encodings/utf_16.py b/lib/encodings/utf_16.py index 809bc9ab..c6124824 100644 --- a/lib/encodings/utf_16.py +++ b/lib/encodings/utf_16.py @@ -73,7 +73,7 @@ def reset(self): self.decoder = None def getstate(self): - # additonal state info from the base class must be None here, + # additional state info from the base class must be None here, # as it isn't passed along to the caller state = codecs.BufferedIncrementalDecoder.getstate(self)[0] # additional state info we pass to the caller: diff --git a/lib/encodings/utf_32.py b/lib/encodings/utf_32.py index c0529285..cdf84d14 100644 --- a/lib/encodings/utf_32.py +++ b/lib/encodings/utf_32.py @@ -68,7 +68,7 @@ def reset(self): self.decoder = None def getstate(self): - # additonal state info from the base class must be None here, + # additional state info from the base class must be None here, # as it isn't passed along to the caller state = codecs.BufferedIncrementalDecoder.getstate(self)[0] # additional state info we pass to the caller: diff --git a/lib/encodings/uu_codec.py b/lib/encodings/uu_codec.py index 2a5728fb..4e58c62f 100644 --- a/lib/encodings/uu_codec.py +++ b/lib/encodings/uu_codec.py @@ -20,6 +20,10 @@ def uu_encode(input, errors='strict', filename='', mode=0o666): read = infile.read write = outfile.write + # Remove newline chars from filename + filename = filename.replace('\n','\\n') + filename = filename.replace('\r','\\r') + # Encode write(('begin %o %s\n' % (mode & 0o777, filename)).encode('ascii')) chunk = read(45) diff --git a/lib/enum.py b/lib/enum.py index 7cb9d45b..b8787d19 100644 --- a/lib/enum.py +++ b/lib/enum.py @@ -106,12 +106,20 @@ def __new__(metacls, cls, bases, classdict): raise ValueError('Invalid enum member name: {0}'.format( ','.join(invalid_names))) + # create a default docstring if one has not been provided + if '__doc__' not in classdict: + classdict['__doc__'] = 'An enumeration.' + # create our new Enum type enum_class = super().__new__(metacls, cls, bases, classdict) enum_class._member_names_ = [] # names in definition order enum_class._member_map_ = OrderedDict() # name->value map enum_class._member_type_ = member_type + # save attributes from super classes so we know if we can take + # the shortcut of storing members in the class dict + base_attributes = {a for b in enum_class.mro() for a in b.__dict__} + # Reverse value->name map for hashable values. enum_class._value2member_map_ = {} @@ -165,6 +173,11 @@ def __new__(metacls, cls, bases, classdict): else: # Aliases don't appear in member names (only in __members__). enum_class._member_names_.append(member_name) + # performance boost for any member that would not shadow + # a DynamicClassAttribute + if member_name not in base_attributes: + setattr(enum_class, member_name, enum_member) + # now add to _member_map_ enum_class._member_map_[member_name] = enum_member try: # This may fail if value is not hashable. We can't add the value @@ -199,7 +212,7 @@ def __bool__(self): """ return True - def __call__(cls, value, names=None, *, module=None, qualname=None, type=None): + def __call__(cls, value, names=None, *, module=None, qualname=None, type=None, start=1): """Either returns an existing member, or creates a new enum class. This method is used both when an enum class is given a value to match @@ -211,7 +224,7 @@ def __call__(cls, value, names=None, *, module=None, qualname=None, type=None): `value` will be the name of the new class. `names` should be either a string of white-space/comma delimited names - (values will start at 1), or an iterator/mapping of name, value pairs. + (values will start at `start`), or an iterator/mapping of name, value pairs. `module` should be set to the module this class is being created in; if it is not set, an attempt to find that module will be made, but if @@ -227,7 +240,7 @@ def __call__(cls, value, names=None, *, module=None, qualname=None, type=None): if names is None: # simple value lookup return cls.__new__(cls, value) # otherwise, functional API: we're creating a new Enum type - return cls._create_(value, names, module=module, qualname=qualname, type=type) + return cls._create_(value, names, module=module, qualname=qualname, type=type, start=start) def __contains__(cls, member): return isinstance(member, cls) and member._name_ in cls._member_map_ @@ -298,16 +311,16 @@ def __setattr__(cls, name, value): raise AttributeError('Cannot reassign members.') super().__setattr__(name, value) - def _create_(cls, class_name, names=None, *, module=None, qualname=None, type=None): + def _create_(cls, class_name, names=None, *, module=None, qualname=None, type=None, start=1): """Convenience method to create a new Enum class. `names` can be: * A string containing member names, separated either with spaces or - commas. Values are auto-numbered from 1. - * An iterable of member names. Values are auto-numbered from 1. + commas. Values are incremented by 1 from `start`. + * An iterable of member names. Values are incremented by 1 from `start`. * An iterable of (member name, value) pairs. - * A mapping of member name -> value. + * A mapping of member name -> value pairs. """ metacls = cls.__class__ @@ -318,7 +331,7 @@ def _create_(cls, class_name, names=None, *, module=None, qualname=None, type=No if isinstance(names, str): names = names.replace(',', ' ').split() if isinstance(names, (tuple, list)) and isinstance(names[0], str): - names = [(e, i) for (i, e) in enumerate(names, 1)] + names = [(e, i) for (i, e) in enumerate(names, start)] # Here, names is either an iterable of (name, value) or a mapping. for item in names: @@ -474,10 +487,9 @@ def __dir__(self): m for cls in self.__class__.mro() for m in cls.__dict__ - if m[0] != '_' + if m[0] != '_' and m not in self._member_map_ ] - return (['__class__', '__doc__', '__module__', 'name', 'value'] + - added_behavior) + return (['__class__', '__doc__', '__module__'] + added_behavior) def __format__(self, format_spec): # mixed-in Enums should use the mixed-in type's __format__, otherwise diff --git a/lib/fileinput.py b/lib/fileinput.py index 81a7545d..d2b52066 100644 --- a/lib/fileinput.py +++ b/lib/fileinput.py @@ -64,13 +64,6 @@ disabled when standard input is read. XXX The current implementation does not work for MS-DOS 8+3 filesystems. -Performance: this module is unfortunately one of the slower ways of -processing large numbers of input lines. Nevertheless, a significant -speed-up has been obtained by using readlines(bufsize) instead of -readline(). A new keyword argument, bufsize=N, is present on the -input() function and the FileInput() class to override the default -buffer size. - XXX Possible additions: - optional getopt argument processing @@ -86,6 +79,7 @@ _state = None +# No longer used DEFAULT_BUFSIZE = 8*1024 def input(files=None, inplace=False, backup="", bufsize=0, @@ -207,17 +201,14 @@ def __init__(self, files=None, inplace=False, backup="", bufsize=0, self._files = files self._inplace = inplace self._backup = backup - self._bufsize = bufsize or DEFAULT_BUFSIZE self._savestdout = None self._output = None self._filename = None - self._lineno = 0 + self._startlineno = 0 self._filelineno = 0 self._file = None self._isstdin = False self._backupfilename = None - self._buffer = [] - self._bufindex = 0 # restrict mode argument to reading modes if mode not in ('r', 'rU', 'U', 'rb'): raise ValueError("FileInput opening mode must be one of " @@ -253,22 +244,18 @@ def __iter__(self): return self def __next__(self): - try: - line = self._buffer[self._bufindex] - except IndexError: - pass - else: - self._bufindex += 1 - self._lineno += 1 - self._filelineno += 1 - return line - line = self.readline() - if not line: - raise StopIteration - return line + while True: + line = self._readline() + if line: + self._filelineno += 1 + return line + if not self._file: + raise StopIteration + self.nextfile() + # repeat with next file def __getitem__(self, i): - if i != self._lineno: + if i != self.lineno(): raise RuntimeError("accessing lines out of order") try: return self.__next__() @@ -277,108 +264,108 @@ def __getitem__(self, i): def nextfile(self): savestdout = self._savestdout - self._savestdout = 0 + self._savestdout = None if savestdout: sys.stdout = savestdout output = self._output - self._output = 0 + self._output = None try: if output: output.close() finally: file = self._file - self._file = 0 + self._file = None + try: + del self._readline # restore FileInput._readline + except AttributeError: + pass try: if file and not self._isstdin: file.close() finally: backupfilename = self._backupfilename - self._backupfilename = 0 + self._backupfilename = None if backupfilename and not self._backup: try: os.unlink(backupfilename) except OSError: pass self._isstdin = False - self._buffer = [] - self._bufindex = 0 def readline(self): - try: - line = self._buffer[self._bufindex] - except IndexError: - pass + while True: + line = self._readline() + if line: + self._filelineno += 1 + return line + if not self._file: + return line + self.nextfile() + # repeat with next file + + def _readline(self): + if not self._files: + if 'b' in self._mode: + return b'' + else: + return '' + self._filename = self._files[0] + self._files = self._files[1:] + self._startlineno = self.lineno() + self._filelineno = 0 + self._file = None + self._isstdin = False + self._backupfilename = 0 + if self._filename == '-': + self._filename = '' + if 'b' in self._mode: + self._file = getattr(sys.stdin, 'buffer', sys.stdin) + else: + self._file = sys.stdin + self._isstdin = True else: - self._bufindex += 1 - self._lineno += 1 - self._filelineno += 1 - return line - if not self._file: - if not self._files: - if 'b' in self._mode: - return b'' - else: - return '' - self._filename = self._files[0] - self._files = self._files[1:] - self._filelineno = 0 - self._file = None - self._isstdin = False - self._backupfilename = 0 - if self._filename == '-': - self._filename = '' - if 'b' in self._mode: - self._file = sys.stdin.buffer + if self._inplace: + self._backupfilename = ( + self._filename + (self._backup or ".bak")) + try: + os.unlink(self._backupfilename) + except OSError: + pass + # The next few lines may raise OSError + os.rename(self._filename, self._backupfilename) + self._file = open(self._backupfilename, self._mode) + try: + perm = os.fstat(self._file.fileno()).st_mode + except OSError: + self._output = open(self._filename, "w") else: - self._file = sys.stdin - self._isstdin = True - else: - if self._inplace: - self._backupfilename = ( - self._filename + (self._backup or ".bak")) + mode = os.O_CREAT | os.O_WRONLY | os.O_TRUNC + if hasattr(os, 'O_BINARY'): + mode |= os.O_BINARY + + fd = os.open(self._filename, mode, perm) + self._output = os.fdopen(fd, "w") try: - os.unlink(self._backupfilename) + if hasattr(os, 'chmod'): + os.chmod(self._filename, perm) except OSError: pass - # The next few lines may raise OSError - os.rename(self._filename, self._backupfilename) - self._file = open(self._backupfilename, self._mode) - try: - perm = os.fstat(self._file.fileno()).st_mode - except OSError: - self._output = open(self._filename, "w") - else: - mode = os.O_CREAT | os.O_WRONLY | os.O_TRUNC - if hasattr(os, 'O_BINARY'): - mode |= os.O_BINARY - - fd = os.open(self._filename, mode, perm) - self._output = os.fdopen(fd, "w") - try: - if hasattr(os, 'chmod'): - os.chmod(self._filename, perm) - except OSError: - pass - self._savestdout = sys.stdout - sys.stdout = self._output + self._savestdout = sys.stdout + sys.stdout = self._output + else: + # This may raise OSError + if self._openhook: + self._file = self._openhook(self._filename, self._mode) else: - # This may raise OSError - if self._openhook: - self._file = self._openhook(self._filename, self._mode) - else: - self._file = open(self._filename, self._mode) - self._buffer = self._file.readlines(self._bufsize) - self._bufindex = 0 - if not self._buffer: - self.nextfile() - # Recursive call - return self.readline() + self._file = open(self._filename, self._mode) + self._readline = self._file.readline # hide FileInput._readline + return self._readline() def filename(self): return self._filename def lineno(self): - return self._lineno + return self._startlineno + self._filelineno def filelineno(self): return self._filelineno diff --git a/lib/formatter.py b/lib/formatter.py index 9338261b..e2394de8 100644 --- a/lib/formatter.py +++ b/lib/formatter.py @@ -20,8 +20,8 @@ import sys import warnings -warnings.warn('the formatter module is deprecated and will be removed in ' - 'Python 3.6', PendingDeprecationWarning) +warnings.warn('the formatter module is deprecated', DeprecationWarning, + stacklevel=2) AS_IS = None diff --git a/lib/fractions.py b/lib/fractions.py index 79e83ff2..9aabab3e 100644 --- a/lib/fractions.py +++ b/lib/fractions.py @@ -20,6 +20,17 @@ def gcd(a, b): Unless b==0, the result will have the same sign as b (so that when b is divided by it, the result comes out positive). """ + import warnings + warnings.warn('fractions.gcd() is deprecated. Use math.gcd() instead.', + DeprecationWarning, 2) + if type(a) is int is type(b): + if (b or a) < 0: + return -math.gcd(a, b) + return math.gcd(a, b) + return _gcd(a, b) + +def _gcd(a, b): + # Supports non-integers for backward compatibility. while b: a, b = b, a%b return a @@ -70,7 +81,7 @@ class Fraction(numbers.Rational): __slots__ = ('_numerator', '_denominator') # We're immutable, so use __new__ not __init__ - def __new__(cls, numerator=0, denominator=None): + def __new__(cls, numerator=0, denominator=None, _normalize=True): """Constructs a Rational. Takes a string like '3/2' or '1.5', another Rational instance, a @@ -104,7 +115,12 @@ def __new__(cls, numerator=0, denominator=None): self = super(Fraction, cls).__new__(cls) if denominator is None: - if isinstance(numerator, numbers.Rational): + if type(numerator) is int: + self._numerator = numerator + self._denominator = 1 + return self + + elif isinstance(numerator, numbers.Rational): self._numerator = numerator.numerator self._denominator = numerator.denominator return self @@ -153,6 +169,9 @@ def __new__(cls, numerator=0, denominator=None): raise TypeError("argument should be a string " "or a Rational instance") + elif type(numerator) is int is type(denominator): + pass # *very* normal case + elif (isinstance(numerator, numbers.Rational) and isinstance(denominator, numbers.Rational)): numerator, denominator = ( @@ -165,9 +184,18 @@ def __new__(cls, numerator=0, denominator=None): if denominator == 0: raise ZeroDivisionError('Fraction(%s, 0)' % numerator) - g = gcd(numerator, denominator) - self._numerator = numerator // g - self._denominator = denominator // g + if _normalize: + if type(numerator) is int is type(denominator): + # *very* normal case + g = math.gcd(numerator, denominator) + if denominator < 0: + g = -g + else: + g = _gcd(numerator, denominator) + numerator //= g + denominator //= g + self._numerator = numerator + self._denominator = denominator return self @classmethod @@ -277,7 +305,8 @@ def denominator(a): def __repr__(self): """repr(self)""" - return ('Fraction(%s, %s)' % (self._numerator, self._denominator)) + return '%s(%s, %s)' % (self.__class__.__name__, + self._numerator, self._denominator) def __str__(self): """str(self)""" @@ -395,17 +424,17 @@ def reverse(b, a): def _add(a, b): """a + b""" - return Fraction(a.numerator * b.denominator + - b.numerator * a.denominator, - a.denominator * b.denominator) + da, db = a.denominator, b.denominator + return Fraction(a.numerator * db + b.numerator * da, + da * db) __add__, __radd__ = _operator_fallbacks(_add, operator.add) def _sub(a, b): """a - b""" - return Fraction(a.numerator * b.denominator - - b.numerator * a.denominator, - a.denominator * b.denominator) + da, db = a.denominator, b.denominator + return Fraction(a.numerator * db - b.numerator * da, + da * db) __sub__, __rsub__ = _operator_fallbacks(_sub, operator.sub) @@ -453,10 +482,16 @@ def __pow__(a, b): power = b.numerator if power >= 0: return Fraction(a._numerator ** power, - a._denominator ** power) - else: + a._denominator ** power, + _normalize=False) + elif a._numerator >= 0: return Fraction(a._denominator ** -power, - a._numerator ** -power) + a._numerator ** -power, + _normalize=False) + else: + return Fraction((-a._denominator) ** -power, + (-a._numerator) ** -power, + _normalize=False) else: # A fractional power will generally produce an # irrational number. @@ -480,15 +515,15 @@ def __rpow__(b, a): def __pos__(a): """+a: Coerces a subclass instance to Fraction""" - return Fraction(a._numerator, a._denominator) + return Fraction(a._numerator, a._denominator, _normalize=False) def __neg__(a): """-a""" - return Fraction(-a._numerator, a._denominator) + return Fraction(-a._numerator, a._denominator, _normalize=False) def __abs__(a): """abs(a)""" - return Fraction(abs(a._numerator), a._denominator) + return Fraction(abs(a._numerator), a._denominator, _normalize=False) def __trunc__(a): """trunc(a)""" @@ -555,6 +590,8 @@ def __hash__(self): def __eq__(a, b): """a == b""" + if type(b) is int: + return a._numerator == b and a._denominator == 1 if isinstance(b, numbers.Rational): return (a._numerator == b.numerator and a._denominator == b.denominator) diff --git a/lib/ftplib.py b/lib/ftplib.py index 47f02f8b..2416fc68 100644 --- a/lib/ftplib.py +++ b/lib/ftplib.py @@ -42,7 +42,7 @@ import warnings from socket import _GLOBAL_DEFAULT_TIMEOUT -__all__ = ["FTP", "Netrc"] +__all__ = ["FTP"] # Magic number from MSG_OOB = 0x1 # Process data out of band @@ -925,115 +925,6 @@ def ftpcp(source, sourcename, target, targetname = '', type = 'I'): target.voidresp() -class Netrc: - """Class to parse & provide access to 'netrc' format files. - - See the netrc(4) man page for information on the file format. - - WARNING: This class is obsolete -- use module netrc instead. - - """ - __defuser = None - __defpasswd = None - __defacct = None - - def __init__(self, filename=None): - warnings.warn("This class is deprecated, use the netrc module instead", - DeprecationWarning, 2) - if filename is None: - if "HOME" in os.environ: - filename = os.path.join(os.environ["HOME"], - ".netrc") - else: - raise OSError("specify file to load or set $HOME") - self.__hosts = {} - self.__macros = {} - fp = open(filename, "r") - in_macro = 0 - while 1: - line = fp.readline() - if not line: - break - if in_macro and line.strip(): - macro_lines.append(line) - continue - elif in_macro: - self.__macros[macro_name] = tuple(macro_lines) - in_macro = 0 - words = line.split() - host = user = passwd = acct = None - default = 0 - i = 0 - while i < len(words): - w1 = words[i] - if i+1 < len(words): - w2 = words[i + 1] - else: - w2 = None - if w1 == 'default': - default = 1 - elif w1 == 'machine' and w2: - host = w2.lower() - i = i + 1 - elif w1 == 'login' and w2: - user = w2 - i = i + 1 - elif w1 == 'password' and w2: - passwd = w2 - i = i + 1 - elif w1 == 'account' and w2: - acct = w2 - i = i + 1 - elif w1 == 'macdef' and w2: - macro_name = w2 - macro_lines = [] - in_macro = 1 - break - i = i + 1 - if default: - self.__defuser = user or self.__defuser - self.__defpasswd = passwd or self.__defpasswd - self.__defacct = acct or self.__defacct - if host: - if host in self.__hosts: - ouser, opasswd, oacct = \ - self.__hosts[host] - user = user or ouser - passwd = passwd or opasswd - acct = acct or oacct - self.__hosts[host] = user, passwd, acct - fp.close() - - def get_hosts(self): - """Return a list of hosts mentioned in the .netrc file.""" - return self.__hosts.keys() - - def get_account(self, host): - """Returns login information for the named host. - - The return value is a triple containing userid, - password, and the accounting field. - - """ - host = host.lower() - user = passwd = acct = None - if host in self.__hosts: - user, passwd, acct = self.__hosts[host] - user = user or self.__defuser - passwd = passwd or self.__defpasswd - acct = acct or self.__defacct - return user, passwd, acct - - def get_macros(self): - """Return a list of all defined macro names.""" - return self.__macros.keys() - - def get_macro(self, macro): - """Return a sequence of lines which define a named macro.""" - return self.__macros[macro] - - - def test(): '''Test program. Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ... @@ -1047,6 +938,8 @@ def test(): print(test.__doc__) sys.exit(0) + import netrc + debugging = 0 rcfile = None while sys.argv[1] == '-d': @@ -1061,14 +954,14 @@ def test(): ftp.set_debuglevel(debugging) userid = passwd = acct = '' try: - netrc = Netrc(rcfile) + netrcobj = netrc.netrc(rcfile) except OSError: if rcfile is not None: sys.stderr.write("Could not open account file" " -- using anonymous login.") else: try: - userid, passwd, acct = netrc.get_account(host) + userid, acct, passwd = netrcobj.authenticators(host) except KeyError: # no account for host sys.stderr.write( diff --git a/lib/functools.py b/lib/functools.py index 2c299d71..60cf3c41 100644 --- a/lib/functools.py +++ b/lib/functools.py @@ -23,7 +23,7 @@ from weakref import WeakKeyDictionary try: from _thread import RLock -except: +except ImportError: class RLock: 'Dummy reentrant lock for builds without threads' def __enter__(self): pass @@ -94,108 +94,109 @@ def wraps(wrapped, # infinite recursion that could occur when the operator dispatch logic # detects a NotImplemented result and then calls a reflected method. -def _gt_from_lt(self, other): +def _gt_from_lt(self, other, NotImplemented=NotImplemented): 'Return a > b. Computed by @total_ordering from (not a < b) and (a != b).' op_result = self.__lt__(other) if op_result is NotImplemented: - return NotImplemented + return op_result return not op_result and self != other -def _le_from_lt(self, other): +def _le_from_lt(self, other, NotImplemented=NotImplemented): 'Return a <= b. Computed by @total_ordering from (a < b) or (a == b).' op_result = self.__lt__(other) return op_result or self == other -def _ge_from_lt(self, other): +def _ge_from_lt(self, other, NotImplemented=NotImplemented): 'Return a >= b. Computed by @total_ordering from (not a < b).' op_result = self.__lt__(other) if op_result is NotImplemented: - return NotImplemented + return op_result return not op_result -def _ge_from_le(self, other): +def _ge_from_le(self, other, NotImplemented=NotImplemented): 'Return a >= b. Computed by @total_ordering from (not a <= b) or (a == b).' op_result = self.__le__(other) if op_result is NotImplemented: - return NotImplemented + return op_result return not op_result or self == other -def _lt_from_le(self, other): +def _lt_from_le(self, other, NotImplemented=NotImplemented): 'Return a < b. Computed by @total_ordering from (a <= b) and (a != b).' op_result = self.__le__(other) if op_result is NotImplemented: - return NotImplemented + return op_result return op_result and self != other -def _gt_from_le(self, other): +def _gt_from_le(self, other, NotImplemented=NotImplemented): 'Return a > b. Computed by @total_ordering from (not a <= b).' op_result = self.__le__(other) if op_result is NotImplemented: - return NotImplemented + return op_result return not op_result -def _lt_from_gt(self, other): +def _lt_from_gt(self, other, NotImplemented=NotImplemented): 'Return a < b. Computed by @total_ordering from (not a > b) and (a != b).' op_result = self.__gt__(other) if op_result is NotImplemented: - return NotImplemented + return op_result return not op_result and self != other -def _ge_from_gt(self, other): +def _ge_from_gt(self, other, NotImplemented=NotImplemented): 'Return a >= b. Computed by @total_ordering from (a > b) or (a == b).' op_result = self.__gt__(other) return op_result or self == other -def _le_from_gt(self, other): +def _le_from_gt(self, other, NotImplemented=NotImplemented): 'Return a <= b. Computed by @total_ordering from (not a > b).' op_result = self.__gt__(other) if op_result is NotImplemented: - return NotImplemented + return op_result return not op_result -def _le_from_ge(self, other): +def _le_from_ge(self, other, NotImplemented=NotImplemented): 'Return a <= b. Computed by @total_ordering from (not a >= b) or (a == b).' op_result = self.__ge__(other) if op_result is NotImplemented: - return NotImplemented + return op_result return not op_result or self == other -def _gt_from_ge(self, other): +def _gt_from_ge(self, other, NotImplemented=NotImplemented): 'Return a > b. Computed by @total_ordering from (a >= b) and (a != b).' op_result = self.__ge__(other) if op_result is NotImplemented: - return NotImplemented + return op_result return op_result and self != other -def _lt_from_ge(self, other): +def _lt_from_ge(self, other, NotImplemented=NotImplemented): 'Return a < b. Computed by @total_ordering from (not a >= b).' op_result = self.__ge__(other) if op_result is NotImplemented: - return NotImplemented + return op_result return not op_result +_convert = { + '__lt__': [('__gt__', _gt_from_lt), + ('__le__', _le_from_lt), + ('__ge__', _ge_from_lt)], + '__le__': [('__ge__', _ge_from_le), + ('__lt__', _lt_from_le), + ('__gt__', _gt_from_le)], + '__gt__': [('__lt__', _lt_from_gt), + ('__ge__', _ge_from_gt), + ('__le__', _le_from_gt)], + '__ge__': [('__le__', _le_from_ge), + ('__gt__', _gt_from_ge), + ('__lt__', _lt_from_ge)] +} + def total_ordering(cls): """Class decorator that fills in missing ordering methods""" - convert = { - '__lt__': [('__gt__', _gt_from_lt), - ('__le__', _le_from_lt), - ('__ge__', _ge_from_lt)], - '__le__': [('__ge__', _ge_from_le), - ('__lt__', _lt_from_le), - ('__gt__', _gt_from_le)], - '__gt__': [('__lt__', _lt_from_gt), - ('__ge__', _ge_from_gt), - ('__le__', _le_from_gt)], - '__ge__': [('__le__', _le_from_ge), - ('__gt__', _gt_from_ge), - ('__lt__', _lt_from_ge)] - } # 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)] + 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]: + for opname, opfunc in _convert[root]: if opname not in roots: opfunc.__name__ = opname setattr(cls, opname, opfunc) @@ -222,8 +223,6 @@ 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 __hash__ = None return K @@ -242,6 +241,14 @@ def partial(func, *args, **keywords): """New function with partial application of the given arguments and keywords. """ + if hasattr(func, 'func'): + args = func.args + args + tmpkw = func.keywords.copy() + tmpkw.update(keywords) + keywords = tmpkw + del tmpkw + func = func.func + def newfunc(*fargs, **fkeywords): newkeywords = keywords.copy() newkeywords.update(fkeywords) @@ -291,7 +298,7 @@ def __repr__(self): for k, v in self.keywords.items()) format_string = "{module}.{cls}({func}, {args}, {keywords})" return format_string.format(module=self.__class__.__module__, - cls=self.__class__.__name__, + cls=self.__class__.__qualname__, func=self.func, args=args, keywords=keywords) @@ -412,120 +419,131 @@ def lru_cache(maxsize=128, typed=False): if maxsize is not None and not isinstance(maxsize, int): raise TypeError('Expected maxsize to be an integer or None') + def decorating_function(user_function): + wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo) + return update_wrapper(wrapper, user_function) + + return decorating_function + +def _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo): # Constants shared by all lru cache instances: sentinel = object() # unique object used to signal cache misses make_key = _make_key # build a key from the function arguments PREV, NEXT, KEY, RESULT = 0, 1, 2, 3 # names for the link fields - def decorating_function(user_function): - cache = {} - hits = misses = 0 - full = False - cache_get = cache.get # bound method to lookup a key or return None - lock = RLock() # because linkedlist updates aren't threadsafe - root = [] # root of the circular doubly linked list - root[:] = [root, root, None, None] # initialize by pointing to self - - if maxsize == 0: - - def wrapper(*args, **kwds): - # No caching -- just a statistics update after a successful call - nonlocal misses - result = user_function(*args, **kwds) - misses += 1 - return result + cache = {} + hits = misses = 0 + full = False + cache_get = cache.get # bound method to lookup a key or return None + lock = RLock() # because linkedlist updates aren't threadsafe + root = [] # root of the circular doubly linked list + root[:] = [root, root, None, None] # initialize by pointing to self + + if maxsize == 0: + + def wrapper(*args, **kwds): + # No caching -- just a statistics update after a successful call + nonlocal misses + result = user_function(*args, **kwds) + misses += 1 + return result - elif maxsize is None: + elif maxsize is None: - def wrapper(*args, **kwds): - # Simple caching without ordering or size limit - nonlocal hits, misses - key = make_key(args, kwds, typed) - result = cache_get(key, sentinel) - if result is not sentinel: - hits += 1 - return result - result = user_function(*args, **kwds) - cache[key] = result - misses += 1 + def wrapper(*args, **kwds): + # Simple caching without ordering or size limit + nonlocal hits, misses + key = make_key(args, kwds, typed) + result = cache_get(key, sentinel) + if result is not sentinel: + hits += 1 return result + result = user_function(*args, **kwds) + cache[key] = result + misses += 1 + return result - else: - - def wrapper(*args, **kwds): - # Size limited caching that tracks accesses by recency - nonlocal root, hits, misses, full - key = make_key(args, kwds, typed) - with lock: - link = cache_get(key) - if link is not None: - # Move the link to the front of the circular queue - link_prev, link_next, _key, result = link - link_prev[NEXT] = link_next - link_next[PREV] = link_prev - last = root[PREV] - last[NEXT] = root[PREV] = link - link[PREV] = last - link[NEXT] = root - hits += 1 - return result - result = user_function(*args, **kwds) - with lock: - if key in cache: - # Getting here means that this same key was added to the - # cache while the lock was released. Since the link - # update is already done, we need only return the - # computed result and update the count of misses. - pass - elif full: - # Use the old root to store the new key and result. - oldroot = root - oldroot[KEY] = key - oldroot[RESULT] = result - # Empty the oldest link and make it the new root. - # Keep a reference to the old key and old result to - # prevent their ref counts from going to zero during the - # update. That will prevent potentially arbitrary object - # clean-up code (i.e. __del__) from running while we're - # still adjusting the links. - root = oldroot[NEXT] - oldkey = root[KEY] - oldresult = root[RESULT] - root[KEY] = root[RESULT] = None - # Now update the cache dictionary. - del cache[oldkey] - # Save the potentially reentrant cache[key] assignment - # for last, after the root and links have been put in - # a consistent state. - cache[key] = oldroot - else: - # Put result in a new link at the front of the queue. - last = root[PREV] - link = [last, root, key, result] - last[NEXT] = root[PREV] = cache[key] = link - full = (len(cache) >= maxsize) - misses += 1 - return result + else: - def cache_info(): - """Report cache statistics""" + def wrapper(*args, **kwds): + # Size limited caching that tracks accesses by recency + nonlocal root, hits, misses, full + key = make_key(args, kwds, typed) with lock: - return _CacheInfo(hits, misses, maxsize, len(cache)) - - def cache_clear(): - """Clear the cache and cache statistics""" - nonlocal hits, misses, full + link = cache_get(key) + if link is not None: + # Move the link to the front of the circular queue + link_prev, link_next, _key, result = link + link_prev[NEXT] = link_next + link_next[PREV] = link_prev + last = root[PREV] + last[NEXT] = root[PREV] = link + link[PREV] = last + link[NEXT] = root + hits += 1 + return result + result = user_function(*args, **kwds) with lock: - cache.clear() - root[:] = [root, root, None, None] - hits = misses = 0 - full = False + if key in cache: + # Getting here means that this same key was added to the + # cache while the lock was released. Since the link + # update is already done, we need only return the + # computed result and update the count of misses. + pass + elif full: + # Use the old root to store the new key and result. + oldroot = root + oldroot[KEY] = key + oldroot[RESULT] = result + # Empty the oldest link and make it the new root. + # Keep a reference to the old key and old result to + # prevent their ref counts from going to zero during the + # update. That will prevent potentially arbitrary object + # clean-up code (i.e. __del__) from running while we're + # still adjusting the links. + root = oldroot[NEXT] + oldkey = root[KEY] + oldresult = root[RESULT] + root[KEY] = root[RESULT] = None + # Now update the cache dictionary. + del cache[oldkey] + # Save the potentially reentrant cache[key] assignment + # for last, after the root and links have been put in + # a consistent state. + cache[key] = oldroot + else: + # Put result in a new link at the front of the queue. + last = root[PREV] + link = [last, root, key, result] + last[NEXT] = root[PREV] = cache[key] = link + # Use the __len__() method instead of the len() function + # which could potentially be wrapped in an lru_cache itself. + full = (cache.__len__() >= maxsize) + misses += 1 + return result - wrapper.cache_info = cache_info - wrapper.cache_clear = cache_clear - return update_wrapper(wrapper, user_function) + def cache_info(): + """Report cache statistics""" + with lock: + return _CacheInfo(hits, misses, maxsize, cache.__len__()) + + def cache_clear(): + """Clear the cache and cache statistics""" + nonlocal hits, misses, full + with lock: + cache.clear() + root[:] = [root, root, None, None] + hits = misses = 0 + full = False + + wrapper.cache_info = cache_info + wrapper.cache_clear = cache_clear + return wrapper - return decorating_function +try: + from _functools import _lru_cache_wrapper +except ImportError: + pass ################################################################################ diff --git a/lib/genericpath.py b/lib/genericpath.py index ca4a5108..67140619 100644 --- a/lib/genericpath.py +++ b/lib/genericpath.py @@ -130,3 +130,16 @@ def _splitext(p, sep, altsep, extsep): filenameIndex += 1 return p, p[:0] + +def _check_arg_types(funcname, *args): + hasstr = hasbytes = False + for s in args: + if isinstance(s, str): + hasstr = True + elif isinstance(s, bytes): + hasbytes = True + else: + raise TypeError('%s() argument must be str or bytes, not %r' % + (funcname, s.__class__.__name__)) from None + if hasstr and hasbytes: + raise TypeError("Can't mix strings and bytes in path components") from None diff --git a/lib/getpass.py b/lib/getpass.py index 7c4e9761..36e17e4c 100644 --- a/lib/getpass.py +++ b/lib/getpass.py @@ -7,7 +7,6 @@ echoing of the password contents while reading. On Windows, the msvcrt module will be used. -On the Mac EasyDialogs.AskPassword is used, if available. """ @@ -99,7 +98,7 @@ def win_getpass(prompt='Password: ', stream=None): """Prompt for password with echo off, using Windows getch().""" if sys.stdin is not sys.__stdin__: return fallback_getpass(prompt, stream) - import msvcrt + for c in prompt: msvcrt.putwch(c) pw = "" diff --git a/lib/gettext.py b/lib/gettext.py index 4e6e15ee..6b215af4 100644 --- a/lib/gettext.py +++ b/lib/gettext.py @@ -197,7 +197,7 @@ def func(n): return int(%s) ''' % result, ns) return ns['func'] - except RuntimeError: + except RecursionError: # Recursion error can be raised in _parse() or exec(). raise ValueError('plural form expression is too complex') @@ -270,7 +270,9 @@ def gettext(self, message): def lgettext(self, message): if self._fallback: return self._fallback.lgettext(message) - return message + if self._output_charset: + return message.encode(self._output_charset) + return message.encode(locale.getpreferredencoding()) def ngettext(self, msgid1, msgid2, n): if self._fallback: @@ -284,9 +286,12 @@ def lngettext(self, msgid1, msgid2, n): if self._fallback: return self._fallback.lngettext(msgid1, msgid2, n) if n == 1: - return msgid1 + tmsg = msgid1 else: - return msgid2 + tmsg = msgid2 + if self._output_charset: + return tmsg.encode(self._output_charset) + return tmsg.encode(locale.getpreferredencoding()) def info(self): return self._info @@ -319,6 +324,13 @@ class GNUTranslations(NullTranslations): LE_MAGIC = 0x950412de BE_MAGIC = 0xde120495 + # Acceptable .mo versions + VERSIONS = (0, 1) + + def _get_versions(self, version): + """Returns a tuple of major version, minor version""" + return (version >> 16, version & 0xffff) + def _parse(self, fp): """Override this method to support alternative .mo formats.""" unpack = struct.unpack @@ -339,6 +351,12 @@ def _parse(self, fp): ii = '>II' else: raise OSError(0, 'Bad magic number', filename) + + major_version, minor_version = self._get_versions(version) + + if major_version not in self.VERSIONS: + raise OSError(0, 'Bad version number ' + str(major_version), filename) + # Now put all messages from the .mo file buffer into the catalog # dictionary. for i in range(0, msgcount): @@ -355,7 +373,7 @@ def _parse(self, fp): if mlen == 0: # Catalog description lastk = None - for b_item in tmsg.split('\n'.encode("ascii")): + for b_item in tmsg.split(b'\n'): item = b_item.decode().strip() if not item: continue @@ -403,7 +421,7 @@ def lgettext(self, message): if tmsg is missing: if self._fallback: return self._fallback.lgettext(message) - return message + tmsg = message if self._output_charset: return tmsg.encode(self._output_charset) return tmsg.encode(locale.getpreferredencoding()) @@ -411,16 +429,16 @@ def lgettext(self, message): def lngettext(self, msgid1, msgid2, n): try: tmsg = self._catalog[(msgid1, self.plural(n))] - if self._output_charset: - return tmsg.encode(self._output_charset) - return tmsg.encode(locale.getpreferredencoding()) except KeyError: if self._fallback: return self._fallback.lngettext(msgid1, msgid2, n) if n == 1: - return msgid1 + tmsg = msgid1 else: - return msgid2 + tmsg = msgid2 + if self._output_charset: + return tmsg.encode(self._output_charset) + return tmsg.encode(locale.getpreferredencoding()) def gettext(self, message): missing = object() @@ -560,11 +578,11 @@ def dgettext(domain, message): return t.gettext(message) def ldgettext(domain, message): + codeset = _localecodesets.get(domain) try: - t = translation(domain, _localedirs.get(domain, None), - codeset=_localecodesets.get(domain)) + t = translation(domain, _localedirs.get(domain, None), codeset=codeset) except OSError: - return message + return message.encode(codeset or locale.getpreferredencoding()) return t.lgettext(message) def dngettext(domain, msgid1, msgid2, n): @@ -579,14 +597,15 @@ def dngettext(domain, msgid1, msgid2, n): return t.ngettext(msgid1, msgid2, n) def ldngettext(domain, msgid1, msgid2, n): + codeset = _localecodesets.get(domain) try: - t = translation(domain, _localedirs.get(domain, None), - codeset=_localecodesets.get(domain)) + t = translation(domain, _localedirs.get(domain, None), codeset=codeset) except OSError: if n == 1: - return msgid1 + tmsg = msgid1 else: - return msgid2 + tmsg = msgid2 + return tmsg.encode(codeset or locale.getpreferredencoding()) return t.lngettext(msgid1, msgid2, n) def gettext(message): diff --git a/lib/glob.py b/lib/glob.py index d6eca248..16330d81 100644 --- a/lib/glob.py +++ b/lib/glob.py @@ -4,9 +4,9 @@ import re import fnmatch -__all__ = ["glob", "iglob"] +__all__ = ["glob", "iglob", "escape"] -def glob(pathname): +def glob(pathname, *, recursive=False): """Return a list of paths matching a pathname pattern. The pattern may contain simple shell-style wildcards a la @@ -14,10 +14,12 @@ def glob(pathname): dot are special cases that are not matched by '*' and '?' patterns. + If recursive is true, the pattern '**' will match any files and + zero or more directories and subdirectories. """ - return list(iglob(pathname)) + return list(iglob(pathname, recursive=recursive)) -def iglob(pathname): +def iglob(pathname, *, recursive=False): """Return an iterator which yields the paths matching a pathname pattern. The pattern may contain simple shell-style wildcards a la @@ -25,7 +27,16 @@ def iglob(pathname): dot are special cases that are not matched by '*' and '?' patterns. + If recursive is true, the pattern '**' will match any files and + zero or more directories and subdirectories. """ + it = _iglob(pathname, recursive) + if recursive and _isrecursive(pathname): + s = next(it) # skip empty string + assert not s + return it + +def _iglob(pathname, recursive): dirname, basename = os.path.split(pathname) if not has_magic(pathname): if basename: @@ -37,17 +48,23 @@ def iglob(pathname): yield pathname return if not dirname: - yield from glob1(None, basename) + if recursive and _isrecursive(basename): + yield from glob2(dirname, basename) + else: + yield from glob1(dirname, basename) return # `os.path.split()` returns the argument itself as a dirname if it is a # drive or UNC path. Prevent an infinite recursion if a drive or UNC path # contains magic characters (i.e. r'\\?\C:'). if dirname != pathname and has_magic(dirname): - dirs = iglob(dirname) + dirs = _iglob(dirname, recursive) else: dirs = [dirname] if has_magic(basename): - glob_in_dir = glob1 + if recursive and _isrecursive(basename): + glob_in_dir = glob2 + else: + glob_in_dir = glob1 else: glob_in_dir = glob0 for dirname in dirs: @@ -83,6 +100,32 @@ def glob0(dirname, basename): return [basename] return [] +# This helper function recursively yields relative pathnames inside a literal +# directory. + +def glob2(dirname, pattern): + assert _isrecursive(pattern) + yield pattern[:0] + yield from _rlistdir(dirname) + +# Recursively yields relative pathnames inside a literal directory. +def _rlistdir(dirname): + if not dirname: + if isinstance(dirname, bytes): + dirname = bytes(os.curdir, 'ASCII') + else: + dirname = os.curdir + try: + names = os.listdir(dirname) + except os.error: + return + for x in names: + if not _ishidden(x): + yield x + path = os.path.join(dirname, x) if dirname else x + for y in _rlistdir(path): + yield os.path.join(x, y) + magic_check = re.compile('([*?[])') magic_check_bytes = re.compile(b'([*?[])') @@ -97,6 +140,12 @@ def has_magic(s): def _ishidden(path): return path[0] in ('.', b'.'[0]) +def _isrecursive(pattern): + if isinstance(pattern, bytes): + return pattern == b'**' + else: + return pattern == '**' + def escape(pathname): """Escape all special characters. """ diff --git a/lib/gzip.py b/lib/gzip.py index 7ad00e17..da4479e9 100644 --- a/lib/gzip.py +++ b/lib/gzip.py @@ -9,6 +9,7 @@ import zlib import builtins import io +import _compression __all__ = ["GzipFile", "open", "compress", "decompress"] @@ -89,49 +90,35 @@ def read(self, size): return self._buffer[read:] + \ self.file.read(size-self._length+read) - def prepend(self, prepend=b'', readprevious=False): + def prepend(self, prepend=b''): if self._read is None: self._buffer = prepend - elif readprevious and len(prepend) <= self._read: + else: # Assume data was read since the last prepend() call self._read -= len(prepend) return - else: - self._buffer = self._buffer[self._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 + def seek(self, off): self._read = None self._buffer = None - return self.file.seek(offset, whence) - - def __getattr__(self, name): - return getattr(self.file, name) + return self.file.seek(off) + def seekable(self): + return True # Allows fast-forwarding even in unseekable streams -class GzipFile(io.BufferedIOBase): +class GzipFile(_compression.BaseStream): """The GzipFile class simulates most of the methods of a file object with - the exception of the readinto() and truncate() methods. + the exception of the truncate() method. This class only supports opening files in binary mode. If you need to open a compressed file in text mode, use the gzip.open() function. """ + # Overridden with internal file object to be closed, if only a filename + # is passed in myfileobj = None - max_read_chunk = 10 * 1024 * 1024 # 10Mb def __init__(self, filename=None, mode=None, compresslevel=9, fileobj=None, mtime=None): @@ -146,7 +133,7 @@ def __init__(self, filename=None, mode=None, a file object. When fileobj is not None, the filename argument is only used to be - included in the gzip file header, which may includes the original + included in the gzip file header, which may include the original filename of the uncompressed file. It defaults to the filename of fileobj, if discernible; otherwise, it defaults to the empty string, and in this case the original filename is not included in the header. @@ -163,13 +150,8 @@ def __init__(self, filename=None, mode=None, at all. The default is 9. The mtime argument is an optional numeric timestamp to be written - to the stream when compressing. All gzip compressed streams - are required to contain a timestamp. If omitted or None, the - current time is used. This module ignores the timestamp when - decompressing; however, some programs, such as gunzip, make use - of it. The format of the timestamp is the same as that of the - return value of time.time() and of the st_mtime member of the - object returned by os.stat(). + to the last modification time field in the stream when compressing. + If omitted or None, the current time is used. """ @@ -188,18 +170,9 @@ def __init__(self, filename=None, mode=None, if mode.startswith('r'): 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 + raw = _GzipReader(fileobj) + self._buffer = io.BufferedReader(raw) self.name = filename - # Starts small, scales exponentially - self.min_readsize = 100 - fileobj = _PaddedFile(fileobj) elif mode.startswith(('w', 'a', 'x')): self.mode = WRITE @@ -209,12 +182,11 @@ def __init__(self, filename=None, mode=None, -zlib.MAX_WBITS, zlib.DEF_MEM_LEVEL, 0) + self._write_mtime = mtime else: raise ValueError("Invalid mode: {!r}".format(mode)) self.fileobj = fileobj - self.offset = 0 - self.mtime = mtime if self.mode == WRITE: self._write_gzip_header() @@ -227,26 +199,22 @@ def filename(self): return self.name + ".gz" return self.name + @property + def mtime(self): + """Last modification time read from stream, or None""" + return self._buffer.raw._last_mtime + def __repr__(self): - fileobj = self.fileobj - if isinstance(fileobj, _PaddedFile): - fileobj = fileobj.file - s = repr(fileobj) + s = repr(self.fileobj) return '' - def _check_closed(self): - """Raises a ValueError if the underlying file object has been closed. - - """ - if self.closed: - raise ValueError('I/O operation on closed file.') - def _init_write(self, filename): self.name = filename - self.crc = zlib.crc32(b"") & 0xffffffff + self.crc = zlib.crc32(b"") self.size = 0 self.writebuf = [] self.bufsize = 0 + self.offset = 0 # Current file offset for seek(), tell(), etc def _write_gzip_header(self): self.fileobj.write(b'\037\213') # magic header @@ -265,7 +233,7 @@ def _write_gzip_header(self): if fname: flags = FNAME self.fileobj.write(chr(flags).encode('latin-1')) - mtime = self.mtime + mtime = self._write_mtime if mtime is None: mtime = time.time() write32u(self.fileobj, int(mtime)) @@ -274,59 +242,8 @@ def _write_gzip_header(self): if fname: self.fileobj.write(fname + b'\000') - def _init_read(self): - self.crc = zlib.crc32(b"") & 0xffffffff - self.size = 0 - - def _read_exact(self, n): - data = self.fileobj.read(n) - while len(data) < n: - b = self.fileobj.read(n - len(data)) - if not b: - raise EOFError("Compressed file ended before the " - "end-of-stream marker was reached") - data += b - return data - - def _read_gzip_header(self): - magic = self.fileobj.read(2) - if magic == b'': - return False - - if magic != b'\037\213': - raise OSError('Not a gzipped file') - - method, flag, self.mtime = struct.unpack(" 0: + if length > 0: self.fileobj.write(self.compress.compress(data)) - self.size += len(data) - self.crc = zlib.crc32(data, self.crc) & 0xffffffff - self.offset += len(data) + self.size += length + self.crc = zlib.crc32(data, self.crc) + self.offset += length - return len(data) + return length def read(self, size=-1): - self._check_closed() + self._check_not_closed() if self.mode != READ: import errno raise OSError(errno.EBADF, "read() on write-only GzipFile object") - - if self.extrasize <= 0 and self.fileobj is None: - return b'' - - readsize = 1024 - if size < 0: # get the whole thing - while self._read(readsize): - readsize = min(self.max_read_chunk, readsize * 2) - size = self.extrasize - else: # just get some more of it - while size > self.extrasize: - if not self._read(readsize): - if size > self.extrasize: - size = self.extrasize - break - readsize = min(self.max_read_chunk, readsize * 2) - - offset = self.offset - self.extrastart - chunk = self.extrabuf[offset: offset + size] - self.extrasize = self.extrasize - size - - self.offset += size - return chunk + return self._buffer.read(size) def read1(self, size=-1): - self._check_closed() + """Implements BufferedIOBase.read1() + + Reads up to a buffer's worth of data is size is negative.""" + self._check_not_closed() if self.mode != READ: import errno raise OSError(errno.EBADF, "read1() on write-only GzipFile object") - if self.extrasize <= 0 and self.fileobj is None: - return b'' - - # For certain input data, a single call to _read() may not return - # any data. In this case, retry until we get some data or reach EOF. - while self.extrasize <= 0 and self._read(): - pass - if size < 0 or size > self.extrasize: - size = self.extrasize - - offset = self.offset - self.extrastart - chunk = self.extrabuf[offset: offset + size] - self.extrasize -= size - self.offset += size - return chunk + if size < 0: + size = io.DEFAULT_BUFFER_SIZE + return self._buffer.read1(size) def peek(self, n): + self._check_not_closed() if self.mode != READ: import errno raise OSError(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'' - # Ensure that we don't return b"" if we haven't reached EOF. - # 1024 is the same buffering heuristic used in read() - while self.extrasize == 0 and self._read(max(n, 1024)): - 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.extrasize = len(buf) + self.extrasize - self.offset -= len(buf) - - def _read(self, size=1024): - if self.fileobj is None: - return False - - if self._new_member: - # If the _new_member flag is set, we have to - # jump to the next member, if there is one. - self._init_read() - if not self._read_gzip_header(): - return False - self.decompress = zlib.decompressobj(-zlib.MAX_WBITS) - self._new_member = False - - # Read a chunk of data from the file - buf = self.fileobj.read(size) - - # If the EOF has been reached, flush the decompression object - # and mark this object as finished. - - 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 ) - return False - - uncompress = self.decompress.decompress(buf) - self._add_read_data( uncompress ) - - if self.decompress.unused_data != b"": - # 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. - # 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() - self._new_member = True - return True - - def _add_read_data(self, data): - self.crc = zlib.crc32(data, self.crc) & 0xffffffff - 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 - # 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. - crc32, isize = struct.unpack(" 0: - self.extrasize -= i - offset - self.offset += i - offset - return self.extrabuf[offset: i] - - size = sys.maxsize - readsize = self.min_readsize - else: - readsize = size - bufs = [] - while size != 0: - c = self.read(readsize) - i = c.find(b'\n') - - # We set i=size to break out of the loop under two - # conditions: 1) there's no newline, and the chunk is - # larger than size, or 2) there is a newline, but the - # resulting line would be longer than 'size'. - if (size <= i) or (i == -1 and len(c) > size): - i = size - 1 - - if i >= 0 or c == b'': - bufs.append(c[:i + 1]) # Add portion of last chunk - self._unread(c[i + 1:]) # Push back rest of chunk + return self.readall() + # size=0 is special because decompress(max_length=0) is not supported + if not size: + return b"" + + # For certain input data, a single + # call to decompress() may not return + # any data. In this case, retry until we get some data or reach EOF. + while True: + if self._decompressor.eof: + # Ending case: we've come to the end of a member in the file, + # so finish up this member, and read a new gzip header. + # Check the CRC and file size, and set the flag so we read + # a new member + self._read_eof() + self._new_member = True + self._decompressor = self._decomp_factory( + **self._decomp_args) + + if self._new_member: + # If the _new_member flag is set, we have to + # jump to the next member, if there is one. + self._init_read() + if not self._read_gzip_header(): + self._size = self._pos + return b"" + self._new_member = False + + # Read a chunk of data from the file + buf = self._fp.read(io.DEFAULT_BUFFER_SIZE) + + uncompress = self._decompressor.decompress(buf, size) + if self._decompressor.unconsumed_tail != b"": + self._fp.prepend(self._decompressor.unconsumed_tail) + elif self._decompressor.unused_data != b"": + # Prepend the already read bytes to the fileobj so they can + # be seen by _read_eof() and _read_gzip_header() + self._fp.prepend(self._decompressor.unused_data) + + if uncompress != b"": break + if buf == b"": + raise EOFError("Compressed file ended before the " + "end-of-stream marker was reached") - # Append chunk to list, decrease 'size', - bufs.append(c) - size = size - len(c) - readsize = min(size, readsize * 2) - if readsize > self.min_readsize: - self.min_readsize = min(readsize, self.min_readsize * 2, 512) - return b''.join(bufs) # Return resulting line + self._add_read_data( uncompress ) + self._pos += len(uncompress) + return uncompress + + def _add_read_data(self, data): + self._crc = zlib.crc32(data, self._crc) + self._stream_size = self._stream_size + len(data) + + def _read_eof(self): + # 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. + crc32, isize = struct.unpack("= startpos, except possibly for pos. pos # is the index of a leaf with a possibly out-of-order value. Restore the # heap invariant. @@ -340,13 +311,7 @@ def _siftup_max(heap, pos): heap[pos] = newitem _siftdown_max(heap, startpos, pos) -# If available, use C implementation -try: - from _heapq import * -except ImportError: - pass - -def merge(*iterables): +def merge(*iterables, key=None, reverse=False): '''Merge multiple sorted inputs into a single sorted output. Similar to sorted(itertools.chain(*iterables)) but returns a generator, @@ -356,51 +321,158 @@ def merge(*iterables): >>> list(merge([1,3,5,7], [0,2,4,8], [5,10,15,20], [], [25])) [0, 1, 2, 3, 4, 5, 5, 7, 8, 10, 15, 20, 25] + If *key* is not None, applies a key function to each element to determine + its sort order. + + >>> list(merge(['dog', 'horse'], ['cat', 'fish', 'kangaroo'], key=len)) + ['dog', 'cat', 'fish', 'horse', 'kangaroo'] + ''' - _heappop, _heapreplace, _StopIteration = heappop, heapreplace, StopIteration - _len = len h = [] h_append = h.append - for itnum, it in enumerate(map(iter, iterables)): + + if reverse: + _heapify = _heapify_max + _heappop = _heappop_max + _heapreplace = _heapreplace_max + direction = -1 + else: + _heapify = heapify + _heappop = heappop + _heapreplace = heapreplace + direction = 1 + + if key is None: + for order, it in enumerate(map(iter, iterables)): + try: + next = it.__next__ + h_append([next(), order * direction, next]) + except StopIteration: + pass + _heapify(h) + while len(h) > 1: + try: + while True: + value, order, next = s = h[0] + yield value + s[0] = next() # raises StopIteration when exhausted + _heapreplace(h, s) # restore heap condition + except StopIteration: + _heappop(h) # remove empty iterator + if h: + # fast case when only a single iterator remains + value, order, next = h[0] + yield value + yield from next.__self__ + return + + for order, it in enumerate(map(iter, iterables)): try: next = it.__next__ - h_append([next(), itnum, next]) - except _StopIteration: + value = next() + h_append([key(value), order * direction, value, next]) + except StopIteration: pass - heapify(h) - - while _len(h) > 1: + _heapify(h) + while len(h) > 1: try: while True: - v, itnum, next = s = h[0] - yield v - s[0] = next() # raises StopIteration when exhausted - _heapreplace(h, s) # restore heap condition - except _StopIteration: - _heappop(h) # remove empty iterator + key_value, order, value, next = s = h[0] + yield value + value = next() + s[0] = key(value) + s[2] = value + _heapreplace(h, s) + except StopIteration: + _heappop(h) if h: - # fast case when only a single iterator remains - v, itnum, next = h[0] - yield v + key_value, order, value, next = h[0] + yield value yield from next.__self__ -# Extend the implementations of nsmallest and nlargest to use a key= argument -_nsmallest = nsmallest + +# Algorithm notes for nlargest() and nsmallest() +# ============================================== +# +# Make a single pass over the data while keeping the k most extreme values +# in a heap. Memory consumption is limited to keeping k values in a list. +# +# Measured performance for random inputs: +# +# number of comparisons +# n inputs k-extreme values (average of 5 trials) % more than min() +# ------------- ---------------- --------------------- ----------------- +# 1,000 100 3,317 231.7% +# 10,000 100 14,046 40.5% +# 100,000 100 105,749 5.7% +# 1,000,000 100 1,007,751 0.8% +# 10,000,000 100 10,009,401 0.1% +# +# Theoretical number of comparisons for k smallest of n random inputs: +# +# Step Comparisons Action +# ---- -------------------------- --------------------------- +# 1 1.66 * k heapify the first k-inputs +# 2 n - k compare remaining elements to top of heap +# 3 k * (1 + lg2(k)) * ln(n/k) replace the topmost value on the heap +# 4 k * lg2(k) - (k/2) final sort of the k most extreme values +# +# Combining and simplifying for a rough estimate gives: +# +# comparisons = n + k * (log(k, 2) * log(n/k) + log(k, 2) + log(n/k)) +# +# Computing the number of comparisons for step 3: +# ----------------------------------------------- +# * For the i-th new value from the iterable, the probability of being in the +# k most extreme values is k/i. For example, the probability of the 101st +# value seen being in the 100 most extreme values is 100/101. +# * If the value is a new extreme value, the cost of inserting it into the +# heap is 1 + log(k, 2). +# * The probability times the cost gives: +# (k/i) * (1 + log(k, 2)) +# * Summing across the remaining n-k elements gives: +# sum((k/i) * (1 + log(k, 2)) for i in range(k+1, n+1)) +# * This reduces to: +# (H(n) - H(k)) * k * (1 + log(k, 2)) +# * Where H(n) is the n-th harmonic number estimated by: +# gamma = 0.5772156649 +# H(n) = log(n, e) + gamma + 1 / (2 * n) +# http://en.wikipedia.org/wiki/Harmonic_series_(mathematics)#Rate_of_divergence +# * Substituting the H(n) formula: +# comparisons = k * (1 + log(k, 2)) * (log(n/k, e) + (1/n - 1/k) / 2) +# +# Worst-case for step 3: +# ---------------------- +# In the worst case, the input data is reversed sorted so that every new element +# must be inserted in the heap: +# +# comparisons = 1.66 * k + log(k, 2) * (n - k) +# +# Alternative Algorithms +# ---------------------- +# Other algorithms were not used because they: +# 1) Took much more auxiliary memory, +# 2) Made multiple passes over the data. +# 3) Made more comparisons in common cases (small k, large n, semi-random input). +# See the more detailed comparison of approach at: +# http://code.activestate.com/recipes/577573-compare-algorithms-for-heapqsmallest + def nsmallest(n, iterable, key=None): """Find the n smallest elements in a dataset. Equivalent to: sorted(iterable, key=key)[:n] """ - # Short-cut for n==1 is to use min() when len(iterable)>0 + + # Short-cut for n==1 is to use min() if n == 1: it = iter(iterable) - head = list(islice(it, 1)) - if not head: - return [] + sentinel = object() if key is None: - return [min(chain(head, it))] - return [min(chain(head, it), key=key)] + result = min(it, default=sentinel) + else: + result = min(it, default=sentinel, key=key) + return [] if result is sentinel else [result] # When n>=size, it's faster to use sorted() try: @@ -413,32 +485,57 @@ def nsmallest(n, iterable, key=None): # When key is none, use simpler decoration if key is None: - it = zip(iterable, count()) # decorate - result = _nsmallest(n, it) - return [r[0] for r in result] # undecorate + it = iter(iterable) + # put the range(n) first so that zip() doesn't + # consume one too many elements from the iterator + result = [(elem, i) for i, elem in zip(range(n), it)] + if not result: + return result + _heapify_max(result) + top = result[0][0] + order = n + _heapreplace = _heapreplace_max + for elem in it: + if elem < top: + _heapreplace(result, (elem, order)) + top = result[0][0] + order += 1 + result.sort() + return [r[0] for r in result] # General case, slowest method - in1, in2 = tee(iterable) - it = zip(map(key, in1), count(), in2) # decorate - result = _nsmallest(n, it) - return [r[2] for r in result] # undecorate + it = iter(iterable) + result = [(key(elem), i, elem) for i, elem in zip(range(n), it)] + if not result: + return result + _heapify_max(result) + top = result[0][0] + order = n + _heapreplace = _heapreplace_max + for elem in it: + k = key(elem) + if k < top: + _heapreplace(result, (k, order, elem)) + top = result[0][0] + order += 1 + result.sort() + return [r[2] for r in result] -_nlargest = nlargest def nlargest(n, iterable, key=None): """Find the n largest elements in a dataset. Equivalent to: sorted(iterable, key=key, reverse=True)[:n] """ - # Short-cut for n==1 is to use max() when len(iterable)>0 + # Short-cut for n==1 is to use max() if n == 1: it = iter(iterable) - head = list(islice(it, 1)) - if not head: - return [] + sentinel = object() if key is None: - return [max(chain(head, it))] - return [max(chain(head, it), key=key)] + result = max(it, default=sentinel) + else: + result = max(it, default=sentinel, key=key) + return [] if result is sentinel else [result] # When n>=size, it's faster to use sorted() try: @@ -451,26 +548,60 @@ def nlargest(n, iterable, key=None): # When key is none, use simpler decoration if key is None: - it = zip(iterable, count(0,-1)) # decorate - result = _nlargest(n, it) - return [r[0] for r in result] # undecorate + it = iter(iterable) + result = [(elem, i) for i, elem in zip(range(0, -n, -1), it)] + if not result: + return result + heapify(result) + top = result[0][0] + order = -n + _heapreplace = heapreplace + for elem in it: + if top < elem: + _heapreplace(result, (elem, order)) + top = result[0][0] + order -= 1 + result.sort(reverse=True) + return [r[0] for r in result] # General case, slowest method - in1, in2 = tee(iterable) - it = zip(map(key, in1), count(0,-1), in2) # decorate - result = _nlargest(n, it) - return [r[2] for r in result] # undecorate + it = iter(iterable) + result = [(key(elem), i, elem) for i, elem in zip(range(0, -n, -1), it)] + if not result: + return result + heapify(result) + top = result[0][0] + order = -n + _heapreplace = heapreplace + for elem in it: + k = key(elem) + if top < k: + _heapreplace(result, (k, order, elem)) + top = result[0][0] + order -= 1 + result.sort(reverse=True) + return [r[2] for r in result] + +# If available, use C implementation +try: + from _heapq import * +except ImportError: + pass +try: + from _heapq import _heapreplace_max +except ImportError: + pass +try: + from _heapq import _heapify_max +except ImportError: + pass +try: + from _heapq import _heappop_max +except ImportError: + pass + if __name__ == "__main__": - # Simple sanity test - heap = [] - data = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0] - for item in data: - heappush(heap, item) - sort = [] - while heap: - sort.append(heappop(heap)) - print(sort) import doctest - doctest.testmod() + print(doctest.testmod()) diff --git a/lib/html/entities.py b/lib/html/entities.py index be3f627c..91ea5da2 100644 --- a/lib/html/entities.py +++ b/lib/html/entities.py @@ -1,5 +1,8 @@ """HTML character entity references.""" +__all__ = ['html5', 'name2codepoint', 'codepoint2name', 'entitydefs'] + + # maps the HTML entity name to the Unicode code point name2codepoint = { 'AElig': 0x00c6, # latin capital letter AE = latin capital ligature AE, U+00C6 ISOlat1 diff --git a/lib/html/parser.py b/lib/html/parser.py index 9ae31b91..b781c63f 100644 --- a/lib/html/parser.py +++ b/lib/html/parser.py @@ -29,35 +29,15 @@ piclose = re.compile('>') commentclose = re.compile(r'--\s*>') # Note: -# 1) the strict attrfind isn't really strict, but we can't make it -# correctly strict without breaking backward compatibility; -# 2) if you change tagfind/attrfind remember to update locatestarttagend too; -# 3) if you change tagfind/attrfind and/or locatestarttagend the parser will +# 1) if you change tagfind/attrfind remember to update locatestarttagend too; +# 2) if you change tagfind/attrfind and/or locatestarttagend the parser will # explode, so don't do it. -tagfind = re.compile('([a-zA-Z][-.a-zA-Z0-9:_]*)(?:\s|/(?!>))*') # see http://www.w3.org/TR/html5/tokenization.html#tag-open-state # and http://www.w3.org/TR/html5/tokenization.html#tag-name-state tagfind_tolerant = re.compile('([a-zA-Z][^\t\n\r\f />\x00]*)(?:\s|/(?!>))*') -attrfind = re.compile( - r'\s*([a-zA-Z_][-.:a-zA-Z_0-9]*)(\s*=\s*' - r'(\'[^\']*\'|"[^"]*"|[^\s"\'=<>`]*))?') attrfind_tolerant = re.compile( r'((?<=[\'"\s/])[^\s/>][^\s/=>]*)(\s*=+\s*' r'(\'[^\']*\'|"[^"]*"|(?![\'"])[^>\s]*))?(?:\s|/(?!>))*') -locatestarttagend = re.compile(r""" - <[a-zA-Z][-.a-zA-Z0-9:_]* # tag name - (?:\s+ # 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* # trailing whitespace -""", re.VERBOSE) locatestarttagend_tolerant = re.compile(r""" <[a-zA-Z][^\t\n\r\f />\x00]* # tag name (?:[\s/]* # optional whitespace before attribute name @@ -79,25 +59,6 @@ endtagfind = re.compile('') -class HTMLParseError(Exception): - """Exception raised for all parse errors.""" - - def __init__(self, msg, position=(None, None)): - assert msg - self.msg = msg - self.lineno = position[0] - self.offset = position[1] - - def __str__(self): - result = self.msg - if self.lineno is not None: - result = result + ", at line %d" % self.lineno - if self.offset is not None: - result = result + ", column %d" % (self.offset + 1) - return result - - -_default_sentinel = object() class HTMLParser(_markupbase.ParserBase): """Find tags and other markup and call handler functions. @@ -123,27 +84,12 @@ class HTMLParser(_markupbase.ParserBase): CDATA_CONTENT_ELEMENTS = ("script", "style") - def __init__(self, strict=_default_sentinel, *, - convert_charrefs=_default_sentinel): + def __init__(self, *, convert_charrefs=True): """Initialize and reset this instance. - If convert_charrefs is True (default: False), all character references + If convert_charrefs is True (the default), all character references are automatically converted to the corresponding Unicode characters. - If strict is set to False (the default) the parser will parse invalid - markup, otherwise it will raise an error. Note that the strict mode - and argument are deprecated. """ - if strict is not _default_sentinel: - warnings.warn("The strict argument and mode are deprecated.", - DeprecationWarning, stacklevel=2) - else: - strict = False # default - self.strict = strict - if convert_charrefs is _default_sentinel: - convert_charrefs = False # default - warnings.warn("The value of convert_charrefs will become True in " - "3.5. You are encouraged to set the value explicitly.", - DeprecationWarning, stacklevel=2) self.convert_charrefs = convert_charrefs self.reset() @@ -168,11 +114,6 @@ def close(self): """Handle any buffered data.""" self.goahead(1) - def error(self, message): - warnings.warn("The 'error' method is deprecated.", - DeprecationWarning, stacklevel=2) - raise HTMLParseError(message, self.getpos()) - __starttag_text = None def get_starttag_text(self): @@ -202,7 +143,7 @@ def goahead(self, end): # or there's more text incoming. If the latter is True, # we can't pass the text to handle_data in case we have # a charref cut in half at end. Try to determine if - # this is the case before proceding by looking for an + # this is the case before proceeding by looking for an # & near the end and see if it's followed by a space or ;. amppos = rawdata.rfind('&', max(i, n-34)) if (amppos >= 0 and @@ -235,10 +176,7 @@ def goahead(self, end): elif startswith("', i + 1) if k < 0: k = rawdata.find('<', i + 1) @@ -290,13 +226,10 @@ def goahead(self, end): if match: # match.group() will contain at least 2 chars if end and match.group() == rawdata[i:]: - if self.strict: - self.error("EOF in middle of entity or char ref") - else: - k = match.end() - if k <= i: - k = n - i = self.updatepos(i, i + 1) + k = match.end() + if k <= i: + k = n + i = self.updatepos(i, i + 1) # incomplete break elif (i + 1) < n: @@ -375,18 +308,12 @@ def parse_starttag(self, i): # Now parse the data between i+1 and j into a tag and attrs attrs = [] - if self.strict: - match = tagfind.match(rawdata, i+1) - else: - match = tagfind_tolerant.match(rawdata, i+1) + match = tagfind_tolerant.match(rawdata, i+1) assert match, 'unexpected call to parse_starttag()' k = match.end() self.lasttag = tag = match.group(1).lower() while k < endpos: - if self.strict: - m = attrfind.match(rawdata, k) - else: - m = attrfind_tolerant.match(rawdata, k) + m = attrfind_tolerant.match(rawdata, k) if not m: break attrname, rest, attrvalue = m.group(1, 2, 3) @@ -409,9 +336,6 @@ def parse_starttag(self, i): - self.__starttag_text.rfind("\n") else: offset = offset + len(self.__starttag_text) - 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('/>'): @@ -427,10 +351,7 @@ def parse_starttag(self, i): # or -1 if incomplete. def check_for_whole_start_tag(self, i): rawdata = self.rawdata - if self.strict: - m = locatestarttagend.match(rawdata, i) - else: - m = locatestarttagend_tolerant.match(rawdata, i) + m = locatestarttagend_tolerant.match(rawdata, i) if m: j = m.end() next = rawdata[j:j+1] @@ -443,9 +364,6 @@ def check_for_whole_start_tag(self, i): # buffer boundary return -1 # else bogus input - if self.strict: - self.updatepos(i, j + 1) - self.error("malformed empty start tag") if j > i: return j else: @@ -458,9 +376,6 @@ def check_for_whole_start_tag(self, i): # end of input in or before attribute value, or we have the # '/' from a '/>' ending return -1 - if self.strict: - self.updatepos(i, j) - self.error("malformed start tag") if j > i: return j else: @@ -480,8 +395,6 @@ def parse_endtag(self, i): if self.cdata_elem is not None: self.handle_data(rawdata[i:gtpos]) return gtpos - if self.strict: - self.error("bad end tag: %r" % (rawdata[i:gtpos],)) # find the name: w3.org/TR/html5/tokenization.html#tag-name-state namematch = tagfind_tolerant.match(rawdata, i+2) if not namematch: @@ -547,8 +460,7 @@ def handle_pi(self, data): pass def unknown_decl(self, data): - if self.strict: - self.error("unknown declaration: %r" % (data,)) + pass # Internal -- helper to remove special character quoting def unescape(self, s): diff --git a/lib/http/__init__.py b/lib/http/__init__.py index 196d3788..d4334cc8 100644 --- a/lib/http/__init__.py +++ b/lib/http/__init__.py @@ -1 +1,134 @@ -# This directory is a Python package. +from enum import IntEnum + +__all__ = ['HTTPStatus'] + +class HTTPStatus(IntEnum): + """HTTP status codes and reason phrases + + Status codes from the following RFCs are all observed: + + * RFC 7231: Hypertext Transfer Protocol (HTTP/1.1), obsoletes 2616 + * RFC 6585: Additional HTTP Status Codes + * RFC 3229: Delta encoding in HTTP + * RFC 4918: HTTP Extensions for WebDAV, obsoletes 2518 + * RFC 5842: Binding Extensions to WebDAV + * RFC 7238: Permanent Redirect + * RFC 2295: Transparent Content Negotiation in HTTP + * RFC 2774: An HTTP Extension Framework + """ + def __new__(cls, value, phrase, description=''): + obj = int.__new__(cls, value) + obj._value_ = value + + obj.phrase = phrase + obj.description = description + return obj + + # informational + CONTINUE = 100, 'Continue', 'Request received, please continue' + SWITCHING_PROTOCOLS = (101, 'Switching Protocols', + 'Switching to new protocol; obey Upgrade header') + PROCESSING = 102, 'Processing' + + # success + OK = 200, 'OK', 'Request fulfilled, document follows' + CREATED = 201, 'Created', 'Document created, URL follows' + ACCEPTED = (202, 'Accepted', + 'Request accepted, processing continues off-line') + NON_AUTHORITATIVE_INFORMATION = (203, + 'Non-Authoritative Information', 'Request fulfilled from cache') + NO_CONTENT = 204, 'No Content', 'Request fulfilled, nothing follows' + RESET_CONTENT = 205, 'Reset Content', 'Clear input form for further input' + PARTIAL_CONTENT = 206, 'Partial Content', 'Partial content follows' + MULTI_STATUS = 207, 'Multi-Status' + ALREADY_REPORTED = 208, 'Already Reported' + IM_USED = 226, 'IM Used' + + # redirection + MULTIPLE_CHOICES = (300, 'Multiple Choices', + 'Object has several resources -- see URI list') + MOVED_PERMANENTLY = (301, 'Moved Permanently', + 'Object moved permanently -- see URI list') + FOUND = 302, 'Found', 'Object moved temporarily -- see URI list' + SEE_OTHER = 303, 'See Other', 'Object moved -- see Method and URL list' + NOT_MODIFIED = (304, 'Not Modified', + 'Document has not changed since given time') + USE_PROXY = (305, 'Use Proxy', + 'You must use proxy specified in Location to access this resource') + TEMPORARY_REDIRECT = (307, 'Temporary Redirect', + 'Object moved temporarily -- see URI list') + PERMANENT_REDIRECT = (308, 'Permanent Redirect', + 'Object moved temporarily -- see URI list') + + # client error + BAD_REQUEST = (400, 'Bad Request', + 'Bad request syntax or unsupported method') + UNAUTHORIZED = (401, 'Unauthorized', + 'No permission -- see authorization schemes') + PAYMENT_REQUIRED = (402, 'Payment Required', + 'No payment -- see charging schemes') + FORBIDDEN = (403, 'Forbidden', + 'Request forbidden -- authorization will not help') + NOT_FOUND = (404, 'Not Found', + 'Nothing matches the given URI') + METHOD_NOT_ALLOWED = (405, 'Method Not Allowed', + 'Specified method is invalid for this resource') + NOT_ACCEPTABLE = (406, 'Not Acceptable', + 'URI not available in preferred format') + PROXY_AUTHENTICATION_REQUIRED = (407, + 'Proxy Authentication Required', + 'You must authenticate with this proxy before proceeding') + REQUEST_TIMEOUT = (408, 'Request Timeout', + 'Request timed out; try again later') + CONFLICT = 409, 'Conflict', 'Request conflict' + GONE = (410, 'Gone', + 'URI no longer exists and has been permanently removed') + LENGTH_REQUIRED = (411, 'Length Required', + 'Client must specify Content-Length') + PRECONDITION_FAILED = (412, 'Precondition Failed', + 'Precondition in headers is false') + REQUEST_ENTITY_TOO_LARGE = (413, 'Request Entity Too Large', + 'Entity is too large') + REQUEST_URI_TOO_LONG = (414, 'Request-URI Too Long', + 'URI is too long') + UNSUPPORTED_MEDIA_TYPE = (415, 'Unsupported Media Type', + 'Entity body in unsupported format') + REQUESTED_RANGE_NOT_SATISFIABLE = (416, + 'Requested Range Not Satisfiable', + 'Cannot satisfy request range') + EXPECTATION_FAILED = (417, 'Expectation Failed', + 'Expect condition could not be satisfied') + UNPROCESSABLE_ENTITY = 422, 'Unprocessable Entity' + LOCKED = 423, 'Locked' + FAILED_DEPENDENCY = 424, 'Failed Dependency' + UPGRADE_REQUIRED = 426, 'Upgrade Required' + PRECONDITION_REQUIRED = (428, 'Precondition Required', + 'The origin server requires the request to be conditional') + TOO_MANY_REQUESTS = (429, 'Too Many Requests', + 'The user has sent too many requests in ' + 'a given amount of time ("rate limiting")') + REQUEST_HEADER_FIELDS_TOO_LARGE = (431, + 'Request Header Fields Too Large', + 'The server is unwilling to process the request because its header ' + 'fields are too large') + + # server errors + INTERNAL_SERVER_ERROR = (500, 'Internal Server Error', + 'Server got itself in trouble') + NOT_IMPLEMENTED = (501, 'Not Implemented', + 'Server does not support this operation') + BAD_GATEWAY = (502, 'Bad Gateway', + 'Invalid responses from another server/proxy') + SERVICE_UNAVAILABLE = (503, 'Service Unavailable', + 'The server cannot process the request due to a high load') + GATEWAY_TIMEOUT = (504, 'Gateway Timeout', + 'The gateway server did not receive a timely response') + HTTP_VERSION_NOT_SUPPORTED = (505, 'HTTP Version Not Supported', + 'Cannot fulfill request') + VARIANT_ALSO_NEGOTIATES = 506, 'Variant Also Negotiates' + INSUFFICIENT_STORAGE = 507, 'Insufficient Storage' + LOOP_DETECTED = 508, 'Loop Detected' + NOT_EXTENDED = 510, 'Not Extended' + NETWORK_AUTHENTICATION_REQUIRED = (511, + 'Network Authentication Required', + 'The client needs to authenticate to gain network access') diff --git a/lib/http/client.py b/lib/http/client.py index 1c69dcb5..f7efe151 100644 --- a/lib/http/client.py +++ b/lib/http/client.py @@ -20,10 +20,12 @@ | ( putheader() )* endheaders() v Request-sent - | - | response = getresponse() - v - Unread-response [Response-headers-read] + |\_____________________________ + | | getresponse() raises + | response = getresponse() | ConnectionError + v v + Unread-response Idle + [Response-headers-read] |\____________________ | | | response.read() | putrequest() @@ -68,6 +70,7 @@ import email.parser import email.message +import http import io import os import re @@ -82,7 +85,8 @@ "UnknownTransferEncoding", "UnimplementedFileMode", "IncompleteRead", "InvalidURL", "ImproperConnectionState", "CannotSendRequest", "CannotSendHeader", "ResponseNotReady", - "BadStatusLine", "LineTooLong", "error", "responses"] + "BadStatusLine", "LineTooLong", "RemoteDisconnected", "error", + "responses"] HTTP_PORT = 80 HTTPS_PORT = 443 @@ -94,122 +98,13 @@ _CS_REQ_STARTED = 'Request-started' _CS_REQ_SENT = 'Request-sent' -# status codes -# informational -CONTINUE = 100 -SWITCHING_PROTOCOLS = 101 -PROCESSING = 102 - -# successful -OK = 200 -CREATED = 201 -ACCEPTED = 202 -NON_AUTHORITATIVE_INFORMATION = 203 -NO_CONTENT = 204 -RESET_CONTENT = 205 -PARTIAL_CONTENT = 206 -MULTI_STATUS = 207 -IM_USED = 226 - -# redirection -MULTIPLE_CHOICES = 300 -MOVED_PERMANENTLY = 301 -FOUND = 302 -SEE_OTHER = 303 -NOT_MODIFIED = 304 -USE_PROXY = 305 -TEMPORARY_REDIRECT = 307 - -# client error -BAD_REQUEST = 400 -UNAUTHORIZED = 401 -PAYMENT_REQUIRED = 402 -FORBIDDEN = 403 -NOT_FOUND = 404 -METHOD_NOT_ALLOWED = 405 -NOT_ACCEPTABLE = 406 -PROXY_AUTHENTICATION_REQUIRED = 407 -REQUEST_TIMEOUT = 408 -CONFLICT = 409 -GONE = 410 -LENGTH_REQUIRED = 411 -PRECONDITION_FAILED = 412 -REQUEST_ENTITY_TOO_LARGE = 413 -REQUEST_URI_TOO_LONG = 414 -UNSUPPORTED_MEDIA_TYPE = 415 -REQUESTED_RANGE_NOT_SATISFIABLE = 416 -EXPECTATION_FAILED = 417 -UNPROCESSABLE_ENTITY = 422 -LOCKED = 423 -FAILED_DEPENDENCY = 424 -UPGRADE_REQUIRED = 426 -PRECONDITION_REQUIRED = 428 -TOO_MANY_REQUESTS = 429 -REQUEST_HEADER_FIELDS_TOO_LARGE = 431 - -# server error -INTERNAL_SERVER_ERROR = 500 -NOT_IMPLEMENTED = 501 -BAD_GATEWAY = 502 -SERVICE_UNAVAILABLE = 503 -GATEWAY_TIMEOUT = 504 -HTTP_VERSION_NOT_SUPPORTED = 505 -INSUFFICIENT_STORAGE = 507 -NOT_EXTENDED = 510 -NETWORK_AUTHENTICATION_REQUIRED = 511 +# hack to maintain backwards compatibility +globals().update(http.HTTPStatus.__members__) + +# another hack to maintain backwards compatibility # Mapping status codes to official W3C names -responses = { - 100: 'Continue', - 101: 'Switching Protocols', - - 200: 'OK', - 201: 'Created', - 202: 'Accepted', - 203: 'Non-Authoritative Information', - 204: 'No Content', - 205: 'Reset Content', - 206: 'Partial Content', - - 300: 'Multiple Choices', - 301: 'Moved Permanently', - 302: 'Found', - 303: 'See Other', - 304: 'Not Modified', - 305: 'Use Proxy', - 306: '(Unused)', - 307: 'Temporary Redirect', - - 400: 'Bad Request', - 401: 'Unauthorized', - 402: 'Payment Required', - 403: 'Forbidden', - 404: 'Not Found', - 405: 'Method Not Allowed', - 406: 'Not Acceptable', - 407: 'Proxy Authentication Required', - 408: 'Request Timeout', - 409: 'Conflict', - 410: 'Gone', - 411: 'Length Required', - 412: 'Precondition Failed', - 413: 'Request Entity Too Large', - 414: 'Request-URI Too Long', - 415: 'Unsupported Media Type', - 416: 'Requested Range Not Satisfiable', - 417: 'Expectation Failed', - 428: 'Precondition Required', - 429: 'Too Many Requests', - 431: 'Request Header Fields Too Large', - - 500: 'Internal Server Error', - 501: 'Not Implemented', - 502: 'Bad Gateway', - 503: 'Service Unavailable', - 504: 'Gateway Timeout', - 505: 'HTTP Version Not Supported', - 511: 'Network Authentication Required', -} +responses = {v: v.phrase for v in http.HTTPStatus.__members__.values()} # maximal amount of data to read at one time in _safe_read MAXAMOUNT = 1048576 @@ -241,16 +136,45 @@ # # VCHAR defined in http://tools.ietf.org/html/rfc5234#appendix-B.1 -# the patterns for both name and value are more leniant than RFC +# the patterns for both name and value are more lenient than RFC # definitions to allow for backwards compatibility _is_legal_header_name = re.compile(rb'[^:\s][^:\r\n]*').fullmatch _is_illegal_header_value = re.compile(rb'\n(?![ \t])|\r(?![ \t\n])').search +# These characters are not allowed within HTTP URL paths. +# See https://tools.ietf.org/html/rfc3986#section-3.3 and the +# https://tools.ietf.org/html/rfc3986#appendix-A pchar definition. +# Prevents CVE-2019-9740. Includes control characters such as \r\n. +# We don't restrict chars above \x7f as putrequest() limits us to ASCII. +_contains_disallowed_url_pchar_re = re.compile('[\x00-\x20\x7f]') +# Arguably only these _should_ allowed: +# _is_allowed_url_pchars_re = re.compile(r"^[/!$&'()*+,;=:@%a-zA-Z0-9._~-]+$") +# We are more lenient for assumed real world compatibility purposes. + +# These characters are not allowed within HTTP method names +# to prevent http header injection. +_contains_disallowed_method_pchar_re = re.compile('[\x00-\x1f]') + # We always set the Content-Length header for these methods because some # servers will otherwise respond with a 411 _METHODS_EXPECTING_BODY = {'PATCH', 'POST', 'PUT'} +def _encode(data, name='data'): + """Call data.encode("latin-1") but show a better error message.""" + try: + return data.encode("latin-1") + except UnicodeEncodeError as err: + raise UnicodeEncodeError( + err.encoding, + err.object, + err.start, + err.end, + "%s (%.20r) is not valid Latin-1. Use %s.encode('utf-8') " + "if you want to send it encoded in UTF-8." % + (name.title(), data[err.start:err.end], name)) from None + + class HTTPMessage(email.message.Message): # XXX The only usage of this method is in # http.server.CGIHTTPRequestHandler. Maybe move the code there so @@ -305,7 +229,7 @@ def parse_headers(fp, _class=HTTPMessage): return email.parser.Parser(_class=_class).parsestr(hstring) -class HTTPResponse(io.RawIOBase): +class HTTPResponse(io.BufferedIOBase): # See RFC 2616 sec 19.6 and RFC 1945 sec 6 for details. @@ -353,7 +277,8 @@ def _read_status(self): if not line: # Presumably, the server closed the connection before # sending a valid response. - raise BadStatusLine(line) + raise RemoteDisconnected("Remote end closed connection without" + " response") try: version, status, reason = line.split(None, 2) except ValueError: @@ -532,9 +457,10 @@ def read(self, amt=None): return b"" if amt is not None: - # Amount is given, so call base class version - # (which is implemented in terms of self.readinto) - return super(HTTPResponse, self).read(amt) + # Amount is given, implement using readinto + b = bytearray(amt) + n = self.readinto(b) + return memoryview(b)[:n].tobytes() else: # Amount is not given (unbounded read) so we must check self.length # and self.chunked @@ -614,71 +540,67 @@ def _read_and_discard_trailer(self): if line in (b'\r\n', b'\n', b''): break + def _get_chunk_left(self): + # return self.chunk_left, reading a new chunk if necessary. + # chunk_left == 0: at the end of the current chunk, need to close it + # chunk_left == None: No current chunk, should read next. + # This function returns non-zero or None if the last chunk has + # been read. + chunk_left = self.chunk_left + if not chunk_left: # Can be 0 or None + if chunk_left is not None: + # We are at the end of chunk. dicard chunk end + self._safe_read(2) # toss the CRLF at the end of the chunk + try: + chunk_left = self._read_next_chunk_size() + except ValueError: + raise IncompleteRead(b'') + if chunk_left == 0: + # last chunk: 1*("0") [ chunk-extension ] CRLF + self._read_and_discard_trailer() + # we read everything; close the "file" + self._close_conn() + chunk_left = None + self.chunk_left = chunk_left + return chunk_left + def _readall_chunked(self): assert self.chunked != _UNKNOWN - chunk_left = self.chunk_left value = [] - while True: - if chunk_left is None: - try: - chunk_left = self._read_next_chunk_size() - if chunk_left == 0: - break - except ValueError: - raise IncompleteRead(b''.join(value)) - value.append(self._safe_read(chunk_left)) - - # we read the whole chunk, get another - self._safe_read(2) # toss the CRLF at the end of the chunk - chunk_left = None - - self._read_and_discard_trailer() - - # we read everything; close the "file" - self._close_conn() - - return b''.join(value) + try: + while True: + chunk_left = self._get_chunk_left() + if chunk_left is None: + break + value.append(self._safe_read(chunk_left)) + self.chunk_left = 0 + return b''.join(value) + except IncompleteRead: + raise IncompleteRead(b''.join(value)) def _readinto_chunked(self, b): assert self.chunked != _UNKNOWN - chunk_left = self.chunk_left - total_bytes = 0 mvb = memoryview(b) - while True: - if chunk_left is None: - try: - chunk_left = self._read_next_chunk_size() - if chunk_left == 0: - break - except ValueError: - raise IncompleteRead(bytes(b[0:total_bytes])) - - if len(mvb) < chunk_left: - n = self._safe_readinto(mvb) - self.chunk_left = chunk_left - n - return total_bytes + n - elif len(mvb) == chunk_left: - n = self._safe_readinto(mvb) - self._safe_read(2) # toss the CRLF at the end of the chunk - self.chunk_left = None - return total_bytes + n - else: - temp_mvb = mvb[0:chunk_left] + try: + while True: + chunk_left = self._get_chunk_left() + if chunk_left is None: + return total_bytes + + if len(mvb) <= chunk_left: + n = self._safe_readinto(mvb) + self.chunk_left = chunk_left - n + return total_bytes + n + + temp_mvb = mvb[:chunk_left] n = self._safe_readinto(temp_mvb) mvb = mvb[n:] total_bytes += n + self.chunk_left = 0 - # we read the whole chunk, get another - self._safe_read(2) # toss the CRLF at the end of the chunk - chunk_left = None - - self._read_and_discard_trailer() - - # we read everything; close the "file" - self._close_conn() - - return total_bytes + except IncompleteRead: + raise IncompleteRead(bytes(b[0:total_bytes])) def _safe_read(self, amt): """Read the number of bytes requested, compensating for partial reads. @@ -719,6 +641,81 @@ def _safe_readinto(self, b): total_bytes += n return total_bytes + def read1(self, n=-1): + """Read with at most one underlying system call. If at least one + byte is buffered, return that instead. + """ + if self.fp is None or self._method == "HEAD": + return b"" + if self.chunked: + return self._read1_chunked(n) + if self.length is not None and (n < 0 or n > self.length): + n = self.length + try: + result = self.fp.read1(n) + except ValueError: + if n >= 0: + raise + # some implementations, like BufferedReader, don't support -1 + # Read an arbitrarily selected largeish chunk. + result = self.fp.read1(16*1024) + if not result and n: + self._close_conn() + elif self.length is not None: + self.length -= len(result) + return result + + def peek(self, n=-1): + # Having this enables IOBase.readline() to read more than one + # byte at a time + if self.fp is None or self._method == "HEAD": + return b"" + if self.chunked: + return self._peek_chunked(n) + return self.fp.peek(n) + + def readline(self, limit=-1): + if self.fp is None or self._method == "HEAD": + return b"" + if self.chunked: + # Fallback to IOBase readline which uses peek() and read() + return super().readline(limit) + if self.length is not None and (limit < 0 or limit > self.length): + limit = self.length + result = self.fp.readline(limit) + if not result and limit: + self._close_conn() + elif self.length is not None: + self.length -= len(result) + return result + + def _read1_chunked(self, n): + # Strictly speaking, _get_chunk_left() may cause more than one read, + # but that is ok, since that is to satisfy the chunked protocol. + chunk_left = self._get_chunk_left() + if chunk_left is None or n == 0: + return b'' + if not (0 <= n <= chunk_left): + n = chunk_left # if n is negative or larger than chunk_left + read = self.fp.read1(n) + self.chunk_left -= len(read) + if not read: + raise IncompleteRead(b"") + return read + + def _peek_chunked(self, n): + # Strictly speaking, _get_chunk_left() may cause more than one read, + # but that is ok, since that is to satisfy the chunked protocol. + try: + chunk_left = self._get_chunk_left() + except IncompleteRead: + return b'' # peek doesn't worry about protocol + if chunk_left is None: + return b'' # eof + # peek is allowed to return more than requested. Just request the + # entire chunk, and truncate what we get. + return self.fp.peek(chunk_left)[:chunk_left] + def fileno(self): return self.fp.fileno() @@ -762,14 +759,6 @@ class HTTPConnection: default_port = HTTP_PORT auto_open = 1 debuglevel = 0 - # TCP Maximum Segment Size (MSS) is determined by the TCP stack on - # a per-connection basis. There is no simple and efficient - # platform independent mechanism for determining the MSS, so - # instead a reasonable estimate is chosen. The getsockopt() - # interface using the TCP_MAXSEG parameter may be a suitable - # approach on some operating systems. A value of 16KiB is chosen - # as a reasonable estimate of the maximum MSS. - mss = 16384 def __init__(self, host, port=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, source_address=None): @@ -786,6 +775,7 @@ def __init__(self, host, port=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, (self.host, self.port) = self._get_hostport(host, port) + self._validate_host(self.host) # This is stored as an instance variable to allow unit # tests to replace it with a suitable mockup self._create_connection = socket.create_connection @@ -851,7 +841,7 @@ def _tunnel(self): response = self.response_class(self.sock, method=self._method) (version, code, message) = response._read_status() - if code != 200: + if code != http.HTTPStatus.OK: self.close() raise OSError("Tunnel connection failed: %d %s" % (code, message.strip())) @@ -865,10 +855,14 @@ def _tunnel(self): if line in (b'\r\n', b'\n', b''): break + if self.debuglevel > 0: + print('header:', line.decode()) + def connect(self): """Connect to the host and port specified in __init__.""" - self.sock = self._create_connection((self.host,self.port), - self.timeout, self.source_address) + self.sock = self._create_connection( + (self.host,self.port), self.timeout, self.source_address) + self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) if self._tunnel_host: self._tunnel() @@ -951,22 +945,13 @@ def _send_output(self, message_body=None): self._buffer.extend((b"", b"")) msg = b"\r\n".join(self._buffer) del self._buffer[:] - # If msg and message_body are sent in a single send() call, - # it will avoid performance problems caused by the interaction - # between delayed ack and the Nagle algorithm. However, - # there is no performance gain if the message is larger - # than MSS (and there is a memory penalty for the message - # copy). - if isinstance(message_body, bytes) and len(message_body) < self.mss: - msg += message_body - message_body = None + self.send(msg) if message_body is not None: - # message_body was not a string (i.e. it is a file), and - # we must run the risk of Nagle. self.send(message_body) - def putrequest(self, method, url, skip_host=0, skip_accept_encoding=0): + def putrequest(self, method, url, skip_host=False, + skip_accept_encoding=False): """Send a request to the server. `method' specifies an HTTP request method, e.g. 'GET'. @@ -1004,14 +989,17 @@ def putrequest(self, method, url, skip_host=0, skip_accept_encoding=0): else: raise CannotSendRequest(self.__state) - # Save the method we use, we need it later in the response phase + self._validate_method(method) + + # Save the method for use later in the response phase self._method = method - if not url: - url = '/' + + url = url or '/' + self._validate_path(url) + request = '%s %s %s' % (method, url, self._http_vsn_str) - # Non-ASCII characters should have been eliminated earlier - self._output(request.encode('ascii')) + self._output(self._encode_request(request)) if self._http_vsn == 11: # Issue some standard headers for better HTTP/1.1 compliance @@ -1089,6 +1077,42 @@ def putrequest(self, method, url, skip_host=0, skip_accept_encoding=0): # For HTTP/1.0, the server will assume "not chunked" pass + def _encode_request(self, request): + # ASCII also helps prevent CVE-2019-9740. + return request.encode('ascii') + + def _validate_method(self, method): + """Validate a method name for putrequest.""" + # prevent http header injection + match = _contains_disallowed_method_pchar_re.search(method) + if match: + raise ValueError( + "method can't contain control characters. %r " + "(found at least %r)" + % (method, match.group())) + + def _validate_path(self, url): + """Validate a url for putrequest.""" + # Prevent CVE-2019-9740. + match = _contains_disallowed_url_pchar_re.search(url) + if match: + msg = ( + "URL can't contain control characters. {url!r} " + "(found at least {matched!r})" + ).format(matched=match.group(), **locals()) + raise InvalidURL(msg) + + def _validate_host(self, host): + """Validate a host so it doesn't contain control characters.""" + # Prevent CVE-2019-18348. + match = _contains_disallowed_url_pchar_re.search(host) + if match: + msg = ( + "URL can't contain control characters. {host!r} " + "(found at least {matched!r})" + ).format(matched=match.group(), host=host) + raise InvalidURL(msg) + def putheader(self, header, *values): """Send a request header line to the server. @@ -1178,7 +1202,7 @@ def _send_request(self, method, url, body, headers): if isinstance(body, str): # RFC 2616 Section 3.7.1 says that text default has a # default charset of iso-8859-1. - body = body.encode('iso-8859-1') + body = _encode(body, 'body') self.endheaders(body) def getresponse(self): @@ -1186,7 +1210,7 @@ def getresponse(self): If the HTTPConnection is in the correct state, returns an instance of HTTPResponse or of whatever object is returned by - class the response_class variable. + the response_class variable. If a request has not been sent or if a previous response has not be handled, ResponseNotReady is raised. If the HTTP @@ -1224,7 +1248,11 @@ class the response_class variable. response = self.response_class(self.sock, method=self._method) try: - response.begin() + try: + response.begin() + except ConnectionError: + self.close() + raise assert response.will_close != _UNKNOWN self.__state = _CS_IDLE @@ -1327,7 +1355,8 @@ def __repr__(self): e = ', %i more expected' % self.expected else: e = '' - return 'IncompleteRead(%i bytes read%s)' % (len(self.partial), e) + return '%s(%i bytes read%s)' % (self.__class__.__name__, + len(self.partial), e) def __str__(self): return repr(self) @@ -1355,5 +1384,10 @@ def __init__(self, line_type): HTTPException.__init__(self, "got more than %d bytes when reading %s" % (_MAXLINE, line_type)) +class RemoteDisconnected(ConnectionResetError, BadStatusLine): + def __init__(self, *pos, **kw): + BadStatusLine.__init__(self, "") + ConnectionResetError.__init__(self, *pos, **kw) + # for backwards compatibility error = HTTPException diff --git a/lib/http/cookiejar.py b/lib/http/cookiejar.py index 954216af..afed5bc9 100644 --- a/lib/http/cookiejar.py +++ b/lib/http/cookiejar.py @@ -120,7 +120,7 @@ def time2netscape(t=None): dt = datetime.datetime.utcnow() else: dt = datetime.datetime.utcfromtimestamp(t) - return "%s %02d-%s-%04d %02d:%02d:%02d GMT" % ( + return "%s, %02d-%s-%04d %02d:%02d:%02d GMT" % ( DAYS[dt.weekday()], dt.day, MONTHS[dt.month-1], dt.year, dt.hour, dt.minute, dt.second) @@ -143,6 +143,10 @@ def offset_from_tz_string(tz): return offset def _str2time(day, mon, yr, hr, min, sec, tz): + yr = int(yr) + if yr > datetime.MAXYEAR: + return None + # translate month name to number # month numbers start with 1 (January) try: @@ -163,7 +167,6 @@ def _str2time(day, mon, yr, hr, min, sec, tz): if min is None: min = 0 if sec is None: sec = 0 - yr = int(yr) day = int(day) hr = int(hr) min = int(min) @@ -213,10 +216,14 @@ def _str2time(day, mon, yr, hr, min, sec, tz): (?::(\d\d))? # optional seconds )? # optional clock \s* - ([-+]?\d{2,4}|(?![APap][Mm]\b)[A-Za-z]+)? # timezone + (?: + ([-+]?\d{2,4}|(?![APap][Mm]\b)[A-Za-z]+) # timezone \s* - (?:\(\w+\))? # ASCII representation of timezone in parens. - \s*$""", re.X | re.ASCII) + )? + (?: + \(\w+\) # ASCII representation of timezone in parens. + \s* + )?$""", re.X | re.ASCII) def http2time(text): """Returns time in seconds since epoch of time represented by a string. @@ -286,9 +293,11 @@ def http2time(text): (?::?(\d\d(?:\.\d*)?))? # optional seconds (and fractional) )? # optional clock \s* - ([-+]?\d\d?:?(:?\d\d)? - |Z|z)? # timezone (Z is "zero meridian", i.e. GMT) - \s*$""", re.X | re. ASCII) + (?: + ([-+]?\d\d?:?(:?\d\d)? + |Z|z) # timezone (Z is "zero meridian", i.e. GMT) + \s* + )?$""", re.X | re. ASCII) def iso2time(text): """ As for http2time, but parses the ISO 8601 formats: @@ -821,7 +830,7 @@ def __repr__(self): args.append("%s=%s" % (name, repr(attr))) args.append("rest=%s" % repr(self._rest)) args.append("rfc2109=%s" % repr(self.rfc2109)) - return "Cookie(%s)" % ", ".join(args) + return "%s(%s)" % (self.__class__.__name__, ", ".join(args)) class CookiePolicy: @@ -830,7 +839,7 @@ class CookiePolicy: May also modify cookies, though this is probably a bad idea. The subclass DefaultCookiePolicy defines the standard rules for Netscape - and RFC 2965 cookies -- override that if you want a customised policy. + and RFC 2965 cookies -- override that if you want a customized policy. """ def set_ok(self, cookie, request): @@ -1851,7 +1860,7 @@ def lwp_cookie_str(cookie): class LWPCookieJar(FileCookieJar): """ The LWPCookieJar saves a sequence of "Set-Cookie3" lines. - "Set-Cookie3" is the format used by the libwww-perl libary, not known + "Set-Cookie3" is the format used by the libwww-perl library, not known to be compatible with any browser, but which is easy to read and doesn't lose information about RFC 2965 cookies. @@ -2012,7 +2021,6 @@ def _really_load(self, f, filename, ignore_discard, ignore_expires): magic = f.readline() if not self.magic_re.search(magic): - f.close() raise LoadError( "%r does not look like a Netscape format cookies file" % filename) diff --git a/lib/http/cookies.py b/lib/http/cookies.py index 482e601d..a73fe387 100644 --- a/lib/http/cookies.py +++ b/lib/http/cookies.py @@ -138,6 +138,12 @@ _semispacejoin = '; '.join _spacejoin = ' '.join +def _warn_deprecated_setter(setter): + import warnings + msg = ('The .%s setter is deprecated. The attribute will be read-only in ' + 'future releases. Please use the set() method instead.' % setter) + warnings.warn(msg, DeprecationWarning, stacklevel=3) + # # Define an exception visible to External modules # @@ -150,89 +156,37 @@ class CookieError(Exception): # a two-way quoting algorithm. Any non-text character is translated # into a 4 character sequence: a forward-slash followed by the # three-digit octal equivalent of the character. Any '\' or '"' is -# quoted with a preceeding '\' slash. +# quoted with a preceding '\' slash. +# Because of the way browsers really handle cookies (as opposed to what +# the RFC says) we also encode "," and ";". # # These are taken from RFC2068 and RFC2109. # _LegalChars is the list of chars which don't require "'s # _Translator hash-table for fast quoting # -_LegalChars = string.ascii_letters + string.digits + "!#$%&'*+-.^_`|~:" -_Translator = { - '\000' : '\\000', '\001' : '\\001', '\002' : '\\002', - '\003' : '\\003', '\004' : '\\004', '\005' : '\\005', - '\006' : '\\006', '\007' : '\\007', '\010' : '\\010', - '\011' : '\\011', '\012' : '\\012', '\013' : '\\013', - '\014' : '\\014', '\015' : '\\015', '\016' : '\\016', - '\017' : '\\017', '\020' : '\\020', '\021' : '\\021', - '\022' : '\\022', '\023' : '\\023', '\024' : '\\024', - '\025' : '\\025', '\026' : '\\026', '\027' : '\\027', - '\030' : '\\030', '\031' : '\\031', '\032' : '\\032', - '\033' : '\\033', '\034' : '\\034', '\035' : '\\035', - '\036' : '\\036', '\037' : '\\037', - - # Because of the way browsers really handle cookies (as opposed - # to what the RFC says) we also encode , and ; - - ',' : '\\054', ';' : '\\073', - - '"' : '\\"', '\\' : '\\\\', - - '\177' : '\\177', '\200' : '\\200', '\201' : '\\201', - '\202' : '\\202', '\203' : '\\203', '\204' : '\\204', - '\205' : '\\205', '\206' : '\\206', '\207' : '\\207', - '\210' : '\\210', '\211' : '\\211', '\212' : '\\212', - '\213' : '\\213', '\214' : '\\214', '\215' : '\\215', - '\216' : '\\216', '\217' : '\\217', '\220' : '\\220', - '\221' : '\\221', '\222' : '\\222', '\223' : '\\223', - '\224' : '\\224', '\225' : '\\225', '\226' : '\\226', - '\227' : '\\227', '\230' : '\\230', '\231' : '\\231', - '\232' : '\\232', '\233' : '\\233', '\234' : '\\234', - '\235' : '\\235', '\236' : '\\236', '\237' : '\\237', - '\240' : '\\240', '\241' : '\\241', '\242' : '\\242', - '\243' : '\\243', '\244' : '\\244', '\245' : '\\245', - '\246' : '\\246', '\247' : '\\247', '\250' : '\\250', - '\251' : '\\251', '\252' : '\\252', '\253' : '\\253', - '\254' : '\\254', '\255' : '\\255', '\256' : '\\256', - '\257' : '\\257', '\260' : '\\260', '\261' : '\\261', - '\262' : '\\262', '\263' : '\\263', '\264' : '\\264', - '\265' : '\\265', '\266' : '\\266', '\267' : '\\267', - '\270' : '\\270', '\271' : '\\271', '\272' : '\\272', - '\273' : '\\273', '\274' : '\\274', '\275' : '\\275', - '\276' : '\\276', '\277' : '\\277', '\300' : '\\300', - '\301' : '\\301', '\302' : '\\302', '\303' : '\\303', - '\304' : '\\304', '\305' : '\\305', '\306' : '\\306', - '\307' : '\\307', '\310' : '\\310', '\311' : '\\311', - '\312' : '\\312', '\313' : '\\313', '\314' : '\\314', - '\315' : '\\315', '\316' : '\\316', '\317' : '\\317', - '\320' : '\\320', '\321' : '\\321', '\322' : '\\322', - '\323' : '\\323', '\324' : '\\324', '\325' : '\\325', - '\326' : '\\326', '\327' : '\\327', '\330' : '\\330', - '\331' : '\\331', '\332' : '\\332', '\333' : '\\333', - '\334' : '\\334', '\335' : '\\335', '\336' : '\\336', - '\337' : '\\337', '\340' : '\\340', '\341' : '\\341', - '\342' : '\\342', '\343' : '\\343', '\344' : '\\344', - '\345' : '\\345', '\346' : '\\346', '\347' : '\\347', - '\350' : '\\350', '\351' : '\\351', '\352' : '\\352', - '\353' : '\\353', '\354' : '\\354', '\355' : '\\355', - '\356' : '\\356', '\357' : '\\357', '\360' : '\\360', - '\361' : '\\361', '\362' : '\\362', '\363' : '\\363', - '\364' : '\\364', '\365' : '\\365', '\366' : '\\366', - '\367' : '\\367', '\370' : '\\370', '\371' : '\\371', - '\372' : '\\372', '\373' : '\\373', '\374' : '\\374', - '\375' : '\\375', '\376' : '\\376', '\377' : '\\377' - } +_LegalChars = string.ascii_letters + string.digits + "!#$%&'*+-.^_`|~:" +_UnescapedChars = _LegalChars + ' ()/<=>?@[]{}' + +_Translator = {n: '\\%03o' % n + for n in set(range(256)) - set(map(ord, _UnescapedChars))} +_Translator.update({ + ord('"'): '\\"', + ord('\\'): '\\\\', +}) -def _quote(str, LegalChars=_LegalChars): +_is_legal_key = re.compile('[%s]+' % re.escape(_LegalChars)).fullmatch + +def _quote(str): 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): + if str is None or _is_legal_key(str): return str else: - return '"' + _nulljoin(_Translator.get(s, s) for s in str) + '"' + return '"' + str.translate(_Translator) + '"' _OctalPatt = re.compile(r"\\[0-3][0-7][0-7]") @@ -241,7 +195,7 @@ def _quote(str, LegalChars=_LegalChars): 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 str is None or len(str) < 2: return str if str[0] != '"' or str[-1] != '"': return str @@ -339,33 +293,108 @@ class Morsel(dict): def __init__(self): # Set defaults - self.key = self.value = self.coded_value = None + self._key = self._value = self._coded_value = None # Set default attributes for key in self._reserved: dict.__setitem__(self, key, "") + @property + def key(self): + return self._key + + @key.setter + def key(self, key): + _warn_deprecated_setter('key') + self._key = key + + @property + def value(self): + return self._value + + @value.setter + def value(self, value): + _warn_deprecated_setter('value') + self._value = value + + @property + def coded_value(self): + return self._coded_value + + @coded_value.setter + def coded_value(self, coded_value): + _warn_deprecated_setter('coded_value') + self._coded_value = coded_value + def __setitem__(self, K, V): K = K.lower() if not K in self._reserved: - raise CookieError("Invalid Attribute %s" % K) + raise CookieError("Invalid attribute %r" % (K,)) dict.__setitem__(self, K, V) + def setdefault(self, key, val=None): + key = key.lower() + if key not in self._reserved: + raise CookieError("Invalid attribute %r" % (key,)) + return dict.setdefault(self, key, val) + + def __eq__(self, morsel): + if not isinstance(morsel, Morsel): + return NotImplemented + return (dict.__eq__(self, morsel) and + self._value == morsel._value and + self._key == morsel._key and + self._coded_value == morsel._coded_value) + + __ne__ = object.__ne__ + + def copy(self): + morsel = Morsel() + dict.update(morsel, self) + morsel.__dict__.update(self.__dict__) + return morsel + + def update(self, values): + data = {} + for key, val in dict(values).items(): + key = key.lower() + if key not in self._reserved: + raise CookieError("Invalid attribute %r" % (key,)) + data[key] = val + dict.update(self, data) + def isReservedKey(self, K): return K.lower() in self._reserved def set(self, key, val, coded_val, LegalChars=_LegalChars): - # First we verify that the key isn't a reserved word - # Second we make sure it only contains legal characters + if LegalChars != _LegalChars: + import warnings + warnings.warn( + 'LegalChars parameter is deprecated, ignored and will ' + 'be removed in future versions.', DeprecationWarning, + stacklevel=2) + if key.lower() in self._reserved: - raise CookieError("Attempt to set a reserved key: %s" % key) - if any(c not in LegalChars for c in key): - raise CookieError("Illegal key value: %s" % key) + raise CookieError('Attempt to set a reserved key %r' % (key,)) + if not _is_legal_key(key): + raise CookieError('Illegal key %r' % (key,)) # It's a good key, so save it. - self.key = key - self.value = val - self.coded_value = coded_val + self._key = key + self._value = val + self._coded_value = coded_val + + def __getstate__(self): + return { + 'key': self._key, + 'value': self._value, + 'coded_value': self._coded_value, + } + + def __setstate__(self, state): + self._key = state['key'] + self._value = state['value'] + self._coded_value = state['coded_value'] def output(self, attrs=None, header="Set-Cookie:"): return "%s %s" % (header, self.OutputString(attrs)) @@ -373,8 +402,7 @@ def output(self, attrs=None, header="Set-Cookie:"): __str__ = output def __repr__(self): - return '<%s: %s=%s>' % (self.__class__.__name__, - self.key, repr(self.value)) + return '<%s: %s>' % (self.__class__.__name__, self.OutputString()) def js_output(self, attrs=None): # Print javascript @@ -408,10 +436,9 @@ def OutputString(self, attrs=None): 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])) + elif key in self._flags: + if value: + append(str(self._reserved[key])) else: append("%s=%s" % (self._reserved[key], value)) @@ -534,10 +561,17 @@ def load(self, rawdata): return def __parse_string(self, str, patt=_CookiePattern): - i = 0 # Our starting point - n = len(str) # Length of string - M = None # current morsel + i = 0 # Our starting point + n = len(str) # Length of string + parsed_items = [] # Parsed (type, key, value) triples + morsel_seen = False # A key=value pair was previously encountered + + TYPE_ATTRIBUTE = 1 + TYPE_KEYVALUE = 2 + # We first parse the whole cookie string and reject it if it's + # syntactically invalid (this helps avoid some classes of injection + # attacks). while 0 <= i < n: # Start looking for a cookie match = patt.match(str, i) @@ -548,22 +582,41 @@ def __parse_string(self, str, patt=_CookiePattern): key, value = match.group("key"), match.group("val") i = match.end(0) - # Parse the key, value in case it's metainfo if key[0] == "$": - # We ignore attributes which pertain to the cookie - # mechanism as a whole. See RFC 2109. - # (Does anyone care?) - if M: - M[key[1:]] = value + if not morsel_seen: + # We ignore attributes which pertain to the cookie + # mechanism as a whole, such as "$Version". + # See RFC 2965. (Does anyone care?) + continue + parsed_items.append((TYPE_ATTRIBUTE, key[1:], value)) elif key.lower() in Morsel._reserved: - if M: - if value is None: - if key.lower() in Morsel._flags: - M[key] = True + if not morsel_seen: + # Invalid cookie string + return + if value is None: + if key.lower() in Morsel._flags: + parsed_items.append((TYPE_ATTRIBUTE, key, True)) else: - M[key] = _unquote(value) + # Invalid cookie string + return + else: + parsed_items.append((TYPE_ATTRIBUTE, key, _unquote(value))) elif value is not None: - rval, cval = self.value_decode(value) + parsed_items.append((TYPE_KEYVALUE, key, self.value_decode(value))) + morsel_seen = True + else: + # Invalid cookie string + return + + # The cookie string is valid, apply it. + M = None # current morsel + for tp, key, value in parsed_items: + if tp == TYPE_ATTRIBUTE: + assert M is not None + M[key] = value + else: + assert tp == TYPE_KEYVALUE + rval, cval = value self.__set(key, rval, cval) M = self[key] diff --git a/lib/http/server.py b/lib/http/server.py index 2cfd3172..00620d1f 100644 --- a/lib/http/server.py +++ b/lib/http/server.py @@ -103,6 +103,8 @@ import copy import argparse +from http import HTTPStatus + # Default error message template DEFAULT_ERROR_MESSAGE = """\ @@ -281,7 +283,9 @@ def parse_request(self): if len(words) == 3: command, path, version = words if version[:5] != 'HTTP/': - self.send_error(400, "Bad request version (%r)" % version) + self.send_error( + HTTPStatus.BAD_REQUEST, + "Bad request version (%r)" % version) return False try: base_version_number = version.split('/', 1)[1] @@ -296,25 +300,31 @@ def parse_request(self): raise ValueError version_number = int(version_number[0]), int(version_number[1]) except (ValueError, IndexError): - self.send_error(400, "Bad request version (%r)" % version) + self.send_error( + HTTPStatus.BAD_REQUEST, + "Bad request version (%r)" % version) return False if version_number >= (1, 1) and self.protocol_version >= "HTTP/1.1": self.close_connection = False if version_number >= (2, 0): - self.send_error(505, - "Invalid HTTP Version (%s)" % base_version_number) + self.send_error( + HTTPStatus.HTTP_VERSION_NOT_SUPPORTED, + "Invalid HTTP Version (%s)" % base_version_number) return False elif len(words) == 2: command, path = words self.close_connection = True if command != 'GET': - self.send_error(400, - "Bad HTTP/0.9 request type (%r)" % command) + self.send_error( + HTTPStatus.BAD_REQUEST, + "Bad HTTP/0.9 request type (%r)" % command) return False elif not words: return False else: - self.send_error(400, "Bad request syntax (%r)" % requestline) + self.send_error( + HTTPStatus.BAD_REQUEST, + "Bad request syntax (%r)" % requestline) return False self.command, self.path, self.request_version = command, path, version @@ -323,7 +333,16 @@ def parse_request(self): self.headers = http.client.parse_headers(self.rfile, _class=self.MessageClass) except http.client.LineTooLong: - self.send_error(400, "Line too long") + self.send_error( + HTTPStatus.BAD_REQUEST, + "Line too long") + return False + except http.client.HTTPException as err: + self.send_error( + HTTPStatus.REQUEST_HEADER_FIELDS_TOO_LARGE, + "Too many headers", + str(err) + ) return False conntype = self.headers.get('Connection', "") @@ -355,7 +374,7 @@ def handle_expect_100(self): False. """ - self.send_response_only(100) + self.send_response_only(HTTPStatus.CONTINUE) self.end_headers() return True @@ -373,7 +392,7 @@ def handle_one_request(self): self.requestline = '' self.request_version = '' self.command = '' - self.send_error(414) + self.send_error(HTTPStatus.REQUEST_URI_TOO_LONG) return if not self.raw_requestline: self.close_connection = True @@ -383,7 +402,9 @@ def handle_one_request(self): return mname = 'do_' + self.command if not hasattr(self, mname): - self.send_error(501, "Unsupported method (%r)" % self.command) + self.send_error( + HTTPStatus.NOT_IMPLEMENTED, + "Unsupported method (%r)" % self.command) return method = getattr(self, mname) method() @@ -429,16 +450,30 @@ def send_error(self, code, message=None, explain=None): if explain is None: explain = longmsg self.log_error("code %d, message %s", code, message) - # using _quote_html to prevent Cross Site Scripting attacks (see bug #1100201) - content = (self.error_message_format % - {'code': code, 'message': _quote_html(message), 'explain': _quote_html(explain)}) - body = content.encode('UTF-8', 'replace') self.send_response(code, message) - self.send_header("Content-Type", self.error_content_type) self.send_header('Connection', 'close') - self.send_header('Content-Length', int(len(body))) + + # Message body is omitted for cases described in: + # - RFC7230: 3.3. 1xx, 204(No Content), 304(Not Modified) + # - RFC7231: 6.3.6. 205(Reset Content) + body = None + if (code >= 200 and + code not in (HTTPStatus.NO_CONTENT, + HTTPStatus.RESET_CONTENT, + HTTPStatus.NOT_MODIFIED)): + # HTML encode to prevent Cross Site Scripting attacks + # (see bug #1100201) + content = (self.error_message_format % { + 'code': code, + 'message': _quote_html(message), + 'explain': _quote_html(explain) + }) + body = content.encode('UTF-8', 'replace') + self.send_header("Content-Type", self.error_content_type) + self.send_header('Content-Length', int(len(body))) self.end_headers() - if self.command != 'HEAD' and code >= 200 and code not in (204, 304): + + if self.command != 'HEAD' and body: self.wfile.write(body) def send_response(self, code, message=None): @@ -499,7 +534,8 @@ def log_request(self, code='-', size='-'): This is called by send_response(). """ - + if isinstance(code, HTTPStatus): + code = code.value self.log_message('"%s" %s %s', self.requestline, str(code), str(size)) @@ -582,82 +618,11 @@ def address_string(self): # MessageClass used to parse headers MessageClass = http.client.HTTPMessage - # Table mapping response codes to messages; entries have the - # form {code: (shortmessage, longmessage)}. - # See RFC 2616 and 6585. + # hack to maintain backwards compatibility responses = { - 100: ('Continue', 'Request received, please continue'), - 101: ('Switching Protocols', - 'Switching to new protocol; obey Upgrade header'), - - 200: ('OK', 'Request fulfilled, document follows'), - 201: ('Created', 'Document created, URL follows'), - 202: ('Accepted', - 'Request accepted, processing continues off-line'), - 203: ('Non-Authoritative Information', 'Request fulfilled from cache'), - 204: ('No Content', 'Request fulfilled, nothing follows'), - 205: ('Reset Content', 'Clear input form for further input.'), - 206: ('Partial Content', 'Partial content follows.'), - - 300: ('Multiple Choices', - 'Object has several resources -- see URI list'), - 301: ('Moved Permanently', 'Object moved permanently -- see URI list'), - 302: ('Found', 'Object moved temporarily -- see URI list'), - 303: ('See Other', 'Object moved -- see Method and URL list'), - 304: ('Not Modified', - 'Document has not changed since given time'), - 305: ('Use Proxy', - 'You must use proxy specified in Location to access this ' - 'resource.'), - 307: ('Temporary Redirect', - 'Object moved temporarily -- see URI list'), - - 400: ('Bad Request', - 'Bad request syntax or unsupported method'), - 401: ('Unauthorized', - 'No permission -- see authorization schemes'), - 402: ('Payment Required', - 'No payment -- see charging schemes'), - 403: ('Forbidden', - 'Request forbidden -- authorization will not help'), - 404: ('Not Found', 'Nothing matches the given URI'), - 405: ('Method Not Allowed', - 'Specified method is invalid for this resource.'), - 406: ('Not Acceptable', 'URI not available in preferred format.'), - 407: ('Proxy Authentication Required', 'You must authenticate with ' - 'this proxy before proceeding.'), - 408: ('Request Timeout', 'Request timed out; try again later.'), - 409: ('Conflict', 'Request conflict.'), - 410: ('Gone', - 'URI no longer exists and has been permanently removed.'), - 411: ('Length Required', 'Client must specify Content-Length.'), - 412: ('Precondition Failed', 'Precondition in headers is false.'), - 413: ('Request Entity Too Large', 'Entity is too large.'), - 414: ('Request-URI Too Long', 'URI is too long.'), - 415: ('Unsupported Media Type', 'Entity body in unsupported format.'), - 416: ('Requested Range Not Satisfiable', - 'Cannot satisfy request range.'), - 417: ('Expectation Failed', - 'Expect condition could not be satisfied.'), - 428: ('Precondition Required', - 'The origin server requires the request to be conditional.'), - 429: ('Too Many Requests', 'The user has sent too many requests ' - 'in a given amount of time ("rate limiting").'), - 431: ('Request Header Fields Too Large', 'The server is unwilling to ' - 'process the request because its header fields are too large.'), - - 500: ('Internal Server Error', 'Server got itself in trouble'), - 501: ('Not Implemented', - 'Server does not support this operation'), - 502: ('Bad Gateway', 'Invalid responses from another server/proxy.'), - 503: ('Service Unavailable', - 'The server cannot process the request due to a high load'), - 504: ('Gateway Timeout', - 'The gateway server did not receive a timely response'), - 505: ('HTTP Version Not Supported', 'Cannot fulfill request.'), - 511: ('Network Authentication Required', - 'The client needs to authenticate to gain network access.'), - } + v: (v.phrase, v.description) + for v in HTTPStatus.__members__.values() + } class SimpleHTTPRequestHandler(BaseHTTPRequestHandler): @@ -707,7 +672,7 @@ def send_head(self): parts = urllib.parse.urlsplit(self.path) if not parts.path.endswith('/'): # redirect browser - doing basically what apache does - self.send_response(301) + self.send_response(HTTPStatus.MOVED_PERMANENTLY) new_parts = (parts[0], parts[1], parts[2] + '/', parts[3], parts[4]) new_url = urllib.parse.urlunsplit(new_parts) @@ -725,10 +690,10 @@ def send_head(self): try: f = open(path, 'rb') except OSError: - self.send_error(404, "File not found") + self.send_error(HTTPStatus.NOT_FOUND, "File not found") return None try: - self.send_response(200) + self.send_response(HTTPStatus.OK) self.send_header("Content-type", ctype) fs = os.fstat(f.fileno()) self.send_header("Content-Length", str(fs[6])) @@ -750,7 +715,9 @@ def list_directory(self, path): try: list = os.listdir(path) except OSError: - self.send_error(404, "No permission to list directory") + self.send_error( + HTTPStatus.NOT_FOUND, + "No permission to list directory") return None list.sort(key=lambda a: a.lower()) r = [] @@ -789,7 +756,7 @@ def list_directory(self, path): f = io.BytesIO() f.write(encoded) f.seek(0) - self.send_response(200) + self.send_response(HTTPStatus.OK) self.send_header("Content-type", "text/html; charset=%s" % enc) self.send_header("Content-Length", str(len(encoded))) self.end_headers() @@ -976,7 +943,9 @@ def do_POST(self): if self.is_cgi(): self.run_cgi() else: - self.send_error(501, "Can only POST to CGI scripts") + self.send_error( + HTTPStatus.NOT_IMPLEMENTED, + "Can only POST to CGI scripts") def send_head(self): """Version of send_head that support CGI scripts""" @@ -1050,17 +1019,21 @@ def run_cgi(self): scriptname = dir + '/' + script scriptfile = self.translate_path(scriptname) if not os.path.exists(scriptfile): - self.send_error(404, "No such CGI script (%r)" % scriptname) + self.send_error( + HTTPStatus.NOT_FOUND, + "No such CGI script (%r)" % scriptname) return if not os.path.isfile(scriptfile): - self.send_error(403, "CGI script is not a plain file (%r)" % - scriptname) + self.send_error( + HTTPStatus.FORBIDDEN, + "CGI script is not a plain file (%r)" % scriptname) return ispy = self.is_python(scriptname) if self.have_fork or not ispy: if not self.is_executable(scriptfile): - self.send_error(403, "CGI script is not executable (%r)" % - scriptname) + self.send_error( + HTTPStatus.FORBIDDEN, + "CGI script is not executable (%r)" % scriptname) return # Reference: http://hoohoo.ncsa.uiuc.edu/cgi/env.html @@ -1128,7 +1101,7 @@ def run_cgi(self): 'HTTP_USER_AGENT', 'HTTP_COOKIE', 'HTTP_REFERER'): env.setdefault(k, "") - self.send_response(200, "Script output follows") + self.send_response(HTTPStatus.OK, "Script output follows") self.flush_headers() decoded_query = query.replace('+', ' ') diff --git a/lib/idlelib/AutoComplete.py b/lib/idlelib/AutoComplete.py index b20512df..ff085d5c 100644 --- a/lib/idlelib/AutoComplete.py +++ b/lib/idlelib/AutoComplete.py @@ -1,6 +1,6 @@ """AutoComplete.py - An IDLE extension for automatically completing names. -This extension can complete either attribute names of file names. It can pop +This extension can complete either attribute names or file names. It can pop a window with all available names, for the user to select from. """ import os @@ -64,7 +64,7 @@ def force_open_completions_event(self, event): def try_open_completions_event(self, event): """Happens when it would be nice to open a completion list, but not - really necessary, for example after an dot, so function + really necessary, for example after a dot, so function calls won't be made. """ lastchar = self.text.get("insert-1c") diff --git a/lib/idlelib/Bindings.py b/lib/idlelib/Bindings.py index ab25ff18..e19a279b 100644 --- a/lib/idlelib/Bindings.py +++ b/lib/idlelib/Bindings.py @@ -69,6 +69,8 @@ ('shell', [ ('_View Last Restart', '<>'), ('_Restart Shell', '<>'), + None, + ('_Interrupt Execution', '<>'), ]), ('debug', [ ('_Go to File/Line', '<>'), diff --git a/lib/idlelib/CREDITS.txt b/lib/idlelib/CREDITS.txt index 5ff599de..3a50eb8e 100644 --- a/lib/idlelib/CREDITS.txt +++ b/lib/idlelib/CREDITS.txt @@ -24,7 +24,7 @@ Noam Raphael (Code Context, Call Tips, many other patches), and Chui Tey (RPC integration, debugger integration and persistent breakpoints). Scott David Daniels, Tal Einat, Hernan Foffani, Christos Georgiou, -Jim Jewett, Martin v. Löwis, Jason Orendorff, Guilherme Polo, Josh Robb, +Jim Jewett, Martin v. Löwis, Jason Orendorff, Guilherme Polo, Josh Robb, Nigel Rowe, Bruce Sherwood, Jeff Shute, and Weeble have submitted useful patches. Thanks, guys! diff --git a/lib/idlelib/CallTipWindow.py b/lib/idlelib/CallTipWindow.py index 8e68a76b..9eec1750 100644 --- a/lib/idlelib/CallTipWindow.py +++ b/lib/idlelib/CallTipWindow.py @@ -9,7 +9,7 @@ HIDE_SEQUENCES = ("", "") CHECKHIDE_VIRTUAL_EVENT_NAME = "<>" CHECKHIDE_SEQUENCES = ("", "") -CHECKHIDE_TIME = 100 # miliseconds +CHECKHIDE_TIME = 100 # milliseconds MARK_RIGHT = "calltipwindowregion_right" diff --git a/lib/idlelib/ChangeLog b/lib/idlelib/ChangeLog index 985871be..0c366645 100644 --- a/lib/idlelib/ChangeLog +++ b/lib/idlelib/ChangeLog @@ -20,7 +20,7 @@ IDLEfork ChangeLog 2001-07-19 14:49 elguavas * ChangeLog, EditorWindow.py, INSTALLATION, NEWS.txt, README.txt, - TODO.txt, idlever.py: + TODO.txt, idlever.py: minor tidy-ups ready for 0.8.1 alpha tarball release 2001-07-17 15:12 kbk @@ -172,7 +172,7 @@ IDLEfork ChangeLog all this work w/ a future-stmt just looks harder and harder." --tim_one - (From Rel 1.8: "Hack to make this still work with Python 1.5.2. + (From Rel 1.8: "Hack to make this still work with Python 1.5.2. ;-( " --fdrake) 2001-07-14 14:51 kbk @@ -193,7 +193,7 @@ IDLEfork ChangeLog test() to _test()." --GvR This was an interesting merge. The join completely missed removing - goodname(), which was adjacent, but outside of, a small conflict. + goodname(), which was adjacent, but outside of, a small conflict. I only caught it by comparing the 1.1.3.2/1.1.3.3 diff. CVS ain't infallible. @@ -516,12 +516,12 @@ IDLEfork ChangeLog 2000-08-15 22:51 nowonder - * IDLEFORK.html: + * IDLEFORK.html: corrected email address 2000-08-15 22:47 nowonder - * IDLEFORK.html: + * IDLEFORK.html: added .html file for http://idlefork.sourceforge.net 2000-08-15 11:13 dscherer @@ -1574,7 +1574,7 @@ Mon Oct 12 23:59:27 1998 Guido van Rossum * Attic/PopupMenu.py: Pass a root to the help window. * SearchBinding.py: - Add parent argument to 'to to line number' dialog box. + Add parent argument to 'go to line number' dialog box. Sat Oct 10 19:15:32 1998 Guido van Rossum diff --git a/lib/idlelib/CodeContext.py b/lib/idlelib/CodeContext.py index 44783b69..7d25adaa 100644 --- a/lib/idlelib/CodeContext.py +++ b/lib/idlelib/CodeContext.py @@ -57,18 +57,18 @@ def toggle_code_context_event(self, event=None): # Calculate the border width and horizontal padding required to # align the context with the text in the main Text widget. # - # All values are passed through int(str()), since some + # All values are passed through getint(), since some # values may be pixel objects, which can't simply be added to ints. widgets = self.editwin.text, self.editwin.text_frame # Calculate the required vertical padding padx = 0 for widget in widgets: - padx += int(str( widget.pack_info()['padx'] )) - padx += int(str( widget.cget('padx') )) + padx += widget.tk.getint(widget.pack_info()['padx']) + padx += widget.tk.getint(widget.cget('padx')) # Calculate the required border width border = 0 for widget in widgets: - border += int(str( widget.cget('border') )) + border += widget.tk.getint(widget.cget('border')) self.label = tkinter.Label(self.editwin.top, text="\n" * (self.context_depth - 1), anchor=W, justify=LEFT, diff --git a/lib/idlelib/ColorDelegator.py b/lib/idlelib/ColorDelegator.py index 9f313496..02eac470 100644 --- a/lib/idlelib/ColorDelegator.py +++ b/lib/idlelib/ColorDelegator.py @@ -2,6 +2,7 @@ import re import keyword import builtins +from tkinter import TkVersion from idlelib.Delegator import Delegator from idlelib.configHandler import idleConf @@ -32,6 +33,28 @@ def make_pat(): prog = re.compile(make_pat(), re.S) idprog = re.compile(r"\s+(\w+)", re.S) +def color_config(text): # Called from htest, Editor, and Turtle Demo. + '''Set color opitons of Text widget. + + Should be called whenever ColorDelegator is called. + ''' + # Not automatic because ColorDelegator does not know 'text'. + theme = idleConf.CurrentTheme() + normal_colors = idleConf.GetHighlight(theme, 'normal') + cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg') + select_colors = idleConf.GetHighlight(theme, 'hilite') + text.config( + foreground=normal_colors['foreground'], + background=normal_colors['background'], + insertbackground=cursor_color, + selectforeground=select_colors['foreground'], + selectbackground=select_colors['background'], + ) + if TkVersion >= 8.5: + text.config( + inactiveselectbackground=select_colors['background']) + + class ColorDelegator(Delegator): def __init__(self): @@ -233,6 +256,7 @@ def removecolors(self): for tag in self.tagdefs: self.tag_remove(tag, "1.0", "end") + def _color_delegator(parent): # htest # from tkinter import Toplevel, Text from idlelib.Percolator import Percolator @@ -247,6 +271,7 @@ def _color_delegator(parent): # htest # text.insert("insert", source) text.focus_set() + color_config(text) p = Percolator(text) d = ColorDelegator() p.insertfilter(d) diff --git a/lib/idlelib/Debugger.py b/lib/idlelib/Debugger.py index 250422ed..d5e217dd 100644 --- a/lib/idlelib/Debugger.py +++ b/lib/idlelib/Debugger.py @@ -372,7 +372,7 @@ class StackViewer(ScrolledList): def __init__(self, master, flist, gui): if macosxSupport.isAquaTk(): # At least on with the stock AquaTk version on OSX 10.4 you'll - # get an shaking GUI that eventually kills IDLE if the width + # get a shaking GUI that eventually kills IDLE if the width # argument is specified. ScrolledList.__init__(self, master) else: diff --git a/lib/idlelib/Delegator.py b/lib/idlelib/Delegator.py index c4765163..dc2a1aae 100644 --- a/lib/idlelib/Delegator.py +++ b/lib/idlelib/Delegator.py @@ -1,10 +1,10 @@ class Delegator: - # The cache is only used to be able to change delegates! - def __init__(self, delegate=None): self.delegate = delegate self.__cache = set() + # Cache is used to only remove added attributes + # when changing the delegate. def __getattr__(self, name): attr = getattr(self.delegate, name) # May raise AttributeError @@ -13,6 +13,9 @@ def __getattr__(self, name): return attr def resetcache(self): + "Removes added attributes while leaving original attributes." + # Function is really about resetting delagator dict + # to original state. Cache is just a means for key in self.__cache: try: delattr(self, key) @@ -21,5 +24,10 @@ def resetcache(self): self.__cache.clear() def setdelegate(self, delegate): + "Reset attributes and change delegate." self.resetcache() self.delegate = delegate + +if __name__ == '__main__': + from unittest import main + main('idlelib.idle_test.test_delegator', verbosity=2) diff --git a/lib/idlelib/EditorWindow.py b/lib/idlelib/EditorWindow.py index b5868be3..86135366 100644 --- a/lib/idlelib/EditorWindow.py +++ b/lib/idlelib/EditorWindow.py @@ -25,9 +25,9 @@ # The default tab setting for a Text widget, in average-width characters. TK_TABWIDTH_DEFAULT = 8 - _py_version = ' (%s)' % platform.python_version() + def _sphinx_version(): "Format sys.version_info to produce the Sphinx version string used to install the chm docs" major, minor, micro, level, serial = sys.version_info @@ -90,13 +90,14 @@ def destroy(self, ev=None): class EditorWindow(object): from idlelib.Percolator import Percolator - from idlelib.ColorDelegator import ColorDelegator + from idlelib.ColorDelegator import ColorDelegator, color_config from idlelib.UndoDelegator import UndoDelegator - from idlelib.IOBinding import IOBinding, filesystemencoding, encoding + from idlelib.IOBinding import IOBinding, encoding from idlelib import Bindings from tkinter import Toplevel from idlelib.MultiStatusBar import MultiStatusBar + filesystemencoding = sys.getfilesystemencoding() # for file names help_url = None def __init__(self, flist=None, filename=None, key=None, root=None): @@ -742,20 +743,7 @@ def ResetColorizer(self): # Called from self.filename_change_hook and from configDialog.py self._rmcolorizer() self._addcolorizer() - theme = idleConf.CurrentTheme() - normal_colors = idleConf.GetHighlight(theme, 'normal') - cursor_color = idleConf.GetHighlight(theme, 'cursor', fgBg='fg') - select_colors = idleConf.GetHighlight(theme, 'hilite') - self.text.config( - foreground=normal_colors['foreground'], - background=normal_colors['background'], - insertbackground=cursor_color, - selectforeground=select_colors['foreground'], - selectbackground=select_colors['background'], - ) - if TkVersion >= 8.5: - self.text.config( - inactiveselectbackground=select_colors['background']) + EditorWindow.color_config(self.text) IDENTCHARS = string.ascii_letters + string.digits + "_" @@ -1699,5 +1687,8 @@ def _editor_window(parent): # htest # # edit.text.bind("<>", edit.close_event) if __name__ == '__main__': + import unittest + unittest.main('idlelib.idle_test.test_editor', verbosity=2, exit=False) + from idlelib.idle_test.htest import run run(_editor_window) diff --git a/lib/idlelib/HISTORY.txt b/lib/idlelib/HISTORY.txt index 01d73ed2..731fabd1 100644 --- a/lib/idlelib/HISTORY.txt +++ b/lib/idlelib/HISTORY.txt @@ -11,7 +11,7 @@ What's New in IDLEfork 0.8.1? *Release date: 22-Jul-2001* - New tarball released as a result of the 'revitalisation' of the IDLEfork - project. + project. - This release requires python 2.1 or better. Compatibility with earlier versions of python (especially ancient ones like 1.5x) is no longer a @@ -26,8 +26,8 @@ What's New in IDLEfork 0.8.1? not working, but I believe this was the case with the previous IDLE fork release (0.7.1) as well. -- This release is being made now to mark the point at which IDLEfork is - launching into a new stage of development. +- This release is being made now to mark the point at which IDLEfork is + launching into a new stage of development. - IDLEfork CVS will now be branched to enable further development and exploration of the two "execution in a remote process" patches submitted by @@ -96,7 +96,7 @@ IDLEfork 0.7.1 - 29 May 2000 instead of the IDLE help; shift-TAB is now a synonym for unindent. - New modules: - + ExecBinding.py Executes program through loader loader.py Bootstraps user program protocol.py RPC protocol diff --git a/lib/idlelib/IOBinding.py b/lib/idlelib/IOBinding.py index 5ec9d546..efd0d5e6 100644 --- a/lib/idlelib/IOBinding.py +++ b/lib/idlelib/IOBinding.py @@ -10,58 +10,62 @@ import tkinter.messagebox as tkMessageBox from tkinter.simpledialog import askstring +import idlelib +from idlelib.configHandler import idleConf - -# Try setting the locale, so that we can find out -# what encoding to use -try: - import locale - locale.setlocale(locale.LC_CTYPE, "") -except (ImportError, locale.Error): - pass - -# Encoding for file names -filesystemencoding = sys.getfilesystemencoding() ### currently unused - -locale_encoding = 'ascii' -if sys.platform == 'win32': - # On Windows, we could use "mbcs". However, to give the user - # a portable encoding name, we need to find the code page - try: - locale_encoding = locale.getdefaultlocale()[1] - codecs.lookup(locale_encoding) - except LookupError: - pass +if idlelib.testing: # Set True by test.test_idle to avoid setlocale. + encoding = 'utf-8' else: + # Try setting the locale, so that we can find out + # what encoding to use try: - # Different things can fail here: the locale module may not be - # loaded, it may not offer nl_langinfo, or CODESET, or the - # resulting codeset may be unknown to Python. We ignore all - # these problems, falling back to ASCII - locale_encoding = locale.nl_langinfo(locale.CODESET) - if locale_encoding is None or locale_encoding is '': - # situation occurs on Mac OS X - locale_encoding = 'ascii' - codecs.lookup(locale_encoding) - except (NameError, AttributeError, LookupError): - # Try getdefaultlocale: it parses environment variables, - # which may give a clue. Unfortunately, getdefaultlocale has - # bugs that can cause ValueError. + import locale + locale.setlocale(locale.LC_CTYPE, "") + except (ImportError, locale.Error): + pass + + locale_decode = 'ascii' + if sys.platform == 'win32': + # On Windows, we could use "mbcs". However, to give the user + # a portable encoding name, we need to find the code page try: locale_encoding = locale.getdefaultlocale()[1] + codecs.lookup(locale_encoding) + except LookupError: + pass + else: + try: + # Different things can fail here: the locale module may not be + # loaded, it may not offer nl_langinfo, or CODESET, or the + # resulting codeset may be unknown to Python. We ignore all + # these problems, falling back to ASCII + locale_encoding = locale.nl_langinfo(locale.CODESET) if locale_encoding is None or locale_encoding is '': # situation occurs on Mac OS X locale_encoding = 'ascii' codecs.lookup(locale_encoding) - except (ValueError, LookupError): - pass + except (NameError, AttributeError, LookupError): + # Try getdefaultlocale: it parses environment variables, + # which may give a clue. Unfortunately, getdefaultlocale has + # bugs that can cause ValueError. + try: + locale_encoding = locale.getdefaultlocale()[1] + if locale_encoding is None or locale_encoding is '': + # situation occurs on Mac OS X + locale_encoding = 'ascii' + codecs.lookup(locale_encoding) + except (ValueError, LookupError): + pass -locale_encoding = locale_encoding.lower() + locale_encoding = locale_encoding.lower() -encoding = locale_encoding ### KBK 07Sep07 This is used all over IDLE, check! - ### 'encoding' is used below in encode(), check! + encoding = locale_encoding + # Encoding is used in multiple files; locale_encoding nowhere. + # The only use of 'encoding' below is in _decode as initial value + # of deprecated block asking user for encoding. + # Perhaps use elsewhere should be reviewed. -coding_re = re.compile(r'^[ \t\f]*#.*coding[:=][ \t]*([-\w.]+)', re.ASCII) +coding_re = re.compile(r'^[ \t\f]*#.*?coding[:=][ \t]*([-\w.]+)', re.ASCII) blank_re = re.compile(r'^[ \t\f]*(?:[#\r\n]|$)', re.ASCII) def coding_spec(data): @@ -300,7 +304,7 @@ def _decode(self, two_lines, bytes): "The file's encoding is invalid for Python 3.x.\n" "IDLE will convert it to UTF-8.\n" "What is the current encoding of the file?", - initialvalue = locale_encoding, + initialvalue = encoding, parent = self.editwin.text) if enc: @@ -525,7 +529,6 @@ def updaterecentfileslist(self,filename): def _io_binding(parent): # htest # from tkinter import Toplevel, Text - from idlelib.configHandler import idleConf root = Toplevel(parent) root.title("Test IOBinding") @@ -536,14 +539,23 @@ def __init__(self, text): self.text = text self.flist = None self.text.bind("", self.open) + self.text.bind('', self.print) self.text.bind("", self.save) + self.text.bind("", self.saveas) + self.text.bind('', self.savecopy) def get_saved(self): return 0 def set_saved(self, flag): pass def reset_undo(self): pass def open(self, event): self.text.event_generate("<>") + def print(self, event): + self.text.event_generate("<>") def save(self, event): self.text.event_generate("<>") + def saveas(self, event): + self.text.event_generate("<>") + def savecopy(self, event): + self.text.event_generate("<>") text = Text(root) text.pack() @@ -552,5 +564,8 @@ def save(self, event): IOBinding(editwin) if __name__ == "__main__": + import unittest + unittest.main('idlelib.idle_test.test_iomenu', verbosity=2, exit=False) + from idlelib.idle_test.htest import run run(_io_binding) diff --git a/lib/idlelib/MultiCall.py b/lib/idlelib/MultiCall.py index 251a84d0..84628549 100644 --- a/lib/idlelib/MultiCall.py +++ b/lib/idlelib/MultiCall.py @@ -111,7 +111,7 @@ def __del__(self): raise # An int in range(1 << len(_modifiers)) represents a combination of modifiers -# (if the least significent bit is on, _modifiers[0] is on, and so on). +# (if the least significant bit is on, _modifiers[0] is on, and so on). # _state_subsets gives for each combination of modifiers, or *state*, # a list of the states which are a subset of it. This list is ordered by the # number of modifiers is the state - the most specific state comes first. diff --git a/lib/idlelib/NEWS.txt b/lib/idlelib/NEWS.txt index 8b8e10b5..72905c18 100644 --- a/lib/idlelib/NEWS.txt +++ b/lib/idlelib/NEWS.txt @@ -1,6 +1,91 @@ -What's New in Idle 3.4.4? +What's New in IDLE 3.5.3? ========================= -*Release date: 2015-12-20* +*Release date: 2017-01-01?* + +- Issue #15308: Add 'interrupt execution' (^C) to Shell menu. + Patch by Roger Serwy, updated by Bayard Randel. + +- Issue #27922: Stop IDLE tests from 'flashing' gui widgets on the screen. + +- Add version to title of IDLE help window. + +- Issue #25564: In section on IDLE -- console differences, mention that + using exec means that __builtins__ is defined for each statement. + +- Issue #27714: text_textview and test_autocomplete now pass when re-run + in the same process. This occurs when test_idle fails when run with the + -w option but without -jn. Fix warning from test_config. + +- Issue #25507: IDLE no longer runs buggy code because of its tkinter imports. + Users must include the same imports required to run directly in Python. + +- Issue #27452: add line counter and crc to IDLE configHandler test dump. + +- Issue #27365: Allow non-ascii chars in IDLE NEWS.txt, for contributor names. + +- Issue #27245: IDLE: Cleanly delete custom themes and key bindings. + Previously, when IDLE was started from a console or by import, a cascade + of warnings was emitted. Patch by Serhiy Storchaka. + + +What's New in IDLE 3.5.2? +========================= +*Release date: 2016-06-26* + +- Issue #5124: Paste with text selected now replaces the selection on X11. + This matches how paste works on Windows, Mac, most modern Linux apps, + and ttk widgets. Original patch by Serhiy Storchaka. + +- Issue #24759: Make clear in idlelib.idle_test.__init__ that the directory + is a private implementation of test.test_idle and tool for maintainers. + +- Issue #27196: Stop 'ThemeChangef' warnings when running IDLE tests. + These persisted after other warnings were suppressed in #20567. + Apply Serhiy Storchaka's update_idletasks solution to four test files. + Record this additional advice in idle_test/README.txt + +- Issue #20567: Revise idle_test/README.txt with advice about avoiding + tk warning messages from tests. Apply advice to several IDLE tests. + +- Issue #27117: Make colorizer htest and turtledemo work with dark themes. + Move code for configuring text widget colors to a new function. + +- Issue #26673: When tk reports font size as 0, change to size 10. + Such fonts on Linux prevented the configuration dialog from opening. + +- Issue #21939: Add test for IDLE's percolator. + Original patch by Saimadhav Heblikar. + +- Issue #21676: Add test for IDLE's replace dialog. + Original patch by Saimadhav Heblikar. + +- Issue #18410: Add test for IDLE's search dialog. + Original patch by Westley Martínez. + +- Issue #21703: Add test for undo delegator. + Original patch by Saimadhav Heblikar . + +- Issue #27044: Add ConfigDialog.remove_var_callbacks to stop memory leaks. + +- Issue #23977: Add more asserts to test_delegator. + +- Issue #20640: Add tests for idlelib.configHelpSourceEdit. + Patch by Saimadhav Heblikar. + +- In the 'IDLE-console differences' section of the IDLE doc, clarify + how running with IDLE affects sys.modules and the standard streams. + +- Issue #25507: fix incorrect change in IOBinding that prevented printing. + Augment IOBinding htest to include all major IOBinding functions. + +- Issue #25905: Revert unwanted conversion of ' to ’ RIGHT SINGLE QUOTATION + MARK in README.txt and open this and NEWS.txt with 'ascii'. + Re-encode CREDITS.txt to utf-8 and open it with 'utf-8'. + + +What's New in IDLE 3.5.1? +========================= +*Release date: 2015-12-06* - Issue 15348: Stop the debugger engine (normally in a user process) before closing the debugger window (running in the IDLE process). @@ -96,6 +181,11 @@ What's New in Idle 3.4.4? - Issue #24790: Remove extraneous code (which also create 2 & 3 conflicts). + +What's New in IDLE 3.5.0? +========================= +*Release date: 2015-09-13* + - Issue #23672: Allow Idle to edit and run files with astral chars in name. Patch by Mohd Sanad Zaki Rizvi. @@ -112,11 +202,6 @@ What's New in Idle 3.4.4? - Issue #23184: remove unused names and imports in idlelib. Initial patch by Al Sweigart. - -What's New in Idle 3.4.3? -========================= -*Release date: 2015-02-25* - - Issue #20577: Configuration of the max line length for the FormatParagraph extension has been moved from the General tab of the Idle preferences dialog to the FormatParagraph tab of the Config Extensions dialog. @@ -129,7 +214,7 @@ What's New in Idle 3.4.3? Changes are written to HOME/.idlerc/config-extensions.cfg. Original patch by Tal Einat. -- Issue #16233: A module browser (File : Class Browser, Alt+C) requires a +- Issue #16233: A module browser (File : Class Browser, Alt+C) requires an editor window with a filename. When Class Browser is requested otherwise, from a shell, output window, or 'Untitled' editor, Idle no longer displays an error box. It now pops up an Open Module box (Alt+M). If a valid name @@ -145,16 +230,11 @@ What's New in Idle 3.4.3? - Issue #23180: Rename IDLE "Windows" menu item to "Window". Patch by Al Sweigart. - -What's New in IDLE 3.4.2? -========================= -*Release date: 2014-10-06* - - Issue #17390: Adjust Editor window title; remove 'Python', move version to end. - Issue #14105: Idle debugger breakpoints no longer disappear - when inseting or deleting lines. + when inserting or deleting lines. - Issue #17172: Turtledemo can now be run from Idle. Currently, the entry is on the Help menu, but it may move to Run. @@ -184,14 +264,8 @@ What's New in IDLE 3.4.2? - Issue #18409: Add unittest for AutoComplete. Patch by Phil Webster. -- Issue #18104: Add idlelib/idle_test/htest.py with a few sample tests to begin - consolidating and improving human-validated tests of Idle. Change other files - as needed to work with htest. Running the module as __main__ runs all tests. - - -What's New in IDLE 3.4.1? -========================= -*Release date: 2014-05-18* +- Issue #21477: htest.py - Improve framework, complete set of tests. + Patches by Saimadhav Heblikar - Issue #18104: Add idlelib/idle_test/htest.py with a few sample tests to begin consolidating and improving human-validated tests of Idle. Change other files @@ -241,7 +315,7 @@ What's New in IDLE 3.3.0? - Issue # 12510: Attempt to get certain tool tips no longer crashes IDLE. Erroneous tool tips have been corrected. Default added for callables. -- Issue10365: File open dialog now works instead of crashing even when +- Issue #10365: File open dialog now works instead of crashing even when parent window is closed while dialog is open. - Issue 14876: use user-selected font for highlight configuration. @@ -277,23 +351,18 @@ What's New in IDLE 3.2.1? - Issue #1028: Ctrl-space binding to show completions was causing IDLE to exit. Tk < 8.5 was sending invalid Unicode null; replaced with valid null. -- toggle failing on Tk 8.5, causing IDLE exits and strange selection - behavior. Issue 4676. Improve selection extension behaviour. +- Issue #4676: toggle failing on Tk 8.5, causing IDLE exits and strange selection + behavior. Improve selection extension behaviour. -- toggle non-functional when NumLock set on Windows. Issue 3851. +- Issue #3851: toggle non-functional when NumLock set on Windows. What's New in IDLE 3.1b1? ========================= *Release date: 06-May-09* -- Use of 'filter' in keybindingDialog.py was causing custom key assignment to - fail. Patch 5707 amaury.forgeotdarc. - - -What's New in IDLE 3.1a1? -========================= -*Release date: 07-Mar-09* +- Issue #5707: Use of 'filter' in keybindingDialog.py was causing custom key assignment to + fail. Patch by Amaury Forgeot d'Arc. - Issue #4815: Offer conversion to UTF-8 if source files have no encoding declaration and are not encoded in UTF-8. @@ -308,684 +377,6 @@ What's New in IDLE 3.1a1? - Issue #2665: On Windows, an IDLE installation upgraded from an old version would not start if a custom theme was defined. - -What's New in IDLE 2.7? (UNRELEASED, but merged into 3.1 releases above.) -======================= -*Release date: XX-XXX-2010* - -- idle.py modified and simplified to better support developing experimental - versions of IDLE which are not installed in the standard location. - -- OutputWindow/PyShell right click menu "Go to file/line" wasn't working with - file paths containing spaces. Bug 5559. - -- Windows: Version string for the .chm help file changed, file not being - accessed Patch 5783 Guilherme Polo - -- Allow multiple IDLE GUI/subprocess pairs to exist simultaneously. Thanks to - David Scherer for suggesting the use of an ephemeral port for the GUI. - Patch 1529142 Weeble. - -- Remove port spec from run.py and fix bug where subprocess fails to - extract port from command line when warnings are present. - -- Tk 8.5 Text widget requires 'wordprocessor' tabstyle attr to handle - mixed space/tab properly. Issue 5129, patch by Guilherme Polo. - -- Issue #3549: On MacOS the preferences menu was not present - - -What's New in IDLE 3.0 final? -============================= -*Release date: 03-Dec-2008* - -- IDLE would print a "Unhandled server exception!" message when internal - debugging is enabled. - -- Issue #4455: IDLE failed to display the windows list when two windows have - the same title. - -- Issue #4383: When IDLE cannot make the connection to its subprocess, it would - fail to properly display the error message. - - -What's New in IDLE 3.0a3? -========================= -*Release date: 29-Feb-2008* - -- help() was not paging to the shell. Issue1650. - -- CodeContext was not importing. - -- Corrected two 3.0 compatibility errors reported by Mark Summerfield: - http://mail.python.org/pipermail/python-3000/2007-December/011491.html - -- Shell was not colorizing due to bug introduced at r57998, Bug 1586. - -- Issue #1585: IDLE uses non-existent xrange() function. - - -What's New in IDLE 3.0a2? -========================= -*Release date: 06-Dec-2007* - -- Windows EOL sequence not converted correctly, encoding error. - Caused file save to fail. Bug 1130. - - -What's New in IDLE 3.0a1? -========================= -*Release date: 31-Aug-2007* - -- IDLE converted to Python 3000 syntax. - -- Strings became Unicode. - -- CallTips module now uses the inspect module to produce the argspec. - -- IDLE modules now use absolute import instead of implied relative import. - -- atexit call replaces sys.exitfunc. The functionality of delete-exitfunc flag - in config-main.cfg remains unchanged: if set, registered exit functions will - be cleared before IDLE exits. - - -What's New in IDLE 2.6 -====================== -*Release date: 01-Oct-2008*, merged into 3.0 releases detailed above (3.0rc2) - -- Issue #2665: On Windows, an IDLE installation upgraded from an old version - would not start if a custom theme was defined. - -- Home / Control-A toggles between left margin and end of leading white - space. issue1196903, patch by Jeff Shute. - -- Improved AutoCompleteWindow logic. issue2062, patch by Tal Einat. - -- Autocompletion of filenames now support alternate separators, e.g. the - '/' char on Windows. issue2061 Patch by Tal Einat. - -- Configured selection highlighting colors were ignored; updating highlighting - in the config dialog would cause non-Python files to be colored as if they - were Python source; improve use of ColorDelagator. Patch 1334. Tal Einat. - -- ScriptBinding event handlers weren't returning 'break'. Patch 2050, Tal Einat - -- There was an error on exit if no sys.exitfunc was defined. Issue 1647. - -- Could not open files in .idlerc directory if latter was hidden on Windows. - Issue 1743, Issue 1862. - -- Configure Dialog: improved layout for keybinding. Patch 1457 Tal Einat. - -- tabpage.py updated: tabbedPages.py now supports multiple dynamic rows - of tabs. Patch 1612746 Tal Einat. - -- Add confirmation dialog before printing. Patch 1717170 Tal Einat. - -- Show paste position if > 80 col. Patch 1659326 Tal Einat. - -- Update cursor color without restarting. Patch 1725576 Tal Einat. - -- Allow keyboard interrupt only when user code is executing in subprocess. - Patch 1225 Tal Einat (reworked from IDLE-Spoon). - -- configDialog cleanup. Patch 1730217 Tal Einat. - -- textView cleanup. Patch 1718043 Tal Einat. - -- Clean up EditorWindow close. - -- Patch 1693258: Fix for duplicate "preferences" menu-OS X. Backport of r56204. - -- OSX: Avoid crash for those versions of Tcl/Tk which don't have a console - -- Bug in idlelib.MultiCall: Options dialog was crashing IDLE if there was an - option in config-extensions w/o a value. Patch #1672481, Tal Einat - -- Corrected some bugs in AutoComplete. Also, Page Up/Down in ACW implemented; - mouse and cursor selection in ACWindow implemented; double Tab inserts - current selection and closes ACW (similar to double-click and Return); scroll - wheel now works in ACW. Added AutoComplete instructions to IDLE Help. - -- AutoCompleteWindow moved below input line, will move above if there - isn't enough space. Patch 1621265 Tal Einat - -- Calltips now 'handle' tuples in the argument list (display '' :) - Suggested solution by Christos Georgiou, Bug 791968. - -- Add 'raw' support to configHandler. Patch 1650174 Tal Einat. - -- Avoid hang when encountering a duplicate in a completion list. Bug 1571112. - -- Patch #1362975: Rework CodeContext indentation algorithm to - avoid hard-coding pixel widths. - -- Bug #813342: Start the IDLE subprocess with -Qnew if the parent - is started with that option. - -- Honor the "Cancel" action in the save dialog (Debian bug #299092) - -- Some syntax errors were being caught by tokenize during the tabnanny - check, resulting in obscure error messages. Do the syntax check - first. Bug 1562716, 1562719 - -- IDLE's version number takes a big jump to match the version number of - the Python release of which it's a part. - - -What's New in IDLE 1.2? -======================= -*Release date: 19-SEP-2006* - -- File menu hotkeys: there were three 'p' assignments. Reassign the - 'Save Copy As' and 'Print' hotkeys to 'y' and 't'. Change the - Shell hotkey from 's' to 'l'. - -- IDLE honors new quit() and exit() commands from site.py Quitter() object. - Patch 1540892, Jim Jewett - -- The 'with' statement is now a Code Context block opener. - Patch 1540851, Jim Jewett - -- Retrieval of previous shell command was not always preserving indentation - (since 1.2a1) Patch 1528468 Tal Einat. - -- Changing tokenize (39046) to detect dedent broke tabnanny check (since 1.2a1) - -- ToggleTab dialog was setting indent to 8 even if cancelled (since 1.2a1). - -- When used w/o subprocess, all exceptions were preceded by an error - message claiming they were IDLE internal errors (since 1.2a1). - -- Bug #1525817: Don't truncate short lines in IDLE's tool tips. - -- Bug #1517990: IDLE keybindings on MacOS X now work correctly - -- Bug #1517996: IDLE now longer shows the default Tk menu when a - path browser, class browser or debugger is the frontmost window on MacOS X - -- EditorWindow.test() was failing. Bug 1417598 - -- EditorWindow failed when used stand-alone if sys.ps1 not set. - Bug 1010370 Dave Florek - -- Tooltips failed on new-syle class __init__ args. Bug 1027566 Loren Guthrie - -- Avoid occasional failure to detect closing paren properly. - Patch 1407280 Tal Einat - -- Rebinding Tab key was inserting 'tab' instead of 'Tab'. Bug 1179168. - -- Colorizer now handles # correctly, also unicode strings and - 'as' keyword in comment directly following import command. Closes 1325071. - Patch 1479219 Tal Einat - -- Patch #1162825: Support non-ASCII characters in IDLE window titles. - -- Source file f.flush() after writing; trying to avoid lossage if user - kills GUI. - -- Options / Keys / Advanced dialog made functional. Also, allow binding - of 'movement' keys. - -- 'syntax' patch adds improved calltips and a new class attribute listbox. - MultiCall module allows binding multiple actions to an event. - Patch 906702 Noam Raphael - -- Better indentation after first line of string continuation. - IDLEfork Patch 681992, Noam Raphael - -- Fixed CodeContext alignment problem, following suggestion from Tal Einat. - -- Increased performance in CodeContext extension Patch 936169 Noam Raphael - -- Mac line endings were incorrect when pasting code from some browsers - when using X11 and the Fink distribution. Python Bug 1263656. - -- when cursor is on a previous command retrieves that command. Instead - of replacing the input line, the previous command is now appended to the - input line. Indentation is preserved, and undo is enabled. - Patch 1196917 Jeff Shute - -- Clarify "tab/space" Error Dialog and "Tab Width" Dialog associated with - the Untabify command. - -- Corrected "tab/space" Error Dialog to show correct menu for Untabify. - Patch 1196980 Jeff Shute - -- New files are colorized by default, and colorizing is removed when - saving as non-Python files. Patch 1196895 Jeff Shute - Closes Python Bugs 775012 and 800432, partial fix IDLEfork 763524 - -- Improve subprocess link error notification. - -- run.py: use Queue's blocking feature instead of sleeping in the main - loop. Patch # 1190163 Michiel de Hoon - -- Add config-main option to make the 'history' feature non-cyclic. - Default remains cyclic. Python Patch 914546 Noam Raphael. - -- Removed ability to configure tabs indent from Options dialog. This 'feature' - has never worked and no one has complained. It is still possible to set a - default tabs (v. spaces) indent 'manually' via config-main.def (or to turn on - tabs for the current EditorWindow via the Format menu) but IDLE will - encourage indentation via spaces. - -- Enable setting the indentation width using the Options dialog. - Bug # 783877 - -- Add keybindings for del-word-left and del-word-right. - -- Discourage using an indent width other than 8 when using tabs to indent - Python code. - -- Restore use of EditorWindow.set_indentation_params(), was dead code since - Autoindent was merged into EditorWindow. This allows IDLE to conform to the - indentation width of a loaded file. (But it still will not switch to tabs - even if the file uses tabs.) Any change in indent width is local to that - window. - -- Add Tabnanny check before Run/F5, not just when Checking module. - -- If an extension can't be loaded, print warning and skip it instead of - erroring out. - -- Improve error handling when .idlerc can't be created (warn and exit). - -- The GUI was hanging if the shell window was closed while a raw_input() - was pending. Restored the quit() of the readline() mainloop(). - http://mail.python.org/pipermail/idle-dev/2004-December/002307.html - -- The remote procedure call module rpc.py can now access data attributes of - remote registered objects. Changes to these attributes are local, however. - - -What's New in IDLE 1.1? -======================= -*Release date: 30-NOV-2004* - -- On OpenBSD, terminating IDLE with ctrl-c from the command line caused a - stuck subprocess MainThread because only the SocketThread was exiting. - -- Saving a Keyset w/o making changes (by using the "Save as New Custom Key Set" - button) caused IDLE to fail on restart (no new keyset was created in - config-keys.cfg). Also true for Theme/highlights. Python Bug 1064535. - -- A change to the linecache.py API caused IDLE to exit when an exception was - raised while running without the subprocess (-n switch). Python Bug 1063840. - -- When paragraph reformat width was made configurable, a bug was - introduced that caused reformatting of comment blocks to ignore how - far the block was indented, effectively adding the indentation width - to the reformat width. This has been repaired, and the reformat - width is again a bound on the total width of reformatted lines. - -- Improve keyboard focus binding, especially in Windows menu. Improve - window raising, especially in the Windows menu and in the debugger. - IDLEfork 763524. - -- If user passes a non-existent filename on the commandline, just - open a new file, don't raise a dialog. IDLEfork 854928. - -- EditorWindow.py was not finding the .chm help file on Windows. Typo - at Rev 1.54. Python Bug 990954 - -- checking sys.platform for substring 'win' was breaking IDLE docs on Mac - (darwin). Also, Mac Safari browser requires full file:// URIs. SF 900580. - -- Redirect the warning stream to the shell during the ScriptBinding check of - user code and format the warning similarly to an exception for both that - check and for runtime warnings raised in the subprocess. - -- CodeContext hint pane visibility state is now persistent across sessions. - The pane no longer appears in the shell window. Added capability to limit - extensions to shell window or editor windows. Noam Raphael addition - to Patch 936169. - -- Paragraph reformat width is now a configurable parameter in the - Options GUI. - -- New Extension: CodeContext. Provides block structuring hints for code - which has scrolled above an edit window. Patch 936169 Noam Raphael. - -- If nulls somehow got into the strings in recent-files.lst - EditorWindow.update_recent_files_list() was failing. Python Bug 931336. - -- If the normal background is changed via Configure/Highlighting, it will - update immediately, thanks to the previously mentioned patch by Nigel Rowe. - -- Add a highlight theme for builtin keywords. Python Patch 805830 Nigel Rowe - This also fixed IDLEfork bug [ 693418 ] Normal text background color not - refreshed and Python bug [897872 ] Unknown color name on HP-UX - -- rpc.py:SocketIO - Large modules were generating large pickles when downloaded - to the execution server. The return of the OK response from the subprocess - initialization was interfering and causing the sending socket to be not - ready. Add an IO ready test to fix this. Moved the polling IO ready test - into pollpacket(). - -- Fix typo in rpc.py, s/b "pickle.PicklingError" not "pickle.UnpicklingError". - -- Added a Tk error dialog to run.py inform the user if the subprocess can't - connect to the user GUI process. Added a timeout to the GUI's listening - socket. Added Tk error dialogs to PyShell.py to announce a failure to bind - the port or connect to the subprocess. Clean up error handling during - connection initiation phase. This is an update of Python Patch 778323. - -- Print correct exception even if source file changed since shell was - restarted. IDLEfork Patch 869012 Noam Raphael - -- Keybindings with the Shift modifier now work correctly. So do bindings which - use the Space key. Limit unmodified user keybindings to the function keys. - Python Bug 775353, IDLEfork Bugs 755647, 761557 - -- After an exception, run.py was not setting the exception vector. Noam - Raphael suggested correcting this so pdb's postmortem pm() would work. - IDLEfork Patch 844675 - -- IDLE now does not fail to save the file anymore if the Tk buffer is not a - Unicode string, yet eol_convention is. Python Bugs 774680, 788378 - -- IDLE didn't start correctly when Python was installed in "Program Files" on - W2K and XP. Python Bugs 780451, 784183 - -- config-main.def documentation incorrectly referred to idle- instead of - config- filenames. SF 782759 Also added note about .idlerc location. - - -What's New in IDLE 1.0? -======================= -*Release date: 29-Jul-2003* - -- Added a banner to the shell discussing warnings possibly raised by personal - firewall software. Added same comment to README.txt. - -- Calltip error when docstring was None Python Bug 775541 - -- Updated extend.txt, help.txt, and config-extensions.def to correctly - reflect the current status of the configuration system. Python Bug 768469 - -- Fixed: Call Tip Trimming May Loop Forever. Python Patch 769142 (Daniels) - -- Replaced apply(f, args, kwds) with f(*args, **kwargs) to improve performance - Python Patch 768187 - -- Break or continue statements outside a loop were causing IDLE crash - Python Bug 767794 - -- Convert Unicode strings from readline to IOBinding.encoding. Also set - sys.std{in|out|err}.encoding, for both the local and the subprocess case. - SF IDLEfork patch 682347. - -- Extend AboutDialog.ViewFile() to support file encodings. Make the CREDITS - file Latin-1. - -- Updated the About dialog to reflect re-integration into Python. Provide - buttons to display Python's NEWS, License, and Credits, plus additional - buttons for IDLE's README and NEWS. - -- TextViewer() now has a third parameter which allows inserting text into the - viewer instead of reading from a file. - -- (Created the .../Lib/idlelib directory in the Python CVS, which is a clone of - IDLEfork modified to install in the Python environment. The code in the - interrupt module has been moved to thread.interrupt_main(). ) - -- Printing the Shell window was failing if it was not saved first SF 748975 - -- When using the Search in Files dialog, if the user had a selection - highlighted in his Editor window, insert it into the dialog search field. - -- The Python Shell entry was disappearing from the Windows menu. - -- Update the Windows file list when a file name change occurs - -- Change to File / Open Module: always pop up the dialog, using the current - selection as the default value. This is easier to use habitually. - -- Avoided a problem with starting the subprocess when 'localhost' doesn't - resolve to the user's loopback interface. SF 747772 - -- Fixed an issue with highlighted errors never de-colorizing. SF 747677. Also - improved notification of Tabnanny Token Error. - -- File / New will by default save in the directory of the Edit window from - which it was initiated. SF 748973 Guido van Rossum patch. - - -What's New in IDLEfork 0.9b1? -============================= -*Release date: 02-Jun-2003* - -- The current working directory of the execution environment (and shell - following completion of execution) is now that of the module being run. - -- Added the delete-exitfunc option to config-main.def. (This option is not - included in the Options dialog.) Setting this to True (the default) will - cause IDLE to not run sys.exitfunc/atexit when the subprocess exits. - -- IDLE now preserves the line ending codes when editing a file produced on - a different platform. SF 661759, SF 538584 - -- Reduced default editor font size to 10 point and increased window height - to provide a better initial impression on Windows. - -- Options / Fonts/Tabs / Set Base Editor Font: List box was not highlighting - the default font when first installed on Windows. SF 661676 - -- Added Autosave feature: when user runs code from edit window, if the file - has been modified IDLE will silently save it if Autosave is enabled. The - option is set in the Options dialog, and the default is to prompt the - user to save the file. SF 661318 Bruce Sherwood patch. - -- Improved the RESTART annotation in the shell window when the user restarts - the shell while it is generating output. Also improved annotation when user - repeatedly hammers the Ctrl-F6 restart. - -- Allow IDLE to run when not installed and cwd is not the IDLE directory - SF Patch 686254 "Run IDLEfork from any directory without set-up" - Raphael - -- When a module is run from an EditorWindow: if its directory is not in - sys.path, prepend it. This allows the module to import other modules in - the same directory. Do the same for a script run from the command line. - -- Correctly restart the subprocess if it is running user code and the user - attempts to run some other module or restarts the shell. Do the same if - the link is broken and it is possible to restart the subprocess and re- - connect to the GUI. SF RFE 661321. - -- Improved exception reporting when running commands or scripts from the - command line. - -- Added a -n command line switch to start IDLE without the subprocess. - Removed the Shell menu when running in that mode. Updated help messages. - -- Added a comment to the shell startup header to indicate when IDLE is not - using the subprocess. - -- Restore the ability to run without the subprocess. This can be important for - some platforms or configurations. (Running without the subprocess allows the - debugger to trace through parts of IDLE itself, which may or may not be - desirable, depending on your point of view. In addition, the traditional - reload/import tricks must be use if user source code is changed.) This is - helpful for developing IDLE using IDLE, because one instance can be used to - edit the code and a separate instance run to test changes. (Multiple - concurrent IDLE instances with subprocesses is a future feature) - -- Improve the error message a user gets when saving a file with non-ASCII - characters and no source encoding is specified. Done by adding a dialog - 'EncodingMessage', which contains the line to add in a fixed-font entry - widget, and which has a button to add that line to the file automatically. - Also, add a configuration option 'EditorWindow/encoding', which has three - possible values: none, utf-8, and locale. None is the default: IDLE will show - this dialog when non-ASCII characters are encountered. utf-8 means that files - with non-ASCII characters are saved as utf-8-with-bom. locale means that - files are saved in the locale's encoding; the dialog is only displayed if the - source contains characters outside the locale's charset. SF 710733 - Loewis - -- Improved I/O response by tweaking the wait parameter in various - calls to signal.signal(). - -- Implemented a threaded subprocess which allows interrupting a pass - loop in user code using the 'interrupt' extension. User code runs - in MainThread, while the RPCServer is handled by SockThread. This is - necessary because Windows doesn't support signals. - -- Implemented the 'interrupt' extension module, which allows a subthread - to raise a KeyboardInterrupt in the main thread. - -- Attempting to save the shell raised an error related to saving - breakpoints, which are not implemented in the shell - -- Provide a correct message when 'exit' or 'quit' are entered at the - IDLE command prompt SF 695861 - -- Eliminate extra blank line in shell output caused by not flushing - stdout when user code ends with an unterminated print. SF 695861 - -- Moved responsibility for exception formatting (i.e. pruning IDLE internal - calls) out of rpc.py into the client and server. - -- Exit IDLE cleanly even when doing subprocess I/O - -- Handle subprocess interrupt with an RPC message. - -- Restart the subprocess if it terminates itself. (VPython programs do that) - -- Support subclassing of exceptions, including in the shell, by moving the - exception formatting to the subprocess. - - -What's New in IDLEfork 0.9 Alpha 2? -=================================== -*Release date: 27-Jan-2003* - -- Updated INSTALL.txt to claify use of the python2 rpm. - -- Improved formatting in IDLE Help. - -- Run menu: Replace "Run Script" with "Run Module". - -- Code encountering an unhandled exception under the debugger now shows - the correct traceback, with IDLE internal levels pruned out. - -- If an exception occurs entirely in IDLE, don't prune the IDLE internal - modules from the traceback displayed. - -- Class Browser and Path Browser now use Alt-Key-2 for vertical zoom. - -- IDLE icons will now install correctly even when setup.py is run from the - build directory - -- Class Browser now compatible with Python2.3 version of pyclbr.py - -- Left cursor move in presence of selected text now moves from left end - of the selection. - -- Add Meta keybindings to "IDLE Classic Windows" to handle reversed - Alt/Meta on some Linux distros. - -- Change default: IDLE now starts with Python Shell. - -- Removed the File Path from the Additional Help Sources scrolled list. - -- Add capability to access Additional Help Sources on the web if the - Help File Path begins with //http or www. (Otherwise local path is - validated, as before.) - -- Additional Help Sources were not being posted on the Help menu in the - order entered. Implement sorting the list by [HelpFiles] 'option' - number. - -- Add Browse button to New Help Source dialog. Arrange to start in - Python/Doc if platform is Windows, otherwise start in current directory. - -- Put the Additional Help Sources directly on the Help menu instead of in - an Extra Help cascade menu. Rearrange the Help menu so the Additional - Help Sources come last. Update help.txt appropriately. - -- Fix Tk root pop-ups in configSectionNameDialog.py and configDialog.py - -- Uniform capitalization in General tab of ConfigDialog, update the doc string. - -- Fix bug in ConfigDialog where SaveAllChangedConfig() was unexpectedly - deleting Additional Help Sources from the user's config file. - -- Make configHelpSourceEdit OK button the default and bind - -- Fix Tk root pop-ups in configHelpSourceEdit: error dialogs not attached - to parents. - -- Use os.startfile() to open both Additional Help and Python Help on the - Windows platform. The application associated with the file type will act as - the viewer. Windows help files (.chm) are now supported via the - Settings/General/Additional Help facility. - -- If Python Help files are installed locally on Linux, use them instead of - accessing python.org. - -- Make the methods for finding the Python help docs more robust, and make - them work in the installed configuration, also. - -- On the Save Before Run dialog, make the OK button the default. One - less mouse action! - -- Add a method: EditorWindow.get_geometry() for future use in implementing - window location persistence. - -- Removed the "Help/Advice" menu entry. Thanks, David! We'll remember! - -- Change the "Classic Windows" theme's paste key to be . - -- Rearrange the Shell menu to put Stack Viewer entries adjacent. - -- Add the ability to restart the subprocess interpreter from the shell window; - add an associated menu entry "Shell/Restart" with binding Control-F6. Update - IDLE help. - -- Upon a restart, annotate the shell window with a "restart boundary". Add a - shell window menu "Shell/View Restart" with binding F6 to jump to the most - recent restart boundary. - -- Add Shell menu to Python Shell; change "Settings" to "Options". - -- Remove incorrect comment in setup.py: IDLEfork is now installed as a package. - -- Add INSTALL.txt, HISTORY.txt, NEWS.txt to installed configuration. - -- In installer text, fix reference to Visual Python, should be VPython. - Properly credit David Scherer. - -- Modified idle, idle.py, idle.pyw to improve exception handling. - - -What's New in IDLEfork 0.9 Alpha 1? -=================================== -*Release date: 31-Dec-2002* - -- First release of major new functionality. For further details refer to - Idle-dev and/or the Sourceforge CVS. - -- Adapted to the Mac platform. - -- Overhauled the IDLE startup options and revised the idle -h help message, - which provides details of command line usage. - -- Multiple bug fixes and usability enhancements. - -- Introduced the new RPC implementation, which includes a debugger. The output - of user code is to the shell, and the shell may be used to inspect the - environment after the run has finished. (In version 0.8.1 the shell - environment was separate from the environment of the user code.) - -- Introduced the configuration GUI and a new About dialog. - -- Removed David Scherer's Remote Procedure Call code and replaced with Guido - van Rossum's. GvR code has support for the IDLE debugger and uses the shell - to inspect the environment of code Run from an Edit window. Files removed: - ExecBinding.py, loader.py, protocol.py, Remote.py, spawn.py - --------------------------------------------------------------------- -Refer to HISTORY.txt for additional information on earlier releases. --------------------------------------------------------------------- +------------------------------------------------------------------------ +Refer to NEWS2x.txt and HISTORY.txt for information on earlier releases. +------------------------------------------------------------------------ diff --git a/lib/idlelib/NEWS2x.txt b/lib/idlelib/NEWS2x.txt new file mode 100644 index 00000000..6751ca5f --- /dev/null +++ b/lib/idlelib/NEWS2x.txt @@ -0,0 +1,660 @@ +What's New in IDLE 2.7? (Merged into 3.1 before 2.7 release.) +======================= +*Release date: XX-XXX-2010* + +- idle.py modified and simplified to better support developing experimental + versions of IDLE which are not installed in the standard location. + +- OutputWindow/PyShell right click menu "Go to file/line" wasn't working with + file paths containing spaces. Bug 5559. + +- Windows: Version string for the .chm help file changed, file not being + accessed Patch 5783 Guilherme Polo + +- Allow multiple IDLE GUI/subprocess pairs to exist simultaneously. Thanks to + David Scherer for suggesting the use of an ephemeral port for the GUI. + Patch 1529142 Weeble. + +- Remove port spec from run.py and fix bug where subprocess fails to + extract port from command line when warnings are present. + +- Tk 8.5 Text widget requires 'wordprocessor' tabstyle attr to handle + mixed space/tab properly. Issue 5129, patch by Guilherme Polo. + +- Issue #3549: On MacOS the preferences menu was not present + +- IDLE would print a "Unhandled server exception!" message when internal + debugging is enabled. + +- Issue #4455: IDLE failed to display the windows list when two windows have + the same title. + +- Issue #4383: When IDLE cannot make the connection to its subprocess, it would + fail to properly display the error message. + +- help() was not paging to the shell. Issue1650. + +- CodeContext was not importing. + +- Corrected two 3.0 compatibility errors reported by Mark Summerfield: + http://mail.python.org/pipermail/python-3000/2007-December/011491.html + +- Shell was not colorizing due to bug introduced at r57998, Bug 1586. + +- Issue #1585: IDLE uses non-existent xrange() function. + +- Windows EOL sequence not converted correctly, encoding error. + Caused file save to fail. Bug 1130. + +- IDLE converted to Python 3000 syntax. + +- Strings became Unicode. + +- CallTips module now uses the inspect module to produce the argspec. + +- IDLE modules now use absolute import instead of implied relative import. + +- atexit call replaces sys.exitfunc. The functionality of delete-exitfunc flag + in config-main.cfg remains unchanged: if set, registered exit functions will + be cleared before IDLE exits. + + +What's New in IDLE 2.6 +====================== +*Release date: 01-Oct-2008*, merged into 3.0 releases detailed above (3.0rc2) + +- Issue #2665: On Windows, an IDLE installation upgraded from an old version + would not start if a custom theme was defined. + +- Home / Control-A toggles between left margin and end of leading white + space. issue1196903, patch by Jeff Shute. + +- Improved AutoCompleteWindow logic. issue2062, patch by Tal Einat. + +- Autocompletion of filenames now support alternate separators, e.g. the + '/' char on Windows. issue2061 Patch by Tal Einat. + +- Configured selection highlighting colors were ignored; updating highlighting + in the config dialog would cause non-Python files to be colored as if they + were Python source; improve use of ColorDelagator. Patch 1334. Tal Einat. + +- ScriptBinding event handlers weren't returning 'break'. Patch 2050, Tal Einat + +- There was an error on exit if no sys.exitfunc was defined. Issue 1647. + +- Could not open files in .idlerc directory if latter was hidden on Windows. + Issue 1743, Issue 1862. + +- Configure Dialog: improved layout for keybinding. Patch 1457 Tal Einat. + +- tabpage.py updated: tabbedPages.py now supports multiple dynamic rows + of tabs. Patch 1612746 Tal Einat. + +- Add confirmation dialog before printing. Patch 1717170 Tal Einat. + +- Show paste position if > 80 col. Patch 1659326 Tal Einat. + +- Update cursor color without restarting. Patch 1725576 Tal Einat. + +- Allow keyboard interrupt only when user code is executing in subprocess. + Patch 1225 Tal Einat (reworked from IDLE-Spoon). + +- configDialog cleanup. Patch 1730217 Tal Einat. + +- textView cleanup. Patch 1718043 Tal Einat. + +- Clean up EditorWindow close. + +- Patch 1693258: Fix for duplicate "preferences" menu-OS X. Backport of r56204. + +- OSX: Avoid crash for those versions of Tcl/Tk which don't have a console + +- Bug in idlelib.MultiCall: Options dialog was crashing IDLE if there was an + option in config-extensions w/o a value. Patch #1672481, Tal Einat + +- Corrected some bugs in AutoComplete. Also, Page Up/Down in ACW implemented; + mouse and cursor selection in ACWindow implemented; double Tab inserts + current selection and closes ACW (similar to double-click and Return); scroll + wheel now works in ACW. Added AutoComplete instructions to IDLE Help. + +- AutoCompleteWindow moved below input line, will move above if there + isn't enough space. Patch 1621265 Tal Einat + +- Calltips now 'handle' tuples in the argument list (display '' :) + Suggested solution by Christos Georgiou, Bug 791968. + +- Add 'raw' support to configHandler. Patch 1650174 Tal Einat. + +- Avoid hang when encountering a duplicate in a completion list. Bug 1571112. + +- Patch #1362975: Rework CodeContext indentation algorithm to + avoid hard-coding pixel widths. + +- Bug #813342: Start the IDLE subprocess with -Qnew if the parent + is started with that option. + +- Honor the "Cancel" action in the save dialog (Debian bug #299092) + +- Some syntax errors were being caught by tokenize during the tabnanny + check, resulting in obscure error messages. Do the syntax check + first. Bug 1562716, 1562719 + +- IDLE's version number takes a big jump to match the version number of + the Python release of which it's a part. + + +What's New in IDLE 1.2? +======================= +*Release date: 19-SEP-2006* + +- File menu hotkeys: there were three 'p' assignments. Reassign the + 'Save Copy As' and 'Print' hotkeys to 'y' and 't'. Change the + Shell hotkey from 's' to 'l'. + +- IDLE honors new quit() and exit() commands from site.py Quitter() object. + Patch 1540892, Jim Jewett + +- The 'with' statement is now a Code Context block opener. + Patch 1540851, Jim Jewett + +- Retrieval of previous shell command was not always preserving indentation + (since 1.2a1) Patch 1528468 Tal Einat. + +- Changing tokenize (39046) to detect dedent broke tabnanny check (since 1.2a1) + +- ToggleTab dialog was setting indent to 8 even if cancelled (since 1.2a1). + +- When used w/o subprocess, all exceptions were preceded by an error + message claiming they were IDLE internal errors (since 1.2a1). + +- Bug #1525817: Don't truncate short lines in IDLE's tool tips. + +- Bug #1517990: IDLE keybindings on MacOS X now work correctly + +- Bug #1517996: IDLE now longer shows the default Tk menu when a + path browser, class browser or debugger is the frontmost window on MacOS X + +- EditorWindow.test() was failing. Bug 1417598 + +- EditorWindow failed when used stand-alone if sys.ps1 not set. + Bug 1010370 Dave Florek + +- Tooltips failed on new-syle class __init__ args. Bug 1027566 Loren Guthrie + +- Avoid occasional failure to detect closing paren properly. + Patch 1407280 Tal Einat + +- Rebinding Tab key was inserting 'tab' instead of 'Tab'. Bug 1179168. + +- Colorizer now handles # correctly, also unicode strings and + 'as' keyword in comment directly following import command. Closes 1325071. + Patch 1479219 Tal Einat + +- Patch #1162825: Support non-ASCII characters in IDLE window titles. + +- Source file f.flush() after writing; trying to avoid lossage if user + kills GUI. + +- Options / Keys / Advanced dialog made functional. Also, allow binding + of 'movement' keys. + +- 'syntax' patch adds improved calltips and a new class attribute listbox. + MultiCall module allows binding multiple actions to an event. + Patch 906702 Noam Raphael + +- Better indentation after first line of string continuation. + IDLEfork Patch 681992, Noam Raphael + +- Fixed CodeContext alignment problem, following suggestion from Tal Einat. + +- Increased performance in CodeContext extension Patch 936169 Noam Raphael + +- Mac line endings were incorrect when pasting code from some browsers + when using X11 and the Fink distribution. Python Bug 1263656. + +- when cursor is on a previous command retrieves that command. Instead + of replacing the input line, the previous command is now appended to the + input line. Indentation is preserved, and undo is enabled. + Patch 1196917 Jeff Shute + +- Clarify "tab/space" Error Dialog and "Tab Width" Dialog associated with + the Untabify command. + +- Corrected "tab/space" Error Dialog to show correct menu for Untabify. + Patch 1196980 Jeff Shute + +- New files are colorized by default, and colorizing is removed when + saving as non-Python files. Patch 1196895 Jeff Shute + Closes Python Bugs 775012 and 800432, partial fix IDLEfork 763524 + +- Improve subprocess link error notification. + +- run.py: use Queue's blocking feature instead of sleeping in the main + loop. Patch # 1190163 Michiel de Hoon + +- Add config-main option to make the 'history' feature non-cyclic. + Default remains cyclic. Python Patch 914546 Noam Raphael. + +- Removed ability to configure tabs indent from Options dialog. This 'feature' + has never worked and no one has complained. It is still possible to set a + default tabs (v. spaces) indent 'manually' via config-main.def (or to turn on + tabs for the current EditorWindow via the Format menu) but IDLE will + encourage indentation via spaces. + +- Enable setting the indentation width using the Options dialog. + Bug # 783877 + +- Add keybindings for del-word-left and del-word-right. + +- Discourage using an indent width other than 8 when using tabs to indent + Python code. + +- Restore use of EditorWindow.set_indentation_params(), was dead code since + Autoindent was merged into EditorWindow. This allows IDLE to conform to the + indentation width of a loaded file. (But it still will not switch to tabs + even if the file uses tabs.) Any change in indent width is local to that + window. + +- Add Tabnanny check before Run/F5, not just when Checking module. + +- If an extension can't be loaded, print warning and skip it instead of + erroring out. + +- Improve error handling when .idlerc can't be created (warn and exit). + +- The GUI was hanging if the shell window was closed while a raw_input() + was pending. Restored the quit() of the readline() mainloop(). + http://mail.python.org/pipermail/idle-dev/2004-December/002307.html + +- The remote procedure call module rpc.py can now access data attributes of + remote registered objects. Changes to these attributes are local, however. + + +What's New in IDLE 1.1? +======================= +*Release date: 30-NOV-2004* + +- On OpenBSD, terminating IDLE with ctrl-c from the command line caused a + stuck subprocess MainThread because only the SocketThread was exiting. + +- Saving a Keyset w/o making changes (by using the "Save as New Custom Key Set" + button) caused IDLE to fail on restart (no new keyset was created in + config-keys.cfg). Also true for Theme/highlights. Python Bug 1064535. + +- A change to the linecache.py API caused IDLE to exit when an exception was + raised while running without the subprocess (-n switch). Python Bug 1063840. + +- When paragraph reformat width was made configurable, a bug was + introduced that caused reformatting of comment blocks to ignore how + far the block was indented, effectively adding the indentation width + to the reformat width. This has been repaired, and the reformat + width is again a bound on the total width of reformatted lines. + +- Improve keyboard focus binding, especially in Windows menu. Improve + window raising, especially in the Windows menu and in the debugger. + IDLEfork 763524. + +- If user passes a non-existent filename on the commandline, just + open a new file, don't raise a dialog. IDLEfork 854928. + +- EditorWindow.py was not finding the .chm help file on Windows. Typo + at Rev 1.54. Python Bug 990954 + +- checking sys.platform for substring 'win' was breaking IDLE docs on Mac + (darwin). Also, Mac Safari browser requires full file:// URIs. SF 900580. + +- Redirect the warning stream to the shell during the ScriptBinding check of + user code and format the warning similarly to an exception for both that + check and for runtime warnings raised in the subprocess. + +- CodeContext hint pane visibility state is now persistent across sessions. + The pane no longer appears in the shell window. Added capability to limit + extensions to shell window or editor windows. Noam Raphael addition + to Patch 936169. + +- Paragraph reformat width is now a configurable parameter in the + Options GUI. + +- New Extension: CodeContext. Provides block structuring hints for code + which has scrolled above an edit window. Patch 936169 Noam Raphael. + +- If nulls somehow got into the strings in recent-files.lst + EditorWindow.update_recent_files_list() was failing. Python Bug 931336. + +- If the normal background is changed via Configure/Highlighting, it will + update immediately, thanks to the previously mentioned patch by Nigel Rowe. + +- Add a highlight theme for builtin keywords. Python Patch 805830 Nigel Rowe + This also fixed IDLEfork bug [ 693418 ] Normal text background color not + refreshed and Python bug [897872 ] Unknown color name on HP-UX + +- rpc.py:SocketIO - Large modules were generating large pickles when downloaded + to the execution server. The return of the OK response from the subprocess + initialization was interfering and causing the sending socket to be not + ready. Add an IO ready test to fix this. Moved the polling IO ready test + into pollpacket(). + +- Fix typo in rpc.py, s/b "pickle.PicklingError" not "pickle.UnpicklingError". + +- Added a Tk error dialog to run.py inform the user if the subprocess can't + connect to the user GUI process. Added a timeout to the GUI's listening + socket. Added Tk error dialogs to PyShell.py to announce a failure to bind + the port or connect to the subprocess. Clean up error handling during + connection initiation phase. This is an update of Python Patch 778323. + +- Print correct exception even if source file changed since shell was + restarted. IDLEfork Patch 869012 Noam Raphael + +- Keybindings with the Shift modifier now work correctly. So do bindings which + use the Space key. Limit unmodified user keybindings to the function keys. + Python Bug 775353, IDLEfork Bugs 755647, 761557 + +- After an exception, run.py was not setting the exception vector. Noam + Raphael suggested correcting this so pdb's postmortem pm() would work. + IDLEfork Patch 844675 + +- IDLE now does not fail to save the file anymore if the Tk buffer is not a + Unicode string, yet eol_convention is. Python Bugs 774680, 788378 + +- IDLE didn't start correctly when Python was installed in "Program Files" on + W2K and XP. Python Bugs 780451, 784183 + +- config-main.def documentation incorrectly referred to idle- instead of + config- filenames. SF 782759 Also added note about .idlerc location. + + +What's New in IDLE 1.0? +======================= +*Release date: 29-Jul-2003* + +- Added a banner to the shell discussing warnings possibly raised by personal + firewall software. Added same comment to README.txt. + +- Calltip error when docstring was None Python Bug 775541 + +- Updated extend.txt, help.txt, and config-extensions.def to correctly + reflect the current status of the configuration system. Python Bug 768469 + +- Fixed: Call Tip Trimming May Loop Forever. Python Patch 769142 (Daniels) + +- Replaced apply(f, args, kwds) with f(*args, **kwargs) to improve performance + Python Patch 768187 + +- Break or continue statements outside a loop were causing IDLE crash + Python Bug 767794 + +- Convert Unicode strings from readline to IOBinding.encoding. Also set + sys.std{in|out|err}.encoding, for both the local and the subprocess case. + SF IDLEfork patch 682347. + +- Extend AboutDialog.ViewFile() to support file encodings. Make the CREDITS + file Latin-1. + +- Updated the About dialog to reflect re-integration into Python. Provide + buttons to display Python's NEWS, License, and Credits, plus additional + buttons for IDLE's README and NEWS. + +- TextViewer() now has a third parameter which allows inserting text into the + viewer instead of reading from a file. + +- (Created the .../Lib/idlelib directory in the Python CVS, which is a clone of + IDLEfork modified to install in the Python environment. The code in the + interrupt module has been moved to thread.interrupt_main(). ) + +- Printing the Shell window was failing if it was not saved first SF 748975 + +- When using the Search in Files dialog, if the user had a selection + highlighted in his Editor window, insert it into the dialog search field. + +- The Python Shell entry was disappearing from the Windows menu. + +- Update the Windows file list when a file name change occurs + +- Change to File / Open Module: always pop up the dialog, using the current + selection as the default value. This is easier to use habitually. + +- Avoided a problem with starting the subprocess when 'localhost' doesn't + resolve to the user's loopback interface. SF 747772 + +- Fixed an issue with highlighted errors never de-colorizing. SF 747677. Also + improved notification of Tabnanny Token Error. + +- File / New will by default save in the directory of the Edit window from + which it was initiated. SF 748973 Guido van Rossum patch. + + +What's New in IDLEfork 0.9b1? +============================= +*Release date: 02-Jun-2003* + +- The current working directory of the execution environment (and shell + following completion of execution) is now that of the module being run. + +- Added the delete-exitfunc option to config-main.def. (This option is not + included in the Options dialog.) Setting this to True (the default) will + cause IDLE to not run sys.exitfunc/atexit when the subprocess exits. + +- IDLE now preserves the line ending codes when editing a file produced on + a different platform. SF 661759, SF 538584 + +- Reduced default editor font size to 10 point and increased window height + to provide a better initial impression on Windows. + +- Options / Fonts/Tabs / Set Base Editor Font: List box was not highlighting + the default font when first installed on Windows. SF 661676 + +- Added Autosave feature: when user runs code from edit window, if the file + has been modified IDLE will silently save it if Autosave is enabled. The + option is set in the Options dialog, and the default is to prompt the + user to save the file. SF 661318 Bruce Sherwood patch. + +- Improved the RESTART annotation in the shell window when the user restarts + the shell while it is generating output. Also improved annotation when user + repeatedly hammers the Ctrl-F6 restart. + +- Allow IDLE to run when not installed and cwd is not the IDLE directory + SF Patch 686254 "Run IDLEfork from any directory without set-up" - Raphael + +- When a module is run from an EditorWindow: if its directory is not in + sys.path, prepend it. This allows the module to import other modules in + the same directory. Do the same for a script run from the command line. + +- Correctly restart the subprocess if it is running user code and the user + attempts to run some other module or restarts the shell. Do the same if + the link is broken and it is possible to restart the subprocess and re- + connect to the GUI. SF RFE 661321. + +- Improved exception reporting when running commands or scripts from the + command line. + +- Added a -n command line switch to start IDLE without the subprocess. + Removed the Shell menu when running in that mode. Updated help messages. + +- Added a comment to the shell startup header to indicate when IDLE is not + using the subprocess. + +- Restore the ability to run without the subprocess. This can be important for + some platforms or configurations. (Running without the subprocess allows the + debugger to trace through parts of IDLE itself, which may or may not be + desirable, depending on your point of view. In addition, the traditional + reload/import tricks must be use if user source code is changed.) This is + helpful for developing IDLE using IDLE, because one instance can be used to + edit the code and a separate instance run to test changes. (Multiple + concurrent IDLE instances with subprocesses is a future feature) + +- Improve the error message a user gets when saving a file with non-ASCII + characters and no source encoding is specified. Done by adding a dialog + 'EncodingMessage', which contains the line to add in a fixed-font entry + widget, and which has a button to add that line to the file automatically. + Also, add a configuration option 'EditorWindow/encoding', which has three + possible values: none, utf-8, and locale. None is the default: IDLE will show + this dialog when non-ASCII characters are encountered. utf-8 means that files + with non-ASCII characters are saved as utf-8-with-bom. locale means that + files are saved in the locale's encoding; the dialog is only displayed if the + source contains characters outside the locale's charset. SF 710733 - Loewis + +- Improved I/O response by tweaking the wait parameter in various + calls to signal.signal(). + +- Implemented a threaded subprocess which allows interrupting a pass + loop in user code using the 'interrupt' extension. User code runs + in MainThread, while the RPCServer is handled by SockThread. This is + necessary because Windows doesn't support signals. + +- Implemented the 'interrupt' extension module, which allows a subthread + to raise a KeyboardInterrupt in the main thread. + +- Attempting to save the shell raised an error related to saving + breakpoints, which are not implemented in the shell + +- Provide a correct message when 'exit' or 'quit' are entered at the + IDLE command prompt SF 695861 + +- Eliminate extra blank line in shell output caused by not flushing + stdout when user code ends with an unterminated print. SF 695861 + +- Moved responsibility for exception formatting (i.e. pruning IDLE internal + calls) out of rpc.py into the client and server. + +- Exit IDLE cleanly even when doing subprocess I/O + +- Handle subprocess interrupt with an RPC message. + +- Restart the subprocess if it terminates itself. (VPython programs do that) + +- Support subclassing of exceptions, including in the shell, by moving the + exception formatting to the subprocess. + + +What's New in IDLEfork 0.9 Alpha 2? +=================================== +*Release date: 27-Jan-2003* + +- Updated INSTALL.txt to claify use of the python2 rpm. + +- Improved formatting in IDLE Help. + +- Run menu: Replace "Run Script" with "Run Module". + +- Code encountering an unhandled exception under the debugger now shows + the correct traceback, with IDLE internal levels pruned out. + +- If an exception occurs entirely in IDLE, don't prune the IDLE internal + modules from the traceback displayed. + +- Class Browser and Path Browser now use Alt-Key-2 for vertical zoom. + +- IDLE icons will now install correctly even when setup.py is run from the + build directory + +- Class Browser now compatible with Python2.3 version of pyclbr.py + +- Left cursor move in presence of selected text now moves from left end + of the selection. + +- Add Meta keybindings to "IDLE Classic Windows" to handle reversed + Alt/Meta on some Linux distros. + +- Change default: IDLE now starts with Python Shell. + +- Removed the File Path from the Additional Help Sources scrolled list. + +- Add capability to access Additional Help Sources on the web if the + Help File Path begins with //http or www. (Otherwise local path is + validated, as before.) + +- Additional Help Sources were not being posted on the Help menu in the + order entered. Implement sorting the list by [HelpFiles] 'option' + number. + +- Add Browse button to New Help Source dialog. Arrange to start in + Python/Doc if platform is Windows, otherwise start in current directory. + +- Put the Additional Help Sources directly on the Help menu instead of in + an Extra Help cascade menu. Rearrange the Help menu so the Additional + Help Sources come last. Update help.txt appropriately. + +- Fix Tk root pop-ups in configSectionNameDialog.py and configDialog.py + +- Uniform capitalization in General tab of ConfigDialog, update the doc string. + +- Fix bug in ConfigDialog where SaveAllChangedConfig() was unexpectedly + deleting Additional Help Sources from the user's config file. + +- Make configHelpSourceEdit OK button the default and bind + +- Fix Tk root pop-ups in configHelpSourceEdit: error dialogs not attached + to parents. + +- Use os.startfile() to open both Additional Help and Python Help on the + Windows platform. The application associated with the file type will act as + the viewer. Windows help files (.chm) are now supported via the + Settings/General/Additional Help facility. + +- If Python Help files are installed locally on Linux, use them instead of + accessing python.org. + +- Make the methods for finding the Python help docs more robust, and make + them work in the installed configuration, also. + +- On the Save Before Run dialog, make the OK button the default. One + less mouse action! + +- Add a method: EditorWindow.get_geometry() for future use in implementing + window location persistence. + +- Removed the "Help/Advice" menu entry. Thanks, David! We'll remember! + +- Change the "Classic Windows" theme's paste key to be . + +- Rearrange the Shell menu to put Stack Viewer entries adjacent. + +- Add the ability to restart the subprocess interpreter from the shell window; + add an associated menu entry "Shell/Restart" with binding Control-F6. Update + IDLE help. + +- Upon a restart, annotate the shell window with a "restart boundary". Add a + shell window menu "Shell/View Restart" with binding F6 to jump to the most + recent restart boundary. + +- Add Shell menu to Python Shell; change "Settings" to "Options". + +- Remove incorrect comment in setup.py: IDLEfork is now installed as a package. + +- Add INSTALL.txt, HISTORY.txt, NEWS.txt to installed configuration. + +- In installer text, fix reference to Visual Python, should be VPython. + Properly credit David Scherer. + +- Modified idle, idle.py, idle.pyw to improve exception handling. + + +What's New in IDLEfork 0.9 Alpha 1? +=================================== +*Release date: 31-Dec-2002* + +- First release of major new functionality. For further details refer to + Idle-dev and/or the Sourceforge CVS. + +- Adapted to the Mac platform. + +- Overhauled the IDLE startup options and revised the idle -h help message, + which provides details of command line usage. + +- Multiple bug fixes and usability enhancements. + +- Introduced the new RPC implementation, which includes a debugger. The output + of user code is to the shell, and the shell may be used to inspect the + environment after the run has finished. (In version 0.8.1 the shell + environment was separate from the environment of the user code.) + +- Introduced the configuration GUI and a new About dialog. + +- Removed David Scherer's Remote Procedure Call code and replaced with Guido + van Rossum's. GvR code has support for the IDLE debugger and uses the shell + to inspect the environment of code Run from an Edit window. Files removed: + ExecBinding.py, loader.py, protocol.py, Remote.py, spawn.py + +-------------------------------------------------------------------- +Refer to HISTORY.txt for additional information on earlier releases. +-------------------------------------------------------------------- diff --git a/lib/idlelib/ParenMatch.py b/lib/idlelib/ParenMatch.py index 19bad8ce..47e10f35 100644 --- a/lib/idlelib/ParenMatch.py +++ b/lib/idlelib/ParenMatch.py @@ -9,7 +9,7 @@ from idlelib.configHandler import idleConf _openers = {')':'(',']':'[','}':'{'} -CHECK_DELAY = 100 # miliseconds +CHECK_DELAY = 100 # milliseconds class ParenMatch: """Highlight matching parentheses diff --git a/lib/idlelib/Percolator.py b/lib/idlelib/Percolator.py index 9e933194..b8be2aa7 100644 --- a/lib/idlelib/Percolator.py +++ b/lib/idlelib/Percolator.py @@ -1,6 +1,7 @@ from idlelib.WidgetRedirector import WidgetRedirector from idlelib.Delegator import Delegator + class Percolator: def __init__(self, text): @@ -16,8 +17,10 @@ def close(self): while self.top is not self.bottom: self.removefilter(self.top) self.top = None - self.bottom.setdelegate(None); self.bottom = None - self.redir.close(); self.redir = None + self.bottom.setdelegate(None) + self.bottom = None + self.redir.close() + self.redir = None self.text = None def insert(self, index, chars, tags=None): @@ -51,54 +54,52 @@ def removefilter(self, filter): f.setdelegate(filter.delegate) filter.setdelegate(None) -def _percolator(parent): + +def _percolator(parent): # htest # import tkinter as tk import re + class Tracer(Delegator): def __init__(self, name): self.name = name Delegator.__init__(self, None) + def insert(self, *args): print(self.name, ": insert", args) self.delegate.insert(*args) + def delete(self, *args): print(self.name, ": delete", args) self.delegate.delete(*args) - root = tk.Tk() - root.title("Test Percolator") + + box = tk.Toplevel(parent) + box.title("Test Percolator") width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) - root.geometry("+%d+%d"%(x, y + 150)) - text = tk.Text(root) + box.geometry("+%d+%d" % (x, y + 150)) + text = tk.Text(box) p = Percolator(text) + pin = p.insertfilter + pout = p.removefilter t1 = Tracer("t1") t2 = Tracer("t2") def toggle1(): - if var1.get() == 0: - var1.set(1) - p.insertfilter(t1) - elif var1.get() == 1: - var1.set(0) - p.removefilter(t1) - + (pin if var1.get() else pout)(t1) def toggle2(): - if var2.get() == 0: - var2.set(1) - p.insertfilter(t2) - elif var2.get() == 1: - var2.set(0) - p.removefilter(t2) + (pin if var2.get() else pout)(t2) text.pack() var1 = tk.IntVar() - cb1 = tk.Checkbutton(root, text="Tracer1", command=toggle1, variable=var1) + cb1 = tk.Checkbutton(box, text="Tracer1", command=toggle1, variable=var1) cb1.pack() var2 = tk.IntVar() - cb2 = tk.Checkbutton(root, text="Tracer2", command=toggle2, variable=var2) + cb2 = tk.Checkbutton(box, text="Tracer2", command=toggle2, variable=var2) cb2.pack() - root.mainloop() - if __name__ == "__main__": + import unittest + unittest.main('idlelib.idle_test.test_percolator', verbosity=2, + exit=False) + from idlelib.idle_test.htest import run run(_percolator) diff --git a/lib/idlelib/PyShell.py b/lib/idlelib/PyShell.py index 1bcc9b68..5dec68e5 100644 --- a/lib/idlelib/PyShell.py +++ b/lib/idlelib/PyShell.py @@ -1396,6 +1396,17 @@ def close(self): self.shell.close() +def fix_x11_paste(root): + "Make paste replace selection on x11. See issue #5124." + if root._windowingsystem == 'x11': + for cls in 'Text', 'Entry', 'Spinbox': + root.bind_class( + cls, + '<>', + 'catch {%W delete sel.first sel.last}\n' + + root.bind_class(cls, '<>')) + + usage_msg = """\ USAGE: idle [-deins] [-t title] [file]* @@ -1528,8 +1539,10 @@ def main(): 'editor-on-startup', type='bool') enable_edit = enable_edit or edit_start enable_shell = enable_shell or not enable_edit + # start editor and/or shell windows: root = Tk(className="Idle") + root.withdraw() # set application icon icondir = os.path.join(os.path.dirname(__file__), 'Icons') @@ -1544,7 +1557,7 @@ def main(): root.wm_iconphoto(True, *icons) fixwordbreaks(root) - root.withdraw() + fix_x11_paste(root) flist = PyShellFileList(root) macosxSupport.setupApp(root, flist) diff --git a/lib/idlelib/README.txt b/lib/idlelib/README.txt index 7bf74c0f..ff445049 100644 --- a/lib/idlelib/README.txt +++ b/lib/idlelib/README.txt @@ -16,7 +16,7 @@ and omissions and lag behind changes in idlelib. IDLELIB FILES -Implemetation files not in IDLE MENU are marked (nim). +Implementation files not in IDLE MENU are marked (nim). Deprecated files and objects are listed separately as the end. Startup @@ -161,14 +161,15 @@ Edit Show surrounding parens # ParenMatch (& Hyperparser) Shell # PyShell - View Last Restart # PyShell.? - Restart Shell # PyShell.? + View Last Restart # PyShell.PyShell.view_restart_mark + Restart Shell # PyShell.PyShell.restart_shell + Interrupt Execution # pyshell.PyShell.cancel_callback Debug (Shell only) Go to File/Line - Debugger # Debugger, RemoteDebugger - Stack Viewer # StackViewer - Auto-open Stack Viewer # StackViewer + Debugger # Debugger, RemoteDebugger, PyShell.toggle_debuger + Stack Viewer # StackViewer, PyShell.open_stack_viewer + Auto-open Stack Viewer # StackViewer Format (Editor only) Indent Region diff --git a/lib/idlelib/ReplaceDialog.py b/lib/idlelib/ReplaceDialog.py index 2665a1c6..f2ea22e7 100644 --- a/lib/idlelib/ReplaceDialog.py +++ b/lib/idlelib/ReplaceDialog.py @@ -1,3 +1,8 @@ +"""Replace dialog for IDLE. Inherits SearchDialogBase for GUI. +Uses idlelib.SearchEngine for search capability. +Defines various replace related functions like replace, replace all, +replace+find. +""" from tkinter import * from idlelib import SearchEngine @@ -6,6 +11,8 @@ def replace(text): + """Returns a singleton ReplaceDialog instance.The single dialog + saves user entries and preferences across instances.""" root = text._root() engine = SearchEngine.get(root) if not hasattr(engine, "_replacedialog"): @@ -24,6 +31,7 @@ def __init__(self, root, engine): self.replvar = StringVar(root) def open(self, text): + """Display the replace dialog""" SearchDialogBase.open(self, text) try: first = text.index("sel.first") @@ -39,6 +47,7 @@ def open(self, text): self.ok = 1 def create_entries(self): + """Create label and text entry widgets""" SearchDialogBase.create_entries(self) self.replent = self.make_entry("Replace with:", self.replvar)[0] @@ -57,9 +66,10 @@ def replace_it(self, event=None): self.do_replace() def default_command(self, event=None): + "Replace and find next." if self.do_find(self.ok): - if self.do_replace(): # Only find next match if replace succeeded. - # A bad re can cause it to fail. + if self.do_replace(): # Only find next match if replace succeeded. + # A bad re can cause it to fail. self.do_find(0) def _replace_expand(self, m, repl): @@ -77,6 +87,7 @@ def _replace_expand(self, m, repl): return new def replace_all(self, event=None): + """Replace all instances of patvar with replvar in text""" prog = self.engine.getprog() if not prog: return @@ -173,6 +184,8 @@ def do_replace(self): return True def show_hit(self, first, last): + """Highlight text from 'first' to 'last'. + 'first', 'last' - Text indices""" text = self.text text.mark_set("insert", first) text.tag_remove("sel", "1.0", "end") @@ -189,11 +202,13 @@ def close(self, event=None): SearchDialogBase.close(self, event) self.text.tag_remove("hit", "1.0", "end") -def _replace_dialog(parent): - root = Tk() - root.title("Test ReplaceDialog") + +def _replace_dialog(parent): # htest # + """htest wrapper function""" + box = Toplevel(parent) + box.title("Test ReplaceDialog") width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) - root.geometry("+%d+%d"%(x, y + 150)) + box.geometry("+%d+%d"%(x, y + 150)) # mock undo delegator methods def undo_block_start(): @@ -202,20 +217,25 @@ def undo_block_start(): def undo_block_stop(): pass - text = Text(root) + text = Text(box, inactiveselectbackground='gray') text.undo_block_start = undo_block_start text.undo_block_stop = undo_block_stop text.pack() - text.insert("insert","This is a sample string.\n"*10) + text.insert("insert","This is a sample sTring\nPlus MORE.") + text.focus_set() def show_replace(): text.tag_add(SEL, "1.0", END) replace(text) text.tag_remove(SEL, "1.0", END) - button = Button(root, text="Replace", command=show_replace) + button = Button(box, text="Replace", command=show_replace) button.pack() if __name__ == '__main__': + import unittest + unittest.main('idlelib.idle_test.test_replacedialog', + verbosity=2, exit=False) + from idlelib.idle_test.htest import run run(_replace_dialog) diff --git a/lib/idlelib/SearchDialog.py b/lib/idlelib/SearchDialog.py index 77ef7b9a..765d53fe 100644 --- a/lib/idlelib/SearchDialog.py +++ b/lib/idlelib/SearchDialog.py @@ -4,6 +4,7 @@ from idlelib.SearchDialogBase import SearchDialogBase def _setup(text): + "Create or find the singleton SearchDialog instance." root = text._root() engine = SearchEngine.get(root) if not hasattr(engine, "_searchdialog"): @@ -11,13 +12,16 @@ def _setup(text): return engine._searchdialog def find(text): + "Handle the editor edit menu item and corresponding event." pat = text.get("sel.first", "sel.last") - return _setup(text).open(text,pat) + return _setup(text).open(text, pat) # Open is inherited from SDBase. def find_again(text): + "Handle the editor edit menu item and corresponding event." return _setup(text).find_again(text) def find_selection(text): + "Handle the editor edit menu item and corresponding event." return _setup(text).find_selection(text) class SearchDialog(SearchDialogBase): @@ -66,24 +70,28 @@ def find_selection(self, text): self.engine.setcookedpat(pat) return self.find_again(text) -def _search_dialog(parent): - root = Tk() - root.title("Test SearchDialog") + +def _search_dialog(parent): # htest # + '''Display search test box.''' + box = Toplevel(parent) + box.title("Test SearchDialog") width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) - root.geometry("+%d+%d"%(x, y + 150)) - text = Text(root) + box.geometry("+%d+%d"%(x, y + 150)) + text = Text(box, inactiveselectbackground='gray') text.pack() - text.insert("insert","This is a sample string.\n"*10) + text.insert("insert","This is a sample string.\n"*5) def show_find(): text.tag_add(SEL, "1.0", END) - s = _setup(text) - s.open(text) + _setup(text).open(text) text.tag_remove(SEL, "1.0", END) - button = Button(root, text="Search", command=show_find) + button = Button(box, text="Search (selection ignored)", command=show_find) button.pack() if __name__ == '__main__': + import unittest + unittest.main('idlelib.idle_test.test_searchdialog', + verbosity=2, exit=False) from idlelib.idle_test.htest import run run(_search_dialog) diff --git a/lib/idlelib/UndoDelegator.py b/lib/idlelib/UndoDelegator.py index 04c1cf5a..1c2502d8 100644 --- a/lib/idlelib/UndoDelegator.py +++ b/lib/idlelib/UndoDelegator.py @@ -336,30 +336,33 @@ def bump_depth(self, incr=1): self.depth = self.depth + incr return self.depth -def _undo_delegator(parent): + +def _undo_delegator(parent): # htest # + import re + import tkinter as tk from idlelib.Percolator import Percolator - root = Tk() - root.title("Test UndoDelegator") + undowin = tk.Toplevel() + undowin.title("Test UndoDelegator") width, height, x, y = list(map(int, re.split('[x+]', parent.geometry()))) - root.geometry("+%d+%d"%(x, y + 150)) + undowin.geometry("+%d+%d"%(x, y + 150)) - text = Text(root) - text.config(height=10) + text = Text(undowin, height=10) text.pack() text.focus_set() p = Percolator(text) d = UndoDelegator() p.insertfilter(d) - undo = Button(root, text="Undo", command=lambda:d.undo_event(None)) + undo = Button(undowin, text="Undo", command=lambda:d.undo_event(None)) undo.pack(side='left') - redo = Button(root, text="Redo", command=lambda:d.redo_event(None)) + redo = Button(undowin, text="Redo", command=lambda:d.redo_event(None)) redo.pack(side='left') - dump = Button(root, text="Dump", command=lambda:d.dump_event(None)) + dump = Button(undowin, text="Dump", command=lambda:d.dump_event(None)) dump.pack(side='left') - root.mainloop() - if __name__ == "__main__": + import unittest + unittest.main('idlelib.idle_test.test_undodelegator', verbosity=2, + exit=False) from idlelib.idle_test.htest import run run(_undo_delegator) diff --git a/lib/idlelib/WidgetRedirector.py b/lib/idlelib/WidgetRedirector.py index b3d7bfa3..b66be9e7 100644 --- a/lib/idlelib/WidgetRedirector.py +++ b/lib/idlelib/WidgetRedirector.py @@ -47,8 +47,9 @@ def __init__(self, widget): tk.createcommand(w, self.dispatch) def __repr__(self): - return "WidgetRedirector(%s<%s>)" % (self.widget.__class__.__name__, - self.widget._w) + return "%s(%s<%s>)" % (self.__class__.__name__, + self.widget.__class__.__name__, + self.widget._w) def close(self): "Unregister operations and revert redirection created by .__init__." @@ -67,7 +68,7 @@ def register(self, operation, function): '''Return OriginalCommand(operation) after registering function. Registration adds an operation: function pair to ._operations. - It also adds an widget function attribute that masks the tkinter + It also adds a widget function attribute that masks the tkinter class instance method. Method masking operates independently from command dispatch. @@ -142,7 +143,8 @@ def __init__(self, redir, operation): self.orig_and_operation = (redir.orig, operation) def __repr__(self): - return "OriginalCommand(%r, %r)" % (self.redir, self.operation) + return "%s(%r, %r)" % (self.__class__.__name__, + self.redir, self.operation) def __call__(self, *args): return self.tk_call(self.orig_and_operation + args) diff --git a/lib/idlelib/__init__.py b/lib/idlelib/__init__.py index 711f61bb..208ced13 100644 --- a/lib/idlelib/__init__.py +++ b/lib/idlelib/__init__.py @@ -6,3 +6,4 @@ The other files are private implementations. Their details are subject to change. See PEP 434 for more. Import them at your own risk. """ +testing = False # Set True by test.test_idle. diff --git a/lib/idlelib/aboutDialog.py b/lib/idlelib/aboutDialog.py index d876a971..a8f75d25 100644 --- a/lib/idlelib/aboutDialog.py +++ b/lib/idlelib/aboutDialog.py @@ -111,6 +111,7 @@ def CreateWidgets(self): command=self.ShowIDLECredits) idle_credits_b.pack(side=LEFT, padx=10, pady=10) + # License, et all, are of type _sitebuiltins._Printer def ShowLicense(self): self.display_printer_text('About - License', license) @@ -120,14 +121,16 @@ def ShowCopyright(self): def ShowPythonCredits(self): self.display_printer_text('About - Python Credits', credits) + # Encode CREDITS.txt to utf-8 for proper version of Loewis. + # Specify others as ascii until need utf-8, so catch errors. def ShowIDLECredits(self): - self.display_file_text('About - Credits', 'CREDITS.txt', 'iso-8859-1') + self.display_file_text('About - Credits', 'CREDITS.txt', 'utf-8') def ShowIDLEAbout(self): - self.display_file_text('About - Readme', 'README.txt') + self.display_file_text('About - Readme', 'README.txt', 'ascii') def ShowIDLENEWS(self): - self.display_file_text('About - NEWS', 'NEWS.txt') + self.display_file_text('About - NEWS', 'NEWS.txt', 'utf-8') def display_printer_text(self, title, printer): printer._Printer__setup() @@ -142,5 +145,7 @@ def Ok(self, event=None): self.destroy() if __name__ == '__main__': + import unittest + unittest.main('idlelib.idle_test.test_helpabout', verbosity=2, exit=False) from idlelib.idle_test.htest import run run(AboutDialog) diff --git a/lib/idlelib/configDialog.py b/lib/idlelib/configDialog.py index 9b16459d..5f5bd36f 100644 --- a/lib/idlelib/configDialog.py +++ b/lib/idlelib/configDialog.py @@ -483,6 +483,17 @@ def AttachVarCallbacks(self): self.autoSave.trace_variable('w', self.VarChanged_autoSave) self.encoding.trace_variable('w', self.VarChanged_encoding) + def remove_var_callbacks(self): + "Remove callbacks to prevent memory leaks." + for var in ( + self.fontSize, self.fontName, self.fontBold, + self.spaceNum, self.colour, self.builtinTheme, + self.customTheme, self.themeIsBuiltin, self.highlightTarget, + self.keyBinding, self.builtinKeys, self.customKeys, + self.keysAreBuiltin, self.winWidth, self.winHeight, + self.startupEdit, self.autoSave, self.encoding,): + var.trace_vdelete('w', var.trace_vinfo()[0][1]) + def VarChanged_font(self, *params): '''When one font attribute changes, save them all, as they are not independent from each other. In particular, when we are @@ -740,6 +751,7 @@ def DeleteCustomKeys(self): if not tkMessageBox.askyesno( 'Delete Key Set', delmsg % keySetName, parent=self): return + self.DeactivateCurrentConfig() #remove key set from config idleConf.userCfg['keys'].remove_section(keySetName) if keySetName in self.changedItems['keys']: @@ -758,7 +770,8 @@ def DeleteCustomKeys(self): self.keysAreBuiltin.set(idleConf.defaultCfg['main'].Get('Keys', 'default')) self.builtinKeys.set(idleConf.defaultCfg['main'].Get('Keys', 'name')) #user can't back out of these changes, they must be applied now - self.Apply() + self.SaveAllChangedConfigs() + self.ActivateConfigChanges() self.SetKeysType() def DeleteCustomTheme(self): @@ -767,6 +780,7 @@ def DeleteCustomTheme(self): if not tkMessageBox.askyesno( 'Delete Theme', delmsg % themeName, parent=self): return + self.DeactivateCurrentConfig() #remove theme from config idleConf.userCfg['highlight'].remove_section(themeName) if themeName in self.changedItems['highlight']: @@ -785,7 +799,8 @@ def DeleteCustomTheme(self): self.themeIsBuiltin.set(idleConf.defaultCfg['main'].Get('Theme', 'default')) self.builtinTheme.set(idleConf.defaultCfg['main'].Get('Theme', 'name')) #user can't back out of these changes, they must be applied now - self.Apply() + self.SaveAllChangedConfigs() + self.ActivateConfigChanges() self.SetThemeType() def GetColour(self): @@ -1196,7 +1211,7 @@ def CreatePageExtensions(self): All values are treated as text, and it is up to the user to supply reasonable values. The only exception to this are the 'enable*' options, - which are boolean, and can be toggled with an True/False button. + which are boolean, and can be toggled with a True/False button. """ parent = self.parent frame = self.tabPages.pages['Extensions'].frame diff --git a/lib/idlelib/configHandler.py b/lib/idlelib/configHandler.py index 531efb4b..8954488f 100644 --- a/lib/idlelib/configHandler.py +++ b/lib/idlelib/configHandler.py @@ -720,7 +720,7 @@ def GetFont(self, root, configType, section): actualFont = Font.actual(f) family = actualFont['family'] size = actualFont['size'] - if size < 0: + if size <= 0: size = 10 # if font in pixels, ignore actual size bold = actualFont['weight']=='bold' return (family, size, 'bold' if bold else 'normal') @@ -740,21 +740,32 @@ def SaveUserCfgFiles(self): idleConf = IdleConf() # TODO Revise test output, write expanded unittest -### module test +# if __name__ == '__main__': + from zlib import crc32 + line, crc = 0, 0 + + def sprint(obj): + global line, crc + txt = str(obj) + line += 1 + crc = crc32(txt.encode(encoding='utf-8'), crc) + print(txt) + #print('***', line, crc, '***') # uncomment for diagnosis + def dumpCfg(cfg): - print('\n', cfg, '\n') - for key in cfg: + print('\n', cfg, '\n') # has variable '0xnnnnnnnn' addresses + for key in sorted(cfg.keys()): sections = cfg[key].sections() - print(key) - print(sections) + sprint(key) + sprint(sections) for section in sections: options = cfg[key].options(section) - print(section) - print(options) + sprint(section) + sprint(options) for option in options: - print(option, '=', cfg[key].Get(section, option)) + sprint(option + ' = ' + cfg[key].Get(section, option)) + dumpCfg(idleConf.defaultCfg) dumpCfg(idleConf.userCfg) - print(idleConf.userCfg['main'].Get('Theme', 'name')) - #print idleConf.userCfg['highlight'].GetDefHighlight('Foo','normal') + print('\nlines = ', line, ', crc = ', crc, sep='') diff --git a/lib/idlelib/configHelpSourceEdit.py b/lib/idlelib/configHelpSourceEdit.py index 242b08db..cde8118f 100644 --- a/lib/idlelib/configHelpSourceEdit.py +++ b/lib/idlelib/configHelpSourceEdit.py @@ -23,10 +23,10 @@ def __init__(self, parent, title, menuItem='', filePath='', _htest=False): self.title(title) self.transient(parent) self.grab_set() - self.protocol("WM_DELETE_WINDOW", self.Cancel) + self.protocol("WM_DELETE_WINDOW", self.cancel) self.parent = parent self.result = None - self.CreateWidgets() + self.create_widgets() self.menu.set(menuItem) self.path.set(filePath) self.withdraw() #hide while setting geometry @@ -41,10 +41,10 @@ def __init__(self, parent, title, menuItem='', filePath='', _htest=False): ((parent.winfo_height()/2 - self.winfo_reqheight()/2) if not _htest else 150))) self.deiconify() #geometry set, unhide - self.bind('', self.Ok) + self.bind('', self.ok) self.wait_window() - def CreateWidgets(self): + def create_widgets(self): self.menu = StringVar(self) self.path = StringVar(self) self.fontSize = StringVar(self) @@ -65,18 +65,18 @@ def CreateWidgets(self): labelPath.pack(anchor=W, padx=5, pady=3) self.entryPath.pack(anchor=W, padx=5, pady=3) browseButton = Button(self.frameMain, text='Browse', width=8, - command=self.browseFile) + command=self.browse_file) browseButton.pack(pady=3) frameButtons = Frame(self) frameButtons.pack(side=BOTTOM, fill=X) self.buttonOk = Button(frameButtons, text='OK', - width=8, default=ACTIVE, command=self.Ok) + width=8, default=ACTIVE, command=self.ok) self.buttonOk.grid(row=0, column=0, padx=5,pady=5) self.buttonCancel = Button(frameButtons, text='Cancel', - width=8, command=self.Cancel) + width=8, command=self.cancel) self.buttonCancel.grid(row=0, column=1, padx=5, pady=5) - def browseFile(self): + def browse_file(self): filetypes = [ ("HTML Files", "*.htm *.html", "TEXT"), ("PDF Files", "*.pdf", "TEXT"), @@ -99,9 +99,9 @@ def browseFile(self): if file: self.path.set(file) - def MenuOk(self): + def menu_ok(self): "Simple validity check for a sensible menu item name" - menuOk = True + menu_ok = True menu = self.menu.get() menu.strip() if not menu: @@ -109,19 +109,19 @@ def MenuOk(self): message='No menu item specified', parent=self) self.entryMenu.focus_set() - menuOk = False + menu_ok = False elif len(menu) > 30: tkMessageBox.showerror(title='Menu Item Error', message='Menu item too long:' '\nLimit 30 characters.', parent=self) self.entryMenu.focus_set() - menuOk = False - return menuOk + menu_ok = False + return menu_ok - def PathOk(self): + def path_ok(self): "Simple validity check for menu file path" - pathOk = True + path_ok = True path = self.path.get() path.strip() if not path: #no path specified @@ -129,7 +129,7 @@ def PathOk(self): message='No help file path specified.', parent=self) self.entryPath.focus_set() - pathOk = False + path_ok = False elif path.startswith(('www.', 'http')): pass else: @@ -140,16 +140,16 @@ def PathOk(self): message='Help file path does not exist.', parent=self) self.entryPath.focus_set() - pathOk = False - return pathOk + path_ok = False + return path_ok - def Ok(self, event=None): - if self.MenuOk() and self.PathOk(): + def ok(self, event=None): + if self.menu_ok() and self.path_ok(): self.result = (self.menu.get().strip(), self.path.get().strip()) if sys.platform == 'darwin': path = self.result[1] - if path.startswith(('www', 'file:', 'http:')): + if path.startswith(('www', 'file:', 'http:', 'https:')): pass else: # Mac Safari insists on using the URI form for local files @@ -157,10 +157,14 @@ def Ok(self, event=None): self.result[1] = "file://" + path self.destroy() - def Cancel(self, event=None): + def cancel(self, event=None): self.result = None self.destroy() if __name__ == '__main__': + import unittest + unittest.main('idlelib.idle_test.test_config_help', + verbosity=2, exit=False) + from idlelib.idle_test.htest import run run(GetHelpSourceDialog) diff --git a/lib/idlelib/help.html b/lib/idlelib/help.html index 2189fd4c..f10cd345 100644 --- a/lib/idlelib/help.html +++ b/lib/idlelib/help.html @@ -6,7 +6,7 @@ - 25.5. IDLE — Python 3.4.3 documentation + 25.5. IDLE — Python 3.5.2 documentation @@ -14,7 +14,7 @@ - + @@ -40,8 +40,8 @@ - -